Coverage Report

Created: 2018-09-25 14:53

/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
}