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