Index: third_party/WebKit/Source/core/layout/ng/inline/ng_inline_layout_algorithm.cc |
diff --git a/third_party/WebKit/Source/core/layout/ng/inline/ng_inline_layout_algorithm.cc b/third_party/WebKit/Source/core/layout/ng/inline/ng_inline_layout_algorithm.cc |
index d78d6b0761ca57ea16f2a0389a06c37edbcd456c..b8466de1d6513e7e0c37d33319b49c208d77b7a6 100644 |
--- a/third_party/WebKit/Source/core/layout/ng/inline/ng_inline_layout_algorithm.cc |
+++ b/third_party/WebKit/Source/core/layout/ng/inline/ng_inline_layout_algorithm.cc |
@@ -271,6 +271,11 @@ bool NGInlineLayoutAlgorithm::CreateLineUpToLastBreakOpportunity() { |
// |last_break_opportunity|. |
start_index_ = last_break_opportunity_index_; |
start_offset_ = last_break_opportunity_offset_; |
+ // If the offset is at the end of the item, move to the next item. |
+ if (start_offset_ == items[start_index_].EndOffset() && |
+ start_index_ < items.size() - 1) { |
+ start_index_++; |
+ } |
DCHECK_GE(end_position_, last_break_opportunity_position_); |
end_position_ -= last_break_opportunity_position_; |
last_break_opportunity_position_ = LayoutUnit(); |
@@ -304,8 +309,9 @@ void NGInlineLayoutAlgorithm::BidiReorder( |
// runs instead of characters. |
Vector<UBiDiLevel, 32> levels; |
levels.ReserveInitialCapacity(line_item_chunks->size()); |
+ const Vector<NGLayoutInlineItem>& items = Node()->Items(); |
for (const auto& chunk : *line_item_chunks) |
- levels.push_back(Node()->Items()[chunk.index].BidiLevel()); |
+ levels.push_back(items[chunk.index].BidiLevel()); |
Vector<int32_t, 32> indices_in_visual_order(line_item_chunks->size()); |
NGBidiParagraph::IndicesInVisualOrder(levels, &indices_in_visual_order); |
@@ -318,6 +324,23 @@ void NGInlineLayoutAlgorithm::BidiReorder( |
line_item_chunks_in_visual_order[visual_index] = |
(*line_item_chunks)[logical_index]; |
} |
+ |
+ // Keep Open before Close in the visual order. |
+ HashMap<LayoutObject*, unsigned> first_index; |
+ for (unsigned i = 0; i < line_item_chunks_in_visual_order.size(); i++) { |
+ LineItemChunk& chunk = line_item_chunks_in_visual_order[i]; |
+ const NGLayoutInlineItem& item = items[chunk.index]; |
+ if (item.Type() != NGLayoutInlineItem::kOpenTag && |
+ item.Type() != NGLayoutInlineItem::kCloseTag) { |
+ continue; |
+ } |
+ auto result = first_index.insert(item.GetLayoutObject(), i); |
+ if (!result.is_new_entry && item.Type() == NGLayoutInlineItem::kOpenTag) { |
+ std::swap(line_item_chunks_in_visual_order[i], |
+ line_item_chunks_in_visual_order[result.stored_value->value]); |
+ } |
+ } |
+ |
line_item_chunks->Swap(line_item_chunks_in_visual_order); |
} |
@@ -383,35 +406,29 @@ bool NGInlineLayoutAlgorithm::PlaceItems( |
// Compute heights of all inline items by placing the dominant baseline at 0. |
// The baseline is adjusted after the height of the line box is computed. |
NGTextFragmentBuilder text_builder(Node()); |
+ NGInlineBoxState* box = OnBeginPlaceItems(); |
LayoutUnit inline_size; |
for (const auto& line_item_chunk : line_item_chunks) { |
const NGLayoutInlineItem& item = items[line_item_chunk.index]; |
- // Skip bidi controls. |
- if (!item.GetLayoutObject()) |
- continue; |
- |
- LayoutUnit block_start; |
+ LayoutUnit line_top; |
if (item.Type() == NGLayoutInlineItem::kText) { |
DCHECK(item.GetLayoutObject()->IsText()); |
- const ComputedStyle* style = item.Style(); |
- // The direction of a fragment is the CSS direction to resolve logical |
- // properties, not the resolved bidi direction. |
- text_builder.SetDirection(style->Direction()) |
- .SetInlineSize(line_item_chunk.inline_size); |
- |
- // |InlineTextBoxPainter| sets the baseline at |top + |
- // ascent-of-primary-font|. Compute |top| to match. |
- NGLineHeightMetrics metrics(*style, baseline_type_); |
- block_start = -metrics.ascent; |
- metrics.AddLeading(style->ComputedLineHeightAsFixed()); |
- text_builder.SetBlockSize(metrics.LineHeight()); |
- line_box.UniteMetrics(metrics); |
- |
+ if (box->text_metrics.IsEmpty()) |
+ box->ComputeTextMetrics(item, baseline_type_); |
+ line_top = box->text_top; |
+ text_builder.SetSize( |
+ {line_item_chunk.inline_size, box->text_metrics.LineHeight()}); |
// Take all used fonts into account if 'line-height: normal'. |
- if (style->LineHeight().IsNegative()) |
+ if (box->include_used_fonts) |
AccumulateUsedFonts(item, line_item_chunk, &line_box); |
+ } else if (item.Type() == NGLayoutInlineItem::kOpenTag) { |
+ box = OnOpenTag(item, &line_box, &text_builder); |
+ continue; |
+ } else if (item.Type() == NGLayoutInlineItem::kCloseTag) { |
+ box = OnCloseTag(item, &line_box, box); |
+ continue; |
} else if (item.Type() == NGLayoutInlineItem::kAtomicInline) { |
- block_start = PlaceAtomicInline(item, &line_box, &text_builder); |
+ line_top = PlaceAtomicInline(item, &line_box, &text_builder); |
} else if (item.Type() == NGLayoutInlineItem::kOutOfFlowPositioned) { |
// TODO(layout-dev): Report the correct static position for the out of |
// flow descendant. We can't do this here yet as it doesn't know the |
@@ -435,7 +452,7 @@ bool NGInlineLayoutAlgorithm::PlaceItems( |
NGLogicalOffset logical_offset( |
inline_size + current_opportunity_.InlineStartOffset() - |
ConstraintSpace().BfcOffset().inline_offset, |
- block_start); |
+ line_top); |
line_box.AddChild(std::move(text_fragment), logical_offset); |
inline_size += line_item_chunk.inline_size; |
} |
@@ -444,6 +461,8 @@ bool NGInlineLayoutAlgorithm::PlaceItems( |
return true; // The line was empty. |
} |
+ OnEndPlaceItems(&line_box); |
+ |
// The baselines are always placed at pixel boundaries. Not doing so results |
// in incorrect layout of text decorations, most notably underlines. |
LayoutUnit baseline = content_size_ + line_box.Metrics().ascent; |
@@ -480,6 +499,166 @@ bool NGInlineLayoutAlgorithm::PlaceItems( |
return true; |
} |
+// Initialize the box state stack for a new line. |
+// @return The initial box state for the line. |
+NGInlineBoxState* NGInlineLayoutAlgorithm::OnBeginPlaceItems() { |
+ if (box_states_.IsEmpty()) { |
+ // For the first line, push a box state for the line itself. |
+ box_states_.Resize(1); |
+ NGInlineBoxState* box = &box_states_.back(); |
+ box->fragment_start = 0; |
+ box->style = &LineStyle(); |
+ return box; |
+ } |
+ |
+ // For the following lines, clear states that are not shared across lines. |
+ for (auto& box : box_states_) { |
+ box.fragment_start = 0; |
+ box.metrics = NGLineHeightMetrics(); |
+ DCHECK(box.pending_descendants.IsEmpty()); |
+ } |
+ return &box_states_.back(); |
+} |
+ |
+// Push a box state stack. |
+NGInlineBoxState* NGInlineLayoutAlgorithm::OnOpenTag( |
+ const NGLayoutInlineItem& item, |
+ NGLineBoxFragmentBuilder* line_box, |
+ NGTextFragmentBuilder* text_builder) { |
+ box_states_.Resize(box_states_.size() + 1); |
+ NGInlineBoxState* box = &box_states_.back(); |
+ box->fragment_start = line_box->Children().size(); |
+ box->style = item.Style(); |
+ text_builder->SetDirection(box->style->Direction()); |
+ return box; |
+} |
+ |
+// Pop a box state stack. |
+NGInlineBoxState* NGInlineLayoutAlgorithm::OnCloseTag( |
+ const NGLayoutInlineItem& item, |
+ NGLineBoxFragmentBuilder* line_box, |
+ NGInlineBoxState* box) { |
+ EndBoxState(box, line_box); |
+ // TODO(kojii): When the algorithm restarts from a break token, the stack may |
+ // underflow. We need either synthesize a missing box state, or push all |
+ // parents on initialize. |
+ box_states_.pop_back(); |
+ return &box_states_.back(); |
+} |
+ |
+// Compute all the pending positioning at the end of a line. |
+void NGInlineLayoutAlgorithm::OnEndPlaceItems( |
+ NGLineBoxFragmentBuilder* line_box) { |
+ for (auto it = box_states_.rbegin(); it != box_states_.rend(); ++it) { |
+ NGInlineBoxState* box = &(*it); |
+ EndBoxState(box, line_box); |
+ } |
+ line_box->UniteMetrics(box_states_.front().metrics); |
+} |
+ |
+// End of a box state, either explicitly by close tag, or implicitly at the end |
+// of a line. |
+void NGInlineLayoutAlgorithm::EndBoxState(NGInlineBoxState* box, |
+ NGLineBoxFragmentBuilder* line_box) { |
+ ApplyBaselineShift(box, line_box); |
+ |
+ // Unite the metrics to the parent box. |
+ if (box != box_states_.begin()) { |
+ box[-1].metrics.Unite(box->metrics); |
+ } |
+} |
+ |
+// Compute vertical position for the 'vertical-align' property. |
+// The timing to apply varies by values; some values apply at the layout of |
+// the box was computed. Other values apply when the layout of the parent or |
+// the line box was computed. |
+// https://www.w3.org/TR/CSS22/visudet.html#propdef-vertical-align |
+// https://www.w3.org/TR/css-inline-3/#propdef-vertical-align |
+void NGInlineLayoutAlgorithm::ApplyBaselineShift( |
+ NGInlineBoxState* box, |
+ NGLineBoxFragmentBuilder* line_box) { |
+ // Compute descendants that depend on the layout size of this box if any. |
+ LayoutUnit baseline_shift; |
+ if (!box->pending_descendants.IsEmpty()) { |
+ for (const auto& child : box->pending_descendants) { |
+ switch (child.vertical_align) { |
+ case EVerticalAlign::kTextTop: |
+ case EVerticalAlign::kTop: |
+ baseline_shift = child.metrics.ascent - box->metrics.ascent; |
+ break; |
+ case EVerticalAlign::kTextBottom: |
+ case EVerticalAlign::kBottom: |
+ baseline_shift = box->metrics.descent - child.metrics.descent; |
+ break; |
+ default: |
+ NOTREACHED(); |
+ continue; |
+ } |
+ line_box->MoveChildrenInBlockDirection( |
+ baseline_shift, child.fragment_start, child.fragment_end); |
+ } |
+ box->pending_descendants.Clear(); |
+ } |
+ |
+ const ComputedStyle& style = *box->style; |
+ EVerticalAlign vertical_align = style.VerticalAlign(); |
+ if (vertical_align == EVerticalAlign::kBaseline) |
+ return; |
+ |
+ // 'vertical-align' aplies only to inline-level elements. |
+ if (box == box_states_.begin()) |
+ return; |
+ |
+ // Check if there are any fragments to move. |
+ unsigned fragment_end = line_box->Children().size(); |
+ if (box->fragment_start == fragment_end) |
+ return; |
+ |
+ switch (vertical_align) { |
+ case EVerticalAlign::kSub: |
+ baseline_shift = style.ComputedFontSizeAsFixed() / 5 + 1; |
+ break; |
+ case EVerticalAlign::kSuper: |
+ baseline_shift = -(style.ComputedFontSizeAsFixed() / 3 + 1); |
+ break; |
+ case EVerticalAlign::kLength: { |
+ // 'Percentages: refer to the 'line-height' of the element itself'. |
+ // https://www.w3.org/TR/CSS22/visudet.html#propdef-vertical-align |
+ const Length& length = style.GetVerticalAlignLength(); |
+ LayoutUnit line_height = length.IsPercentOrCalc() |
+ ? style.ComputedLineHeightAsFixed() |
+ : box->text_metrics.LineHeight(); |
+ baseline_shift = -ValueForLength(length, line_height); |
+ break; |
+ } |
+ case EVerticalAlign::kMiddle: |
+ baseline_shift = (box->metrics.ascent - box->metrics.descent) / 2; |
+ if (const SimpleFontData* font_data = style.GetFont().PrimaryFont()) { |
+ baseline_shift -= LayoutUnit::FromFloatRound( |
+ font_data->GetFontMetrics().XHeight() / 2); |
+ } |
+ break; |
+ case EVerticalAlign::kBaselineMiddle: |
+ baseline_shift = (box->metrics.ascent - box->metrics.descent) / 2; |
+ break; |
+ case EVerticalAlign::kTop: |
+ case EVerticalAlign::kBottom: |
+ // 'top' and 'bottom' require the layout size of the line box. |
+ box_states_.front().pending_descendants.push_back(NGPendingPositions{ |
+ box->fragment_start, fragment_end, box->metrics, vertical_align}); |
+ return; |
+ default: |
+ // Other values require the layout size of the parent box. |
+ SECURITY_CHECK(box != box_states_.begin()); |
+ box[-1].pending_descendants.push_back(NGPendingPositions{ |
+ box->fragment_start, fragment_end, box->metrics, vertical_align}); |
+ return; |
+ } |
+ box->metrics.Move(baseline_shift); |
+ line_box->MoveChildrenInBlockDirection(baseline_shift, box->fragment_start, |
+ fragment_end); |
+} |
+ |
void NGInlineLayoutAlgorithm::AccumulateUsedFonts( |
const NGLayoutInlineItem& item, |
const LineItemChunk& line_item_chunk, |
@@ -508,21 +687,23 @@ LayoutUnit NGInlineLayoutAlgorithm::PlaceAtomicInline( |
// TODO(kojii): Try to eliminate the wrapping text fragment and use the |
// |fragment| directly. Currently |CopyFragmentDataToLayoutBlockFlow| |
// requires a text fragment. |
- text_builder->SetInlineSize(fragment.InlineSize()).SetBlockSize(block_size); |
+ text_builder->SetSize({fragment.InlineSize(), block_size}); |
// TODO(kojii): Add baseline position to NGPhysicalFragment. |
- LayoutBox* box = ToLayoutBox(item.GetLayoutObject()); |
+ LayoutBox* layout_box = ToLayoutBox(item.GetLayoutObject()); |
LineDirectionMode line_direction_mode = |
IsHorizontalWritingMode() ? LineDirectionMode::kHorizontalLine |
: LineDirectionMode::kVerticalLine; |
- LayoutUnit baseline_offset(box->BaselinePosition( |
+ LayoutUnit baseline_offset(layout_box->BaselinePosition( |
baseline_type_, IsFirstLine(), line_direction_mode)); |
- line_box->UniteMetrics({baseline_offset, block_size - baseline_offset}); |
+ |
+ NGLineHeightMetrics metrics(baseline_offset, block_size - baseline_offset); |
+ box_states_.back().metrics.Unite(metrics); |
// TODO(kojii): Figure out what to do with OOF in NGLayoutResult. |
// Floats are ok because atomic inlines are BFC? |
- return -baseline_offset; |
+ return -metrics.ascent; |
} |
void NGInlineLayoutAlgorithm::FindNextLayoutOpportunity() { |