aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPatrick Walton <pcwalton@mimiga.net>2014-12-10 23:17:05 -0800
committerPatrick Walton <pcwalton@mimiga.net>2014-12-12 14:55:41 -0800
commitcaee309ef4186de7b39529b37a7d5c68849e5222 (patch)
tree07c836e1079070f5804862f5124df57cf0508fe2
parent071d320728cc2a4976801bd7a955f8efa138b739 (diff)
downloadservo-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.rs22
-rw-r--r--components/layout/inline.rs74
-rw-r--r--components/layout/table.rs1
-rw-r--r--components/layout/table_cell.rs7
-rw-r--r--components/layout/table_row.rs1
-rw-r--r--components/layout/table_rowgroup.rs5
-rw-r--r--components/layout/table_wrapper.rs2
-rw-r--r--components/style/properties/mod.rs.mako2
-rw-r--r--tests/ref/basic.list1
-rw-r--r--tests/ref/text_indent_a.html40
-rw-r--r--tests/ref/text_indent_ref.html46
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>