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_line_breaker.h" | 5 #include "core/layout/ng/inline/ng_line_breaker.h" |
6 | 6 |
7 #include "core/layout/ng/inline/ng_inline_break_token.h" | 7 #include "core/layout/ng/inline/ng_inline_break_token.h" |
8 #include "core/layout/ng/inline/ng_inline_layout_algorithm.h" | 8 #include "core/layout/ng/inline/ng_inline_layout_algorithm.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_text_fragment.h" | 10 #include "core/layout/ng/inline/ng_text_fragment.h" |
11 #include "core/layout/ng/ng_box_fragment.h" | 11 #include "core/layout/ng/ng_box_fragment.h" |
12 #include "core/layout/ng/ng_break_token.h" | 12 #include "core/layout/ng/ng_break_token.h" |
13 #include "core/layout/ng/ng_constraint_space.h" | 13 #include "core/layout/ng/ng_constraint_space.h" |
14 #include "core/layout/ng/ng_fragment_builder.h" | 14 #include "core/layout/ng/ng_fragment_builder.h" |
15 #include "core/layout/ng/ng_layout_opportunity_iterator.h" | 15 #include "core/layout/ng/ng_layout_opportunity_iterator.h" |
16 #include "core/layout/ng/ng_length_utils.h" | 16 #include "core/layout/ng/ng_length_utils.h" |
17 #include "core/style/ComputedStyle.h" | 17 #include "core/style/ComputedStyle.h" |
18 #include "platform/fonts/shaping/HarfBuzzShaper.h" | 18 #include "platform/fonts/shaping/HarfBuzzShaper.h" |
19 #include "platform/fonts/shaping/ShapingLineBreaker.h" | 19 #include "platform/fonts/shaping/ShapingLineBreaker.h" |
20 #include "platform/text/TextBreakIterator.h" | |
21 | 20 |
22 namespace blink { | 21 namespace blink { |
23 | 22 |
24 namespace { | 23 namespace { |
25 | 24 |
26 // Use a mock of ShapingLineBreaker for test/debug purposes. | 25 // Use a mock of ShapingLineBreaker for test/debug purposes. |
27 #define MOCK_SHAPE_LINE | 26 #define MOCK_SHAPE_LINE |
28 | 27 |
29 #if defined(MOCK_SHAPE_LINE) | 28 #if defined(MOCK_SHAPE_LINE) |
30 // The mock for ShapingLineBreaker::ShapeLine(). | 29 // The mock for ShapingLineBreaker::ShapeLine(). |
31 // Given the design of ShapingLineBreaker, expected semantics are: | 30 // See BreakText() for the expected semantics. |
32 // - The returned offset is always > item.StartOffset(). | |
33 // - offset < item.EndOffset(): | |
34 // - width <= available_width: the break opportunity to fit is found. | |
35 // - width > available_width: the first break opportunity did not fit. | |
36 // - offset == item.EndOffset(): | |
37 // - width <= available_width: the break opportunity at the end of the item | |
38 // fits. | |
39 // - width > available_width: the first break opportunity is at the end of | |
40 // the item and it does not fit. | |
41 // - offset > item.EndOffset():, the first break opportunity is beyond the | |
42 // end of item and thus cannot measure. In this case, inline_size shows the | |
43 // width until the end of the item. It may fit or may not. | |
44 std::pair<unsigned, LayoutUnit> ShapeLineMock( | 31 std::pair<unsigned, LayoutUnit> ShapeLineMock( |
45 const NGInlineItem& item, | 32 const NGInlineItem& item, |
46 unsigned offset, | 33 unsigned offset, |
47 LayoutUnit available_width, | 34 LayoutUnit available_width, |
48 const LazyLineBreakIterator& break_iterator) { | 35 const LazyLineBreakIterator& break_iterator) { |
49 bool has_break_opportunities = false; | 36 bool has_break_opportunities = false; |
50 LayoutUnit inline_size; | 37 LayoutUnit inline_size; |
51 while (true) { | 38 while (true) { |
52 unsigned next_break = break_iterator.NextBreakOpportunity(offset + 1); | 39 unsigned next_break = break_iterator.NextBreakOpportunity(offset + 1); |
53 LayoutUnit next_inline_size = | 40 LayoutUnit next_inline_size = |
54 inline_size + | 41 inline_size + |
55 item.InlineSize(offset, std::min(next_break, item.EndOffset())); | 42 item.InlineSize(offset, std::min(next_break, item.EndOffset())); |
56 if (next_inline_size > available_width) { | 43 if (next_inline_size > available_width) { |
57 if (!has_break_opportunities) | 44 if (!has_break_opportunities) |
58 return std::make_pair(next_break, next_inline_size); | 45 return std::make_pair(next_break, next_inline_size); |
59 return std::make_pair(offset, inline_size); | 46 return std::make_pair(offset, inline_size); |
60 } | 47 } |
61 if (next_break >= item.EndOffset()) | 48 if (next_break >= item.EndOffset()) |
62 return std::make_pair(next_break, next_inline_size); | 49 return std::make_pair(next_break, next_inline_size); |
63 offset = next_break; | 50 offset = next_break; |
64 inline_size = next_inline_size; | 51 inline_size = next_inline_size; |
65 has_break_opportunities = true; | 52 has_break_opportunities = true; |
66 } | 53 } |
67 } | 54 } |
68 #endif | 55 #endif |
69 | 56 |
70 LineBreakType GetLineBreakType(const ComputedStyle& style) { | |
71 if (style.AutoWrap()) { | |
72 if (style.WordBreak() == EWordBreak::kBreakAll || | |
73 style.WordBreak() == EWordBreak::kBreakWord) | |
74 return LineBreakType::kBreakAll; | |
75 if (style.WordBreak() == EWordBreak::kKeepAll) | |
76 return LineBreakType::kKeepAll; | |
77 } | |
78 return LineBreakType::kNormal; | |
79 } | |
80 | |
81 } // namespace | 57 } // namespace |
82 | 58 |
83 NGLineBreaker::NGLineBreaker(NGInlineNode* node, | 59 NGLineBreaker::NGLineBreaker(NGInlineNode* node, |
84 const NGConstraintSpace* space, | 60 const NGConstraintSpace* space, |
85 NGInlineBreakToken* break_token) | 61 NGInlineBreakToken* break_token) |
86 : node_(node), constraint_space_(space), item_index_(0), offset_(0) { | 62 : node_(node), |
63 constraint_space_(space), | |
64 item_index_(0), | |
65 offset_(0), | |
66 break_iterator_(node->Text()) { | |
87 if (break_token) { | 67 if (break_token) { |
88 item_index_ = break_token->ItemIndex(); | 68 item_index_ = break_token->ItemIndex(); |
89 offset_ = break_token->TextOffset(); | 69 offset_ = break_token->TextOffset(); |
90 node->AssertOffset(item_index_, offset_); | 70 node->AssertOffset(item_index_, offset_); |
91 } | 71 } |
92 } | 72 } |
93 | 73 |
94 void NGLineBreaker::NextLine(NGInlineItemResults* item_results, | 74 void NGLineBreaker::NextLine(NGInlineItemResults* item_results, |
95 NGInlineLayoutAlgorithm* algorithm) { | 75 NGInlineLayoutAlgorithm* algorithm) { |
96 BreakLine(item_results, algorithm); | 76 BreakLine(item_results, algorithm); |
97 | 77 |
98 // TODO(kojii): When editing, or caret is enabled, trailing spaces at wrap | 78 // TODO(kojii): When editing, or caret is enabled, trailing spaces at wrap |
99 // point should not be removed. For other cases, we can a) remove, b) leave | 79 // point should not be removed. For other cases, we can a) remove, b) leave |
100 // characters without glyphs, or c) leave both characters and glyphs without | 80 // characters without glyphs, or c) leave both characters and glyphs without |
101 // measuring. Need to decide which one works the best. | 81 // measuring. Need to decide which one works the best. |
102 SkipCollapsibleWhitespaces(); | 82 SkipCollapsibleWhitespaces(); |
103 } | 83 } |
104 | 84 |
105 void NGLineBreaker::BreakLine(NGInlineItemResults* item_results, | 85 void NGLineBreaker::BreakLine(NGInlineItemResults* item_results, |
106 NGInlineLayoutAlgorithm* algorithm) { | 86 NGInlineLayoutAlgorithm* algorithm) { |
107 DCHECK(item_results->IsEmpty()); | 87 DCHECK(item_results->IsEmpty()); |
108 const Vector<NGInlineItem>& items = node_->Items(); | 88 const Vector<NGInlineItem>& items = node_->Items(); |
109 const String& text = node_->Text(); | |
110 const ComputedStyle& style = node_->Style(); | 89 const ComputedStyle& style = node_->Style(); |
111 LazyLineBreakIterator break_iterator(text, style.LocaleForLineBreakIterator(), | 90 UpdateStyle(style); |
112 GetLineBreakType(style)); | |
113 #if !defined(MOCK_SHAPE_LINE) | 91 #if !defined(MOCK_SHAPE_LINE) |
92 // TODO(kojii): Instantiate in the constructor. | |
114 HarfBuzzShaper shaper(text.Characters16(), text.length()); | 93 HarfBuzzShaper shaper(text.Characters16(), text.length()); |
115 #endif | 94 #endif |
116 LayoutUnit available_width = algorithm->AvailableWidth(); | 95 available_width_ = algorithm->AvailableWidth(); |
117 LayoutUnit position; | 96 position_ = LayoutUnit(0); |
97 LineBreakState state = LineBreakState::kNotBreakable; | |
98 | |
118 while (item_index_ < items.size()) { | 99 while (item_index_ < items.size()) { |
100 // CloseTag prohibits to break before. | |
119 const NGInlineItem& item = items[item_index_]; | 101 const NGInlineItem& item = items[item_index_]; |
102 if (item.Type() == NGInlineItem::kCloseTag) { | |
103 item_results->push_back( | |
104 NGInlineItemResult(item_index_, offset_, item.EndOffset())); | |
105 HandleCloseTag(item, &item_results->back()); | |
106 continue; | |
107 } | |
108 | |
109 if (state == LineBreakState::kBreakAfterTrailings) | |
110 return; | |
111 if (state == LineBreakState::kIsBreakable && position_ > available_width_) | |
112 return HandleOverflow(item_results); | |
113 | |
120 item_results->push_back( | 114 item_results->push_back( |
121 NGInlineItemResult(item_index_, offset_, item.EndOffset())); | 115 NGInlineItemResult(item_index_, offset_, item.EndOffset())); |
122 NGInlineItemResult* item_result = &item_results->back(); | 116 NGInlineItemResult* item_result = &item_results->back(); |
117 if (item.Type() == NGInlineItem::kText) { | |
118 state = HandleText(item, item_result); | |
119 } else if (item.Type() == NGInlineItem::kAtomicInline) { | |
120 state = HandleAtomicInline(item, item_result); | |
121 } else if (item.Type() == NGInlineItem::kControl) { | |
122 state = HandleControlItem(item, item_result); | |
123 if (state == LineBreakState::kForcedBreak) | |
124 return; | |
125 } else if (item.Type() == NGInlineItem::kOpenTag) { | |
126 HandleOpenTag(item, item_result); | |
127 state = LineBreakState::kNotBreakable; | |
128 } else if (item.Type() == NGInlineItem::kFloating) { | |
129 HandleFloat(item, item_results, algorithm); | |
130 } else { | |
131 MoveToNextOf(item); | |
132 } | |
133 } | |
134 if (state == LineBreakState::kIsBreakable && position_ > available_width_) | |
135 return HandleOverflow(item_results); | |
136 } | |
123 | 137 |
124 // If the start offset is at the item boundary, try to add the entire item. | 138 NGLineBreaker::LineBreakState NGLineBreaker::HandleText( |
125 if (offset_ == item.StartOffset()) { | 139 const NGInlineItem& item, |
126 if (item.Type() == NGInlineItem::kText) { | 140 NGInlineItemResult* item_result) { |
127 item_result->inline_size = item.InlineSize(); | 141 DCHECK_EQ(item.Type(), NGInlineItem::kText); |
128 } else if (item.Type() == NGInlineItem::kAtomicInline) { | |
129 LayoutAtomicInline(item, item_result); | |
130 } else if (item.Type() == NGInlineItem::kControl) { | |
131 if (HandleControlItem(item, text, item_result, position)) { | |
132 MoveToNextOf(item); | |
133 break; | |
134 } | |
135 } else if (item.Type() == NGInlineItem::kFloating) { | |
136 algorithm->LayoutAndPositionFloat(position, item.GetLayoutObject()); | |
137 // Floats may change the available width if they fit. | |
138 available_width = algorithm->AvailableWidth(); | |
139 // Floats are already positioned in the container_builder. | |
140 item_results->pop_back(); | |
141 MoveToNextOf(item); | |
142 continue; | |
143 } else { | |
144 MoveToNextOf(item); | |
145 continue; | |
146 } | |
147 LayoutUnit next_position = position + item_result->inline_size; | |
148 if (next_position <= available_width) { | |
149 MoveToNextOf(item); | |
150 position = next_position; | |
151 continue; | |
152 } | |
153 | 142 |
154 // The entire item does not fit. Handle non-text items as overflow, | 143 // If the start offset is at the item boundary, try to add the entire item. |
155 // since only text item is breakable. | 144 if (offset_ == item.StartOffset()) { |
156 if (item.Type() != NGInlineItem::kText) { | 145 item_result->inline_size = item.InlineSize(); |
157 MoveToNextOf(item); | 146 LayoutUnit next_position = position_ + item_result->inline_size; |
158 return HandleOverflow(item_results, break_iterator); | 147 if (!auto_wrap_ || next_position <= available_width_) { |
159 } | 148 position_ = next_position; |
149 MoveToNextOf(item); | |
150 if (auto_wrap_ && break_iterator_.IsBreakable(item.EndOffset())) | |
151 return LineBreakState::kIsBreakable; | |
152 item_result->prohibit_break_after = true; | |
153 return LineBreakState::kNotBreakable; | |
160 } | 154 } |
155 } | |
161 | 156 |
162 // Either the start or the break is in the mid of a text item. | 157 if (auto_wrap_) { |
163 DCHECK_EQ(item.Type(), NGInlineItem::kText); | 158 // Try to break inside of this text item. |
164 DCHECK_LT(offset_, item.EndOffset()); | 159 BreakText(item_result, item, available_width_ - position_); |
165 break_iterator.SetLocale(item.Style()->LocaleForLineBreakIterator()); | 160 position_ += item_result->inline_size; |
166 break_iterator.SetBreakType(GetLineBreakType(*item.Style())); | 161 |
162 bool is_overflow = position_ > available_width_; | |
163 item_result->no_break_opportunities_inside = is_overflow; | |
164 if (item_result->end_offset < item.EndOffset()) { | |
165 offset_ = item_result->end_offset; | |
166 return is_overflow ? LineBreakState::kIsBreakable | |
167 : LineBreakState::kBreakAfterTrailings; | |
168 } | |
169 MoveToNextOf(item); | |
170 return item_result->prohibit_break_after ? LineBreakState::kNotBreakable | |
171 : LineBreakState::kIsBreakable; | |
172 } | |
173 | |
174 // Add the rest of the item if !auto_wrap. | |
175 // Because the start position may need to reshape, run ShapingLineBreaker | |
176 // with max available width. | |
177 DCHECK_NE(offset_, item.StartOffset()); | |
178 BreakText(item_result, item, LayoutUnit::Max()); | |
179 DCHECK_EQ(item_result->end_offset, item.EndOffset()); | |
180 item_result->no_break_opportunities_inside = true; | |
181 item_result->prohibit_break_after = true; | |
182 position_ += item_result->inline_size; | |
183 MoveToNextOf(item); | |
184 return LineBreakState::kNotBreakable; | |
185 } | |
186 | |
187 void NGLineBreaker::BreakText(NGInlineItemResult* item_result, | |
188 const NGInlineItem& item, | |
189 LayoutUnit available_width) { | |
190 DCHECK_EQ(item.Type(), NGInlineItem::kText); | |
191 item.AssertOffset(item_result->start_offset); | |
192 | |
167 #if defined(MOCK_SHAPE_LINE) | 193 #if defined(MOCK_SHAPE_LINE) |
168 unsigned break_offset; | 194 std::tie(item_result->end_offset, item_result->inline_size) = ShapeLineMock( |
169 std::tie(break_offset, item_result->inline_size) = ShapeLineMock( | 195 item, item_result->start_offset, available_width, break_iterator_); |
170 item, offset_, available_width - position, break_iterator); | |
171 #else | 196 #else |
172 // TODO(kojii): We need to instantiate ShapingLineBreaker here because it | 197 // TODO(kojii): We need to instantiate ShapingLineBreaker here because it |
173 // has item-specific info as context. Should they be part of ShapeLine() to | 198 // has item-specific info as context. Should they be part of ShapeLine() to |
174 // instantiate once, or is this just fine since instatiation is not | 199 // instantiate once, or is this just fine since instatiation is not |
175 // expensive? | 200 // expensive? |
176 DCHECK_EQ(item.TextShapeResult()->StartIndexForResult(), | 201 DCHECK_EQ(item.TextShapeResult()->StartIndexForResult(), item.StartOffset()); |
177 item.StartOffset()); | 202 DCHECK_EQ(item.TextShapeResult()->EndIndexForResult(), item.EndOffset()); |
178 DCHECK_EQ(item.TextShapeResult()->EndIndexForResult(), item.EndOffset()); | 203 ShapingLineBreaker breaker(&shaper, &item.Style()->GetFont(), |
179 ShapingLineBreaker breaker(&shaper, &item.Style()->GetFont(), | 204 item.TextShapeResult(), break_iterator_); |
180 item.TextShapeResult(), &break_iterator); | 205 item_result->shape_result = breaker.ShapeLine( |
181 unsigned break_offset; | 206 item_result->start_offset, available_width, &item_result->end_offset); |
182 item_result->shape_result = | 207 item_result->inline_size = item_result->shape_result->SnappedWidth(); |
183 breaker.ShapeLine(offset_, available_width - position, &break_offset); | |
184 item_result->inline_size = item_result->shape_result->SnappedWidth(); | |
185 #endif | 208 #endif |
186 DCHECK_GT(break_offset, offset_); | 209 DCHECK_GT(item_result->end_offset, item_result->start_offset); |
187 position += item_result->inline_size; | 210 // * If width <= available_width: |
188 | 211 // * If offset < item.EndOffset(): the break opportunity to fit is found. |
189 // If the break found within the item, break here. | 212 // * If offset == item.EndOffset(): the break opportunity at the end fits. |
190 if (break_offset < item.EndOffset()) { | 213 // There may be room for more characters. |
191 offset_ = item_result->end_offset = break_offset; | 214 // * If offset > item.EndOffset(): the first break opportunity is beyond |
192 if (position <= available_width) | 215 // the end. There may be room for more characters. |
193 break; | 216 // * If width > available_width: The first break opporunity does not fit. |
194 // The first break opportunity of the item does not fit. | 217 // offset is the first break opportunity, either inside, at the end, or |
195 } else { | 218 // beyond the end. |
196 // No break opporunity in the item, or the first break opportunity is at | 219 if (item_result->end_offset <= item.EndOffset()) { |
197 // the end of the item. If it fits, continue to the next item. | 220 item_result->prohibit_break_after = false; |
198 item_result->end_offset = item.EndOffset(); | 221 } else { |
199 MoveToNextOf(item); | 222 item_result->prohibit_break_after = true; |
200 if (position <= available_width) | 223 item_result->end_offset = item.EndOffset(); |
201 continue; | |
202 } | |
203 | |
204 // We need to look at next item if we're overflowing, and the break | |
205 // opportunity is beyond this item. | |
206 if (break_offset > item.EndOffset()) | |
207 continue; | |
208 return HandleOverflow(item_results, break_iterator); | |
209 } | 224 } |
210 } | 225 } |
211 | 226 |
212 // Measure control items; new lines and tab, that are similar to text, affect | 227 // Measure control items; new lines and tab, that are similar to text, affect |
213 // layout, but do not need shaping/painting. | 228 // layout, but do not need shaping/painting. |
214 bool NGLineBreaker::HandleControlItem(const NGInlineItem& item, | 229 NGLineBreaker::LineBreakState NGLineBreaker::HandleControlItem( |
215 const String& text, | 230 const NGInlineItem& item, |
216 NGInlineItemResult* item_result, | 231 NGInlineItemResult* item_result) { |
217 LayoutUnit position) { | |
218 DCHECK_EQ(item.Length(), 1u); | 232 DCHECK_EQ(item.Length(), 1u); |
219 UChar character = text[item.StartOffset()]; | 233 UChar character = node_->Text()[item.StartOffset()]; |
220 if (character == kNewlineCharacter) | 234 if (character == kNewlineCharacter) { |
221 return true; | 235 MoveToNextOf(item); |
222 | 236 return LineBreakState::kForcedBreak; |
237 } | |
223 DCHECK_EQ(character, kTabulationCharacter); | 238 DCHECK_EQ(character, kTabulationCharacter); |
224 DCHECK(item.Style()); | 239 DCHECK(item.Style()); |
225 const ComputedStyle& style = *item.Style(); | 240 const ComputedStyle& style = *item.Style(); |
226 const Font& font = style.GetFont(); | 241 const Font& font = style.GetFont(); |
227 item_result->inline_size = font.TabWidth(style.GetTabSize(), position); | 242 item_result->inline_size = font.TabWidth(style.GetTabSize(), position_); |
228 return false; | 243 position_ += item_result->inline_size; |
244 MoveToNextOf(item); | |
245 // TODO(kojii): Implement break around the tab character. | |
246 return LineBreakState::kIsBreakable; | |
229 } | 247 } |
230 | 248 |
231 void NGLineBreaker::LayoutAtomicInline(const NGInlineItem& item, | 249 NGLineBreaker::LineBreakState NGLineBreaker::HandleAtomicInline( |
232 NGInlineItemResult* item_result) { | 250 const NGInlineItem& item, |
251 NGInlineItemResult* item_result) { | |
233 DCHECK_EQ(item.Type(), NGInlineItem::kAtomicInline); | 252 DCHECK_EQ(item.Type(), NGInlineItem::kAtomicInline); |
234 NGBlockNode* node = new NGBlockNode(item.GetLayoutObject()); | 253 NGBlockNode* node = new NGBlockNode(item.GetLayoutObject()); |
235 const ComputedStyle& style = node->Style(); | 254 const ComputedStyle& style = node->Style(); |
236 NGConstraintSpaceBuilder constraint_space_builder(constraint_space_); | 255 NGConstraintSpaceBuilder constraint_space_builder(constraint_space_); |
237 RefPtr<NGConstraintSpace> constraint_space = | 256 RefPtr<NGConstraintSpace> constraint_space = |
238 constraint_space_builder.SetIsNewFormattingContext(true) | 257 constraint_space_builder.SetIsNewFormattingContext(true) |
239 .SetIsShrinkToFit(true) | 258 .SetIsShrinkToFit(true) |
240 .SetTextDirection(style.Direction()) | 259 .SetTextDirection(style.Direction()) |
241 .ToConstraintSpace(FromPlatformWritingMode(style.GetWritingMode())); | 260 .ToConstraintSpace(FromPlatformWritingMode(style.GetWritingMode())); |
242 item_result->layout_result = node->Layout(constraint_space.Get()); | 261 item_result->layout_result = node->Layout(constraint_space.Get()); |
243 | 262 |
244 item_result->inline_size = | 263 item_result->inline_size = |
245 NGBoxFragment(constraint_space_->WritingMode(), | 264 NGBoxFragment(constraint_space_->WritingMode(), |
246 ToNGPhysicalBoxFragment( | 265 ToNGPhysicalBoxFragment( |
247 item_result->layout_result->PhysicalFragment().Get())) | 266 item_result->layout_result->PhysicalFragment().Get())) |
248 .InlineSize(); | 267 .InlineSize(); |
249 | 268 |
250 item_result->margins = | 269 item_result->margins = |
251 ComputeMargins(*constraint_space_, style, | 270 ComputeMargins(*constraint_space_, style, |
252 constraint_space_->WritingMode(), style.Direction()); | 271 constraint_space_->WritingMode(), style.Direction()); |
253 item_result->inline_size += item_result->margins.InlineSum(); | 272 item_result->inline_size += item_result->margins.InlineSum(); |
273 | |
274 position_ += item_result->inline_size; | |
275 MoveToNextOf(item); | |
276 if (auto_wrap_) | |
277 return LineBreakState::kIsBreakable; | |
278 item_result->prohibit_break_after = true; | |
279 return LineBreakState::kNotBreakable; | |
280 } | |
281 | |
282 void NGLineBreaker::HandleFloat(const NGInlineItem& item, | |
283 NGInlineItemResults* item_results, | |
284 NGInlineLayoutAlgorithm* algorithm) { | |
285 algorithm->LayoutAndPositionFloat(position_, item.GetLayoutObject()); | |
286 // Floats may change the available width if they fit. | |
287 available_width_ = algorithm->AvailableWidth(); | |
288 // Floats are already positioned in the container_builder. | |
289 item_results->pop_back(); | |
290 MoveToNextOf(item); | |
291 } | |
292 | |
293 void NGLineBreaker::HandleOpenTag(const NGInlineItem& item, | |
294 NGInlineItemResult* item_result) { | |
295 if (item.HasStartEdge()) { | |
296 DCHECK(item.Style()); | |
297 // TODO(kojii): We compute 16 values and discard 12 out of that, and do it 3 | |
298 // times per element. We may want to cache this. crrev.com/2865903002/#msg14 | |
299 NGBoxStrut margins = ComputeMargins(*constraint_space_, *item.Style(), | |
300 constraint_space_->WritingMode(), | |
301 constraint_space_->Direction()); | |
302 NGBoxStrut borders = ComputeBorders(*constraint_space_, *item.Style()); | |
303 NGBoxStrut paddings = ComputePadding(*constraint_space_, *item.Style()); | |
304 item_result->inline_size = | |
305 margins.inline_start + borders.inline_start + paddings.inline_start; | |
306 position_ += item_result->inline_size; | |
307 } | |
308 UpdateStyle(*item.Style()); | |
309 MoveToNextOf(item); | |
310 } | |
311 | |
312 void NGLineBreaker::HandleCloseTag(const NGInlineItem& item, | |
313 NGInlineItemResult* item_result) { | |
314 if (item.HasEndEdge()) { | |
315 DCHECK(item.Style()); | |
316 NGBoxStrut margins = ComputeMargins(*constraint_space_, *item.Style(), | |
317 constraint_space_->WritingMode(), | |
318 constraint_space_->Direction()); | |
319 NGBoxStrut borders = ComputeBorders(*constraint_space_, *item.Style()); | |
320 NGBoxStrut paddings = ComputePadding(*constraint_space_, *item.Style()); | |
321 item_result->inline_size = | |
322 margins.inline_end + borders.inline_end + paddings.inline_end; | |
323 position_ += item_result->inline_size; | |
324 } | |
325 DCHECK(item.GetLayoutObject() && item.GetLayoutObject()->Parent()); | |
326 UpdateStyle(item.GetLayoutObject()->Parent()->StyleRef()); | |
327 MoveToNextOf(item); | |
254 } | 328 } |
255 | 329 |
256 // Handles when the last item overflows. | 330 // Handles when the last item overflows. |
257 // At this point, item_results does not fit into the current line, and there | 331 // At this point, item_results does not fit into the current line, and there |
258 // are no break opportunities in item_results.back(). | 332 // are no break opportunities in item_results.back(). |
259 void NGLineBreaker::HandleOverflow( | 333 void NGLineBreaker::HandleOverflow(NGInlineItemResults* item_results) { |
260 NGInlineItemResults* item_results, | 334 const Vector<NGInlineItem>& items = node_->Items(); |
261 const LazyLineBreakIterator& break_iterator) { | 335 LayoutUnit rewind_width = available_width_ - position_; |
262 DCHECK_GT(offset_, 0u); | 336 DCHECK_LT(rewind_width, 0); |
263 | 337 |
264 // Find the last break opportunity. If none, let this line overflow. | 338 // Search for a break opportunity that can fit. |
265 unsigned line_start_offset = item_results->front().start_offset; | 339 // Also keep track of the first break opportunity in case of overflow. |
266 unsigned break_offset = | 340 unsigned break_before = 0; |
267 break_iterator.PreviousBreakOpportunity(offset_ - 1, line_start_offset); | 341 unsigned break_before_if_before_allow = 0; |
268 if (!break_offset || break_offset <= line_start_offset) { | 342 LayoutUnit rewind_width_if_before_allow; |
269 AppendCloseTags(item_results); | 343 bool last_item_prohibits_break_before = true; |
270 return; | 344 for (unsigned i = item_results->size(); i;) { |
345 NGInlineItemResult* item_result = &(*item_results)[--i]; | |
346 const NGInlineItem& item = items[item_result->item_index]; | |
347 rewind_width += item_result->inline_size; | |
348 if (item.Type() == NGInlineItem::kText || | |
349 item.Type() == NGInlineItem::kAtomicInline) { | |
350 // Try to break inside of this item. | |
351 if (item.Type() == NGInlineItem::kText && rewind_width >= 0 && | |
352 !item_result->no_break_opportunities_inside) { | |
353 // When the text fits but its right margin does not, the break point | |
354 // must not be at the end. | |
355 LayoutUnit item_available_width = | |
356 std::min(rewind_width, item_result->inline_size - 1); | |
357 BreakText(item_result, item, item_available_width); | |
358 if (item_result->inline_size <= item_available_width) { | |
359 DCHECK_LT(item_result->end_offset, item.EndOffset()); | |
360 DCHECK(!item_result->prohibit_break_after); | |
361 return Rewind(item_results, i + 1); | |
362 } | |
363 if (!item_result->prohibit_break_after && | |
364 !last_item_prohibits_break_before) { | |
365 break_before = i + 1; | |
366 } | |
367 } | |
368 | |
369 // Try to break after this item. | |
370 if (break_before_if_before_allow && !item_result->prohibit_break_after) { | |
371 if (rewind_width_if_before_allow >= 0) | |
372 return Rewind(item_results, break_before_if_before_allow); | |
373 break_before = break_before_if_before_allow; | |
374 } | |
375 | |
376 // Otherwise, before this item is a possible break point. | |
377 break_before_if_before_allow = i; | |
378 rewind_width_if_before_allow = rewind_width; | |
379 last_item_prohibits_break_before = false; | |
380 } else if (item.Type() == NGInlineItem::kCloseTag) { | |
381 last_item_prohibits_break_before = true; | |
382 } else { | |
383 if (i + 1 == break_before_if_before_allow) { | |
384 break_before_if_before_allow = i; | |
385 rewind_width_if_before_allow = rewind_width; | |
386 } | |
387 last_item_prohibits_break_before = false; | |
388 } | |
271 } | 389 } |
272 | 390 |
273 // Truncate the end of the line to the break opportunity. | 391 // The rewind point did not found, let this line overflow. |
274 const Vector<NGInlineItem>& items = node_->Items(); | 392 // If there was a break opporunity, the overflow should stop there. |
275 unsigned new_end = item_results->size(); | 393 if (break_before) |
276 while (true) { | 394 Rewind(item_results, break_before); |
277 NGInlineItemResult* item_result = &(*item_results)[--new_end]; | 395 } |
278 if (item_result->start_offset < break_offset) { | |
279 // The break is at the mid of the item. Adjust the end_offset to the new | |
280 // break offset. | |
281 const NGInlineItem& item = items[item_result->item_index]; | |
282 item.AssertEndOffset(break_offset); | |
283 DCHECK_EQ(item.Type(), NGInlineItem::kText); | |
284 DCHECK_NE(item_result->end_offset, break_offset); | |
285 item_result->end_offset = break_offset; | |
286 item_result->inline_size = | |
287 item.InlineSize(item_result->start_offset, item_result->end_offset); | |
288 // TODO(kojii): May need to reshape. Add to ShapingLineBreaker? | |
289 new_end++; | |
290 break; | |
291 } | |
292 if (item_result->start_offset == break_offset) { | |
293 // The new break offset is at the item boundary. Remove items up to the | |
294 // new break offset. | |
295 // TODO(kojii): Remove open tags as well. | |
296 break; | |
297 } | |
298 } | |
299 DCHECK_GT(new_end, 0u); | |
300 | 396 |
397 void NGLineBreaker::Rewind(NGInlineItemResults* item_results, | |
398 unsigned new_end) { | |
301 // TODO(kojii): Should we keep results for the next line? We don't need to | 399 // TODO(kojii): Should we keep results for the next line? We don't need to |
302 // re-layout atomic inlines. | 400 // re-layout atomic inlines. |
303 // TODO(kojii): Removing processed floats is likely a problematic. Keep | 401 // TODO(kojii): Removing processed floats is likely a problematic. Keep |
304 // floats in this line, or keep it for the next line. | 402 // floats in this line, or keep it for the next line. |
305 item_results->Shrink(new_end); | 403 item_results->Shrink(new_end); |
306 | 404 |
307 // Update the current item index and offset to the new break point. | 405 MoveToNextOf(item_results->back()); |
308 const NGInlineItemResult& last_item_result = item_results->back(); | 406 } |
309 offset_ = last_item_result.end_offset; | 407 |
310 item_index_ = last_item_result.item_index; | 408 void NGLineBreaker::UpdateStyle(const ComputedStyle& style) { |
ikilpatrick
2017/05/24 04:03:39
can this be renamed? I got concerned initially tha
kojii
2017/05/24 15:50:55
How about "UpdateBreakIterator"? I was using it un
| |
311 if (items[item_index_].EndOffset() == offset_) | 409 auto_wrap_ = style.AutoWrap(); |
312 item_index_++; | 410 |
411 if (auto_wrap_) { | |
412 break_iterator_.SetLocale(style.LocaleForLineBreakIterator()); | |
413 | |
414 if (style.WordBreak() == EWordBreak::kBreakAll || | |
415 style.WordBreak() == EWordBreak::kBreakWord) { | |
416 break_iterator_.SetBreakType(LineBreakType::kBreakAll); | |
417 } else if (style.WordBreak() == EWordBreak::kKeepAll) { | |
418 break_iterator_.SetBreakType(LineBreakType::kKeepAll); | |
419 } else { | |
420 break_iterator_.SetBreakType(LineBreakType::kNormal); | |
421 } | |
422 | |
423 // TODO(kojii): Implement word-wrap/overflow-wrap property | |
424 } | |
313 } | 425 } |
314 | 426 |
315 void NGLineBreaker::MoveToNextOf(const NGInlineItem& item) { | 427 void NGLineBreaker::MoveToNextOf(const NGInlineItem& item) { |
316 DCHECK_EQ(&item, &node_->Items()[item_index_]); | 428 DCHECK_EQ(&item, &node_->Items()[item_index_]); |
317 offset_ = item.EndOffset(); | 429 offset_ = item.EndOffset(); |
318 item_index_++; | 430 item_index_++; |
319 } | 431 } |
320 | 432 |
433 void NGLineBreaker::MoveToNextOf(const NGInlineItemResult& item_result) { | |
434 offset_ = item_result.end_offset; | |
435 item_index_ = item_result.item_index; | |
436 const NGInlineItem& item = node_->Items()[item_result.item_index]; | |
437 if (offset_ == item.EndOffset()) | |
438 item_index_++; | |
439 } | |
440 | |
321 void NGLineBreaker::SkipCollapsibleWhitespaces() { | 441 void NGLineBreaker::SkipCollapsibleWhitespaces() { |
322 const Vector<NGInlineItem>& items = node_->Items(); | 442 const Vector<NGInlineItem>& items = node_->Items(); |
323 if (item_index_ >= items.size()) | 443 if (item_index_ >= items.size()) |
324 return; | 444 return; |
325 const NGInlineItem& item = items[item_index_]; | 445 const NGInlineItem& item = items[item_index_]; |
326 if (item.Type() != NGInlineItem::kText || !item.Style()->CollapseWhiteSpace()) | 446 if (item.Type() != NGInlineItem::kText || !item.Style()->CollapseWhiteSpace()) |
327 return; | 447 return; |
328 | 448 |
329 DCHECK_LT(offset_, item.EndOffset()); | 449 DCHECK_LT(offset_, item.EndOffset()); |
330 if (node_->Text()[offset_] == kSpaceCharacter) { | 450 if (node_->Text()[offset_] == kSpaceCharacter) { |
331 // Skip one whitespace. Collapsible spaces are collapsed to single space in | 451 // Skip one whitespace. Collapsible spaces are collapsed to single space in |
332 // NGInlineItemBuilder, so this removes all collapsible spaces. | 452 // NGInlineItemBuilder, so this removes all collapsible spaces. |
333 offset_++; | 453 offset_++; |
334 if (offset_ == item.EndOffset()) | 454 if (offset_ == item.EndOffset()) |
335 item_index_++; | 455 item_index_++; |
336 } | 456 } |
337 } | 457 } |
338 | 458 |
339 void NGLineBreaker::AppendCloseTags(NGInlineItemResults* item_results) { | |
340 const Vector<NGInlineItem>& items = node_->Items(); | |
341 for (; item_index_ < items.size(); item_index_++) { | |
342 const NGInlineItem& item = items[item_index_]; | |
343 if (item.Type() != NGInlineItem::kCloseTag) | |
344 break; | |
345 DCHECK_EQ(offset_, item.EndOffset()); | |
346 item_results->push_back(NGInlineItemResult(item_index_, offset_, offset_)); | |
347 } | |
348 } | |
349 | |
350 RefPtr<NGInlineBreakToken> NGLineBreaker::CreateBreakToken() const { | 459 RefPtr<NGInlineBreakToken> NGLineBreaker::CreateBreakToken() const { |
351 const Vector<NGInlineItem>& items = node_->Items(); | 460 const Vector<NGInlineItem>& items = node_->Items(); |
352 if (item_index_ >= items.size()) | 461 if (item_index_ >= items.size()) |
353 return nullptr; | 462 return nullptr; |
354 return NGInlineBreakToken::Create(node_, item_index_, offset_); | 463 return NGInlineBreakToken::Create(node_, item_index_, offset_); |
355 } | 464 } |
356 | 465 |
357 } // namespace blink | 466 } // namespace blink |
OLD | NEW |