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 |