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:
Brian J. Burg 2012-09-12 09:52:01 -07:00
parent 4e3e5a879d
commit 1095c9e4ff
24 changed files with 1205 additions and 832 deletions

View file

@ -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. */
}

View file

@ -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]);
}
}

View file

@ -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);

View file

@ -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>;

View file

@ -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?"
}

View file

@ -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
}

View file

@ -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),

View file

@ -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,

View file

@ -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
View 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
View 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;
}
}
}

View file

@ -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
}

View file

@ -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();
}
}
}

View file

@ -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)
}
}
}

View file

@ -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; }

View file

@ -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

View file

@ -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
View 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();
}
}

View file

@ -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;
}

View 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);
}
}

View file

@ -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

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;
}