| 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/ng_layout_inline_items_builder.h" | 5 #include "core/layout/ng/ng_layout_inline_items_builder.h" |
| 6 | 6 |
| 7 #include "core/layout/LayoutObject.h" | 7 #include "core/layout/LayoutObject.h" |
| 8 #include "core/layout/ng/ng_inline_node.h" | 8 #include "core/layout/ng/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 NGLayoutInlineItemsBuilder::~NGLayoutInlineItemsBuilder() { | 13 NGLayoutInlineItemsBuilder::~NGLayoutInlineItemsBuilder() { |
| 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 NGLayoutInlineItemsBuilder::ToString() { | 18 String NGLayoutInlineItemsBuilder::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 unsigned next_start_offset = text_.length(); |
| 25 RemoveTrailingCollapsibleSpace(&next_start_offset); | 25 RemoveTrailingCollapsibleSpaceIfExists(&next_start_offset); |
| 26 | 26 |
| 27 return text_.toString(); | 27 return text_.toString(); |
| 28 } | 28 } |
| 29 | 29 |
| 30 // Determine "Ambiguous" East Asian Width is Wide or Narrow. | 30 // Determine "Ambiguous" East Asian Width is Wide or Narrow. |
| 31 // Unicode East Asian Width | 31 // Unicode East Asian Width |
| 32 // http://unicode.org/reports/tr11/ | 32 // http://unicode.org/reports/tr11/ |
| 33 static bool IsAmbiguosEastAsianWidthWide(const ComputedStyle* style) { | 33 static bool IsAmbiguosEastAsianWidthWide(const ComputedStyle* style) { |
| 34 UScriptCode script = style->getFontDescription().script(); | 34 UScriptCode script = style->getFontDescription().script(); |
| 35 return script == USCRIPT_KATAKANA_OR_HIRAGANA || | 35 return script == USCRIPT_KATAKANA_OR_HIRAGANA || |
| (...skipping 12 matching lines...) Expand all Loading... |
| 48 // Determine whether a newline should be removed or not. | 48 // Determine whether a newline should be removed or not. |
| 49 // CSS Text, Segment Break Transformation Rules | 49 // CSS Text, Segment Break Transformation Rules |
| 50 // https://drafts.csswg.org/css-text-3/#line-break-transform | 50 // https://drafts.csswg.org/css-text-3/#line-break-transform |
| 51 static bool ShouldRemoveNewlineSlow(const StringBuilder& before, | 51 static bool ShouldRemoveNewlineSlow(const StringBuilder& before, |
| 52 const ComputedStyle* before_style, | 52 const ComputedStyle* before_style, |
| 53 const String& after, | 53 const String& after, |
| 54 unsigned after_index, | 54 unsigned after_index, |
| 55 const ComputedStyle* after_style) { | 55 const ComputedStyle* after_style) { |
| 56 // Remove if either before/after the newline is zeroWidthSpaceCharacter. | 56 // Remove if either before/after the newline is zeroWidthSpaceCharacter. |
| 57 UChar32 last = 0; | 57 UChar32 last = 0; |
| 58 if (!before.isEmpty()) { | 58 DCHECK(!before.isEmpty() && before[before.length() - 1] == ' '); |
| 59 last = before[before.length() - 1]; | 59 if (before.length() >= 2) { |
| 60 last = before[before.length() - 2]; |
| 60 if (last == zeroWidthSpaceCharacter) | 61 if (last == zeroWidthSpaceCharacter) |
| 61 return true; | 62 return true; |
| 62 } | 63 } |
| 63 UChar32 next = 0; | 64 UChar32 next = 0; |
| 64 if (after_index < after.length()) { | 65 if (after_index < after.length()) { |
| 65 next = after[after_index]; | 66 next = after[after_index]; |
| 66 if (next == zeroWidthSpaceCharacter) | 67 if (next == zeroWidthSpaceCharacter) |
| 67 return true; | 68 return true; |
| 68 } | 69 } |
| 69 | 70 |
| (...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 104 static void AppendItem(Vector<NGLayoutInlineItem>* items, | 105 static void AppendItem(Vector<NGLayoutInlineItem>* items, |
| 105 unsigned start, | 106 unsigned start, |
| 106 unsigned end, | 107 unsigned end, |
| 107 const ComputedStyle* style = nullptr, | 108 const ComputedStyle* style = nullptr, |
| 108 LayoutObject* layout_object = nullptr) { | 109 LayoutObject* layout_object = nullptr) { |
| 109 DCHECK(items->isEmpty() || items->back().EndOffset() == start); | 110 DCHECK(items->isEmpty() || items->back().EndOffset() == start); |
| 110 DCHECK_LT(start, end); | 111 DCHECK_LT(start, end); |
| 111 items->push_back(NGLayoutInlineItem(start, end, style, layout_object)); | 112 items->push_back(NGLayoutInlineItem(start, end, style, layout_object)); |
| 112 } | 113 } |
| 113 | 114 |
| 115 static inline bool IsCollapsibleSpace(UChar c, bool preserve_newline) { |
| 116 return c == spaceCharacter || c == tabulationCharacter || |
| 117 (!preserve_newline && c == newlineCharacter); |
| 118 } |
| 119 |
| 114 void NGLayoutInlineItemsBuilder::Append(const String& string, | 120 void NGLayoutInlineItemsBuilder::Append(const String& string, |
| 115 const ComputedStyle* style, | 121 const ComputedStyle* style, |
| 116 LayoutObject* layout_object) { | 122 LayoutObject* layout_object) { |
| 117 if (string.isEmpty()) | 123 if (string.isEmpty()) |
| 118 return; | 124 return; |
| 119 | 125 |
| 120 if (has_pending_newline_) | |
| 121 ProcessPendingNewline(string, style); | |
| 122 | |
| 123 EWhiteSpace whitespace = style->whiteSpace(); | 126 EWhiteSpace whitespace = style->whiteSpace(); |
| 124 bool preserve_newline = | 127 bool preserve_newline = |
| 125 ComputedStyle::preserveNewline(whitespace) && !is_svgtext_; | 128 ComputedStyle::preserveNewline(whitespace) && !is_svgtext_; |
| 126 bool collapse_whitespace = ComputedStyle::collapseWhiteSpace(whitespace); | 129 bool collapse_whitespace = ComputedStyle::collapseWhiteSpace(whitespace); |
| 127 unsigned start_offset = text_.length(); | 130 unsigned start_offset = text_.length(); |
| 128 | 131 |
| 129 if (!collapse_whitespace) { | 132 if (!collapse_whitespace) { |
| 130 text_.append(string); | 133 text_.append(string); |
| 131 is_last_collapsible_space_ = false; | 134 last_collapsible_space_ = CollapsibleSpace::None; |
| 132 } else { | 135 } else { |
| 133 text_.reserveCapacity(string.length()); | 136 text_.reserveCapacity(string.length()); |
| 134 for (unsigned i = 0; i < string.length(); i++) { | 137 for (unsigned i = 0; i < string.length();) { |
| 135 UChar c = string[i]; | 138 UChar c = string[i]; |
| 136 bool is_collapsible_space; | |
| 137 if (c == newlineCharacter) { | 139 if (c == newlineCharacter) { |
| 138 RemoveTrailingCollapsibleSpace(&start_offset); | |
| 139 if (preserve_newline) { | 140 if (preserve_newline) { |
| 141 RemoveTrailingCollapsibleSpaceIfExists(&start_offset); |
| 140 text_.append(c); | 142 text_.append(c); |
| 141 // Remove collapsible spaces immediately following a newline. | 143 // Remove collapsible spaces immediately following a newline. |
| 142 is_last_collapsible_space_ = true; | 144 last_collapsible_space_ = CollapsibleSpace::Space; |
| 145 i++; |
| 143 continue; | 146 continue; |
| 144 } | 147 } |
| 145 if (i + 1 == string.length()) { | 148 |
| 146 // If at the end of string, process this newline on the next Append. | 149 if (last_collapsible_space_ == CollapsibleSpace::None) |
| 147 has_pending_newline_ = true; | 150 text_.append(spaceCharacter); |
| 151 last_collapsible_space_ = CollapsibleSpace::Newline; |
| 152 i++; |
| 153 continue; |
| 154 } |
| 155 |
| 156 if (c == spaceCharacter || c == tabulationCharacter) { |
| 157 if (last_collapsible_space_ == CollapsibleSpace::None) { |
| 158 text_.append(spaceCharacter); |
| 159 last_collapsible_space_ = CollapsibleSpace::Space; |
| 160 } |
| 161 i++; |
| 162 continue; |
| 163 } |
| 164 |
| 165 if (last_collapsible_space_ == CollapsibleSpace::Newline) { |
| 166 RemoveTrailingCollapsibleNewlineIfNeeded(&start_offset, string, i, |
| 167 style); |
| 168 } |
| 169 |
| 170 unsigned start_of_non_space = i; |
| 171 for (i++; i < string.length(); i++) { |
| 172 if (IsCollapsibleSpace(string[i], false)) |
| 148 break; | 173 break; |
| 149 } | |
| 150 if (ShouldRemoveNewline(text_, style, string, i + 1, style)) | |
| 151 continue; | |
| 152 is_collapsible_space = true; | |
| 153 } else { | |
| 154 is_collapsible_space = c == spaceCharacter || c == tabulationCharacter; | |
| 155 } | 174 } |
| 156 if (!is_collapsible_space) { | 175 text_.append(string, start_of_non_space, i - start_of_non_space); |
| 157 text_.append(c); | 176 last_collapsible_space_ = CollapsibleSpace::None; |
| 158 is_last_collapsible_space_ = false; | |
| 159 } else if (!is_last_collapsible_space_) { | |
| 160 text_.append(spaceCharacter); | |
| 161 is_last_collapsible_space_ = true; | |
| 162 } | |
| 163 } | 177 } |
| 164 } | 178 } |
| 165 | 179 |
| 166 if (text_.length() > start_offset) | 180 if (text_.length() > start_offset) |
| 167 AppendItem(items_, start_offset, text_.length(), style, layout_object); | 181 AppendItem(items_, start_offset, text_.length(), style, layout_object); |
| 168 } | 182 } |
| 169 | 183 |
| 170 void NGLayoutInlineItemsBuilder::Append(UChar character, | 184 void NGLayoutInlineItemsBuilder::Append(UChar character, |
| 171 const ComputedStyle* style, | 185 const ComputedStyle* style, |
| 172 LayoutObject* layout_object) { | 186 LayoutObject* layout_object) { |
| 173 DCHECK(character != spaceCharacter && character != tabulationCharacter && | 187 DCHECK(character != spaceCharacter && character != tabulationCharacter && |
| 174 character != newlineCharacter && character != zeroWidthSpaceCharacter); | 188 character != newlineCharacter && character != zeroWidthSpaceCharacter); |
| 175 if (has_pending_newline_) | |
| 176 ProcessPendingNewline(emptyString, nullptr); | |
| 177 | 189 |
| 178 text_.append(character); | 190 text_.append(character); |
| 179 unsigned end_offset = text_.length(); | 191 unsigned end_offset = text_.length(); |
| 180 AppendItem(items_, end_offset - 1, end_offset, style, layout_object); | 192 AppendItem(items_, end_offset - 1, end_offset, style, layout_object); |
| 181 is_last_collapsible_space_ = false; | 193 last_collapsible_space_ = CollapsibleSpace::None; |
| 182 } | 194 } |
| 183 | 195 |
| 184 void NGLayoutInlineItemsBuilder::AppendAsOpaqueToSpaceCollapsing( | 196 void NGLayoutInlineItemsBuilder::RemoveTrailingCollapsibleNewlineIfNeeded( |
| 185 UChar character) { | 197 unsigned* next_start_offset, |
| 186 if (has_pending_newline_) | 198 const String& after, |
| 187 ProcessPendingNewline(emptyString, nullptr); | 199 unsigned after_index, |
| 200 const ComputedStyle* after_style) { |
| 201 DCHECK_EQ(last_collapsible_space_, CollapsibleSpace::Newline); |
| 188 | 202 |
| 189 text_.append(character); | 203 if (text_.isEmpty() || text_[text_.length() - 1] != spaceCharacter) |
| 190 unsigned end_offset = text_.length(); | 204 return; |
| 191 AppendItem(items_, end_offset - 1, end_offset, nullptr); | 205 |
| 206 const ComputedStyle* before_style = after_style; |
| 207 if (!items_->isEmpty()) { |
| 208 NGLayoutInlineItem& item = items_->back(); |
| 209 if (text_.length() < item.EndOffset() + 2) |
| 210 before_style = item.Style(); |
| 211 } |
| 212 |
| 213 if (ShouldRemoveNewline(text_, before_style, after, after_index, after_style)) |
| 214 RemoveTrailingCollapsibleSpace(next_start_offset); |
| 192 } | 215 } |
| 193 | 216 |
| 194 void NGLayoutInlineItemsBuilder::ProcessPendingNewline( | 217 void NGLayoutInlineItemsBuilder::RemoveTrailingCollapsibleSpaceIfExists( |
| 195 const String& string, | 218 unsigned* next_start_offset) { |
| 196 const ComputedStyle* style) { | 219 if (last_collapsible_space_ != CollapsibleSpace::None && !text_.isEmpty() && |
| 197 DCHECK(has_pending_newline_); | 220 text_[text_.length() - 1] == spaceCharacter) |
| 198 if (!items_->isEmpty()) { | 221 RemoveTrailingCollapsibleSpace(next_start_offset); |
| 199 NGLayoutInlineItem& item = items_->back(); | |
| 200 if (!ShouldRemoveNewline(text_, item.Style(), string, 0, style)) { | |
| 201 text_.append(spaceCharacter); | |
| 202 item.SetEndOffset(text_.length()); | |
| 203 } | |
| 204 } | |
| 205 // Remove spaces following a newline even when the newline was removed. | |
| 206 is_last_collapsible_space_ = true; | |
| 207 has_pending_newline_ = false; | |
| 208 } | 222 } |
| 209 | 223 |
| 210 void NGLayoutInlineItemsBuilder::RemoveTrailingCollapsibleSpace( | 224 void NGLayoutInlineItemsBuilder::RemoveTrailingCollapsibleSpace( |
| 211 unsigned* next_start_offset) { | 225 unsigned* next_start_offset) { |
| 212 if (!is_last_collapsible_space_ || text_.isEmpty()) | 226 DCHECK_NE(last_collapsible_space_, CollapsibleSpace::None); |
| 213 return; | 227 DCHECK(!text_.isEmpty() && text_[text_.length() - 1] == spaceCharacter); |
| 214 DCHECK_EQ(spaceCharacter, text_[text_.length() - 1]); | 228 |
| 215 unsigned new_size = text_.length() - 1; | 229 unsigned new_size = text_.length() - 1; |
| 216 text_.resize(new_size); | 230 text_.resize(new_size); |
| 217 is_last_collapsible_space_ = false; | 231 last_collapsible_space_ = CollapsibleSpace::None; |
| 218 | 232 |
| 219 // Adjust the last item if the removed space is already appended. | 233 // Adjust the last item if the removed space is already appended. |
| 220 if (*next_start_offset > new_size) { | 234 if (*next_start_offset > new_size) { |
| 221 *next_start_offset = new_size; | 235 *next_start_offset = new_size; |
| 222 if (!items_->isEmpty()) { | 236 if (!items_->isEmpty()) { |
| 223 NGLayoutInlineItem& last_item = items_->back(); | 237 NGLayoutInlineItem& last_item = items_->back(); |
| 224 DCHECK_EQ(last_item.EndOffset(), new_size + 1); | 238 DCHECK_EQ(last_item.EndOffset(), new_size + 1); |
| 225 if (last_item.StartOffset() == new_size) | 239 if (last_item.StartOffset() == new_size) |
| 226 items_->pop_back(); | 240 items_->pop_back(); |
| 227 else | 241 else |
| 228 last_item.SetEndOffset(new_size); | 242 last_item.SetEndOffset(new_size); |
| 229 } | 243 } |
| 230 } | 244 } |
| 231 } | 245 } |
| 232 | 246 |
| 233 void NGLayoutInlineItemsBuilder::AppendBidiControl(const ComputedStyle* style, | 247 void NGLayoutInlineItemsBuilder::AppendBidiControl(const ComputedStyle* style, |
| 234 UChar ltr, | 248 UChar ltr, |
| 235 UChar rtl) { | 249 UChar rtl) { |
| 236 AppendAsOpaqueToSpaceCollapsing( | 250 Append(style->direction() == TextDirection::kRtl ? rtl : ltr); |
| 237 style->direction() == TextDirection::kRtl ? rtl : ltr); | |
| 238 } | 251 } |
| 239 | 252 |
| 240 void NGLayoutInlineItemsBuilder::EnterBlock(const ComputedStyle* style) { | 253 void NGLayoutInlineItemsBuilder::EnterBlock(const ComputedStyle* style) { |
| 241 // Handle bidi-override on the block itself. | 254 // Handle bidi-override on the block itself. |
| 242 // Isolate and embed values are enforced by default and redundant on the block | 255 // Isolate and embed values are enforced by default and redundant on the block |
| 243 // elements. | 256 // elements. |
| 244 // Plaintext and direction are handled as the paragraph level by | 257 // Plaintext and direction are handled as the paragraph level by |
| 245 // NGBidiParagraph::SetParagraph(). | 258 // NGBidiParagraph::SetParagraph(). |
| 246 if (style->getUnicodeBidi() == UnicodeBidi::kBidiOverride || | 259 if (style->getUnicodeBidi() == UnicodeBidi::kBidiOverride || |
| 247 style->getUnicodeBidi() == UnicodeBidi::kIsolateOverride) { | 260 style->getUnicodeBidi() == UnicodeBidi::kIsolateOverride) { |
| (...skipping 18 matching lines...) Expand all Loading... |
| 266 AppendBidiControl(style, leftToRightOverrideCharacter, | 279 AppendBidiControl(style, leftToRightOverrideCharacter, |
| 267 rightToLeftOverrideCharacter); | 280 rightToLeftOverrideCharacter); |
| 268 Enter(node, popDirectionalFormattingCharacter); | 281 Enter(node, popDirectionalFormattingCharacter); |
| 269 break; | 282 break; |
| 270 case UnicodeBidi::kIsolate: | 283 case UnicodeBidi::kIsolate: |
| 271 AppendBidiControl(style, leftToRightIsolateCharacter, | 284 AppendBidiControl(style, leftToRightIsolateCharacter, |
| 272 rightToLeftIsolateCharacter); | 285 rightToLeftIsolateCharacter); |
| 273 Enter(node, popDirectionalIsolateCharacter); | 286 Enter(node, popDirectionalIsolateCharacter); |
| 274 break; | 287 break; |
| 275 case UnicodeBidi::kPlaintext: | 288 case UnicodeBidi::kPlaintext: |
| 276 AppendAsOpaqueToSpaceCollapsing(firstStrongIsolateCharacter); | 289 Append(firstStrongIsolateCharacter); |
| 277 Enter(node, popDirectionalIsolateCharacter); | 290 Enter(node, popDirectionalIsolateCharacter); |
| 278 break; | 291 break; |
| 279 case UnicodeBidi::kIsolateOverride: | 292 case UnicodeBidi::kIsolateOverride: |
| 280 AppendAsOpaqueToSpaceCollapsing(firstStrongIsolateCharacter); | 293 Append(firstStrongIsolateCharacter); |
| 281 AppendBidiControl(style, leftToRightOverrideCharacter, | 294 AppendBidiControl(style, leftToRightOverrideCharacter, |
| 282 rightToLeftOverrideCharacter); | 295 rightToLeftOverrideCharacter); |
| 283 Enter(node, popDirectionalIsolateCharacter); | 296 Enter(node, popDirectionalIsolateCharacter); |
| 284 Enter(node, popDirectionalFormattingCharacter); | 297 Enter(node, popDirectionalFormattingCharacter); |
| 285 break; | 298 break; |
| 286 } | 299 } |
| 287 } | 300 } |
| 288 | 301 |
| 289 void NGLayoutInlineItemsBuilder::Enter(LayoutObject* node, | 302 void NGLayoutInlineItemsBuilder::Enter(LayoutObject* node, |
| 290 UChar character_to_exit) { | 303 UChar character_to_exit) { |
| 291 exits_.push_back(OnExitNode{node, character_to_exit}); | 304 exits_.push_back(OnExitNode{node, character_to_exit}); |
| 292 has_bidi_controls_ = true; | 305 has_bidi_controls_ = true; |
| 293 } | 306 } |
| 294 | 307 |
| 295 void NGLayoutInlineItemsBuilder::ExitBlock() { | 308 void NGLayoutInlineItemsBuilder::ExitBlock() { |
| 296 Exit(nullptr); | 309 Exit(nullptr); |
| 297 } | 310 } |
| 298 | 311 |
| 299 void NGLayoutInlineItemsBuilder::ExitInline(LayoutObject* node) { | 312 void NGLayoutInlineItemsBuilder::ExitInline(LayoutObject* node) { |
| 300 DCHECK(node); | 313 DCHECK(node); |
| 301 Exit(node); | 314 Exit(node); |
| 302 } | 315 } |
| 303 | 316 |
| 304 void NGLayoutInlineItemsBuilder::Exit(LayoutObject* node) { | 317 void NGLayoutInlineItemsBuilder::Exit(LayoutObject* node) { |
| 305 while (!exits_.isEmpty() && exits_.back().node == node) { | 318 while (!exits_.isEmpty() && exits_.back().node == node) { |
| 306 AppendAsOpaqueToSpaceCollapsing(exits_.back().character); | 319 Append(exits_.back().character); |
| 307 exits_.pop_back(); | 320 exits_.pop_back(); |
| 308 } | 321 } |
| 309 } | 322 } |
| 310 | 323 |
| 311 } // namespace blink | 324 } // namespace blink |
| OLD | NEW |