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_items_builder.h" | 5 #include "core/layout/ng/inline/ng_inline_items_builder.h" |
6 | 6 |
7 #include "core/layout/LayoutObject.h" | 7 #include "core/layout/LayoutObject.h" |
8 #include "core/layout/ng/inline/ng_inline_node.h" | 8 #include "core/layout/ng/inline/ng_inline_node.h" |
9 #include "core/style/ComputedStyle.h" | 9 #include "core/style/ComputedStyle.h" |
10 | 10 |
11 namespace blink { | 11 namespace blink { |
12 | 12 |
13 NGInlineItemsBuilder::~NGInlineItemsBuilder() { | 13 NGInlineItemsBuilder::~NGInlineItemsBuilder() { |
14 DCHECK_EQ(0u, exits_.size()); | 14 DCHECK_EQ(0u, exits_.size()); |
15 DCHECK_EQ(text_.length(), items_->IsEmpty() ? 0 : items_->back().EndOffset()); | 15 DCHECK_EQ(text_.length(), items_->IsEmpty() ? 0 : items_->back().EndOffset()); |
16 } | 16 } |
17 | 17 |
18 String NGInlineItemsBuilder::ToString() { | 18 bool NGInlineItemsBuilder::Finalize() { |
19 // Segment Break Transformation Rules[1] defines to keep trailing new lines, | 19 // Segment Break Transformation Rules[1] defines to keep trailing new lines, |
20 // but it will be removed in Phase II[2]. We prefer not to add trailing new | 20 // but it will be removed in Phase II[2]. We prefer not to add trailing new |
21 // lines and collapsible spaces in Phase I. | 21 // lines and collapsible spaces in Phase I. |
22 // [1] https://drafts.csswg.org/css-text-3/#line-break-transform | 22 // [1] https://drafts.csswg.org/css-text-3/#line-break-transform |
23 // [2] https://drafts.csswg.org/css-text-3/#white-space-phase-2 | 23 // [2] https://drafts.csswg.org/css-text-3/#white-space-phase-2 |
24 unsigned next_start_offset = text_.length(); | 24 unsigned next_start_offset = text_.length(); |
25 RemoveTrailingCollapsibleSpaceIfExists(&next_start_offset); | 25 return RemoveTrailingCollapsibleSpaceIfExists(&next_start_offset); |
| 26 } |
26 | 27 |
| 28 String NGInlineItemsBuilder::ToString() { |
27 return text_.ToString(); | 29 return text_.ToString(); |
28 } | 30 } |
29 | 31 |
30 // Determine "Ambiguous" East Asian Width is Wide or Narrow. | 32 // Determine "Ambiguous" East Asian Width is Wide or Narrow. |
31 // Unicode East Asian Width | 33 // Unicode East Asian Width |
32 // http://unicode.org/reports/tr11/ | 34 // http://unicode.org/reports/tr11/ |
33 static bool IsAmbiguosEastAsianWidthWide(const ComputedStyle* style) { | 35 static bool IsAmbiguosEastAsianWidthWide(const ComputedStyle* style) { |
34 UScriptCode script = style->GetFontDescription().GetScript(); | 36 UScriptCode script = style->GetFontDescription().GetScript(); |
35 return script == USCRIPT_KATAKANA_OR_HIRAGANA || | 37 return script == USCRIPT_KATAKANA_OR_HIRAGANA || |
36 script == USCRIPT_SIMPLIFIED_HAN || script == USCRIPT_TRADITIONAL_HAN; | 38 script == USCRIPT_SIMPLIFIED_HAN || script == USCRIPT_TRADITIONAL_HAN; |
(...skipping 80 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
117 return c == kSpaceCharacter || c == kTabulationCharacter || | 119 return c == kSpaceCharacter || c == kTabulationCharacter || |
118 c == kNewlineCharacter; | 120 c == kNewlineCharacter; |
119 } | 121 } |
120 | 122 |
121 // Characters needing a separate control item than other text items. | 123 // Characters needing a separate control item than other text items. |
122 // It makes the line breaker easier to handle. | 124 // It makes the line breaker easier to handle. |
123 static inline bool IsControlItemCharacter(UChar c) { | 125 static inline bool IsControlItemCharacter(UChar c) { |
124 return c == kTabulationCharacter || c == kNewlineCharacter; | 126 return c == kTabulationCharacter || c == kNewlineCharacter; |
125 } | 127 } |
126 | 128 |
127 void NGInlineItemsBuilder::Append(const String& string, | 129 static inline size_t GetLastAppendedCharacterIndex( |
128 const ComputedStyle* style, | 130 const Vector<unsigned>& collapsed_indexes, |
129 LayoutObject* layout_object) { | 131 unsigned start, |
| 132 unsigned end) { |
| 133 if (start == end || collapsed_indexes.size() == end - start) |
| 134 return kNotFound; |
| 135 if (collapsed_indexes.IsEmpty()) |
| 136 return end - 1; |
| 137 DCHECK_GT(end, collapsed_indexes.back()); |
| 138 if (collapsed_indexes.back() + 1 < end) |
| 139 return collapsed_indexes.back() + 1; |
| 140 for (unsigned i = collapsed_indexes.size(); i > 1;) { |
| 141 unsigned current_collapsed = collapsed_indexes[--i]; |
| 142 unsigned last_collapsed = collapsed_indexes[i - 1]; |
| 143 DCHECK_GT(current_collapsed, last_collapsed); |
| 144 if (last_collapsed + 1 < current_collapsed) |
| 145 return last_collapsed + 1; |
| 146 } |
| 147 DCHECK_GT(collapsed_indexes[0], start); |
| 148 return collapsed_indexes[0] - 1; |
| 149 } |
| 150 |
| 151 NGInlineItemsBuilder::AppendResult NGInlineItemsBuilder::Append( |
| 152 const String& string, |
| 153 const ComputedStyle* style, |
| 154 LayoutObject* layout_object) { |
130 if (string.IsEmpty()) | 155 if (string.IsEmpty()) |
131 return; | 156 return AppendResult(false, {}); |
132 text_.ReserveCapacity(string.length()); | 157 text_.ReserveCapacity(string.length()); |
133 | 158 |
134 EWhiteSpace whitespace = style->WhiteSpace(); | 159 EWhiteSpace whitespace = style->WhiteSpace(); |
135 if (!ComputedStyle::CollapseWhiteSpace(whitespace)) | 160 if (!ComputedStyle::CollapseWhiteSpace(whitespace)) |
136 return AppendWithoutWhiteSpaceCollapsing(string, style, layout_object); | 161 return AppendWithoutWhiteSpaceCollapsing(string, style, layout_object); |
137 if (ComputedStyle::PreserveNewline(whitespace) && !is_svgtext_) | 162 if (ComputedStyle::PreserveNewline(whitespace) && !is_svgtext_) |
138 return AppendWithPreservingNewlines(string, style, layout_object); | 163 return AppendWithPreservingNewlines(string, style, layout_object); |
139 | 164 |
140 AppendWithWhiteSpaceCollapsing(string, 0, string.length(), style, | 165 return AppendWithWhiteSpaceCollapsing(string, 0, string.length(), style, |
141 layout_object); | 166 layout_object); |
142 } | 167 } |
143 | 168 |
144 void NGInlineItemsBuilder::AppendWithWhiteSpaceCollapsing( | 169 NGInlineItemsBuilder::AppendResult |
| 170 NGInlineItemsBuilder::AppendWithWhiteSpaceCollapsing( |
145 const String& string, | 171 const String& string, |
146 unsigned start, | 172 unsigned start, |
147 unsigned end, | 173 unsigned end, |
148 const ComputedStyle* style, | 174 const ComputedStyle* style, |
149 LayoutObject* layout_object) { | 175 LayoutObject* layout_object) { |
| 176 AppendResult result(false, {}); |
150 unsigned start_offset = text_.length(); | 177 unsigned start_offset = text_.length(); |
151 for (unsigned i = start; i < end;) { | 178 for (unsigned i = start; i < end;) { |
152 UChar c = string[i]; | 179 UChar c = string[i]; |
153 if (c == kNewlineCharacter) { | 180 if (c == kNewlineCharacter) { |
154 // LayoutBR does not set preserve_newline, but should be preserved. | 181 // LayoutBR does not set preserve_newline, but should be preserved. |
155 if (!i && end == 1 && layout_object && layout_object->IsBR()) { | 182 if (!i && end == 1 && layout_object && layout_object->IsBR()) |
156 AppendForcedBreak(style, layout_object); | 183 return AppendResult(AppendForcedBreak(style, layout_object), {}); |
157 return; | |
158 } | |
159 | 184 |
160 if (last_collapsible_space_ == CollapsibleSpace::kNone) | 185 if (last_collapsible_space_ == CollapsibleSpace::kNone) |
161 text_.Append(kSpaceCharacter); | 186 text_.Append(kSpaceCharacter); |
| 187 else |
| 188 result.second.push_back(i); |
162 last_collapsible_space_ = CollapsibleSpace::kNewline; | 189 last_collapsible_space_ = CollapsibleSpace::kNewline; |
163 i++; | 190 i++; |
164 continue; | 191 continue; |
165 } | 192 } |
166 | 193 |
167 if (c == kSpaceCharacter || c == kTabulationCharacter) { | 194 if (c == kSpaceCharacter || c == kTabulationCharacter) { |
168 if (last_collapsible_space_ == CollapsibleSpace::kNone) { | 195 if (last_collapsible_space_ == CollapsibleSpace::kNone) { |
169 text_.Append(kSpaceCharacter); | 196 text_.Append(kSpaceCharacter); |
170 last_collapsible_space_ = CollapsibleSpace::kSpace; | 197 last_collapsible_space_ = CollapsibleSpace::kSpace; |
| 198 } else { |
| 199 result.second.push_back(i); |
171 } | 200 } |
172 i++; | 201 i++; |
173 continue; | 202 continue; |
174 } | 203 } |
175 | 204 |
176 if (last_collapsible_space_ == CollapsibleSpace::kNewline) { | 205 if (last_collapsible_space_ == CollapsibleSpace::kNewline) { |
177 RemoveTrailingCollapsibleNewlineIfNeeded(&start_offset, string, i, style); | 206 bool removed = RemoveTrailingCollapsibleNewlineIfNeeded(&start_offset, |
| 207 string, i, style); |
| 208 if (removed) { |
| 209 const size_t last_appended = |
| 210 GetLastAppendedCharacterIndex(result.second, start, i); |
| 211 if (last_appended == kNotFound) { |
| 212 result.first = true; |
| 213 } else { |
| 214 result.second.push_back(last_appended); |
| 215 std::sort(result.second.begin(), result.second.end()); |
| 216 } |
| 217 } |
178 } | 218 } |
179 | 219 |
180 size_t end_of_non_space = string.Find(IsCollapsibleSpace, i + 1); | 220 size_t end_of_non_space = string.Find(IsCollapsibleSpace, i + 1); |
181 if (end_of_non_space == kNotFound) | 221 if (end_of_non_space == kNotFound) |
182 end_of_non_space = string.length(); | 222 end_of_non_space = string.length(); |
183 text_.Append(string, i, end_of_non_space - i); | 223 text_.Append(string, i, end_of_non_space - i); |
184 i = end_of_non_space; | 224 i = end_of_non_space; |
185 last_collapsible_space_ = CollapsibleSpace::kNone; | 225 last_collapsible_space_ = CollapsibleSpace::kNone; |
186 } | 226 } |
187 | 227 |
188 if (text_.length() > start_offset) { | 228 if (text_.length() > start_offset) { |
189 AppendItem(items_, NGInlineItem::kText, start_offset, text_.length(), style, | 229 AppendItem(items_, NGInlineItem::kText, start_offset, text_.length(), style, |
190 layout_object); | 230 layout_object); |
191 } | 231 } |
| 232 |
| 233 return result; |
192 } | 234 } |
193 | 235 |
194 // Even when without whitespace collapsing, control characters (newlines and | 236 // Even when without whitespace collapsing, control characters (newlines and |
195 // tabs) are in their own control items to make the line breaker easier. | 237 // tabs) are in their own control items to make the line breaker easier. |
196 void NGInlineItemsBuilder::AppendWithoutWhiteSpaceCollapsing( | 238 NGInlineItemsBuilder::AppendResult |
| 239 NGInlineItemsBuilder::AppendWithoutWhiteSpaceCollapsing( |
197 const String& string, | 240 const String& string, |
198 const ComputedStyle* style, | 241 const ComputedStyle* style, |
199 LayoutObject* layout_object) { | 242 LayoutObject* layout_object) { |
200 for (unsigned start = 0; start < string.length();) { | 243 for (unsigned start = 0; start < string.length();) { |
201 UChar c = string[start]; | 244 UChar c = string[start]; |
202 if (IsControlItemCharacter(c)) { | 245 if (IsControlItemCharacter(c)) { |
203 Append(NGInlineItem::kControl, c, style, layout_object); | 246 Append(NGInlineItem::kControl, c, style, layout_object); |
204 start++; | 247 start++; |
205 continue; | 248 continue; |
206 } | 249 } |
207 | 250 |
208 size_t end = string.Find(IsControlItemCharacter, start + 1); | 251 size_t end = string.Find(IsControlItemCharacter, start + 1); |
209 if (end == kNotFound) | 252 if (end == kNotFound) |
210 end = string.length(); | 253 end = string.length(); |
211 unsigned start_offset = text_.length(); | 254 unsigned start_offset = text_.length(); |
212 text_.Append(string, start, end - start); | 255 text_.Append(string, start, end - start); |
213 AppendItem(items_, NGInlineItem::kText, start_offset, text_.length(), style, | 256 AppendItem(items_, NGInlineItem::kText, start_offset, text_.length(), style, |
214 layout_object); | 257 layout_object); |
215 start = end; | 258 start = end; |
216 } | 259 } |
217 | 260 |
218 last_collapsible_space_ = CollapsibleSpace::kNone; | 261 last_collapsible_space_ = CollapsibleSpace::kNone; |
| 262 return AppendResult(false, {}); |
219 } | 263 } |
220 | 264 |
221 void NGInlineItemsBuilder::AppendWithPreservingNewlines( | 265 NGInlineItemsBuilder::AppendResult |
| 266 NGInlineItemsBuilder::AppendWithPreservingNewlines( |
222 const String& string, | 267 const String& string, |
223 const ComputedStyle* style, | 268 const ComputedStyle* style, |
224 LayoutObject* layout_object) { | 269 LayoutObject* layout_object) { |
| 270 AppendResult result(false, {}); |
225 for (unsigned start = 0; start < string.length();) { | 271 for (unsigned start = 0; start < string.length();) { |
226 if (string[start] == kNewlineCharacter) { | 272 if (string[start] == kNewlineCharacter) { |
227 AppendForcedBreak(style, layout_object); | 273 if (AppendForcedBreak(style, layout_object)) { |
| 274 const size_t last_appended = |
| 275 GetLastAppendedCharacterIndex(result.second, 0, start); |
| 276 if (last_appended == kNotFound) { |
| 277 result.first = true; |
| 278 } else { |
| 279 result.second.push_back(last_appended); |
| 280 std::sort(result.second.begin(), result.second.end()); |
| 281 } |
| 282 } |
228 start++; | 283 start++; |
229 continue; | 284 continue; |
230 } | 285 } |
231 | 286 |
232 size_t end = string.find(kNewlineCharacter, start + 1); | 287 size_t end = string.find(kNewlineCharacter, start + 1); |
233 if (end == kNotFound) | 288 if (end == kNotFound) |
234 end = string.length(); | 289 end = string.length(); |
235 AppendWithWhiteSpaceCollapsing(string, start, end, style, layout_object); | 290 AppendResult run_result = AppendWithWhiteSpaceCollapsing( |
| 291 string, start, end, style, layout_object); |
| 292 if (run_result.first) { |
| 293 const size_t last_appended = |
| 294 GetLastAppendedCharacterIndex(result.second, 0, start); |
| 295 if (last_appended == kNotFound) { |
| 296 result.first = true; |
| 297 } else { |
| 298 result.second.push_back(last_appended); |
| 299 std::sort(result.second.begin(), result.second.end()); |
| 300 } |
| 301 } |
| 302 result.second.AppendVector(run_result.second); |
236 start = end; | 303 start = end; |
237 } | 304 } |
| 305 return result; |
238 } | 306 } |
239 | 307 |
240 void NGInlineItemsBuilder::AppendForcedBreak(const ComputedStyle* style, | 308 bool NGInlineItemsBuilder::AppendForcedBreak(const ComputedStyle* style, |
241 LayoutObject* layout_object) { | 309 LayoutObject* layout_object) { |
242 // Remove collapsible spaces immediately before a preserved newline. | 310 // Remove collapsible spaces immediately before a preserved newline. |
243 unsigned start_offset = text_.length(); | 311 unsigned start_offset = text_.length(); |
244 RemoveTrailingCollapsibleSpaceIfExists(&start_offset); | 312 bool result = RemoveTrailingCollapsibleSpaceIfExists(&start_offset); |
245 | 313 |
246 Append(NGInlineItem::kControl, kNewlineCharacter, style, layout_object); | 314 Append(NGInlineItem::kControl, kNewlineCharacter, style, layout_object); |
247 | 315 |
248 // Remove collapsible spaces immediately after a preserved newline. | 316 // Remove collapsible spaces immediately after a preserved newline. |
249 last_collapsible_space_ = CollapsibleSpace::kSpace; | 317 last_collapsible_space_ = CollapsibleSpace::kSpace; |
| 318 return result; |
250 } | 319 } |
251 | 320 |
252 void NGInlineItemsBuilder::Append(NGInlineItem::NGInlineItemType type, | 321 void NGInlineItemsBuilder::Append(NGInlineItem::NGInlineItemType type, |
253 UChar character, | 322 UChar character, |
254 const ComputedStyle* style, | 323 const ComputedStyle* style, |
255 LayoutObject* layout_object) { | 324 LayoutObject* layout_object) { |
256 DCHECK_NE(character, kSpaceCharacter); | 325 DCHECK_NE(character, kSpaceCharacter); |
257 DCHECK_NE(character, kZeroWidthSpaceCharacter); | 326 DCHECK_NE(character, kZeroWidthSpaceCharacter); |
258 | 327 |
259 text_.Append(character); | 328 text_.Append(character); |
260 unsigned end_offset = text_.length(); | 329 unsigned end_offset = text_.length(); |
261 AppendItem(items_, type, end_offset - 1, end_offset, style, layout_object); | 330 AppendItem(items_, type, end_offset - 1, end_offset, style, layout_object); |
262 last_collapsible_space_ = CollapsibleSpace::kNone; | 331 last_collapsible_space_ = CollapsibleSpace::kNone; |
263 } | 332 } |
264 | 333 |
265 void NGInlineItemsBuilder::Append(NGInlineItem::NGInlineItemType type, | 334 void NGInlineItemsBuilder::Append(NGInlineItem::NGInlineItemType type, |
266 const ComputedStyle* style, | 335 const ComputedStyle* style, |
267 LayoutObject* layout_object) { | 336 LayoutObject* layout_object) { |
268 unsigned end_offset = text_.length(); | 337 unsigned end_offset = text_.length(); |
269 AppendItem(items_, type, end_offset, end_offset, style, layout_object); | 338 AppendItem(items_, type, end_offset, end_offset, style, layout_object); |
270 } | 339 } |
271 | 340 |
272 void NGInlineItemsBuilder::RemoveTrailingCollapsibleNewlineIfNeeded( | 341 bool NGInlineItemsBuilder::RemoveTrailingCollapsibleNewlineIfNeeded( |
273 unsigned* next_start_offset, | 342 unsigned* next_start_offset, |
274 const String& after, | 343 const String& after, |
275 unsigned after_index, | 344 unsigned after_index, |
276 const ComputedStyle* after_style) { | 345 const ComputedStyle* after_style) { |
277 DCHECK_EQ(last_collapsible_space_, CollapsibleSpace::kNewline); | 346 DCHECK_EQ(last_collapsible_space_, CollapsibleSpace::kNewline); |
278 | 347 |
279 if (text_.IsEmpty() || text_[text_.length() - 1] != kSpaceCharacter) | 348 if (text_.IsEmpty() || text_[text_.length() - 1] != kSpaceCharacter) |
280 return; | 349 return false; |
281 | 350 |
282 const ComputedStyle* before_style = after_style; | 351 const ComputedStyle* before_style = after_style; |
283 if (!items_->IsEmpty()) { | 352 if (!items_->IsEmpty()) { |
284 NGInlineItem& item = items_->back(); | 353 NGInlineItem& item = items_->back(); |
285 if (text_.length() < item.EndOffset() + 2) | 354 if (text_.length() < item.EndOffset() + 2) |
286 before_style = item.Style(); | 355 before_style = item.Style(); |
287 } | 356 } |
288 | 357 |
289 if (ShouldRemoveNewline(text_, before_style, after, after_index, after_style)) | 358 if (!ShouldRemoveNewline(text_, before_style, after, after_index, |
290 RemoveTrailingCollapsibleSpace(next_start_offset); | 359 after_style)) |
| 360 return false; |
| 361 RemoveTrailingCollapsibleSpace(next_start_offset); |
| 362 return true; |
291 } | 363 } |
292 | 364 |
293 void NGInlineItemsBuilder::RemoveTrailingCollapsibleSpaceIfExists( | 365 bool NGInlineItemsBuilder::RemoveTrailingCollapsibleSpaceIfExists( |
294 unsigned* next_start_offset) { | 366 unsigned* next_start_offset) { |
295 if (last_collapsible_space_ != CollapsibleSpace::kNone && !text_.IsEmpty() && | 367 if (last_collapsible_space_ == CollapsibleSpace::kNone || text_.IsEmpty() || |
296 text_[text_.length() - 1] == kSpaceCharacter) | 368 text_[text_.length() - 1] != kSpaceCharacter) |
297 RemoveTrailingCollapsibleSpace(next_start_offset); | 369 return false; |
| 370 RemoveTrailingCollapsibleSpace(next_start_offset); |
| 371 return true; |
298 } | 372 } |
299 | 373 |
300 void NGInlineItemsBuilder::RemoveTrailingCollapsibleSpace( | 374 void NGInlineItemsBuilder::RemoveTrailingCollapsibleSpace( |
301 unsigned* next_start_offset) { | 375 unsigned* next_start_offset) { |
302 DCHECK_NE(last_collapsible_space_, CollapsibleSpace::kNone); | 376 DCHECK_NE(last_collapsible_space_, CollapsibleSpace::kNone); |
303 DCHECK(!text_.IsEmpty()); | 377 DCHECK(!text_.IsEmpty()); |
304 DCHECK_EQ(text_[text_.length() - 1], kSpaceCharacter); | 378 DCHECK_EQ(text_[text_.length() - 1], kSpaceCharacter); |
305 | 379 |
306 unsigned new_size = text_.length() - 1; | 380 unsigned new_size = text_.length() - 1; |
307 text_.Resize(new_size); | 381 text_.Resize(new_size); |
(...skipping 114 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
422 } | 496 } |
423 | 497 |
424 void NGInlineItemsBuilder::Exit(LayoutObject* node) { | 498 void NGInlineItemsBuilder::Exit(LayoutObject* node) { |
425 while (!exits_.IsEmpty() && exits_.back().node == node) { | 499 while (!exits_.IsEmpty() && exits_.back().node == node) { |
426 Append(NGInlineItem::kBidiControl, exits_.back().character); | 500 Append(NGInlineItem::kBidiControl, exits_.back().character); |
427 exits_.pop_back(); | 501 exits_.pop_back(); |
428 } | 502 } |
429 } | 503 } |
430 | 504 |
431 } // namespace blink | 505 } // namespace blink |
OLD | NEW |