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 String NGInlineItemsBuilder::ToString() { |
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 RemoveTrailingCollapsibleSpaceIfExists(); |
25 RemoveTrailingCollapsibleSpaceIfExists(&next_start_offset); | |
26 | 25 |
27 return text_.ToString(); | 26 return text_.ToString(); |
28 } | 27 } |
29 | 28 |
30 // Determine "Ambiguous" East Asian Width is Wide or Narrow. | 29 // Determine "Ambiguous" East Asian Width is Wide or Narrow. |
31 // Unicode East Asian Width | 30 // Unicode East Asian Width |
32 // http://unicode.org/reports/tr11/ | 31 // http://unicode.org/reports/tr11/ |
33 static bool IsAmbiguosEastAsianWidthWide(const ComputedStyle* style) { | 32 static bool IsAmbiguosEastAsianWidthWide(const ComputedStyle* style) { |
34 UScriptCode script = style->GetFontDescription().GetScript(); | 33 UScriptCode script = style->GetFontDescription().GetScript(); |
35 return script == USCRIPT_KATAKANA_OR_HIRAGANA || | 34 return script == USCRIPT_KATAKANA_OR_HIRAGANA || |
(...skipping 131 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
167 if (c == kSpaceCharacter || c == kTabulationCharacter) { | 166 if (c == kSpaceCharacter || c == kTabulationCharacter) { |
168 if (last_collapsible_space_ == CollapsibleSpace::kNone) { | 167 if (last_collapsible_space_ == CollapsibleSpace::kNone) { |
169 text_.Append(kSpaceCharacter); | 168 text_.Append(kSpaceCharacter); |
170 last_collapsible_space_ = CollapsibleSpace::kSpace; | 169 last_collapsible_space_ = CollapsibleSpace::kSpace; |
171 } | 170 } |
172 i++; | 171 i++; |
173 continue; | 172 continue; |
174 } | 173 } |
175 | 174 |
176 if (last_collapsible_space_ == CollapsibleSpace::kNewline) { | 175 if (last_collapsible_space_ == CollapsibleSpace::kNewline) { |
177 RemoveTrailingCollapsibleNewlineIfNeeded(&start_offset, string, i, style); | 176 RemoveTrailingCollapsibleNewlineIfNeeded(string, i, style); |
| 177 start_offset = std::min(start_offset, text_.length()); |
178 } | 178 } |
179 | 179 |
180 size_t end_of_non_space = string.Find(IsCollapsibleSpace, i + 1); | 180 size_t end_of_non_space = string.Find(IsCollapsibleSpace, i + 1); |
181 if (end_of_non_space == kNotFound) | 181 if (end_of_non_space == kNotFound) |
182 end_of_non_space = string.length(); | 182 end_of_non_space = string.length(); |
183 text_.Append(string, i, end_of_non_space - i); | 183 text_.Append(string, i, end_of_non_space - i); |
184 i = end_of_non_space; | 184 i = end_of_non_space; |
185 last_collapsible_space_ = CollapsibleSpace::kNone; | 185 last_collapsible_space_ = CollapsibleSpace::kNone; |
186 } | 186 } |
187 | 187 |
(...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
233 if (end == kNotFound) | 233 if (end == kNotFound) |
234 end = string.length(); | 234 end = string.length(); |
235 AppendWithWhiteSpaceCollapsing(string, start, end, style, layout_object); | 235 AppendWithWhiteSpaceCollapsing(string, start, end, style, layout_object); |
236 start = end; | 236 start = end; |
237 } | 237 } |
238 } | 238 } |
239 | 239 |
240 void NGInlineItemsBuilder::AppendForcedBreak(const ComputedStyle* style, | 240 void NGInlineItemsBuilder::AppendForcedBreak(const ComputedStyle* style, |
241 LayoutObject* layout_object) { | 241 LayoutObject* layout_object) { |
242 // Remove collapsible spaces immediately before a preserved newline. | 242 // Remove collapsible spaces immediately before a preserved newline. |
243 unsigned start_offset = text_.length(); | 243 RemoveTrailingCollapsibleSpaceIfExists(); |
244 RemoveTrailingCollapsibleSpaceIfExists(&start_offset); | |
245 | 244 |
246 Append(NGInlineItem::kControl, kNewlineCharacter, style, layout_object); | 245 Append(NGInlineItem::kControl, kNewlineCharacter, style, layout_object); |
247 | 246 |
248 // Remove collapsible spaces immediately after a preserved newline. | 247 // Remove collapsible spaces immediately after a preserved newline. |
249 last_collapsible_space_ = CollapsibleSpace::kSpace; | 248 last_collapsible_space_ = CollapsibleSpace::kSpace; |
250 } | 249 } |
251 | 250 |
252 void NGInlineItemsBuilder::Append(NGInlineItem::NGInlineItemType type, | 251 void NGInlineItemsBuilder::Append(NGInlineItem::NGInlineItemType type, |
253 UChar character, | 252 UChar character, |
254 const ComputedStyle* style, | 253 const ComputedStyle* style, |
255 LayoutObject* layout_object) { | 254 LayoutObject* layout_object) { |
256 DCHECK_NE(character, kSpaceCharacter); | 255 DCHECK_NE(character, kSpaceCharacter); |
257 DCHECK_NE(character, kZeroWidthSpaceCharacter); | 256 DCHECK_NE(character, kZeroWidthSpaceCharacter); |
258 | 257 |
259 text_.Append(character); | 258 text_.Append(character); |
260 unsigned end_offset = text_.length(); | 259 unsigned end_offset = text_.length(); |
261 AppendItem(items_, type, end_offset - 1, end_offset, style, layout_object); | 260 AppendItem(items_, type, end_offset - 1, end_offset, style, layout_object); |
262 last_collapsible_space_ = CollapsibleSpace::kNone; | 261 last_collapsible_space_ = CollapsibleSpace::kNone; |
263 } | 262 } |
264 | 263 |
265 void NGInlineItemsBuilder::Append(NGInlineItem::NGInlineItemType type, | 264 void NGInlineItemsBuilder::AppendOpaque(NGInlineItem::NGInlineItemType type, |
266 const ComputedStyle* style, | 265 UChar character) { |
267 LayoutObject* layout_object) { | 266 text_.Append(character); |
| 267 unsigned end_offset = text_.length(); |
| 268 AppendItem(items_, type, end_offset - 1, end_offset, nullptr, nullptr); |
| 269 } |
| 270 |
| 271 void NGInlineItemsBuilder::AppendOpaque(NGInlineItem::NGInlineItemType type, |
| 272 const ComputedStyle* style, |
| 273 LayoutObject* layout_object) { |
268 unsigned end_offset = text_.length(); | 274 unsigned end_offset = text_.length(); |
269 AppendItem(items_, type, end_offset, end_offset, style, layout_object); | 275 AppendItem(items_, type, end_offset, end_offset, style, layout_object); |
270 } | 276 } |
271 | 277 |
| 278 // Removes the collapsible newline at the end of |text_| if exists and the |
| 279 // removal conditions met. |
272 void NGInlineItemsBuilder::RemoveTrailingCollapsibleNewlineIfNeeded( | 280 void NGInlineItemsBuilder::RemoveTrailingCollapsibleNewlineIfNeeded( |
273 unsigned* next_start_offset, | |
274 const String& after, | 281 const String& after, |
275 unsigned after_index, | 282 unsigned after_index, |
276 const ComputedStyle* after_style) { | 283 const ComputedStyle* after_style) { |
277 DCHECK_EQ(last_collapsible_space_, CollapsibleSpace::kNewline); | 284 DCHECK_EQ(last_collapsible_space_, CollapsibleSpace::kNewline); |
278 | 285 |
279 if (text_.IsEmpty() || text_[text_.length() - 1] != kSpaceCharacter) | 286 if (text_.IsEmpty() || text_[text_.length() - 1] != kSpaceCharacter) |
280 return; | 287 return; |
281 | 288 |
282 const ComputedStyle* before_style = after_style; | 289 const ComputedStyle* before_style = after_style; |
283 if (!items_->IsEmpty()) { | 290 if (!items_->IsEmpty()) { |
284 NGInlineItem& item = items_->back(); | 291 NGInlineItem& item = items_->back(); |
285 if (text_.length() < item.EndOffset() + 2) | 292 if (text_.length() < item.EndOffset() + 2) |
286 before_style = item.Style(); | 293 before_style = item.Style(); |
287 } | 294 } |
288 | 295 |
289 if (ShouldRemoveNewline(text_, before_style, after, after_index, after_style)) | 296 if (ShouldRemoveNewline(text_, before_style, after, after_index, after_style)) |
290 RemoveTrailingCollapsibleSpace(next_start_offset); | 297 RemoveTrailingCollapsibleSpace(text_.length() - 1); |
291 } | 298 } |
292 | 299 |
293 void NGInlineItemsBuilder::RemoveTrailingCollapsibleSpaceIfExists( | 300 // Removes the collapsible space at the end of |text_| if exists. |
294 unsigned* next_start_offset) { | 301 void NGInlineItemsBuilder::RemoveTrailingCollapsibleSpaceIfExists() { |
295 if (last_collapsible_space_ != CollapsibleSpace::kNone && !text_.IsEmpty() && | 302 if (last_collapsible_space_ == CollapsibleSpace::kNone || text_.IsEmpty()) |
296 text_[text_.length() - 1] == kSpaceCharacter) | 303 return; |
297 RemoveTrailingCollapsibleSpace(next_start_offset); | 304 |
| 305 // Look for the last space character since characters that are opaque to |
| 306 // whitespace collapsing may be appended. |
| 307 for (unsigned i = text_.length(); i;) { |
| 308 UChar ch = text_[--i]; |
| 309 if (ch == kSpaceCharacter) { |
| 310 RemoveTrailingCollapsibleSpace(i); |
| 311 return; |
| 312 } |
| 313 |
| 314 // AppendForcedBreak sets CollapsibleSpace::kSpace to ignore leading |
| 315 // spaces. In this case, the trailing collapsible space does not exist. |
| 316 if (ch == kNewlineCharacter) |
| 317 return; |
| 318 } |
| 319 NOTREACHED(); |
298 } | 320 } |
299 | 321 |
300 void NGInlineItemsBuilder::RemoveTrailingCollapsibleSpace( | 322 // Removes the collapsible space at the specified index. |
301 unsigned* next_start_offset) { | 323 void NGInlineItemsBuilder::RemoveTrailingCollapsibleSpace(unsigned index) { |
302 DCHECK_NE(last_collapsible_space_, CollapsibleSpace::kNone); | 324 DCHECK_NE(last_collapsible_space_, CollapsibleSpace::kNone); |
303 DCHECK(!text_.IsEmpty()); | 325 DCHECK(!text_.IsEmpty()); |
304 DCHECK_EQ(text_[text_.length() - 1], kSpaceCharacter); | 326 DCHECK_EQ(text_[index], kSpaceCharacter); |
305 | 327 |
306 unsigned new_size = text_.length() - 1; | 328 text_.erase(index); |
307 text_.Resize(new_size); | |
308 last_collapsible_space_ = CollapsibleSpace::kNone; | 329 last_collapsible_space_ = CollapsibleSpace::kNone; |
309 | 330 |
310 if (*next_start_offset <= new_size) | 331 // Adjust items if the removed space is already included. |
311 return; | |
312 *next_start_offset = new_size; | |
313 | |
314 // Adjust the last item if the removed space is already appended. | |
315 for (unsigned i = items_->size(); i > 0;) { | 332 for (unsigned i = items_->size(); i > 0;) { |
316 NGInlineItem& item = (*items_)[--i]; | 333 NGInlineItem& item = (*items_)[--i]; |
317 DCHECK_EQ(item.EndOffset(), new_size + 1); | 334 if (index >= item.EndOffset()) |
318 if (item.Type() == NGInlineItem::kText) { | 335 return; |
319 DCHECK_GE(item.Length(), 1u); | 336 if (item.StartOffset() <= index) { |
320 if (item.Length() > 1) | 337 if (item.Length() == 1) { |
321 item.SetEndOffset(new_size); | 338 DCHECK_EQ(item.StartOffset(), index); |
322 else | 339 DCHECK_EQ(item.Type(), NGInlineItem::kText); |
323 items_->erase(i); | 340 items_->erase(i); |
324 break; | 341 } else { |
| 342 item.SetEndOffset(item.EndOffset() - 1); |
| 343 } |
| 344 return; |
325 } | 345 } |
326 if (!item.Length()) { | 346 |
327 // Trailing spaces can be removed across non-character items. | 347 // Trailing spaces can be removed across non-character items. |
328 item.SetOffset(new_size, new_size); | 348 // Adjust their offsets if after the removed index. |
329 continue; | 349 item.SetOffset(item.StartOffset() - 1, item.EndOffset() - 1); |
330 } | |
331 NOTREACHED(); | |
332 break; | |
333 } | 350 } |
334 } | 351 } |
335 | 352 |
336 void NGInlineItemsBuilder::AppendBidiControl(const ComputedStyle* style, | 353 void NGInlineItemsBuilder::AppendBidiControl(const ComputedStyle* style, |
337 UChar ltr, | 354 UChar ltr, |
338 UChar rtl) { | 355 UChar rtl) { |
339 Append(NGInlineItem::kBidiControl, | 356 AppendOpaque(NGInlineItem::kBidiControl, |
340 style->Direction() == TextDirection::kRtl ? rtl : ltr); | 357 IsLtr(style->Direction()) ? ltr : rtl); |
341 } | 358 } |
342 | 359 |
343 void NGInlineItemsBuilder::EnterBlock(const ComputedStyle* style) { | 360 void NGInlineItemsBuilder::EnterBlock(const ComputedStyle* style) { |
344 // Handle bidi-override on the block itself. | 361 // Handle bidi-override on the block itself. |
345 switch (style->GetUnicodeBidi()) { | 362 switch (style->GetUnicodeBidi()) { |
346 case UnicodeBidi::kNormal: | 363 case UnicodeBidi::kNormal: |
347 case UnicodeBidi::kEmbed: | 364 case UnicodeBidi::kEmbed: |
348 case UnicodeBidi::kIsolate: | 365 case UnicodeBidi::kIsolate: |
349 // Isolate and embed values are enforced by default and redundant on the | 366 // Isolate and embed values are enforced by default and redundant on the |
350 // block elements. | 367 // block elements. |
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
382 AppendBidiControl(style, kLeftToRightOverrideCharacter, | 399 AppendBidiControl(style, kLeftToRightOverrideCharacter, |
383 kRightToLeftOverrideCharacter); | 400 kRightToLeftOverrideCharacter); |
384 Enter(node, kPopDirectionalFormattingCharacter); | 401 Enter(node, kPopDirectionalFormattingCharacter); |
385 break; | 402 break; |
386 case UnicodeBidi::kIsolate: | 403 case UnicodeBidi::kIsolate: |
387 AppendBidiControl(style, kLeftToRightIsolateCharacter, | 404 AppendBidiControl(style, kLeftToRightIsolateCharacter, |
388 kRightToLeftIsolateCharacter); | 405 kRightToLeftIsolateCharacter); |
389 Enter(node, kPopDirectionalIsolateCharacter); | 406 Enter(node, kPopDirectionalIsolateCharacter); |
390 break; | 407 break; |
391 case UnicodeBidi::kPlaintext: | 408 case UnicodeBidi::kPlaintext: |
392 Append(NGInlineItem::kBidiControl, kFirstStrongIsolateCharacter); | 409 AppendOpaque(NGInlineItem::kBidiControl, kFirstStrongIsolateCharacter); |
393 Enter(node, kPopDirectionalIsolateCharacter); | 410 Enter(node, kPopDirectionalIsolateCharacter); |
394 break; | 411 break; |
395 case UnicodeBidi::kIsolateOverride: | 412 case UnicodeBidi::kIsolateOverride: |
396 Append(NGInlineItem::kBidiControl, kFirstStrongIsolateCharacter); | 413 AppendOpaque(NGInlineItem::kBidiControl, kFirstStrongIsolateCharacter); |
397 AppendBidiControl(style, kLeftToRightOverrideCharacter, | 414 AppendBidiControl(style, kLeftToRightOverrideCharacter, |
398 kRightToLeftOverrideCharacter); | 415 kRightToLeftOverrideCharacter); |
399 Enter(node, kPopDirectionalIsolateCharacter); | 416 Enter(node, kPopDirectionalIsolateCharacter); |
400 Enter(node, kPopDirectionalFormattingCharacter); | 417 Enter(node, kPopDirectionalFormattingCharacter); |
401 break; | 418 break; |
402 } | 419 } |
403 | 420 |
404 Append(NGInlineItem::kOpenTag, style, node); | 421 AppendOpaque(NGInlineItem::kOpenTag, style, node); |
405 } | 422 } |
406 | 423 |
407 void NGInlineItemsBuilder::Enter(LayoutObject* node, UChar character_to_exit) { | 424 void NGInlineItemsBuilder::Enter(LayoutObject* node, UChar character_to_exit) { |
408 exits_.push_back(OnExitNode{node, character_to_exit}); | 425 exits_.push_back(OnExitNode{node, character_to_exit}); |
409 has_bidi_controls_ = true; | 426 has_bidi_controls_ = true; |
410 } | 427 } |
411 | 428 |
412 void NGInlineItemsBuilder::ExitBlock() { | 429 void NGInlineItemsBuilder::ExitBlock() { |
413 Exit(nullptr); | 430 Exit(nullptr); |
414 } | 431 } |
415 | 432 |
416 void NGInlineItemsBuilder::ExitInline(LayoutObject* node) { | 433 void NGInlineItemsBuilder::ExitInline(LayoutObject* node) { |
417 DCHECK(node); | 434 DCHECK(node); |
418 | 435 |
419 Append(NGInlineItem::kCloseTag, node->Style(), node); | 436 AppendOpaque(NGInlineItem::kCloseTag, node->Style(), node); |
420 | 437 |
421 Exit(node); | 438 Exit(node); |
422 } | 439 } |
423 | 440 |
424 void NGInlineItemsBuilder::Exit(LayoutObject* node) { | 441 void NGInlineItemsBuilder::Exit(LayoutObject* node) { |
425 while (!exits_.IsEmpty() && exits_.back().node == node) { | 442 while (!exits_.IsEmpty() && exits_.back().node == node) { |
426 Append(NGInlineItem::kBidiControl, exits_.back().character); | 443 AppendOpaque(NGInlineItem::kBidiControl, exits_.back().character); |
427 exits_.pop_back(); | 444 exits_.pop_back(); |
428 } | 445 } |
429 } | 446 } |
430 | 447 |
431 } // namespace blink | 448 } // namespace blink |
OLD | NEW |