mirror of
https://github.com/servo/servo.git
synced 2025-08-09 23:45:35 +01:00
Rewrite the layout tree to use tandem flow/box trees. Flows are
responsible for determining layout positioning based on formatting context (block, inline, absolute, etc), while boxes are relatively dumb, responsible only for reporting content-specific metrics to the coordinating flow context, and creating display lists of oneself. This design is experimental and may be axed in favor of a traditional, single-tree layout data structure if it doesn't work out.
This commit is contained in:
parent
4e3e5a879d
commit
1095c9e4ff
24 changed files with 1205 additions and 832 deletions
|
@ -1,11 +1,11 @@
|
|||
#[doc="Applies the appropriate CSS style to boxes."]
|
||||
|
||||
use gfx::geometry::au_to_px;
|
||||
use layout::base::{Box, BTree, NTree, LayoutData, SpecifiedStyle, ImageHolder,
|
||||
BlockBox, InlineBox, IntrinsicBox, TextBox};
|
||||
use layout::traverse_parallel::{top_down_traversal};
|
||||
use std::net::url::Url;
|
||||
use au = gfx::geometry;
|
||||
use layout::base::{Box, SpecifiedStyle};
|
||||
use layout::traverse_parallel::top_down_traversal;
|
||||
use image::ImageHolder;
|
||||
use resource::image_cache_task::ImageCacheTask;
|
||||
use std::net::url::Url;
|
||||
|
||||
use css::values::*;
|
||||
|
||||
|
@ -37,7 +37,7 @@ struct StyleApplicator {
|
|||
reflow: fn~(),
|
||||
}
|
||||
|
||||
|
||||
// TODO: normalize this into a normal preorder tree traversal function
|
||||
fn apply_style(box: @Box, doc_url: &Url, image_cache_task: ImageCacheTask, reflow: fn~()) {
|
||||
let applicator = StyleApplicator {
|
||||
box: box,
|
||||
|
@ -61,24 +61,19 @@ fn inheritance_wrapper(box : @Box, doc_url: &Url, image_cache_task: ImageCacheTa
|
|||
reflow: reflow
|
||||
};
|
||||
applicator.apply_style();
|
||||
inherit_fontsize(box);
|
||||
inherit_height(box);
|
||||
inherit_width(box);
|
||||
}
|
||||
|
||||
/* Turns symbolic (abs, rel) and relative font sizes into absolute lengths */
|
||||
fn inherit_fontsize(box : @Box) {
|
||||
// TODO: complete this
|
||||
return
|
||||
}
|
||||
/*
|
||||
fn resolve_fontsize(box : @Box) {
|
||||
// TODO: complete this
|
||||
return
|
||||
}
|
||||
|
||||
#[doc="Compute the specified height of a layout box based on it's css specification and its
|
||||
parent's height."]
|
||||
fn inherit_height(box : @Box) {
|
||||
let style = box.node.get_specified_style();
|
||||
fn resolve_height(box : @Box) -> au {
|
||||
let style = box.node.get_style();
|
||||
let inherit_val = match box.tree.parent {
|
||||
None => style.height.initial(),
|
||||
Some(node) => node.appearance.height
|
||||
None => au(0),
|
||||
Some(parent) => parent.data.computed_size.height
|
||||
};
|
||||
|
||||
box.appearance.height = match style.height {
|
||||
|
@ -91,9 +86,7 @@ fn inherit_height(box : @Box) {
|
|||
}
|
||||
}
|
||||
|
||||
#[doc="Compute the specified width of a layout box based on it's css specification and its
|
||||
parent's width."]
|
||||
fn inherit_width(box : @Box) {
|
||||
fn resolve_width(box : @Box) {
|
||||
let style = box.node.get_specified_style();
|
||||
let inherit_val = match box.tree.parent {
|
||||
None => style.height.initial(),
|
||||
|
@ -108,7 +101,7 @@ fn inherit_width(box : @Box) {
|
|||
BoxLength(Em(n)) => BoxLength(Px(n * box.appearance.font_size.abs()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
impl StyleApplicator {
|
||||
fn apply_css_style() {
|
||||
|
@ -141,7 +134,7 @@ impl StyleApplicator {
|
|||
// FIXME: Some sort of BASE HREF support!
|
||||
// FIXME: Parse URLs!
|
||||
let new_url = make_url(option::unwrap(url), Some(copy *self.doc_url));
|
||||
self.box.appearance.background_image = Some(ImageHolder(new_url, self.image_cache_task, self.reflow))
|
||||
self.box.data.background_image = Some(ImageHolder(new_url, self.image_cache_task, self.reflow))
|
||||
};
|
||||
}
|
||||
_ => { /* Ignore. */ }
|
||||
|
@ -155,57 +148,5 @@ impl StyleApplicator {
|
|||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use dom::base::{Attr, HTMLDivElement, HTMLHeadElement, HTMLImageElement, ElementData};
|
||||
use dom::base::{NodeScope, Node, UnknownElement};
|
||||
use dvec::DVec;
|
||||
|
||||
#[allow(non_implicitly_copyable_typarams)]
|
||||
fn new_node(scope: NodeScope, -name: ~str) -> Node {
|
||||
let elmt = ElementData(name, ~HTMLDivElement);
|
||||
return scope.new_node(dom::base::Element(elmt));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore(reason = "busted")]
|
||||
fn test_percent_height() {
|
||||
let scope = NodeScope();
|
||||
|
||||
let parent = new_node(scope, ~"parent");
|
||||
let child = new_node(scope, ~"child");
|
||||
let child2 = new_node(scope, ~"child");
|
||||
let g1 = new_node(scope, ~"gchild");
|
||||
let g2 = new_node(scope, ~"gchild");
|
||||
|
||||
scope.add_child(parent, child);
|
||||
scope.add_child(parent, child2);
|
||||
scope.add_child(child, g1);
|
||||
scope.add_child(child, g2);
|
||||
let _handles = parent.initialize_style_for_subtree();
|
||||
|
||||
// TODO: use helper methods to create test values
|
||||
let px100 = BoxLength(Px(100.0));
|
||||
let px10 = BoxLength(Px(10.0));
|
||||
let px50 = BoxLength(Px(50.0));
|
||||
let pc50 = BoxPercent(50.0);
|
||||
|
||||
do parent.aux |aux| { aux.specified_style.height = Specified(px100); }
|
||||
do child.aux |aux| { aux.specified_style.height = Specified(BoxAuto); }
|
||||
do child2.aux |aux| { aux.specified_style.height = Specified(pc50); }
|
||||
do g1.aux |aux| { aux.specified_style.height = Specified(pc50); }
|
||||
do g2.aux |aux| { aux.specified_style.height = Specified(px10); }
|
||||
|
||||
let parent_box = parent.construct_boxes();
|
||||
let child_box = parent_box.get().tree.first_child.get();
|
||||
let child2_box = parent_box.get().tree.last_child.get();
|
||||
let g1_box = child_box.tree.first_child.get();
|
||||
let g2_box = child_box.tree.last_child.get();
|
||||
|
||||
top_down_traversal(parent_box.get(), inherit_height);
|
||||
|
||||
assert parent_box.get().appearance.height == px100;
|
||||
assert child_box.appearance.height == BoxAuto;
|
||||
assert child2_box.appearance.height == px50;
|
||||
assert g1_box.appearance.height == BoxAuto;
|
||||
assert g2_box.appearance.height == px10;
|
||||
}
|
||||
}
|
||||
/* TODO: rewrite once cascade and resolve written. */
|
||||
}
|
|
@ -167,12 +167,12 @@ impl Node : PrivStyleMethods {
|
|||
fn update_style(decl : StyleDeclaration) {
|
||||
self.aux(|layout| {
|
||||
match decl {
|
||||
BackgroundColor(col) => layout.specified_style.background_color = col,
|
||||
Display(dis) => layout.specified_style.display_type = dis,
|
||||
FontSize(size) => layout.specified_style.font_size = size,
|
||||
Height(size) => layout.specified_style.height = size,
|
||||
Color(col) => layout.specified_style.text_color = col,
|
||||
Width(size) => layout.specified_style.width = size
|
||||
BackgroundColor(col) => layout.style.background_color = col,
|
||||
Display(dis) => layout.style.display_type = dis,
|
||||
FontSize(size) => layout.style.font_size = size,
|
||||
Height(size) => layout.style.height = size,
|
||||
Color(col) => layout.style.text_color = col,
|
||||
Width(size) => layout.style.width = size
|
||||
};
|
||||
})
|
||||
}
|
||||
|
@ -202,7 +202,7 @@ impl Node : MatchingMethods {
|
|||
}
|
||||
}
|
||||
|
||||
self.aux(|a| #debug["Changed the style to: %?", copy *a.specified_style]);
|
||||
self.aux(|a| #debug["Changed the style to: %?", copy *a.style]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,9 +6,10 @@ use css::values::*;
|
|||
use css::values::Stylesheet;
|
||||
use dom::base::{HTMLDivElement, HTMLHeadElement, HTMLImageElement, UnknownElement, HTMLScriptElement};
|
||||
use dom::base::{Comment, Doctype, Element, Node, NodeKind, Text};
|
||||
use dom::base::{LayoutData};
|
||||
use util::color::{Color, rgb};
|
||||
use util::color::css_colors::{white, black};
|
||||
use layout::base::{LayoutData, NTree};
|
||||
use dom::base::NodeTree;
|
||||
|
||||
type SpecifiedStyle = {mut background_color : CSSValue<CSSBackgroundColor>,
|
||||
mut display_type : CSSValue<CSSDisplay>,
|
||||
|
@ -97,7 +98,7 @@ impl Node : StylePriv {
|
|||
if !self.has_aux() {
|
||||
let node_kind = self.read(|n| copy *n.kind);
|
||||
let the_layout_data = @LayoutData({
|
||||
mut specified_style : ~empty_style_for_node_kind(node_kind),
|
||||
mut style : ~empty_style_for_node_kind(node_kind),
|
||||
mut box : None
|
||||
});
|
||||
|
||||
|
@ -112,7 +113,7 @@ impl Node : StylePriv {
|
|||
|
||||
trait StyleMethods {
|
||||
fn initialize_style_for_subtree() -> ~[@LayoutData];
|
||||
fn get_specified_style() -> SpecifiedStyle;
|
||||
fn style() -> SpecifiedStyle;
|
||||
fn recompute_style_for_subtree(styles : ARC<Stylesheet>);
|
||||
}
|
||||
|
||||
|
@ -121,7 +122,7 @@ impl Node : StyleMethods {
|
|||
fn initialize_style_for_subtree() -> ~[@LayoutData] {
|
||||
let mut handles = self.initialize_style();
|
||||
|
||||
for NTree.each_child(self) |kid| {
|
||||
for NodeTree.each_child(self) |kid| {
|
||||
handles += kid.initialize_style_for_subtree();
|
||||
}
|
||||
|
||||
|
@ -134,11 +135,11 @@ impl Node : StyleMethods {
|
|||
|
||||
TODO: Return a safe reference; don't copy.
|
||||
"]
|
||||
fn get_specified_style() -> SpecifiedStyle {
|
||||
fn style() -> SpecifiedStyle {
|
||||
if !self.has_aux() {
|
||||
fail ~"get_specified_style() called on a node without a style!";
|
||||
fail ~"get_style() called on a node without a style!";
|
||||
}
|
||||
return copy *self.aux(|x| copy x).specified_style;
|
||||
return copy *self.aux(|x| copy x).style;
|
||||
}
|
||||
|
||||
#[doc="
|
||||
|
@ -152,7 +153,7 @@ impl Node : StyleMethods {
|
|||
let mut i = 0u;
|
||||
|
||||
// Compute the styles of each of our children in parallel
|
||||
for NTree.each_child(self) |kid| {
|
||||
for NodeTree.each_child(self) |kid| {
|
||||
i = i + 1u;
|
||||
let new_styles = clone(&styles);
|
||||
|
||||
|
|
|
@ -1,21 +1,22 @@
|
|||
#[doc="The core DOM types. Defines the basic DOM hierarchy as well as all the HTML elements."]
|
||||
|
||||
use gfx::geometry::au;
|
||||
use geom::size::Size2D;
|
||||
use layout::base::LayoutData;
|
||||
use util::tree;
|
||||
use js::rust::{bare_compartment, compartment, methods};
|
||||
use js::jsapi::{JSClass, JSObject, JSPropertySpec, JSContext, jsid, jsval, JSBool};
|
||||
use js::{JSPROP_ENUMERATE, JSPROP_SHARED};
|
||||
use js::crust::*;
|
||||
use js::glue::bindgen::RUST_OBJECT_TO_JSVAL;
|
||||
use dvec::DVec;
|
||||
use ptr::null;
|
||||
use dom::bindings;
|
||||
use std::arc::ARC;
|
||||
use css::values::Stylesheet;
|
||||
use comm::{Port, Chan};
|
||||
use content::content_task::{ControlMsg, Timer};
|
||||
use css::styles::SpecifiedStyle;
|
||||
use css::values::Stylesheet;
|
||||
use dom::bindings;
|
||||
use dvec::DVec;
|
||||
use geom::size::Size2D;
|
||||
use gfx::geometry::au;
|
||||
use js::crust::*;
|
||||
use js::glue::bindgen::RUST_OBJECT_TO_JSVAL;
|
||||
use js::jsapi::{JSClass, JSObject, JSPropertySpec, JSContext, jsid, jsval, JSBool};
|
||||
use js::rust::{bare_compartment, compartment, methods};
|
||||
use js::{JSPROP_ENUMERATE, JSPROP_SHARED};
|
||||
use layout::base::Box;
|
||||
use ptr::null;
|
||||
use std::arc::ARC;
|
||||
use util::tree;
|
||||
|
||||
enum TimerControlMsg {
|
||||
Fire(~dom::bindings::window::TimerData),
|
||||
|
@ -66,6 +67,20 @@ enum NodeData = {
|
|||
kind: ~NodeKind,
|
||||
};
|
||||
|
||||
|
||||
/* The tree holding Nodes (read-only) */
|
||||
enum NodeTree { NodeTree }
|
||||
|
||||
impl NodeTree : tree::ReadMethods<Node> {
|
||||
fn each_child(node: Node, f: fn(Node) -> bool) {
|
||||
tree::each_child(self, node, f)
|
||||
}
|
||||
|
||||
fn with_tree_fields<R>(&&n: Node, f: fn(tree::Tree<Node>) -> R) -> R {
|
||||
n.read(|n| f(n.tree))
|
||||
}
|
||||
}
|
||||
|
||||
enum NodeKind {
|
||||
Doctype(DoctypeData),
|
||||
Comment(~str),
|
||||
|
@ -148,10 +163,16 @@ enum ElementKind {
|
|||
HTMLScriptElement
|
||||
}
|
||||
|
||||
#[doc="
|
||||
The rd_aux data is a (weak) pointer to the layout data, which contains the CSS info as well as
|
||||
the primary box. Note that there may be multiple boxes per DOM node.
|
||||
"]
|
||||
|
||||
/** The RCU rd_aux data is a (weak) pointer to the layout data,
|
||||
defined by this `LayoutData` enum. It contains the CSS style object
|
||||
as well as the primary `Box`.
|
||||
|
||||
Note that there may be multiple boxes per DOM node. */
|
||||
enum LayoutData = {
|
||||
mut style: ~SpecifiedStyle,
|
||||
mut box: Option<@Box>
|
||||
};
|
||||
|
||||
type Node = rcu::Handle<NodeData, LayoutData>;
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use au = gfx::geometry;
|
||||
use js::rust::{bare_compartment, methods, jsobj};
|
||||
use js::{JS_ARGV, JSCLASS_HAS_RESERVED_SLOTS, JSPROP_ENUMERATE, JSPROP_SHARED, JSVAL_NULL,
|
||||
JS_THIS_OBJECT, JS_SET_RVAL, JSPROP_NATIVE_ACCESSORS};
|
||||
|
@ -17,7 +18,6 @@ use ptr::null;
|
|||
use node::unwrap;
|
||||
use dom::base::{HTMLImageElement, HTMLScriptElement, HTMLHeadElement, HTMLDivElement,
|
||||
UnknownElement};
|
||||
use gfx::geometry::{au_to_px, px_to_au};
|
||||
|
||||
extern fn finalize(_fop: *JSFreeOp, obj: *JSObject) {
|
||||
#debug("element finalize!");
|
||||
|
@ -81,7 +81,7 @@ extern fn HTMLImageElement_getWidth(cx: *JSContext, _argc: c_uint, vp: *mut jsva
|
|||
}
|
||||
});
|
||||
*vp = RUST_INT_TO_JSVAL(
|
||||
(au_to_px(width) & (i32::max_value as int)) as libc::c_int);
|
||||
(au::to_px(width) & (i32::max_value as int)) as libc::c_int);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
@ -99,7 +99,7 @@ extern fn HTMLImageElement_setWidth(cx: *JSContext, _argc: c_uint, vp: *mut jsva
|
|||
match ed.kind {
|
||||
~HTMLImageElement(img) => {
|
||||
let arg = ptr::offset(JS_ARGV(cx, unsafe::reinterpret_cast(&vp)), 0);
|
||||
img.size.width = px_to_au(RUST_JSVAL_TO_INT(*arg) as int)
|
||||
img.size.width = au::from_px(RUST_JSVAL_TO_INT(*arg) as int)
|
||||
},
|
||||
_ => fail ~"why is this not an image element?"
|
||||
}
|
||||
|
|
|
@ -14,52 +14,49 @@ impl au : Num {
|
|||
pure fn neg() -> au { au(-*self) }
|
||||
|
||||
pure fn to_int() -> int { *self as int }
|
||||
|
||||
static pure fn from_int(n: int) -> au {
|
||||
au((n & (i32::max_value as int)) as i32)
|
||||
}
|
||||
}
|
||||
|
||||
impl au : cmp::Eq {
|
||||
pure fn eq(&&other: au) -> bool {
|
||||
*self == *other
|
||||
}
|
||||
pure fn ne(&&other: au) -> bool {
|
||||
*self != *other
|
||||
}
|
||||
impl au : cmp::Ord {
|
||||
pure fn lt(&&other: au) -> bool { *self < *other }
|
||||
pure fn le(&&other: au) -> bool { *self <= *other }
|
||||
pure fn ge(&&other: au) -> bool { *self >= *other }
|
||||
pure fn gt(&&other: au) -> bool { *self > *other }
|
||||
}
|
||||
|
||||
impl au : cmp::Ord {
|
||||
pure fn lt(&&other: au) -> bool {
|
||||
*self < *other
|
||||
}
|
||||
pure fn le(&&other: au) -> bool {
|
||||
*self <= *other
|
||||
}
|
||||
pure fn ge(&&other: au) -> bool {
|
||||
*self >= *other
|
||||
}
|
||||
pure fn gt(&&other: au) -> bool {
|
||||
*self > *other
|
||||
}
|
||||
impl au : cmp::Eq {
|
||||
pure fn eq(&&other: au) -> bool { *self == *other }
|
||||
pure fn ne(&&other: au) -> bool { *self != *other }
|
||||
}
|
||||
|
||||
pure fn min(x: au, y: au) -> au { if x < y { x } else { y } }
|
||||
pure fn max(x: au, y: au) -> au { if x > y { x } else { y } }
|
||||
|
||||
fn box<A:Copy Num>(x: A, y: A, w: A, h: A) -> Rect<A> {
|
||||
Rect(Point2D(x, y), Size2D(w, h))
|
||||
}
|
||||
|
||||
fn zero_rect_au() -> Rect<au> {
|
||||
fn zero_rect() -> Rect<au> {
|
||||
let z = au(0);
|
||||
Rect(Point2D(z, z), Size2D(z, z))
|
||||
}
|
||||
|
||||
fn zero_size_au() -> Size2D<au> {
|
||||
|
||||
fn zero_point() -> Point2D<au> {
|
||||
Point2D(au(0), au(0))
|
||||
}
|
||||
|
||||
fn zero_size() -> Size2D<au> {
|
||||
Size2D(au(0), au(0))
|
||||
}
|
||||
|
||||
pure fn px_to_au(i: int) -> au {
|
||||
pure fn from_px(i: int) -> au {
|
||||
from_int(i * 60)
|
||||
}
|
||||
|
||||
pure fn au_to_px(au: au) -> int {
|
||||
pure fn to_px(au: au) -> int {
|
||||
(*au / 60) as int
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use au = geometry;
|
||||
use au::au;
|
||||
use platform::osmain;
|
||||
use geometry::*;
|
||||
use comm::*;
|
||||
use image::base::Image;
|
||||
use dl = display_list;
|
||||
|
@ -88,8 +89,8 @@ trait ToAzureRect {
|
|||
|
||||
impl Rect<au> : ToAzureRect {
|
||||
fn to_azure_rect() -> Rect<AzFloat> {
|
||||
Rect(Point2D(au_to_px(self.origin.x) as AzFloat, au_to_px(self.origin.y) as AzFloat),
|
||||
Size2D(au_to_px(self.size.width) as AzFloat, au_to_px(self.size.height) as AzFloat))
|
||||
Rect(Point2D(au::to_px(self.origin.x) as AzFloat, au::to_px(self.origin.y) as AzFloat),
|
||||
Size2D(au::to_px(self.size.width) as AzFloat, au::to_px(self.size.height) as AzFloat))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -167,8 +168,8 @@ pub fn draw_glyphs(draw_target: &DrawTarget, bounds: Rect<au>, text_run: &GlyphR
|
|||
let azglyph: AzGlyph = {
|
||||
mIndex: glyph.index as uint32_t,
|
||||
mPosition: {
|
||||
x: au_to_px(origin.x.add(glyph.pos.offset.x)) as AzFloat,
|
||||
y: au_to_px(origin.y.add(glyph.pos.offset.y)) as AzFloat
|
||||
x: au::to_px(origin.x.add(glyph.pos.offset.x)) as AzFloat,
|
||||
y: au::to_px(origin.y.add(glyph.pos.offset.y)) as AzFloat
|
||||
}
|
||||
};
|
||||
origin = Point2D(origin.x.add(glyph.pos.advance.x),
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
#[doc="Constructs a DOM tree from an incoming token stream."]
|
||||
|
||||
use au = gfx::geometry;
|
||||
use au::au;
|
||||
use dom::base::{Attr, Element, ElementData, ElementKind, HTMLDivElement, HTMLHeadElement,
|
||||
HTMLScriptElement};
|
||||
use dom::base::{HTMLImageElement, Node, NodeScope, Text, UnknownElement};
|
||||
use geom::size::Size2D;
|
||||
use gfx::geometry;
|
||||
use gfx::geometry::au;
|
||||
use html::lexer;
|
||||
use html::lexer::Token;
|
||||
use css::values::Stylesheet;
|
||||
|
@ -37,7 +37,7 @@ fn link_up_attribute(scope: NodeScope, node: Node, -key: ~str, -value: ~str) {
|
|||
None => {
|
||||
// Drop on the floor.
|
||||
}
|
||||
Some(s) => { img.size.width = geometry::px_to_au(s); }
|
||||
Some(s) => { img.size.width = au::from_px(s); }
|
||||
}
|
||||
}
|
||||
HTMLImageElement(img) if key == ~"height" => {
|
||||
|
@ -46,7 +46,7 @@ fn link_up_attribute(scope: NodeScope, node: Node, -key: ~str, -value: ~str) {
|
|||
// Drop on the floor.
|
||||
}
|
||||
Some(s) => {
|
||||
img.size.height = geometry::px_to_au(s);
|
||||
img.size.height = au::from_px(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -68,8 +68,8 @@ fn build_element_kind(tag_name: ~str) -> ~ElementKind {
|
|||
match tag_name {
|
||||
~"div" => ~HTMLDivElement,
|
||||
~"img" => {
|
||||
~HTMLImageElement({ mut size: Size2D(geometry::px_to_au(100),
|
||||
geometry::px_to_au(100))
|
||||
~HTMLImageElement({ mut size: Size2D(au::from_px(100),
|
||||
au::from_px(100))
|
||||
})
|
||||
}
|
||||
~"script" => ~HTMLScriptElement,
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
use au = gfx::geometry;
|
||||
use dom::base::{Attr, Comment, Doctype, DoctypeData, Element, ElementData, ElementKind};
|
||||
use dom::base::{HTMLDivElement, HTMLHeadElement, HTMLImageElement, HTMLScriptElement};
|
||||
use dom::base::{Node, NodeScope, Text, UnknownElement};
|
||||
use css::values::Stylesheet;
|
||||
use geom::size::Size2D;
|
||||
use gfx::geometry::px_to_au;
|
||||
use html::dom_builder::CSSMessage;
|
||||
use resource::resource_task::{Done, Load, Payload, ResourceTask};
|
||||
use CSSExitMessage = html::dom_builder::Exit;
|
||||
|
@ -118,7 +118,7 @@ fn build_element_kind(tag_name: &str) -> ~ElementKind {
|
|||
if tag_name == "div" {
|
||||
~HTMLDivElement
|
||||
} else if tag_name == "img" {
|
||||
~HTMLImageElement({ mut size: Size2D(px_to_au(100), px_to_au(100)) })
|
||||
~HTMLImageElement({ mut size: Size2D(au::from_px(100), au::from_px(100)) })
|
||||
} else if tag_name == "script" {
|
||||
~HTMLScriptElement
|
||||
} else if tag_name == "head" {
|
||||
|
@ -187,13 +187,13 @@ fn parse_html(scope: NodeScope, url: Url, resource_task: ResourceTask) -> HtmlPa
|
|||
HTMLImageElement(img) if attribute.name == "width" => {
|
||||
match int::from_str(from_slice(attribute.value)) {
|
||||
None => {} // Drop on the floor.
|
||||
Some(s) => img.size.width = px_to_au(s)
|
||||
Some(s) => img.size.width = au::from_px(s)
|
||||
}
|
||||
}
|
||||
HTMLImageElement(img) if attribute.name == "height" => {
|
||||
match int::from_str(from_slice(attribute.value)) {
|
||||
None => {} // Drop on the floor.
|
||||
Some(s) => img.size.height = px_to_au(s)
|
||||
Some(s) => img.size.height = au::from_px(s)
|
||||
}
|
||||
}
|
||||
HTMLDivElement | HTMLImageElement(*) | HTMLHeadElement |
|
||||
|
|
9
src/servo/image.rs
Normal file
9
src/servo/image.rs
Normal file
|
@ -0,0 +1,9 @@
|
|||
/* This file exists just to make it easier to import things inside of
|
||||
./images/ without specifying the file they came out of imports.
|
||||
|
||||
Note that you still must define each of the files as a module in
|
||||
servo.rc. This is not ideal and may be changed in the future. */
|
||||
|
||||
pub use holder::ImageHolder;
|
||||
pub use base::Image;
|
||||
|
92
src/servo/image/holder.rs
Normal file
92
src/servo/image/holder.rs
Normal file
|
@ -0,0 +1,92 @@
|
|||
use std::net::url::Url;
|
||||
use std::arc::{ARC, clone};
|
||||
use resource::image_cache_task::ImageCacheTask;
|
||||
use resource::image_cache_task;
|
||||
|
||||
/** A struct to store image data. The image will be loaded once, the
|
||||
first time it is requested, and an arc will be stored. Clones of
|
||||
this arc are given out on demand.
|
||||
*/
|
||||
pub struct ImageHolder {
|
||||
// Invariant: at least one of url and image is not none, except
|
||||
// occasionally while get_image is being called
|
||||
mut url : Option<Url>,
|
||||
mut image : Option<ARC<~Image>>,
|
||||
image_cache_task: ImageCacheTask,
|
||||
reflow_cb: fn~(),
|
||||
|
||||
}
|
||||
|
||||
fn ImageHolder(-url : Url, image_cache_task: ImageCacheTask, cb: fn~()) -> ImageHolder {
|
||||
let holder = ImageHolder {
|
||||
url : Some(copy url),
|
||||
image : None,
|
||||
image_cache_task : image_cache_task,
|
||||
reflow_cb : copy cb,
|
||||
};
|
||||
|
||||
// Tell the image cache we're going to be interested in this url
|
||||
// FIXME: These two messages must be sent to prep an image for use
|
||||
// but they are intended to be spread out in time. Ideally prefetch
|
||||
// should be done as early as possible and decode only once we
|
||||
// are sure that the image will be used.
|
||||
image_cache_task.send(image_cache_task::Prefetch(copy url));
|
||||
image_cache_task.send(image_cache_task::Decode(move url));
|
||||
|
||||
holder
|
||||
}
|
||||
|
||||
impl ImageHolder {
|
||||
// This function should not be called by two tasks at the same time
|
||||
fn get_image() -> Option<ARC<~Image>> {
|
||||
// If this is the first time we've called this function, load
|
||||
// the image and store it for the future
|
||||
if self.image.is_none() {
|
||||
assert self.url.is_some();
|
||||
|
||||
let mut temp = None;
|
||||
temp <-> self.url;
|
||||
let url = option::unwrap(temp);
|
||||
|
||||
let response_port = Port();
|
||||
self.image_cache_task.send(image_cache_task::GetImage(copy url, response_port.chan()));
|
||||
self.image = match response_port.recv() {
|
||||
image_cache_task::ImageReady(image) => Some(clone(&image)),
|
||||
image_cache_task::ImageNotReady => {
|
||||
// Need to reflow when the image is available
|
||||
let image_cache_task = self.image_cache_task;
|
||||
let reflow = copy self.reflow_cb;
|
||||
do task::spawn |copy url, move reflow| {
|
||||
let response_port = Port();
|
||||
image_cache_task.send(image_cache_task::WaitForImage(copy url, response_port.chan()));
|
||||
match response_port.recv() {
|
||||
image_cache_task::ImageReady(*) => reflow(),
|
||||
image_cache_task::ImageNotReady => fail /*not possible*/,
|
||||
image_cache_task::ImageFailed => ()
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
image_cache_task::ImageFailed => {
|
||||
#info("image was not ready for %s", url.to_str());
|
||||
// FIXME: Need to schedule another layout when the image is ready
|
||||
None
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if self.image.is_some() {
|
||||
// Temporarily swap out the arc of the image so we can clone
|
||||
// it without breaking purity, then put it back and return the
|
||||
// clone. This is not threadsafe.
|
||||
let mut temp = None;
|
||||
temp <-> self.image;
|
||||
let im_arc = option::unwrap(temp);
|
||||
self.image = Some(clone(&im_arc));
|
||||
|
||||
return Some(im_arc);
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,58 +1,184 @@
|
|||
#[doc="Fundamental layout structures and algorithms."]
|
||||
/* Fundamental layout structures and algorithms. */
|
||||
|
||||
use au = gfx::geometry;
|
||||
use core::dvec::DVec;
|
||||
use core::to_str::ToStr;
|
||||
use core::rand;
|
||||
use css::styles::SpecifiedStyle;
|
||||
use css::values::{BoxSizing, Length, Px};
|
||||
use dom::base::{Element, ElementKind, HTMLDivElement, HTMLImageElement, Node, NodeData};
|
||||
use dom::base::{NodeKind};
|
||||
use css::values::{BoxSizing, Length, Px, CSSDisplay};
|
||||
use dom::base::{Element, ElementKind, HTMLDivElement, HTMLImageElement};
|
||||
use dom::base::{Node, NodeData, NodeKind, NodeTree};
|
||||
use dom::rcu;
|
||||
use gfx::geometry;
|
||||
use gfx::geometry::{au, zero_size_au};
|
||||
use geom::point::Point2D;
|
||||
use geom::rect::Rect;
|
||||
use geom::size::Size2D;
|
||||
use image::base::Image;
|
||||
use util::tree;
|
||||
use util::color::Color;
|
||||
use text::TextBox;
|
||||
use traverse_parallel::extended_full_traversal;
|
||||
use vec::{push, push_all};
|
||||
use std::net::url::Url;
|
||||
use resource::image_cache_task;
|
||||
use image_cache_task::ImageCacheTask;
|
||||
use core::to_str::ToStr;
|
||||
use gfx::geometry::au;
|
||||
use image::{Image, ImageHolder};
|
||||
use layout::block::BlockFlowData;
|
||||
use layout::inline::InlineFlowData;
|
||||
use layout::root::RootFlowData;
|
||||
use layout::text::TextBoxData;
|
||||
use servo_text::text_run::TextRun;
|
||||
use std::arc::{ARC, clone};
|
||||
use std::net::url::Url;
|
||||
use task::spawn;
|
||||
use util::color::Color;
|
||||
use util::tree;
|
||||
use vec::{push, push_all};
|
||||
|
||||
enum BoxKind {
|
||||
BlockBox,
|
||||
InlineBox,
|
||||
IntrinsicBox(@Size2D<au>),
|
||||
TextBoxKind(@TextBox)
|
||||
/* The type of the formatting context, and data specific to each
|
||||
context, such as lineboxes or float lists */
|
||||
enum FlowContextData {
|
||||
AbsoluteFlow,
|
||||
BlockFlow(BlockFlowData),
|
||||
FloatFlow,
|
||||
InlineBlockFlow,
|
||||
InlineFlow(InlineFlowData),
|
||||
RootFlow(RootFlowData),
|
||||
TableFlow
|
||||
}
|
||||
|
||||
impl BoxKind : cmp::Eq {
|
||||
pure fn eq(&&other: BoxKind) -> bool {
|
||||
match (self, other) {
|
||||
(BlockBox, BlockBox) => true,
|
||||
(InlineBox, InlineBox) => true,
|
||||
_ => fail ~"unimplemented case in BoxKind.eq"
|
||||
/* A particular kind of layout context. It manages the positioning of
|
||||
layout boxes within the context.
|
||||
|
||||
Flow contexts form a tree that is induced by the structure of the
|
||||
box tree. Each context is responsible for laying out one or more
|
||||
boxes, according to the flow type. The number of flow contexts should
|
||||
be much fewer than the number of boxes. The context maintains a vector
|
||||
of its constituent boxes in their document order.
|
||||
*/
|
||||
struct FlowContext {
|
||||
kind: FlowContextData,
|
||||
data: FlowLayoutData,
|
||||
/* reference to parent, children flow contexts */
|
||||
tree: tree::Tree<@FlowContext>,
|
||||
/* TODO: debug only */
|
||||
mut id: int
|
||||
}
|
||||
|
||||
fn FlowContext(id: int, kind: FlowContextData, tree: tree::Tree<@FlowContext>) -> FlowContext {
|
||||
FlowContext {
|
||||
kind: kind,
|
||||
data: FlowLayoutData(),
|
||||
tree: tree,
|
||||
id: id
|
||||
}
|
||||
}
|
||||
|
||||
impl @FlowContext : cmp::Eq {
|
||||
pure fn eq(&&other: @FlowContext) -> bool { box::ptr_eq(self, other) }
|
||||
pure fn ne(&&other: @FlowContext) -> bool { !box::ptr_eq(self, other) }
|
||||
}
|
||||
|
||||
/* A box's kind influences how its styles are interpreted during
|
||||
layout. For example, replaced content such as images are resized
|
||||
differently than tables, text, or other content.
|
||||
|
||||
It also holds data specific to different box types, such as text.
|
||||
*/
|
||||
enum BoxData {
|
||||
GenericBox,
|
||||
ImageBox(Size2D<au>),
|
||||
TextBox(TextBoxData)
|
||||
}
|
||||
|
||||
struct Box {
|
||||
/* references to children, parent */
|
||||
tree : tree::Tree<@Box>,
|
||||
/* originating DOM node */
|
||||
node : Node,
|
||||
/* reference to containing flow context, which this box
|
||||
participates in */
|
||||
ctx : @FlowContext,
|
||||
/* results of flow computation */
|
||||
data : BoxLayoutData,
|
||||
/* kind tag and kind-specific data */
|
||||
kind : BoxData,
|
||||
/* TODO: debug only */
|
||||
mut id: int
|
||||
}
|
||||
|
||||
fn Box(id: int, node: Node, ctx: @FlowContext, kind: BoxData) -> Box {
|
||||
Box {
|
||||
/* will be set when box is parented */
|
||||
tree : tree::empty(),
|
||||
node : node,
|
||||
ctx : ctx,
|
||||
data : BoxLayoutData(),
|
||||
kind : kind,
|
||||
id : id
|
||||
}
|
||||
}
|
||||
|
||||
impl @Box {
|
||||
pure fn is_replaced() -> bool {
|
||||
match self.kind {
|
||||
ImageBox(*) => true, // TODO: form elements, etc
|
||||
_ => false
|
||||
}
|
||||
}
|
||||
pure fn ne(&&other: BoxKind) -> bool {
|
||||
return !self.eq(other);
|
||||
|
||||
pure fn get_min_width() -> au {
|
||||
match self.kind {
|
||||
// TODO: this should account for min/pref widths of the
|
||||
// box element in isolation. That includes
|
||||
// border/margin/padding but not child widths. The block
|
||||
// FlowContext will combine the width of this element and
|
||||
// that of its children to arrive at the context width.
|
||||
GenericBox => au(0),
|
||||
// TODO: If image isn't available, consult Node
|
||||
// attrs, etc. to determine intrinsic dimensions. These
|
||||
// dimensions are not defined by CSS 2.1, but are defined
|
||||
// by the HTML5 spec in Section 4.8.1
|
||||
ImageBox(size) => size.width,
|
||||
// TODO: account for line breaks, etc. The run should know
|
||||
// how to compute its own min and pref widths, and should
|
||||
// probably cache them.
|
||||
TextBox(d) => d.runs.foldl(au(0), |sum, run| {
|
||||
au::max(sum, run.min_break_width())
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Appearance {
|
||||
mut background_image: Option<ImageHolder>,
|
||||
// TODO: create some sort of layout-specific enum to differentiate between
|
||||
// relative and resolved values.
|
||||
mut width: BoxSizing,
|
||||
mut height: BoxSizing,
|
||||
mut font_size: Length,
|
||||
}
|
||||
pure fn get_pref_width() -> au {
|
||||
match self.kind {
|
||||
// TODO: this should account for min/pref widths of the
|
||||
// box element in isolation. That includes
|
||||
// border/margin/padding but not child widths. The block
|
||||
// FlowContext will combine the width of this element and
|
||||
// that of its children to arrive at the context width.
|
||||
GenericBox => au(0),
|
||||
// TODO: If image isn't available, consult Node
|
||||
// attrs, etc. to determine intrinsic dimensions. These
|
||||
// dimensions are not defined by CSS 2.1, but are defined
|
||||
// by the HTML5 spec in Section 4.8.1
|
||||
ImageBox(size) => size.width,
|
||||
// TODO: account for line breaks, etc. The run should know
|
||||
// how to compute its own min and pref widths, and should
|
||||
// probably cache them.
|
||||
TextBox(d) => d.runs.foldl(au(0), |sum, run| {
|
||||
au::max(sum, run.size().width)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/* Returns the amount of left, right "fringe" used by this
|
||||
box. This should be based on margin, border, padding, width. */
|
||||
fn get_used_width() -> (au, au) {
|
||||
// TODO: this should actually do some computation!
|
||||
// See CSS 2.1, Section 10.3, 10.4.
|
||||
|
||||
(au(0), au(0))
|
||||
}
|
||||
|
||||
/* Returns the amount of left, right "fringe" used by this
|
||||
box. This should be based on margin, border, padding, width. */
|
||||
fn get_used_height() -> (au, au) {
|
||||
// TODO: this should actually do some computation!
|
||||
// See CSS 2.1, Section 10.5, 10.6.
|
||||
|
||||
(au(0), au(0))
|
||||
}
|
||||
|
||||
impl Appearance {
|
||||
// This will be very unhappy if it is getting run in parallel with
|
||||
// anything trying to read the background image
|
||||
fn get_image() -> Option<ARC<~Image>> {
|
||||
|
@ -61,155 +187,59 @@ impl Appearance {
|
|||
// Do a dance where we swap the ImageHolder out before we can
|
||||
// get the image out of it because we can't match against it
|
||||
// because holder.get_image() is not pure.
|
||||
if (self.background_image).is_some() {
|
||||
if (self.data.background_image).is_some() {
|
||||
let mut temp = None;
|
||||
temp <-> self.background_image;
|
||||
temp <-> self.data.background_image;
|
||||
let holder <- option::unwrap(temp);
|
||||
image = holder.get_image();
|
||||
self.background_image = Some(holder);
|
||||
self.data.background_image = Some(holder);
|
||||
}
|
||||
|
||||
return image;
|
||||
image
|
||||
}
|
||||
}
|
||||
|
||||
struct FlowLayoutData {
|
||||
mut min_width: au,
|
||||
mut pref_width: au,
|
||||
mut position: Rect<au>,
|
||||
}
|
||||
|
||||
fn Appearance(kind: NodeKind) -> Appearance {
|
||||
// TODO: these should come from initial() or elsewhere
|
||||
Appearance {
|
||||
font_size : Px(14.0),
|
||||
|
||||
fn FlowLayoutData() -> FlowLayoutData {
|
||||
FlowLayoutData {
|
||||
min_width: au(0),
|
||||
pref_width: au(0),
|
||||
position : au::zero_rect(),
|
||||
}
|
||||
}
|
||||
|
||||
struct BoxLayoutData {
|
||||
mut min_width: au,
|
||||
mut pref_width: au,
|
||||
mut position: Rect<au>,
|
||||
|
||||
mut font_size: Length,
|
||||
mut background_image: Option<ImageHolder>,
|
||||
}
|
||||
|
||||
fn BoxLayoutData() -> BoxLayoutData {
|
||||
BoxLayoutData {
|
||||
min_width: au(0),
|
||||
pref_width: au(0),
|
||||
position : au::zero_rect(),
|
||||
|
||||
font_size : Px(0.0),
|
||||
background_image : None,
|
||||
width : kind.default_width(),
|
||||
height : kind.default_height(),
|
||||
}
|
||||
}
|
||||
|
||||
struct Box {
|
||||
tree: tree::Tree<@Box>,
|
||||
node: Node,
|
||||
kind: BoxKind,
|
||||
mut bounds: Rect<au>,
|
||||
appearance: Appearance,
|
||||
}
|
||||
// FIXME: Why do these have to be redefined for each node type?
|
||||
|
||||
fn Box(node: Node, kind: BoxKind) -> Box {
|
||||
Box {
|
||||
appearance : node.read(|n| Appearance(*n.kind)),
|
||||
tree : tree::empty(),
|
||||
node : node,
|
||||
kind : kind,
|
||||
bounds : geometry::zero_rect_au(),
|
||||
}
|
||||
}
|
||||
/* The tree holding boxes */
|
||||
enum BoxTree { BoxTree }
|
||||
|
||||
#[doc="A struct to store image data. The image will be loaded once,
|
||||
the first time it is requested, and an arc will be stored. Clones of
|
||||
this arc are given out on demand."]
|
||||
struct ImageHolder {
|
||||
// Invariant: at least one of url and image is not none, except
|
||||
// occasionally while get_image is being called
|
||||
mut url : Option<Url>,
|
||||
mut image : Option<ARC<~Image>>,
|
||||
image_cache_task: ImageCacheTask,
|
||||
reflow: fn~(),
|
||||
|
||||
}
|
||||
|
||||
fn ImageHolder(-url : Url, image_cache_task: ImageCacheTask, reflow: fn~()) -> ImageHolder {
|
||||
let holder = ImageHolder {
|
||||
url : Some(copy url),
|
||||
image : None,
|
||||
image_cache_task : image_cache_task,
|
||||
reflow : copy reflow,
|
||||
};
|
||||
|
||||
// Tell the image cache we're going to be interested in this url
|
||||
// FIXME: These two messages must be sent to prep an image for use
|
||||
// but they are intended to be spread out in time. Ideally prefetch
|
||||
// should be done as early as possible and decode only once we
|
||||
// are sure that the image will be used.
|
||||
image_cache_task.send(image_cache_task::Prefetch(copy url));
|
||||
image_cache_task.send(image_cache_task::Decode(move url));
|
||||
|
||||
holder
|
||||
}
|
||||
|
||||
impl ImageHolder {
|
||||
// This function should not be called by two tasks at the same time
|
||||
fn get_image() -> Option<ARC<~Image>> {
|
||||
// If this is the first time we've called this function, load
|
||||
// the image and store it for the future
|
||||
if self.image.is_none() {
|
||||
assert self.url.is_some();
|
||||
|
||||
let mut temp = None;
|
||||
temp <-> self.url;
|
||||
let url = option::unwrap(temp);
|
||||
|
||||
let response_port = Port();
|
||||
self.image_cache_task.send(image_cache_task::GetImage(copy url, response_port.chan()));
|
||||
self.image = match response_port.recv() {
|
||||
image_cache_task::ImageReady(image) => Some(clone(&image)),
|
||||
image_cache_task::ImageNotReady => {
|
||||
// Need to reflow when the image is available
|
||||
let image_cache_task = self.image_cache_task;
|
||||
let reflow = copy self.reflow;
|
||||
do spawn |copy url, move reflow| {
|
||||
let response_port = Port();
|
||||
image_cache_task.send(image_cache_task::WaitForImage(copy url, response_port.chan()));
|
||||
match response_port.recv() {
|
||||
image_cache_task::ImageReady(*) => reflow(),
|
||||
image_cache_task::ImageNotReady => fail /*not possible*/,
|
||||
image_cache_task::ImageFailed => ()
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
image_cache_task::ImageFailed => {
|
||||
#info("image was not ready for %s", url.to_str());
|
||||
// FIXME: Need to schedule another layout when the image is ready
|
||||
None
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if self.image.is_some() {
|
||||
// Temporarily swap out the arc of the image so we can clone
|
||||
// it without breaking purity, then put it back and return the
|
||||
// clone. This is not threadsafe.
|
||||
let mut temp = None;
|
||||
temp <-> self.image;
|
||||
let im_arc = option::unwrap(temp);
|
||||
self.image = Some(clone(&im_arc));
|
||||
|
||||
return Some(im_arc);
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum LayoutData = {
|
||||
mut specified_style: ~SpecifiedStyle,
|
||||
mut box: Option<@Box>
|
||||
};
|
||||
|
||||
// FIXME: This is way too complex! Why do these have to have dummy receivers? --pcw
|
||||
|
||||
enum NTree { NTree }
|
||||
impl NTree : tree::ReadMethods<Node> {
|
||||
fn each_child(node: Node, f: fn(Node) -> bool) {
|
||||
tree::each_child(self, node, f)
|
||||
}
|
||||
|
||||
fn with_tree_fields<R>(&&n: Node, f: fn(tree::Tree<Node>) -> R) -> R {
|
||||
n.read(|n| f(n.tree))
|
||||
}
|
||||
}
|
||||
|
||||
enum BTree { BTree }
|
||||
|
||||
impl BTree : tree::ReadMethods<@Box> {
|
||||
impl BoxTree : tree::ReadMethods<@Box> {
|
||||
fn each_child(node: @Box, f: fn(&&@Box) -> bool) {
|
||||
tree::each_child(self, node, f)
|
||||
}
|
||||
|
@ -219,9 +249,10 @@ impl BTree : tree::ReadMethods<@Box> {
|
|||
}
|
||||
}
|
||||
|
||||
impl BTree : tree::WriteMethods<@Box> {
|
||||
fn add_child(node: @Box, child: @Box) {
|
||||
tree::add_child(self, node, child)
|
||||
impl BoxTree : tree::WriteMethods<@Box> {
|
||||
fn add_child(parent: @Box, child: @Box) {
|
||||
assert !box::ptr_eq(parent, child);
|
||||
tree::add_child(self, parent, child)
|
||||
}
|
||||
|
||||
fn with_tree_fields<R>(&&b: @Box, f: fn(tree::Tree<@Box>) -> R) -> R {
|
||||
|
@ -229,111 +260,168 @@ impl BTree : tree::WriteMethods<@Box> {
|
|||
}
|
||||
}
|
||||
|
||||
impl @Box {
|
||||
#[doc="The main reflow routine."]
|
||||
fn reflow() {
|
||||
/* The tree holding FlowContexts */
|
||||
enum FlowTree { FlowTree }
|
||||
|
||||
impl FlowTree : tree::ReadMethods<@FlowContext> {
|
||||
fn each_child(ctx: @FlowContext, f: fn(&&@FlowContext) -> bool) {
|
||||
tree::each_child(self, ctx, f)
|
||||
}
|
||||
|
||||
fn with_tree_fields<R>(&&b: @FlowContext, f: fn(tree::Tree<@FlowContext>) -> R) -> R {
|
||||
f(b.tree)
|
||||
}
|
||||
}
|
||||
|
||||
impl FlowTree : tree::WriteMethods<@FlowContext> {
|
||||
fn add_child(parent: @FlowContext, child: @FlowContext) {
|
||||
assert !box::ptr_eq(parent, child);
|
||||
tree::add_child(self, parent, child)
|
||||
}
|
||||
|
||||
fn with_tree_fields<R>(&&b: @FlowContext, f: fn(tree::Tree<@FlowContext>) -> R) -> R {
|
||||
f(b.tree)
|
||||
}
|
||||
}
|
||||
|
||||
impl @FlowContext {
|
||||
fn bubble_widths() {
|
||||
match self.kind {
|
||||
BlockBox => self.reflow_block(),
|
||||
InlineBox => self.reflow_inline(),
|
||||
IntrinsicBox(size) => self.reflow_intrinsic(*size),
|
||||
TextBoxKind(subbox) => self.reflow_text(subbox)
|
||||
BlockFlow(*) => self.bubble_widths_block(),
|
||||
InlineFlow(*) => self.bubble_widths_inline(),
|
||||
RootFlow(*) => self.bubble_widths_root(),
|
||||
_ => fail fmt!("Tried to bubble_widths of flow: %?", self.kind)
|
||||
}
|
||||
}
|
||||
|
||||
#[doc="Dumps the box tree, for debugging, with indentation."]
|
||||
fn dump_indent(indent: uint) {
|
||||
let mut s = ~"";
|
||||
for uint::range(0u, indent) |_i| {
|
||||
s += ~" ";
|
||||
}
|
||||
|
||||
s += #fmt("%?", self.kind);
|
||||
#debug["%s", s];
|
||||
|
||||
for BTree.each_child(self) |kid| {
|
||||
kid.dump_indent(indent + 1u)
|
||||
fn assign_widths() {
|
||||
match self.kind {
|
||||
BlockFlow(*) => self.assign_widths_block(),
|
||||
InlineFlow(*) => self.assign_widths_inline(),
|
||||
RootFlow(*) => self.assign_widths_root(),
|
||||
_ => fail fmt!("Tried to assign_widths of flow: %?", self.kind)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc = "
|
||||
Set your width to the maximum available width and return the
|
||||
maximum available width any children can use. Currently children
|
||||
are just given the same available width.
|
||||
"]
|
||||
fn give_kids_width(+available_width : au, box : @Box) -> au {
|
||||
// TODO: give smaller available widths if the width of the
|
||||
// containing box is constrained
|
||||
match box.kind {
|
||||
BlockBox => box.bounds.size.width = available_width,
|
||||
InlineBox | IntrinsicBox(*) | TextBoxKind(*) => { }
|
||||
}
|
||||
|
||||
available_width
|
||||
}
|
||||
|
||||
#[doc="Wrapper around reflow so it can be passed to traverse"]
|
||||
fn reflow_wrapper(b : @Box) {
|
||||
b.reflow();
|
||||
}
|
||||
|
||||
impl @Box {
|
||||
#[doc="
|
||||
Run a parallel traversal over the layout tree rooted at
|
||||
this box. On the top-down traversal give each box the
|
||||
available width determined by their parent and on the
|
||||
bottom-up traversal reflow each box based on their
|
||||
attributes and their children's sizes.
|
||||
"]
|
||||
fn reflow_subtree(available_width : au) {
|
||||
extended_full_traversal(self, available_width, give_kids_width, reflow_wrapper);
|
||||
}
|
||||
|
||||
#[doc="The trivial reflow routine for instrinsically-sized frames."]
|
||||
fn reflow_intrinsic(size: Size2D<au>) {
|
||||
self.bounds.size = copy size;
|
||||
|
||||
#debug["reflow_intrinsic size=%?", copy self.bounds];
|
||||
}
|
||||
|
||||
#[doc="Dumps the box tree, for debugging."]
|
||||
fn dump() {
|
||||
self.dump_indent(0u);
|
||||
fn assign_height() {
|
||||
match self.kind {
|
||||
BlockFlow(*) => self.assign_height_block(),
|
||||
InlineFlow(*) => self.assign_height_inline(),
|
||||
RootFlow(*) => self.assign_height_root(),
|
||||
_ => fail fmt!("Tried to assign_height of flow: %?", self.kind)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Debugging
|
||||
|
||||
trait PrivateNodeMethods{
|
||||
trait DebugMethods {
|
||||
fn dump();
|
||||
fn dump_indent(ident: uint);
|
||||
fn debug_str() -> ~str;
|
||||
}
|
||||
|
||||
impl Node : PrivateNodeMethods {
|
||||
#[doc="Dumps the node tree, for debugging, with indentation."]
|
||||
impl @FlowContext : DebugMethods {
|
||||
fn dump() {
|
||||
self.dump_indent(0u);
|
||||
}
|
||||
|
||||
/** Dumps the flow tree, for debugging, with indentation. */
|
||||
fn dump_indent(indent: uint) {
|
||||
let mut s = ~"|";
|
||||
for uint::range(0u, indent) |_i| {
|
||||
s += ~"---- ";
|
||||
}
|
||||
|
||||
s += self.debug_str();
|
||||
debug!("%s", s);
|
||||
|
||||
for FlowTree.each_child(self) |child| {
|
||||
child.dump_indent(indent + 1u)
|
||||
}
|
||||
}
|
||||
|
||||
/* TODO: we need a string builder. This is horribly inefficient */
|
||||
fn debug_str() -> ~str {
|
||||
let repr = match self.kind {
|
||||
InlineFlow(d) => {
|
||||
let mut s = d.boxes.foldl(~"InlineFlow(children=", |s, box| {
|
||||
fmt!("%s %?", s, box.id)
|
||||
});
|
||||
s += ~")"; s
|
||||
},
|
||||
BlockFlow(d) => {
|
||||
match d.box {
|
||||
Some(b) => fmt!("BlockFlow(box=b%?)", d.box.get().id),
|
||||
None => ~"BlockFlow",
|
||||
}
|
||||
},
|
||||
_ => fmt!("%?", self.kind)
|
||||
};
|
||||
|
||||
fmt!("c%? %?", self.id, repr)
|
||||
}
|
||||
}
|
||||
|
||||
impl Node : DebugMethods {
|
||||
/* Dumps the subtree rooted at this node, for debugging. */
|
||||
fn dump() {
|
||||
self.dump_indent(0u);
|
||||
}
|
||||
/* Dumps the node tree, for debugging, with indentation. */
|
||||
fn dump_indent(indent: uint) {
|
||||
let mut s = ~"";
|
||||
for uint::range(0u, indent) |_i| {
|
||||
s += ~" ";
|
||||
}
|
||||
|
||||
s += #fmt("%?", self.read(|n| copy n.kind ));
|
||||
#debug["%s", s];
|
||||
s += self.debug_str();
|
||||
debug!("%s", s);
|
||||
|
||||
for NTree.each_child(self) |kid| {
|
||||
for NodeTree.each_child(self) |kid| {
|
||||
kid.dump_indent(indent + 1u)
|
||||
}
|
||||
}
|
||||
|
||||
fn debug_str() -> ~str {
|
||||
fmt!("%?", self.read(|n| copy n.kind ))
|
||||
}
|
||||
}
|
||||
|
||||
trait NodeMethods {
|
||||
fn dump();
|
||||
}
|
||||
|
||||
impl Node : NodeMethods {
|
||||
#[doc="Dumps the subtree rooted at this node, for debugging."]
|
||||
impl @Box : DebugMethods {
|
||||
fn dump() {
|
||||
self.dump_indent(0u);
|
||||
}
|
||||
|
||||
/* Dumps the node tree, for debugging, with indentation. */
|
||||
fn dump_indent(indent: uint) {
|
||||
let mut s = ~"";
|
||||
for uint::range(0u, indent) |_i| {
|
||||
s += ~" ";
|
||||
}
|
||||
|
||||
s += self.debug_str();
|
||||
debug!("%s", s);
|
||||
|
||||
for BoxTree.each_child(self) |kid| {
|
||||
kid.dump_indent(indent + 1u)
|
||||
}
|
||||
}
|
||||
|
||||
fn debug_str() -> ~str {
|
||||
let repr = match self.kind {
|
||||
GenericBox(*) => ~"GenericBox",
|
||||
ImageBox(*) => ~"ImageBox",
|
||||
TextBox(d) => {
|
||||
let mut s = d.runs.foldl(~"TextBox(runs=", |s, run| {
|
||||
fmt!("%s \"%s\"", s, run.text)
|
||||
});
|
||||
s += ~")"; s
|
||||
}
|
||||
};
|
||||
|
||||
fmt!("box b%?: %?", self.id, repr)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -360,49 +448,15 @@ mod test {
|
|||
|
||||
fn flat_bounds(root: @Box) -> ~[Rect<au>] {
|
||||
let mut r = ~[];
|
||||
for tree::each_child(BTree, root) |c| {
|
||||
for tree::each_child(BoxTree, root) |c| {
|
||||
push_all(r, flat_bounds(c));
|
||||
}
|
||||
|
||||
push(r, copy root.bounds);
|
||||
push(r, copy root.data.position);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore(reason = "busted")]
|
||||
fn do_layout() {
|
||||
let s = Scope();
|
||||
|
||||
fn mk_img(size: Size2D<au>) -> ~ElementKind {
|
||||
~HTMLImageElement({mut size: size})
|
||||
}
|
||||
|
||||
let n0 = s.new_node(Element(ElementData(~"img", mk_img(Size2D(au(10),au(10))))));
|
||||
let n1 = s.new_node(Element(ElementData(~"img", mk_img(Size2D(au(10),au(10))))));
|
||||
let n2 = s.new_node(Element(ElementData(~"img", mk_img(Size2D(au(10),au(20))))));
|
||||
let n3 = s.new_node(Element(ElementData(~"div", ~HTMLDivElement)));
|
||||
|
||||
tree::add_child(s, n3, n0);
|
||||
tree::add_child(s, n3, n1);
|
||||
tree::add_child(s, n3, n2);
|
||||
|
||||
let b0 = n0.construct_boxes().get();
|
||||
let b1 = n1.construct_boxes().get();
|
||||
let b2 = n2.construct_boxes().get();
|
||||
let b3 = n3.construct_boxes().get();
|
||||
|
||||
tree::add_child(BTree, b3, b0);
|
||||
tree::add_child(BTree, b3, b1);
|
||||
tree::add_child(BTree, b3, b2);
|
||||
|
||||
b3.reflow_subtree(au(100));
|
||||
let fb = flat_bounds(b3);
|
||||
#debug["fb=%?", fb];
|
||||
assert fb == ~[geometry::box(au(0), au(0), au(10), au(10)), // n0
|
||||
geometry::box(au(0), au(10), au(10), au(15)), // n1
|
||||
geometry::box(au(0), au(25), au(10), au(20)), // n2
|
||||
geometry::box(au(0), au(0), au(100), au(45))]; // n3
|
||||
}
|
||||
// TODO: redo tests here, but probably is part of box_builder.rs
|
||||
}
|
||||
|
||||
|
|
|
@ -1,57 +1,149 @@
|
|||
#[doc="Block layout."]
|
||||
|
||||
use au = gfx::geometry;
|
||||
use css::values::*;
|
||||
use geom::point::Point2D;
|
||||
use geom::size::Size2D;
|
||||
use gfx::geometry::{px_to_au, au};
|
||||
use gfx::geometry::au;
|
||||
use layout::base::{Box, FlowContext, FlowTree, InlineBlockFlow, BlockFlow, RootFlow};
|
||||
use util::tree;
|
||||
use base::{Box, BlockBox, BTree};
|
||||
|
||||
trait BlockLayoutMethods {
|
||||
fn reflow_block();
|
||||
struct BlockFlowData {
|
||||
mut box: Option<@Box>
|
||||
}
|
||||
|
||||
#[doc="The public block layout methods."]
|
||||
impl @Box : BlockLayoutMethods {
|
||||
#[doc="The main reflow routine for block layout."]
|
||||
fn reflow_block() {
|
||||
assert self.kind == BlockBox;
|
||||
|
||||
#debug["starting reflow block"];
|
||||
|
||||
// Root here is the root of the reflow, not necessarily the doc as
|
||||
// a whole.
|
||||
//
|
||||
// This routine:
|
||||
// - generates root.bounds.size
|
||||
// - generates root.bounds.origin for each child
|
||||
|
||||
let mut current_height = 0;
|
||||
|
||||
// Find the combined height of all the children and mark the
|
||||
// relative heights of the children in the box
|
||||
for tree::each_child(BTree, self) |c| {
|
||||
// FIXME subtract borders, margins, etc
|
||||
c.bounds.origin = Point2D(au(0), au(current_height));
|
||||
current_height += *c.bounds.size.height;
|
||||
}
|
||||
|
||||
let height = match self.appearance.height {
|
||||
BoxLength(Px(p)) => px_to_au(p.to_int()),
|
||||
BoxAuto => au(current_height),
|
||||
_ => fail ~"inhereit_height failed, height is neither a Px or auto"
|
||||
};
|
||||
|
||||
// FIXME: Width is wrong in the calculation below.
|
||||
let width = match self.appearance.width {
|
||||
BoxLength(Px(p)) => px_to_au(p.to_int()),
|
||||
BoxAuto => self.bounds.size.width, // Do nothing here, width was set by top-down pass
|
||||
_ => fail ~"inhereit_width failed, width is neither a Px or auto"
|
||||
};
|
||||
|
||||
self.bounds.size = Size2D(width, height);
|
||||
|
||||
#debug["reflow_block size=%?", copy self.bounds];
|
||||
fn BlockFlowData() -> BlockFlowData {
|
||||
BlockFlowData {
|
||||
box: None
|
||||
}
|
||||
}
|
||||
|
||||
trait BlockLayout {
|
||||
pure fn starts_block_flow() -> bool;
|
||||
pure fn access_block<T>(fn(&&BlockFlowData) -> T) -> T;
|
||||
pure fn with_block_box(fn(&&@Box) -> ()) -> ();
|
||||
|
||||
fn bubble_widths_block();
|
||||
fn assign_widths_block();
|
||||
fn assign_height_block();
|
||||
}
|
||||
|
||||
impl @FlowContext : BlockLayout {
|
||||
|
||||
pure fn starts_block_flow() -> bool {
|
||||
match self.kind {
|
||||
RootFlow(*) | BlockFlow(*) | InlineBlockFlow(*) => true,
|
||||
_ => false
|
||||
}
|
||||
}
|
||||
|
||||
pure fn access_block<T>(cb:fn(&&BlockFlowData) -> T) -> T {
|
||||
match self.kind {
|
||||
BlockFlow(d) => cb(d),
|
||||
_ => fail fmt!("Tried to access() data of BlockFlow, but this is a %?", self.kind)
|
||||
}
|
||||
}
|
||||
|
||||
/* Get the current flow's corresponding block box, if it exists, and do something with it.
|
||||
This works on both BlockFlow and RootFlow, since they are mostly the same. */
|
||||
pure fn with_block_box(cb:fn(&&@Box) -> ()) -> () {
|
||||
match self.kind {
|
||||
BlockFlow(*) => {
|
||||
do self.access_block |d| {
|
||||
let mut box = d.box;
|
||||
box.iter(cb)
|
||||
}
|
||||
},
|
||||
RootFlow(*) => {
|
||||
do self.access_root |d| {
|
||||
let mut box = d.box;
|
||||
box.iter(cb)
|
||||
}
|
||||
},
|
||||
_ => fail fmt!("Tried to do something with_block_box(), but this is a %?", self.kind)
|
||||
}
|
||||
}
|
||||
|
||||
/* Recursively (bottom-up) determine the context's preferred and
|
||||
minimum widths. When called on this context, all child contexts
|
||||
have had their min/pref widths set. This function must decide
|
||||
min/pref widths based on child context widths and dimensions of
|
||||
any boxes it is responsible for flowing. */
|
||||
|
||||
/* TODO: floats */
|
||||
/* TODO: absolute contexts */
|
||||
/* TODO: inline-blocks */
|
||||
fn bubble_widths_block() {
|
||||
assert self.starts_block_flow();
|
||||
|
||||
let mut min_width = au(0);
|
||||
let mut pref_width = au(0);
|
||||
|
||||
/* find max width from child block contexts */
|
||||
for FlowTree.each_child(self) |child_ctx| {
|
||||
assert child_ctx.starts_block_flow() || child_ctx.starts_inline_flow();
|
||||
|
||||
min_width = au::max(min_width, child_ctx.data.min_width);
|
||||
pref_width = au::max(pref_width, child_ctx.data.pref_width);
|
||||
}
|
||||
|
||||
/* if not an anonymous block context, add in block box's widths.
|
||||
these widths will not include child elements, just padding etc. */
|
||||
do self.with_block_box |box| {
|
||||
min_width += box.get_min_width();
|
||||
pref_width += box.get_pref_width();
|
||||
}
|
||||
|
||||
self.data.min_width = min_width;
|
||||
self.data.pref_width = pref_width;
|
||||
}
|
||||
|
||||
/* Recursively (top-down) determines the actual width of child
|
||||
contexts and boxes. When called on this context, the context has
|
||||
had its width set by the parent context.
|
||||
|
||||
Dual boxes consume some width first, and the remainder is assigned to
|
||||
all child (block) contexts. */
|
||||
|
||||
fn assign_widths_block() {
|
||||
assert self.starts_block_flow();
|
||||
|
||||
let mut remaining_width = self.data.position.size.width;
|
||||
let mut right_used = au(0);
|
||||
let mut left_used = au(0);
|
||||
|
||||
/* Let the box consume some width. It will return the amount remaining
|
||||
for its children. */
|
||||
do self.with_block_box |box| {
|
||||
box.data.position.size.width = remaining_width;
|
||||
let (left_used, right_used) = box.get_used_width();
|
||||
remaining_width -= (left_used + right_used);
|
||||
}
|
||||
|
||||
for FlowTree.each_child(self) |child_ctx| {
|
||||
assert child_ctx.starts_block_flow() || child_ctx.starts_inline_flow();
|
||||
child_ctx.data.position.origin.x = left_used;
|
||||
child_ctx.data.position.size.width = remaining_width;
|
||||
}
|
||||
}
|
||||
|
||||
fn assign_height_block() {
|
||||
assert self.starts_block_flow();
|
||||
|
||||
let mut cur_y = au(0);
|
||||
|
||||
for FlowTree.each_child(self) |child_ctx| {
|
||||
child_ctx.data.position.origin.y = cur_y;
|
||||
cur_y += child_ctx.data.position.size.height;
|
||||
}
|
||||
|
||||
self.data.position.size.height = cur_y;
|
||||
|
||||
let used_top = au(0);
|
||||
let used_bot = au(0);
|
||||
|
||||
do self.with_block_box |box| {
|
||||
box.data.position.origin.y = au(0);
|
||||
box.data.position.size.height = cur_y;
|
||||
let (used_top, used_bot) = box.get_used_height();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,205 +1,195 @@
|
|||
#[doc="Creates CSS boxes from a DOM."]
|
||||
|
||||
use css::values::{CSSDisplay, DisplayBlock, DisplayInline, DisplayNone, Specified};
|
||||
use dom::base::{ElementData, HTMLDivElement, HTMLImageElement, Element, Text, Node, Doctype, Comment};
|
||||
use gfx::geometry::zero_size_au;
|
||||
use layout::base::{Appearance, BTree, BlockBox, Box, BoxKind, InlineBox, IntrinsicBox, NTree};
|
||||
use layout::base::{TextBoxKind};
|
||||
use layout::text::TextBox;
|
||||
use util::tree;
|
||||
/** Creates CSS boxes from a DOM. */
|
||||
use au = gfx::geometry;
|
||||
use core::dvec::DVec;
|
||||
use css::values::{CSSDisplay, DisplayBlock, DisplayInline, DisplayInlineBlock, DisplayNone};
|
||||
use css::values::{Inherit, Initial, Specified};
|
||||
use dom::base::{ElementData, HTMLDivElement, HTMLImageElement};
|
||||
use dom::base::{Element, Text, Node, Doctype, Comment, NodeTree};
|
||||
use layout::base::{Box, BoxData, GenericBox, ImageBox, TextBox, BoxTree};
|
||||
use layout::base::{FlowContext, FlowContextData, BlockFlow, InlineFlow, InlineBlockFlow, RootFlow, FlowTree};
|
||||
use layout::block::BlockFlowData;
|
||||
use layout::inline::InlineFlowData;
|
||||
use layout::root::RootFlowData;
|
||||
use layout::text::TextBoxData;
|
||||
use option::is_none;
|
||||
use util::tree;
|
||||
use servo_text::text_run::TextRun;
|
||||
use servo_text::font_library::FontLibrary;
|
||||
|
||||
export box_builder_methods;
|
||||
export LayoutTreeBuilder;
|
||||
|
||||
enum ctxt = {
|
||||
// The parent node that we're scanning.
|
||||
parent_node: Node,
|
||||
// The parent box that these boxes will be added to.
|
||||
parent_box: @Box,
|
||||
|
||||
//
|
||||
// The current anonymous box that we're currently appending inline nodes to.
|
||||
//
|
||||
// See CSS2 9.2.1.1.
|
||||
//
|
||||
|
||||
mut anon_box: Option<@Box>
|
||||
};
|
||||
|
||||
fn create_context(parent_node: Node, parent_box: @Box) -> ctxt {
|
||||
return ctxt({
|
||||
parent_node: parent_node,
|
||||
parent_box: parent_box,
|
||||
mut anon_box: None
|
||||
});
|
||||
struct LayoutTreeBuilder {
|
||||
mut root_box: Option<@Box>,
|
||||
mut root_ctx: Option<@FlowContext>,
|
||||
mut next_bid: int,
|
||||
mut next_cid: int
|
||||
}
|
||||
|
||||
impl ctxt {
|
||||
#[doc="
|
||||
Constructs boxes for the parent's children, when the parent's 'display' attribute is 'block'.
|
||||
"]
|
||||
fn construct_boxes_for_block_children() {
|
||||
for NTree.each_child(self.parent_node) |kid| {
|
||||
|
||||
// Create boxes for the child. Get its primary box.
|
||||
let kid_box = kid.construct_boxes();
|
||||
if (kid_box.is_none()) {
|
||||
loop
|
||||
}
|
||||
|
||||
// Determine the child's display.
|
||||
let disp = kid.get_specified_style().display_type;
|
||||
if disp != Specified(DisplayInline) {
|
||||
self.finish_anonymous_box_if_necessary();
|
||||
}
|
||||
|
||||
// Add the child's box to the current enclosing box or the current anonymous box.
|
||||
match kid.get_specified_style().display_type {
|
||||
Specified(DisplayBlock) => BTree.add_child(self.parent_box, kid_box.get()),
|
||||
Specified(DisplayInline) => {
|
||||
let anon_box = match self.anon_box {
|
||||
None => {
|
||||
//
|
||||
// The anonymous box inherits the attributes of its parents for now, so
|
||||
// that properties of intrinsic boxes are not spread to their parenting
|
||||
// anonymous box.
|
||||
//
|
||||
// TODO: check what CSS actually specifies
|
||||
//
|
||||
|
||||
let b = @Box(self.parent_node, InlineBox);
|
||||
self.anon_box = Some(b);
|
||||
b
|
||||
}
|
||||
Some(b) => b
|
||||
};
|
||||
BTree.add_child(anon_box, kid_box.get());
|
||||
}
|
||||
Specified(DisplayNone) => {
|
||||
// Nothing to do.
|
||||
}
|
||||
_ => { //hack for now
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc="
|
||||
Constructs boxes for the parent's children, when the parent's 'display'
|
||||
attribute is 'inline'.
|
||||
"]
|
||||
fn construct_boxes_for_inline_children() {
|
||||
for NTree.each_child(self.parent_node) |kid| {
|
||||
|
||||
// Construct boxes for the child. Get its primary box.
|
||||
let kid_box = kid.construct_boxes();
|
||||
|
||||
// Determine the child's display.
|
||||
let disp = kid.get_specified_style().display_type;
|
||||
if disp != Specified(DisplayInline) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
// Add the child's box to the current enclosing box.
|
||||
match kid.get_specified_style().display_type {
|
||||
Specified(DisplayBlock) => {
|
||||
// TODO
|
||||
#warn("TODO: non-inline display found inside inline box");
|
||||
BTree.add_child(self.parent_box, kid_box.get());
|
||||
}
|
||||
Specified(DisplayInline) => {
|
||||
BTree.add_child(self.parent_box, kid_box.get());
|
||||
}
|
||||
Specified(DisplayNone) => {
|
||||
// Nothing to do.
|
||||
}
|
||||
_ => { //hack for now
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc="Constructs boxes for the parent's children."]
|
||||
fn construct_boxes_for_children() {
|
||||
#debug("parent node:");
|
||||
self.parent_node.dump();
|
||||
|
||||
match self.parent_node.get_specified_style().display_type {
|
||||
Specified(DisplayBlock) => self.construct_boxes_for_block_children(),
|
||||
Specified(DisplayInline) => self.construct_boxes_for_inline_children(),
|
||||
Specified(DisplayNone) => { /* Nothing to do. */ }
|
||||
_ => { //hack for now
|
||||
}
|
||||
}
|
||||
|
||||
self.finish_anonymous_box_if_necessary();
|
||||
assert is_none(self.anon_box);
|
||||
}
|
||||
|
||||
#[doc="
|
||||
Flushes the anonymous box we're creating if it exists. This appends the
|
||||
anonymous box to the block.
|
||||
"]
|
||||
fn finish_anonymous_box_if_necessary() {
|
||||
match copy self.anon_box {
|
||||
None => { /* Nothing to do. */ }
|
||||
Some(b) => BTree.add_child(self.parent_box, b)
|
||||
}
|
||||
self.anon_box = None;
|
||||
fn LayoutTreeBuilder() -> LayoutTreeBuilder {
|
||||
LayoutTreeBuilder {
|
||||
root_box: None,
|
||||
root_ctx: None,
|
||||
next_bid: -1,
|
||||
next_cid: -1
|
||||
}
|
||||
}
|
||||
|
||||
trait PrivBoxBuilder {
|
||||
fn determine_box_kind() -> Option<BoxKind>;
|
||||
}
|
||||
impl LayoutTreeBuilder {
|
||||
/* Debug-only ids */
|
||||
fn next_box_id() -> int { self.next_bid += 1; self.next_bid }
|
||||
fn next_ctx_id() -> int { self.next_cid += 1; self.next_cid }
|
||||
|
||||
impl Node : PrivBoxBuilder {
|
||||
#[doc="
|
||||
Determines the kind of box that this node needs. Also, for images, computes the intrinsic
|
||||
size.
|
||||
"]
|
||||
fn determine_box_kind() -> Option<BoxKind> {
|
||||
match self.read(|n| copy n.kind) {
|
||||
~Text(string) => Some(TextBoxKind(@TextBox(copy string))),
|
||||
~Element(element) => {
|
||||
match (copy *element.kind, self.get_specified_style().display_type) {
|
||||
(HTMLImageElement({size}), _) => Some(IntrinsicBox(@size)),
|
||||
(_, Specified(DisplayBlock)) => Some(BlockBox),
|
||||
(_, Specified(DisplayInline)) => Some(InlineBox),
|
||||
(_, Specified(DisplayNone)) => None,
|
||||
(_, Specified(_)) => Some(InlineBox),
|
||||
(_, _) => {
|
||||
fail ~"The specified display style should be a default instead of none"
|
||||
}
|
||||
/** Creates necessary box(es) and flow context(s) for the current DOM node,
|
||||
and recurses on its children. */
|
||||
fn construct_recursively(cur_node: Node, parent_ctx: @FlowContext, parent_box: @Box) {
|
||||
let style = cur_node.style();
|
||||
|
||||
// DEBUG
|
||||
let n_str = fmt!("%?", cur_node.read(|n| copy n.kind ));
|
||||
debug!("Considering node: %?", n_str);
|
||||
|
||||
// TODO: handle interactions with 'float', 'position' (CSS 2.1, Section 9.7)
|
||||
let display = match style.display_type {
|
||||
Specified(v) => match v {
|
||||
// tree ends here if 'display: none'
|
||||
DisplayNone => return,
|
||||
_ => v
|
||||
},
|
||||
Inherit | Initial => DisplayInline // TODO: fail instead once resolve works
|
||||
//fail ~"Node should have resolved value for 'display', but was initial or inherit"
|
||||
};
|
||||
|
||||
// first, create the proper box kind, based on node characteristics
|
||||
let box_data = match cur_node.create_box_data(display) {
|
||||
None => return,
|
||||
Some(data) => data
|
||||
};
|
||||
|
||||
// then, figure out its proper context, possibly reorganizing.
|
||||
let next_ctx: @FlowContext = match box_data {
|
||||
/* Text box is always an inline flow. create implicit inline
|
||||
flow ctx if we aren't inside one already. */
|
||||
TextBox(*) => {
|
||||
if (parent_ctx.starts_inline_flow()) {
|
||||
parent_ctx
|
||||
} else {
|
||||
self.make_ctx(InlineFlow(InlineFlowData()), tree::empty())
|
||||
}
|
||||
},
|
||||
~Doctype(*)
|
||||
| ~Comment(*) => None
|
||||
ImageBox(*) | GenericBox => {
|
||||
match display {
|
||||
DisplayInline | DisplayInlineBlock => {
|
||||
/* if inline, try to put into inline context,
|
||||
making a new one if necessary */
|
||||
if (parent_ctx.starts_inline_flow()) {
|
||||
parent_ctx
|
||||
} else {
|
||||
self.make_ctx(InlineFlow(InlineFlowData()), tree::empty())
|
||||
}
|
||||
},
|
||||
/* block boxes always create a new context */
|
||||
DisplayBlock => {
|
||||
self.make_ctx(BlockFlow(BlockFlowData()), tree::empty())
|
||||
},
|
||||
_ => fail fmt!("unsupported display type in box generation: %?", display)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// make box, add box to any context-specific list.
|
||||
let mut new_box = self.make_box(cur_node, parent_ctx, box_data);
|
||||
debug!("Assign ^box to flow: %?", next_ctx.debug_str());
|
||||
|
||||
match next_ctx.kind {
|
||||
InlineFlow(d) => { d.boxes.push(new_box) }
|
||||
BlockFlow(d) => { d.box = Some(new_box) }
|
||||
_ => {} // TODO: float lists, etc.
|
||||
};
|
||||
|
||||
// connect the box to its parent box
|
||||
debug!("Adding child box b%? of b%?", parent_box.id, new_box.id);
|
||||
BoxTree.add_child(parent_box, new_box);
|
||||
|
||||
if (!next_ctx.eq(parent_ctx)) {
|
||||
debug!("Adding child flow f%? of f%?", parent_ctx.id, next_ctx.id);
|
||||
FlowTree.add_child(parent_ctx, next_ctx);
|
||||
}
|
||||
// recurse
|
||||
do NodeTree.each_child(cur_node) |child_node| {
|
||||
self.construct_recursively(child_node, next_ctx, new_box); true
|
||||
}
|
||||
|
||||
// Fixup any irregularities, such as split inlines (CSS 2.1 Section 9.2.1.1)
|
||||
if (next_ctx.starts_inline_flow()) {
|
||||
let mut found_child_inline = false;
|
||||
let mut found_child_block = false;
|
||||
|
||||
do FlowTree.each_child(next_ctx) |child_ctx| {
|
||||
match child_ctx.kind {
|
||||
InlineFlow(*) | InlineBlockFlow => found_child_inline = true,
|
||||
BlockFlow(*) => found_child_block = true,
|
||||
_ => {}
|
||||
}; true
|
||||
}
|
||||
|
||||
if found_child_block && found_child_inline {
|
||||
self.fixup_split_inline(next_ctx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn fixup_split_inline(foo: @FlowContext) {
|
||||
// TODO: finish me.
|
||||
fail ~"TODO: handle case where an inline is split by a block"
|
||||
}
|
||||
|
||||
/** entry point for box creation. Should only be
|
||||
called on root DOM element. */
|
||||
fn construct_trees(root: Node) -> Result<@Box, ()> {
|
||||
self.root_ctx = Some(self.make_ctx(RootFlow(RootFlowData()), tree::empty()));
|
||||
self.root_box = Some(self.make_box(root, self.root_ctx.get(), GenericBox));
|
||||
|
||||
self.construct_recursively(root, self.root_ctx.get(), self.root_box.get());
|
||||
return Ok(self.root_box.get())
|
||||
}
|
||||
|
||||
fn make_ctx(kind : FlowContextData, tree: tree::Tree<@FlowContext>) -> @FlowContext {
|
||||
let ret = @FlowContext(self.next_ctx_id(), kind, tree);
|
||||
debug!("Created context: %s", ret.debug_str());
|
||||
ret
|
||||
}
|
||||
|
||||
fn make_box(node : Node, ctx: @FlowContext, data: BoxData) -> @Box {
|
||||
let ret = @Box(self.next_box_id(), node, ctx, data);
|
||||
debug!("Created box: %s", ret.debug_str());
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
trait BoxBuilder {
|
||||
fn construct_boxes() -> Option<@Box>;
|
||||
trait PrivateBuilderMethods {
|
||||
fn create_box_data(display: CSSDisplay) -> Option<BoxData>;
|
||||
}
|
||||
|
||||
impl Node : BoxBuilder {
|
||||
#[doc="Creates boxes for this node. This is the entry point."]
|
||||
fn construct_boxes() -> Option<@Box> {
|
||||
match self.determine_box_kind() {
|
||||
None => None,
|
||||
Some(kind) => {
|
||||
let my_box = @Box(self, kind);
|
||||
match kind {
|
||||
BlockBox | InlineBox => {
|
||||
let cx = create_context(self, my_box);
|
||||
cx.construct_boxes_for_children();
|
||||
}
|
||||
_ => {
|
||||
// Nothing to do.
|
||||
impl Node : PrivateBuilderMethods {
|
||||
fn create_box_data(display: CSSDisplay) -> Option<BoxData> {
|
||||
do self.read |node| {
|
||||
match node.kind {
|
||||
~Doctype(*) | ~Comment(*) => None,
|
||||
~Text(string) => {
|
||||
// TODO: clean this up. Fonts should not be created here.
|
||||
let flib = FontLibrary();
|
||||
let font = flib.get_test_font();
|
||||
let run = TextRun(font, string);
|
||||
Some(TextBox(TextBoxData(copy string, ~[move run])))
|
||||
}
|
||||
~Element(element) => {
|
||||
match (element.kind, display) {
|
||||
(~HTMLImageElement({size}), _) => Some(ImageBox(size)),
|
||||
// (_, Specified(_)) => Some(GenericBox),
|
||||
(_, _) => Some(GenericBox) // TODO: replace this with the commented lines
|
||||
// (_, _) => fail ~"Can't create box for Node with non-specified 'display' type"
|
||||
}
|
||||
}
|
||||
Some(my_box)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,18 +1,21 @@
|
|||
export build_display_list;
|
||||
|
||||
use au = gfx::geometry;
|
||||
use base::Box;
|
||||
use css::values::{BgColor, BgTransparent, Specified};
|
||||
use base::{Box, BTree, ImageHolder, TextBoxKind};
|
||||
use dl = gfx::display_list;
|
||||
use dom::base::{Text, NodeScope};
|
||||
use dom::rcu::Scope;
|
||||
use dvec::DVec;
|
||||
use either::{Left, Right};
|
||||
use geom::point::Point2D;
|
||||
use geom::rect::Rect;
|
||||
use geom::size::Size2D;
|
||||
use gfx::geometry::{au, au_to_px, box, px_to_au};
|
||||
use util::tree;
|
||||
use gfx::geometry::au;
|
||||
use layout::text::TextBoxData;
|
||||
use layout::base::{TextBox, BoxTree};
|
||||
use servo_text::text_run::TextRun;
|
||||
use dvec::DVec;
|
||||
use util::tree;
|
||||
use vec::push;
|
||||
|
||||
#[doc = "
|
||||
|
@ -39,13 +42,13 @@ Builds a display list for a box and all its children.
|
|||
"]
|
||||
fn build_display_list_from_origin(list: dl::DisplayList, box: @Box, origin: Point2D<au>) {
|
||||
let box_origin = Point2D(
|
||||
px_to_au(au_to_px(origin.x) + au_to_px(box.bounds.origin.x)),
|
||||
px_to_au(au_to_px(origin.y) + au_to_px(box.bounds.origin.y)));
|
||||
#debug("Handed origin %?, box has bounds %?, starting with origin %?", origin, copy box.bounds, box_origin);
|
||||
au::from_px(au::to_px(origin.x) + au::to_px(box.data.position.origin.x)),
|
||||
au::from_px(au::to_px(origin.y) + au::to_px(box.data.position.origin.y)));
|
||||
#debug("Handed origin %?, box has bounds %?, starting with origin %?", origin, box.data.position.size, box_origin);
|
||||
|
||||
box_to_display_items(list, box, box_origin);
|
||||
|
||||
for BTree.each_child(box) |c| {
|
||||
for BoxTree.each_child(box) |c| {
|
||||
#debug("Recursively building display list with origin %?", box_origin);
|
||||
build_display_list_from_origin(list, c, box_origin);
|
||||
}
|
||||
|
@ -80,23 +83,23 @@ fn box_to_display_items(list: dl::DisplayList, box: @Box, origin: Point2D<au>) {
|
|||
|
||||
#debug("request to display a box from origin %?", origin);
|
||||
|
||||
let bounds : Rect<au> = Rect(origin, copy box.bounds.size);
|
||||
let bounds : Rect<au> = Rect(origin, copy box.data.position.size);
|
||||
|
||||
match box.kind {
|
||||
TextBoxKind(subbox) => {
|
||||
let runs = &mut subbox.runs;
|
||||
TextBox(d) => {
|
||||
let mut runs = d.runs;
|
||||
list.push(~dl::SolidColor(bounds, 255u8, 255u8, 255u8));
|
||||
|
||||
let mut bounds = bounds;
|
||||
for uint::range(0, runs.len()) |i| {
|
||||
bounds.size.height = runs[i].size().height;
|
||||
let glyph_run = text_run_to_dl_glyph_run(&mut runs[i]);
|
||||
let glyph_run = text_run_to_dl_glyph_run(& runs[i]);
|
||||
list.push(~dl::Glyphs(bounds, glyph_run));
|
||||
bounds.origin.y += bounds.size.height;
|
||||
}
|
||||
return;
|
||||
|
||||
pure fn text_run_to_dl_glyph_run(text_run: &mut TextRun) ->
|
||||
pure fn text_run_to_dl_glyph_run(text_run: &TextRun) ->
|
||||
dl::GlyphRun {
|
||||
dl::GlyphRun {
|
||||
glyphs: copy text_run.glyphs
|
||||
|
@ -109,14 +112,14 @@ fn box_to_display_items(list: dl::DisplayList, box: @Box, origin: Point2D<au>) {
|
|||
};
|
||||
|
||||
// Check if there is a background image, if not set the background color.
|
||||
let image = box.appearance.get_image();
|
||||
let image = box.get_image();
|
||||
|
||||
if image.is_some() {
|
||||
list.push(~dl::Image(bounds, option::unwrap(image)))
|
||||
} else {
|
||||
// DAC
|
||||
// TODO: shouldn't need to unbox CSSValue by now
|
||||
let boxed_color = box.node.get_specified_style().background_color;
|
||||
let boxed_color = box.node.style().background_color;
|
||||
let color = match boxed_color {
|
||||
Specified(BgColor(c)) => c,
|
||||
Specified(BgTransparent) | _ => util::color::rgba(0,0,0,0.0)
|
||||
|
@ -130,18 +133,16 @@ fn box_to_display_items(list: dl::DisplayList, box: @Box, origin: Point2D<au>) {
|
|||
fn should_convert_text_boxes_to_solid_color_background_items() {
|
||||
#[test];
|
||||
|
||||
use layout::box_builder::LayoutTreeBuilder;
|
||||
|
||||
let s = Scope();
|
||||
let n = s.new_node(Text(~"firecracker"));
|
||||
let b = n.construct_boxes().get();
|
||||
let builder = LayoutTreeBuilder();
|
||||
let b = builder.construct_trees(n).get();
|
||||
|
||||
let subbox = match b.kind {
|
||||
TextBoxKind(subbox) => subbox,
|
||||
_ => fail
|
||||
};
|
||||
|
||||
b.reflow_text(subbox);
|
||||
b.reflow_text();
|
||||
let list = DVec();
|
||||
box_to_display_items(list, b, Point2D(px_to_au(0), px_to_au(0)));
|
||||
box_to_display_items(list, b, Point2D(au::from_px(0), au::from_px(0)));
|
||||
|
||||
do list.borrow |l| {
|
||||
match l[0].data {
|
||||
|
@ -153,19 +154,16 @@ fn should_convert_text_boxes_to_solid_color_background_items() {
|
|||
|
||||
fn should_convert_text_boxes_to_text_items() {
|
||||
#[test];
|
||||
use layout::box_builder::LayoutTreeBuilder;
|
||||
|
||||
let s = Scope();
|
||||
let n = s.new_node(Text(~"firecracker"));
|
||||
let b = n.construct_boxes().get();
|
||||
let builder = LayoutTreeBuilder();
|
||||
let b = builder.construct_trees(n).get();
|
||||
|
||||
let subbox = match b.kind {
|
||||
TextBoxKind(subbox) => { subbox },
|
||||
_ => fail
|
||||
};
|
||||
|
||||
b.reflow_text(subbox);
|
||||
b.reflow_text();
|
||||
let list = DVec();
|
||||
box_to_display_items(list, b, Point2D(px_to_au(0), px_to_au(0)));
|
||||
box_to_display_items(list, b, Point2D(au::from_px(0), au::from_px(0)));
|
||||
|
||||
do list.borrow |l| {
|
||||
match l[1].data {
|
||||
|
@ -178,23 +176,20 @@ fn should_convert_text_boxes_to_text_items() {
|
|||
fn should_calculate_the_bounds_of_the_text_box_background_color() {
|
||||
#[test];
|
||||
#[ignore(cfg(target_os = "macos"))];
|
||||
use layout::box_builder::LayoutTreeBuilder;
|
||||
|
||||
let s = Scope();
|
||||
let n = s.new_node(Text(~"firecracker"));
|
||||
let b = n.construct_boxes().get();
|
||||
let builder = LayoutTreeBuilder();
|
||||
let b = builder.construct_trees(n).get();
|
||||
|
||||
let subbox = match b.kind {
|
||||
TextBoxKind(subbox) => { subbox },
|
||||
_ => fail
|
||||
};
|
||||
|
||||
b.reflow_text(subbox);
|
||||
b.reflow_text();
|
||||
let list = DVec();
|
||||
box_to_display_items(list, b, Point2D(px_to_au(0), px_to_au(0)));
|
||||
box_to_display_items(list, b, Point2D(au::from_px(0), au::from_px(0)));
|
||||
|
||||
let expected = Rect(
|
||||
Point2D(px_to_au(0), px_to_au(0)),
|
||||
Size2D(px_to_au(84), px_to_au(20))
|
||||
Point2D(au::from_px(0), au::from_px(0)),
|
||||
Size2D(au::from_px(84), au::from_px(20))
|
||||
);
|
||||
|
||||
do list.borrow |l| { assert l[0].bounds == expected }
|
||||
|
@ -203,23 +198,20 @@ fn should_calculate_the_bounds_of_the_text_box_background_color() {
|
|||
fn should_calculate_the_bounds_of_the_text_items() {
|
||||
#[test];
|
||||
#[ignore(reason = "busted")];
|
||||
use layout::box_builder::LayoutTreeBuilder;
|
||||
|
||||
let s = Scope();
|
||||
let n = s.new_node(Text(~"firecracker"));
|
||||
let b = n.construct_boxes().get();
|
||||
let builder = LayoutTreeBuilder();
|
||||
let b = builder.construct_trees(n).get();
|
||||
|
||||
let subbox = match b.kind {
|
||||
TextBoxKind(subbox) => { subbox },
|
||||
_ => fail
|
||||
};
|
||||
|
||||
b.reflow_text(subbox);
|
||||
b.reflow_text();
|
||||
let list = DVec();
|
||||
box_to_display_items(list, b, Point2D(px_to_au(0), px_to_au(0)));
|
||||
box_to_display_items(list, b, Point2D(au::from_px(0), au::from_px(0)));
|
||||
|
||||
let expected = Rect(
|
||||
Point2D(px_to_au(0), px_to_au(0)),
|
||||
Size2D(px_to_au(84), px_to_au(20))
|
||||
Point2D(au::from_px(0), au::from_px(0)),
|
||||
Size2D(au::from_px(84), au::from_px(20))
|
||||
);
|
||||
|
||||
do list.borrow |l| { assert l[1].bounds == expected; }
|
||||
|
|
|
@ -1,54 +1,125 @@
|
|||
#[doc="Inline layout."]
|
||||
|
||||
use base::{Box, InlineBox, BTree};
|
||||
use au = gfx::geometry;
|
||||
use base::Box;
|
||||
use core::dvec::DVec;
|
||||
use css::values::{BoxAuto, BoxLength, Px};
|
||||
use dom::rcu;
|
||||
use geom::point::Point2D;
|
||||
use geom::size::Size2D;
|
||||
use gfx::geometry::{au, px_to_au};
|
||||
use gfx::geometry::au;
|
||||
use layout::base::{FlowContext, InlineFlow, BoxTree, ImageBox, TextBox, GenericBox};
|
||||
use num::Num;
|
||||
use util::tree;
|
||||
|
||||
trait InlineLayout {
|
||||
fn reflow_inline();
|
||||
struct InlineFlowData {
|
||||
boxes: ~DVec<@Box>
|
||||
}
|
||||
|
||||
#[doc="The main reflow routine for inline layout."]
|
||||
impl @Box : InlineLayout {
|
||||
fn reflow_inline() {
|
||||
assert self.kind == InlineBox;
|
||||
|
||||
#debug["starting reflow inline"];
|
||||
|
||||
// FIXME: This is clownshoes inline layout and is not even close to
|
||||
// correct.
|
||||
let y = 0;
|
||||
let mut x = 0;
|
||||
let mut current_height = 0;
|
||||
|
||||
// loop over children and set them at the proper horizontal offset
|
||||
for tree::each_child(BTree, self) |kid| {
|
||||
kid.bounds.origin = Point2D(au(x), au(y));
|
||||
x += *kid.bounds.size.width;
|
||||
current_height = i32::max(current_height, *kid.bounds.size.height);
|
||||
}
|
||||
|
||||
let height = match self.appearance.height {
|
||||
BoxLength(Px(p)) => px_to_au(p.to_int()),
|
||||
BoxAuto => au(current_height),
|
||||
_ => fail ~"inhereit_height failed, height is neither a Px or auto"
|
||||
};
|
||||
|
||||
let width = match self.appearance.width {
|
||||
BoxLength(Px(p)) => px_to_au(p.to_int()),
|
||||
BoxAuto => au(i32::max(x, *self.bounds.size.width)),
|
||||
_ => fail ~"inhereit_width failed, width is neither a Px or auto"
|
||||
};
|
||||
|
||||
// The maximum available width should have been set in the top-down pass
|
||||
self.bounds.size = Size2D(width, height);
|
||||
|
||||
#debug["reflow_inline size=%?", copy self.bounds];
|
||||
fn InlineFlowData() -> InlineFlowData {
|
||||
InlineFlowData {
|
||||
boxes: ~DVec()
|
||||
}
|
||||
}
|
||||
|
||||
trait InlineLayout {
|
||||
pure fn starts_inline_flow() -> bool;
|
||||
|
||||
pure fn access_inline<T>(fn(&&InlineFlowData) -> T) -> T;
|
||||
fn bubble_widths_inline();
|
||||
fn assign_widths_inline();
|
||||
fn assign_height_inline();
|
||||
}
|
||||
|
||||
impl @FlowContext : InlineLayout {
|
||||
pure fn starts_inline_flow() -> bool { match self.kind { InlineFlow(*) => true, _ => false } }
|
||||
|
||||
pure fn access_inline<T>(cb: fn(&&InlineFlowData) -> T) -> T {
|
||||
match self.kind {
|
||||
InlineFlow(d) => cb(d),
|
||||
_ => fail fmt!("Tried to access() data of InlineFlow, but this is a %?", self.kind)
|
||||
}
|
||||
}
|
||||
|
||||
fn bubble_widths_inline() {
|
||||
assert self.starts_inline_flow();
|
||||
|
||||
let mut min_width = au(0);
|
||||
let mut pref_width = au(0);
|
||||
|
||||
/* TODO: implement a "line sizes" API. Each inline element
|
||||
should report its longest possible chunk (i.e., text run) and
|
||||
shortest chunk (i.e., smallest word or hyphenatable segment).
|
||||
|
||||
Until this exists, pretend that the text is indivisible, just
|
||||
like a replaced element. */
|
||||
|
||||
do self.access_inline |d| {
|
||||
for d.boxes.each |box| {
|
||||
min_width = au::max(min_width, box.get_min_width());
|
||||
pref_width = au::max(pref_width, box.get_pref_width());
|
||||
}
|
||||
}
|
||||
|
||||
self.data.min_width = min_width;
|
||||
self.data.pref_width = pref_width;
|
||||
}
|
||||
|
||||
/* Recursively (top-down) determines the actual width of child
|
||||
contexts and boxes. When called on this context, the context has
|
||||
had its width set by the parent context. */
|
||||
fn assign_widths_inline() {
|
||||
assert self.starts_inline_flow();
|
||||
|
||||
/* Perform inline flow with the available width. */
|
||||
//let avail_width = self.data.position.size.width;
|
||||
|
||||
let line_height = au::from_px(20);
|
||||
//let mut cur_x = au(0);
|
||||
let mut cur_y = au(0);
|
||||
|
||||
do self.access_inline |d| {
|
||||
for d.boxes.each |box| {
|
||||
/* TODO: actually do inline flow.
|
||||
- Create a working linebox, and successively put boxes
|
||||
into it, splitting if necessary.
|
||||
|
||||
- Set width and height for each positioned element based on
|
||||
where its chunks ended up.
|
||||
|
||||
- Save the dvec of this context's lineboxes. */
|
||||
|
||||
box.data.position.size.width = match box.kind {
|
||||
ImageBox(sz) => sz.width,
|
||||
TextBox(d) => d.runs[0].size().width,
|
||||
// TODO: this should be set to the extents of its children
|
||||
GenericBox(*) => au(0)
|
||||
};
|
||||
|
||||
box.data.position.size.height = match box.kind {
|
||||
ImageBox(sz) => sz.height,
|
||||
TextBox(d) => d.runs[0].size().height,
|
||||
// TODO: this should be set to the extents of its children
|
||||
GenericBox(*) => au(0)
|
||||
};
|
||||
|
||||
box.data.position.origin = Point2D(au(0), cur_y);
|
||||
cur_y += au::max(line_height, box.data.position.size.height);
|
||||
} // for boxes.each |box|
|
||||
}
|
||||
|
||||
self.data.position.size.height = cur_y;
|
||||
|
||||
/* There are no child contexts, so stop here. */
|
||||
|
||||
// TODO: once there are 'inline-block' elements, this won't be
|
||||
// true. In that case, perform inline flow, and then set the
|
||||
// block flow context's width as the width of the
|
||||
// 'inline-block' box that created this flow.
|
||||
|
||||
} // fn assign_widths_inline
|
||||
|
||||
fn assign_height_inline() {
|
||||
// Don't need to set box or ctx heights, since that is done
|
||||
// during inline flowing.
|
||||
}
|
||||
|
||||
} // @FlowContext : InlineLayout
|
||||
|
|
|
@ -3,22 +3,24 @@
|
|||
rendered.
|
||||
"];
|
||||
|
||||
use std::arc::ARC;
|
||||
use au = gfx::geometry;
|
||||
use content::content_task;
|
||||
use css::resolve::apply::apply_style;
|
||||
use css::values::Stylesheet;
|
||||
use display_list_builder::build_display_list;
|
||||
use dom::base::Node;
|
||||
use css::values::Stylesheet;
|
||||
use gfx::geometry::px_to_au;
|
||||
use gfx::render_task;
|
||||
use render_task::RenderTask;
|
||||
use layout::base::Box;
|
||||
use resource::image_cache_task::ImageCacheTask;
|
||||
use std::net::url::Url;
|
||||
use css::resolve::apply::apply_style;
|
||||
use dom::event::{Event, ReflowEvent};
|
||||
use content::content_task;
|
||||
use gfx::render_task;
|
||||
use layout::base::Box;
|
||||
use layout::box_builder::LayoutTreeBuilder;
|
||||
use render_task::RenderTask;
|
||||
use resource::image_cache_task::ImageCacheTask;
|
||||
use std::arc::ARC;
|
||||
use std::net::url::Url;
|
||||
|
||||
use task::*;
|
||||
use layout::traverse::*;
|
||||
use comm::*;
|
||||
use task::*;
|
||||
|
||||
type LayoutTask = Chan<Msg>;
|
||||
|
||||
|
@ -38,11 +40,12 @@ fn LayoutTask(render_task: RenderTask, image_cache_task: ImageCacheTask) -> Layo
|
|||
match request.recv() {
|
||||
PingMsg(ping_channel) => ping_channel.send(content_task::PongMsg),
|
||||
ExitMsg => {
|
||||
#debug("layout: ExitMsg received");
|
||||
debug!("layout: ExitMsg received");
|
||||
break;
|
||||
}
|
||||
BuildMsg(node, styles, doc_url, event_chan) => {
|
||||
#debug("layout: received layout request for:");
|
||||
debug!("layout: received layout request for: %s", doc_url.to_str());
|
||||
debug!("layout: parsed Node tree");
|
||||
node.dump();
|
||||
|
||||
do util::time::time(~"layout") {
|
||||
|
@ -50,18 +53,29 @@ fn LayoutTask(render_task: RenderTask, image_cache_task: ImageCacheTask) -> Layo
|
|||
node.recompute_style_for_subtree(styles);
|
||||
|
||||
let root_box: @Box;
|
||||
match node.construct_boxes() {
|
||||
None => fail ~"Root node should always exist; did it get 'display: none' somehow?",
|
||||
Some(root) => root_box = root
|
||||
let builder = LayoutTreeBuilder();
|
||||
match builder.construct_trees(node) {
|
||||
Ok(root) => root_box = root,
|
||||
Err(*) => fail ~"Root node should always exist"
|
||||
}
|
||||
|
||||
|
||||
debug!("layout: constructed Box tree");
|
||||
root_box.dump();
|
||||
|
||||
let reflow: fn~() = || event_chan.send(ReflowEvent);
|
||||
debug!("layout: constructed Flow tree");
|
||||
root_box.ctx.dump();
|
||||
|
||||
apply_style(root_box, &doc_url, image_cache_task, reflow);
|
||||
/* resolve styles (convert relative values) down the box tree */
|
||||
let reflow_cb: fn~() = || event_chan.send(ReflowEvent);
|
||||
apply_style(root_box, &doc_url, image_cache_task, reflow_cb);
|
||||
|
||||
root_box.reflow_subtree(px_to_au(800));
|
||||
/* perform layout passes over the flow tree */
|
||||
let root_flow = root_box.ctx;
|
||||
do root_flow.traverse_postorder |f| { f.bubble_widths() }
|
||||
root_flow.data.position.origin = au::zero_point();
|
||||
root_flow.data.position.size.width = au::from_px(800); // TODO: window/frame size
|
||||
do root_flow.traverse_preorder |f| { f.assign_widths() }
|
||||
do root_flow.traverse_postorder |f| { f.assign_height() }
|
||||
|
||||
let dlist = build_display_list(root_box);
|
||||
render_task.send(render_task::RenderMsg(dlist));
|
||||
|
|
62
src/servo/layout/root.rs
Normal file
62
src/servo/layout/root.rs
Normal file
|
@ -0,0 +1,62 @@
|
|||
use au = gfx::geometry;
|
||||
use css::values::*;
|
||||
use geom::point::Point2D;
|
||||
use geom::size::Size2D;
|
||||
use gfx::geometry::au;
|
||||
use layout::base::{Box, FlowContext, FlowTree, InlineBlockFlow, BlockFlow, RootFlow};
|
||||
use util::tree;
|
||||
|
||||
struct RootFlowData {
|
||||
mut box: Option<@Box>
|
||||
}
|
||||
|
||||
fn RootFlowData() -> RootFlowData {
|
||||
RootFlowData {
|
||||
box: None
|
||||
}
|
||||
}
|
||||
|
||||
trait RootLayout {
|
||||
pure fn starts_root_flow() -> bool;
|
||||
pure fn access_root<T>(fn(&&RootFlowData) -> T) -> T;
|
||||
|
||||
fn bubble_widths_root();
|
||||
fn assign_widths_root();
|
||||
fn assign_height_root();
|
||||
}
|
||||
|
||||
impl @FlowContext : RootLayout {
|
||||
|
||||
pure fn starts_root_flow() -> bool {
|
||||
match self.kind {
|
||||
RootFlow(*) => true,
|
||||
_ => false
|
||||
}
|
||||
}
|
||||
|
||||
pure fn access_root<T>(cb:fn(&&RootFlowData) -> T) -> T {
|
||||
match self.kind {
|
||||
RootFlow(d) => cb(d),
|
||||
_ => fail fmt!("Tried to access() data of RootFlow, but this is a %?", self.kind)
|
||||
}
|
||||
}
|
||||
|
||||
/* defer to the block algorithm */
|
||||
fn bubble_widths_root() {
|
||||
assert self.starts_root_flow();
|
||||
self.bubble_widths_block()
|
||||
}
|
||||
|
||||
fn assign_widths_root() {
|
||||
assert self.starts_root_flow();
|
||||
|
||||
/* TODO: should determine frame width here, not in
|
||||
LayoutTask. Until then, defer to block. */
|
||||
self.assign_widths_block() }
|
||||
|
||||
fn assign_height_root() {
|
||||
assert self.starts_root_flow();
|
||||
|
||||
self.assign_height_block();
|
||||
}
|
||||
}
|
|
@ -1,32 +1,33 @@
|
|||
#[doc="Text layout."]
|
||||
|
||||
use au = gfx::geometry;
|
||||
use geom::size::Size2D;
|
||||
use gfx::geometry::{au, px_to_au};
|
||||
use gfx::geometry::au;
|
||||
use servo_text::text_run::TextRun;
|
||||
use servo_text::font_library::FontLibrary;
|
||||
use base::{Box, TextBoxKind};
|
||||
use layout::base::{TextBox, Box};
|
||||
|
||||
struct TextBox {
|
||||
struct TextBoxData {
|
||||
text: ~str,
|
||||
mut runs: ~[TextRun],
|
||||
mut runs: ~[TextRun]
|
||||
}
|
||||
|
||||
fn TextBox(text: ~str) -> TextBox {
|
||||
TextBox {
|
||||
fn TextBoxData(text: ~str, runs: ~[TextRun]) -> TextBoxData {
|
||||
TextBoxData {
|
||||
text: text,
|
||||
runs: ~[],
|
||||
runs: runs
|
||||
}
|
||||
}
|
||||
|
||||
trait TextLayout {
|
||||
fn reflow_text(subbox: @TextBox);
|
||||
fn reflow_text();
|
||||
}
|
||||
|
||||
#[doc="The main reflow routine for text layout."]
|
||||
impl @Box : TextLayout {
|
||||
fn reflow_text(subbox: @TextBox) {
|
||||
match self.kind {
|
||||
TextBoxKind(*) => { /* ok */ }
|
||||
fn reflow_text() {
|
||||
let d = match self.kind {
|
||||
TextBox(d) => { d }
|
||||
_ => { fail ~"expected text box in reflow_text!" }
|
||||
};
|
||||
|
||||
|
@ -35,9 +36,9 @@ impl @Box : TextLayout {
|
|||
let font = flib.get_test_font();
|
||||
|
||||
// Do line breaking.
|
||||
let mut current = TextRun(font, subbox.text);
|
||||
let mut current = TextRun(font, d.text);
|
||||
let mut lines = dvec::DVec();
|
||||
let mut width_left = px_to_au(800);
|
||||
let mut width_left = au::from_px(800);
|
||||
let mut max_width = au(0);
|
||||
|
||||
while current.size().width > width_left {
|
||||
|
@ -73,8 +74,8 @@ impl @Box : TextLayout {
|
|||
let total_height = au(*current.size().height * line_count);
|
||||
lines.push(move current);
|
||||
|
||||
self.bounds.size = Size2D(max_width, total_height);
|
||||
subbox.runs = dvec::unwrap(lines);
|
||||
self.data.position.size = Size2D(max_width, total_height);
|
||||
d.runs = move dvec::unwrap(lines);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -82,20 +83,18 @@ fn should_calculate_the_size_of_the_text_box() {
|
|||
#[test];
|
||||
#[ignore(cfg(target_os = "macos"))];
|
||||
|
||||
use au = gfx::geometry;
|
||||
use dom::rcu::{Scope};
|
||||
use dom::base::{Text, NodeScope};
|
||||
use util::tree;
|
||||
use gfx::geometry::px_to_au;
|
||||
use layout::box_builder::LayoutTreeBuilder;
|
||||
|
||||
let s = Scope();
|
||||
let n = s.new_node(Text(~"firecracker"));
|
||||
let b = n.construct_boxes().get();
|
||||
let builder = LayoutTreeBuilder();
|
||||
let b = builder.construct_trees(n).get();
|
||||
|
||||
let subbox = match b.kind {
|
||||
TextBoxKind(subbox) => { subbox },
|
||||
_ => fail
|
||||
};
|
||||
b.reflow_text(subbox);
|
||||
let expected = Size2D(px_to_au(84), px_to_au(20));
|
||||
assert b.bounds.size == expected;
|
||||
b.reflow_text();
|
||||
let expected = Size2D(au::from_px(84), au::from_px(20));
|
||||
assert b.data.position.size == expected;
|
||||
}
|
||||
|
|
32
src/servo/layout/traverse.rs
Normal file
32
src/servo/layout/traverse.rs
Normal file
|
@ -0,0 +1,32 @@
|
|||
/** Interface for running tree-based traversals over layout boxes and contextsg */
|
||||
|
||||
use layout::base::{Box, BoxTree};
|
||||
use layout::base::{FlowContext, FlowTree};
|
||||
|
||||
trait BoxTraversals {
|
||||
fn traverse_preorder(preorder_cb: ~fn(@Box));
|
||||
}
|
||||
|
||||
impl @Box : BoxTraversals {
|
||||
fn traverse_preorder(preorder_cb: ~fn(@Box)) {
|
||||
preorder_cb(self);
|
||||
do BoxTree.each_child(self) |child| { child.traverse_preorder(preorder_cb); true }
|
||||
}
|
||||
}
|
||||
|
||||
trait FlowContextTraversals {
|
||||
fn traverse_preorder(preorder_cb: ~fn(@FlowContext));
|
||||
fn traverse_postorder(postorder_cb: ~fn(@FlowContext));
|
||||
}
|
||||
|
||||
impl @FlowContext : FlowContextTraversals {
|
||||
fn traverse_preorder(preorder_cb: ~fn(@FlowContext)) {
|
||||
preorder_cb(self);
|
||||
do FlowTree.each_child(self) |child| { child.traverse_preorder(preorder_cb); true }
|
||||
}
|
||||
|
||||
fn traverse_postorder(postorder_cb: ~fn(@FlowContext)) {
|
||||
do FlowTree.each_child(self) |child| { child.traverse_postorder(postorder_cb); true }
|
||||
postorder_cb(self);
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
#[doc = "Interface for running tree-based traversals over layout boxes"]
|
||||
|
||||
use base::{Box, BTree, NodeMethods};
|
||||
#[doc = "Interface for running tree-based traversals over layout boxes"]
|
||||
use base::{Box, BoxTree};
|
||||
use intrinsic::TyDesc;
|
||||
|
||||
export full_traversal;
|
||||
|
@ -67,7 +67,7 @@ fn traverse_helper<T : Copy Send>(-root : @Box, returned : T, -top_down : fn~(+T
|
|||
// current task will block until all of it's children return,
|
||||
// so the original owner of the @-box will not exit while the
|
||||
// children are still live.
|
||||
for BTree.each_child(root) |kid| {
|
||||
for BoxTree.each_child(root) |kid| {
|
||||
count += 1;
|
||||
|
||||
// Unwrap the box so we can send it out of this task
|
||||
|
|
|
@ -58,7 +58,9 @@ mod layout {
|
|||
mod display_list_builder;
|
||||
mod inline;
|
||||
mod layout_task;
|
||||
mod root;
|
||||
mod text;
|
||||
mod traverse;
|
||||
mod traverse_parallel;
|
||||
}
|
||||
|
||||
|
@ -73,6 +75,7 @@ mod gfx {
|
|||
|
||||
mod image {
|
||||
mod base;
|
||||
mod holder;
|
||||
mod encode {
|
||||
mod tga;
|
||||
}
|
||||
|
@ -97,17 +100,17 @@ mod text {
|
|||
export font;
|
||||
export shaper;
|
||||
|
||||
mod glyph;
|
||||
mod text_run;
|
||||
mod font;
|
||||
mod font_library;
|
||||
mod shaper;
|
||||
mod glyph;
|
||||
mod native_font {
|
||||
#[cfg(target_os = "macos")]
|
||||
mod quartz_native_font;
|
||||
#[cfg(target_os = "linux")]
|
||||
mod ft_native_font;
|
||||
}
|
||||
mod shaper;
|
||||
mod text_run;
|
||||
mod util;
|
||||
}
|
||||
|
||||
|
|
|
@ -2,12 +2,13 @@ extern mod harfbuzz;
|
|||
|
||||
export shape_text;
|
||||
|
||||
use au = gfx::geometry;
|
||||
use libc::types::common::c99::int32_t;
|
||||
use libc::{c_uint, c_int, c_void, c_char};
|
||||
use font::Font;
|
||||
use glyph::{Glyph, GlyphPos};
|
||||
use ptr::{null, addr_of, offset};
|
||||
use gfx::geometry::{au, px_to_au};
|
||||
use gfx::geometry::au;
|
||||
use geom::point::Point2D;
|
||||
use font_library::FontLibrary;
|
||||
|
||||
|
@ -137,10 +138,10 @@ extern fn glyph_h_advance_func(_font: *hb_font_t,
|
|||
}
|
||||
|
||||
fn hb_glyph_pos_to_servo_glyph_pos(hb_pos: &hb_glyph_position_t) -> GlyphPos {
|
||||
GlyphPos(Point2D(px_to_au(hb_pos.x_advance as int),
|
||||
px_to_au(hb_pos.y_advance as int)),
|
||||
Point2D(px_to_au(hb_pos.x_offset as int),
|
||||
px_to_au(hb_pos.y_offset as int)))
|
||||
GlyphPos(Point2D(au::from_px(hb_pos.x_advance as int),
|
||||
au::from_px(hb_pos.y_advance as int)),
|
||||
Point2D(au::from_px(hb_pos.x_offset as int),
|
||||
au::from_px(hb_pos.y_offset as int)))
|
||||
}
|
||||
|
||||
fn should_get_glyph_indexes() {
|
||||
|
@ -162,6 +163,6 @@ fn should_get_glyph_h_advance() {
|
|||
let font = lib.get_test_font();
|
||||
let glyphs = shape_text(font, ~"firecracker");
|
||||
let actual = glyphs.map(|g| g.pos.advance.x);
|
||||
let expected = (~[6, 4, 7, 9, 8, 7, 10, 8, 9, 9, 7]).map(|a| px_to_au(a));
|
||||
let expected = (~[6, 4, 7, 9, 8, 7, 10, 8, 9, 9, 7]).map(|a| au::from_px(a));
|
||||
assert expected == actual;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use au = gfx::geometry;
|
||||
use geom::point::Point2D;
|
||||
use geom::size::Size2D;
|
||||
use gfx::geometry::{au, px_to_au};
|
||||
use gfx::geometry::au;
|
||||
use libc::{c_void};
|
||||
use font_library::FontLibrary;
|
||||
use font::Font;
|
||||
|
@ -9,7 +10,7 @@ use shaper::shape_text;
|
|||
|
||||
/// A single, unbroken line of text
|
||||
struct TextRun {
|
||||
priv text: ~str,
|
||||
text: ~str,
|
||||
priv glyphs: ~[Glyph],
|
||||
priv size_: Size2D<au>,
|
||||
priv min_break_width_: au,
|
||||
|
@ -76,8 +77,8 @@ fn TextRun(font: &Font, +text: ~str) -> TextRun {
|
|||
}
|
||||
|
||||
fn glyph_run_size(glyphs: &[Glyph]) -> Size2D<au> {
|
||||
let height = px_to_au(20);
|
||||
let pen_start_x = px_to_au(0);
|
||||
let height = au::from_px(20);
|
||||
let pen_start_x = au::from_px(0);
|
||||
let pen_start_y = height;
|
||||
let pen_start = Point2D(pen_start_x, pen_start_y);
|
||||
let pen_end = glyphs.foldl(pen_start, |cur, glyph| {
|
||||
|
@ -137,7 +138,7 @@ fn test_calc_min_break_width1() {
|
|||
let flib = FontLibrary();
|
||||
let font = flib.get_test_font();
|
||||
let actual = calc_min_break_width(font, ~"firecracker");
|
||||
let expected = px_to_au(84);
|
||||
let expected = au::from_px(84);
|
||||
assert expected == actual;
|
||||
}
|
||||
|
||||
|
@ -146,7 +147,7 @@ fn test_calc_min_break_width2() {
|
|||
let flib = FontLibrary();
|
||||
let font = flib.get_test_font();
|
||||
let actual = calc_min_break_width(font, ~"firecracker yumyum");
|
||||
let expected = px_to_au(84);
|
||||
let expected = au::from_px(84);
|
||||
assert expected == actual;
|
||||
}
|
||||
|
||||
|
@ -155,7 +156,7 @@ fn test_calc_min_break_width3() {
|
|||
let flib = FontLibrary();
|
||||
let font = flib.get_test_font();
|
||||
let actual = calc_min_break_width(font, ~"yumyum firecracker");
|
||||
let expected = px_to_au(84);
|
||||
let expected = au::from_px(84);
|
||||
assert expected == actual;
|
||||
}
|
||||
|
||||
|
@ -164,7 +165,7 @@ fn test_calc_min_break_width4() {
|
|||
let flib = FontLibrary();
|
||||
let font = flib.get_test_font();
|
||||
let actual = calc_min_break_width(font, ~"yumyum firecracker yumyum");
|
||||
let expected = px_to_au(84);
|
||||
let expected = au::from_px(84);
|
||||
assert expected == actual;
|
||||
}
|
||||
|
||||
|
@ -237,7 +238,7 @@ fn test_split3() {
|
|||
let flib = FontLibrary();
|
||||
let font = flib.get_test_font();
|
||||
let run = TextRun(font, ~"firecracker firecracker");
|
||||
let break_runs = run.split(font, run.min_break_width() + px_to_au(10));
|
||||
let break_runs = run.split(font, run.min_break_width() + au::from_px(10));
|
||||
assert break_runs.first().text == ~"firecracker";
|
||||
assert break_runs.second().text == ~"firecracker";
|
||||
|
||||
|
@ -249,7 +250,7 @@ fn should_calculate_the_total_size() {
|
|||
let flib = FontLibrary();
|
||||
let font = flib.get_test_font();
|
||||
let run = TextRun(font, ~"firecracker");
|
||||
let expected = Size2D(px_to_au(84), px_to_au(20));
|
||||
let expected = Size2D(au::from_px(84), au::from_px(20));
|
||||
assert run.size() == expected;
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue