mirror of
https://github.com/servo/servo.git
synced 2025-08-03 04:30:10 +01:00
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.)
This commit is contained in:
parent
071d320728
commit
caee309ef4
11 changed files with 173 additions and 28 deletions
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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()));
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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()));
|
||||
|
|
|
@ -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()));
|
||||
|
|
|
@ -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()));
|
||||
|
|
|
@ -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">
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue