layout: Perform text decoration propagation per CSS 2.1 § 16.3.1 without

going to the DOM.
This commit is contained in:
Patrick Walton 2013-12-12 17:26:23 -08:00
parent 7b3c3542a3
commit a2e91d242b
13 changed files with 215 additions and 63 deletions

View file

@ -92,12 +92,38 @@ pub struct SolidColorDisplayItem<E> {
/// Renders text.
pub struct TextDisplayItem<E> {
/// Fields common to all display items.
base: BaseDisplayItem<E>,
/// The text run.
text_run: Arc<~TextRun>,
/// The range of text within the text run.
range: Range,
/// The color of the text.
color: Color,
/// A bitfield of flags for text display items.
flags: TextDisplayItemFlags,
}
/// Flags for text display items.
pub struct TextDisplayItemFlags(u8);
impl TextDisplayItemFlags {
pub fn new() -> TextDisplayItemFlags {
TextDisplayItemFlags(0)
}
}
// Whether underlining is forced on.
bitfield!(TextDisplayItemFlags, override_underline, set_override_underline, 0x01)
// Whether overlining is forced on.
bitfield!(TextDisplayItemFlags, override_overline, set_override_overline, 0x02)
// Whether line-through is forced on.
bitfield!(TextDisplayItemFlags, override_line_through, set_override_line_through, 0x04)
/// Renders an image.
pub struct ImageDisplayItem<E> {
base: BaseDisplayItem<E>,
@ -184,18 +210,18 @@ impl<E> DisplayItem<E> {
let strikeout_size = font_metrics.strikeout_size;
let strikeout_offset = font_metrics.strikeout_offset;
if text_run.decoration.underline {
if text_run.decoration.underline || text.flags.override_underline() {
let underline_y = baseline_origin.y - underline_offset;
let underline_bounds = Rect(Point2D(baseline_origin.x, underline_y),
Size2D(width, underline_size));
render_context.draw_solid_color(&underline_bounds, text.color);
}
if text_run.decoration.overline {
if text_run.decoration.overline || text.flags.override_overline() {
let overline_bounds = Rect(Point2D(baseline_origin.x, origin.y),
Size2D(width, underline_size));
render_context.draw_solid_color(&overline_bounds, text.color);
}
if text_run.decoration.line_through {
if text_run.decoration.line_through || text.flags.override_line_through() {
let strikeout_y = baseline_origin.y - strikeout_offset;
let strikeout_bounds = Rect(Point2D(baseline_origin.x, strikeout_y),
Size2D(width, strikeout_size));

View file

@ -40,6 +40,10 @@ pub use gfx_font_list = font_list;
pub use servo_gfx_font = font;
pub use servo_gfx_font_list = font_list;
// Macros
mod macros;
// Private rendering modules
mod render_context;
// Rendering

View file

@ -0,0 +1,22 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#[macro_escape];
macro_rules! bitfield(
($bitfieldname:ident, $getter:ident, $setter:ident, $value:expr) => (
impl $bitfieldname {
#[inline]
pub fn $getter(self) -> bool {
(*self & $value) != 0
}
#[inline]
pub fn $setter(&mut self, value: bool) {
*self = $bitfieldname((**self & !$value) | (if value { $value } else { 0 }))
}
}
)
)

View file

@ -501,7 +501,7 @@ impl BlockFlow {
// add box that starts block context
for box in self.box.iter() {
box.build_display_list(builder, dirty, &self.base.abs_position, list)
box.build_display_list(builder, dirty, self.base.abs_position, (&*self) as &Flow, list)
}
// TODO: handle any out-of-flow elements
@ -532,7 +532,7 @@ impl BlockFlow {
let offset = self.base.abs_position + self.float.get_ref().rel_pos;
// add box that starts block context
for box in self.box.iter() {
box.build_display_list(builder, dirty, &offset, list)
box.build_display_list(builder, dirty, offset, (&*self) as &Flow, list)
}
@ -621,10 +621,10 @@ impl Flow for BlockFlow {
self.base.position.origin = Au::zero_point();
self.base.position.size.width = ctx.screen_size.size.width;
self.base.floats_in = FloatContext::new(self.base.num_floats);
self.base.is_inorder = false;
self.base.flags.set_inorder(false);
}
//position was set to the containing block by the flow's parent
// The position was set to the containing block by the flow's parent.
let mut remaining_width = self.base.position.size.width;
let mut x_offset = Au::new(0);
@ -632,7 +632,7 @@ impl Flow for BlockFlow {
self.float.get_mut_ref().containing_width = remaining_width;
// Parent usually sets this, but floats are never inorder
self.base.is_inorder = false;
self.base.flags.set_inorder(false);
}
for box in self.box.iter() {
@ -677,10 +677,10 @@ impl Flow for BlockFlow {
}
let has_inorder_children = if self.is_float() {
self.base.num_floats > 0
} else {
self.base.is_inorder || self.base.num_floats > 0
};
self.base.num_floats > 0
} else {
self.base.flags.inorder() || self.base.num_floats > 0
};
for kid in self.base.child_iter() {
assert!(kid.starts_block_flow() || kid.starts_inline_flow());
@ -688,11 +688,16 @@ impl Flow for BlockFlow {
let child_base = flow::mut_base(*kid);
child_base.position.origin.x = x_offset;
child_base.position.size.width = remaining_width;
child_base.is_inorder = has_inorder_children;
child_base.flags.set_inorder(has_inorder_children);
if !child_base.is_inorder {
if !child_base.flags.inorder() {
child_base.floats_in = FloatContext::new(0);
}
// Per CSS 2.1 § 16.3.1, text decoration propagates to all children in flow.
//
// TODO(pcwalton): When we have out-of-flow children, don't unconditionally propagate.
child_base.flags.propagate_text_decoration_from_parent(self.base.flags)
}
}

View file

@ -11,7 +11,8 @@ use gfx::color::rgb;
use gfx::display_list::{BaseDisplayItem, BorderDisplayItem, BorderDisplayItemClass};
use gfx::display_list::{DisplayList, ImageDisplayItem, ImageDisplayItemClass};
use gfx::display_list::{SolidColorDisplayItem, SolidColorDisplayItemClass, TextDisplayItem};
use gfx::display_list::{TextDisplayItemClass, ClipDisplayItem, ClipDisplayItemClass};
use gfx::display_list::{TextDisplayItemClass, TextDisplayItemFlags, ClipDisplayItem};
use gfx::display_list::{ClipDisplayItemClass};
use gfx::font::{FontStyle, FontWeight300};
use gfx::text::text_run::TextRun;
use script::dom::node::{AbstractNode, LayoutView};
@ -27,12 +28,14 @@ use std::cmp::ApproxEq;
use std::num::Zero;
use style::ComputedValues;
use style::computed_values::{LengthOrPercentage, overflow};
use style::computed_values::{border_style, clear, float, font_family, font_style, line_height};
use style::computed_values::{position, text_align, text_decoration, vertical_align, visibility};
use style::computed_values::{border_style, clear, font_family, font_style, line_height};
use style::computed_values::{text_align, text_decoration, vertical_align, visibility};
use css::node_style::StyledNode;
use layout::display_list_builder::{DisplayListBuilder, ExtraDisplayListData, ToGfxColor};
use layout::float_context::{ClearType, ClearLeft, ClearRight, ClearBoth};
use layout::flow::Flow;
use layout::flow;
use layout::model::{MaybeAuto, specified};
/// Boxes (`struct Box`) are the leaves of the layout tree. They cannot position themselves. In
@ -460,41 +463,15 @@ impl Box {
self.style().Box.vertical_align
}
/// Returns the text decoration of the computed style of the nearest `Element` node
/// Returns the text decoration of this box, according to the style of the nearest ancestor
/// element.
///
/// NB: This may not be the actual text decoration, because of the override rules specified in
/// CSS 2.1 § 16.3.1. Unfortunately, computing this properly doesn't really fit into Servo's
/// model. Therefore, this is a best lower bound approximation, but the end result may actually
/// have the various decoration flags turned on afterward.
pub fn text_decoration(&self) -> text_decoration::T {
/// Computes the propagated value of text-decoration, as specified in CSS 2.1 § 16.3.1
/// TODO: make sure this works with anonymous box generation.
fn get_propagated_text_decoration(element: AbstractNode<LayoutView>)
-> text_decoration::T {
//Skip over non-element nodes in the DOM
if !element.is_element() {
return match element.parent_node() {
None => text_decoration::none,
Some(parent) => get_propagated_text_decoration(parent),
};
}
// FIXME: Implement correctly.
let display_in_flow = true;
let position = element.style().get().Box.position;
let float = element.style().get().Box.float;
let in_flow = (position == position::static_) && (float == float::none) &&
display_in_flow;
let text_decoration = element.style().get().Text.text_decoration;
if text_decoration == text_decoration::none && in_flow {
match element.parent_node() {
None => text_decoration::none,
Some(parent) => get_propagated_text_decoration(parent),
}
} else {
text_decoration
}
}
get_propagated_text_decoration(self.nearest_ancestor_element())
self.style().Text.text_decoration
}
/// Returns the sum of margin, border, and padding on the left.
@ -622,13 +599,14 @@ impl Box {
&self,
_: &DisplayListBuilder,
dirty: &Rect<Au>,
offset: &Point2D<Au>,
offset: Point2D<Au>,
flow: &Flow,
list: &Cell<DisplayList<E>>) {
let box_bounds = self.position.get();
let absolute_box_bounds = box_bounds.translate(offset);
let absolute_box_bounds = box_bounds.translate(&offset);
debug!("Box::build_display_list at rel={}, abs={}: {:s}",
box_bounds, absolute_box_bounds, self.debug_str());
debug!("Box::build_display_list: dirty={}, offset={}", *dirty, *offset);
debug!("Box::build_display_list: dirty={}, offset={}", *dirty, offset);
if self.style().Box.visibility != visibility::visible {
return;
@ -661,6 +639,13 @@ impl Box {
let color = self.style().Color.color.to_gfx_color();
// Set the various text display item flags.
let flow_flags = flow::base(flow).flags;
let mut text_flags = TextDisplayItemFlags::new();
text_flags.set_override_underline(flow_flags.override_underline());
text_flags.set_override_overline(flow_flags.override_overline());
text_flags.set_override_line_through(flow_flags.override_line_through());
// Create the text box.
do list.with_mut_ref |list| {
let text_display_item = ~TextDisplayItem {
@ -671,6 +656,7 @@ impl Box {
text_run: text_box.run.clone(),
range: text_box.range,
color: color,
flags: text_flags,
};
list.append_item(TextDisplayItemClass(text_display_item))

View file

@ -42,7 +42,7 @@ impl ExtraDisplayListData for Nothing {
/// Right now, the builder isn't used for much, but it establishes the pattern we'll need once we
/// support display-list-based hit testing and so forth.
pub struct DisplayListBuilder<'self> {
ctx: &'self LayoutContext,
ctx: &'self LayoutContext,
}
//

View file

@ -33,17 +33,17 @@ use layout::display_list_builder::{DisplayListBuilder, ExtraDisplayListData};
use layout::float_context::{FloatContext, Invalid};
use layout::incremental::RestyleDamage;
use layout::inline::InlineFlow;
use gfx::display_list::{ClipDisplayItemClass};
use extra::dlist::{DList, DListIterator, MutDListIterator};
use extra::container::Deque;
use geom::point::Point2D;
use geom::rect::Rect;
use gfx::display_list::DisplayList;
use servo_util::geometry::Au;
use gfx::display_list::{ClipDisplayItemClass, DisplayList};
use script::dom::node::{AbstractNode, LayoutView};
use servo_util::geometry::Au;
use std::cast;
use std::cell::Cell;
use style::ComputedValues;
/// Virtual methods that make up a float context.
///
@ -305,6 +305,49 @@ pub trait PostorderFlowTraversal {
}
}
/// Flags used in flows, tightly packed to save space.
pub struct FlowFlags(u8);
/// The bitmask of flags that represent text decoration fields that get propagated downward.
///
/// NB: If you update this field, you must update the bitfields below.
static TEXT_DECORATION_OVERRIDE_BITMASK: u8 = 0b00001110;
impl FlowFlags {
/// Creates a new set of flow flags from the given style.
fn new(style: &ComputedValues) -> FlowFlags {
let text_decoration = style.Text.text_decoration;
let mut flags = FlowFlags(0);
flags.set_override_underline(text_decoration.underline);
flags.set_override_overline(text_decoration.overline);
flags.set_override_line_through(text_decoration.line_through);
flags
}
/// Propagates text decoration flags from an appropriate parent flow per CSS 2.1 § 16.3.1.
pub fn propagate_text_decoration_from_parent(&mut self, parent: FlowFlags) {
*self = FlowFlags(**self | (*parent & TEXT_DECORATION_OVERRIDE_BITMASK))
}
}
// Whether we need an in-order traversal.
bitfield!(FlowFlags, inorder, set_inorder, 0x01)
// Whether this flow forces `text-decoration: underline` on.
//
// NB: If you update this, you need to update TEXT_DECORATION_OVERRIDE_BITMASK.
bitfield!(FlowFlags, override_underline, set_override_underline, 0x02)
// Whether this flow forces `text-decoration: overline` on.
//
// NB: If you update this, you need to update TEXT_DECORATION_OVERRIDE_BITMASK.
bitfield!(FlowFlags, override_overline, set_override_overline, 0x04)
// Whether this flow forces `text-decoration: line-through` on.
//
// NB: If you update this, you need to update TEXT_DECORATION_OVERRIDE_BITMASK.
bitfield!(FlowFlags, override_line_through, set_override_line_through, 0x08)
/// Data common to all flows.
///
/// FIXME: We need a naming convention for pseudo-inheritance like this. How about
@ -313,6 +356,7 @@ pub struct FlowData {
node: AbstractNode<LayoutView>,
restyle_damage: RestyleDamage,
/// The children of this flow.
children: DList<~Flow:>,
/* TODO (Issue #87): debug only */
@ -336,7 +380,9 @@ pub struct FlowData {
floats_out: FloatContext,
num_floats: uint,
abs_position: Point2D<Au>,
is_inorder: bool,
/// Various flags for flows, tightly packed to save space.
flags: FlowFlags,
}
pub struct BoxIterator {
@ -359,6 +405,7 @@ impl Iterator<@Box> for BoxIterator {
impl FlowData {
#[inline]
pub fn new(id: int, node: AbstractNode<LayoutView>) -> FlowData {
let style = node.style();
FlowData {
node: node,
restyle_damage: node.restyle_damage(),
@ -375,7 +422,8 @@ impl FlowData {
floats_out: Invalid,
num_floats: 0,
abs_position: Point2D(Au::new(0), Au::new(0)),
is_inorder: false
flags: FlowFlags::new(style.get()),
}
}

View file

@ -497,7 +497,7 @@ impl InlineFlow {
self.boxes.len());
for box in self.boxes.iter() {
box.build_display_list(builder, dirty, &self.base.abs_position, list)
box.build_display_list(builder, dirty, self.base.abs_position, (&*self) as &Flow, list)
}
// TODO(#225): Should `inline-block` elements have flows as children of the inline flow or
@ -672,7 +672,7 @@ impl Flow for InlineFlow {
for kid in self.base.child_iter() {
let child_base = flow::mut_base(*kid);
child_base.position.size.width = self.base.position.size.width;
child_base.is_inorder = self.base.is_inorder;
child_base.flags.set_inorder(self.base.flags.inorder());
}
// There are no child contexts, so stop here.

View file

@ -178,7 +178,7 @@ impl<'self> PostorderFlowTraversal for AssignHeightsAndStoreOverflowTraversal<'s
#[inline]
fn should_process(&mut self, flow: &mut Flow) -> bool {
!flow::base(flow).is_inorder
!flow::base(flow).flags.inorder()
}
}

View file

@ -22,3 +22,20 @@ macro_rules! spawn_with(
do ($task).spawn_with(( $($var),+ , () )) |( $($var),+ , () )| $body
)
)
macro_rules! bitfield(
($bitfieldname:ident, $getter:ident, $setter:ident, $value:expr) => (
impl $bitfieldname {
#[inline]
pub fn $getter(self) -> bool {
(*self & $value) != 0
}
#[inline]
pub fn $setter(&mut self, value: bool) {
*self = $bitfieldname((**self & !$value) | (if value { $value } else { 0 }))
}
}
)
)

View file

@ -17,3 +17,4 @@
== png_rgba_colorspace_a.html png_rgba_colorspace_b.html
== border_style_none_a.html border_style_none_b.html
== acid1_a.html acid1_b.html
== text_decoration_propagation_a.html text_decoration_propagation_b.html

View file

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html>
<head>
<title>I CAN'T LIVE ANOTHER DAY WITHOUT AIR CONDITIONING!</title>
<style>
#a {
text-decoration: underline;
}
</style>
</head>
<body>
<div id=a>
hi
<div id=b>
there
</div>
</div>
</body>
</html>

View file

@ -0,0 +1,23 @@
<!DOCTYPE html>
<html>
<head>
<title>I CAN'T LIVE ANOTHER DAY WITHOUT AIR CONDITIONING!</title>
<style>
#a {
text-decoration: underline;
}
#b {
text-decoration: underline;
}
</style>
</head>
<body>
<div id=a>
hi
<div id=b>
there
</div>
</div>
</body>
</html>