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