/src/mozilla-central/layout/generic/nsTextFrameUtils.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
2 | | /* vim: set ts=8 sts=2 et sw=2 tw=80: */ |
3 | | /* This Source Code Form is subject to the terms of the Mozilla Public |
4 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
5 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
6 | | |
7 | | #include "nsTextFrameUtils.h" |
8 | | |
9 | | #include "nsBidiUtils.h" |
10 | | #include "nsCharTraits.h" |
11 | | #include "nsIContent.h" |
12 | | #include "nsStyleStruct.h" |
13 | | #include "nsTextFragment.h" |
14 | | #include "nsUnicharUtils.h" |
15 | | #include <algorithm> |
16 | | |
17 | | using namespace mozilla; |
18 | | |
19 | | static bool |
20 | | IsDiscardable(char16_t ch, nsTextFrameUtils::Flags* aFlags) |
21 | 0 | { |
22 | 0 | // Unlike IS_DISCARDABLE, we don't discard \r. \r will be ignored by gfxTextRun |
23 | 0 | // and discarding it would force us to copy text in many cases of preformatted |
24 | 0 | // text containing \r\n. |
25 | 0 | if (ch == CH_SHY) { |
26 | 0 | *aFlags |= nsTextFrameUtils::Flags::TEXT_HAS_SHY; |
27 | 0 | return true; |
28 | 0 | } |
29 | 0 | return IsBidiControl(ch); |
30 | 0 | } |
31 | | |
32 | | static bool |
33 | | IsDiscardable(uint8_t ch, nsTextFrameUtils::Flags* aFlags) |
34 | 0 | { |
35 | 0 | if (ch == CH_SHY) { |
36 | 0 | *aFlags |= nsTextFrameUtils::Flags::TEXT_HAS_SHY; |
37 | 0 | return true; |
38 | 0 | } |
39 | 0 | return false; |
40 | 0 | } |
41 | | |
42 | | static bool |
43 | | IsSegmentBreak(char16_t aCh) |
44 | 0 | { |
45 | 0 | return aCh == '\n' || aCh == '\r'; |
46 | 0 | } |
47 | | |
48 | | static bool |
49 | | IsSpaceOrTab(char16_t aCh) |
50 | 0 | { |
51 | 0 | return aCh == ' ' || aCh == '\t'; |
52 | 0 | } |
53 | | |
54 | | static bool |
55 | | IsSpaceOrTabOrSegmentBreak(char16_t aCh) |
56 | 0 | { |
57 | 0 | return IsSpaceOrTab(aCh) || IsSegmentBreak(aCh); |
58 | 0 | } |
59 | | |
60 | | template<typename CharT> |
61 | | /* static */ bool |
62 | | nsTextFrameUtils::IsSkippableCharacterForTransformText(CharT aChar) |
63 | 0 | { |
64 | 0 | return aChar == ' ' || |
65 | 0 | aChar == '\t' || |
66 | 0 | aChar == '\n' || |
67 | 0 | aChar == CH_SHY || |
68 | 0 | (aChar > 0xFF && IsBidiControl(aChar)); |
69 | 0 | } Unexecuted instantiation: bool nsTextFrameUtils::IsSkippableCharacterForTransformText<unsigned char>(unsigned char) Unexecuted instantiation: bool nsTextFrameUtils::IsSkippableCharacterForTransformText<char16_t>(char16_t) |
70 | | |
71 | | #ifdef DEBUG |
72 | | template<typename CharT> |
73 | | static void AssertSkippedExpectedChars(const CharT* aText, |
74 | | const gfxSkipChars& aSkipChars, |
75 | | int32_t aSkipCharsOffset) |
76 | | { |
77 | | gfxSkipCharsIterator it(aSkipChars); |
78 | | it.AdvanceOriginal(aSkipCharsOffset); |
79 | | while (it.GetOriginalOffset() < it.GetOriginalEnd()) { |
80 | | CharT ch = aText[it.GetOriginalOffset() - aSkipCharsOffset]; |
81 | | MOZ_ASSERT(!it.IsOriginalCharSkipped() || |
82 | | nsTextFrameUtils::IsSkippableCharacterForTransformText(ch), |
83 | | "skipped unexpected character; need to update " |
84 | | "IsSkippableCharacterForTransformText?"); |
85 | | it.AdvanceOriginal(1); |
86 | | } |
87 | | } |
88 | | #endif |
89 | | |
90 | | template<class CharT> |
91 | | static CharT* |
92 | | TransformWhiteSpaces(const CharT* aText, uint32_t aLength, |
93 | | uint32_t aBegin, uint32_t aEnd, |
94 | | bool aHasSegmentBreak, |
95 | | bool& aInWhitespace, |
96 | | CharT* aOutput, |
97 | | nsTextFrameUtils::Flags& aFlags, |
98 | | nsTextFrameUtils::CompressionMode aCompression, |
99 | | gfxSkipChars* aSkipChars) |
100 | 0 | { |
101 | 0 | MOZ_ASSERT(aCompression == nsTextFrameUtils::COMPRESS_WHITESPACE || |
102 | 0 | aCompression == nsTextFrameUtils::COMPRESS_WHITESPACE_NEWLINE, |
103 | 0 | "whitespaces should be skippable!!"); |
104 | 0 | // Get the context preceding/following this white space range. |
105 | 0 | // For 8-bit text (sizeof CharT == 1), the checks here should get optimized |
106 | 0 | // out, and isSegmentBreakSkippable should be initialized to be 'false'. |
107 | 0 | bool isSegmentBreakSkippable = |
108 | 0 | sizeof(CharT) > 1 && |
109 | 0 | ((aBegin > 0 && IS_ZERO_WIDTH_SPACE(aText[aBegin - 1])) || |
110 | 0 | (aEnd < aLength && IS_ZERO_WIDTH_SPACE(aText[aEnd]))); |
111 | 0 | if (sizeof(CharT) > 1 && !isSegmentBreakSkippable && |
112 | 0 | aBegin > 0 && aEnd < aLength) { |
113 | 0 | uint32_t ucs4before; |
114 | 0 | uint32_t ucs4after; |
115 | 0 | if (aBegin > 1 && |
116 | 0 | NS_IS_LOW_SURROGATE(aText[aBegin - 1]) && |
117 | 0 | NS_IS_HIGH_SURROGATE(aText[aBegin - 2])) { |
118 | 0 | ucs4before = SURROGATE_TO_UCS4(aText[aBegin - 2], aText[aBegin - 1]); |
119 | 0 | } else { |
120 | 0 | ucs4before = aText[aBegin - 1]; |
121 | 0 | } |
122 | 0 | if (aEnd + 1 < aLength && |
123 | 0 | NS_IS_HIGH_SURROGATE(aText[aEnd]) && |
124 | 0 | NS_IS_LOW_SURROGATE(aText[aEnd + 1])) { |
125 | 0 | ucs4after = SURROGATE_TO_UCS4(aText[aEnd], aText[aEnd + 1]); |
126 | 0 | } else { |
127 | 0 | ucs4after = aText[aEnd]; |
128 | 0 | } |
129 | 0 | // Discard newlines between characters that have F, W, or H |
130 | 0 | // EastAsianWidth property and neither side is Hangul. |
131 | 0 | isSegmentBreakSkippable = IsSegmentBreakSkipChar(ucs4before) && |
132 | 0 | IsSegmentBreakSkipChar(ucs4after); |
133 | 0 | } |
134 | 0 |
|
135 | 0 | for (uint32_t i = aBegin; i < aEnd; ++i) { |
136 | 0 | CharT ch = aText[i]; |
137 | 0 | bool keepChar = false; |
138 | 0 | bool keepTransformedWhiteSpace = false; |
139 | 0 | if (IsDiscardable(ch, &aFlags)) { |
140 | 0 | aSkipChars->SkipChar(); |
141 | 0 | continue; |
142 | 0 | } |
143 | 0 | if (IsSpaceOrTab(ch)) { |
144 | 0 | if (aHasSegmentBreak) { |
145 | 0 | // If white-space is set to normal, nowrap, or pre-line, white space |
146 | 0 | // characters are considered collapsible and all spaces and tabs |
147 | 0 | // immediately preceding or following a segment break are removed. |
148 | 0 | aSkipChars->SkipChar(); |
149 | 0 | continue; |
150 | 0 | } |
151 | 0 | |
152 | 0 | if (aInWhitespace) { |
153 | 0 | aSkipChars->SkipChar(); |
154 | 0 | continue; |
155 | 0 | } else { |
156 | 0 | keepTransformedWhiteSpace = true; |
157 | 0 | } |
158 | 0 | } else { |
159 | 0 | // Apply Segment Break Transformation Rules (CSS Text 3 - 4.1.2) for |
160 | 0 | // segment break characters. |
161 | 0 | if (aCompression == nsTextFrameUtils::COMPRESS_WHITESPACE || |
162 | 0 | // XXX: According to CSS Text 3, a lone CR should not always be |
163 | 0 | // kept, but still go through the Segment Break Transformation |
164 | 0 | // Rules. However, this is what current modern browser engines |
165 | 0 | // (webkit/blink/edge) do. So, once we can get some clarity |
166 | 0 | // from the specification issue, we should either remove the |
167 | 0 | // lone CR condition here, or leave it here with this comment |
168 | 0 | // being rephrased. |
169 | 0 | // Please see https://github.com/w3c/csswg-drafts/issues/855. |
170 | 0 | ch == '\r') { |
171 | 0 | keepChar = true; |
172 | 0 | } else { |
173 | 0 | // aCompression == COMPRESS_WHITESPACE_NEWLINE |
174 | 0 |
|
175 | 0 | // Any collapsible segment break immediately following another |
176 | 0 | // collapsible segment break is removed. Then the remaining segment |
177 | 0 | // break is either transformed into a space (U+0020) or removed |
178 | 0 | // depending on the context before and after the break. |
179 | 0 | if (isSegmentBreakSkippable || aInWhitespace) { |
180 | 0 | aSkipChars->SkipChar(); |
181 | 0 | continue; |
182 | 0 | } |
183 | 0 | isSegmentBreakSkippable = true; |
184 | 0 | keepTransformedWhiteSpace = true; |
185 | 0 | } |
186 | 0 | } |
187 | 0 |
|
188 | 0 | if (keepChar) { |
189 | 0 | *aOutput++ = ch; |
190 | 0 | aSkipChars->KeepChar(); |
191 | 0 | aInWhitespace = IsSpaceOrTab(ch); |
192 | 0 | } else if (keepTransformedWhiteSpace) { |
193 | 0 | *aOutput++ = ' '; |
194 | 0 | aSkipChars->KeepChar(); |
195 | 0 | aInWhitespace = true; |
196 | 0 | } else { |
197 | 0 | MOZ_ASSERT_UNREACHABLE("Should've skipped the character!!"); |
198 | 0 | } |
199 | 0 | } |
200 | 0 | return aOutput; |
201 | 0 | } Unexecuted instantiation: Unified_cpp_layout_generic3.cpp:unsigned char* TransformWhiteSpaces<unsigned char>(unsigned char const*, unsigned int, unsigned int, unsigned int, bool, bool&, unsigned char*, nsTextFrameUtils::Flags&, nsTextFrameUtils::CompressionMode, gfxSkipChars*) Unexecuted instantiation: Unified_cpp_layout_generic3.cpp:char16_t* TransformWhiteSpaces<char16_t>(char16_t const*, unsigned int, unsigned int, unsigned int, bool, bool&, char16_t*, nsTextFrameUtils::Flags&, nsTextFrameUtils::CompressionMode, gfxSkipChars*) |
202 | | |
203 | | template<class CharT> |
204 | | CharT* |
205 | | nsTextFrameUtils::TransformText(const CharT* aText, uint32_t aLength, |
206 | | CharT* aOutput, |
207 | | CompressionMode aCompression, |
208 | | uint8_t* aIncomingFlags, |
209 | | gfxSkipChars* aSkipChars, |
210 | | Flags* aAnalysisFlags) |
211 | 0 | { |
212 | 0 | Flags flags = Flags(); |
213 | | #ifdef DEBUG |
214 | | int32_t skipCharsOffset = aSkipChars->GetOriginalCharCount(); |
215 | | #endif |
216 | |
|
217 | 0 | bool lastCharArabic = false; |
218 | 0 | if (aCompression == COMPRESS_NONE || |
219 | 0 | aCompression == COMPRESS_NONE_TRANSFORM_TO_SPACE) { |
220 | 0 | // Skip discardables. |
221 | 0 | uint32_t i; |
222 | 0 | for (i = 0; i < aLength; ++i) { |
223 | 0 | CharT ch = aText[i]; |
224 | 0 | if (IsDiscardable(ch, &flags)) { |
225 | 0 | aSkipChars->SkipChar(); |
226 | 0 | } else { |
227 | 0 | aSkipChars->KeepChar(); |
228 | 0 | if (ch > ' ') { |
229 | 0 | lastCharArabic = IS_ARABIC_CHAR(ch); |
230 | 0 | } else if (aCompression == COMPRESS_NONE_TRANSFORM_TO_SPACE) { |
231 | 0 | if (ch == '\t' || ch == '\n') { |
232 | 0 | ch = ' '; |
233 | 0 | } |
234 | 0 | } else { |
235 | 0 | // aCompression == COMPRESS_NONE |
236 | 0 | if (ch == '\t') { |
237 | 0 | flags |= Flags::TEXT_HAS_TAB; |
238 | 0 | } |
239 | 0 | } |
240 | 0 | *aOutput++ = ch; |
241 | 0 | } |
242 | 0 | } |
243 | 0 | if (lastCharArabic) { |
244 | 0 | *aIncomingFlags |= INCOMING_ARABICCHAR; |
245 | 0 | } else { |
246 | 0 | *aIncomingFlags &= ~INCOMING_ARABICCHAR; |
247 | 0 | } |
248 | 0 | *aIncomingFlags &= ~INCOMING_WHITESPACE; |
249 | 0 | } else { |
250 | 0 | bool inWhitespace = (*aIncomingFlags & INCOMING_WHITESPACE) != 0; |
251 | 0 | uint32_t i; |
252 | 0 | for (i = 0; i < aLength; ++i) { |
253 | 0 | CharT ch = aText[i]; |
254 | 0 | // CSS Text 3 - 4.1. The White Space Processing Rules |
255 | 0 | // White space processing in CSS affects only the document white space |
256 | 0 | // characters: spaces (U+0020), tabs (U+0009), and segment breaks. |
257 | 0 | // Since we need the context of segment breaks and their surrounding |
258 | 0 | // white spaces to proceed the white space processing, a consecutive run |
259 | 0 | // of spaces/tabs/segment breaks is collected in a first pass loop, then |
260 | 0 | // we apply the collapsing and transformation rules to this run in a |
261 | 0 | // second pass loop. |
262 | 0 | if (IsSpaceOrTabOrSegmentBreak(ch)) { |
263 | 0 | bool keepLastSpace = false; |
264 | 0 | bool hasSegmentBreak = IsSegmentBreak(ch); |
265 | 0 | uint32_t countTrailingDiscardables = 0; |
266 | 0 | uint32_t j; |
267 | 0 | for (j = i + 1; j < aLength && |
268 | 0 | (IsSpaceOrTabOrSegmentBreak(aText[j]) || |
269 | 0 | IsDiscardable(aText[j], &flags)); |
270 | 0 | j++) { |
271 | 0 | if (IsSegmentBreak(aText[j])) { |
272 | 0 | hasSegmentBreak = true; |
273 | 0 | } |
274 | 0 | } |
275 | 0 | // Exclude trailing discardables before checking space combining |
276 | 0 | // sequence tail. |
277 | 0 | for (; IsDiscardable(aText[j - 1], &flags); j--) { |
278 | 0 | countTrailingDiscardables++; |
279 | 0 | } |
280 | 0 | // If the last white space is followed by a combining sequence tail, |
281 | 0 | // exclude it from the range of TransformWhiteSpaces. |
282 | 0 | if (sizeof(CharT) > 1 && aText[j - 1] == ' ' && j < aLength && |
283 | 0 | IsSpaceCombiningSequenceTail(&aText[j], aLength - j)) { |
284 | 0 | keepLastSpace = true; |
285 | 0 | j--; |
286 | 0 | } |
287 | 0 | if (j > i) { |
288 | 0 | aOutput = TransformWhiteSpaces(aText, aLength, i, j, hasSegmentBreak, |
289 | 0 | inWhitespace, aOutput, flags, |
290 | 0 | aCompression, aSkipChars); |
291 | 0 | } |
292 | 0 | // We need to keep KeepChar()/SkipChar() in order, so process the |
293 | 0 | // last white space first, then process the trailing discardables. |
294 | 0 | if (keepLastSpace) { |
295 | 0 | keepLastSpace = false; |
296 | 0 | *aOutput++ = ' '; |
297 | 0 | aSkipChars->KeepChar(); |
298 | 0 | lastCharArabic = false; |
299 | 0 | j++; |
300 | 0 | } |
301 | 0 | for (; countTrailingDiscardables > 0; countTrailingDiscardables--) { |
302 | 0 | aSkipChars->SkipChar(); |
303 | 0 | j++; |
304 | 0 | } |
305 | 0 | i = j - 1; |
306 | 0 | continue; |
307 | 0 | } |
308 | 0 | // Process characters other than the document white space characters. |
309 | 0 | if (IsDiscardable(ch, &flags)) { |
310 | 0 | aSkipChars->SkipChar(); |
311 | 0 | } else { |
312 | 0 | *aOutput++ = ch; |
313 | 0 | aSkipChars->KeepChar(); |
314 | 0 | } |
315 | 0 | lastCharArabic = IS_ARABIC_CHAR(ch); |
316 | 0 | inWhitespace = false; |
317 | 0 | } |
318 | 0 |
|
319 | 0 | if (lastCharArabic) { |
320 | 0 | *aIncomingFlags |= INCOMING_ARABICCHAR; |
321 | 0 | } else { |
322 | 0 | *aIncomingFlags &= ~INCOMING_ARABICCHAR; |
323 | 0 | } |
324 | 0 | if (inWhitespace) { |
325 | 0 | *aIncomingFlags |= INCOMING_WHITESPACE; |
326 | 0 | } else { |
327 | 0 | *aIncomingFlags &= ~INCOMING_WHITESPACE; |
328 | 0 | } |
329 | 0 | } |
330 | 0 |
|
331 | 0 | *aAnalysisFlags = flags; |
332 | 0 |
|
333 | | #ifdef DEBUG |
334 | | AssertSkippedExpectedChars(aText, *aSkipChars, skipCharsOffset); |
335 | | #endif |
336 | | return aOutput; |
337 | 0 | } Unexecuted instantiation: unsigned char* nsTextFrameUtils::TransformText<unsigned char>(unsigned char const*, unsigned int, unsigned char*, nsTextFrameUtils::CompressionMode, unsigned char*, gfxSkipChars*, nsTextFrameUtils::Flags*) Unexecuted instantiation: char16_t* nsTextFrameUtils::TransformText<char16_t>(char16_t const*, unsigned int, char16_t*, nsTextFrameUtils::CompressionMode, unsigned char*, gfxSkipChars*, nsTextFrameUtils::Flags*) |
338 | | |
339 | | /* |
340 | | * NOTE: The TransformText and IsSkippableCharacterForTransformText template |
341 | | * functions are part of the public API of nsTextFrameUtils, while |
342 | | * their function bodies are not available in the header. They may stop working |
343 | | * (fail to resolve symbol in link time) once their callsites are moved to a |
344 | | * different translation unit (e.g. a different unified source file). |
345 | | * Explicit instantiating this function template with `uint8_t` and `char16_t` |
346 | | * could prevent us from the potential risk. |
347 | | */ |
348 | | template uint8_t* |
349 | | nsTextFrameUtils::TransformText(const uint8_t* aText, uint32_t aLength, |
350 | | uint8_t* aOutput, |
351 | | CompressionMode aCompression, |
352 | | uint8_t* aIncomingFlags, |
353 | | gfxSkipChars* aSkipChars, |
354 | | Flags* aAnalysisFlags); |
355 | | template char16_t* |
356 | | nsTextFrameUtils::TransformText(const char16_t* aText, uint32_t aLength, |
357 | | char16_t* aOutput, |
358 | | CompressionMode aCompression, |
359 | | uint8_t* aIncomingFlags, |
360 | | gfxSkipChars* aSkipChars, |
361 | | Flags* aAnalysisFlags); |
362 | | template bool |
363 | | nsTextFrameUtils::IsSkippableCharacterForTransformText(uint8_t aChar); |
364 | | template bool |
365 | | nsTextFrameUtils::IsSkippableCharacterForTransformText(char16_t aChar); |
366 | | |
367 | | uint32_t |
368 | | nsTextFrameUtils::ComputeApproximateLengthWithWhitespaceCompression( |
369 | | nsIContent *aContent, const nsStyleText *aStyleText) |
370 | 0 | { |
371 | 0 | const nsTextFragment *frag = aContent->GetText(); |
372 | 0 | // This is an approximation so we don't really need anything |
373 | 0 | // too fancy here. |
374 | 0 | uint32_t len; |
375 | 0 | if (aStyleText->WhiteSpaceIsSignificant()) { |
376 | 0 | len = frag->GetLength(); |
377 | 0 | } else { |
378 | 0 | bool is2b = frag->Is2b(); |
379 | 0 | union { |
380 | 0 | const char *s1b; |
381 | 0 | const char16_t *s2b; |
382 | 0 | } u; |
383 | 0 | if (is2b) { |
384 | 0 | u.s2b = frag->Get2b(); |
385 | 0 | } else { |
386 | 0 | u.s1b = frag->Get1b(); |
387 | 0 | } |
388 | 0 | bool prevWS = true; // more important to ignore blocks with |
389 | 0 | // only whitespace than get inline boundaries |
390 | 0 | // exactly right |
391 | 0 | len = 0; |
392 | 0 | for (uint32_t i = 0, i_end = frag->GetLength(); i < i_end; ++i) { |
393 | 0 | char16_t c = is2b ? u.s2b[i] : u.s1b[i]; |
394 | 0 | if (c == ' ' || c == '\n' || c == '\t' || c == '\r') { |
395 | 0 | if (!prevWS) { |
396 | 0 | ++len; |
397 | 0 | } |
398 | 0 | prevWS = true; |
399 | 0 | } else { |
400 | 0 | ++len; |
401 | 0 | prevWS = false; |
402 | 0 | } |
403 | 0 | } |
404 | 0 | } |
405 | 0 | return len; |
406 | 0 | } |
407 | | |
408 | 0 | bool nsSkipCharsRunIterator::NextRun() { |
409 | 0 | do { |
410 | 0 | if (mRunLength) { |
411 | 0 | mIterator.AdvanceOriginal(mRunLength); |
412 | 0 | NS_ASSERTION(mRunLength > 0, "No characters in run (initial length too large?)"); |
413 | 0 | if (!mSkipped || mLengthIncludesSkipped) { |
414 | 0 | mRemainingLength -= mRunLength; |
415 | 0 | } |
416 | 0 | } |
417 | 0 | if (!mRemainingLength) |
418 | 0 | return false; |
419 | 0 | int32_t length; |
420 | 0 | mSkipped = mIterator.IsOriginalCharSkipped(&length); |
421 | 0 | mRunLength = std::min(length, mRemainingLength); |
422 | 0 | } while (!mVisitSkipped && mSkipped); |
423 | 0 |
|
424 | 0 | return true; |
425 | 0 | } |