Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(23)

Side by Side Diff: third_party/WebKit/Source/core/layout/ng/ng_layout_inline_items_builder.cc

Issue 2749013003: [LayoutNG] Fix whitespace collapsing when a node is a newline (Closed)
Patch Set: Created 3 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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
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
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
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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698