diff options
author | Patrick Walton <pcwalton@mimiga.net> | 2014-12-10 23:17:05 -0800 |
---|---|---|
committer | Patrick Walton <pcwalton@mimiga.net> | 2014-12-12 14:55:41 -0800 |
commit | caee309ef4186de7b39529b37a7d5c68849e5222 (patch) | |
tree | 07c836e1079070f5804862f5124df57cf0508fe2 | |
parent | 071d320728cc2a4976801bd7a955f8efa138b739 (diff) | |
download | servo-caee309ef4186de7b39529b37a7d5c68849e5222.tar.gz servo-caee309ef4186de7b39529b37a7d5c68849e5222.zip |
layout: Implement `text-indent` per CSS 2.1 § 16.1.
I had to use a somewhat unconventional method of computing text
indentation (propagating from blocks down to inlines) because of the way
containing blocks are handled in Servo.
(As a side note, neither Gecko nor WebKit correctly handles percentages
in `text-align`, at least incrementally -- i.e. when the percentages are
relative to the viewport and the viewport is resized.)
-rw-r--r-- | components/layout/block.rs | 22 | ||||
-rw-r--r-- | components/layout/inline.rs | 74 | ||||
-rw-r--r-- | components/layout/table.rs | 1 | ||||
-rw-r--r-- | components/layout/table_cell.rs | 7 | ||||
-rw-r--r-- | components/layout/table_row.rs | 1 | ||||
-rw-r--r-- | components/layout/table_rowgroup.rs | 5 | ||||
-rw-r--r-- | components/layout/table_wrapper.rs | 2 | ||||
-rw-r--r-- | components/style/properties/mod.rs.mako | 2 | ||||
-rw-r--r-- | tests/ref/basic.list | 1 | ||||
-rw-r--r-- | tests/ref/text_indent_a.html | 40 | ||||
-rw-r--r-- | tests/ref/text_indent_ref.html | 46 |
11 files changed, 173 insertions, 28 deletions
diff --git a/components/layout/block.rs b/components/layout/block.rs index f58184f98ed..61fff95ec09 100644 --- a/components/layout/block.rs +++ b/components/layout/block.rs @@ -1256,6 +1256,7 @@ impl BlockFlow { #[inline(always)] pub fn propagate_assigned_inline_size_to_children( &mut self, + layout_context: &LayoutContext, inline_start_content_edge: Au, content_inline_size: Au, optional_column_computed_inline_sizes: Option<&[ColumnComputedInlineSize]>) { @@ -1306,6 +1307,13 @@ impl BlockFlow { (LPA_Length(length), _) => Some(length), }; + // Calculate containing block inline size. + let containing_block_size = if flags.contains(IS_ABSOLUTELY_POSITIONED) { + self.containing_block_size(layout_context.shared.screen_size).inline + } else { + content_inline_size + }; + for (i, kid) in self.base.child_iter().enumerate() { { let kid_base = flow::mut_base(kid); @@ -1383,7 +1391,16 @@ impl BlockFlow { // Per CSS 2.1 § 16.3.1, text alignment propagates to all children in flow. // // TODO(#2018, pcwalton): Do this in the cascade instead. - flow::mut_base(kid).flags.propagate_text_alignment_from_parent(flags.clone()) + flow::mut_base(kid).flags.propagate_text_alignment_from_parent(flags.clone()); + + // Handle `text-indent` on behalf of any inline children that we have. This is + // necessary because any percentages are relative to the containing block, which only + // we know. + if kid.is_inline_flow() { + kid.as_inline().first_line_indentation = + specified(self.fragment.style().get_inheritedtext().text_indent, + containing_block_size); + } } } @@ -1609,7 +1626,8 @@ impl Flow for BlockFlow { let padding_and_borders = self.fragment.border_padding.inline_start_end(); let content_inline_size = self.fragment.border_box.size.inline - padding_and_borders; - self.propagate_assigned_inline_size_to_children(inline_start_content_edge, + self.propagate_assigned_inline_size_to_children(layout_context, + inline_start_content_edge, content_inline_size, None); } diff --git a/components/layout/inline.rs b/components/layout/inline.rs index c28da42b160..f7e7441ebbc 100644 --- a/components/layout/inline.rs +++ b/components/layout/inline.rs @@ -177,11 +177,13 @@ struct LineBreaker { lines: Vec<Line>, /// The current position in the block direction. cur_b: Au, + /// The computed value of the indentation for the first line (`text-indent`, CSS 2.1 § 16.1). + first_line_indentation: Au, } impl LineBreaker { - /// Creates a new `LineBreaker` with a set of floats. - fn new(float_context: Floats) -> LineBreaker { + /// Creates a new `LineBreaker` with a set of floats and the indentation of the first line. + fn new(float_context: Floats, first_line_indentation: Au) -> LineBreaker { LineBreaker { new_fragments: Vec::new(), work_list: RingBuf::new(), @@ -193,6 +195,7 @@ impl LineBreaker { floats: float_context, lines: Vec::new(), cur_b: Au(0), + first_line_indentation: first_line_indentation, } } @@ -325,7 +328,7 @@ impl LineBreaker { let placement_inline_size = if first_fragment.can_split() { Au(0) } else { - first_fragment.border_box.size.inline + first_fragment.border_box.size.inline + self.indentation_for_pending_fragment() }; // Try to place the fragment between floats. @@ -486,9 +489,9 @@ impl LineBreaker { // If we're not going to overflow the green zone vertically, we might still do so // horizontally. We'll try to place the whole fragment on this line and break somewhere if // it doesn't fit. - + let indentation = self.indentation_for_pending_fragment(); let new_inline_size = self.pending_line.bounds.size.inline + - fragment.border_box.size.inline; + fragment.border_box.size.inline + indentation; if new_inline_size <= green_zone.inline { debug!("LineBreaker: fragment fits without splitting"); self.push_fragment_to_line(fragment); @@ -506,7 +509,8 @@ impl LineBreaker { } // Split it up! - let available_inline_size = green_zone.inline - self.pending_line.bounds.size.inline; + let available_inline_size = green_zone.inline - self.pending_line.bounds.size.inline - + indentation; let (inline_start_fragment, inline_end_fragment) = match fragment.find_split_info_for_inline_size(CharIndex(0), available_inline_size, @@ -535,7 +539,7 @@ impl LineBreaker { // Push the first fragment onto the line we're working on and start off the next line with // the second fragment. If there's no second fragment, the next line will start off empty. - match (inline_start_fragment, inline_end_fragment) { + match (inline_start_fragment, inline_end_fragment) { (Some(inline_start_fragment), Some(inline_end_fragment)) => { self.push_fragment_to_line(inline_start_fragment); self.work_list.push_front(inline_end_fragment) @@ -551,6 +555,7 @@ impl LineBreaker { /// Pushes a fragment to the current line unconditionally. fn push_fragment_to_line(&mut self, fragment: Fragment) { + let indentation = self.indentation_for_pending_fragment(); if self.pending_line_is_empty() { assert!(self.new_fragments.len() <= (u16::MAX as uint)); self.pending_line.range.reset(FragmentIndex(self.new_fragments.len() as int), @@ -559,12 +564,22 @@ impl LineBreaker { self.pending_line.range.extend_by(FragmentIndex(1)); self.pending_line.bounds.size.inline = self.pending_line.bounds.size.inline + - fragment.border_box.size.inline; + fragment.border_box.size.inline + + indentation; self.pending_line.bounds.size.block = max(self.pending_line.bounds.size.block, fragment.border_box.size.block); self.new_fragments.push(fragment); } + /// Returns the indentation that needs to be applied before the fragment we're reflowing. + fn indentation_for_pending_fragment(&self) -> Au { + if self.pending_line_is_empty() && self.lines.is_empty() { + self.first_line_indentation + } else { + Au(0) + } + } + /// Returns true if the pending line is empty and false otherwise. fn pending_line_is_empty(&self) -> bool { self.pending_line.range.length() == num::zero() @@ -698,6 +713,11 @@ pub struct InlineFlow { /// The minimum depth below the baseline for each line, as specified by the line block-size and /// font style. pub minimum_depth_below_baseline: Au, + + /// The amount of indentation to use on the first line. This is determined by our block parent + /// (because percentages are relative to the containing block, and we aren't in a position to + /// compute things relative to our parent's containing block). + pub first_line_indentation: Au, } impl InlineFlow { @@ -708,6 +728,7 @@ impl InlineFlow { lines: Vec::new(), minimum_block_size_above_baseline: Au(0), minimum_depth_below_baseline: Au(0), + first_line_indentation: Au(0), } } @@ -787,21 +808,22 @@ impl InlineFlow { /// Sets fragment positions in the inline direction based on alignment for one line. fn set_inline_fragment_positions(fragments: &mut InlineFragments, line: &Line, - line_align: text_align::T) { + line_align: text_align::T, + indentation: Au) { // Figure out how much inline-size we have. let slack_inline_size = max(Au(0), line.green_zone.inline - line.bounds.size.inline); // Set the fragment inline positions based on that alignment. - let mut offset = line.bounds.start.i; - offset = offset + match line_align { - // So sorry, but justified text is more complicated than shuffling line - // coordinates. - // - // TODO(burg, issue #213): Implement `text-align: justify`. - text_align::left | text_align::justify => Au(0), - text_align::center => slack_inline_size.scale_by(0.5), - text_align::right => slack_inline_size, - }; + let mut inline_start_position_for_fragment = line.bounds.start.i + indentation + + match line_align { + // So sorry, but justified text is more complicated than shuffling line + // coordinates. + // + // TODO(burg, issue #213): Implement `text-align: justify`. + text_align::left | text_align::justify => Au(0), + text_align::center => slack_inline_size.scale_by(0.5), + text_align::right => slack_inline_size, + }; for fragment_index in range(line.range.begin(), line.range.end()) { let fragment = fragments.get_mut(fragment_index.to_uint()); @@ -999,9 +1021,15 @@ impl Flow for InlineFlow { self.fragments.merge_broken_lines(); self.lines = Vec::new(); + // Determine how much indentation the first line wants. + let mut indentation = if self.fragments.is_empty() { + Au(0) + } else { + self.first_line_indentation + }; // Perform line breaking. - let mut scanner = LineBreaker::new(self.base.floats.clone()); + let mut scanner = LineBreaker::new(self.base.floats.clone(), indentation); scanner.scan_for_lines(self, layout_context); // Now, go through each line and lay out the fragments inside. @@ -1010,7 +1038,8 @@ impl Flow for InlineFlow { // Lay out fragments in the inline direction. InlineFlow::set_inline_fragment_positions(&mut self.fragments, line, - self.base.flags.text_align()); + self.base.flags.text_align(), + indentation); // Set the block-start position of the current line. // `line_height_offset` is updated at the end of the previous loop. @@ -1110,6 +1139,9 @@ impl Flow for InlineFlow { largest_depth_below_baseline; line_distance_from_flow_block_start = line_distance_from_flow_block_start + line.bounds.size.block; + + // We're no longer on the first line, so set indentation to zero. + indentation = Au(0) } // End of `lines.iter_mut()` loop. // Assign block sizes for any inline-block descendants. diff --git a/components/layout/table.rs b/components/layout/table.rs index b65fa689919..37f589917a2 100644 --- a/components/layout/table.rs +++ b/components/layout/table.rs @@ -315,6 +315,7 @@ impl Flow for TableFlow { self.block_flow.base.flags.remove(IMPACTED_BY_RIGHT_FLOATS); self.block_flow.propagate_assigned_inline_size_to_children( + layout_context, inline_start_content_edge, content_inline_size, Some(self.column_computed_inline_sizes.as_slice())); diff --git a/components/layout/table_cell.rs b/components/layout/table_cell.rs index 7dbfa3d678c..8dd0817b6da 100644 --- a/components/layout/table_cell.rs +++ b/components/layout/table_cell.rs @@ -96,7 +96,7 @@ impl Flow for TableCellFlow { /// Recursively (top-down) determines the actual inline-size of child contexts and fragments. /// When called on this context, the context has had its inline-size set by the parent table /// row. - fn assign_inline_sizes(&mut self, ctx: &LayoutContext) { + fn assign_inline_sizes(&mut self, layout_context: &LayoutContext) { let _scope = layout_debug_scope!("table_cell::assign_inline_sizes {:x}", self.block_flow.base.debug_id()); debug!("assign_inline_sizes({}): assigning inline_size for flow", "table_cell"); @@ -107,7 +107,7 @@ impl Flow for TableCellFlow { let inline_size_computer = InternalTable; inline_size_computer.compute_used_inline_size(&mut self.block_flow, - ctx, + layout_context, containing_block_inline_size); let inline_start_content_edge = @@ -117,7 +117,8 @@ impl Flow for TableCellFlow { let content_inline_size = self.block_flow.fragment.border_box.size.inline - padding_and_borders; - self.block_flow.propagate_assigned_inline_size_to_children(inline_start_content_edge, + self.block_flow.propagate_assigned_inline_size_to_children(layout_context, + inline_start_content_edge, content_inline_size, None); } diff --git a/components/layout/table_row.rs b/components/layout/table_row.rs index 5ecdac0b5b4..307892635a0 100644 --- a/components/layout/table_row.rs +++ b/components/layout/table_row.rs @@ -234,6 +234,7 @@ impl Flow for TableRowFlow { containing_block_inline_size); self.block_flow.propagate_assigned_inline_size_to_children( + layout_context, inline_start_content_edge, containing_block_inline_size, Some(self.column_computed_inline_sizes.as_slice())); diff --git a/components/layout/table_rowgroup.rs b/components/layout/table_rowgroup.rs index 0b9d37d1182..c3837185a47 100644 --- a/components/layout/table_rowgroup.rs +++ b/components/layout/table_rowgroup.rs @@ -143,7 +143,7 @@ impl Flow for TableRowGroupFlow { /// Recursively (top-down) determines the actual inline-size of child contexts and fragments. /// When called on this context, the context has had its inline-size set by the parent context. - fn assign_inline_sizes(&mut self, ctx: &LayoutContext) { + fn assign_inline_sizes(&mut self, layout_context: &LayoutContext) { let _scope = layout_debug_scope!("table_rowgroup::assign_inline_sizes {:x}", self.block_flow.base.debug_id()); debug!("assign_inline_sizes({}): assigning inline_size for flow", "table_rowgroup"); @@ -157,10 +157,11 @@ impl Flow for TableRowGroupFlow { let inline_size_computer = InternalTable; inline_size_computer.compute_used_inline_size(&mut self.block_flow, - ctx, + layout_context, containing_block_inline_size); self.block_flow.propagate_assigned_inline_size_to_children( + layout_context, inline_start_content_edge, content_inline_size, Some(self.column_computed_inline_sizes.as_slice())); diff --git a/components/layout/table_wrapper.rs b/components/layout/table_wrapper.rs index 5003dca66c1..271a6545d1d 100644 --- a/components/layout/table_wrapper.rs +++ b/components/layout/table_wrapper.rs @@ -299,12 +299,14 @@ impl Flow for TableWrapperFlow { match assigned_column_inline_sizes { None => { self.block_flow.propagate_assigned_inline_size_to_children( + layout_context, inline_start_content_edge, content_inline_size, None) } Some(ref assigned_column_inline_sizes) => { self.block_flow.propagate_assigned_inline_size_to_children( + layout_context, inline_start_content_edge, content_inline_size, Some(assigned_column_inline_sizes.as_slice())); diff --git a/components/style/properties/mod.rs.mako b/components/style/properties/mod.rs.mako index 2b9d9ae4971..ef0b28d78fc 100644 --- a/components/style/properties/mod.rs.mako +++ b/components/style/properties/mod.rs.mako @@ -1111,6 +1111,8 @@ pub mod longhands { } </%self:single_component_value> + ${predefined_type("text-indent", "LengthOrPercentage", "computed::LP_Length(Au(0))")} + ${new_style_struct("Text", is_inherited=False)} <%self:longhand name="text-decoration"> diff --git a/tests/ref/basic.list b/tests/ref/basic.list index a6496e794a0..b508104adef 100644 --- a/tests/ref/basic.list +++ b/tests/ref/basic.list @@ -197,3 +197,4 @@ fragment=top != ../html/acid2.html acid2_ref.html != border_black_groove.html border_black_solid.html != border_black_ridge.html border_black_solid.html != border_black_ridge.html border_black_groove.html +== text_indent_a.html text_indent_ref.html diff --git a/tests/ref/text_indent_a.html b/tests/ref/text_indent_a.html new file mode 100644 index 00000000000..9f99909b90e --- /dev/null +++ b/tests/ref/text_indent_a.html @@ -0,0 +1,40 @@ +<!DOCTYPE html> +<html> +<!-- Tests that `text-indent` works, in particular when combined with `text-align`. --> +<head> +<link rel="stylesheet" type="text/css" href="css/ahem.css"> +<style> +section { + color: blue; + position: absolute; + width: 200px; + height: 200px; +} +#a { + left: 0; + top: 0; + text-indent: 100px; +} +#b { + left: 0; + top: 200px; + text-indent: 50%; +} +#b2 { + left: 0; + top: 0; +} +#c { + left: 200px; + top: 0; + text-align: right; + text-indent: 100px; +} +</style> +</head> +<body> +<section id=a>X X</section> +<section id=b><section id=b2>X X</section></section> +<section id=c>X X</section> +</body> +</html> diff --git a/tests/ref/text_indent_ref.html b/tests/ref/text_indent_ref.html new file mode 100644 index 00000000000..c4f7c2eaf56 --- /dev/null +++ b/tests/ref/text_indent_ref.html @@ -0,0 +1,46 @@ +<!DOCTYPE html> +<html> +<!-- Tests that `text-indent` works, in particular when combined with `text-align`. --> +<head> +<link rel="stylesheet" type="text/css" href="css/ahem.css"> +<style> +section { + background: blue; + position: absolute; + width: 100px; +} +#a { + top: 0; + left: 100px; + height: 100px; +} +#b { + top: 100px; + left: 0; + height: 100px; +} +#c { + top: 200px; + left: 100px; + height: 100px; +} +#d { + top: 300px; + left: 0; + height: 100px; +} +#e { + top: 0; + left: 300px; + height: 200px; +} +</style> +</head> +<body> +<section id=a></section> +<section id=b></section> +<section id=c></section> +<section id=d></section> +<section id=e></section> +</body> +</html> |