OLD | NEW |
1 // Copyright 2016 The Chromium Authors. All rights reserved. | 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "core/layout/ng/inline/ng_inline_layout_algorithm.h" | 5 #include "core/layout/ng/inline/ng_inline_layout_algorithm.h" |
6 | 6 |
7 #include "core/layout/ng/inline/ng_bidi_paragraph.h" | 7 #include "core/layout/ng/inline/ng_bidi_paragraph.h" |
8 #include "core/layout/ng/inline/ng_inline_break_token.h" | 8 #include "core/layout/ng/inline/ng_inline_break_token.h" |
9 #include "core/layout/ng/inline/ng_inline_node.h" | 9 #include "core/layout/ng/inline/ng_inline_node.h" |
10 #include "core/layout/ng/inline/ng_line_box_fragment.h" | 10 #include "core/layout/ng/inline/ng_line_box_fragment.h" |
(...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
55 | 55 |
56 NGInlineLayoutAlgorithm::NGInlineLayoutAlgorithm( | 56 NGInlineLayoutAlgorithm::NGInlineLayoutAlgorithm( |
57 NGInlineNode* inline_node, | 57 NGInlineNode* inline_node, |
58 NGConstraintSpace* space, | 58 NGConstraintSpace* space, |
59 NGInlineBreakToken* break_token) | 59 NGInlineBreakToken* break_token) |
60 : NGLayoutAlgorithm(inline_node, space, break_token), | 60 : NGLayoutAlgorithm(inline_node, space, break_token), |
61 is_horizontal_writing_mode_( | 61 is_horizontal_writing_mode_( |
62 blink::IsHorizontalWritingMode(space->WritingMode())), | 62 blink::IsHorizontalWritingMode(space->WritingMode())), |
63 disallow_first_line_rules_(false), | 63 disallow_first_line_rules_(false), |
64 space_builder_(space) | 64 space_builder_(space) |
65 #if DCHECK_IS_ON() | |
66 , | |
67 is_bidi_reordered_(false) | |
68 #endif | |
69 { | 65 { |
70 container_builder_.MutableUnpositionedFloats() = space->UnpositionedFloats(); | 66 container_builder_.MutableUnpositionedFloats() = space->UnpositionedFloats(); |
71 | 67 |
72 // TODO(crbug.com/716930): We may be an empty LayoutInline due to splitting. | 68 // TODO(crbug.com/716930): We may be an empty LayoutInline due to splitting. |
73 // Only resolve our BFC offset if we know that we are non-empty as we may | 69 // Only resolve our BFC offset if we know that we are non-empty as we may |
74 // need to pass through our margin strut. | 70 // need to pass through our margin strut. |
75 if (!inline_node->Items().IsEmpty()) { | 71 if (!inline_node->Items().IsEmpty()) { |
76 NGLogicalOffset bfc_offset = ConstraintSpace().BfcOffset(); | 72 NGLogicalOffset bfc_offset = ConstraintSpace().BfcOffset(); |
77 bfc_offset.block_offset += ConstraintSpace().MarginStrut().Sum(); | 73 bfc_offset.block_offset += ConstraintSpace().MarginStrut().Sum(); |
78 MaybeUpdateFragmentBfcOffset(ConstraintSpace(), bfc_offset, | 74 MaybeUpdateFragmentBfcOffset(ConstraintSpace(), bfc_offset, |
79 &container_builder_); | 75 &container_builder_); |
80 PositionPendingFloats(bfc_offset.block_offset, &container_builder_, | 76 PositionPendingFloats(bfc_offset.block_offset, &container_builder_, |
81 MutableConstraintSpace()); | 77 MutableConstraintSpace()); |
82 } | 78 } |
83 | 79 |
84 if (!is_horizontal_writing_mode_) | 80 if (!is_horizontal_writing_mode_) |
85 baseline_type_ = FontBaseline::kIdeographicBaseline; | 81 baseline_type_ = FontBaseline::kIdeographicBaseline; |
86 if (break_token) | 82 |
87 Initialize(break_token->ItemIndex(), break_token->TextOffset()); | 83 if (break_token) { |
88 else | 84 // If a break_token is given, we're re-starting layout for 2nd or later |
89 Initialize(0, 0); | 85 // lines, and that the first line we create should not use the first line |
| 86 // rules. |
| 87 DCHECK(!break_token->IsFinished()); |
| 88 DCHECK(break_token->TextOffset() || break_token->ItemIndex()); |
| 89 disallow_first_line_rules_ = true; |
| 90 } else { |
| 91 auto& engine = Node()->GetLayoutObject()->GetDocument().GetStyleEngine(); |
| 92 disallow_first_line_rules_ = !engine.UsesFirstLineRules(); |
| 93 } |
| 94 |
| 95 FindNextLayoutOpportunity(); |
90 } | 96 } |
91 | 97 |
92 bool NGInlineLayoutAlgorithm::IsFirstLine() const { | 98 bool NGInlineLayoutAlgorithm::IsFirstLine() const { |
93 return !disallow_first_line_rules_ && container_builder_.Children().IsEmpty(); | 99 return !disallow_first_line_rules_ && container_builder_.Children().IsEmpty(); |
94 } | 100 } |
95 | 101 |
96 const ComputedStyle& NGInlineLayoutAlgorithm::FirstLineStyle() const { | 102 const ComputedStyle& NGInlineLayoutAlgorithm::FirstLineStyle() const { |
97 return Node()->GetLayoutObject()->FirstLineStyleRef(); | 103 return Node()->GetLayoutObject()->FirstLineStyleRef(); |
98 } | 104 } |
99 | 105 |
100 const ComputedStyle& NGInlineLayoutAlgorithm::LineStyle() const { | 106 const ComputedStyle& NGInlineLayoutAlgorithm::LineStyle() const { |
101 return IsFirstLine() ? FirstLineStyle() : Style(); | 107 return IsFirstLine() ? FirstLineStyle() : Style(); |
102 } | 108 } |
103 | 109 |
104 bool NGInlineLayoutAlgorithm::CanFitOnLine() const { | 110 LayoutUnit NGInlineLayoutAlgorithm::AvailableWidth() const { |
105 LayoutUnit available_size = current_opportunity_.InlineSize(); | 111 return current_opportunity_.InlineSize(); |
106 if (available_size == NGSizeIndefinite) | |
107 return true; | |
108 return end_position_ <= available_size; | |
109 } | 112 } |
110 | 113 |
111 bool NGInlineLayoutAlgorithm::HasItems() const { | 114 bool NGInlineLayoutAlgorithm::CreateLine( |
112 return start_offset_ != end_offset_; | 115 NGInlineItemResults* item_results, |
113 } | 116 RefPtr<NGInlineBreakToken> break_token) { |
| 117 if (Node()->IsBidiEnabled()) |
| 118 BidiReorder(item_results); |
114 | 119 |
115 bool NGInlineLayoutAlgorithm::HasBreakOpportunity() const { | 120 if (!PlaceItems(item_results, break_token)) |
116 return start_offset_ != last_break_opportunity_offset_; | |
117 } | |
118 | |
119 bool NGInlineLayoutAlgorithm::HasItemsAfterLastBreakOpportunity() const { | |
120 return last_break_opportunity_offset_ != end_offset_; | |
121 } | |
122 | |
123 void NGInlineLayoutAlgorithm::Initialize(unsigned index, unsigned offset) { | |
124 if (index || offset) | |
125 Node()->AssertOffset(index, offset); | |
126 | |
127 start_index_ = last_index_ = last_break_opportunity_index_ = index; | |
128 start_offset_ = end_offset_ = last_break_opportunity_offset_ = offset; | |
129 end_position_ = last_break_opportunity_position_ = LayoutUnit(); | |
130 | |
131 auto& engine = Node()->GetLayoutObject()->GetDocument().GetStyleEngine(); | |
132 disallow_first_line_rules_ = index || offset || !engine.UsesFirstLineRules(); | |
133 | |
134 FindNextLayoutOpportunity(); | |
135 } | |
136 | |
137 void NGInlineLayoutAlgorithm::SetEnd(unsigned new_end_offset) { | |
138 DCHECK_GT(new_end_offset, end_offset_); | |
139 const Vector<NGInlineItem>& items = Node()->Items(); | |
140 DCHECK_LE(new_end_offset, items.back().EndOffset()); | |
141 | |
142 // SetEnd() while |new_end_offset| is beyond the current last item. | |
143 unsigned index = last_index_; | |
144 const NGInlineItem* item = &items[index]; | |
145 if (new_end_offset > item->EndOffset()) { | |
146 if (end_offset_ < item->EndOffset()) { | |
147 SetEnd(index, item->EndOffset(), | |
148 InlineSize(*item, end_offset_, item->EndOffset())); | |
149 } | |
150 item = &items[++index]; | |
151 | |
152 while (new_end_offset > item->EndOffset()) { | |
153 SetEnd(index, item->EndOffset(), InlineSize(*item)); | |
154 item = &items[++index]; | |
155 } | |
156 } | |
157 | |
158 SetEnd(index, new_end_offset, InlineSize(*item, end_offset_, new_end_offset)); | |
159 | |
160 // Include closing elements. | |
161 while (new_end_offset == item->EndOffset() && index < items.size() - 1) { | |
162 item = &items[++index]; | |
163 if (item->Type() != NGInlineItem::kCloseTag) | |
164 break; | |
165 SetEnd(index, new_end_offset, InlineSize(*item)); | |
166 } | |
167 } | |
168 | |
169 void NGInlineLayoutAlgorithm::SetEnd(unsigned index, | |
170 unsigned new_end_offset, | |
171 LayoutUnit inline_size_since_current_end) { | |
172 const Vector<NGInlineItem>& items = Node()->Items(); | |
173 DCHECK_LE(new_end_offset, items.back().EndOffset()); | |
174 | |
175 // |new_end_offset| should be in the current item or next. | |
176 // TODO(kojii): Reconsider this restriction if needed. | |
177 DCHECK((index == last_index_ && new_end_offset > end_offset_) || | |
178 (index == last_index_ + 1 && new_end_offset >= end_offset_ && | |
179 end_offset_ == items[last_index_].EndOffset())); | |
180 const NGInlineItem& item = items[index]; | |
181 item.AssertEndOffset(new_end_offset); | |
182 | |
183 if (item.Type() == NGInlineItem::kFloating) { | |
184 LayoutAndPositionFloat( | |
185 LayoutUnit(end_position_) + inline_size_since_current_end, | |
186 item.GetLayoutObject()); | |
187 } | |
188 | |
189 last_index_ = index; | |
190 end_offset_ = new_end_offset; | |
191 end_position_ += inline_size_since_current_end; | |
192 } | |
193 | |
194 void NGInlineLayoutAlgorithm::SetBreakOpportunity() { | |
195 last_break_opportunity_index_ = last_index_; | |
196 last_break_opportunity_offset_ = end_offset_; | |
197 last_break_opportunity_position_ = end_position_; | |
198 } | |
199 | |
200 void NGInlineLayoutAlgorithm::SetStartOfHangables(unsigned offset) { | |
201 // TODO(kojii): Implement. | |
202 } | |
203 | |
204 LayoutUnit NGInlineLayoutAlgorithm::InlineSize(const NGInlineItem& item) { | |
205 if (item.Type() == NGInlineItem::kAtomicInline) | |
206 return InlineSizeFromLayout(item); | |
207 return item.InlineSize(); | |
208 } | |
209 | |
210 LayoutUnit NGInlineLayoutAlgorithm::InlineSize(const NGInlineItem& item, | |
211 unsigned start_offset, | |
212 unsigned end_offset) { | |
213 if (item.StartOffset() == start_offset && item.EndOffset() == end_offset) | |
214 return InlineSize(item); | |
215 return item.InlineSize(start_offset, end_offset); | |
216 } | |
217 | |
218 LayoutUnit NGInlineLayoutAlgorithm::InlineSizeFromLayout( | |
219 const NGInlineItem& item) { | |
220 return NGBoxFragment(ConstraintSpace().WritingMode(), | |
221 ToNGPhysicalBoxFragment( | |
222 LayoutItem(item)->PhysicalFragment().Get())) | |
223 .InlineSize(); | |
224 } | |
225 | |
226 const NGLayoutResult* NGInlineLayoutAlgorithm::LayoutItem( | |
227 const NGInlineItem& item) { | |
228 // Returns the cached NGLayoutResult if available. | |
229 const Vector<NGInlineItem>& items = Node()->Items(); | |
230 if (layout_results_.IsEmpty()) | |
231 layout_results_.resize(items.size()); | |
232 unsigned index = std::distance(items.begin(), &item); | |
233 RefPtr<NGLayoutResult>* layout_result = &layout_results_[index]; | |
234 if (*layout_result) | |
235 return layout_result->Get(); | |
236 | |
237 DCHECK_EQ(item.Type(), NGInlineItem::kAtomicInline); | |
238 NGBlockNode* node = new NGBlockNode(item.GetLayoutObject()); | |
239 // TODO(kojii): Keep node in NGInlineItem. | |
240 const ComputedStyle& style = node->Style(); | |
241 NGConstraintSpaceBuilder constraint_space_builder(&ConstraintSpace()); | |
242 RefPtr<NGConstraintSpace> constraint_space = | |
243 constraint_space_builder.SetIsNewFormattingContext(true) | |
244 .SetIsShrinkToFit(true) | |
245 .SetTextDirection(style.Direction()) | |
246 .ToConstraintSpace(FromPlatformWritingMode(style.GetWritingMode())); | |
247 *layout_result = node->Layout(constraint_space.Get()); | |
248 return layout_result->Get(); | |
249 } | |
250 | |
251 bool NGInlineLayoutAlgorithm::CreateLine() { | |
252 if (HasItemsAfterLastBreakOpportunity()) | |
253 SetBreakOpportunity(); | |
254 return CreateLineUpToLastBreakOpportunity(); | |
255 } | |
256 | |
257 bool NGInlineLayoutAlgorithm::CreateLineUpToLastBreakOpportunity() { | |
258 const Vector<NGInlineItem>& items = Node()->Items(); | |
259 | |
260 // Create a list of LineItemChunk from |start| and |last_break_opportunity|. | |
261 // TODO(kojii): Consider refactoring LineItemChunk once NGLineBuilder's public | |
262 // API is more finalized. It does not fit well with the current API. | |
263 Vector<LineItemChunk, 32> line_item_chunks; | |
264 unsigned start_offset = start_offset_; | |
265 for (unsigned i = start_index_; i <= last_break_opportunity_index_; i++) { | |
266 const NGInlineItem& item = items[i]; | |
267 unsigned end_offset = | |
268 std::min(item.EndOffset(), last_break_opportunity_offset_); | |
269 line_item_chunks.push_back( | |
270 LineItemChunk{i, start_offset, end_offset, | |
271 InlineSize(item, start_offset, end_offset)}); | |
272 start_offset = end_offset; | |
273 } | |
274 | |
275 if (Node()->IsBidiEnabled()) | |
276 BidiReorder(&line_item_chunks); | |
277 | |
278 if (!PlaceItems(line_item_chunks)) | |
279 return false; | 121 return false; |
280 | 122 |
281 // Prepare for the next line. | 123 // Prepare for the next line. |
282 // Move |start| to |last_break_opportunity|, keeping items after | |
283 // |last_break_opportunity|. | |
284 start_index_ = last_break_opportunity_index_; | |
285 start_offset_ = last_break_opportunity_offset_; | |
286 // If the offset is at the end of the item, move to the next item. | |
287 if (start_offset_ == items[start_index_].EndOffset() && | |
288 start_index_ < items.size() - 1) { | |
289 start_index_++; | |
290 } | |
291 DCHECK_GE(end_position_, last_break_opportunity_position_); | |
292 end_position_ -= last_break_opportunity_position_; | |
293 last_break_opportunity_position_ = LayoutUnit(); | |
294 #if DCHECK_IS_ON() | |
295 is_bidi_reordered_ = false; | |
296 #endif | |
297 | |
298 NGLogicalOffset origin_point = | 124 NGLogicalOffset origin_point = |
299 GetOriginPointForFloats(ContainerBfcOffset(), content_size_); | 125 GetOriginPointForFloats(ContainerBfcOffset(), content_size_); |
300 PositionPendingFloats(origin_point.block_offset, &container_builder_, | 126 PositionPendingFloats(origin_point.block_offset, &container_builder_, |
301 MutableConstraintSpace()); | 127 MutableConstraintSpace()); |
302 FindNextLayoutOpportunity(); | 128 FindNextLayoutOpportunity(); |
303 return true; | 129 return true; |
304 } | 130 } |
305 | 131 |
306 void NGInlineLayoutAlgorithm::BidiReorder( | 132 void NGInlineLayoutAlgorithm::BidiReorder(NGInlineItemResults* line_items) { |
307 Vector<LineItemChunk, 32>* line_item_chunks) { | |
308 #if DCHECK_IS_ON() | |
309 DCHECK(!is_bidi_reordered_); | |
310 is_bidi_reordered_ = true; | |
311 #endif | |
312 | |
313 // TODO(kojii): UAX#9 L1 is not supported yet. Supporting L1 may change | 133 // TODO(kojii): UAX#9 L1 is not supported yet. Supporting L1 may change |
314 // embedding levels of parts of runs, which requires to split items. | 134 // embedding levels of parts of runs, which requires to split items. |
315 // http://unicode.org/reports/tr9/#L1 | 135 // http://unicode.org/reports/tr9/#L1 |
316 // BidiResolver does not support L1 crbug.com/316409. | 136 // BidiResolver does not support L1 crbug.com/316409. |
317 | 137 |
318 // Create a list of chunk indices in the visual order. | 138 // Create a list of chunk indices in the visual order. |
319 // ICU |ubidi_getVisualMap()| works for a run of characters. Since we can | 139 // ICU |ubidi_getVisualMap()| works for a run of characters. Since we can |
320 // handle the direction of each run, we use |ubidi_reorderVisual()| to reorder | 140 // handle the direction of each run, we use |ubidi_reorderVisual()| to reorder |
321 // runs instead of characters. | 141 // runs instead of characters. |
322 Vector<UBiDiLevel, 32> levels; | 142 Vector<UBiDiLevel, 32> levels; |
323 levels.ReserveInitialCapacity(line_item_chunks->size()); | 143 levels.ReserveInitialCapacity(line_items->size()); |
324 const Vector<NGInlineItem>& items = Node()->Items(); | 144 const Vector<NGInlineItem>& items = Node()->Items(); |
325 for (const auto& chunk : *line_item_chunks) | 145 for (const auto& item_result : *line_items) |
326 levels.push_back(items[chunk.index].BidiLevel()); | 146 levels.push_back(items[item_result.item_index].BidiLevel()); |
327 Vector<int32_t, 32> indices_in_visual_order(line_item_chunks->size()); | 147 Vector<int32_t, 32> indices_in_visual_order(line_items->size()); |
328 NGBidiParagraph::IndicesInVisualOrder(levels, &indices_in_visual_order); | 148 NGBidiParagraph::IndicesInVisualOrder(levels, &indices_in_visual_order); |
329 | 149 |
330 // Reorder |line_item_chunks| in visual order. | 150 // Reorder to the visual order. |
331 Vector<LineItemChunk, 32> line_item_chunks_in_visual_order( | 151 NGInlineItemResults line_items_in_visual_order(line_items->size()); |
332 line_item_chunks->size()); | |
333 for (unsigned visual_index = 0; visual_index < indices_in_visual_order.size(); | 152 for (unsigned visual_index = 0; visual_index < indices_in_visual_order.size(); |
334 visual_index++) { | 153 visual_index++) { |
335 unsigned logical_index = indices_in_visual_order[visual_index]; | 154 unsigned logical_index = indices_in_visual_order[visual_index]; |
336 line_item_chunks_in_visual_order[visual_index] = | 155 line_items_in_visual_order[visual_index] = |
337 (*line_item_chunks)[logical_index]; | 156 std::move((*line_items)[logical_index]); |
338 } | 157 } |
339 | 158 |
340 // Keep Open before Close in the visual order. | 159 // Keep Open before Close in the visual order. |
341 HashMap<LayoutObject*, unsigned> first_index; | 160 HashMap<LayoutObject*, unsigned> first_index; |
342 for (unsigned i = 0; i < line_item_chunks_in_visual_order.size(); i++) { | 161 for (unsigned i = 0; i < line_items_in_visual_order.size(); i++) { |
343 LineItemChunk& chunk = line_item_chunks_in_visual_order[i]; | 162 NGInlineItemResult& item_result = line_items_in_visual_order[i]; |
344 const NGInlineItem& item = items[chunk.index]; | 163 const NGInlineItem& item = items[item_result.item_index]; |
345 if (item.Type() != NGInlineItem::kOpenTag && | 164 if (item.Type() != NGInlineItem::kOpenTag && |
346 item.Type() != NGInlineItem::kCloseTag) { | 165 item.Type() != NGInlineItem::kCloseTag) { |
347 continue; | 166 continue; |
348 } | 167 } |
349 auto result = first_index.insert(item.GetLayoutObject(), i); | 168 auto result = first_index.insert(item.GetLayoutObject(), i); |
350 if (!result.is_new_entry && item.Type() == NGInlineItem::kOpenTag) { | 169 if (!result.is_new_entry && item.Type() == NGInlineItem::kOpenTag) { |
351 std::swap(line_item_chunks_in_visual_order[i], | 170 std::swap(line_items_in_visual_order[i], |
352 line_item_chunks_in_visual_order[result.stored_value->value]); | 171 line_items_in_visual_order[result.stored_value->value]); |
353 } | 172 } |
354 } | 173 } |
355 | 174 |
356 line_item_chunks->swap(line_item_chunks_in_visual_order); | 175 line_items->swap(line_items_in_visual_order); |
357 } | 176 } |
358 | 177 |
359 // TODO(glebl): Add the support of clearance for inline floats. | 178 // TODO(glebl): Add the support of clearance for inline floats. |
360 void NGInlineLayoutAlgorithm::LayoutAndPositionFloat( | 179 void NGInlineLayoutAlgorithm::LayoutAndPositionFloat( |
361 LayoutUnit end_position, | 180 LayoutUnit end_position, |
362 LayoutObject* layout_object) { | 181 LayoutObject* layout_object) { |
363 NGBlockNode* node = new NGBlockNode(layout_object); | 182 NGBlockNode* node = new NGBlockNode(layout_object); |
364 | 183 |
365 RefPtr<NGConstraintSpace> float_space = | 184 RefPtr<NGConstraintSpace> float_space = |
366 CreateConstraintSpaceForFloat(Node()->Style(), *node, &space_builder_); | 185 CreateConstraintSpaceForFloat(Node()->Style(), *node, &space_builder_); |
(...skipping 28 matching lines...) Expand all Loading... |
395 float_does_not_fit) { | 214 float_does_not_fit) { |
396 container_builder_.AddUnpositionedFloat(floating_object); | 215 container_builder_.AddUnpositionedFloat(floating_object); |
397 } else { | 216 } else { |
398 container_builder_.MutablePositionedFloats().push_back( | 217 container_builder_.MutablePositionedFloats().push_back( |
399 PositionFloat(floating_object.Get(), MutableConstraintSpace())); | 218 PositionFloat(floating_object.Get(), MutableConstraintSpace())); |
400 FindNextLayoutOpportunity(); | 219 FindNextLayoutOpportunity(); |
401 } | 220 } |
402 } | 221 } |
403 | 222 |
404 bool NGInlineLayoutAlgorithm::PlaceItems( | 223 bool NGInlineLayoutAlgorithm::PlaceItems( |
405 const Vector<LineItemChunk, 32>& line_item_chunks) { | 224 NGInlineItemResults* line_items, |
| 225 RefPtr<NGInlineBreakToken> break_token) { |
406 const Vector<NGInlineItem>& items = Node()->Items(); | 226 const Vector<NGInlineItem>& items = Node()->Items(); |
407 | 227 |
408 const ComputedStyle& line_style = LineStyle(); | 228 const ComputedStyle& line_style = LineStyle(); |
409 NGLineHeightMetrics line_metrics(line_style, baseline_type_); | 229 NGLineHeightMetrics line_metrics(line_style, baseline_type_); |
410 NGLineHeightMetrics line_metrics_with_leading = line_metrics; | 230 NGLineHeightMetrics line_metrics_with_leading = line_metrics; |
411 line_metrics_with_leading.AddLeading(line_style.ComputedLineHeightAsFixed()); | 231 line_metrics_with_leading.AddLeading(line_style.ComputedLineHeightAsFixed()); |
412 NGLineBoxFragmentBuilder line_box(Node()); | 232 NGLineBoxFragmentBuilder line_box(Node()); |
413 | 233 |
414 // Compute heights of all inline items by placing the dominant baseline at 0. | 234 // Compute heights of all inline items by placing the dominant baseline at 0. |
415 // The baseline is adjusted after the height of the line box is computed. | 235 // The baseline is adjusted after the height of the line box is computed. |
416 NGTextFragmentBuilder text_builder(Node()); | 236 NGTextFragmentBuilder text_builder(Node()); |
417 NGInlineBoxState* box = | 237 NGInlineBoxState* box = |
418 box_states_.OnBeginPlaceItems(&LineStyle(), baseline_type_); | 238 box_states_.OnBeginPlaceItems(&LineStyle(), baseline_type_); |
419 LayoutUnit inline_size; | 239 LayoutUnit inline_size; |
420 for (const auto& line_item_chunk : line_item_chunks) { | 240 for (auto& item_result : *line_items) { |
421 const NGInlineItem& item = items[line_item_chunk.index]; | 241 const NGInlineItem& item = items[item_result.item_index]; |
422 LayoutUnit line_top; | 242 LayoutUnit line_top; |
423 if (item.Type() == NGInlineItem::kText) { | 243 if (item.Type() == NGInlineItem::kText) { |
424 DCHECK(item.GetLayoutObject()->IsText()); | 244 DCHECK(item.GetLayoutObject()->IsText()); |
425 DCHECK(!box->text_metrics.IsEmpty()); | 245 DCHECK(!box->text_metrics.IsEmpty()); |
426 line_top = box->text_top; | 246 line_top = box->text_top; |
427 text_builder.SetSize( | 247 text_builder.SetSize( |
428 {line_item_chunk.inline_size, box->text_metrics.LineHeight()}); | 248 {item_result.inline_size, box->text_metrics.LineHeight()}); |
429 // Take all used fonts into account if 'line-height: normal'. | 249 // Take all used fonts into account if 'line-height: normal'. |
430 if (box->include_used_fonts) { | 250 if (box->include_used_fonts) { |
431 box->AccumulateUsedFonts(item, line_item_chunk.start_offset, | 251 box->AccumulateUsedFonts(item, item_result.start_offset, |
432 line_item_chunk.end_offset, baseline_type_); | 252 item_result.end_offset, baseline_type_); |
433 } | 253 } |
434 } else if (item.Type() == NGInlineItem::kOpenTag) { | 254 } else if (item.Type() == NGInlineItem::kOpenTag) { |
435 box = box_states_.OnOpenTag(item, &line_box, &text_builder); | 255 box = box_states_.OnOpenTag(item, &line_box, &text_builder); |
436 // Compute text metrics for all inline boxes since even empty inlines | 256 // Compute text metrics for all inline boxes since even empty inlines |
437 // influence the line height. | 257 // influence the line height. |
438 // https://drafts.csswg.org/css2/visudet.html#line-height | 258 // https://drafts.csswg.org/css2/visudet.html#line-height |
439 // TODO(kojii): Review if atomic inline level should have open/close. | 259 // TODO(kojii): Review if atomic inline level should have open/close. |
440 if (!item.GetLayoutObject()->IsAtomicInlineLevel()) | 260 if (!item.GetLayoutObject()->IsAtomicInlineLevel()) |
441 box->ComputeTextMetrics(*item.Style(), baseline_type_); | 261 box->ComputeTextMetrics(*item.Style(), baseline_type_); |
442 continue; | 262 continue; |
443 } else if (item.Type() == NGInlineItem::kCloseTag) { | 263 } else if (item.Type() == NGInlineItem::kCloseTag) { |
444 box = box_states_.OnCloseTag(item, &line_box, box, baseline_type_); | 264 box = box_states_.OnCloseTag(item, &line_box, box, baseline_type_); |
445 continue; | 265 continue; |
446 } else if (item.Type() == NGInlineItem::kAtomicInline) { | 266 } else if (item.Type() == NGInlineItem::kAtomicInline) { |
447 line_top = PlaceAtomicInline(item, &line_box, box, &text_builder); | 267 line_top = |
| 268 PlaceAtomicInline(item, &item_result, &line_box, box, &text_builder); |
448 } else if (item.Type() == NGInlineItem::kOutOfFlowPositioned) { | 269 } else if (item.Type() == NGInlineItem::kOutOfFlowPositioned) { |
449 // TODO(layout-dev): Report the correct static position for the out of | 270 // TODO(layout-dev): Report the correct static position for the out of |
450 // flow descendant. We can't do this here yet as it doesn't know the | 271 // flow descendant. We can't do this here yet as it doesn't know the |
451 // size of the line box. | 272 // size of the line box. |
452 container_builder_.AddOutOfFlowDescendant( | 273 container_builder_.AddOutOfFlowDescendant( |
453 // Absolute positioning blockifies the box's display type. | 274 // Absolute positioning blockifies the box's display type. |
454 // https://drafts.csswg.org/css-display/#transformations | 275 // https://drafts.csswg.org/css-display/#transformations |
455 new NGBlockNode(item.GetLayoutObject()), | 276 new NGBlockNode(item.GetLayoutObject()), |
456 NGStaticPosition::Create(ConstraintSpace().WritingMode(), | 277 NGStaticPosition::Create(ConstraintSpace().WritingMode(), |
457 ConstraintSpace().Direction(), | 278 ConstraintSpace().Direction(), |
458 NGPhysicalOffset())); | 279 NGPhysicalOffset())); |
459 continue; | 280 continue; |
460 } else { | 281 } else { |
461 continue; | 282 continue; |
462 } | 283 } |
463 | 284 |
464 RefPtr<NGPhysicalTextFragment> text_fragment = text_builder.ToTextFragment( | 285 RefPtr<NGPhysicalTextFragment> text_fragment = text_builder.ToTextFragment( |
465 line_item_chunk.index, line_item_chunk.start_offset, | 286 item_result.item_index, item_result.start_offset, |
466 line_item_chunk.end_offset); | 287 item_result.end_offset); |
467 | 288 |
468 NGLogicalOffset logical_offset( | 289 NGLogicalOffset logical_offset( |
469 inline_size + current_opportunity_.InlineStartOffset() - | 290 inline_size + current_opportunity_.InlineStartOffset() - |
470 ConstraintSpace().BfcOffset().inline_offset, | 291 ConstraintSpace().BfcOffset().inline_offset, |
471 line_top); | 292 line_top); |
472 line_box.AddChild(std::move(text_fragment), logical_offset); | 293 line_box.AddChild(std::move(text_fragment), logical_offset); |
473 inline_size += line_item_chunk.inline_size; | 294 inline_size += item_result.inline_size; |
474 } | 295 } |
475 | 296 |
476 if (line_box.Children().IsEmpty()) { | 297 if (line_box.Children().IsEmpty()) { |
477 return true; // The line was empty. | 298 return true; // The line was empty. |
478 } | 299 } |
479 | 300 |
480 box_states_.OnEndPlaceItems(&line_box, baseline_type_); | 301 box_states_.OnEndPlaceItems(&line_box, baseline_type_); |
481 | 302 |
482 // The baselines are always placed at pixel boundaries. Not doing so results | 303 // The baselines are always placed at pixel boundaries. Not doing so results |
483 // in incorrect layout of text decorations, most notably underlines. | 304 // in incorrect layout of text decorations, most notably underlines. |
484 LayoutUnit baseline = content_size_ + line_box.Metrics().ascent; | 305 LayoutUnit baseline = content_size_ + line_box.Metrics().ascent; |
485 baseline = LayoutUnit(baseline.Round()); | 306 baseline = LayoutUnit(baseline.Round()); |
486 | 307 |
487 // Check if the line fits into the constraint space in block direction. | 308 // Check if the line fits into the constraint space in block direction. |
488 LayoutUnit line_bottom = baseline + line_box.Metrics().descent; | 309 LayoutUnit line_bottom = baseline + line_box.Metrics().descent; |
489 | 310 |
490 if (!container_builder_.Children().IsEmpty() && | 311 if (!container_builder_.Children().IsEmpty() && |
491 ConstraintSpace().AvailableSize().block_size != NGSizeIndefinite && | 312 ConstraintSpace().AvailableSize().block_size != NGSizeIndefinite && |
492 line_bottom > ConstraintSpace().AvailableSize().block_size) { | 313 line_bottom > ConstraintSpace().AvailableSize().block_size) { |
493 return false; | 314 return false; |
494 } | 315 } |
495 | 316 |
496 // If there are more content to consume, create an unfinished break token. | 317 line_box.SetBreakToken(std::move(break_token)); |
497 if (last_break_opportunity_index_ != items.size() - 1 || | |
498 last_break_opportunity_offset_ != Node()->Text().length()) { | |
499 line_box.SetBreakToken(NGInlineBreakToken::Create( | |
500 Node(), last_break_opportunity_index_, last_break_opportunity_offset_)); | |
501 } | |
502 | 318 |
503 // TODO(kojii): Implement flipped line (vertical-lr). In this case, line_top | 319 // TODO(kojii): Implement flipped line (vertical-lr). In this case, line_top |
504 // and block_start do not match. | 320 // and block_start do not match. |
505 | 321 |
506 // Up until this point, children are placed so that the dominant baseline is | 322 // Up until this point, children are placed so that the dominant baseline is |
507 // at 0. Move them to the final baseline position, and set the logical top of | 323 // at 0. Move them to the final baseline position, and set the logical top of |
508 // the line box to the line top. | 324 // the line box to the line top. |
509 line_box.MoveChildrenInBlockDirection(baseline); | 325 line_box.MoveChildrenInBlockDirection(baseline); |
510 line_box.SetInlineSize(inline_size); | 326 line_box.SetInlineSize(inline_size); |
511 container_builder_.AddChild( | 327 container_builder_.AddChild( |
512 line_box.ToLineBoxFragment(), | 328 line_box.ToLineBoxFragment(), |
513 {LayoutUnit(), baseline - box_states_.LineBoxState().metrics.ascent}); | 329 {LayoutUnit(), baseline - box_states_.LineBoxState().metrics.ascent}); |
514 | 330 |
515 max_inline_size_ = std::max(max_inline_size_, inline_size); | 331 max_inline_size_ = std::max(max_inline_size_, inline_size); |
516 content_size_ = line_bottom; | 332 content_size_ = line_bottom; |
517 return true; | 333 return true; |
518 } | 334 } |
519 | 335 |
| 336 // TODO(kojii): Currently, this function does not change item_result, but |
| 337 // when NG paint is enabled, this will std::move() the LayoutResult. |
520 LayoutUnit NGInlineLayoutAlgorithm::PlaceAtomicInline( | 338 LayoutUnit NGInlineLayoutAlgorithm::PlaceAtomicInline( |
521 const NGInlineItem& item, | 339 const NGInlineItem& item, |
| 340 NGInlineItemResult* item_result, |
522 NGLineBoxFragmentBuilder* line_box, | 341 NGLineBoxFragmentBuilder* line_box, |
523 NGInlineBoxState* state, | 342 NGInlineBoxState* state, |
524 NGTextFragmentBuilder* text_builder) { | 343 NGTextFragmentBuilder* text_builder) { |
| 344 DCHECK(item_result->layout_result); |
525 // For replaced elements, inline-block elements, and inline-table elements, | 345 // For replaced elements, inline-block elements, and inline-table elements, |
526 // the height is the height of their margin box. | 346 // the height is the height of their margin box. |
527 // https://drafts.csswg.org/css2/visudet.html#line-height | 347 // https://drafts.csswg.org/css2/visudet.html#line-height |
528 NGBoxFragment fragment( | 348 NGBoxFragment fragment( |
529 ConstraintSpace().WritingMode(), | 349 ConstraintSpace().WritingMode(), |
530 ToNGPhysicalBoxFragment(LayoutItem(item)->PhysicalFragment().Get())); | 350 ToNGPhysicalBoxFragment( |
| 351 item_result->layout_result->PhysicalFragment().Get())); |
531 DCHECK(item.Style()); | 352 DCHECK(item.Style()); |
532 NGBoxStrut margins = ComputeMargins(ConstraintSpace(), *item.Style(), | 353 NGBoxStrut margins = ComputeMargins(ConstraintSpace(), *item.Style(), |
533 ConstraintSpace().WritingMode(), | 354 ConstraintSpace().WritingMode(), |
534 item.Style()->Direction()); | 355 item.Style()->Direction()); |
535 LayoutUnit block_size = fragment.BlockSize() + margins.BlockSum(); | 356 LayoutUnit block_size = fragment.BlockSize() + margins.BlockSum(); |
536 | 357 |
537 // TODO(kojii): Try to eliminate the wrapping text fragment and use the | 358 // TODO(kojii): Try to eliminate the wrapping text fragment and use the |
538 // |fragment| directly. Currently |CopyFragmentDataToLayoutBlockFlow| | 359 // |fragment| directly. Currently |CopyFragmentDataToLayoutBlockFlow| |
539 // requires a text fragment. | 360 // requires a text fragment. |
540 text_builder->SetSize({fragment.InlineSize(), block_size}); | 361 text_builder->SetSize({fragment.InlineSize(), block_size}); |
(...skipping 20 matching lines...) Expand all Loading... |
561 if (container_builder_.BfcOffset()) | 382 if (container_builder_.BfcOffset()) |
562 iter_offset = ContainerBfcOffset(); | 383 iter_offset = ContainerBfcOffset(); |
563 iter_offset.block_offset += content_size_; | 384 iter_offset.block_offset += content_size_; |
564 auto* iter = MutableConstraintSpace()->LayoutOpportunityIterator(iter_offset); | 385 auto* iter = MutableConstraintSpace()->LayoutOpportunityIterator(iter_offset); |
565 NGLayoutOpportunity opportunity = iter->Next(); | 386 NGLayoutOpportunity opportunity = iter->Next(); |
566 if (!opportunity.IsEmpty()) | 387 if (!opportunity.IsEmpty()) |
567 current_opportunity_ = opportunity; | 388 current_opportunity_ = opportunity; |
568 } | 389 } |
569 | 390 |
570 RefPtr<NGLayoutResult> NGInlineLayoutAlgorithm::Layout() { | 391 RefPtr<NGLayoutResult> NGInlineLayoutAlgorithm::Layout() { |
571 // TODO(koji): The relationship of NGInlineLayoutAlgorithm and NGLineBreaker | 392 NGLineBreaker line_breaker(Node(), constraint_space_, BreakToken()); |
572 // should be inverted. | 393 NGInlineItemResults item_results; |
573 if (!Node()->Text().IsEmpty()) { | 394 while (true) { |
574 // TODO(eae): Does this need the LayoutText::LocaleForLineBreakIterator | 395 line_breaker.NextLine(&item_results, this); |
575 // logic to switch the locale based on breaking mode? | 396 if (item_results.IsEmpty()) |
576 NGLineBreaker line_breaker(Node()->Style().Locale()); | 397 break; |
577 line_breaker.BreakLines(this, Node()->Text(), start_offset_); | 398 CreateLine(&item_results, line_breaker.CreateBreakToken()); |
| 399 item_results.clear(); |
578 } | 400 } |
579 | 401 |
580 // TODO(kojii): Check if the line box width should be content or available. | 402 // TODO(kojii): Check if the line box width should be content or available. |
581 NGLogicalSize size(max_inline_size_, content_size_); | 403 NGLogicalSize size(max_inline_size_, content_size_); |
582 container_builder_.SetSize(size).SetOverflowSize(size); | 404 container_builder_.SetSize(size).SetOverflowSize(size); |
583 | 405 |
584 // TODO(crbug.com/716930): We may be an empty LayoutInline due to splitting. | 406 // TODO(crbug.com/716930): We may be an empty LayoutInline due to splitting. |
585 // Margin struts shouldn't need to be passed through like this once we've | 407 // Margin struts shouldn't need to be passed through like this once we've |
586 // removed LayoutInline splitting. | 408 // removed LayoutInline splitting. |
587 if (!container_builder_.BfcOffset()) { | 409 if (!container_builder_.BfcOffset()) { |
588 container_builder_.SetEndMarginStrut(ConstraintSpace().MarginStrut()); | 410 container_builder_.SetEndMarginStrut(ConstraintSpace().MarginStrut()); |
589 } | 411 } |
590 | 412 |
591 return container_builder_.ToBoxFragment(); | 413 return container_builder_.ToBoxFragment(); |
592 } | 414 } |
593 | 415 |
594 MinMaxContentSize NGInlineLayoutAlgorithm::ComputeMinMaxContentSizeByLayout() { | |
595 DCHECK_EQ(ConstraintSpace().AvailableSize().inline_size, LayoutUnit()); | |
596 DCHECK_EQ(ConstraintSpace().AvailableSize().block_size, NGSizeIndefinite); | |
597 if (!Node()->Text().IsEmpty()) { | |
598 NGLineBreaker line_breaker(Node()->Style().Locale()); | |
599 line_breaker.BreakLines(this, Node()->Text(), start_offset_); | |
600 } | |
601 MinMaxContentSize sizes; | |
602 sizes.min_content = MaxInlineSize(); | |
603 | |
604 // max-content is the width without any line wrapping. | |
605 // TODO(kojii): Implement hard breaks (<br> etc.) to break. | |
606 for (const auto& item : Node()->Items()) | |
607 sizes.max_content += InlineSize(item); | |
608 | |
609 return sizes; | |
610 } | |
611 | |
612 } // namespace blink | 416 } // namespace blink |
OLD | NEW |