mirror of
https://github.com/servo/servo.git
synced 2025-08-12 08:55:32 +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."]
|
#[doc="Applies the appropriate CSS style to boxes."]
|
||||||
|
|
||||||
use gfx::geometry::au_to_px;
|
use au = gfx::geometry;
|
||||||
use layout::base::{Box, BTree, NTree, LayoutData, SpecifiedStyle, ImageHolder,
|
use layout::base::{Box, SpecifiedStyle};
|
||||||
BlockBox, InlineBox, IntrinsicBox, TextBox};
|
use layout::traverse_parallel::top_down_traversal;
|
||||||
use layout::traverse_parallel::{top_down_traversal};
|
use image::ImageHolder;
|
||||||
use std::net::url::Url;
|
|
||||||
use resource::image_cache_task::ImageCacheTask;
|
use resource::image_cache_task::ImageCacheTask;
|
||||||
|
use std::net::url::Url;
|
||||||
|
|
||||||
use css::values::*;
|
use css::values::*;
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ struct StyleApplicator {
|
||||||
reflow: fn~(),
|
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~()) {
|
fn apply_style(box: @Box, doc_url: &Url, image_cache_task: ImageCacheTask, reflow: fn~()) {
|
||||||
let applicator = StyleApplicator {
|
let applicator = StyleApplicator {
|
||||||
box: box,
|
box: box,
|
||||||
|
@ -61,24 +61,19 @@ fn inheritance_wrapper(box : @Box, doc_url: &Url, image_cache_task: ImageCacheTa
|
||||||
reflow: reflow
|
reflow: reflow
|
||||||
};
|
};
|
||||||
applicator.apply_style();
|
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) {
|
fn resolve_fontsize(box : @Box) {
|
||||||
// TODO: complete this
|
// TODO: complete this
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc="Compute the specified height of a layout box based on it's css specification and its
|
fn resolve_height(box : @Box) -> au {
|
||||||
parent's height."]
|
let style = box.node.get_style();
|
||||||
fn inherit_height(box : @Box) {
|
|
||||||
let style = box.node.get_specified_style();
|
|
||||||
let inherit_val = match box.tree.parent {
|
let inherit_val = match box.tree.parent {
|
||||||
None => style.height.initial(),
|
None => au(0),
|
||||||
Some(node) => node.appearance.height
|
Some(parent) => parent.data.computed_size.height
|
||||||
};
|
};
|
||||||
|
|
||||||
box.appearance.height = match style.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
|
fn resolve_width(box : @Box) {
|
||||||
parent's width."]
|
|
||||||
fn inherit_width(box : @Box) {
|
|
||||||
let style = box.node.get_specified_style();
|
let style = box.node.get_specified_style();
|
||||||
let inherit_val = match box.tree.parent {
|
let inherit_val = match box.tree.parent {
|
||||||
None => style.height.initial(),
|
None => style.height.initial(),
|
||||||
|
@ -108,7 +101,7 @@ fn inherit_width(box : @Box) {
|
||||||
BoxLength(Em(n)) => BoxLength(Px(n * box.appearance.font_size.abs()))
|
BoxLength(Em(n)) => BoxLength(Px(n * box.appearance.font_size.abs()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
|
|
||||||
impl StyleApplicator {
|
impl StyleApplicator {
|
||||||
fn apply_css_style() {
|
fn apply_css_style() {
|
||||||
|
@ -141,7 +134,7 @@ impl StyleApplicator {
|
||||||
// FIXME: Some sort of BASE HREF support!
|
// FIXME: Some sort of BASE HREF support!
|
||||||
// FIXME: Parse URLs!
|
// FIXME: Parse URLs!
|
||||||
let new_url = make_url(option::unwrap(url), Some(copy *self.doc_url));
|
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. */ }
|
_ => { /* Ignore. */ }
|
||||||
|
@ -155,57 +148,5 @@ impl StyleApplicator {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use dom::base::{Attr, HTMLDivElement, HTMLHeadElement, HTMLImageElement, ElementData};
|
/* TODO: rewrite once cascade and resolve written. */
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -167,12 +167,12 @@ impl Node : PrivStyleMethods {
|
||||||
fn update_style(decl : StyleDeclaration) {
|
fn update_style(decl : StyleDeclaration) {
|
||||||
self.aux(|layout| {
|
self.aux(|layout| {
|
||||||
match decl {
|
match decl {
|
||||||
BackgroundColor(col) => layout.specified_style.background_color = col,
|
BackgroundColor(col) => layout.style.background_color = col,
|
||||||
Display(dis) => layout.specified_style.display_type = dis,
|
Display(dis) => layout.style.display_type = dis,
|
||||||
FontSize(size) => layout.specified_style.font_size = size,
|
FontSize(size) => layout.style.font_size = size,
|
||||||
Height(size) => layout.specified_style.height = size,
|
Height(size) => layout.style.height = size,
|
||||||
Color(col) => layout.specified_style.text_color = col,
|
Color(col) => layout.style.text_color = col,
|
||||||
Width(size) => layout.specified_style.width = size
|
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 css::values::Stylesheet;
|
||||||
use dom::base::{HTMLDivElement, HTMLHeadElement, HTMLImageElement, UnknownElement, HTMLScriptElement};
|
use dom::base::{HTMLDivElement, HTMLHeadElement, HTMLImageElement, UnknownElement, HTMLScriptElement};
|
||||||
use dom::base::{Comment, Doctype, Element, Node, NodeKind, Text};
|
use dom::base::{Comment, Doctype, Element, Node, NodeKind, Text};
|
||||||
|
use dom::base::{LayoutData};
|
||||||
use util::color::{Color, rgb};
|
use util::color::{Color, rgb};
|
||||||
use util::color::css_colors::{white, black};
|
use util::color::css_colors::{white, black};
|
||||||
use layout::base::{LayoutData, NTree};
|
use dom::base::NodeTree;
|
||||||
|
|
||||||
type SpecifiedStyle = {mut background_color : CSSValue<CSSBackgroundColor>,
|
type SpecifiedStyle = {mut background_color : CSSValue<CSSBackgroundColor>,
|
||||||
mut display_type : CSSValue<CSSDisplay>,
|
mut display_type : CSSValue<CSSDisplay>,
|
||||||
|
@ -97,7 +98,7 @@ impl Node : StylePriv {
|
||||||
if !self.has_aux() {
|
if !self.has_aux() {
|
||||||
let node_kind = self.read(|n| copy *n.kind);
|
let node_kind = self.read(|n| copy *n.kind);
|
||||||
let the_layout_data = @LayoutData({
|
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
|
mut box : None
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -112,7 +113,7 @@ impl Node : StylePriv {
|
||||||
|
|
||||||
trait StyleMethods {
|
trait StyleMethods {
|
||||||
fn initialize_style_for_subtree() -> ~[@LayoutData];
|
fn initialize_style_for_subtree() -> ~[@LayoutData];
|
||||||
fn get_specified_style() -> SpecifiedStyle;
|
fn style() -> SpecifiedStyle;
|
||||||
fn recompute_style_for_subtree(styles : ARC<Stylesheet>);
|
fn recompute_style_for_subtree(styles : ARC<Stylesheet>);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,7 +122,7 @@ impl Node : StyleMethods {
|
||||||
fn initialize_style_for_subtree() -> ~[@LayoutData] {
|
fn initialize_style_for_subtree() -> ~[@LayoutData] {
|
||||||
let mut handles = self.initialize_style();
|
let mut handles = self.initialize_style();
|
||||||
|
|
||||||
for NTree.each_child(self) |kid| {
|
for NodeTree.each_child(self) |kid| {
|
||||||
handles += kid.initialize_style_for_subtree();
|
handles += kid.initialize_style_for_subtree();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,11 +135,11 @@ impl Node : StyleMethods {
|
||||||
|
|
||||||
TODO: Return a safe reference; don't copy.
|
TODO: Return a safe reference; don't copy.
|
||||||
"]
|
"]
|
||||||
fn get_specified_style() -> SpecifiedStyle {
|
fn style() -> SpecifiedStyle {
|
||||||
if !self.has_aux() {
|
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="
|
#[doc="
|
||||||
|
@ -152,7 +153,7 @@ impl Node : StyleMethods {
|
||||||
let mut i = 0u;
|
let mut i = 0u;
|
||||||
|
|
||||||
// Compute the styles of each of our children in parallel
|
// 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;
|
i = i + 1u;
|
||||||
let new_styles = clone(&styles);
|
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."]
|
#[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 comm::{Port, Chan};
|
||||||
use content::content_task::{ControlMsg, Timer};
|
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 {
|
enum TimerControlMsg {
|
||||||
Fire(~dom::bindings::window::TimerData),
|
Fire(~dom::bindings::window::TimerData),
|
||||||
|
@ -66,6 +67,20 @@ enum NodeData = {
|
||||||
kind: ~NodeKind,
|
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 {
|
enum NodeKind {
|
||||||
Doctype(DoctypeData),
|
Doctype(DoctypeData),
|
||||||
Comment(~str),
|
Comment(~str),
|
||||||
|
@ -148,10 +163,16 @@ enum ElementKind {
|
||||||
HTMLScriptElement
|
HTMLScriptElement
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc="
|
|
||||||
The rd_aux data is a (weak) pointer to the layout data, which contains the CSS info as well as
|
/** The RCU rd_aux data is a (weak) pointer to the layout data,
|
||||||
the primary box. Note that there may be multiple boxes per DOM node.
|
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>;
|
type Node = rcu::Handle<NodeData, LayoutData>;
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use au = gfx::geometry;
|
||||||
use js::rust::{bare_compartment, methods, jsobj};
|
use js::rust::{bare_compartment, methods, jsobj};
|
||||||
use js::{JS_ARGV, JSCLASS_HAS_RESERVED_SLOTS, JSPROP_ENUMERATE, JSPROP_SHARED, JSVAL_NULL,
|
use js::{JS_ARGV, JSCLASS_HAS_RESERVED_SLOTS, JSPROP_ENUMERATE, JSPROP_SHARED, JSVAL_NULL,
|
||||||
JS_THIS_OBJECT, JS_SET_RVAL, JSPROP_NATIVE_ACCESSORS};
|
JS_THIS_OBJECT, JS_SET_RVAL, JSPROP_NATIVE_ACCESSORS};
|
||||||
|
@ -17,7 +18,6 @@ use ptr::null;
|
||||||
use node::unwrap;
|
use node::unwrap;
|
||||||
use dom::base::{HTMLImageElement, HTMLScriptElement, HTMLHeadElement, HTMLDivElement,
|
use dom::base::{HTMLImageElement, HTMLScriptElement, HTMLHeadElement, HTMLDivElement,
|
||||||
UnknownElement};
|
UnknownElement};
|
||||||
use gfx::geometry::{au_to_px, px_to_au};
|
|
||||||
|
|
||||||
extern fn finalize(_fop: *JSFreeOp, obj: *JSObject) {
|
extern fn finalize(_fop: *JSFreeOp, obj: *JSObject) {
|
||||||
#debug("element finalize!");
|
#debug("element finalize!");
|
||||||
|
@ -81,7 +81,7 @@ extern fn HTMLImageElement_getWidth(cx: *JSContext, _argc: c_uint, vp: *mut jsva
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
*vp = RUST_INT_TO_JSVAL(
|
*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;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,7 +99,7 @@ extern fn HTMLImageElement_setWidth(cx: *JSContext, _argc: c_uint, vp: *mut jsva
|
||||||
match ed.kind {
|
match ed.kind {
|
||||||
~HTMLImageElement(img) => {
|
~HTMLImageElement(img) => {
|
||||||
let arg = ptr::offset(JS_ARGV(cx, unsafe::reinterpret_cast(&vp)), 0);
|
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?"
|
_ => fail ~"why is this not an image element?"
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,52 +14,49 @@ impl au : Num {
|
||||||
pure fn neg() -> au { au(-*self) }
|
pure fn neg() -> au { au(-*self) }
|
||||||
|
|
||||||
pure fn to_int() -> int { *self as int }
|
pure fn to_int() -> int { *self as int }
|
||||||
|
|
||||||
static pure fn from_int(n: int) -> au {
|
static pure fn from_int(n: int) -> au {
|
||||||
au((n & (i32::max_value as int)) as i32)
|
au((n & (i32::max_value as int)) as i32)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl au : cmp::Eq {
|
impl au : cmp::Ord {
|
||||||
pure fn eq(&&other: au) -> bool {
|
pure fn lt(&&other: au) -> bool { *self < *other }
|
||||||
*self == *other
|
pure fn le(&&other: au) -> bool { *self <= *other }
|
||||||
}
|
pure fn ge(&&other: au) -> bool { *self >= *other }
|
||||||
pure fn ne(&&other: au) -> bool {
|
pure fn gt(&&other: au) -> bool { *self > *other }
|
||||||
*self != *other
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl au : cmp::Ord {
|
impl au : cmp::Eq {
|
||||||
pure fn lt(&&other: au) -> bool {
|
pure fn eq(&&other: au) -> bool { *self == *other }
|
||||||
*self < *other
|
pure fn ne(&&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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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> {
|
fn box<A:Copy Num>(x: A, y: A, w: A, h: A) -> Rect<A> {
|
||||||
Rect(Point2D(x, y), Size2D(w, h))
|
Rect(Point2D(x, y), Size2D(w, h))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn zero_rect_au() -> Rect<au> {
|
fn zero_rect() -> Rect<au> {
|
||||||
let z = au(0);
|
let z = au(0);
|
||||||
Rect(Point2D(z, z), Size2D(z, z))
|
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))
|
Size2D(au(0), au(0))
|
||||||
}
|
}
|
||||||
|
|
||||||
pure fn px_to_au(i: int) -> au {
|
pure fn from_px(i: int) -> au {
|
||||||
from_int(i * 60)
|
from_int(i * 60)
|
||||||
}
|
}
|
||||||
|
|
||||||
pure fn au_to_px(au: au) -> int {
|
pure fn to_px(au: au) -> int {
|
||||||
(*au / 60) as int
|
(*au / 60) as int
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
|
use au = geometry;
|
||||||
|
use au::au;
|
||||||
use platform::osmain;
|
use platform::osmain;
|
||||||
use geometry::*;
|
|
||||||
use comm::*;
|
use comm::*;
|
||||||
use image::base::Image;
|
use image::base::Image;
|
||||||
use dl = display_list;
|
use dl = display_list;
|
||||||
|
@ -88,8 +89,8 @@ trait ToAzureRect {
|
||||||
|
|
||||||
impl Rect<au> : ToAzureRect {
|
impl Rect<au> : ToAzureRect {
|
||||||
fn to_azure_rect() -> Rect<AzFloat> {
|
fn to_azure_rect() -> Rect<AzFloat> {
|
||||||
Rect(Point2D(au_to_px(self.origin.x) as AzFloat, au_to_px(self.origin.y) 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))
|
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 = {
|
let azglyph: AzGlyph = {
|
||||||
mIndex: glyph.index as uint32_t,
|
mIndex: glyph.index as uint32_t,
|
||||||
mPosition: {
|
mPosition: {
|
||||||
x: au_to_px(origin.x.add(glyph.pos.offset.x)) 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
|
y: au::to_px(origin.y.add(glyph.pos.offset.y)) as AzFloat
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
origin = Point2D(origin.x.add(glyph.pos.advance.x),
|
origin = Point2D(origin.x.add(glyph.pos.advance.x),
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
#[doc="Constructs a DOM tree from an incoming token stream."]
|
#[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,
|
use dom::base::{Attr, Element, ElementData, ElementKind, HTMLDivElement, HTMLHeadElement,
|
||||||
HTMLScriptElement};
|
HTMLScriptElement};
|
||||||
use dom::base::{HTMLImageElement, Node, NodeScope, Text, UnknownElement};
|
use dom::base::{HTMLImageElement, Node, NodeScope, Text, UnknownElement};
|
||||||
use geom::size::Size2D;
|
use geom::size::Size2D;
|
||||||
use gfx::geometry;
|
|
||||||
use gfx::geometry::au;
|
|
||||||
use html::lexer;
|
use html::lexer;
|
||||||
use html::lexer::Token;
|
use html::lexer::Token;
|
||||||
use css::values::Stylesheet;
|
use css::values::Stylesheet;
|
||||||
|
@ -37,7 +37,7 @@ fn link_up_attribute(scope: NodeScope, node: Node, -key: ~str, -value: ~str) {
|
||||||
None => {
|
None => {
|
||||||
// Drop on the floor.
|
// 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" => {
|
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.
|
// Drop on the floor.
|
||||||
}
|
}
|
||||||
Some(s) => {
|
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 {
|
match tag_name {
|
||||||
~"div" => ~HTMLDivElement,
|
~"div" => ~HTMLDivElement,
|
||||||
~"img" => {
|
~"img" => {
|
||||||
~HTMLImageElement({ mut size: Size2D(geometry::px_to_au(100),
|
~HTMLImageElement({ mut size: Size2D(au::from_px(100),
|
||||||
geometry::px_to_au(100))
|
au::from_px(100))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
~"script" => ~HTMLScriptElement,
|
~"script" => ~HTMLScriptElement,
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
|
use au = gfx::geometry;
|
||||||
use dom::base::{Attr, Comment, Doctype, DoctypeData, Element, ElementData, ElementKind};
|
use dom::base::{Attr, Comment, Doctype, DoctypeData, Element, ElementData, ElementKind};
|
||||||
use dom::base::{HTMLDivElement, HTMLHeadElement, HTMLImageElement, HTMLScriptElement};
|
use dom::base::{HTMLDivElement, HTMLHeadElement, HTMLImageElement, HTMLScriptElement};
|
||||||
use dom::base::{Node, NodeScope, Text, UnknownElement};
|
use dom::base::{Node, NodeScope, Text, UnknownElement};
|
||||||
use css::values::Stylesheet;
|
use css::values::Stylesheet;
|
||||||
use geom::size::Size2D;
|
use geom::size::Size2D;
|
||||||
use gfx::geometry::px_to_au;
|
|
||||||
use html::dom_builder::CSSMessage;
|
use html::dom_builder::CSSMessage;
|
||||||
use resource::resource_task::{Done, Load, Payload, ResourceTask};
|
use resource::resource_task::{Done, Load, Payload, ResourceTask};
|
||||||
use CSSExitMessage = html::dom_builder::Exit;
|
use CSSExitMessage = html::dom_builder::Exit;
|
||||||
|
@ -118,7 +118,7 @@ fn build_element_kind(tag_name: &str) -> ~ElementKind {
|
||||||
if tag_name == "div" {
|
if tag_name == "div" {
|
||||||
~HTMLDivElement
|
~HTMLDivElement
|
||||||
} else if tag_name == "img" {
|
} 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" {
|
} else if tag_name == "script" {
|
||||||
~HTMLScriptElement
|
~HTMLScriptElement
|
||||||
} else if tag_name == "head" {
|
} 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" => {
|
HTMLImageElement(img) if attribute.name == "width" => {
|
||||||
match int::from_str(from_slice(attribute.value)) {
|
match int::from_str(from_slice(attribute.value)) {
|
||||||
None => {} // Drop on the floor.
|
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" => {
|
HTMLImageElement(img) if attribute.name == "height" => {
|
||||||
match int::from_str(from_slice(attribute.value)) {
|
match int::from_str(from_slice(attribute.value)) {
|
||||||
None => {} // Drop on the floor.
|
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 |
|
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::styles::SpecifiedStyle;
|
||||||
use css::values::{BoxSizing, Length, Px};
|
use css::values::{BoxSizing, Length, Px, CSSDisplay};
|
||||||
use dom::base::{Element, ElementKind, HTMLDivElement, HTMLImageElement, Node, NodeData};
|
use dom::base::{Element, ElementKind, HTMLDivElement, HTMLImageElement};
|
||||||
use dom::base::{NodeKind};
|
use dom::base::{Node, NodeData, NodeKind, NodeTree};
|
||||||
use dom::rcu;
|
use dom::rcu;
|
||||||
use gfx::geometry;
|
|
||||||
use gfx::geometry::{au, zero_size_au};
|
|
||||||
use geom::point::Point2D;
|
|
||||||
use geom::rect::Rect;
|
use geom::rect::Rect;
|
||||||
use geom::size::Size2D;
|
use geom::size::Size2D;
|
||||||
use image::base::Image;
|
use gfx::geometry::au;
|
||||||
use util::tree;
|
use image::{Image, ImageHolder};
|
||||||
use util::color::Color;
|
use layout::block::BlockFlowData;
|
||||||
use text::TextBox;
|
use layout::inline::InlineFlowData;
|
||||||
use traverse_parallel::extended_full_traversal;
|
use layout::root::RootFlowData;
|
||||||
use vec::{push, push_all};
|
use layout::text::TextBoxData;
|
||||||
use std::net::url::Url;
|
use servo_text::text_run::TextRun;
|
||||||
use resource::image_cache_task;
|
|
||||||
use image_cache_task::ImageCacheTask;
|
|
||||||
use core::to_str::ToStr;
|
|
||||||
use std::arc::{ARC, clone};
|
use std::arc::{ARC, clone};
|
||||||
|
use std::net::url::Url;
|
||||||
use task::spawn;
|
use task::spawn;
|
||||||
|
use util::color::Color;
|
||||||
|
use util::tree;
|
||||||
|
use vec::{push, push_all};
|
||||||
|
|
||||||
enum BoxKind {
|
/* The type of the formatting context, and data specific to each
|
||||||
BlockBox,
|
context, such as lineboxes or float lists */
|
||||||
InlineBox,
|
enum FlowContextData {
|
||||||
IntrinsicBox(@Size2D<au>),
|
AbsoluteFlow,
|
||||||
TextBoxKind(@TextBox)
|
BlockFlow(BlockFlowData),
|
||||||
|
FloatFlow,
|
||||||
|
InlineBlockFlow,
|
||||||
|
InlineFlow(InlineFlowData),
|
||||||
|
RootFlow(RootFlowData),
|
||||||
|
TableFlow
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BoxKind : cmp::Eq {
|
/* A particular kind of layout context. It manages the positioning of
|
||||||
pure fn eq(&&other: BoxKind) -> bool {
|
layout boxes within the context.
|
||||||
match (self, other) {
|
|
||||||
(BlockBox, BlockBox) => true,
|
Flow contexts form a tree that is induced by the structure of the
|
||||||
(InlineBox, InlineBox) => true,
|
box tree. Each context is responsible for laying out one or more
|
||||||
_ => fail ~"unimplemented case in BoxKind.eq"
|
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 {
|
pure fn get_pref_width() -> au {
|
||||||
mut background_image: Option<ImageHolder>,
|
match self.kind {
|
||||||
// TODO: create some sort of layout-specific enum to differentiate between
|
// TODO: this should account for min/pref widths of the
|
||||||
// relative and resolved values.
|
// box element in isolation. That includes
|
||||||
mut width: BoxSizing,
|
// border/margin/padding but not child widths. The block
|
||||||
mut height: BoxSizing,
|
// FlowContext will combine the width of this element and
|
||||||
mut font_size: Length,
|
// 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
|
// This will be very unhappy if it is getting run in parallel with
|
||||||
// anything trying to read the background image
|
// anything trying to read the background image
|
||||||
fn get_image() -> Option<ARC<~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
|
// 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
|
// get the image out of it because we can't match against it
|
||||||
// because holder.get_image() is not pure.
|
// because holder.get_image() is not pure.
|
||||||
if (self.background_image).is_some() {
|
if (self.data.background_image).is_some() {
|
||||||
let mut temp = None;
|
let mut temp = None;
|
||||||
temp <-> self.background_image;
|
temp <-> self.data.background_image;
|
||||||
let holder <- option::unwrap(temp);
|
let holder <- option::unwrap(temp);
|
||||||
image = holder.get_image();
|
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
|
fn FlowLayoutData() -> FlowLayoutData {
|
||||||
Appearance {
|
FlowLayoutData {
|
||||||
font_size : Px(14.0),
|
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,
|
background_image : None,
|
||||||
width : kind.default_width(),
|
|
||||||
height : kind.default_height(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Box {
|
// FIXME: Why do these have to be redefined for each node type?
|
||||||
tree: tree::Tree<@Box>,
|
|
||||||
node: Node,
|
|
||||||
kind: BoxKind,
|
|
||||||
mut bounds: Rect<au>,
|
|
||||||
appearance: Appearance,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn Box(node: Node, kind: BoxKind) -> Box {
|
/* The tree holding boxes */
|
||||||
Box {
|
enum BoxTree { BoxTree }
|
||||||
appearance : node.read(|n| Appearance(*n.kind)),
|
|
||||||
tree : tree::empty(),
|
|
||||||
node : node,
|
|
||||||
kind : kind,
|
|
||||||
bounds : geometry::zero_rect_au(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[doc="A struct to store image data. The image will be loaded once,
|
impl BoxTree : tree::ReadMethods<@Box> {
|
||||||
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> {
|
|
||||||
fn each_child(node: @Box, f: fn(&&@Box) -> bool) {
|
fn each_child(node: @Box, f: fn(&&@Box) -> bool) {
|
||||||
tree::each_child(self, node, f)
|
tree::each_child(self, node, f)
|
||||||
}
|
}
|
||||||
|
@ -219,9 +249,10 @@ impl BTree : tree::ReadMethods<@Box> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BTree : tree::WriteMethods<@Box> {
|
impl BoxTree : tree::WriteMethods<@Box> {
|
||||||
fn add_child(node: @Box, child: @Box) {
|
fn add_child(parent: @Box, child: @Box) {
|
||||||
tree::add_child(self, node, child)
|
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 {
|
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 {
|
/* The tree holding FlowContexts */
|
||||||
#[doc="The main reflow routine."]
|
enum FlowTree { FlowTree }
|
||||||
fn reflow() {
|
|
||||||
|
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 {
|
match self.kind {
|
||||||
BlockBox => self.reflow_block(),
|
BlockFlow(*) => self.bubble_widths_block(),
|
||||||
InlineBox => self.reflow_inline(),
|
InlineFlow(*) => self.bubble_widths_inline(),
|
||||||
IntrinsicBox(size) => self.reflow_intrinsic(*size),
|
RootFlow(*) => self.bubble_widths_root(),
|
||||||
TextBoxKind(subbox) => self.reflow_text(subbox)
|
_ => fail fmt!("Tried to bubble_widths of flow: %?", self.kind)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc="Dumps the box tree, for debugging, with indentation."]
|
fn assign_widths() {
|
||||||
fn dump_indent(indent: uint) {
|
match self.kind {
|
||||||
let mut s = ~"";
|
BlockFlow(*) => self.assign_widths_block(),
|
||||||
for uint::range(0u, indent) |_i| {
|
InlineFlow(*) => self.assign_widths_inline(),
|
||||||
s += ~" ";
|
RootFlow(*) => self.assign_widths_root(),
|
||||||
}
|
_ => fail fmt!("Tried to assign_widths of flow: %?", self.kind)
|
||||||
|
|
||||||
s += #fmt("%?", self.kind);
|
|
||||||
#debug["%s", s];
|
|
||||||
|
|
||||||
for BTree.each_child(self) |kid| {
|
|
||||||
kid.dump_indent(indent + 1u)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#[doc = "
|
fn assign_height() {
|
||||||
Set your width to the maximum available width and return the
|
match self.kind {
|
||||||
maximum available width any children can use. Currently children
|
BlockFlow(*) => self.assign_height_block(),
|
||||||
are just given the same available width.
|
InlineFlow(*) => self.assign_height_inline(),
|
||||||
"]
|
RootFlow(*) => self.assign_height_root(),
|
||||||
fn give_kids_width(+available_width : au, box : @Box) -> au {
|
_ => fail fmt!("Tried to assign_height of flow: %?", self.kind)
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Debugging
|
// Debugging
|
||||||
|
|
||||||
trait PrivateNodeMethods{
|
trait DebugMethods {
|
||||||
|
fn dump();
|
||||||
fn dump_indent(ident: uint);
|
fn dump_indent(ident: uint);
|
||||||
|
fn debug_str() -> ~str;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Node : PrivateNodeMethods {
|
impl @FlowContext : DebugMethods {
|
||||||
#[doc="Dumps the node tree, for debugging, with indentation."]
|
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) {
|
fn dump_indent(indent: uint) {
|
||||||
let mut s = ~"";
|
let mut s = ~"";
|
||||||
for uint::range(0u, indent) |_i| {
|
for uint::range(0u, indent) |_i| {
|
||||||
s += ~" ";
|
s += ~" ";
|
||||||
}
|
}
|
||||||
|
|
||||||
s += #fmt("%?", self.read(|n| copy n.kind ));
|
s += self.debug_str();
|
||||||
#debug["%s", s];
|
debug!("%s", s);
|
||||||
|
|
||||||
for NTree.each_child(self) |kid| {
|
for NodeTree.each_child(self) |kid| {
|
||||||
kid.dump_indent(indent + 1u)
|
kid.dump_indent(indent + 1u)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn debug_str() -> ~str {
|
||||||
|
fmt!("%?", self.read(|n| copy n.kind ))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
trait NodeMethods {
|
impl @Box : DebugMethods {
|
||||||
fn dump();
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Node : NodeMethods {
|
|
||||||
#[doc="Dumps the subtree rooted at this node, for debugging."]
|
|
||||||
fn dump() {
|
fn dump() {
|
||||||
self.dump_indent(0u);
|
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)]
|
#[cfg(test)]
|
||||||
|
@ -360,49 +448,15 @@ mod test {
|
||||||
|
|
||||||
fn flat_bounds(root: @Box) -> ~[Rect<au>] {
|
fn flat_bounds(root: @Box) -> ~[Rect<au>] {
|
||||||
let mut r = ~[];
|
let mut r = ~[];
|
||||||
for tree::each_child(BTree, root) |c| {
|
for tree::each_child(BoxTree, root) |c| {
|
||||||
push_all(r, flat_bounds(c));
|
push_all(r, flat_bounds(c));
|
||||||
}
|
}
|
||||||
|
|
||||||
push(r, copy root.bounds);
|
push(r, copy root.data.position);
|
||||||
|
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
// TODO: redo tests here, but probably is part of box_builder.rs
|
||||||
#[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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,57 +1,149 @@
|
||||||
#[doc="Block layout."]
|
use au = gfx::geometry;
|
||||||
|
|
||||||
use css::values::*;
|
use css::values::*;
|
||||||
use geom::point::Point2D;
|
use geom::point::Point2D;
|
||||||
use geom::size::Size2D;
|
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 util::tree;
|
||||||
use base::{Box, BlockBox, BTree};
|
|
||||||
|
|
||||||
trait BlockLayoutMethods {
|
struct BlockFlowData {
|
||||||
fn reflow_block();
|
mut box: Option<@Box>
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc="The public block layout methods."]
|
fn BlockFlowData() -> BlockFlowData {
|
||||||
impl @Box : BlockLayoutMethods {
|
BlockFlowData {
|
||||||
#[doc="The main reflow routine for block layout."]
|
box: None
|
||||||
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];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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."]
|
/** Creates CSS boxes from a DOM. */
|
||||||
|
use au = gfx::geometry;
|
||||||
use css::values::{CSSDisplay, DisplayBlock, DisplayInline, DisplayNone, Specified};
|
use core::dvec::DVec;
|
||||||
use dom::base::{ElementData, HTMLDivElement, HTMLImageElement, Element, Text, Node, Doctype, Comment};
|
use css::values::{CSSDisplay, DisplayBlock, DisplayInline, DisplayInlineBlock, DisplayNone};
|
||||||
use gfx::geometry::zero_size_au;
|
use css::values::{Inherit, Initial, Specified};
|
||||||
use layout::base::{Appearance, BTree, BlockBox, Box, BoxKind, InlineBox, IntrinsicBox, NTree};
|
use dom::base::{ElementData, HTMLDivElement, HTMLImageElement};
|
||||||
use layout::base::{TextBoxKind};
|
use dom::base::{Element, Text, Node, Doctype, Comment, NodeTree};
|
||||||
use layout::text::TextBox;
|
use layout::base::{Box, BoxData, GenericBox, ImageBox, TextBox, BoxTree};
|
||||||
use util::tree;
|
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 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 = {
|
struct LayoutTreeBuilder {
|
||||||
// The parent node that we're scanning.
|
mut root_box: Option<@Box>,
|
||||||
parent_node: Node,
|
mut root_ctx: Option<@FlowContext>,
|
||||||
// The parent box that these boxes will be added to.
|
mut next_bid: int,
|
||||||
parent_box: @Box,
|
mut next_cid: int
|
||||||
|
|
||||||
//
|
|
||||||
// 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
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ctxt {
|
fn LayoutTreeBuilder() -> LayoutTreeBuilder {
|
||||||
#[doc="
|
LayoutTreeBuilder {
|
||||||
Constructs boxes for the parent's children, when the parent's 'display' attribute is 'block'.
|
root_box: None,
|
||||||
"]
|
root_ctx: None,
|
||||||
fn construct_boxes_for_block_children() {
|
next_bid: -1,
|
||||||
for NTree.each_child(self.parent_node) |kid| {
|
next_cid: -1
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
trait PrivBoxBuilder {
|
impl LayoutTreeBuilder {
|
||||||
fn determine_box_kind() -> Option<BoxKind>;
|
/* 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 {
|
/** Creates necessary box(es) and flow context(s) for the current DOM node,
|
||||||
#[doc="
|
and recurses on its children. */
|
||||||
Determines the kind of box that this node needs. Also, for images, computes the intrinsic
|
fn construct_recursively(cur_node: Node, parent_ctx: @FlowContext, parent_box: @Box) {
|
||||||
size.
|
let style = cur_node.style();
|
||||||
"]
|
|
||||||
fn determine_box_kind() -> Option<BoxKind> {
|
// DEBUG
|
||||||
match self.read(|n| copy n.kind) {
|
let n_str = fmt!("%?", cur_node.read(|n| copy n.kind ));
|
||||||
~Text(string) => Some(TextBoxKind(@TextBox(copy string))),
|
debug!("Considering node: %?", n_str);
|
||||||
~Element(element) => {
|
|
||||||
match (copy *element.kind, self.get_specified_style().display_type) {
|
// TODO: handle interactions with 'float', 'position' (CSS 2.1, Section 9.7)
|
||||||
(HTMLImageElement({size}), _) => Some(IntrinsicBox(@size)),
|
let display = match style.display_type {
|
||||||
(_, Specified(DisplayBlock)) => Some(BlockBox),
|
Specified(v) => match v {
|
||||||
(_, Specified(DisplayInline)) => Some(InlineBox),
|
// tree ends here if 'display: none'
|
||||||
(_, Specified(DisplayNone)) => None,
|
DisplayNone => return,
|
||||||
(_, Specified(_)) => Some(InlineBox),
|
_ => v
|
||||||
(_, _) => {
|
},
|
||||||
fail ~"The specified display style should be a default instead of none"
|
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(*)
|
ImageBox(*) | GenericBox => {
|
||||||
| ~Comment(*) => None
|
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 {
|
trait PrivateBuilderMethods {
|
||||||
fn construct_boxes() -> Option<@Box>;
|
fn create_box_data(display: CSSDisplay) -> Option<BoxData>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Node : BoxBuilder {
|
impl Node : PrivateBuilderMethods {
|
||||||
#[doc="Creates boxes for this node. This is the entry point."]
|
fn create_box_data(display: CSSDisplay) -> Option<BoxData> {
|
||||||
fn construct_boxes() -> Option<@Box> {
|
do self.read |node| {
|
||||||
match self.determine_box_kind() {
|
match node.kind {
|
||||||
None => None,
|
~Doctype(*) | ~Comment(*) => None,
|
||||||
Some(kind) => {
|
~Text(string) => {
|
||||||
let my_box = @Box(self, kind);
|
// TODO: clean this up. Fonts should not be created here.
|
||||||
match kind {
|
let flib = FontLibrary();
|
||||||
BlockBox | InlineBox => {
|
let font = flib.get_test_font();
|
||||||
let cx = create_context(self, my_box);
|
let run = TextRun(font, string);
|
||||||
cx.construct_boxes_for_children();
|
Some(TextBox(TextBoxData(copy string, ~[move run])))
|
||||||
}
|
}
|
||||||
_ => {
|
~Element(element) => {
|
||||||
// Nothing to do.
|
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;
|
export build_display_list;
|
||||||
|
|
||||||
|
use au = gfx::geometry;
|
||||||
|
use base::Box;
|
||||||
use css::values::{BgColor, BgTransparent, Specified};
|
use css::values::{BgColor, BgTransparent, Specified};
|
||||||
use base::{Box, BTree, ImageHolder, TextBoxKind};
|
|
||||||
use dl = gfx::display_list;
|
use dl = gfx::display_list;
|
||||||
use dom::base::{Text, NodeScope};
|
use dom::base::{Text, NodeScope};
|
||||||
use dom::rcu::Scope;
|
use dom::rcu::Scope;
|
||||||
|
use dvec::DVec;
|
||||||
use either::{Left, Right};
|
use either::{Left, Right};
|
||||||
use geom::point::Point2D;
|
use geom::point::Point2D;
|
||||||
use geom::rect::Rect;
|
use geom::rect::Rect;
|
||||||
use geom::size::Size2D;
|
use geom::size::Size2D;
|
||||||
use gfx::geometry::{au, au_to_px, box, px_to_au};
|
use gfx::geometry::au;
|
||||||
use util::tree;
|
use layout::text::TextBoxData;
|
||||||
|
use layout::base::{TextBox, BoxTree};
|
||||||
use servo_text::text_run::TextRun;
|
use servo_text::text_run::TextRun;
|
||||||
use dvec::DVec;
|
use util::tree;
|
||||||
use vec::push;
|
use vec::push;
|
||||||
|
|
||||||
#[doc = "
|
#[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>) {
|
fn build_display_list_from_origin(list: dl::DisplayList, box: @Box, origin: Point2D<au>) {
|
||||||
let box_origin = Point2D(
|
let box_origin = Point2D(
|
||||||
px_to_au(au_to_px(origin.x) + au_to_px(box.bounds.origin.x)),
|
au::from_px(au::to_px(origin.x) + au::to_px(box.data.position.origin.x)),
|
||||||
px_to_au(au_to_px(origin.y) + au_to_px(box.bounds.origin.y)));
|
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, copy box.bounds, box_origin);
|
#debug("Handed origin %?, box has bounds %?, starting with origin %?", origin, box.data.position.size, box_origin);
|
||||||
|
|
||||||
box_to_display_items(list, box, 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);
|
#debug("Recursively building display list with origin %?", box_origin);
|
||||||
build_display_list_from_origin(list, c, 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);
|
#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 {
|
match box.kind {
|
||||||
TextBoxKind(subbox) => {
|
TextBox(d) => {
|
||||||
let runs = &mut subbox.runs;
|
let mut runs = d.runs;
|
||||||
list.push(~dl::SolidColor(bounds, 255u8, 255u8, 255u8));
|
list.push(~dl::SolidColor(bounds, 255u8, 255u8, 255u8));
|
||||||
|
|
||||||
let mut bounds = bounds;
|
let mut bounds = bounds;
|
||||||
for uint::range(0, runs.len()) |i| {
|
for uint::range(0, runs.len()) |i| {
|
||||||
bounds.size.height = runs[i].size().height;
|
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));
|
list.push(~dl::Glyphs(bounds, glyph_run));
|
||||||
bounds.origin.y += bounds.size.height;
|
bounds.origin.y += bounds.size.height;
|
||||||
}
|
}
|
||||||
return;
|
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 {
|
||||||
dl::GlyphRun {
|
dl::GlyphRun {
|
||||||
glyphs: copy text_run.glyphs
|
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.
|
// 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() {
|
if image.is_some() {
|
||||||
list.push(~dl::Image(bounds, option::unwrap(image)))
|
list.push(~dl::Image(bounds, option::unwrap(image)))
|
||||||
} else {
|
} else {
|
||||||
// DAC
|
// DAC
|
||||||
// TODO: shouldn't need to unbox CSSValue by now
|
// 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 {
|
let color = match boxed_color {
|
||||||
Specified(BgColor(c)) => c,
|
Specified(BgColor(c)) => c,
|
||||||
Specified(BgTransparent) | _ => util::color::rgba(0,0,0,0.0)
|
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() {
|
fn should_convert_text_boxes_to_solid_color_background_items() {
|
||||||
#[test];
|
#[test];
|
||||||
|
|
||||||
|
use layout::box_builder::LayoutTreeBuilder;
|
||||||
|
|
||||||
let s = Scope();
|
let s = Scope();
|
||||||
let n = s.new_node(Text(~"firecracker"));
|
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 {
|
b.reflow_text();
|
||||||
TextBoxKind(subbox) => subbox,
|
|
||||||
_ => fail
|
|
||||||
};
|
|
||||||
|
|
||||||
b.reflow_text(subbox);
|
|
||||||
let list = DVec();
|
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| {
|
do list.borrow |l| {
|
||||||
match l[0].data {
|
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() {
|
fn should_convert_text_boxes_to_text_items() {
|
||||||
#[test];
|
#[test];
|
||||||
|
use layout::box_builder::LayoutTreeBuilder;
|
||||||
|
|
||||||
let s = Scope();
|
let s = Scope();
|
||||||
let n = s.new_node(Text(~"firecracker"));
|
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 {
|
b.reflow_text();
|
||||||
TextBoxKind(subbox) => { subbox },
|
|
||||||
_ => fail
|
|
||||||
};
|
|
||||||
|
|
||||||
b.reflow_text(subbox);
|
|
||||||
let list = DVec();
|
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| {
|
do list.borrow |l| {
|
||||||
match l[1].data {
|
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() {
|
fn should_calculate_the_bounds_of_the_text_box_background_color() {
|
||||||
#[test];
|
#[test];
|
||||||
#[ignore(cfg(target_os = "macos"))];
|
#[ignore(cfg(target_os = "macos"))];
|
||||||
|
use layout::box_builder::LayoutTreeBuilder;
|
||||||
|
|
||||||
let s = Scope();
|
let s = Scope();
|
||||||
let n = s.new_node(Text(~"firecracker"));
|
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 {
|
b.reflow_text();
|
||||||
TextBoxKind(subbox) => { subbox },
|
|
||||||
_ => fail
|
|
||||||
};
|
|
||||||
|
|
||||||
b.reflow_text(subbox);
|
|
||||||
let list = DVec();
|
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(
|
let expected = Rect(
|
||||||
Point2D(px_to_au(0), px_to_au(0)),
|
Point2D(au::from_px(0), au::from_px(0)),
|
||||||
Size2D(px_to_au(84), px_to_au(20))
|
Size2D(au::from_px(84), au::from_px(20))
|
||||||
);
|
);
|
||||||
|
|
||||||
do list.borrow |l| { assert l[0].bounds == expected }
|
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() {
|
fn should_calculate_the_bounds_of_the_text_items() {
|
||||||
#[test];
|
#[test];
|
||||||
#[ignore(reason = "busted")];
|
#[ignore(reason = "busted")];
|
||||||
|
use layout::box_builder::LayoutTreeBuilder;
|
||||||
|
|
||||||
let s = Scope();
|
let s = Scope();
|
||||||
let n = s.new_node(Text(~"firecracker"));
|
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 {
|
b.reflow_text();
|
||||||
TextBoxKind(subbox) => { subbox },
|
|
||||||
_ => fail
|
|
||||||
};
|
|
||||||
|
|
||||||
b.reflow_text(subbox);
|
|
||||||
let list = DVec();
|
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(
|
let expected = Rect(
|
||||||
Point2D(px_to_au(0), px_to_au(0)),
|
Point2D(au::from_px(0), au::from_px(0)),
|
||||||
Size2D(px_to_au(84), px_to_au(20))
|
Size2D(au::from_px(84), au::from_px(20))
|
||||||
);
|
);
|
||||||
|
|
||||||
do list.borrow |l| { assert l[1].bounds == expected; }
|
do list.borrow |l| { assert l[1].bounds == expected; }
|
||||||
|
|
|
@ -1,54 +1,125 @@
|
||||||
#[doc="Inline layout."]
|
use au = gfx::geometry;
|
||||||
|
use base::Box;
|
||||||
use base::{Box, InlineBox, BTree};
|
use core::dvec::DVec;
|
||||||
use css::values::{BoxAuto, BoxLength, Px};
|
use css::values::{BoxAuto, BoxLength, Px};
|
||||||
use dom::rcu;
|
use dom::rcu;
|
||||||
use geom::point::Point2D;
|
use geom::point::Point2D;
|
||||||
use geom::size::Size2D;
|
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 num::Num;
|
||||||
use util::tree;
|
use util::tree;
|
||||||
|
|
||||||
trait InlineLayout {
|
struct InlineFlowData {
|
||||||
fn reflow_inline();
|
boxes: ~DVec<@Box>
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc="The main reflow routine for inline layout."]
|
fn InlineFlowData() -> InlineFlowData {
|
||||||
impl @Box : InlineLayout {
|
InlineFlowData {
|
||||||
fn reflow_inline() {
|
boxes: ~DVec()
|
||||||
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];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.
|
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 display_list_builder::build_display_list;
|
||||||
use dom::base::Node;
|
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 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 comm::*;
|
||||||
|
use task::*;
|
||||||
|
|
||||||
type LayoutTask = Chan<Msg>;
|
type LayoutTask = Chan<Msg>;
|
||||||
|
|
||||||
|
@ -38,11 +40,12 @@ fn LayoutTask(render_task: RenderTask, image_cache_task: ImageCacheTask) -> Layo
|
||||||
match request.recv() {
|
match request.recv() {
|
||||||
PingMsg(ping_channel) => ping_channel.send(content_task::PongMsg),
|
PingMsg(ping_channel) => ping_channel.send(content_task::PongMsg),
|
||||||
ExitMsg => {
|
ExitMsg => {
|
||||||
#debug("layout: ExitMsg received");
|
debug!("layout: ExitMsg received");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
BuildMsg(node, styles, doc_url, event_chan) => {
|
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();
|
node.dump();
|
||||||
|
|
||||||
do util::time::time(~"layout") {
|
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);
|
node.recompute_style_for_subtree(styles);
|
||||||
|
|
||||||
let root_box: @Box;
|
let root_box: @Box;
|
||||||
match node.construct_boxes() {
|
let builder = LayoutTreeBuilder();
|
||||||
None => fail ~"Root node should always exist; did it get 'display: none' somehow?",
|
match builder.construct_trees(node) {
|
||||||
Some(root) => root_box = root
|
Ok(root) => root_box = root,
|
||||||
|
Err(*) => fail ~"Root node should always exist"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
debug!("layout: constructed Box tree");
|
||||||
root_box.dump();
|
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);
|
let dlist = build_display_list(root_box);
|
||||||
render_task.send(render_task::RenderMsg(dlist));
|
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."]
|
#[doc="Text layout."]
|
||||||
|
|
||||||
|
use au = gfx::geometry;
|
||||||
use geom::size::Size2D;
|
use geom::size::Size2D;
|
||||||
use gfx::geometry::{au, px_to_au};
|
use gfx::geometry::au;
|
||||||
use servo_text::text_run::TextRun;
|
use servo_text::text_run::TextRun;
|
||||||
use servo_text::font_library::FontLibrary;
|
use servo_text::font_library::FontLibrary;
|
||||||
use base::{Box, TextBoxKind};
|
use layout::base::{TextBox, Box};
|
||||||
|
|
||||||
struct TextBox {
|
struct TextBoxData {
|
||||||
text: ~str,
|
text: ~str,
|
||||||
mut runs: ~[TextRun],
|
mut runs: ~[TextRun]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn TextBox(text: ~str) -> TextBox {
|
fn TextBoxData(text: ~str, runs: ~[TextRun]) -> TextBoxData {
|
||||||
TextBox {
|
TextBoxData {
|
||||||
text: text,
|
text: text,
|
||||||
runs: ~[],
|
runs: runs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
trait TextLayout {
|
trait TextLayout {
|
||||||
fn reflow_text(subbox: @TextBox);
|
fn reflow_text();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc="The main reflow routine for text layout."]
|
#[doc="The main reflow routine for text layout."]
|
||||||
impl @Box : TextLayout {
|
impl @Box : TextLayout {
|
||||||
fn reflow_text(subbox: @TextBox) {
|
fn reflow_text() {
|
||||||
match self.kind {
|
let d = match self.kind {
|
||||||
TextBoxKind(*) => { /* ok */ }
|
TextBox(d) => { d }
|
||||||
_ => { fail ~"expected text box in reflow_text!" }
|
_ => { fail ~"expected text box in reflow_text!" }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -35,9 +36,9 @@ impl @Box : TextLayout {
|
||||||
let font = flib.get_test_font();
|
let font = flib.get_test_font();
|
||||||
|
|
||||||
// Do line breaking.
|
// Do line breaking.
|
||||||
let mut current = TextRun(font, subbox.text);
|
let mut current = TextRun(font, d.text);
|
||||||
let mut lines = dvec::DVec();
|
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);
|
let mut max_width = au(0);
|
||||||
|
|
||||||
while current.size().width > width_left {
|
while current.size().width > width_left {
|
||||||
|
@ -73,8 +74,8 @@ impl @Box : TextLayout {
|
||||||
let total_height = au(*current.size().height * line_count);
|
let total_height = au(*current.size().height * line_count);
|
||||||
lines.push(move current);
|
lines.push(move current);
|
||||||
|
|
||||||
self.bounds.size = Size2D(max_width, total_height);
|
self.data.position.size = Size2D(max_width, total_height);
|
||||||
subbox.runs = dvec::unwrap(lines);
|
d.runs = move dvec::unwrap(lines);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,20 +83,18 @@ fn should_calculate_the_size_of_the_text_box() {
|
||||||
#[test];
|
#[test];
|
||||||
#[ignore(cfg(target_os = "macos"))];
|
#[ignore(cfg(target_os = "macos"))];
|
||||||
|
|
||||||
|
use au = gfx::geometry;
|
||||||
use dom::rcu::{Scope};
|
use dom::rcu::{Scope};
|
||||||
use dom::base::{Text, NodeScope};
|
use dom::base::{Text, NodeScope};
|
||||||
use util::tree;
|
use util::tree;
|
||||||
use gfx::geometry::px_to_au;
|
use layout::box_builder::LayoutTreeBuilder;
|
||||||
|
|
||||||
let s = Scope();
|
let s = Scope();
|
||||||
let n = s.new_node(Text(~"firecracker"));
|
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 {
|
b.reflow_text();
|
||||||
TextBoxKind(subbox) => { subbox },
|
let expected = Size2D(au::from_px(84), au::from_px(20));
|
||||||
_ => fail
|
assert b.data.position.size == expected;
|
||||||
};
|
|
||||||
b.reflow_text(subbox);
|
|
||||||
let expected = Size2D(px_to_au(84), px_to_au(20));
|
|
||||||
assert b.bounds.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;
|
use intrinsic::TyDesc;
|
||||||
|
|
||||||
export full_traversal;
|
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,
|
// current task will block until all of it's children return,
|
||||||
// so the original owner of the @-box will not exit while the
|
// so the original owner of the @-box will not exit while the
|
||||||
// children are still live.
|
// children are still live.
|
||||||
for BTree.each_child(root) |kid| {
|
for BoxTree.each_child(root) |kid| {
|
||||||
count += 1;
|
count += 1;
|
||||||
|
|
||||||
// Unwrap the box so we can send it out of this task
|
// Unwrap the box so we can send it out of this task
|
||||||
|
|
|
@ -58,7 +58,9 @@ mod layout {
|
||||||
mod display_list_builder;
|
mod display_list_builder;
|
||||||
mod inline;
|
mod inline;
|
||||||
mod layout_task;
|
mod layout_task;
|
||||||
|
mod root;
|
||||||
mod text;
|
mod text;
|
||||||
|
mod traverse;
|
||||||
mod traverse_parallel;
|
mod traverse_parallel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,6 +75,7 @@ mod gfx {
|
||||||
|
|
||||||
mod image {
|
mod image {
|
||||||
mod base;
|
mod base;
|
||||||
|
mod holder;
|
||||||
mod encode {
|
mod encode {
|
||||||
mod tga;
|
mod tga;
|
||||||
}
|
}
|
||||||
|
@ -97,17 +100,17 @@ mod text {
|
||||||
export font;
|
export font;
|
||||||
export shaper;
|
export shaper;
|
||||||
|
|
||||||
mod glyph;
|
|
||||||
mod text_run;
|
|
||||||
mod font;
|
mod font;
|
||||||
mod font_library;
|
mod font_library;
|
||||||
mod shaper;
|
mod glyph;
|
||||||
mod native_font {
|
mod native_font {
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
mod quartz_native_font;
|
mod quartz_native_font;
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
mod ft_native_font;
|
mod ft_native_font;
|
||||||
}
|
}
|
||||||
|
mod shaper;
|
||||||
|
mod text_run;
|
||||||
mod util;
|
mod util;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,12 +2,13 @@ extern mod harfbuzz;
|
||||||
|
|
||||||
export shape_text;
|
export shape_text;
|
||||||
|
|
||||||
|
use au = gfx::geometry;
|
||||||
use libc::types::common::c99::int32_t;
|
use libc::types::common::c99::int32_t;
|
||||||
use libc::{c_uint, c_int, c_void, c_char};
|
use libc::{c_uint, c_int, c_void, c_char};
|
||||||
use font::Font;
|
use font::Font;
|
||||||
use glyph::{Glyph, GlyphPos};
|
use glyph::{Glyph, GlyphPos};
|
||||||
use ptr::{null, addr_of, offset};
|
use ptr::{null, addr_of, offset};
|
||||||
use gfx::geometry::{au, px_to_au};
|
use gfx::geometry::au;
|
||||||
use geom::point::Point2D;
|
use geom::point::Point2D;
|
||||||
use font_library::FontLibrary;
|
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 {
|
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),
|
GlyphPos(Point2D(au::from_px(hb_pos.x_advance as int),
|
||||||
px_to_au(hb_pos.y_advance as int)),
|
au::from_px(hb_pos.y_advance as int)),
|
||||||
Point2D(px_to_au(hb_pos.x_offset as int),
|
Point2D(au::from_px(hb_pos.x_offset as int),
|
||||||
px_to_au(hb_pos.y_offset as int)))
|
au::from_px(hb_pos.y_offset as int)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn should_get_glyph_indexes() {
|
fn should_get_glyph_indexes() {
|
||||||
|
@ -162,6 +163,6 @@ fn should_get_glyph_h_advance() {
|
||||||
let font = lib.get_test_font();
|
let font = lib.get_test_font();
|
||||||
let glyphs = shape_text(font, ~"firecracker");
|
let glyphs = shape_text(font, ~"firecracker");
|
||||||
let actual = glyphs.map(|g| g.pos.advance.x);
|
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;
|
assert expected == actual;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
|
use au = gfx::geometry;
|
||||||
use geom::point::Point2D;
|
use geom::point::Point2D;
|
||||||
use geom::size::Size2D;
|
use geom::size::Size2D;
|
||||||
use gfx::geometry::{au, px_to_au};
|
use gfx::geometry::au;
|
||||||
use libc::{c_void};
|
use libc::{c_void};
|
||||||
use font_library::FontLibrary;
|
use font_library::FontLibrary;
|
||||||
use font::Font;
|
use font::Font;
|
||||||
|
@ -9,7 +10,7 @@ use shaper::shape_text;
|
||||||
|
|
||||||
/// A single, unbroken line of text
|
/// A single, unbroken line of text
|
||||||
struct TextRun {
|
struct TextRun {
|
||||||
priv text: ~str,
|
text: ~str,
|
||||||
priv glyphs: ~[Glyph],
|
priv glyphs: ~[Glyph],
|
||||||
priv size_: Size2D<au>,
|
priv size_: Size2D<au>,
|
||||||
priv min_break_width_: 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> {
|
fn glyph_run_size(glyphs: &[Glyph]) -> Size2D<au> {
|
||||||
let height = px_to_au(20);
|
let height = au::from_px(20);
|
||||||
let pen_start_x = px_to_au(0);
|
let pen_start_x = au::from_px(0);
|
||||||
let pen_start_y = height;
|
let pen_start_y = height;
|
||||||
let pen_start = Point2D(pen_start_x, pen_start_y);
|
let pen_start = Point2D(pen_start_x, pen_start_y);
|
||||||
let pen_end = glyphs.foldl(pen_start, |cur, glyph| {
|
let pen_end = glyphs.foldl(pen_start, |cur, glyph| {
|
||||||
|
@ -137,7 +138,7 @@ fn test_calc_min_break_width1() {
|
||||||
let flib = FontLibrary();
|
let flib = FontLibrary();
|
||||||
let font = flib.get_test_font();
|
let font = flib.get_test_font();
|
||||||
let actual = calc_min_break_width(font, ~"firecracker");
|
let actual = calc_min_break_width(font, ~"firecracker");
|
||||||
let expected = px_to_au(84);
|
let expected = au::from_px(84);
|
||||||
assert expected == actual;
|
assert expected == actual;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -146,7 +147,7 @@ fn test_calc_min_break_width2() {
|
||||||
let flib = FontLibrary();
|
let flib = FontLibrary();
|
||||||
let font = flib.get_test_font();
|
let font = flib.get_test_font();
|
||||||
let actual = calc_min_break_width(font, ~"firecracker yumyum");
|
let actual = calc_min_break_width(font, ~"firecracker yumyum");
|
||||||
let expected = px_to_au(84);
|
let expected = au::from_px(84);
|
||||||
assert expected == actual;
|
assert expected == actual;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -155,7 +156,7 @@ fn test_calc_min_break_width3() {
|
||||||
let flib = FontLibrary();
|
let flib = FontLibrary();
|
||||||
let font = flib.get_test_font();
|
let font = flib.get_test_font();
|
||||||
let actual = calc_min_break_width(font, ~"yumyum firecracker");
|
let actual = calc_min_break_width(font, ~"yumyum firecracker");
|
||||||
let expected = px_to_au(84);
|
let expected = au::from_px(84);
|
||||||
assert expected == actual;
|
assert expected == actual;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -164,7 +165,7 @@ fn test_calc_min_break_width4() {
|
||||||
let flib = FontLibrary();
|
let flib = FontLibrary();
|
||||||
let font = flib.get_test_font();
|
let font = flib.get_test_font();
|
||||||
let actual = calc_min_break_width(font, ~"yumyum firecracker yumyum");
|
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;
|
assert expected == actual;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -237,7 +238,7 @@ fn test_split3() {
|
||||||
let flib = FontLibrary();
|
let flib = FontLibrary();
|
||||||
let font = flib.get_test_font();
|
let font = flib.get_test_font();
|
||||||
let run = TextRun(font, ~"firecracker firecracker");
|
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.first().text == ~"firecracker";
|
||||||
assert break_runs.second().text == ~"firecracker";
|
assert break_runs.second().text == ~"firecracker";
|
||||||
|
|
||||||
|
@ -249,7 +250,7 @@ fn should_calculate_the_total_size() {
|
||||||
let flib = FontLibrary();
|
let flib = FontLibrary();
|
||||||
let font = flib.get_test_font();
|
let font = flib.get_test_font();
|
||||||
let run = TextRun(font, ~"firecracker");
|
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;
|
assert run.size() == expected;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue