Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(62)

Side by Side Diff: third_party/WebKit/Source/core/layout/ng/inline/ng_line_breaker.cc

Issue 2871733003: [LayoutNG] Refactor NGLineBreaker for ShapingLineBreaker (Closed)
Patch Set: WIP, eae review Created 3 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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_layout_algorithm.h" 8 #include "core/layout/ng/inline/ng_inline_layout_algorithm.h"
8 #include "core/layout/ng/inline/ng_inline_node.h" 9 #include "core/layout/ng/inline/ng_inline_node.h"
9 #include "core/layout/ng/inline/ng_text_fragment.h" 10 #include "core/layout/ng/inline/ng_text_fragment.h"
10 #include "core/layout/ng/ng_box_fragment.h" 11 #include "core/layout/ng/ng_box_fragment.h"
11 #include "core/layout/ng/ng_break_token.h" 12 #include "core/layout/ng/ng_break_token.h"
12 #include "core/layout/ng/ng_constraint_space.h" 13 #include "core/layout/ng/ng_constraint_space.h"
13 #include "core/layout/ng/ng_fragment_builder.h" 14 #include "core/layout/ng/ng_fragment_builder.h"
14 #include "core/layout/ng/ng_layout_opportunity_iterator.h" 15 #include "core/layout/ng/ng_layout_opportunity_iterator.h"
15 #include "core/style/ComputedStyle.h" 16 #include "core/style/ComputedStyle.h"
17 #include "platform/fonts/shaping/HarfBuzzShaper.h"
18 #include "platform/fonts/shaping/ShapingLineBreaker.h"
16 #include "platform/text/TextBreakIterator.h" 19 #include "platform/text/TextBreakIterator.h"
17 20
18 namespace blink { 21 namespace blink {
19 22
20 static bool IsHangable(UChar ch) { 23 namespace {
21 return ch == ' '; 24
22 } 25 // Use a mock of ShapingLineBreaker for test/debug purposes.
23 26 #define MOCK_SHAPE_LINE
24 void NGLineBreaker::BreakLines(NGInlineLayoutAlgorithm* algorithm, 27
25 const String& text_content, 28 #if defined(MOCK_SHAPE_LINE)
26 unsigned current_offset) { 29 // The mock for ShapingLineBreaker::ShapeLine().
27 DCHECK(!text_content.IsEmpty()); 30 // The returned offset > item.StartOffset().
28 LazyLineBreakIterator line_break_iterator(text_content, locale_); 31 // - If offset < item.EndOffset() and width <= available_width, the break
29 const unsigned end_offset = text_content.length(); 32 // opportunity to fit to the available_width is found.
30 while (current_offset < end_offset) { 33 // - If offset < item.EndOffset() and width > available_width, the first break
31 // Find the next break opportunity. 34 // opportunity did not fit.
32 int tmp_next_breakable_offset = -1; 35 // - If offset == item.EndOffset() and width <= available_width, the break
33 line_break_iterator.IsBreakable(current_offset + 1, 36 // opportunity at the end of the item fits.
34 tmp_next_breakable_offset); 37 // - If offset == item.EndOffset() and width > available_width, the first
35 current_offset = 38 // break opportunity is at the end of the item but it does not fit.
36 tmp_next_breakable_offset >= 0 ? tmp_next_breakable_offset : end_offset; 39 // - If offset > item.EndOffset(), the first break opportunity is beyond the
37 DCHECK_LE(current_offset, end_offset); 40 // end of item and thus cannot measure. In this case, inline_size shows the
38 41 // width until the end of the item. It may fit or may not.
39 // Advance the break opportunity to the end of hangable characters; e.g., 42 std::pair<unsigned, LayoutUnit> ShapeLineMock(
40 // spaces. 43 const NGInlineItem& item,
41 // Unlike the ICU line breaker, LazyLineBreakIterator breaks before 44 unsigned offset,
42 // breakable spaces, and expect the line breaker to handle spaces 45 LayoutUnit available_width,
43 // differently. This logic computes in the ICU way; break after spaces, and 46 const LazyLineBreakIterator& break_iterator) {
44 // handle spaces as hangable characters. 47 bool has_break_opportunities = false;
45 unsigned start_of_hangables = current_offset; 48 LayoutUnit inline_size;
46 while (current_offset < end_offset && 49 while (true) {
47 IsHangable(text_content[current_offset])) 50 unsigned next_break = break_iterator.NextBreakOpportunity(offset + 1);
48 current_offset++; 51 LayoutUnit next_inline_size =
49 52 inline_size +
50 // Set the end to the next break opportunity. 53 item.InlineSize(offset, std::min(next_break, item.EndOffset()));
51 algorithm->SetEnd(current_offset); 54 if (next_inline_size > available_width || next_break >= item.EndOffset()) {
52 55 if (!has_break_opportunities)
53 // If there are more available spaces, mark the break opportunity and fetch 56 return std::make_pair(next_break, next_inline_size);
54 // more text. 57 return std::make_pair(offset, inline_size);
55 // TODO(layout-ng): check if the height of the linebox can fit within 58 }
56 // the current opportunity. 59 offset = next_break;
57 if (algorithm->CanFitOnLine()) { 60 inline_size = next_inline_size;
58 algorithm->SetBreakOpportunity(); 61 has_break_opportunities = true;
62 }
63 }
64 #endif
65
66 LineBreakType GetLineBreakType(const ComputedStyle& style) {
67 if (style.AutoWrap()) {
68 if (style.WordBreak() == kBreakAllWordBreak ||
69 style.WordBreak() == kBreakWordBreak)
70 return LineBreakType::kBreakAll;
71 if (style.WordBreak() == kKeepAllWordBreak)
72 return LineBreakType::kKeepAll;
73 }
74 return LineBreakType::kNormal;
75 }
76
77 } // namespace
78
79 NGLineBreaker::NGLineBreaker(NGInlineNode* node,
80 const NGConstraintSpace* space,
81 NGInlineBreakToken* break_token)
82 : node_(node), constraint_space_(space), item_index_(0), offset_(0) {
83 if (break_token) {
84 item_index_ = break_token->ItemIndex();
85 offset_ = break_token->TextOffset();
86 node->AssertOffset(item_index_, offset_);
87 }
88 }
89
90 void NGLineBreaker::NextLine(NGInlineItemResults* item_results,
91 NGInlineLayoutAlgorithm* algorithm) {
92 DCHECK(item_results->IsEmpty());
93 const Vector<NGInlineItem>& items = node_->Items();
94 const String& text = node_->Text();
95 const ComputedStyle& style = node_->Style();
96 LazyLineBreakIterator break_iterator(text, style.LocaleForLineBreakIterator(),
97 GetLineBreakType(style));
98 #if !defined(MOCK_SHAPE_LINE)
99 HarfBuzzShaper shaper(text.Characters16(), text.length());
100 #endif
101 LayoutUnit available_width = algorithm->AvailableWidth();
102 LayoutUnit position;
103 unsigned offset = offset_;
104 unsigned item_index = item_index_;
105 while (item_index < items.size()) {
ikilpatrick 2017/05/11 15:55:55 ... move this for loop into NGInlineLayoutAlgorith
kojii 2017/05/11 16:18:29 I don't like NGLineBreaker taking NGInlineLayoutAl
106 const NGInlineItem& item = items[item_index];
107 item_results->push_back(
108 NGInlineItemResult(item_index, offset, item.EndOffset()));
109 NGInlineItemResult* item_result = &item_results->back();
110
111 // If the start offset is at the item boundary, try to add the entire item.
112 if (offset == item.StartOffset()) {
113 if (item.Type() == NGInlineItem::kText) {
114 item_result->inline_size = item.InlineSize();
115 } else if (item.Type() == NGInlineItem::kAtomicInline) {
116 LayoutAtomicInline(item, item_result);
117 } else if (item.Type() == NGInlineItem::kFloating) {
118 // Floats may change the available width if they fit.
119 algorithm->LayoutAndPositionFloat(position, item.GetLayoutObject());
120 available_width = algorithm->AvailableWidth();
121 DCHECK_LE(position, available_width);
122 } else {
123 offset = item.EndOffset();
124 item_index++;
125 continue;
126 }
127 LayoutUnit next_position = position + item_result->inline_size;
128 if (next_position <= available_width) {
129 offset = item.EndOffset();
130 item_index++;
131 position = next_position;
132 continue;
133 }
134
135 // The entire item does not fit. Handle non-text items as overflow,
136 // since only text item is breakable.
137 if (item.Type() != NGInlineItem::kText) {
138 offset_ = item.EndOffset();
139 item_index_ = item_index + 1;
140 return HandleOverflow(item_results, break_iterator);
141 }
142 }
143
144 // Either the start or the break is in the mid of a text item.
145 DCHECK_EQ(item.Type(), NGInlineItem::kText);
146 DCHECK_LT(offset, item.EndOffset());
147 break_iterator.SetLocale(item.Style()->LocaleForLineBreakIterator());
148 break_iterator.SetBreakType(GetLineBreakType(*item.Style()));
149 #if defined(MOCK_SHAPE_LINE)
150 unsigned break_offset;
151 std::tie(break_offset, item_result->inline_size) =
152 ShapeLineMock(item, offset, available_width - position, break_iterator);
153 #else
154 // TODO(kojii): Change constructor to take LazyLineBreakIterator.
155 // TODO(kojii): We need to instantiate ShapingLineBreaker here because it
156 // has item-specific info as context. Should they be part of ShapeLine() to
157 // instantiate once, or is this just fine since instatiation is not
158 // expensive?
159 ShapingLineBreaker breaker(&shaper, &item.Style()->GetFont(),
160 item.TextShapeResult(), item.Style()->Locale(),
161 LineBreakType::kNormal);
162 // TODO(kojii): Set item.StartOffset/EndOffset() to ShapingLineBreaker, or
163 // change ShapingLineBreaker to take the range from ShapeResult.
164 // item.TextShapeResult() does not have results for outside the item offset
165 // range.
166 unsigned break_offset;
167 item_result->shape_result =
168 breaker.ShapeLine(offset, available_width - position, &break_offset);
169 item_result->inline_size = item_result->shape_result->SnappedWidth();
170 #endif
171 DCHECK_GT(break_offset, offset);
172 position += item_result->inline_size;
173
174 // If the break found within the item, break here.
175 if (break_offset < item.EndOffset()) {
176 offset = item_result->end_offset = break_offset;
177 if (position <= available_width)
178 break;
179 // The first break opportunity of the item does not fit.
180 } else {
181 // No break opporunity in the item, or the first break opportunity is at
182 // the end of the item. If it fits, continue to the next item.
183 offset = item_result->end_offset = item.EndOffset();
184 item_index++;
185 if (position <= available_width)
186 continue;
187 }
188
189 offset_ = offset;
190 item_index_ = item_index;
191 // We need to look at next item if we're overflowing, and the break
192 // opportunity is beyond this item.
193 if (break_offset > item.EndOffset())
59 continue; 194 continue;
60 } 195 return HandleOverflow(item_results, break_iterator);
61 196 }
62 // Compute hangable characters if exists. 197 offset_ = offset;
63 if (current_offset != start_of_hangables) { 198 item_index_ = item_index;
64 algorithm->SetStartOfHangables(start_of_hangables); 199
65 // If text before hangables can fit, include it in the current line. 200 SkipCollapsibleWhitespaces();
66 if (algorithm->CanFitOnLine()) 201 }
67 algorithm->SetBreakOpportunity(); 202
68 } 203 void NGLineBreaker::LayoutAtomicInline(const NGInlineItem& item,
69 204 NGInlineItemResult* item_result) {
70 if (!algorithm->HasBreakOpportunity()) { 205 DCHECK_EQ(item.Type(), NGInlineItem::kAtomicInline);
71 // The first word (break opportunity) did not fit on the line. 206 NGBlockNode* node = new NGBlockNode(item.GetLayoutObject());
72 // Create a line including items that don't fit, allowing them to 207 const ComputedStyle& style = node->Style();
73 // overflow. 208 NGConstraintSpaceBuilder constraint_space_builder(constraint_space_);
74 if (!algorithm->CreateLine()) 209 RefPtr<NGConstraintSpace> constraint_space =
75 return; 210 constraint_space_builder.SetIsNewFormattingContext(true)
76 } else { 211 .SetIsShrinkToFit(true)
77 if (!algorithm->CreateLineUpToLastBreakOpportunity()) 212 .SetTextDirection(style.Direction())
78 return; 213 .ToConstraintSpace(FromPlatformWritingMode(style.GetWritingMode()));
79 214 item_result->layout_result = node->Layout(constraint_space.Get());
80 // Items after the last break opportunity were sent to the next line. 215
81 // Set the break opportunity, or create a line if the word doesn't fit. 216 item_result->inline_size =
82 if (algorithm->HasItems()) { 217 NGBoxFragment(constraint_space_->WritingMode(),
83 if (algorithm->CanFitOnLine()) 218 ToNGPhysicalBoxFragment(
84 algorithm->SetBreakOpportunity(); 219 item_result->layout_result->PhysicalFragment().Get()))
85 else if (!algorithm->CreateLine()) 220 .InlineSize();
86 return; 221 }
87 } 222
88 } 223 void NGLineBreaker::HandleOverflow(
89 } 224 NGInlineItemResults* item_results,
90 225 const LazyLineBreakIterator& break_iterator) {
91 // If inline children ended with items left in the line builder, create a line 226 // At this point, item_results does not fit into the current line, and
92 // for them. 227 // there're no break opportunities in item_results.back().
93 if (algorithm->HasItems()) 228
94 algorithm->CreateLine(); 229 // Overflow if there were no break opprtunities to back.
230 unsigned line_start_offset = item_results->front().start_offset;
231 DCHECK_GT(offset_, 0u);
232 unsigned break_offset =
233 break_iterator.PreviousBreakOpportunity(offset_ - 1, line_start_offset);
234 if (!break_offset || break_offset <= line_start_offset)
235 return SkipCollapsibleWhitespaces();
236
237 // Truncate the end of the line to the previous break opportunity.
238 const Vector<NGInlineItem>& items = node_->Items();
239 unsigned new_end = item_results->size();
240 while (true) {
241 NGInlineItemResult* item_result = &(*item_results)[--new_end];
242 if (item_result->start_offset < break_offset) {
243 // The break is at the mid of the item. Adjust the end_offset to the new
244 // break offset.
245 const NGInlineItem& item = items[item_result->item_index];
246 item.AssertEndOffset(break_offset);
247 DCHECK_EQ(item.Type(), NGInlineItem::kText);
248 DCHECK_NE(item_result->end_offset, break_offset);
249 item_result->end_offset = break_offset;
250 item_result->inline_size =
251 item.InlineSize(item_result->start_offset, item_result->end_offset);
252 // TODO(kojii): May need to reshape. Add to ShapingLineBreaker?
253 new_end++;
254 break;
255 }
256 if (item_result->start_offset == break_offset) {
257 // The new break offset is at the item boundary. Remove items up to the
258 // new break offset.
259 // TODO(kojii): Remove open tags as well.
260 break;
261 }
262 }
263 DCHECK_GT(new_end, 0u);
264
265 // TODO(kojii): Should we keep results for the next line? We don't need to
266 // re-layout atomic inlines. Also, processing floats twice is likely a
267 // problematic. Keep floats in this line, or keep it for the next line.
268 item_results->Shrink(new_end);
269
270 // Update the current item index and offset to the new break point.
271 const NGInlineItemResult& last_item_result = item_results->back();
272 offset_ = last_item_result.end_offset;
273 item_index_ = last_item_result.item_index;
274 if (items[item_index_].EndOffset() == offset_)
275 item_index_++;
276 }
277
278 void NGLineBreaker::SkipCollapsibleWhitespaces() {
279 const Vector<NGInlineItem>& items = node_->Items();
280 if (item_index_ >= items.size())
281 return;
282 const NGInlineItem& item = items[item_index_];
283 if (item.Type() != NGInlineItem::kText || !item.Style()->CollapseWhiteSpace())
284 return;
285 DCHECK_LT(offset_, item.EndOffset());
286
287 // TODO(kojii): When editing, or caret is enabled, trailing spaces at wrap
288 // point should not be removed. For other cases, we can a) remove, b) leave
289 // characters without glyphs, or c) leave both characters and glyphs without
290 // measuring. Need to decide which one works the best.
291 if (node_->Text()[offset_] == kSpaceCharacter) {
292 // Skip one whitespace. Collapsible spaces are collapsed to single space in
293 // NGInlineItemBuilder, so this removes all collapsible spaces.
294 offset_++;
295 if (offset_ == item.EndOffset())
296 item_index_++;
297 }
298 }
299
300 RefPtr<NGInlineBreakToken> NGLineBreaker::CreateBreakToken() const {
301 const Vector<NGInlineItem>& items = node_->Items();
302 if (item_index_ >= items.size())
303 return nullptr;
304 return NGInlineBreakToken::Create(node_, item_index_, offset_);
95 } 305 }
96 306
97 } // namespace blink 307 } // namespace blink
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698