Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/layout/generic/nsTextFrame.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
/* rendering object for textual content of elements */
8
9
#include "nsTextFrame.h"
10
11
#include "gfx2DGlue.h"
12
#include "gfxPrefs.h"
13
#include "gfxUtils.h"
14
#include "mozilla/Attributes.h"
15
#include "mozilla/ComputedStyle.h"
16
#include "mozilla/DebugOnly.h"
17
#include "mozilla/gfx/2D.h"
18
#include "mozilla/Likely.h"
19
#include "mozilla/MathAlgorithms.h"
20
#include "mozilla/StaticPrefs.h"
21
#include "mozilla/TextEvents.h"
22
#include "mozilla/BinarySearch.h"
23
#include "mozilla/IntegerRange.h"
24
#include "mozilla/Unused.h"
25
#include "mozilla/PodOperations.h"
26
27
#include "nsCOMPtr.h"
28
#include "nsBlockFrame.h"
29
#include "nsFontMetrics.h"
30
#include "nsSplittableFrame.h"
31
#include "nsLineLayout.h"
32
#include "nsString.h"
33
#include "nsUnicharUtils.h"
34
#include "nsPresContext.h"
35
#include "nsIContent.h"
36
#include "nsStyleConsts.h"
37
#include "nsStyleStruct.h"
38
#include "nsStyleStructInlines.h"
39
#include "SVGTextFrame.h"
40
#include "nsCoord.h"
41
#include "gfxContext.h"
42
#include "nsIPresShell.h"
43
#include "nsTArray.h"
44
#include "nsCSSPseudoElements.h"
45
#include "nsCSSFrameConstructor.h"
46
#include "nsCompatibility.h"
47
#include "nsCSSColorUtils.h"
48
#include "nsLayoutUtils.h"
49
#include "nsDisplayList.h"
50
#include "nsFrame.h"
51
#include "nsIMathMLFrame.h"
52
#include "nsPlaceholderFrame.h"
53
#include "nsTextFrameUtils.h"
54
#include "nsTextRunTransformations.h"
55
#include "MathMLTextRunFactory.h"
56
#include "nsUnicodeProperties.h"
57
#include "nsStyleUtil.h"
58
#include "nsRubyFrame.h"
59
#include "TextDrawTarget.h"
60
61
#include "nsTextFragment.h"
62
#include "nsGkAtoms.h"
63
#include "nsFrameSelection.h"
64
#include "nsRange.h"
65
#include "nsCSSRendering.h"
66
#include "nsContentUtils.h"
67
#include "nsLineBreaker.h"
68
#include "nsIFrameInlines.h"
69
#include "mozilla/intl/WordBreaker.h"
70
#include "mozilla/ServoStyleSet.h"
71
#include "mozilla/layers/LayersMessages.h"
72
#include "mozilla/layers/WebRenderLayerManager.h"
73
#include "mozilla/layers/WebRenderBridgeChild.h"
74
#include "mozilla/webrender/WebRenderAPI.h"
75
#include "mozilla/layers/StackingContextHelper.h"
76
77
#include <algorithm>
78
#include <limits>
79
#ifdef ACCESSIBILITY
80
#include "nsAccessibilityService.h"
81
#endif
82
83
#include "nsPrintfCString.h"
84
85
#include "gfxContext.h"
86
#include "mozilla/gfx/DrawTargetRecording.h"
87
88
#include "mozilla/UniquePtr.h"
89
#include "mozilla/dom/Element.h"
90
#include "mozilla/LookAndFeel.h"
91
92
#include "GeckoProfiler.h"
93
94
#ifdef DEBUG
95
#undef NOISY_REFLOW
96
#undef NOISY_TRIM
97
#else
98
#undef NOISY_REFLOW
99
#undef NOISY_TRIM
100
#endif
101
102
#ifdef DrawText
103
#undef DrawText
104
#endif
105
106
using namespace mozilla;
107
using namespace mozilla::dom;
108
using namespace mozilla::gfx;
109
using namespace mozilla::layers;
110
111
typedef mozilla::layout::TextDrawTarget TextDrawTarget;
112
113
struct TabWidth {
114
  TabWidth(uint32_t aOffset, uint32_t aWidth)
115
    : mOffset(aOffset), mWidth(float(aWidth))
116
0
  { }
117
118
  uint32_t mOffset; // DOM offset relative to the current frame's offset.
119
  float    mWidth;  // extra space to be added at this position (in app units)
120
};
121
122
struct TabWidthStore {
123
  explicit TabWidthStore(int32_t aValidForContentOffset)
124
    : mLimit(0)
125
    , mValidForContentOffset(aValidForContentOffset)
126
0
  { }
127
128
  // Apply tab widths to the aSpacing array, which corresponds to characters
129
  // beginning at aOffset and has length aLength. (Width records outside this
130
  // range will be ignored.)
131
  void ApplySpacing(gfxTextRun::PropertyProvider::Spacing *aSpacing,
132
                    uint32_t aOffset, uint32_t aLength);
133
134
  // Offset up to which tabs have been measured; positions beyond this have not
135
  // been calculated yet but may be appended if needed later.  It's a DOM
136
  // offset relative to the current frame's offset.
137
  uint32_t mLimit;
138
139
  // Need to recalc tab offsets if frame content offset differs from this.
140
  int32_t mValidForContentOffset;
141
142
  // A TabWidth record for each tab character measured so far.
143
  nsTArray<TabWidth> mWidths;
144
};
145
146
namespace {
147
148
struct TabwidthAdaptor
149
{
150
  const nsTArray<TabWidth>& mWidths;
151
  explicit TabwidthAdaptor(const nsTArray<TabWidth>& aWidths)
152
0
    : mWidths(aWidths) {}
153
0
  uint32_t operator[](size_t aIdx) const {
154
0
    return mWidths[aIdx].mOffset;
155
0
  }
156
};
157
158
} // namespace
159
160
void
161
TabWidthStore::ApplySpacing(gfxTextRun::PropertyProvider::Spacing *aSpacing,
162
                            uint32_t aOffset, uint32_t aLength)
163
0
{
164
0
  size_t i = 0;
165
0
  const size_t len = mWidths.Length();
166
0
167
0
  // If aOffset is non-zero, do a binary search to find where to start
168
0
  // processing the tab widths, in case the list is really long. (See bug
169
0
  // 953247.)
170
0
  // We need to start from the first entry where mOffset >= aOffset.
171
0
  if (aOffset > 0) {
172
0
    mozilla::BinarySearch(TabwidthAdaptor(mWidths), 0, len, aOffset, &i);
173
0
  }
174
0
175
0
  uint32_t limit = aOffset + aLength;
176
0
  while (i < len) {
177
0
    const TabWidth& tw = mWidths[i];
178
0
    if (tw.mOffset >= limit) {
179
0
      break;
180
0
    }
181
0
    aSpacing[tw.mOffset - aOffset].mAfter += tw.mWidth;
182
0
    i++;
183
0
  }
184
0
}
185
186
NS_DECLARE_FRAME_PROPERTY_DELETABLE(TabWidthProperty, TabWidthStore)
187
188
NS_DECLARE_FRAME_PROPERTY_WITHOUT_DTOR(OffsetToFrameProperty, nsTextFrame)
189
190
NS_DECLARE_FRAME_PROPERTY_RELEASABLE(UninflatedTextRunProperty, gfxTextRun)
191
192
NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(FontSizeInflationProperty, float)
193
194
/**
195
 * A glyph observer for the change of a font glyph in a text run.
196
 *
197
 * This is stored in {Simple, Complex}TextRunUserData.
198
 */
199
class GlyphObserver final : public gfxFont::GlyphChangeObserver
200
{
201
public:
202
  GlyphObserver(gfxFont* aFont, gfxTextRun* aTextRun)
203
0
    : gfxFont::GlyphChangeObserver(aFont), mTextRun(aTextRun) {
204
0
    MOZ_ASSERT(aTextRun->GetUserData());
205
0
  }
206
  void NotifyGlyphsChanged() override;
207
private:
208
  gfxTextRun* mTextRun;
209
};
210
211
static const nsFrameState TEXT_REFLOW_FLAGS =
212
   TEXT_FIRST_LETTER |
213
   TEXT_START_OF_LINE |
214
   TEXT_END_OF_LINE |
215
   TEXT_HYPHEN_BREAK |
216
   TEXT_TRIMMED_TRAILING_WHITESPACE |
217
   TEXT_JUSTIFICATION_ENABLED |
218
   TEXT_HAS_NONCOLLAPSED_CHARACTERS |
219
   TEXT_SELECTION_UNDERLINE_OVERFLOWED |
220
   TEXT_NO_RENDERED_GLYPHS;
221
222
static const nsFrameState TEXT_WHITESPACE_FLAGS =
223
    TEXT_IS_ONLY_WHITESPACE |
224
    TEXT_ISNOT_ONLY_WHITESPACE;
225
226
/*
227
 * Some general notes
228
 *
229
 * Text frames delegate work to gfxTextRun objects. The gfxTextRun object
230
 * transforms text to positioned glyphs. It can report the geometry of the
231
 * glyphs and paint them. Text frames configure gfxTextRuns by providing text,
232
 * spacing, language, and other information.
233
 *
234
 * A gfxTextRun can cover more than one DOM text node. This is necessary to
235
 * get kerning, ligatures and shaping for text that spans multiple text nodes
236
 * but is all the same font.
237
 *
238
 * The userdata for a gfxTextRun object can be:
239
 *
240
 *   - A nsTextFrame* in the case a text run maps to only one flow. In this
241
 *   case, the textrun's user data pointer is a pointer to mStartFrame for that
242
 *   flow, mDOMOffsetToBeforeTransformOffset is zero, and mContentLength is the
243
 *   length of the text node.
244
 *
245
 *   - A SimpleTextRunUserData in the case a text run maps to one flow, but we
246
 *   still have to keep a list of glyph observers.
247
 *
248
 *   - A ComplexTextRunUserData in the case a text run maps to multiple flows,
249
 *   but we need to keep a list of glyph observers.
250
 *
251
 *   - A TextRunUserData in the case a text run maps multiple flows, but it
252
 *   doesn't have any glyph observer for changes in SVG fonts.
253
 *
254
 * You can differentiate between the four different cases with the
255
 * TEXT_IS_SIMPLE_FLOW and TEXT_MIGHT_HAVE_GLYPH_CHANGES flags.
256
 *
257
 * We go to considerable effort to make sure things work even if in-flow
258
 * siblings have different ComputedStyles (i.e., first-letter and first-line).
259
 *
260
 * Our convention is that unsigned integer character offsets are offsets into
261
 * the transformed string. Signed integer character offsets are offsets into
262
 * the DOM string.
263
 *
264
 * XXX currently we don't handle hyphenated breaks between text frames where the
265
 * hyphen occurs at the end of the first text frame, e.g.
266
 *   <b>Kit&shy;</b>ty
267
 */
268
269
/**
270
 * This is our user data for the textrun, when textRun->GetFlags2() has
271
 * TEXT_IS_SIMPLE_FLOW set, and also TEXT_MIGHT_HAVE_GLYPH_CHANGES.
272
 *
273
 * This allows having an array of observers if there are fonts whose glyphs
274
 * might change, but also avoid allocation in the simple case that there aren't.
275
 */
276
struct SimpleTextRunUserData {
277
  nsTArray<UniquePtr<GlyphObserver>> mGlyphObservers;
278
  nsTextFrame* mFrame;
279
  explicit SimpleTextRunUserData(nsTextFrame* aFrame)
280
    : mFrame(aFrame)
281
0
  {
282
0
  }
283
};
284
285
/**
286
 * We use an array of these objects to record which text frames
287
 * are associated with the textrun. mStartFrame is the start of a list of
288
 * text frames. Some sequence of its continuations are covered by the textrun.
289
 * A content textnode can have at most one TextRunMappedFlow associated with it
290
 * for a given textrun.
291
 *
292
 * mDOMOffsetToBeforeTransformOffset is added to DOM offsets for those frames to obtain
293
 * the offset into the before-transformation text of the textrun. It can be
294
 * positive (when a text node starts in the middle of a text run) or
295
 * negative (when a text run starts in the middle of a text node). Of course
296
 * it can also be zero.
297
 */
298
struct TextRunMappedFlow {
299
  nsTextFrame* mStartFrame;
300
  int32_t      mDOMOffsetToBeforeTransformOffset;
301
  // The text mapped starts at mStartFrame->GetContentOffset() and is this long
302
  uint32_t     mContentLength;
303
};
304
305
/**
306
 * This is the type in the gfxTextRun's userdata field in the common case that
307
 * the text run maps to multiple flows, but no fonts have been found with
308
 * animatable glyphs.
309
 *
310
 * This way, we avoid allocating and constructing the extra nsTArray.
311
 */
312
struct TextRunUserData {
313
#ifdef DEBUG
314
  TextRunMappedFlow* mMappedFlows;
315
#endif
316
  uint32_t           mMappedFlowCount;
317
  uint32_t           mLastFlowIndex;
318
};
319
320
/**
321
 * This is our user data for the textrun, when textRun->GetFlags2() does not
322
 * have TEXT_IS_SIMPLE_FLOW set and has the TEXT_MIGHT HAVE_GLYPH_CHANGES flag.
323
 */
324
struct ComplexTextRunUserData : public TextRunUserData {
325
  nsTArray<UniquePtr<GlyphObserver>> mGlyphObservers;
326
};
327
328
/**
329
 * This helper object computes colors used for painting, and also IME
330
 * underline information. The data is computed lazily and cached as necessary.
331
 * These live for just the duration of one paint operation.
332
 */
333
class nsTextPaintStyle {
334
public:
335
  explicit nsTextPaintStyle(nsTextFrame* aFrame);
336
337
0
  void SetResolveColors(bool aResolveColors) {
338
0
    mResolveColors = aResolveColors;
339
0
  }
340
341
  nscolor GetTextColor();
342
343
  // SVG text has its own painting process, so we should never get its stroke
344
  // property from here.
345
0
  nscolor GetWebkitTextStrokeColor() {
346
0
    if (nsSVGUtils::IsInSVGTextSubtree(mFrame)) {
347
0
      return 0;
348
0
    }
349
0
    return mFrame->StyleText()->mWebkitTextStrokeColor.CalcColor(mFrame);
350
0
  }
351
0
  float GetWebkitTextStrokeWidth() {
352
0
    if (nsSVGUtils::IsInSVGTextSubtree(mFrame)) {
353
0
      return 0.0f;
354
0
    }
355
0
    nscoord coord = mFrame->StyleText()->mWebkitTextStrokeWidth;
356
0
    return mFrame->PresContext()->AppUnitsToFloatDevPixels(coord);
357
0
  }
358
359
  /**
360
   * Compute the colors for normally-selected text. Returns false if
361
   * the normal selection is not being displayed.
362
   */
363
  bool GetSelectionColors(nscolor* aForeColor,
364
                            nscolor* aBackColor);
365
  void GetHighlightColors(nscolor* aForeColor,
366
                          nscolor* aBackColor);
367
  void GetURLSecondaryColor(nscolor* aForeColor);
368
  void GetIMESelectionColors(int32_t  aIndex,
369
                             nscolor* aForeColor,
370
                             nscolor* aBackColor);
371
  // if this returns false, we don't need to draw underline.
372
  bool GetSelectionUnderlineForPaint(int32_t  aIndex,
373
                                       nscolor* aLineColor,
374
                                       float*   aRelativeSize,
375
                                       uint8_t* aStyle);
376
377
  // if this returns false, we don't need to draw underline.
378
  static bool GetSelectionUnderline(nsPresContext* aPresContext,
379
                                      int32_t aIndex,
380
                                      nscolor* aLineColor,
381
                                      float* aRelativeSize,
382
                                      uint8_t* aStyle);
383
384
  // if this returns false, no text-shadow was specified for the selection
385
  // and the *aShadow parameter was not modified.
386
  bool GetSelectionShadow(nsCSSShadowArray** aShadow);
387
388
0
  nsPresContext* PresContext() const { return mPresContext; }
389
390
  enum {
391
    eIndexRawInput = 0,
392
    eIndexSelRawText,
393
    eIndexConvText,
394
    eIndexSelConvText,
395
    eIndexSpellChecker
396
  };
397
398
  static int32_t GetUnderlineStyleIndexForSelectionType(
399
                   SelectionType aSelectionType)
400
0
  {
401
0
    switch (aSelectionType) {
402
0
      case SelectionType::eIMERawClause:
403
0
        return eIndexRawInput;
404
0
      case SelectionType::eIMESelectedRawClause:
405
0
        return eIndexSelRawText;
406
0
      case SelectionType::eIMEConvertedClause:
407
0
        return eIndexConvText;
408
0
      case SelectionType::eIMESelectedClause:
409
0
        return eIndexSelConvText;
410
0
      case SelectionType::eSpellCheck:
411
0
        return eIndexSpellChecker;
412
0
      default:
413
0
        NS_WARNING("non-IME selection type");
414
0
        return eIndexRawInput;
415
0
    }
416
0
  }
417
418
  nscolor GetSystemFieldForegroundColor();
419
  nscolor GetSystemFieldBackgroundColor();
420
421
protected:
422
  nsTextFrame*   mFrame;
423
  nsPresContext* mPresContext;
424
  bool           mInitCommonColors;
425
  bool           mInitSelectionColorsAndShadow;
426
  bool           mResolveColors;
427
428
  // Selection data
429
430
  nscolor      mSelectionTextColor;
431
  nscolor      mSelectionBGColor;
432
  RefPtr<nsCSSShadowArray> mSelectionShadow;
433
  bool                       mHasSelectionShadow;
434
435
  // Common data
436
437
  int32_t mSufficientContrast;
438
  nscolor mFrameBackgroundColor;
439
  nscolor mSystemFieldForegroundColor;
440
  nscolor mSystemFieldBackgroundColor;
441
442
  // selection colors and underline info, the colors are resolved colors if
443
  // mResolveColors is true (which is the default), i.e., the foreground color
444
  // and background color are swapped if it's needed. And also line color will
445
  // be resolved from them.
446
  struct nsSelectionStyle {
447
    bool    mInit;
448
    nscolor mTextColor;
449
    nscolor mBGColor;
450
    nscolor mUnderlineColor;
451
    uint8_t mUnderlineStyle;
452
    float   mUnderlineRelativeSize;
453
  };
454
  nsSelectionStyle mSelectionStyle[5];
455
456
  // Color initializations
457
  void InitCommonColors();
458
  bool InitSelectionColorsAndShadow();
459
460
  nsSelectionStyle* GetSelectionStyle(int32_t aIndex);
461
  void InitSelectionStyle(int32_t aIndex);
462
463
  bool EnsureSufficientContrast(nscolor *aForeColor, nscolor *aBackColor);
464
465
  nscolor GetResolvedForeColor(nscolor aColor, nscolor aDefaultForeColor,
466
                               nscolor aBackColor);
467
};
468
469
static TextRunUserData*
470
CreateUserData(uint32_t aMappedFlowCount)
471
0
{
472
0
  TextRunUserData* data = static_cast<TextRunUserData*>
473
0
      (moz_xmalloc(sizeof(TextRunUserData) +
474
0
       aMappedFlowCount * sizeof(TextRunMappedFlow)));
475
#ifdef DEBUG
476
  data->mMappedFlows = reinterpret_cast<TextRunMappedFlow*>(data + 1);
477
#endif
478
  data->mMappedFlowCount = aMappedFlowCount;
479
0
  data->mLastFlowIndex = 0;
480
0
  return data;
481
0
}
482
483
static void
484
DestroyUserData(TextRunUserData* aUserData)
485
0
{
486
0
  if (aUserData) {
487
0
    free(aUserData);
488
0
  }
489
0
}
490
491
static ComplexTextRunUserData*
492
CreateComplexUserData(uint32_t aMappedFlowCount)
493
0
{
494
0
  ComplexTextRunUserData* data = static_cast<ComplexTextRunUserData*>
495
0
      (moz_xmalloc(sizeof(ComplexTextRunUserData) +
496
0
       aMappedFlowCount * sizeof(TextRunMappedFlow)));
497
0
  new (data) ComplexTextRunUserData();
498
#ifdef DEBUG
499
  data->mMappedFlows = reinterpret_cast<TextRunMappedFlow*>(data + 1);
500
#endif
501
  data->mMappedFlowCount = aMappedFlowCount;
502
0
  data->mLastFlowIndex = 0;
503
0
  return data;
504
0
}
505
506
static void
507
DestroyComplexUserData(ComplexTextRunUserData* aUserData)
508
0
{
509
0
  if (aUserData) {
510
0
    aUserData->~ComplexTextRunUserData();
511
0
    free(aUserData);
512
0
  }
513
0
}
514
515
static void
516
DestroyTextRunUserData(gfxTextRun* aTextRun)
517
0
{
518
0
  MOZ_ASSERT(aTextRun->GetUserData());
519
0
  if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::TEXT_IS_SIMPLE_FLOW) {
520
0
    if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::TEXT_MIGHT_HAVE_GLYPH_CHANGES) {
521
0
      delete static_cast<SimpleTextRunUserData*>(aTextRun->GetUserData());
522
0
    }
523
0
  } else {
524
0
    if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::TEXT_MIGHT_HAVE_GLYPH_CHANGES) {
525
0
      DestroyComplexUserData(
526
0
        static_cast<ComplexTextRunUserData*>(aTextRun->GetUserData()));
527
0
    } else {
528
0
      DestroyUserData(
529
0
        static_cast<TextRunUserData*>(aTextRun->GetUserData()));
530
0
    }
531
0
  }
532
0
  aTextRun->ClearFlagBits(nsTextFrameUtils::Flags::TEXT_MIGHT_HAVE_GLYPH_CHANGES);
533
0
  aTextRun->SetUserData(nullptr);
534
0
}
535
536
static TextRunMappedFlow*
537
GetMappedFlows(const gfxTextRun* aTextRun)
538
0
{
539
0
  MOZ_ASSERT(aTextRun->GetUserData(), "UserData must exist.");
540
0
  MOZ_ASSERT(!(aTextRun->GetFlags2() & nsTextFrameUtils::Flags::TEXT_IS_SIMPLE_FLOW),
541
0
            "The method should not be called for simple flows.");
542
0
  TextRunMappedFlow* flows;
543
0
  if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::TEXT_MIGHT_HAVE_GLYPH_CHANGES) {
544
0
    flows = reinterpret_cast<TextRunMappedFlow*>(
545
0
      static_cast<ComplexTextRunUserData*>(aTextRun->GetUserData()) + 1);
546
0
  } else {
547
0
    flows = reinterpret_cast<TextRunMappedFlow*>(
548
0
      static_cast<TextRunUserData*>(aTextRun->GetUserData()) + 1);
549
0
  }
550
0
  MOZ_ASSERT(static_cast<TextRunUserData*>(aTextRun->GetUserData())->
551
0
             mMappedFlows == flows,
552
0
             "GetMappedFlows should return the same pointer as mMappedFlows.");
553
0
  return flows;
554
0
}
555
556
/**
557
 * These are utility functions just for helping with the complexity related with
558
 * the text runs user data.
559
 */
560
static nsTextFrame*
561
GetFrameForSimpleFlow(const gfxTextRun* aTextRun)
562
0
{
563
0
  MOZ_ASSERT(aTextRun->GetFlags2() & nsTextFrameUtils::Flags::TEXT_IS_SIMPLE_FLOW,
564
0
             "Not so simple flow?");
565
0
  if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::TEXT_MIGHT_HAVE_GLYPH_CHANGES) {
566
0
    return static_cast<SimpleTextRunUserData*>(aTextRun->GetUserData())->mFrame;
567
0
  }
568
0
569
0
  return static_cast<nsTextFrame*>(aTextRun->GetUserData());
570
0
}
571
572
/**
573
 * Remove |aTextRun| from the frame continuation chain starting at
574
 * |aStartContinuation| if non-null, otherwise starting at |aFrame|.
575
 * Unmark |aFrame| as a text run owner if it's the frame we start at.
576
 * Return true if |aStartContinuation| is non-null and was found
577
 * in the next-continuation chain of |aFrame|.
578
 */
579
static bool
580
ClearAllTextRunReferences(nsTextFrame* aFrame, gfxTextRun* aTextRun,
581
                          nsTextFrame* aStartContinuation,
582
                          nsFrameState aWhichTextRunState)
583
0
{
584
0
  MOZ_ASSERT(aFrame, "null frame");
585
0
  MOZ_ASSERT(!aStartContinuation ||
586
0
             (!aStartContinuation->GetTextRun(nsTextFrame::eInflated) ||
587
0
              aStartContinuation->GetTextRun(nsTextFrame::eInflated) == aTextRun) ||
588
0
             (!aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) ||
589
0
              aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) == aTextRun),
590
0
             "wrong aStartContinuation for this text run");
591
0
592
0
  if (!aStartContinuation || aStartContinuation == aFrame) {
593
0
    aFrame->RemoveStateBits(aWhichTextRunState);
594
0
  } else {
595
0
    do {
596
0
      NS_ASSERTION(aFrame->IsTextFrame(), "Bad frame");
597
0
      aFrame = aFrame->GetNextContinuation();
598
0
    } while (aFrame && aFrame != aStartContinuation);
599
0
  }
600
0
  bool found = aStartContinuation == aFrame;
601
0
  while (aFrame) {
602
0
    NS_ASSERTION(aFrame->IsTextFrame(), "Bad frame");
603
0
    if (!aFrame->RemoveTextRun(aTextRun)) {
604
0
      break;
605
0
    }
606
0
    aFrame = aFrame->GetNextContinuation();
607
0
  }
608
0
609
0
  MOZ_ASSERT(!found || aStartContinuation, "how did we find null?");
610
0
  return found;
611
0
}
612
613
/**
614
 * Kill all references to |aTextRun| starting at |aStartContinuation|.
615
 * It could be referenced by any of its owners, and all their in-flows.
616
 * If |aStartContinuation| is null then process all userdata frames
617
 * and their continuations.
618
 * @note the caller is expected to take care of possibly destroying the
619
 * text run if all userdata frames were reset (userdata is deallocated
620
 * by this function though). The caller can detect this has occured by
621
 * checking |aTextRun->GetUserData() == nullptr|.
622
 */
623
static void
624
UnhookTextRunFromFrames(gfxTextRun* aTextRun, nsTextFrame* aStartContinuation)
625
0
{
626
0
  if (!aTextRun->GetUserData()) {
627
0
    return;
628
0
  }
629
0
630
0
  if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::TEXT_IS_SIMPLE_FLOW) {
631
0
    nsTextFrame* userDataFrame = GetFrameForSimpleFlow(aTextRun);
632
0
    nsFrameState whichTextRunState =
633
0
      userDataFrame->GetTextRun(nsTextFrame::eInflated) == aTextRun
634
0
        ? TEXT_IN_TEXTRUN_USER_DATA
635
0
        : TEXT_IN_UNINFLATED_TEXTRUN_USER_DATA;
636
0
    DebugOnly<bool> found =
637
0
      ClearAllTextRunReferences(userDataFrame, aTextRun,
638
0
                                aStartContinuation, whichTextRunState);
639
0
    NS_ASSERTION(!aStartContinuation || found,
640
0
                 "aStartContinuation wasn't found in simple flow text run");
641
0
    if (!(userDataFrame->GetStateBits() & whichTextRunState)) {
642
0
      DestroyTextRunUserData(aTextRun);
643
0
    }
644
0
  } else {
645
0
    auto userData = static_cast<TextRunUserData*>(aTextRun->GetUserData());
646
0
    TextRunMappedFlow* userMappedFlows = GetMappedFlows(aTextRun);
647
0
    int32_t destroyFromIndex = aStartContinuation ? -1 : 0;
648
0
    for (uint32_t i = 0; i < userData->mMappedFlowCount; ++i) {
649
0
      nsTextFrame* userDataFrame = userMappedFlows[i].mStartFrame;
650
0
      nsFrameState whichTextRunState =
651
0
        userDataFrame->GetTextRun(nsTextFrame::eInflated) == aTextRun
652
0
          ? TEXT_IN_TEXTRUN_USER_DATA
653
0
          : TEXT_IN_UNINFLATED_TEXTRUN_USER_DATA;
654
0
      bool found =
655
0
        ClearAllTextRunReferences(userDataFrame, aTextRun,
656
0
                                  aStartContinuation, whichTextRunState);
657
0
      if (found) {
658
0
        if (userDataFrame->GetStateBits() & whichTextRunState) {
659
0
          destroyFromIndex = i + 1;
660
0
        } else {
661
0
          destroyFromIndex = i;
662
0
        }
663
0
        aStartContinuation = nullptr;
664
0
      }
665
0
    }
666
0
    NS_ASSERTION(destroyFromIndex >= 0,
667
0
                 "aStartContinuation wasn't found in multi flow text run");
668
0
    if (destroyFromIndex == 0) {
669
0
      DestroyTextRunUserData(aTextRun);
670
0
    } else {
671
0
      userData->mMappedFlowCount = uint32_t(destroyFromIndex);
672
0
      if (userData->mLastFlowIndex >= uint32_t(destroyFromIndex)) {
673
0
        userData->mLastFlowIndex = uint32_t(destroyFromIndex) - 1;
674
0
      }
675
0
    }
676
0
  }
677
0
}
678
679
static void
680
InvalidateFrameDueToGlyphsChanged(nsIFrame* aFrame)
681
0
{
682
0
  MOZ_ASSERT(aFrame);
683
0
684
0
  nsIPresShell* shell = aFrame->PresShell();
685
0
  for (nsIFrame* f = aFrame; f;
686
0
       f = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(f)) {
687
0
    f->InvalidateFrame();
688
0
689
0
    // If this is a non-display text frame within SVG <text>, we need
690
0
    // to reflow the SVGTextFrame. (This is similar to reflowing the
691
0
    // SVGTextFrame in response to style changes, in
692
0
    // SVGTextFrame::DidSetComputedStyle.)
693
0
    if (nsSVGUtils::IsInSVGTextSubtree(f) &&
694
0
        f->GetStateBits() & NS_FRAME_IS_NONDISPLAY) {
695
0
      auto svgTextFrame = static_cast<SVGTextFrame*>(
696
0
        nsLayoutUtils::GetClosestFrameOfType(f, LayoutFrameType::SVGText));
697
0
      svgTextFrame->ScheduleReflowSVGNonDisplayText(nsIPresShell::eResize);
698
0
    } else {
699
0
      // Theoretically we could just update overflow areas, perhaps using
700
0
      // OverflowChangedTracker, but that would do a bunch of work eagerly that
701
0
      // we should probably do lazily here since there could be a lot
702
0
      // of text frames affected and we'd like to coalesce the work. So that's
703
0
      // not easy to do well.
704
0
      shell->FrameNeedsReflow(f, nsIPresShell::eResize, NS_FRAME_IS_DIRTY);
705
0
    }
706
0
  }
707
0
}
708
709
void
710
GlyphObserver::NotifyGlyphsChanged()
711
0
{
712
0
  if (mTextRun->GetFlags2() & nsTextFrameUtils::Flags::TEXT_IS_SIMPLE_FLOW) {
713
0
    InvalidateFrameDueToGlyphsChanged(GetFrameForSimpleFlow(mTextRun));
714
0
    return;
715
0
  }
716
0
717
0
  auto data = static_cast<TextRunUserData*>(mTextRun->GetUserData());
718
0
  TextRunMappedFlow* userMappedFlows = GetMappedFlows(mTextRun);
719
0
  for (uint32_t i = 0; i < data->mMappedFlowCount; ++i) {
720
0
    InvalidateFrameDueToGlyphsChanged(userMappedFlows[i].mStartFrame);
721
0
  }
722
0
}
723
724
0
int32_t nsTextFrame::GetContentEnd() const {
725
0
  nsTextFrame* next = GetNextContinuation();
726
0
  return next ? next->GetContentOffset() : mContent->GetText()->GetLength();
727
0
}
728
729
struct FlowLengthProperty {
730
  int32_t mStartOffset;
731
  // The offset of the next fixed continuation after mStartOffset, or
732
  // of the end of the text if there is none
733
  int32_t mEndFlowOffset;
734
};
735
736
0
int32_t nsTextFrame::GetInFlowContentLength() {
737
0
  if (!(mState & NS_FRAME_IS_BIDI)) {
738
0
    return mContent->TextLength() - mContentOffset;
739
0
  }
740
0
741
0
  FlowLengthProperty* flowLength =
742
0
    mContent->HasFlag(NS_HAS_FLOWLENGTH_PROPERTY)
743
0
    ? static_cast<FlowLengthProperty*>(mContent->GetProperty(nsGkAtoms::flowlength))
744
0
    : nullptr;
745
0
746
0
  /**
747
0
   * This frame must start inside the cached flow. If the flow starts at
748
0
   * mContentOffset but this frame is empty, logically it might be before the
749
0
   * start of the cached flow.
750
0
   */
751
0
  if (flowLength &&
752
0
      (flowLength->mStartOffset < mContentOffset ||
753
0
       (flowLength->mStartOffset == mContentOffset && GetContentEnd() > mContentOffset)) &&
754
0
      flowLength->mEndFlowOffset > mContentOffset) {
755
#ifdef DEBUG
756
    NS_ASSERTION(flowLength->mEndFlowOffset >= GetContentEnd(),
757
     "frame crosses fixed continuation boundary");
758
#endif
759
    return flowLength->mEndFlowOffset - mContentOffset;
760
0
  }
761
0
762
0
  nsTextFrame* nextBidi = LastInFlow()->GetNextContinuation();
763
0
  int32_t endFlow = nextBidi ? nextBidi->GetContentOffset() : mContent->TextLength();
764
0
765
0
  if (!flowLength) {
766
0
    flowLength = new FlowLengthProperty;
767
0
    if (NS_FAILED(mContent->SetProperty(nsGkAtoms::flowlength, flowLength,
768
0
                                        nsINode::DeleteProperty<FlowLengthProperty>))) {
769
0
      delete flowLength;
770
0
      flowLength = nullptr;
771
0
    }
772
0
    mContent->SetFlags(NS_HAS_FLOWLENGTH_PROPERTY);
773
0
  }
774
0
  if (flowLength) {
775
0
    flowLength->mStartOffset = mContentOffset;
776
0
    flowLength->mEndFlowOffset = endFlow;
777
0
  }
778
0
779
0
  return endFlow - mContentOffset;
780
0
}
781
782
// Smarter versions of dom::IsSpaceCharacter.
783
// Unicode is really annoying; sometimes a space character isn't whitespace ---
784
// when it combines with another character
785
// So we have several versions of IsSpace for use in different contexts.
786
787
static bool IsSpaceCombiningSequenceTail(const nsTextFragment* aFrag, uint32_t aPos)
788
0
{
789
0
  NS_ASSERTION(aPos <= aFrag->GetLength(), "Bad offset");
790
0
  if (!aFrag->Is2b())
791
0
    return false;
792
0
  return nsTextFrameUtils::IsSpaceCombiningSequenceTail(
793
0
    aFrag->Get2b() + aPos, aFrag->GetLength() - aPos);
794
0
}
795
796
// Check whether aPos is a space for CSS 'word-spacing' purposes
797
static bool
798
IsCSSWordSpacingSpace(const nsTextFragment* aFrag, uint32_t aPos,
799
                      const nsTextFrame* aFrame, const nsStyleText* aStyleText)
800
0
{
801
0
  NS_ASSERTION(aPos < aFrag->GetLength(), "No text for IsSpace!");
802
0
803
0
  char16_t ch = aFrag->CharAt(aPos);
804
0
  switch (ch) {
805
0
  case ' ':
806
0
  case CH_NBSP:
807
0
    return !IsSpaceCombiningSequenceTail(aFrag, aPos + 1);
808
0
  case '\r':
809
0
  case '\t': return !aStyleText->WhiteSpaceIsSignificant();
810
0
  case '\n': return !aStyleText->NewlineIsSignificant(aFrame);
811
0
  default: return false;
812
0
  }
813
0
}
814
815
// Check whether the string aChars/aLength starts with space that's
816
// trimmable according to CSS 'white-space:normal/nowrap'.
817
static bool IsTrimmableSpace(const char16_t* aChars, uint32_t aLength)
818
0
{
819
0
  NS_ASSERTION(aLength > 0, "No text for IsSpace!");
820
0
821
0
  char16_t ch = *aChars;
822
0
  if (ch == ' ')
823
0
    return !nsTextFrameUtils::IsSpaceCombiningSequenceTail(aChars + 1, aLength - 1);
824
0
  return ch == '\t' || ch == '\f' || ch == '\n' || ch == '\r';
825
0
}
826
827
// Check whether the character aCh is trimmable according to CSS
828
// 'white-space:normal/nowrap'
829
static bool IsTrimmableSpace(char aCh)
830
0
{
831
0
  return aCh == ' ' || aCh == '\t' || aCh == '\f' || aCh == '\n' || aCh == '\r';
832
0
}
833
834
static bool IsTrimmableSpace(const nsTextFragment* aFrag, uint32_t aPos,
835
                               const nsStyleText* aStyleText)
836
0
{
837
0
  NS_ASSERTION(aPos < aFrag->GetLength(), "No text for IsSpace!");
838
0
839
0
  switch (aFrag->CharAt(aPos)) {
840
0
  case ' ': return !aStyleText->WhiteSpaceIsSignificant() &&
841
0
                   !IsSpaceCombiningSequenceTail(aFrag, aPos + 1);
842
0
  case '\n': return !aStyleText->NewlineIsSignificantStyle() &&
843
0
                    aStyleText->mWhiteSpace != mozilla::StyleWhiteSpace::PreSpace;
844
0
  case '\t':
845
0
  case '\r':
846
0
  case '\f': return !aStyleText->WhiteSpaceIsSignificant();
847
0
  default: return false;
848
0
  }
849
0
}
850
851
static bool IsSelectionSpace(const nsTextFragment* aFrag, uint32_t aPos)
852
0
{
853
0
  NS_ASSERTION(aPos < aFrag->GetLength(), "No text for IsSpace!");
854
0
  char16_t ch = aFrag->CharAt(aPos);
855
0
  if (ch == ' ' || ch == CH_NBSP)
856
0
    return !IsSpaceCombiningSequenceTail(aFrag, aPos + 1);
857
0
  return ch == '\t' || ch == '\n' || ch == '\f' || ch == '\r';
858
0
}
859
860
// Count the amount of trimmable whitespace (as per CSS
861
// 'white-space:normal/nowrap') in a text fragment. The first
862
// character is at offset aStartOffset; the maximum number of characters
863
// to check is aLength. aDirection is -1 or 1 depending on whether we should
864
// progress backwards or forwards.
865
static uint32_t
866
GetTrimmableWhitespaceCount(const nsTextFragment* aFrag,
867
                            int32_t aStartOffset, int32_t aLength,
868
                            int32_t aDirection)
869
0
{
870
0
  int32_t count = 0;
871
0
  if (aFrag->Is2b()) {
872
0
    const char16_t* str = aFrag->Get2b() + aStartOffset;
873
0
    int32_t fragLen = aFrag->GetLength() - aStartOffset;
874
0
    for (; count < aLength; ++count) {
875
0
      if (!IsTrimmableSpace(str, fragLen))
876
0
        break;
877
0
      str += aDirection;
878
0
      fragLen -= aDirection;
879
0
    }
880
0
  } else {
881
0
    const char* str = aFrag->Get1b() + aStartOffset;
882
0
    for (; count < aLength; ++count) {
883
0
      if (!IsTrimmableSpace(*str))
884
0
        break;
885
0
      str += aDirection;
886
0
    }
887
0
  }
888
0
  return count;
889
0
}
890
891
static bool
892
IsAllWhitespace(const nsTextFragment* aFrag, bool aAllowNewline)
893
0
{
894
0
  if (aFrag->Is2b())
895
0
    return false;
896
0
  int32_t len = aFrag->GetLength();
897
0
  const char* str = aFrag->Get1b();
898
0
  for (int32_t i = 0; i < len; ++i) {
899
0
    char ch = str[i];
900
0
    if (ch == ' ' || ch == '\t' || ch == '\r' || (ch == '\n' && aAllowNewline))
901
0
      continue;
902
0
    return false;
903
0
  }
904
0
  return true;
905
0
}
906
907
static void
908
ClearObserversFromTextRun(gfxTextRun* aTextRun)
909
0
{
910
0
  if (!(aTextRun->GetFlags2() & nsTextFrameUtils::Flags::TEXT_MIGHT_HAVE_GLYPH_CHANGES)) {
911
0
    return;
912
0
  }
913
0
914
0
  if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::TEXT_IS_SIMPLE_FLOW) {
915
0
    static_cast<SimpleTextRunUserData*>(aTextRun->GetUserData())
916
0
      ->mGlyphObservers.Clear();
917
0
  } else {
918
0
    static_cast<ComplexTextRunUserData*>(aTextRun->GetUserData())
919
0
      ->mGlyphObservers.Clear();
920
0
  }
921
0
}
922
923
static void
924
CreateObserversForAnimatedGlyphs(gfxTextRun* aTextRun)
925
0
{
926
0
  if (!aTextRun->GetUserData()) {
927
0
    return;
928
0
  }
929
0
930
0
  ClearObserversFromTextRun(aTextRun);
931
0
932
0
  nsTArray<gfxFont*> fontsWithAnimatedGlyphs;
933
0
  uint32_t numGlyphRuns;
934
0
  const gfxTextRun::GlyphRun* glyphRuns =
935
0
    aTextRun->GetGlyphRuns(&numGlyphRuns);
936
0
  for (uint32_t i = 0; i < numGlyphRuns; ++i) {
937
0
    gfxFont* font = glyphRuns[i].mFont;
938
0
    if (font->GlyphsMayChange() && !fontsWithAnimatedGlyphs.Contains(font)) {
939
0
      fontsWithAnimatedGlyphs.AppendElement(font);
940
0
    }
941
0
  }
942
0
  if (fontsWithAnimatedGlyphs.IsEmpty()) {
943
0
    // NB: Theoretically, we should clear the TEXT_MIGHT_HAVE_GLYPH_CHANGES
944
0
    // here. That would involve de-allocating the simple user data struct if
945
0
    // present too, and resetting the pointer to the frame. In practice, I
946
0
    // don't think worth doing that work here, given the flag's only purpose is
947
0
    // to distinguish what kind of user data is there.
948
0
    return;
949
0
  }
950
0
951
0
  nsTArray<UniquePtr<GlyphObserver>>* observers;
952
0
953
0
  if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::TEXT_IS_SIMPLE_FLOW) {
954
0
    // Swap the frame pointer for a just-allocated SimpleTextRunUserData if
955
0
    // appropriate.
956
0
    if (!(aTextRun->GetFlags2() & nsTextFrameUtils::Flags::TEXT_MIGHT_HAVE_GLYPH_CHANGES)) {
957
0
      auto frame = static_cast<nsTextFrame*>(aTextRun->GetUserData());
958
0
      aTextRun->SetUserData(new SimpleTextRunUserData(frame));
959
0
    }
960
0
961
0
    auto data =
962
0
      static_cast<SimpleTextRunUserData*>(aTextRun->GetUserData());
963
0
    observers = &data->mGlyphObservers;
964
0
  } else {
965
0
    if (!(aTextRun->GetFlags2() & nsTextFrameUtils::Flags::TEXT_MIGHT_HAVE_GLYPH_CHANGES)) {
966
0
      auto oldData = static_cast<TextRunUserData*>(aTextRun->GetUserData());
967
0
      TextRunMappedFlow* oldMappedFlows = GetMappedFlows(aTextRun);
968
0
      ComplexTextRunUserData* data =
969
0
        CreateComplexUserData(oldData->mMappedFlowCount);
970
0
      TextRunMappedFlow* dataMappedFlows =
971
0
        reinterpret_cast<TextRunMappedFlow*>(data + 1);
972
0
      data->mLastFlowIndex = oldData->mLastFlowIndex;
973
0
      for (uint32_t i = 0; i < oldData->mMappedFlowCount; ++i) {
974
0
        dataMappedFlows[i] = oldMappedFlows[i];
975
0
      }
976
0
      DestroyUserData(oldData);
977
0
      aTextRun->SetUserData(data);
978
0
    }
979
0
    auto data = static_cast<ComplexTextRunUserData*>(aTextRun->GetUserData());
980
0
    observers = &data->mGlyphObservers;
981
0
  }
982
0
983
0
  aTextRun->SetFlagBits(nsTextFrameUtils::Flags::TEXT_MIGHT_HAVE_GLYPH_CHANGES);
984
0
985
0
  for (auto font : fontsWithAnimatedGlyphs) {
986
0
    observers->AppendElement(new GlyphObserver(font, aTextRun));
987
0
  }
988
0
}
989
990
/**
991
 * This class accumulates state as we scan a paragraph of text. It detects
992
 * textrun boundaries (changes from text to non-text, hard
993
 * line breaks, and font changes) and builds a gfxTextRun at each boundary.
994
 * It also detects linebreaker run boundaries (changes from text to non-text,
995
 * and hard line breaks) and at each boundary runs the linebreaker to compute
996
 * potential line breaks. It also records actual line breaks to store them in
997
 * the textruns.
998
 */
999
class BuildTextRunsScanner {
1000
public:
1001
  BuildTextRunsScanner(nsPresContext* aPresContext, DrawTarget* aDrawTarget,
1002
      nsIFrame* aLineContainer, nsTextFrame::TextRunType aWhichTextRun) :
1003
    mDrawTarget(aDrawTarget),
1004
    mLineContainer(aLineContainer),
1005
    mCommonAncestorWithLastFrame(nullptr),
1006
    mMissingFonts(aPresContext->MissingFontRecorder()),
1007
    mBidiEnabled(aPresContext->BidiEnabled()),
1008
    mStartOfLine(true),
1009
    mSkipIncompleteTextRuns(false),
1010
    mCanStopOnThisLine(false),
1011
    mWhichTextRun(aWhichTextRun),
1012
    mNextRunContextInfo(nsTextFrameUtils::INCOMING_NONE),
1013
0
    mCurrentRunContextInfo(nsTextFrameUtils::INCOMING_NONE) {
1014
0
    ResetRunInfo();
1015
0
  }
1016
0
  ~BuildTextRunsScanner() {
1017
0
    NS_ASSERTION(mBreakSinks.IsEmpty(), "Should have been cleared");
1018
0
    NS_ASSERTION(mLineBreakBeforeFrames.IsEmpty(), "Should have been cleared");
1019
0
    NS_ASSERTION(mMappedFlows.IsEmpty(), "Should have been cleared");
1020
0
  }
1021
1022
0
  void SetAtStartOfLine() {
1023
0
    mStartOfLine = true;
1024
0
    mCanStopOnThisLine = false;
1025
0
  }
1026
0
  void SetSkipIncompleteTextRuns(bool aSkip) {
1027
0
    mSkipIncompleteTextRuns = aSkip;
1028
0
  }
1029
0
  void SetCommonAncestorWithLastFrame(nsIFrame* aFrame) {
1030
0
    mCommonAncestorWithLastFrame = aFrame;
1031
0
  }
1032
0
  bool CanStopOnThisLine() {
1033
0
    return mCanStopOnThisLine;
1034
0
  }
1035
0
  nsIFrame* GetCommonAncestorWithLastFrame() {
1036
0
    return mCommonAncestorWithLastFrame;
1037
0
  }
1038
0
  void LiftCommonAncestorWithLastFrameToParent(nsIFrame* aFrame) {
1039
0
    if (mCommonAncestorWithLastFrame &&
1040
0
        mCommonAncestorWithLastFrame->GetParent() == aFrame) {
1041
0
      mCommonAncestorWithLastFrame = aFrame;
1042
0
    }
1043
0
  }
1044
  void ScanFrame(nsIFrame* aFrame);
1045
  bool IsTextRunValidForMappedFlows(const gfxTextRun* aTextRun);
1046
  void FlushFrames(bool aFlushLineBreaks, bool aSuppressTrailingBreak);
1047
  void FlushLineBreaks(gfxTextRun* aTrailingTextRun);
1048
0
  void ResetRunInfo() {
1049
0
    mLastFrame = nullptr;
1050
0
    mMappedFlows.Clear();
1051
0
    mLineBreakBeforeFrames.Clear();
1052
0
    mMaxTextLength = 0;
1053
0
    mDoubleByteText = false;
1054
0
  }
1055
  void AccumulateRunInfo(nsTextFrame* aFrame);
1056
  /**
1057
   * @return null to indicate either textrun construction failed or
1058
   * we constructed just a partial textrun to set up linebreaker and other
1059
   * state for following textruns.
1060
   */
1061
  already_AddRefed<gfxTextRun> BuildTextRunForFrames(void* aTextBuffer);
1062
  bool SetupLineBreakerContext(gfxTextRun *aTextRun);
1063
  void AssignTextRun(gfxTextRun* aTextRun, float aInflation);
1064
  nsTextFrame* GetNextBreakBeforeFrame(uint32_t* aIndex);
1065
  void SetupBreakSinksForTextRun(gfxTextRun* aTextRun, const void* aTextPtr);
1066
  void SetupTextEmphasisForTextRun(gfxTextRun* aTextRun, const void* aTextPtr);
1067
  struct FindBoundaryState {
1068
    nsIFrame*    mStopAtFrame;
1069
    nsTextFrame* mFirstTextFrame;
1070
    nsTextFrame* mLastTextFrame;
1071
    bool mSeenTextRunBoundaryOnLaterLine;
1072
    bool mSeenTextRunBoundaryOnThisLine;
1073
    bool mSeenSpaceForLineBreakingOnThisLine;
1074
    nsTArray<char16_t>& mBuffer;
1075
  };
1076
  enum FindBoundaryResult {
1077
    FB_CONTINUE,
1078
    FB_STOPPED_AT_STOP_FRAME,
1079
    FB_FOUND_VALID_TEXTRUN_BOUNDARY
1080
  };
1081
  FindBoundaryResult FindBoundaries(nsIFrame* aFrame, FindBoundaryState* aState);
1082
1083
  bool ContinueTextRunAcrossFrames(nsTextFrame* aFrame1, nsTextFrame* aFrame2);
1084
1085
  // Like TextRunMappedFlow but with some differences. mStartFrame to mEndFrame
1086
  // (exclusive) are a sequence of in-flow frames (if mEndFrame is null, then
1087
  // continuations starting from mStartFrame are a sequence of in-flow frames).
1088
  struct MappedFlow {
1089
    nsTextFrame* mStartFrame;
1090
    nsTextFrame* mEndFrame;
1091
    // When we consider breaking between elements, the nearest common
1092
    // ancestor of the elements containing the characters is the one whose
1093
    // CSS 'white-space' property governs. So this records the nearest common
1094
    // ancestor of mStartFrame and the previous text frame, or null if there
1095
    // was no previous text frame on this line.
1096
    nsIFrame*    mAncestorControllingInitialBreak;
1097
1098
0
    int32_t GetContentEnd() {
1099
0
      return mEndFrame ? mEndFrame->GetContentOffset()
1100
0
          : mStartFrame->GetContent()->GetText()->GetLength();
1101
0
    }
1102
  };
1103
1104
  class BreakSink final : public nsILineBreakSink {
1105
  public:
1106
    BreakSink(gfxTextRun* aTextRun, DrawTarget* aDrawTarget,
1107
              uint32_t aOffsetIntoTextRun)
1108
      : mTextRun(aTextRun)
1109
      , mDrawTarget(aDrawTarget)
1110
      , mOffsetIntoTextRun(aOffsetIntoTextRun)
1111
0
    {}
1112
1113
    void SetBreaks(uint32_t aOffset, uint32_t aLength,
1114
0
                   uint8_t* aBreakBefore) final {
1115
0
      gfxTextRun::Range range(aOffset + mOffsetIntoTextRun,
1116
0
                              aOffset + mOffsetIntoTextRun + aLength);
1117
0
      if (mTextRun->SetPotentialLineBreaks(range, aBreakBefore)) {
1118
0
        // Be conservative and assume that some breaks have been set
1119
0
        mTextRun->ClearFlagBits(nsTextFrameUtils::Flags::TEXT_NO_BREAKS);
1120
0
      }
1121
0
    }
1122
1123
    void SetCapitalization(uint32_t aOffset, uint32_t aLength,
1124
0
                           bool* aCapitalize) final {
1125
0
      MOZ_ASSERT(mTextRun->GetFlags2() & nsTextFrameUtils::Flags::TEXT_IS_TRANSFORMED,
1126
0
                 "Text run should be transformed!");
1127
0
      if (mTextRun->GetFlags2() & nsTextFrameUtils::Flags::TEXT_IS_TRANSFORMED) {
1128
0
        nsTransformedTextRun* transformedTextRun =
1129
0
          static_cast<nsTransformedTextRun*>(mTextRun.get());
1130
0
        transformedTextRun->SetCapitalization(aOffset + mOffsetIntoTextRun, aLength,
1131
0
                                              aCapitalize);
1132
0
      }
1133
0
    }
1134
1135
0
    void Finish(gfxMissingFontRecorder* aMFR) {
1136
0
      MOZ_ASSERT(!(mTextRun->GetFlags2() & nsTextFrameUtils::Flags::TEXT_UNUSED_FLAGS),
1137
0
                   "Flag set that should never be set! (memory safety error?)");
1138
0
      if (mTextRun->GetFlags2() & nsTextFrameUtils::Flags::TEXT_IS_TRANSFORMED) {
1139
0
        nsTransformedTextRun* transformedTextRun =
1140
0
          static_cast<nsTransformedTextRun*>(mTextRun.get());
1141
0
        transformedTextRun->FinishSettingProperties(mDrawTarget, aMFR);
1142
0
      }
1143
0
      // The way nsTransformedTextRun is implemented, its glyph runs aren't
1144
0
      // available until after nsTransformedTextRun::FinishSettingProperties()
1145
0
      // is called. So that's why we defer checking for animated glyphs to here.
1146
0
      CreateObserversForAnimatedGlyphs(mTextRun);
1147
0
    }
1148
1149
    RefPtr<gfxTextRun> mTextRun;
1150
    DrawTarget*  mDrawTarget;
1151
    uint32_t     mOffsetIntoTextRun;
1152
  };
1153
1154
private:
1155
  AutoTArray<MappedFlow,10>   mMappedFlows;
1156
  AutoTArray<nsTextFrame*,50> mLineBreakBeforeFrames;
1157
  AutoTArray<UniquePtr<BreakSink>,10> mBreakSinks;
1158
  nsLineBreaker                 mLineBreaker;
1159
  RefPtr<gfxTextRun>            mCurrentFramesAllSameTextRun;
1160
  DrawTarget*                   mDrawTarget;
1161
  nsIFrame*                     mLineContainer;
1162
  nsTextFrame*                  mLastFrame;
1163
  // The common ancestor of the current frame and the previous leaf frame
1164
  // on the line, or null if there was no previous leaf frame.
1165
  nsIFrame*                     mCommonAncestorWithLastFrame;
1166
  gfxMissingFontRecorder*       mMissingFonts;
1167
  // mMaxTextLength is an upper bound on the size of the text in all mapped frames
1168
  // The value UINT32_MAX represents overflow; text will be discarded
1169
  uint32_t                      mMaxTextLength;
1170
  bool                          mDoubleByteText;
1171
  bool                          mBidiEnabled;
1172
  bool                          mStartOfLine;
1173
  bool                          mSkipIncompleteTextRuns;
1174
  bool                          mCanStopOnThisLine;
1175
  nsTextFrame::TextRunType      mWhichTextRun;
1176
  uint8_t                       mNextRunContextInfo;
1177
  uint8_t                       mCurrentRunContextInfo;
1178
};
1179
1180
static nsIFrame*
1181
FindLineContainer(nsIFrame* aFrame)
1182
0
{
1183
0
  while (aFrame && (aFrame->IsFrameOfType(nsIFrame::eLineParticipant) ||
1184
0
                    aFrame->CanContinueTextRun())) {
1185
0
    aFrame = aFrame->GetParent();
1186
0
  }
1187
0
  return aFrame;
1188
0
}
1189
1190
static bool
1191
IsLineBreakingWhiteSpace(char16_t aChar)
1192
0
{
1193
0
  // 0x0A (\n) is not handled as white-space by the line breaker, since
1194
0
  // we break before it, if it isn't transformed to a normal space.
1195
0
  // (If we treat it as normal white-space then we'd only break after it.)
1196
0
  // However, it does induce a line break or is converted to a regular
1197
0
  // space, and either way it can be used to bound the region of text
1198
0
  // that needs to be analyzed for line breaking.
1199
0
  return nsLineBreaker::IsSpace(aChar) || aChar == 0x0A;
1200
0
}
1201
1202
static bool
1203
TextContainsLineBreakerWhiteSpace(const void* aText, uint32_t aLength,
1204
                                  bool aIsDoubleByte)
1205
0
{
1206
0
  if (aIsDoubleByte) {
1207
0
    const char16_t* chars = static_cast<const char16_t*>(aText);
1208
0
    for (uint32_t i = 0; i < aLength; ++i) {
1209
0
      if (IsLineBreakingWhiteSpace(chars[i]))
1210
0
        return true;
1211
0
    }
1212
0
    return false;
1213
0
  } else {
1214
0
    const uint8_t* chars = static_cast<const uint8_t*>(aText);
1215
0
    for (uint32_t i = 0; i < aLength; ++i) {
1216
0
      if (IsLineBreakingWhiteSpace(chars[i]))
1217
0
        return true;
1218
0
    }
1219
0
    return false;
1220
0
  }
1221
0
}
1222
1223
static_assert(uint8_t(mozilla::StyleWhiteSpace::Normal) == 0, "Convention: StyleWhiteSpace::Normal should be 0");
1224
static_assert(uint8_t(mozilla::StyleWhiteSpace::Pre) == 1, "Convention: StyleWhiteSpace::Pre should be 1");
1225
static_assert(uint8_t(mozilla::StyleWhiteSpace::Nowrap) == 2, "Convention: StyleWhiteSpace::NoWrap should be 2");
1226
static_assert(uint8_t(mozilla::StyleWhiteSpace::PreWrap) == 3, "Convention: StyleWhiteSpace::PreWrap should be 3");
1227
static_assert(uint8_t(mozilla::StyleWhiteSpace::PreLine) == 4, "Convention: StyleWhiteSpace::PreLine should be 4");
1228
static_assert(uint8_t(mozilla::StyleWhiteSpace::PreSpace) == 5, "Convention: StyleWhiteSpace::PreSpace should be 5");
1229
1230
static nsTextFrameUtils::CompressionMode
1231
GetCSSWhitespaceToCompressionMode(nsTextFrame* aFrame,
1232
                                  const nsStyleText* aStyleText)
1233
0
{
1234
0
  static const nsTextFrameUtils::CompressionMode sModes[] =
1235
0
  {
1236
0
    nsTextFrameUtils::COMPRESS_WHITESPACE_NEWLINE,     // normal
1237
0
    nsTextFrameUtils::COMPRESS_NONE,                   // pre
1238
0
    nsTextFrameUtils::COMPRESS_WHITESPACE_NEWLINE,     // nowrap
1239
0
    nsTextFrameUtils::COMPRESS_NONE,                   // pre-wrap
1240
0
    nsTextFrameUtils::COMPRESS_WHITESPACE,             // pre-line
1241
0
    nsTextFrameUtils::COMPRESS_NONE_TRANSFORM_TO_SPACE // -moz-pre-space
1242
0
  };
1243
0
1244
0
  auto compression = sModes[uint8_t(aStyleText->mWhiteSpace)];
1245
0
  if (compression == nsTextFrameUtils::COMPRESS_NONE &&
1246
0
      !aStyleText->NewlineIsSignificant(aFrame)) {
1247
0
    // If newline is set to be preserved, but then suppressed,
1248
0
    // transform newline to space.
1249
0
    compression = nsTextFrameUtils::COMPRESS_NONE_TRANSFORM_TO_SPACE;
1250
0
  }
1251
0
  return compression;
1252
0
}
1253
1254
struct FrameTextTraversal
1255
{
1256
  FrameTextTraversal()
1257
    : mFrameToScan(nullptr)
1258
    , mOverflowFrameToScan(nullptr)
1259
    , mScanSiblings(false)
1260
    , mLineBreakerCanCrossFrameBoundary(false)
1261
    , mTextRunCanCrossFrameBoundary(false)
1262
0
  {}
1263
1264
  // These fields identify which frames should be recursively scanned
1265
  // The first normal frame to scan (or null, if no such frame should be scanned)
1266
  nsIFrame*    mFrameToScan;
1267
  // The first overflow frame to scan (or null, if no such frame should be scanned)
1268
  nsIFrame*    mOverflowFrameToScan;
1269
  // Whether to scan the siblings of mFrameToDescendInto/mOverflowFrameToDescendInto
1270
  bool mScanSiblings;
1271
1272
  // These identify the boundaries of the context required for
1273
  // line breaking or textrun construction
1274
  bool mLineBreakerCanCrossFrameBoundary;
1275
  bool mTextRunCanCrossFrameBoundary;
1276
1277
0
  nsIFrame* NextFrameToScan() {
1278
0
    nsIFrame* f;
1279
0
    if (mFrameToScan) {
1280
0
      f = mFrameToScan;
1281
0
      mFrameToScan = mScanSiblings ? f->GetNextSibling() : nullptr;
1282
0
    } else if (mOverflowFrameToScan) {
1283
0
      f = mOverflowFrameToScan;
1284
0
      mOverflowFrameToScan = mScanSiblings ? f->GetNextSibling() : nullptr;
1285
0
    } else {
1286
0
      f = nullptr;
1287
0
    }
1288
0
    return f;
1289
0
  }
1290
};
1291
1292
static FrameTextTraversal
1293
CanTextCrossFrameBoundary(nsIFrame* aFrame)
1294
0
{
1295
0
  FrameTextTraversal result;
1296
0
1297
0
  bool continuesTextRun = aFrame->CanContinueTextRun();
1298
0
  if (aFrame->IsPlaceholderFrame()) {
1299
0
    // placeholders are "invisible", so a text run should be able to span
1300
0
    // across one. But don't descend into the out-of-flow.
1301
0
    result.mLineBreakerCanCrossFrameBoundary = true;
1302
0
    if (continuesTextRun) {
1303
0
      // ... Except for first-letter floats, which are really in-flow
1304
0
      // from the point of view of capitalization etc, so we'd better
1305
0
      // descend into them. But we actually need to break the textrun for
1306
0
      // first-letter floats since things look bad if, say, we try to make a
1307
0
      // ligature across the float boundary.
1308
0
      result.mFrameToScan =
1309
0
        (static_cast<nsPlaceholderFrame*>(aFrame))->GetOutOfFlowFrame();
1310
0
    } else {
1311
0
      result.mTextRunCanCrossFrameBoundary = true;
1312
0
    }
1313
0
  } else {
1314
0
    if (continuesTextRun) {
1315
0
      result.mFrameToScan = aFrame->PrincipalChildList().FirstChild();
1316
0
      result.mOverflowFrameToScan =
1317
0
        aFrame->GetChildList(nsIFrame::kOverflowList).FirstChild();
1318
0
      NS_WARNING_ASSERTION(
1319
0
        !result.mOverflowFrameToScan,
1320
0
        "Scanning overflow inline frames is something we should avoid");
1321
0
      result.mScanSiblings = true;
1322
0
      result.mTextRunCanCrossFrameBoundary = true;
1323
0
      result.mLineBreakerCanCrossFrameBoundary = true;
1324
0
    } else {
1325
0
      MOZ_ASSERT(!aFrame->IsRubyTextContainerFrame(),
1326
0
                 "Shouldn't call this method for ruby text container");
1327
0
    }
1328
0
  }
1329
0
  return result;
1330
0
}
1331
1332
BuildTextRunsScanner::FindBoundaryResult
1333
BuildTextRunsScanner::FindBoundaries(nsIFrame* aFrame, FindBoundaryState* aState)
1334
0
{
1335
0
  LayoutFrameType frameType = aFrame->Type();
1336
0
  if (frameType == LayoutFrameType::RubyTextContainer) {
1337
0
    // Don't stop a text run for ruby text container. We want ruby text
1338
0
    // containers to be skipped, but continue the text run across them.
1339
0
    return FB_CONTINUE;
1340
0
  }
1341
0
1342
0
  nsTextFrame* textFrame = frameType == LayoutFrameType::Text
1343
0
                             ? static_cast<nsTextFrame*>(aFrame)
1344
0
                             : nullptr;
1345
0
  if (textFrame) {
1346
0
    if (aState->mLastTextFrame &&
1347
0
        textFrame != aState->mLastTextFrame->GetNextInFlow() &&
1348
0
        !ContinueTextRunAcrossFrames(aState->mLastTextFrame, textFrame)) {
1349
0
      aState->mSeenTextRunBoundaryOnThisLine = true;
1350
0
      if (aState->mSeenSpaceForLineBreakingOnThisLine)
1351
0
        return FB_FOUND_VALID_TEXTRUN_BOUNDARY;
1352
0
    }
1353
0
    if (!aState->mFirstTextFrame) {
1354
0
      aState->mFirstTextFrame = textFrame;
1355
0
    }
1356
0
    aState->mLastTextFrame = textFrame;
1357
0
  }
1358
0
1359
0
  if (aFrame == aState->mStopAtFrame)
1360
0
    return FB_STOPPED_AT_STOP_FRAME;
1361
0
1362
0
  if (textFrame) {
1363
0
    if (aState->mSeenSpaceForLineBreakingOnThisLine) {
1364
0
      return FB_CONTINUE;
1365
0
    }
1366
0
    const nsTextFragment* frag = textFrame->GetContent()->GetText();
1367
0
    uint32_t start = textFrame->GetContentOffset();
1368
0
    uint32_t length = textFrame->GetContentLength();
1369
0
    const void* text;
1370
0
    if (frag->Is2b()) {
1371
0
      // It is possible that we may end up removing all whitespace in
1372
0
      // a piece of text because of The White Space Processing Rules,
1373
0
      // so we need to transform it before we can check existence of
1374
0
      // such whitespaces.
1375
0
      aState->mBuffer.EnsureLengthAtLeast(length);
1376
0
      nsTextFrameUtils::CompressionMode compression =
1377
0
        GetCSSWhitespaceToCompressionMode(textFrame, textFrame->StyleText());
1378
0
      uint8_t incomingFlags = 0;
1379
0
      gfxSkipChars skipChars;
1380
0
      nsTextFrameUtils::Flags analysisFlags;
1381
0
      char16_t* bufStart = aState->mBuffer.Elements();
1382
0
      char16_t* bufEnd = nsTextFrameUtils::TransformText(
1383
0
        frag->Get2b() + start, length, bufStart, compression,
1384
0
        &incomingFlags, &skipChars, &analysisFlags);
1385
0
      text = bufStart;
1386
0
      length = bufEnd - bufStart;
1387
0
    } else {
1388
0
      // If the text only contains ASCII characters, it is currently
1389
0
      // impossible that TransformText would remove all whitespaces,
1390
0
      // and thus the check below should return the same result for
1391
0
      // transformed text and original text. So we don't need to try
1392
0
      // transforming it here.
1393
0
      text = static_cast<const void*>(frag->Get1b() + start);
1394
0
    }
1395
0
    if (TextContainsLineBreakerWhiteSpace(text, length, frag->Is2b())) {
1396
0
      aState->mSeenSpaceForLineBreakingOnThisLine = true;
1397
0
      if (aState->mSeenTextRunBoundaryOnLaterLine) {
1398
0
        return FB_FOUND_VALID_TEXTRUN_BOUNDARY;
1399
0
      }
1400
0
    }
1401
0
    return FB_CONTINUE;
1402
0
  }
1403
0
1404
0
  FrameTextTraversal traversal = CanTextCrossFrameBoundary(aFrame);
1405
0
  if (!traversal.mTextRunCanCrossFrameBoundary) {
1406
0
    aState->mSeenTextRunBoundaryOnThisLine = true;
1407
0
    if (aState->mSeenSpaceForLineBreakingOnThisLine)
1408
0
      return FB_FOUND_VALID_TEXTRUN_BOUNDARY;
1409
0
  }
1410
0
1411
0
  for (nsIFrame* f = traversal.NextFrameToScan(); f;
1412
0
       f = traversal.NextFrameToScan()) {
1413
0
    FindBoundaryResult result = FindBoundaries(f, aState);
1414
0
    if (result != FB_CONTINUE)
1415
0
      return result;
1416
0
  }
1417
0
1418
0
  if (!traversal.mTextRunCanCrossFrameBoundary) {
1419
0
    aState->mSeenTextRunBoundaryOnThisLine = true;
1420
0
    if (aState->mSeenSpaceForLineBreakingOnThisLine)
1421
0
      return FB_FOUND_VALID_TEXTRUN_BOUNDARY;
1422
0
  }
1423
0
1424
0
  return FB_CONTINUE;
1425
0
}
1426
1427
// build text runs for the 200 lines following aForFrame, and stop after that
1428
// when we get a chance.
1429
0
#define NUM_LINES_TO_BUILD_TEXT_RUNS 200
1430
1431
/**
1432
 * General routine for building text runs. This is hairy because of the need
1433
 * to build text runs that span content nodes.
1434
 *
1435
 * @param aContext The gfxContext we're using to construct this text run.
1436
 * @param aForFrame The nsTextFrame for which we're building this text run.
1437
 * @param aLineContainer the line container containing aForFrame; if null,
1438
 *        we'll walk the ancestors to find it.  It's required to be non-null
1439
 *        when aForFrameLine is non-null.
1440
 * @param aForFrameLine the line containing aForFrame; if null, we'll figure
1441
 *        out the line (slowly)
1442
 * @param aWhichTextRun The type of text run we want to build. If font inflation
1443
 *        is enabled, this will be eInflated, otherwise it's eNotInflated.
1444
 */
1445
static void
1446
BuildTextRuns(DrawTarget* aDrawTarget, nsTextFrame* aForFrame,
1447
              nsIFrame* aLineContainer,
1448
              const nsLineList::iterator* aForFrameLine,
1449
              nsTextFrame::TextRunType aWhichTextRun)
1450
0
{
1451
0
  MOZ_ASSERT(aForFrame, "for no frame?");
1452
0
  NS_ASSERTION(!aForFrameLine || aLineContainer,
1453
0
               "line but no line container");
1454
0
1455
0
  nsIFrame* lineContainerChild = aForFrame;
1456
0
  if (!aLineContainer) {
1457
0
    if (aForFrame->IsFloatingFirstLetterChild()) {
1458
0
      lineContainerChild = aForFrame->GetParent()->GetPlaceholderFrame();
1459
0
    }
1460
0
    aLineContainer = FindLineContainer(lineContainerChild);
1461
0
  } else {
1462
0
    NS_ASSERTION((aLineContainer == FindLineContainer(aForFrame) ||
1463
0
                  (aLineContainer->IsLetterFrame() &&
1464
0
                   aLineContainer->IsFloating())),
1465
0
                 "Wrong line container hint");
1466
0
  }
1467
0
1468
0
  if (aForFrame->HasAnyStateBits(TEXT_IS_IN_TOKEN_MATHML)) {
1469
0
    aLineContainer->AddStateBits(TEXT_IS_IN_TOKEN_MATHML);
1470
0
    if (aForFrame->HasAnyStateBits(NS_FRAME_IS_IN_SINGLE_CHAR_MI)) {
1471
0
      aLineContainer->AddStateBits(NS_FRAME_IS_IN_SINGLE_CHAR_MI);
1472
0
    }
1473
0
  }
1474
0
  if (aForFrame->HasAnyStateBits(NS_FRAME_MATHML_SCRIPT_DESCENDANT)) {
1475
0
    aLineContainer->AddStateBits(NS_FRAME_MATHML_SCRIPT_DESCENDANT);
1476
0
  }
1477
0
1478
0
  nsPresContext* presContext = aLineContainer->PresContext();
1479
0
  BuildTextRunsScanner scanner(presContext, aDrawTarget,
1480
0
                               aLineContainer, aWhichTextRun);
1481
0
1482
0
  nsBlockFrame* block = nsLayoutUtils::GetAsBlock(aLineContainer);
1483
0
1484
0
  if (!block) {
1485
0
    nsIFrame* textRunContainer = aLineContainer;
1486
0
    if (aLineContainer->IsRubyTextContainerFrame()) {
1487
0
      textRunContainer = aForFrame;
1488
0
      while (textRunContainer && !textRunContainer->IsRubyTextFrame()) {
1489
0
        textRunContainer = textRunContainer->GetParent();
1490
0
      }
1491
0
      MOZ_ASSERT(textRunContainer &&
1492
0
                 textRunContainer->GetParent() == aLineContainer);
1493
0
    } else {
1494
0
      NS_ASSERTION(
1495
0
        !aLineContainer->GetPrevInFlow() && !aLineContainer->GetNextInFlow(),
1496
0
        "Breakable non-block line containers other than "
1497
0
        "ruby text container is not supported");
1498
0
    }
1499
0
    // Just loop through all the children of the linecontainer ... it's really
1500
0
    // just one line
1501
0
    scanner.SetAtStartOfLine();
1502
0
    scanner.SetCommonAncestorWithLastFrame(nullptr);
1503
0
    for (nsIFrame* child : textRunContainer->PrincipalChildList()) {
1504
0
      scanner.ScanFrame(child);
1505
0
    }
1506
0
    // Set mStartOfLine so FlushFrames knows its textrun ends a line
1507
0
    scanner.SetAtStartOfLine();
1508
0
    scanner.FlushFrames(true, false);
1509
0
    return;
1510
0
  }
1511
0
1512
0
  // Find the line containing 'lineContainerChild'.
1513
0
1514
0
  bool isValid = true;
1515
0
  nsBlockInFlowLineIterator backIterator(block, &isValid);
1516
0
  if (aForFrameLine) {
1517
0
    backIterator = nsBlockInFlowLineIterator(block, *aForFrameLine);
1518
0
  } else {
1519
0
    backIterator = nsBlockInFlowLineIterator(block, lineContainerChild, &isValid);
1520
0
    NS_ASSERTION(isValid, "aForFrame not found in block, someone lied to us");
1521
0
    NS_ASSERTION(backIterator.GetContainer() == block,
1522
0
                 "Someone lied to us about the block");
1523
0
  }
1524
0
  nsBlockFrame::LineIterator startLine = backIterator.GetLine();
1525
0
1526
0
  // Find a line where we can start building text runs. We choose the last line
1527
0
  // where:
1528
0
  // -- there is a textrun boundary between the start of the line and the
1529
0
  // start of aForFrame
1530
0
  // -- there is a space between the start of the line and the textrun boundary
1531
0
  // (this is so we can be sure the line breaks will be set properly
1532
0
  // on the textruns we construct).
1533
0
  // The possibly-partial text runs up to and including the first space
1534
0
  // are not reconstructed. We construct partial text runs for that text ---
1535
0
  // for the sake of simplifying the code and feeding the linebreaker ---
1536
0
  // but we discard them instead of assigning them to frames.
1537
0
  // This is a little awkward because we traverse lines in the reverse direction
1538
0
  // but we traverse the frames in each line in the forward direction.
1539
0
  nsBlockInFlowLineIterator forwardIterator = backIterator;
1540
0
  nsIFrame* stopAtFrame = lineContainerChild;
1541
0
  nsTextFrame* nextLineFirstTextFrame = nullptr;
1542
0
  AutoTArray<char16_t, BIG_TEXT_NODE_SIZE> buffer;
1543
0
  bool seenTextRunBoundaryOnLaterLine = false;
1544
0
  bool mayBeginInTextRun = true;
1545
0
  while (true) {
1546
0
    forwardIterator = backIterator;
1547
0
    nsBlockFrame::LineIterator line = backIterator.GetLine();
1548
0
    if (!backIterator.Prev() || backIterator.GetLine()->IsBlock()) {
1549
0
      mayBeginInTextRun = false;
1550
0
      break;
1551
0
    }
1552
0
1553
0
    BuildTextRunsScanner::FindBoundaryState state = { stopAtFrame, nullptr, nullptr,
1554
0
      bool(seenTextRunBoundaryOnLaterLine), false, false, buffer };
1555
0
    nsIFrame* child = line->mFirstChild;
1556
0
    bool foundBoundary = false;
1557
0
    for (int32_t i = line->GetChildCount() - 1; i >= 0; --i) {
1558
0
      BuildTextRunsScanner::FindBoundaryResult result =
1559
0
          scanner.FindBoundaries(child, &state);
1560
0
      if (result == BuildTextRunsScanner::FB_FOUND_VALID_TEXTRUN_BOUNDARY) {
1561
0
        foundBoundary = true;
1562
0
        break;
1563
0
      } else if (result == BuildTextRunsScanner::FB_STOPPED_AT_STOP_FRAME) {
1564
0
        break;
1565
0
      }
1566
0
      child = child->GetNextSibling();
1567
0
    }
1568
0
    if (foundBoundary)
1569
0
      break;
1570
0
    if (!stopAtFrame && state.mLastTextFrame && nextLineFirstTextFrame &&
1571
0
        !scanner.ContinueTextRunAcrossFrames(state.mLastTextFrame, nextLineFirstTextFrame)) {
1572
0
      // Found a usable textrun boundary at the end of the line
1573
0
      if (state.mSeenSpaceForLineBreakingOnThisLine)
1574
0
        break;
1575
0
      seenTextRunBoundaryOnLaterLine = true;
1576
0
    } else if (state.mSeenTextRunBoundaryOnThisLine) {
1577
0
      seenTextRunBoundaryOnLaterLine = true;
1578
0
    }
1579
0
    stopAtFrame = nullptr;
1580
0
    if (state.mFirstTextFrame) {
1581
0
      nextLineFirstTextFrame = state.mFirstTextFrame;
1582
0
    }
1583
0
  }
1584
0
  scanner.SetSkipIncompleteTextRuns(mayBeginInTextRun);
1585
0
1586
0
  // Now iterate over all text frames starting from the current line. First-in-flow
1587
0
  // text frames will be accumulated into textRunFrames as we go. When a
1588
0
  // text run boundary is required we flush textRunFrames ((re)building their
1589
0
  // gfxTextRuns as necessary).
1590
0
  bool seenStartLine = false;
1591
0
  uint32_t linesAfterStartLine = 0;
1592
0
  do {
1593
0
    nsBlockFrame::LineIterator line = forwardIterator.GetLine();
1594
0
    if (line->IsBlock())
1595
0
      break;
1596
0
    line->SetInvalidateTextRuns(false);
1597
0
    scanner.SetAtStartOfLine();
1598
0
    scanner.SetCommonAncestorWithLastFrame(nullptr);
1599
0
    nsIFrame* child = line->mFirstChild;
1600
0
    for (int32_t i = line->GetChildCount() - 1; i >= 0; --i) {
1601
0
      scanner.ScanFrame(child);
1602
0
      child = child->GetNextSibling();
1603
0
    }
1604
0
    if (line.get() == startLine.get()) {
1605
0
      seenStartLine = true;
1606
0
    }
1607
0
    if (seenStartLine) {
1608
0
      ++linesAfterStartLine;
1609
0
      if (linesAfterStartLine >= NUM_LINES_TO_BUILD_TEXT_RUNS && scanner.CanStopOnThisLine()) {
1610
0
        // Don't flush frames; we may be in the middle of a textrun
1611
0
        // that we can't end here. That's OK, we just won't build it.
1612
0
        // Note that we must already have finished the textrun for aForFrame,
1613
0
        // because we've seen the end of a textrun in a line after the line
1614
0
        // containing aForFrame.
1615
0
        scanner.FlushLineBreaks(nullptr);
1616
0
        // This flushes out mMappedFlows and mLineBreakBeforeFrames, which
1617
0
        // silences assertions in the scanner destructor.
1618
0
        scanner.ResetRunInfo();
1619
0
        return;
1620
0
      }
1621
0
    }
1622
0
  } while (forwardIterator.Next());
1623
0
1624
0
  // Set mStartOfLine so FlushFrames knows its textrun ends a line
1625
0
  scanner.SetAtStartOfLine();
1626
0
  scanner.FlushFrames(true, false);
1627
0
}
1628
1629
static char16_t*
1630
ExpandBuffer(char16_t* aDest, uint8_t* aSrc, uint32_t aCount)
1631
0
{
1632
0
  while (aCount) {
1633
0
    *aDest = *aSrc;
1634
0
    ++aDest;
1635
0
    ++aSrc;
1636
0
    --aCount;
1637
0
  }
1638
0
  return aDest;
1639
0
}
1640
1641
bool
1642
BuildTextRunsScanner::IsTextRunValidForMappedFlows(const gfxTextRun* aTextRun)
1643
0
{
1644
0
  if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::TEXT_IS_SIMPLE_FLOW) {
1645
0
    return mMappedFlows.Length() == 1 &&
1646
0
      mMappedFlows[0].mStartFrame == GetFrameForSimpleFlow(aTextRun) &&
1647
0
      mMappedFlows[0].mEndFrame == nullptr;
1648
0
  }
1649
0
1650
0
  auto userData = static_cast<TextRunUserData*>(aTextRun->GetUserData());
1651
0
  TextRunMappedFlow* userMappedFlows = GetMappedFlows(aTextRun);
1652
0
  if (userData->mMappedFlowCount != mMappedFlows.Length())
1653
0
    return false;
1654
0
  for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) {
1655
0
    if (userMappedFlows[i].mStartFrame != mMappedFlows[i].mStartFrame ||
1656
0
        int32_t(userMappedFlows[i].mContentLength) !=
1657
0
            mMappedFlows[i].GetContentEnd() - mMappedFlows[i].mStartFrame->GetContentOffset())
1658
0
      return false;
1659
0
  }
1660
0
  return true;
1661
0
}
1662
1663
/**
1664
 * This gets called when we need to make a text run for the current list of
1665
 * frames.
1666
 */
1667
void BuildTextRunsScanner::FlushFrames(bool aFlushLineBreaks, bool aSuppressTrailingBreak)
1668
0
{
1669
0
  RefPtr<gfxTextRun> textRun;
1670
0
  if (!mMappedFlows.IsEmpty()) {
1671
0
    if (!mSkipIncompleteTextRuns && mCurrentFramesAllSameTextRun &&
1672
0
        !!(mCurrentFramesAllSameTextRun->GetFlags2() & nsTextFrameUtils::Flags::TEXT_INCOMING_WHITESPACE) ==
1673
0
        !!(mCurrentRunContextInfo & nsTextFrameUtils::INCOMING_WHITESPACE) &&
1674
0
        !!(mCurrentFramesAllSameTextRun->GetFlags() & gfx::ShapedTextFlags::TEXT_INCOMING_ARABICCHAR) ==
1675
0
        !!(mCurrentRunContextInfo & nsTextFrameUtils::INCOMING_ARABICCHAR) &&
1676
0
        IsTextRunValidForMappedFlows(mCurrentFramesAllSameTextRun)) {
1677
0
      // Optimization: We do not need to (re)build the textrun.
1678
0
      textRun = mCurrentFramesAllSameTextRun;
1679
0
1680
0
      // Feed this run's text into the linebreaker to provide context.
1681
0
      if (!SetupLineBreakerContext(textRun)) {
1682
0
        return;
1683
0
      }
1684
0
1685
0
      // Update mNextRunContextInfo appropriately
1686
0
      mNextRunContextInfo = nsTextFrameUtils::INCOMING_NONE;
1687
0
      if (textRun->GetFlags2() & nsTextFrameUtils::Flags::TEXT_TRAILING_WHITESPACE) {
1688
0
        mNextRunContextInfo |= nsTextFrameUtils::INCOMING_WHITESPACE;
1689
0
      }
1690
0
      if (textRun->GetFlags() & gfx::ShapedTextFlags::TEXT_TRAILING_ARABICCHAR) {
1691
0
        mNextRunContextInfo |= nsTextFrameUtils::INCOMING_ARABICCHAR;
1692
0
      }
1693
0
    } else {
1694
0
      AutoTArray<uint8_t,BIG_TEXT_NODE_SIZE> buffer;
1695
0
      uint32_t bufferSize = mMaxTextLength*(mDoubleByteText ? 2 : 1);
1696
0
      if (bufferSize < mMaxTextLength || bufferSize == UINT32_MAX ||
1697
0
          !buffer.AppendElements(bufferSize, fallible)) {
1698
0
        return;
1699
0
      }
1700
0
      textRun = BuildTextRunForFrames(buffer.Elements());
1701
0
    }
1702
0
  }
1703
0
1704
0
  if (aFlushLineBreaks) {
1705
0
    FlushLineBreaks(aSuppressTrailingBreak ? nullptr : textRun.get());
1706
0
  }
1707
0
1708
0
  mCanStopOnThisLine = true;
1709
0
  ResetRunInfo();
1710
0
}
1711
1712
void BuildTextRunsScanner::FlushLineBreaks(gfxTextRun* aTrailingTextRun)
1713
0
{
1714
0
  bool trailingLineBreak;
1715
0
  nsresult rv = mLineBreaker.Reset(&trailingLineBreak);
1716
0
  // textRun may be null for various reasons, including because we constructed
1717
0
  // a partial textrun just to get the linebreaker and other state set up
1718
0
  // to build the next textrun.
1719
0
  if (NS_SUCCEEDED(rv) && trailingLineBreak && aTrailingTextRun) {
1720
0
    aTrailingTextRun->SetFlagBits(nsTextFrameUtils::Flags::TEXT_HAS_TRAILING_BREAK);
1721
0
  }
1722
0
1723
0
  for (uint32_t i = 0; i < mBreakSinks.Length(); ++i) {
1724
0
    // TODO cause frames associated with the textrun to be reflowed, if they
1725
0
    // aren't being reflowed already!
1726
0
    mBreakSinks[i]->Finish(mMissingFonts);
1727
0
  }
1728
0
  mBreakSinks.Clear();
1729
0
}
1730
1731
void BuildTextRunsScanner::AccumulateRunInfo(nsTextFrame* aFrame)
1732
0
{
1733
0
  if (mMaxTextLength != UINT32_MAX) {
1734
0
    NS_ASSERTION(mMaxTextLength < UINT32_MAX - aFrame->GetContentLength(), "integer overflow");
1735
0
    if (mMaxTextLength >= UINT32_MAX - aFrame->GetContentLength()) {
1736
0
      mMaxTextLength = UINT32_MAX;
1737
0
    } else {
1738
0
      mMaxTextLength += aFrame->GetContentLength();
1739
0
    }
1740
0
  }
1741
0
  mDoubleByteText |= aFrame->GetContent()->GetText()->Is2b();
1742
0
  mLastFrame = aFrame;
1743
0
  mCommonAncestorWithLastFrame = aFrame->GetParent();
1744
0
1745
0
  MappedFlow* mappedFlow = &mMappedFlows[mMappedFlows.Length() - 1];
1746
0
  NS_ASSERTION(mappedFlow->mStartFrame == aFrame ||
1747
0
               mappedFlow->GetContentEnd() == aFrame->GetContentOffset(),
1748
0
               "Overlapping or discontiguous frames => BAD");
1749
0
  mappedFlow->mEndFrame = aFrame->GetNextContinuation();
1750
0
  if (mCurrentFramesAllSameTextRun != aFrame->GetTextRun(mWhichTextRun)) {
1751
0
    mCurrentFramesAllSameTextRun = nullptr;
1752
0
  }
1753
0
1754
0
  if (mStartOfLine) {
1755
0
    mLineBreakBeforeFrames.AppendElement(aFrame);
1756
0
    mStartOfLine = false;
1757
0
  }
1758
0
}
1759
1760
static bool
1761
HasTerminalNewline(const nsTextFrame* aFrame)
1762
0
{
1763
0
  if (aFrame->GetContentLength() == 0)
1764
0
    return false;
1765
0
  const nsTextFragment* frag = aFrame->GetContent()->GetText();
1766
0
  return frag->CharAt(aFrame->GetContentEnd() - 1) == '\n';
1767
0
}
1768
1769
static gfxFont::Metrics
1770
GetFirstFontMetrics(gfxFontGroup* aFontGroup, bool aVerticalMetrics)
1771
0
{
1772
0
  if (!aFontGroup)
1773
0
    return gfxFont::Metrics();
1774
0
  gfxFont* font = aFontGroup->GetFirstValidFont();
1775
0
  return font->GetMetrics(aVerticalMetrics ? gfxFont::eVertical
1776
0
                                           : gfxFont::eHorizontal);
1777
0
}
1778
1779
static gfxFloat
1780
GetSpaceWidthAppUnits(const gfxTextRun* aTextRun)
1781
0
{
1782
0
  // Round the space width when converting to appunits the same way textruns
1783
0
  // do.
1784
0
  gfxFloat spaceWidthAppUnits =
1785
0
    NS_round(GetFirstFontMetrics(aTextRun->GetFontGroup(),
1786
0
                                 aTextRun->UseCenterBaseline()).spaceWidth *
1787
0
             aTextRun->GetAppUnitsPerDevUnit());
1788
0
1789
0
  return spaceWidthAppUnits;
1790
0
}
1791
1792
static gfxFloat
1793
GetMinTabAdvanceAppUnits(const gfxTextRun* aTextRun)
1794
0
{
1795
0
  gfxFloat chWidthAppUnits =
1796
0
    NS_round(GetFirstFontMetrics(aTextRun->GetFontGroup(),
1797
0
                                 aTextRun->IsVertical()).zeroOrAveCharWidth *
1798
0
             aTextRun->GetAppUnitsPerDevUnit());
1799
0
  return 0.5 * chWidthAppUnits;
1800
0
}
1801
1802
static nscoord
1803
LetterSpacing(nsIFrame* aFrame, const nsStyleText* aStyleText = nullptr)
1804
0
{
1805
0
  if (nsSVGUtils::IsInSVGTextSubtree(aFrame)) {
1806
0
    return 0;
1807
0
  }
1808
0
  if (!aStyleText) {
1809
0
    aStyleText = aFrame->StyleText();
1810
0
  }
1811
0
1812
0
  const nsStyleCoord& coord = aStyleText->mLetterSpacing;
1813
0
  if (eStyleUnit_Coord == coord.GetUnit()) {
1814
0
    return coord.GetCoordValue();
1815
0
  }
1816
0
  return 0;
1817
0
}
1818
1819
// This function converts non-coord values (e.g. percentages) to nscoord.
1820
static nscoord
1821
WordSpacing(nsIFrame* aFrame, const gfxTextRun* aTextRun,
1822
            const nsStyleText* aStyleText = nullptr)
1823
0
{
1824
0
  if (nsSVGUtils::IsInSVGTextSubtree(aFrame)) {
1825
0
    return 0;
1826
0
  }
1827
0
  if (!aStyleText) {
1828
0
    aStyleText = aFrame->StyleText();
1829
0
  }
1830
0
1831
0
  const nsStyleCoord& coord = aStyleText->mWordSpacing;
1832
0
  if (coord.IsCoordPercentCalcUnit()) {
1833
0
    nscoord pctBasis = coord.HasPercent() ? GetSpaceWidthAppUnits(aTextRun) : 0;
1834
0
    return coord.ComputeCoordPercentCalc(pctBasis);
1835
0
  }
1836
0
  return 0;
1837
0
}
1838
1839
// Returns gfxTextRunFactory::TEXT_ENABLE_SPACING if non-standard
1840
// letter-spacing or word-spacing is present.
1841
static gfx::ShapedTextFlags
1842
GetSpacingFlags(nsIFrame* aFrame, const nsStyleText* aStyleText = nullptr)
1843
0
{
1844
0
  if (nsSVGUtils::IsInSVGTextSubtree(aFrame)) {
1845
0
    return gfx::ShapedTextFlags();
1846
0
  }
1847
0
1848
0
  const nsStyleText* styleText = aFrame->StyleText();
1849
0
  const nsStyleCoord& ls = styleText->mLetterSpacing;
1850
0
  const nsStyleCoord& ws = styleText->mWordSpacing;
1851
0
1852
0
  // It's possible to have a calc() value that computes to zero but for which
1853
0
  // IsDefinitelyZero() is false, in which case we'll return
1854
0
  // TEXT_ENABLE_SPACING unnecessarily. That's ok because such cases are likely
1855
0
  // to be rare, and avoiding TEXT_ENABLE_SPACING is just an optimization.
1856
0
  bool nonStandardSpacing =
1857
0
    (eStyleUnit_Coord == ls.GetUnit() && ls.GetCoordValue() != 0) ||
1858
0
    (eStyleUnit_Coord == ws.GetUnit() && ws.GetCoordValue() != 0) ||
1859
0
    (eStyleUnit_Percent == ws.GetUnit() && ws.GetPercentValue() != 0) ||
1860
0
    (eStyleUnit_Calc == ws.GetUnit() && !ws.GetCalcValue()->IsDefinitelyZero());
1861
0
1862
0
  return nonStandardSpacing
1863
0
    ? gfx::ShapedTextFlags::TEXT_ENABLE_SPACING
1864
0
    : gfx::ShapedTextFlags();
1865
0
}
1866
1867
bool
1868
BuildTextRunsScanner::ContinueTextRunAcrossFrames(nsTextFrame* aFrame1, nsTextFrame* aFrame2)
1869
0
{
1870
0
  // We don't need to check font size inflation, since
1871
0
  // |FindLineContainer| above (via |nsIFrame::CanContinueTextRun|)
1872
0
  // ensures that text runs never cross block boundaries.  This means
1873
0
  // that the font size inflation on all text frames in the text run is
1874
0
  // already guaranteed to be the same as each other (and for the line
1875
0
  // container).
1876
0
  if (mBidiEnabled) {
1877
0
    FrameBidiData data1 = aFrame1->GetBidiData();
1878
0
    FrameBidiData data2 = aFrame2->GetBidiData();
1879
0
    if (data1.embeddingLevel != data2.embeddingLevel ||
1880
0
        data2.precedingControl != kBidiLevelNone) {
1881
0
      return false;
1882
0
    }
1883
0
  }
1884
0
1885
0
  ComputedStyle* sc1 = aFrame1->Style();
1886
0
  const nsStyleText* textStyle1 = sc1->StyleText();
1887
0
  // If the first frame ends in a preformatted newline, then we end the textrun
1888
0
  // here. This avoids creating giant textruns for an entire plain text file.
1889
0
  // Note that we create a single text frame for a preformatted text node,
1890
0
  // even if it has newlines in it, so typically we won't see trailing newlines
1891
0
  // until after reflow has broken up the frame into one (or more) frames per
1892
0
  // line. That's OK though.
1893
0
  if (textStyle1->NewlineIsSignificant(aFrame1) && HasTerminalNewline(aFrame1))
1894
0
    return false;
1895
0
1896
0
  if (aFrame1->GetContent() == aFrame2->GetContent() &&
1897
0
      aFrame1->GetNextInFlow() != aFrame2) {
1898
0
    // aFrame2 must be a non-fluid continuation of aFrame1. This can happen
1899
0
    // sometimes when the unicode-bidi property is used; the bidi resolver
1900
0
    // breaks text into different frames even though the text has the same
1901
0
    // direction. We can't allow these two frames to share the same textrun
1902
0
    // because that would violate our invariant that two flows in the same
1903
0
    // textrun have different content elements.
1904
0
    return false;
1905
0
  }
1906
0
1907
0
  ComputedStyle* sc2 = aFrame2->Style();
1908
0
  const nsStyleText* textStyle2 = sc2->StyleText();
1909
0
  if (sc1 == sc2)
1910
0
    return true;
1911
0
1912
0
  nsPresContext* pc = aFrame1->PresContext();
1913
0
  MOZ_ASSERT(pc == aFrame2->PresContext());
1914
0
1915
0
  const nsStyleFont* fontStyle1 = sc1->StyleFont();
1916
0
  const nsStyleFont* fontStyle2 = sc2->StyleFont();
1917
0
  nscoord letterSpacing1 = LetterSpacing(aFrame1);
1918
0
  nscoord letterSpacing2 = LetterSpacing(aFrame2);
1919
0
  return fontStyle1->mFont == fontStyle2->mFont &&
1920
0
    fontStyle1->mLanguage == fontStyle2->mLanguage &&
1921
0
    textStyle1->mTextTransform == textStyle2->mTextTransform &&
1922
0
    nsLayoutUtils::GetTextRunFlagsForStyle(sc1, pc, fontStyle1, textStyle1, letterSpacing1) ==
1923
0
      nsLayoutUtils::GetTextRunFlagsForStyle(sc2, pc, fontStyle2, textStyle2, letterSpacing2);
1924
0
}
1925
1926
void BuildTextRunsScanner::ScanFrame(nsIFrame* aFrame)
1927
0
{
1928
0
  LayoutFrameType frameType = aFrame->Type();
1929
0
  if (frameType == LayoutFrameType::RubyTextContainer) {
1930
0
    // Don't include any ruby text container into the text run.
1931
0
    return;
1932
0
  }
1933
0
1934
0
  // First check if we can extend the current mapped frame block. This is common.
1935
0
  if (mMappedFlows.Length() > 0) {
1936
0
    MappedFlow* mappedFlow = &mMappedFlows[mMappedFlows.Length() - 1];
1937
0
    if (mappedFlow->mEndFrame == aFrame &&
1938
0
        (aFrame->GetStateBits() & NS_FRAME_IS_FLUID_CONTINUATION)) {
1939
0
      NS_ASSERTION(frameType == LayoutFrameType::Text,
1940
0
                   "Flow-sibling of a text frame is not a text frame?");
1941
0
1942
0
      // Don't do this optimization if mLastFrame has a terminal newline...
1943
0
      // it's quite likely preformatted and we might want to end the textrun here.
1944
0
      // This is almost always true:
1945
0
      if (mLastFrame->Style() == aFrame->Style() &&
1946
0
          !HasTerminalNewline(mLastFrame)) {
1947
0
        AccumulateRunInfo(static_cast<nsTextFrame*>(aFrame));
1948
0
        return;
1949
0
      }
1950
0
    }
1951
0
  }
1952
0
1953
0
  // Now see if we can add a new set of frames to the current textrun
1954
0
  if (frameType == LayoutFrameType::Text) {
1955
0
    nsTextFrame* frame = static_cast<nsTextFrame*>(aFrame);
1956
0
1957
0
    if (mLastFrame) {
1958
0
      if (!ContinueTextRunAcrossFrames(mLastFrame, frame)) {
1959
0
        FlushFrames(false, false);
1960
0
      } else {
1961
0
        if (mLastFrame->GetContent() == frame->GetContent()) {
1962
0
          AccumulateRunInfo(frame);
1963
0
          return;
1964
0
        }
1965
0
      }
1966
0
    }
1967
0
1968
0
    MappedFlow* mappedFlow = mMappedFlows.AppendElement();
1969
0
    if (!mappedFlow)
1970
0
      return;
1971
0
1972
0
    mappedFlow->mStartFrame = frame;
1973
0
    mappedFlow->mAncestorControllingInitialBreak = mCommonAncestorWithLastFrame;
1974
0
1975
0
    AccumulateRunInfo(frame);
1976
0
    if (mMappedFlows.Length() == 1) {
1977
0
      mCurrentFramesAllSameTextRun = frame->GetTextRun(mWhichTextRun);
1978
0
      mCurrentRunContextInfo = mNextRunContextInfo;
1979
0
    }
1980
0
    return;
1981
0
  }
1982
0
1983
0
  FrameTextTraversal traversal = CanTextCrossFrameBoundary(aFrame);
1984
0
  bool isBR = frameType == LayoutFrameType::Br;
1985
0
  if (!traversal.mLineBreakerCanCrossFrameBoundary) {
1986
0
    // BR frames are special. We do not need or want to record a break opportunity
1987
0
    // before a BR frame.
1988
0
    FlushFrames(true, isBR);
1989
0
    mCommonAncestorWithLastFrame = aFrame;
1990
0
    mNextRunContextInfo &= ~nsTextFrameUtils::INCOMING_WHITESPACE;
1991
0
    mStartOfLine = false;
1992
0
  } else if (!traversal.mTextRunCanCrossFrameBoundary) {
1993
0
    FlushFrames(false, false);
1994
0
  }
1995
0
1996
0
  for (nsIFrame* f = traversal.NextFrameToScan(); f;
1997
0
       f = traversal.NextFrameToScan()) {
1998
0
    ScanFrame(f);
1999
0
  }
2000
0
2001
0
  if (!traversal.mLineBreakerCanCrossFrameBoundary) {
2002
0
    // Really if we're a BR frame this is unnecessary since descendInto will be
2003
0
    // false. In fact this whole "if" statement should move into the descendInto.
2004
0
    FlushFrames(true, isBR);
2005
0
    mCommonAncestorWithLastFrame = aFrame;
2006
0
    mNextRunContextInfo &= ~nsTextFrameUtils::INCOMING_WHITESPACE;
2007
0
  } else if (!traversal.mTextRunCanCrossFrameBoundary) {
2008
0
    FlushFrames(false, false);
2009
0
  }
2010
0
2011
0
  LiftCommonAncestorWithLastFrameToParent(aFrame->GetParent());
2012
0
}
2013
2014
nsTextFrame*
2015
BuildTextRunsScanner::GetNextBreakBeforeFrame(uint32_t* aIndex)
2016
0
{
2017
0
  uint32_t index = *aIndex;
2018
0
  if (index >= mLineBreakBeforeFrames.Length())
2019
0
    return nullptr;
2020
0
  *aIndex = index + 1;
2021
0
  return static_cast<nsTextFrame*>(mLineBreakBeforeFrames.ElementAt(index));
2022
0
}
2023
2024
static gfxFontGroup*
2025
GetFontGroupForFrame(const nsIFrame* aFrame, float aFontSizeInflation,
2026
                     nsFontMetrics** aOutFontMetrics = nullptr)
2027
0
{
2028
0
  RefPtr<nsFontMetrics> metrics =
2029
0
    nsLayoutUtils::GetFontMetricsForFrame(aFrame, aFontSizeInflation);
2030
0
  gfxFontGroup* fontGroup = metrics->GetThebesFontGroup();
2031
0
2032
0
  // Populate outparam before we return:
2033
0
  if (aOutFontMetrics) {
2034
0
    metrics.forget(aOutFontMetrics);
2035
0
  }
2036
0
  // XXX this is a bit bogus, we're releasing 'metrics' so the
2037
0
  // returned font-group might actually be torn down, although because
2038
0
  // of the way the device context caches font metrics, this seems to
2039
0
  // not actually happen. But we should fix this.
2040
0
  return fontGroup;
2041
0
}
2042
2043
static already_AddRefed<DrawTarget>
2044
CreateReferenceDrawTarget(const nsTextFrame* aTextFrame)
2045
0
{
2046
0
  RefPtr<gfxContext> ctx =
2047
0
    aTextFrame->PresShell()->CreateReferenceRenderingContext();
2048
0
  RefPtr<DrawTarget> dt = ctx->GetDrawTarget();
2049
0
  return dt.forget();
2050
0
}
2051
2052
static already_AddRefed<gfxTextRun>
2053
GetHyphenTextRun(const gfxTextRun* aTextRun, DrawTarget* aDrawTarget,
2054
                 nsTextFrame* aTextFrame)
2055
0
{
2056
0
  RefPtr<DrawTarget> dt = aDrawTarget;
2057
0
  if (!dt) {
2058
0
    dt = CreateReferenceDrawTarget(aTextFrame);
2059
0
    if (!dt) {
2060
0
      return nullptr;
2061
0
    }
2062
0
  }
2063
0
2064
0
  return aTextRun->GetFontGroup()->
2065
0
    MakeHyphenTextRun(dt, aTextRun->GetAppUnitsPerDevUnit());
2066
0
}
2067
2068
already_AddRefed<gfxTextRun>
2069
BuildTextRunsScanner::BuildTextRunForFrames(void* aTextBuffer)
2070
0
{
2071
0
  gfxSkipChars skipChars;
2072
0
2073
0
  const void* textPtr = aTextBuffer;
2074
0
  bool anyTextTransformStyle = false;
2075
0
  bool anyMathMLStyling = false;
2076
0
  bool anyTextEmphasis = false;
2077
0
  uint8_t sstyScriptLevel = 0;
2078
0
  uint32_t mathFlags = 0;
2079
0
  gfx::ShapedTextFlags flags = gfx::ShapedTextFlags();
2080
0
  nsTextFrameUtils::Flags flags2 = nsTextFrameUtils::Flags::TEXT_NO_BREAKS;
2081
0
2082
0
  if (mCurrentRunContextInfo & nsTextFrameUtils::INCOMING_WHITESPACE) {
2083
0
    flags2 |= nsTextFrameUtils::Flags::TEXT_INCOMING_WHITESPACE;
2084
0
  }
2085
0
  if (mCurrentRunContextInfo & nsTextFrameUtils::INCOMING_ARABICCHAR) {
2086
0
    flags |= gfx::ShapedTextFlags::TEXT_INCOMING_ARABICCHAR;
2087
0
  }
2088
0
2089
0
  AutoTArray<int32_t,50> textBreakPoints;
2090
0
  TextRunUserData dummyData;
2091
0
  TextRunMappedFlow dummyMappedFlow;
2092
0
  TextRunMappedFlow* userMappedFlows;
2093
0
  TextRunUserData* userData;
2094
0
  TextRunUserData* userDataToDestroy;
2095
0
  // If the situation is particularly simple (and common) we don't need to
2096
0
  // allocate userData.
2097
0
  if (mMappedFlows.Length() == 1 && !mMappedFlows[0].mEndFrame &&
2098
0
      mMappedFlows[0].mStartFrame->GetContentOffset() == 0) {
2099
0
    userData = &dummyData;
2100
0
    userMappedFlows = &dummyMappedFlow;
2101
0
    userDataToDestroy = nullptr;
2102
0
    dummyData.mMappedFlowCount = mMappedFlows.Length();
2103
0
    dummyData.mLastFlowIndex = 0;
2104
0
  } else {
2105
0
    userData = CreateUserData(mMappedFlows.Length());
2106
0
    userMappedFlows = reinterpret_cast<TextRunMappedFlow*>(userData + 1);
2107
0
    userDataToDestroy = userData;
2108
0
  }
2109
0
2110
0
  uint32_t currentTransformedTextOffset = 0;
2111
0
2112
0
  uint32_t nextBreakIndex = 0;
2113
0
  nsTextFrame* nextBreakBeforeFrame = GetNextBreakBeforeFrame(&nextBreakIndex);
2114
0
  bool isSVG = nsSVGUtils::IsInSVGTextSubtree(mLineContainer);
2115
0
  bool enabledJustification =
2116
0
    (mLineContainer->StyleText()->mTextAlign == NS_STYLE_TEXT_ALIGN_JUSTIFY ||
2117
0
     mLineContainer->StyleText()->mTextAlignLast == NS_STYLE_TEXT_ALIGN_JUSTIFY);
2118
0
2119
0
  const nsStyleText* textStyle = nullptr;
2120
0
  const nsStyleFont* fontStyle = nullptr;
2121
0
  ComputedStyle* lastComputedStyle = nullptr;
2122
0
  for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) {
2123
0
    MappedFlow* mappedFlow = &mMappedFlows[i];
2124
0
    nsTextFrame* f = mappedFlow->mStartFrame;
2125
0
2126
0
    lastComputedStyle = f->Style();
2127
0
    // Detect use of text-transform or font-variant anywhere in the run
2128
0
    textStyle = f->StyleText();
2129
0
    if (NS_STYLE_TEXT_TRANSFORM_NONE != textStyle->mTextTransform ||
2130
0
        // text-combine-upright requires converting from full-width
2131
0
        // characters to non-full-width correspendent in some cases.
2132
0
        lastComputedStyle->IsTextCombined()) {
2133
0
      anyTextTransformStyle = true;
2134
0
    }
2135
0
    if (textStyle->HasTextEmphasis()) {
2136
0
      anyTextEmphasis = true;
2137
0
    }
2138
0
    flags |= GetSpacingFlags(f);
2139
0
    nsTextFrameUtils::CompressionMode compression =
2140
0
      GetCSSWhitespaceToCompressionMode(f, textStyle);
2141
0
    if ((enabledJustification || f->ShouldSuppressLineBreak()) &&
2142
0
        !textStyle->WhiteSpaceIsSignificant() && !isSVG) {
2143
0
      flags |= gfx::ShapedTextFlags::TEXT_ENABLE_SPACING;
2144
0
    }
2145
0
    fontStyle = f->StyleFont();
2146
0
    nsIFrame* parent = mLineContainer->GetParent();
2147
0
    if (NS_MATHML_MATHVARIANT_NONE != fontStyle->mMathVariant) {
2148
0
      if (NS_MATHML_MATHVARIANT_NORMAL != fontStyle->mMathVariant) {
2149
0
        anyMathMLStyling = true;
2150
0
      }
2151
0
    } else if (mLineContainer->GetStateBits() & NS_FRAME_IS_IN_SINGLE_CHAR_MI) {
2152
0
      flags2 |= nsTextFrameUtils::Flags::TEXT_IS_SINGLE_CHAR_MI;
2153
0
      anyMathMLStyling = true;
2154
0
      // Test for fontstyle attribute as StyleFont() may not be accurate
2155
0
      // To be consistent in terms of ignoring CSS style changes, fontweight
2156
0
      // gets checked too.
2157
0
      if (parent) {
2158
0
        nsIContent* content = parent->GetContent();
2159
0
        if (content && content->IsElement()) {
2160
0
          if (content->AsElement()->AttrValueIs(kNameSpaceID_None,
2161
0
                                                nsGkAtoms::fontstyle_,
2162
0
                                                NS_LITERAL_STRING("normal"),
2163
0
                                                eCaseMatters)) {
2164
0
            mathFlags |= MathMLTextRunFactory::MATH_FONT_STYLING_NORMAL;
2165
0
          }
2166
0
          if (content->AsElement()->AttrValueIs(kNameSpaceID_None,
2167
0
                                                nsGkAtoms::fontweight_,
2168
0
                                                NS_LITERAL_STRING("bold"),
2169
0
                                                eCaseMatters)) {
2170
0
            mathFlags |= MathMLTextRunFactory::MATH_FONT_WEIGHT_BOLD;
2171
0
          }
2172
0
        }
2173
0
      }
2174
0
    }
2175
0
    if (mLineContainer->HasAnyStateBits(TEXT_IS_IN_TOKEN_MATHML)) {
2176
0
      // All MathML tokens except <mtext> use 'math' script.
2177
0
      if (!(parent && parent->GetContent() &&
2178
0
          parent->GetContent()->IsMathMLElement(nsGkAtoms::mtext_))) {
2179
0
        flags |= gfx::ShapedTextFlags::TEXT_USE_MATH_SCRIPT;
2180
0
      }
2181
0
      nsIMathMLFrame* mathFrame = do_QueryFrame(parent);
2182
0
      if (mathFrame) {
2183
0
        nsPresentationData presData;
2184
0
        mathFrame->GetPresentationData(presData);
2185
0
        if (NS_MATHML_IS_DTLS_SET(presData.flags)) {
2186
0
          mathFlags |= MathMLTextRunFactory::MATH_FONT_FEATURE_DTLS;
2187
0
          anyMathMLStyling = true;
2188
0
        }
2189
0
      }
2190
0
    }
2191
0
    nsIFrame* child = mLineContainer;
2192
0
    uint8_t oldScriptLevel = 0;
2193
0
    while (parent &&
2194
0
           child->HasAnyStateBits(NS_FRAME_MATHML_SCRIPT_DESCENDANT)) {
2195
0
      // Reconstruct the script level ignoring any user overrides. It is
2196
0
      // calculated this way instead of using scriptlevel to ensure the
2197
0
      // correct ssty font feature setting is used even if the user sets a
2198
0
      // different (especially negative) scriptlevel.
2199
0
      nsIMathMLFrame* mathFrame= do_QueryFrame(parent);
2200
0
      if (mathFrame) {
2201
0
        sstyScriptLevel += mathFrame->ScriptIncrement(child);
2202
0
      }
2203
0
      if (sstyScriptLevel < oldScriptLevel) {
2204
0
        // overflow
2205
0
        sstyScriptLevel = UINT8_MAX;
2206
0
        break;
2207
0
      }
2208
0
      child = parent;
2209
0
      parent = parent->GetParent();
2210
0
      oldScriptLevel = sstyScriptLevel;
2211
0
    }
2212
0
    if (sstyScriptLevel) {
2213
0
      anyMathMLStyling = true;
2214
0
    }
2215
0
2216
0
    // Figure out what content is included in this flow.
2217
0
    nsIContent* content = f->GetContent();
2218
0
    const nsTextFragment* frag = content->GetText();
2219
0
    int32_t contentStart = mappedFlow->mStartFrame->GetContentOffset();
2220
0
    int32_t contentEnd = mappedFlow->GetContentEnd();
2221
0
    int32_t contentLength = contentEnd - contentStart;
2222
0
2223
0
    TextRunMappedFlow* newFlow = &userMappedFlows[i];
2224
0
    newFlow->mStartFrame = mappedFlow->mStartFrame;
2225
0
    newFlow->mDOMOffsetToBeforeTransformOffset = skipChars.GetOriginalCharCount() -
2226
0
      mappedFlow->mStartFrame->GetContentOffset();
2227
0
    newFlow->mContentLength = contentLength;
2228
0
2229
0
    while (nextBreakBeforeFrame && nextBreakBeforeFrame->GetContent() == content) {
2230
0
      textBreakPoints.AppendElement(
2231
0
          nextBreakBeforeFrame->GetContentOffset() + newFlow->mDOMOffsetToBeforeTransformOffset);
2232
0
      nextBreakBeforeFrame = GetNextBreakBeforeFrame(&nextBreakIndex);
2233
0
    }
2234
0
2235
0
    nsTextFrameUtils::Flags analysisFlags;
2236
0
    if (frag->Is2b()) {
2237
0
      NS_ASSERTION(mDoubleByteText, "Wrong buffer char size!");
2238
0
      char16_t* bufStart = static_cast<char16_t*>(aTextBuffer);
2239
0
      char16_t* bufEnd = nsTextFrameUtils::TransformText(
2240
0
          frag->Get2b() + contentStart, contentLength, bufStart,
2241
0
          compression, &mNextRunContextInfo, &skipChars, &analysisFlags);
2242
0
      aTextBuffer = bufEnd;
2243
0
      currentTransformedTextOffset = bufEnd - static_cast<const char16_t*>(textPtr);
2244
0
    } else {
2245
0
      if (mDoubleByteText) {
2246
0
        // Need to expand the text. First transform it into a temporary buffer,
2247
0
        // then expand.
2248
0
        AutoTArray<uint8_t,BIG_TEXT_NODE_SIZE> tempBuf;
2249
0
        uint8_t* bufStart = tempBuf.AppendElements(contentLength, fallible);
2250
0
        if (!bufStart) {
2251
0
          DestroyUserData(userDataToDestroy);
2252
0
          return nullptr;
2253
0
        }
2254
0
        uint8_t* end = nsTextFrameUtils::TransformText(
2255
0
            reinterpret_cast<const uint8_t*>(frag->Get1b()) + contentStart, contentLength,
2256
0
            bufStart, compression, &mNextRunContextInfo, &skipChars, &analysisFlags);
2257
0
        aTextBuffer = ExpandBuffer(static_cast<char16_t*>(aTextBuffer),
2258
0
                                   tempBuf.Elements(), end - tempBuf.Elements());
2259
0
        currentTransformedTextOffset =
2260
0
          static_cast<char16_t*>(aTextBuffer) - static_cast<const char16_t*>(textPtr);
2261
0
      } else {
2262
0
        uint8_t* bufStart = static_cast<uint8_t*>(aTextBuffer);
2263
0
        uint8_t* end = nsTextFrameUtils::TransformText(
2264
0
            reinterpret_cast<const uint8_t*>(frag->Get1b()) + contentStart, contentLength,
2265
0
            bufStart, compression, &mNextRunContextInfo, &skipChars, &analysisFlags);
2266
0
        aTextBuffer = end;
2267
0
        currentTransformedTextOffset = end - static_cast<const uint8_t*>(textPtr);
2268
0
      }
2269
0
    }
2270
0
    flags2 |= analysisFlags;
2271
0
  }
2272
0
2273
0
  void* finalUserData;
2274
0
  if (userData == &dummyData) {
2275
0
    flags2 |= nsTextFrameUtils::Flags::TEXT_IS_SIMPLE_FLOW;
2276
0
    userData = nullptr;
2277
0
    finalUserData = mMappedFlows[0].mStartFrame;
2278
0
  } else {
2279
0
    finalUserData = userData;
2280
0
  }
2281
0
2282
0
  uint32_t transformedLength = currentTransformedTextOffset;
2283
0
2284
0
  // Now build the textrun
2285
0
  nsTextFrame* firstFrame = mMappedFlows[0].mStartFrame;
2286
0
  float fontInflation;
2287
0
  if (mWhichTextRun == nsTextFrame::eNotInflated) {
2288
0
    fontInflation = 1.0f;
2289
0
  } else {
2290
0
    fontInflation = nsLayoutUtils::FontSizeInflationFor(firstFrame);
2291
0
  }
2292
0
2293
0
  gfxFontGroup* fontGroup = GetFontGroupForFrame(firstFrame, fontInflation);
2294
0
  if (!fontGroup) {
2295
0
    DestroyUserData(userDataToDestroy);
2296
0
    return nullptr;
2297
0
  }
2298
0
2299
0
  if (flags2 & nsTextFrameUtils::Flags::TEXT_HAS_TAB) {
2300
0
    flags |= gfx::ShapedTextFlags::TEXT_ENABLE_SPACING;
2301
0
  }
2302
0
  if (flags2 & nsTextFrameUtils::Flags::TEXT_HAS_SHY) {
2303
0
    flags |= gfx::ShapedTextFlags::TEXT_ENABLE_HYPHEN_BREAKS;
2304
0
  }
2305
0
  if (mBidiEnabled && (IS_LEVEL_RTL(firstFrame->GetEmbeddingLevel()))) {
2306
0
    flags |= gfx::ShapedTextFlags::TEXT_IS_RTL;
2307
0
  }
2308
0
  if (mNextRunContextInfo & nsTextFrameUtils::INCOMING_WHITESPACE) {
2309
0
    flags2 |= nsTextFrameUtils::Flags::TEXT_TRAILING_WHITESPACE;
2310
0
  }
2311
0
  if (mNextRunContextInfo & nsTextFrameUtils::INCOMING_ARABICCHAR) {
2312
0
    flags |= gfx::ShapedTextFlags::TEXT_TRAILING_ARABICCHAR;
2313
0
  }
2314
0
  // ContinueTextRunAcrossFrames guarantees that it doesn't matter which
2315
0
  // frame's style is used, so we use a mixture of the first frame and
2316
0
  // last frame's style
2317
0
  flags |= nsLayoutUtils::GetTextRunFlagsForStyle(lastComputedStyle,
2318
0
      firstFrame->PresContext(), fontStyle, textStyle,
2319
0
      LetterSpacing(firstFrame, textStyle));
2320
0
  // XXX this is a bit of a hack. For performance reasons, if we're favouring
2321
0
  // performance over quality, don't try to get accurate glyph extents.
2322
0
  if (!(flags & gfx::ShapedTextFlags::TEXT_OPTIMIZE_SPEED)) {
2323
0
    flags |= gfx::ShapedTextFlags::TEXT_NEED_BOUNDING_BOX;
2324
0
  }
2325
0
2326
0
  // Convert linebreak coordinates to transformed string offsets
2327
0
  NS_ASSERTION(nextBreakIndex == mLineBreakBeforeFrames.Length(),
2328
0
               "Didn't find all the frames to break-before...");
2329
0
  gfxSkipCharsIterator iter(skipChars);
2330
0
  AutoTArray<uint32_t,50> textBreakPointsAfterTransform;
2331
0
  for (uint32_t i = 0; i < textBreakPoints.Length(); ++i) {
2332
0
    nsTextFrameUtils::AppendLineBreakOffset(&textBreakPointsAfterTransform,
2333
0
            iter.ConvertOriginalToSkipped(textBreakPoints[i]));
2334
0
  }
2335
0
  if (mStartOfLine) {
2336
0
    nsTextFrameUtils::AppendLineBreakOffset(&textBreakPointsAfterTransform,
2337
0
                                            transformedLength);
2338
0
  }
2339
0
2340
0
  // Setup factory chain
2341
0
  UniquePtr<nsTransformingTextRunFactory> transformingFactory;
2342
0
  if (anyTextTransformStyle) {
2343
0
    transformingFactory =
2344
0
      MakeUnique<nsCaseTransformTextRunFactory>(std::move(transformingFactory));
2345
0
  }
2346
0
  if (anyMathMLStyling) {
2347
0
    transformingFactory =
2348
0
      MakeUnique<MathMLTextRunFactory>(std::move(transformingFactory), mathFlags,
2349
0
                                       sstyScriptLevel, fontInflation);
2350
0
  }
2351
0
  nsTArray<RefPtr<nsTransformedCharStyle>> styles;
2352
0
  if (transformingFactory) {
2353
0
    iter.SetOriginalOffset(0);
2354
0
    for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) {
2355
0
      MappedFlow* mappedFlow = &mMappedFlows[i];
2356
0
      nsTextFrame* f;
2357
0
      ComputedStyle* sc = nullptr;
2358
0
      RefPtr<nsTransformedCharStyle> charStyle;
2359
0
      for (f = mappedFlow->mStartFrame; f != mappedFlow->mEndFrame;
2360
0
           f = f->GetNextContinuation()) {
2361
0
        uint32_t offset = iter.GetSkippedOffset();
2362
0
        iter.AdvanceOriginal(f->GetContentLength());
2363
0
        uint32_t end = iter.GetSkippedOffset();
2364
0
        // Text-combined frames have content-dependent transform, so we
2365
0
        // want to create new nsTransformedCharStyle for them anyway.
2366
0
        if (sc != f->Style() || sc->IsTextCombined()) {
2367
0
          sc = f->Style();
2368
0
          charStyle = new nsTransformedCharStyle(sc, f->PresContext());
2369
0
          if (sc->IsTextCombined() && f->CountGraphemeClusters() > 1) {
2370
0
            charStyle->mForceNonFullWidth = true;
2371
0
          }
2372
0
        }
2373
0
        uint32_t j;
2374
0
        for (j = offset; j < end; ++j) {
2375
0
          styles.AppendElement(charStyle);
2376
0
        }
2377
0
      }
2378
0
    }
2379
0
    flags2 |= nsTextFrameUtils::Flags::TEXT_IS_TRANSFORMED;
2380
0
    NS_ASSERTION(iter.GetSkippedOffset() == transformedLength,
2381
0
                 "We didn't cover all the characters in the text run!");
2382
0
  }
2383
0
2384
0
  RefPtr<gfxTextRun> textRun;
2385
0
  gfxTextRunFactory::Parameters params =
2386
0
      { mDrawTarget, finalUserData, &skipChars,
2387
0
        textBreakPointsAfterTransform.Elements(),
2388
0
        uint32_t(textBreakPointsAfterTransform.Length()),
2389
0
        int32_t(firstFrame->PresContext()->AppUnitsPerDevPixel())};
2390
0
2391
0
  if (mDoubleByteText) {
2392
0
    const char16_t* text = static_cast<const char16_t*>(textPtr);
2393
0
    if (transformingFactory) {
2394
0
      textRun = transformingFactory->MakeTextRun(text, transformedLength,
2395
0
                                                 &params, fontGroup, flags, flags2,
2396
0
                                                 std::move(styles), true);
2397
0
      if (textRun) {
2398
0
        // ownership of the factory has passed to the textrun
2399
0
        // TODO: bug 1285316: clean up ownership transfer from the factory to
2400
0
        // the textrun
2401
0
        Unused << transformingFactory.release();
2402
0
      }
2403
0
    } else {
2404
0
      textRun = fontGroup->MakeTextRun(text, transformedLength, &params,
2405
0
                                       flags, flags2, mMissingFonts);
2406
0
    }
2407
0
  } else {
2408
0
    const uint8_t* text = static_cast<const uint8_t*>(textPtr);
2409
0
    flags |= gfx::ShapedTextFlags::TEXT_IS_8BIT;
2410
0
    if (transformingFactory) {
2411
0
      textRun = transformingFactory->MakeTextRun(text, transformedLength,
2412
0
                                                 &params, fontGroup, flags, flags2,
2413
0
                                                 std::move(styles), true);
2414
0
      if (textRun) {
2415
0
        // ownership of the factory has passed to the textrun
2416
0
        // TODO: bug 1285316: clean up ownership transfer from the factory to
2417
0
        // the textrun
2418
0
        Unused << transformingFactory.release();
2419
0
      }
2420
0
    } else {
2421
0
      textRun = fontGroup->MakeTextRun(text, transformedLength, &params,
2422
0
                                       flags, flags2, mMissingFonts);
2423
0
    }
2424
0
  }
2425
0
  if (!textRun) {
2426
0
    DestroyUserData(userDataToDestroy);
2427
0
    return nullptr;
2428
0
  }
2429
0
2430
0
  // We have to set these up after we've created the textrun, because
2431
0
  // the breaks may be stored in the textrun during this very call.
2432
0
  // This is a bit annoying because it requires another loop over the frames
2433
0
  // making up the textrun, but I don't see a way to avoid this.
2434
0
  SetupBreakSinksForTextRun(textRun.get(), textPtr);
2435
0
2436
0
  if (anyTextEmphasis) {
2437
0
    SetupTextEmphasisForTextRun(textRun.get(), textPtr);
2438
0
  }
2439
0
2440
0
  if (mSkipIncompleteTextRuns) {
2441
0
    mSkipIncompleteTextRuns = !TextContainsLineBreakerWhiteSpace(textPtr,
2442
0
        transformedLength, mDoubleByteText);
2443
0
    // Since we're doing to destroy the user data now, avoid a dangling
2444
0
    // pointer. Strictly speaking we don't need to do this since it should
2445
0
    // not be used (since this textrun will not be used and will be
2446
0
    // itself deleted soon), but it's always better to not have dangling
2447
0
    // pointers around.
2448
0
    textRun->SetUserData(nullptr);
2449
0
    DestroyUserData(userDataToDestroy);
2450
0
    return nullptr;
2451
0
  }
2452
0
2453
0
  // Actually wipe out the textruns associated with the mapped frames and associate
2454
0
  // those frames with this text run.
2455
0
  AssignTextRun(textRun.get(), fontInflation);
2456
0
  return textRun.forget();
2457
0
}
2458
2459
// This is a cut-down version of BuildTextRunForFrames used to set up
2460
// context for the line-breaker, when the textrun has already been created.
2461
// So it does the same walk over the mMappedFlows, but doesn't actually
2462
// build a new textrun.
2463
bool
2464
BuildTextRunsScanner::SetupLineBreakerContext(gfxTextRun *aTextRun)
2465
0
{
2466
0
  AutoTArray<uint8_t,BIG_TEXT_NODE_SIZE> buffer;
2467
0
  uint32_t bufferSize = mMaxTextLength*(mDoubleByteText ? 2 : 1);
2468
0
  if (bufferSize < mMaxTextLength || bufferSize == UINT32_MAX) {
2469
0
    return false;
2470
0
  }
2471
0
  void *textPtr = buffer.AppendElements(bufferSize, fallible);
2472
0
  if (!textPtr) {
2473
0
    return false;
2474
0
  }
2475
0
2476
0
  gfxSkipChars skipChars;
2477
0
2478
0
  for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) {
2479
0
    MappedFlow* mappedFlow = &mMappedFlows[i];
2480
0
    nsTextFrame* f = mappedFlow->mStartFrame;
2481
0
2482
0
    const nsStyleText* textStyle = f->StyleText();
2483
0
    nsTextFrameUtils::CompressionMode compression =
2484
0
      GetCSSWhitespaceToCompressionMode(f, textStyle);
2485
0
2486
0
    // Figure out what content is included in this flow.
2487
0
    nsIContent* content = f->GetContent();
2488
0
    const nsTextFragment* frag = content->GetText();
2489
0
    int32_t contentStart = mappedFlow->mStartFrame->GetContentOffset();
2490
0
    int32_t contentEnd = mappedFlow->GetContentEnd();
2491
0
    int32_t contentLength = contentEnd - contentStart;
2492
0
2493
0
    nsTextFrameUtils::Flags analysisFlags;
2494
0
    if (frag->Is2b()) {
2495
0
      NS_ASSERTION(mDoubleByteText, "Wrong buffer char size!");
2496
0
      char16_t* bufStart = static_cast<char16_t*>(textPtr);
2497
0
      char16_t* bufEnd = nsTextFrameUtils::TransformText(
2498
0
          frag->Get2b() + contentStart, contentLength, bufStart,
2499
0
          compression, &mNextRunContextInfo, &skipChars, &analysisFlags);
2500
0
      textPtr = bufEnd;
2501
0
    } else {
2502
0
      if (mDoubleByteText) {
2503
0
        // Need to expand the text. First transform it into a temporary buffer,
2504
0
        // then expand.
2505
0
        AutoTArray<uint8_t,BIG_TEXT_NODE_SIZE> tempBuf;
2506
0
        uint8_t* bufStart = tempBuf.AppendElements(contentLength, fallible);
2507
0
        if (!bufStart) {
2508
0
          return false;
2509
0
        }
2510
0
        uint8_t* end = nsTextFrameUtils::TransformText(
2511
0
            reinterpret_cast<const uint8_t*>(frag->Get1b()) + contentStart, contentLength,
2512
0
            bufStart, compression, &mNextRunContextInfo, &skipChars, &analysisFlags);
2513
0
        textPtr = ExpandBuffer(static_cast<char16_t*>(textPtr),
2514
0
                               tempBuf.Elements(), end - tempBuf.Elements());
2515
0
      } else {
2516
0
        uint8_t* bufStart = static_cast<uint8_t*>(textPtr);
2517
0
        uint8_t* end = nsTextFrameUtils::TransformText(
2518
0
            reinterpret_cast<const uint8_t*>(frag->Get1b()) + contentStart, contentLength,
2519
0
            bufStart, compression, &mNextRunContextInfo, &skipChars, &analysisFlags);
2520
0
        textPtr = end;
2521
0
      }
2522
0
    }
2523
0
  }
2524
0
2525
0
  // We have to set these up after we've created the textrun, because
2526
0
  // the breaks may be stored in the textrun during this very call.
2527
0
  // This is a bit annoying because it requires another loop over the frames
2528
0
  // making up the textrun, but I don't see a way to avoid this.
2529
0
  SetupBreakSinksForTextRun(aTextRun, buffer.Elements());
2530
0
2531
0
  return true;
2532
0
}
2533
2534
static bool
2535
HasCompressedLeadingWhitespace(nsTextFrame* aFrame, const nsStyleText* aStyleText,
2536
                               int32_t aContentEndOffset,
2537
                               const gfxSkipCharsIterator& aIterator)
2538
0
{
2539
0
  if (!aIterator.IsOriginalCharSkipped())
2540
0
    return false;
2541
0
2542
0
  gfxSkipCharsIterator iter = aIterator;
2543
0
  int32_t frameContentOffset = aFrame->GetContentOffset();
2544
0
  const nsTextFragment* frag = aFrame->GetContent()->GetText();
2545
0
  while (frameContentOffset < aContentEndOffset && iter.IsOriginalCharSkipped()) {
2546
0
    if (IsTrimmableSpace(frag, frameContentOffset, aStyleText))
2547
0
      return true;
2548
0
    ++frameContentOffset;
2549
0
    iter.AdvanceOriginal(1);
2550
0
  }
2551
0
  return false;
2552
0
}
2553
2554
void
2555
BuildTextRunsScanner::SetupBreakSinksForTextRun(gfxTextRun* aTextRun,
2556
                                                const void* aTextPtr)
2557
0
{
2558
0
  using mozilla::intl::LineBreaker;
2559
0
2560
0
  // for word-break style
2561
0
  switch (mLineContainer->StyleText()->mWordBreak) {
2562
0
    case NS_STYLE_WORDBREAK_BREAK_ALL:
2563
0
      mLineBreaker.SetWordBreak(LineBreaker::kWordBreak_BreakAll);
2564
0
      break;
2565
0
    case NS_STYLE_WORDBREAK_KEEP_ALL:
2566
0
      mLineBreaker.SetWordBreak(LineBreaker::kWordBreak_KeepAll);
2567
0
      break;
2568
0
    default:
2569
0
      mLineBreaker.SetWordBreak(LineBreaker::kWordBreak_Normal);
2570
0
      break;
2571
0
  }
2572
0
2573
0
  // textruns have uniform language
2574
0
  const nsStyleFont *styleFont = mMappedFlows[0].mStartFrame->StyleFont();
2575
0
  // We should only use a language for hyphenation if it was specified
2576
0
  // explicitly.
2577
0
  nsAtom* hyphenationLanguage =
2578
0
    styleFont->mExplicitLanguage ? styleFont->mLanguage.get() : nullptr;
2579
0
  // We keep this pointed at the skip-chars data for the current mappedFlow.
2580
0
  // This lets us cheaply check whether the flow has compressed initial
2581
0
  // whitespace...
2582
0
  gfxSkipCharsIterator iter(aTextRun->GetSkipChars());
2583
0
2584
0
  for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) {
2585
0
    MappedFlow* mappedFlow = &mMappedFlows[i];
2586
0
    uint32_t offset = iter.GetSkippedOffset();
2587
0
    gfxSkipCharsIterator iterNext = iter;
2588
0
    iterNext.AdvanceOriginal(mappedFlow->GetContentEnd() -
2589
0
            mappedFlow->mStartFrame->GetContentOffset());
2590
0
2591
0
    UniquePtr<BreakSink>* breakSink =
2592
0
      mBreakSinks.AppendElement(MakeUnique<BreakSink>(aTextRun, mDrawTarget, offset));
2593
0
    if (!breakSink || !*breakSink)
2594
0
      return;
2595
0
2596
0
    uint32_t length = iterNext.GetSkippedOffset() - offset;
2597
0
    uint32_t flags = 0;
2598
0
    nsIFrame* initialBreakController = mappedFlow->mAncestorControllingInitialBreak;
2599
0
    if (!initialBreakController) {
2600
0
      initialBreakController = mLineContainer;
2601
0
    }
2602
0
    if (!initialBreakController->StyleText()->
2603
0
                                 WhiteSpaceCanWrap(initialBreakController)) {
2604
0
      flags |= nsLineBreaker::BREAK_SUPPRESS_INITIAL;
2605
0
    }
2606
0
    nsTextFrame* startFrame = mappedFlow->mStartFrame;
2607
0
    const nsStyleText* textStyle = startFrame->StyleText();
2608
0
    if (!textStyle->WhiteSpaceCanWrap(startFrame)) {
2609
0
      flags |= nsLineBreaker::BREAK_SUPPRESS_INSIDE;
2610
0
    }
2611
0
    if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::TEXT_NO_BREAKS) {
2612
0
      flags |= nsLineBreaker::BREAK_SKIP_SETTING_NO_BREAKS;
2613
0
    }
2614
0
    if (textStyle->mTextTransform == NS_STYLE_TEXT_TRANSFORM_CAPITALIZE) {
2615
0
      flags |= nsLineBreaker::BREAK_NEED_CAPITALIZATION;
2616
0
    }
2617
0
    if (textStyle->mHyphens == StyleHyphens::Auto) {
2618
0
      flags |= nsLineBreaker::BREAK_USE_AUTO_HYPHENATION;
2619
0
    }
2620
0
2621
0
    if (HasCompressedLeadingWhitespace(startFrame, textStyle,
2622
0
                                       mappedFlow->GetContentEnd(), iter)) {
2623
0
      mLineBreaker.AppendInvisibleWhitespace(flags);
2624
0
    }
2625
0
2626
0
    if (length > 0) {
2627
0
      BreakSink* sink =
2628
0
        mSkipIncompleteTextRuns ? nullptr : (*breakSink).get();
2629
0
      if (mDoubleByteText) {
2630
0
        const char16_t* text = reinterpret_cast<const char16_t*>(aTextPtr);
2631
0
        mLineBreaker.AppendText(hyphenationLanguage, text + offset,
2632
0
                                length, flags, sink);
2633
0
      } else {
2634
0
        const uint8_t* text = reinterpret_cast<const uint8_t*>(aTextPtr);
2635
0
        mLineBreaker.AppendText(hyphenationLanguage, text + offset,
2636
0
                                length, flags, sink);
2637
0
      }
2638
0
    }
2639
0
2640
0
    iter = iterNext;
2641
0
  }
2642
0
}
2643
2644
static bool
2645
MayCharacterHaveEmphasisMark(uint32_t aCh)
2646
0
{
2647
0
  auto category = unicode::GetGeneralCategory(aCh);
2648
0
  // Comparing an unsigned variable against zero is a compile error,
2649
0
  // so we use static assert here to ensure we really don't need to
2650
0
  // compare it with the given constant.
2651
0
  static_assert(IsUnsigned<decltype(category)>::value &&
2652
0
                HB_UNICODE_GENERAL_CATEGORY_CONTROL == 0,
2653
0
                "if this constant is not zero, or category is signed, "
2654
0
                "we need to explicitly do the comparison below");
2655
0
  return !(category <= HB_UNICODE_GENERAL_CATEGORY_UNASSIGNED ||
2656
0
           (category >= HB_UNICODE_GENERAL_CATEGORY_LINE_SEPARATOR &&
2657
0
            category <= HB_UNICODE_GENERAL_CATEGORY_SPACE_SEPARATOR));
2658
0
}
2659
2660
static bool
2661
MayCharacterHaveEmphasisMark(uint8_t aCh)
2662
0
{
2663
0
  // 0x00~0x1f and 0x7f~0x9f are in category Cc
2664
0
  // 0x20 and 0xa0 are in category Zs
2665
0
  bool result = !(aCh <= 0x20 || (aCh >= 0x7f && aCh <= 0xa0));
2666
0
  MOZ_ASSERT(result == MayCharacterHaveEmphasisMark(uint32_t(aCh)),
2667
0
             "result for uint8_t should match result for uint32_t");
2668
0
  return result;
2669
0
}
2670
2671
void
2672
BuildTextRunsScanner::SetupTextEmphasisForTextRun(gfxTextRun* aTextRun,
2673
                                                  const void* aTextPtr)
2674
0
{
2675
0
  if (!mDoubleByteText) {
2676
0
    auto text = reinterpret_cast<const uint8_t*>(aTextPtr);
2677
0
    for (auto i : IntegerRange(aTextRun->GetLength())) {
2678
0
      if (!MayCharacterHaveEmphasisMark(text[i])) {
2679
0
        aTextRun->SetNoEmphasisMark(i);
2680
0
      }
2681
0
    }
2682
0
  } else {
2683
0
    auto text = reinterpret_cast<const char16_t*>(aTextPtr);
2684
0
    auto length = aTextRun->GetLength();
2685
0
    for (size_t i = 0; i < length; ++i) {
2686
0
      if (NS_IS_HIGH_SURROGATE(text[i]) && i + 1 < length &&
2687
0
          NS_IS_LOW_SURROGATE(text[i + 1])) {
2688
0
        uint32_t ch = SURROGATE_TO_UCS4(text[i], text[i + 1]);
2689
0
        if (!MayCharacterHaveEmphasisMark(ch)) {
2690
0
          aTextRun->SetNoEmphasisMark(i);
2691
0
          aTextRun->SetNoEmphasisMark(i + 1);
2692
0
        }
2693
0
        ++i;
2694
0
      } else {
2695
0
        if (!MayCharacterHaveEmphasisMark(uint32_t(text[i]))) {
2696
0
          aTextRun->SetNoEmphasisMark(i);
2697
0
        }
2698
0
      }
2699
0
    }
2700
0
  }
2701
0
}
2702
2703
// Find the flow corresponding to aContent in aUserData
2704
static inline TextRunMappedFlow*
2705
FindFlowForContent(TextRunUserData* aUserData, nsIContent* aContent,
2706
                   TextRunMappedFlow* userMappedFlows)
2707
0
{
2708
0
  // Find the flow that contains us
2709
0
  int32_t i = aUserData->mLastFlowIndex;
2710
0
  int32_t delta = 1;
2711
0
  int32_t sign = 1;
2712
0
  // Search starting at the current position and examine close-by
2713
0
  // positions first, moving further and further away as we go.
2714
0
  while (i >= 0 && uint32_t(i) < aUserData->mMappedFlowCount) {
2715
0
    TextRunMappedFlow* flow = &userMappedFlows[i];
2716
0
    if (flow->mStartFrame->GetContent() == aContent) {
2717
0
      return flow;
2718
0
    }
2719
0
2720
0
    i += delta;
2721
0
    sign = -sign;
2722
0
    delta = -delta + sign;
2723
0
  }
2724
0
2725
0
  // We ran into an array edge.  Add |delta| to |i| once more to get
2726
0
  // back to the side where we still need to search, then step in
2727
0
  // the |sign| direction.
2728
0
  i += delta;
2729
0
  if (sign > 0) {
2730
0
    for (; i < int32_t(aUserData->mMappedFlowCount); ++i) {
2731
0
      TextRunMappedFlow* flow = &userMappedFlows[i];
2732
0
      if (flow->mStartFrame->GetContent() == aContent) {
2733
0
        return flow;
2734
0
      }
2735
0
    }
2736
0
  } else {
2737
0
    for (; i >= 0; --i) {
2738
0
      TextRunMappedFlow* flow = &userMappedFlows[i];
2739
0
      if (flow->mStartFrame->GetContent() == aContent) {
2740
0
        return flow;
2741
0
      }
2742
0
    }
2743
0
  }
2744
0
2745
0
  return nullptr;
2746
0
}
2747
2748
void
2749
BuildTextRunsScanner::AssignTextRun(gfxTextRun* aTextRun, float aInflation)
2750
0
{
2751
0
  for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) {
2752
0
    MappedFlow* mappedFlow = &mMappedFlows[i];
2753
0
    nsTextFrame* startFrame = mappedFlow->mStartFrame;
2754
0
    nsTextFrame* endFrame = mappedFlow->mEndFrame;
2755
0
    nsTextFrame* f;
2756
0
    for (f = startFrame; f != endFrame; f = f->GetNextContinuation()) {
2757
#ifdef DEBUG_roc
2758
      if (f->GetTextRun(mWhichTextRun)) {
2759
        gfxTextRun* textRun = f->GetTextRun(mWhichTextRun);
2760
        if (textRun->GetFlags2() & nsTextFrameUtils::Flags::TEXT_IS_SIMPLE_FLOW) {
2761
          if (mMappedFlows[0].mStartFrame != GetFrameForSimpleFlow(textRun)) {
2762
            NS_WARNING("REASSIGNING SIMPLE FLOW TEXT RUN!");
2763
          }
2764
        } else {
2765
          auto userData = static_cast<TextRunUserData*>(aTextRun->GetUserData());
2766
          TextRunMappedFlow* userMappedFlows = GetMappedFlows(aTextRun);
2767
          if (userData->mMappedFlowCount >= mMappedFlows.Length() ||
2768
              userMappedFlows[userData->mMappedFlowCount - 1].mStartFrame !=
2769
              mMappedFlows[userdata->mMappedFlowCount - 1].mStartFrame) {
2770
            NS_WARNING("REASSIGNING MULTIFLOW TEXT RUN (not append)!");
2771
          }
2772
        }
2773
      }
2774
#endif
2775
2776
0
      gfxTextRun* oldTextRun = f->GetTextRun(mWhichTextRun);
2777
0
      if (oldTextRun) {
2778
0
        nsTextFrame* firstFrame = nullptr;
2779
0
        uint32_t startOffset = 0;
2780
0
        if (oldTextRun->GetFlags2() & nsTextFrameUtils::Flags::TEXT_IS_SIMPLE_FLOW) {
2781
0
          firstFrame = GetFrameForSimpleFlow(oldTextRun);
2782
0
        } else {
2783
0
          auto userData = static_cast<TextRunUserData*>(oldTextRun->GetUserData());
2784
0
          TextRunMappedFlow* userMappedFlows = GetMappedFlows(oldTextRun);
2785
0
          firstFrame = userMappedFlows[0].mStartFrame;
2786
0
          if (MOZ_UNLIKELY(f != firstFrame)) {
2787
0
            TextRunMappedFlow* flow =
2788
0
              FindFlowForContent(userData, f->GetContent(), userMappedFlows);
2789
0
            if (flow) {
2790
0
              startOffset = flow->mDOMOffsetToBeforeTransformOffset;
2791
0
            } else {
2792
0
              NS_ERROR("Can't find flow containing frame 'f'");
2793
0
            }
2794
0
          }
2795
0
        }
2796
0
2797
0
        // Optimization: if |f| is the first frame in the flow then there are no
2798
0
        // prev-continuations that use |oldTextRun|.
2799
0
        nsTextFrame* clearFrom = nullptr;
2800
0
        if (MOZ_UNLIKELY(f != firstFrame)) {
2801
0
          // If all the frames in the mapped flow starting at |f| (inclusive)
2802
0
          // are empty then we let the prev-continuations keep the old text run.
2803
0
          gfxSkipCharsIterator iter(oldTextRun->GetSkipChars(), startOffset, f->GetContentOffset());
2804
0
          uint32_t textRunOffset = iter.ConvertOriginalToSkipped(f->GetContentOffset());
2805
0
          clearFrom = textRunOffset == oldTextRun->GetLength() ? f : nullptr;
2806
0
        }
2807
0
        f->ClearTextRun(clearFrom, mWhichTextRun);
2808
0
2809
#ifdef DEBUG
2810
        if (firstFrame && !firstFrame->GetTextRun(mWhichTextRun)) {
2811
          // oldTextRun was destroyed - assert that we don't reference it.
2812
          for (uint32_t j = 0; j < mBreakSinks.Length(); ++j) {
2813
            NS_ASSERTION(oldTextRun != mBreakSinks[j]->mTextRun,
2814
                         "destroyed text run is still in use");
2815
          }
2816
        }
2817
#endif
2818
      }
2819
0
      f->SetTextRun(aTextRun, mWhichTextRun, aInflation);
2820
0
    }
2821
0
    // Set this bit now; we can't set it any earlier because
2822
0
    // f->ClearTextRun() might clear it out.
2823
0
    nsFrameState whichTextRunState =
2824
0
      startFrame->GetTextRun(nsTextFrame::eInflated) == aTextRun
2825
0
        ? TEXT_IN_TEXTRUN_USER_DATA
2826
0
        : TEXT_IN_UNINFLATED_TEXTRUN_USER_DATA;
2827
0
    startFrame->AddStateBits(whichTextRunState);
2828
0
  }
2829
0
}
2830
2831
0
NS_QUERYFRAME_HEAD(nsTextFrame)
2832
0
  NS_QUERYFRAME_ENTRY(nsTextFrame)
2833
0
NS_QUERYFRAME_TAIL_INHERITING(nsFrame)
2834
2835
gfxSkipCharsIterator
2836
nsTextFrame::EnsureTextRun(TextRunType aWhichTextRun,
2837
                           DrawTarget* aRefDrawTarget,
2838
                           nsIFrame* aLineContainer,
2839
                           const nsLineList::iterator* aLine,
2840
                           uint32_t* aFlowEndInTextRun)
2841
0
{
2842
0
  gfxTextRun *textRun = GetTextRun(aWhichTextRun);
2843
0
  if (!textRun || (aLine && (*aLine)->GetInvalidateTextRuns())) {
2844
0
    RefPtr<DrawTarget> refDT = aRefDrawTarget;
2845
0
    if (!refDT) {
2846
0
      refDT = CreateReferenceDrawTarget(this);
2847
0
    }
2848
0
    if (refDT) {
2849
0
      BuildTextRuns(refDT, this, aLineContainer, aLine, aWhichTextRun);
2850
0
    }
2851
0
    textRun = GetTextRun(aWhichTextRun);
2852
0
    if (!textRun) {
2853
0
      // A text run was not constructed for this frame. This is bad. The caller
2854
0
      // will check mTextRun.
2855
0
      return gfxSkipCharsIterator(gfxPlatform::
2856
0
                                  GetPlatform()->EmptySkipChars(), 0);
2857
0
    }
2858
0
    TabWidthStore* tabWidths = GetProperty(TabWidthProperty());
2859
0
    if (tabWidths && tabWidths->mValidForContentOffset != GetContentOffset()) {
2860
0
      DeleteProperty(TabWidthProperty());
2861
0
    }
2862
0
  }
2863
0
2864
0
  if (textRun->GetFlags2() & nsTextFrameUtils::Flags::TEXT_IS_SIMPLE_FLOW) {
2865
0
    if (aFlowEndInTextRun) {
2866
0
      *aFlowEndInTextRun = textRun->GetLength();
2867
0
    }
2868
0
    return gfxSkipCharsIterator(textRun->GetSkipChars(), 0, mContentOffset);
2869
0
  }
2870
0
2871
0
  auto userData = static_cast<TextRunUserData*>(textRun->GetUserData());
2872
0
  TextRunMappedFlow* userMappedFlows = GetMappedFlows(textRun);
2873
0
  TextRunMappedFlow* flow =
2874
0
    FindFlowForContent(userData, mContent, userMappedFlows);
2875
0
  if (flow) {
2876
0
    // Since textruns can only contain one flow for a given content element,
2877
0
    // this must be our flow.
2878
0
    uint32_t flowIndex = flow - userMappedFlows;
2879
0
    userData->mLastFlowIndex = flowIndex;
2880
0
    gfxSkipCharsIterator iter(textRun->GetSkipChars(),
2881
0
                              flow->mDOMOffsetToBeforeTransformOffset, mContentOffset);
2882
0
    if (aFlowEndInTextRun) {
2883
0
      if (flowIndex + 1 < userData->mMappedFlowCount) {
2884
0
        gfxSkipCharsIterator end(textRun->GetSkipChars());
2885
0
        *aFlowEndInTextRun = end.ConvertOriginalToSkipped(
2886
0
              flow[1].mStartFrame->GetContentOffset() + flow[1].mDOMOffsetToBeforeTransformOffset);
2887
0
      } else {
2888
0
        *aFlowEndInTextRun = textRun->GetLength();
2889
0
      }
2890
0
    }
2891
0
    return iter;
2892
0
  }
2893
0
2894
0
  NS_ERROR("Can't find flow containing this frame???");
2895
0
  return gfxSkipCharsIterator(gfxPlatform::GetPlatform()->EmptySkipChars(), 0);
2896
0
}
2897
2898
static uint32_t
2899
GetEndOfTrimmedText(const nsTextFragment* aFrag, const nsStyleText* aStyleText,
2900
                    uint32_t aStart, uint32_t aEnd,
2901
                    gfxSkipCharsIterator* aIterator)
2902
0
{
2903
0
  aIterator->SetSkippedOffset(aEnd);
2904
0
  while (aIterator->GetSkippedOffset() > aStart) {
2905
0
    aIterator->AdvanceSkipped(-1);
2906
0
    if (!IsTrimmableSpace(aFrag, aIterator->GetOriginalOffset(), aStyleText))
2907
0
      return aIterator->GetSkippedOffset() + 1;
2908
0
  }
2909
0
  return aStart;
2910
0
}
2911
2912
nsTextFrame::TrimmedOffsets
2913
nsTextFrame::GetTrimmedOffsets(const nsTextFragment* aFrag,
2914
                               bool aTrimAfter, bool aPostReflow) const
2915
0
{
2916
0
  NS_ASSERTION(mTextRun, "Need textrun here");
2917
0
  if (aPostReflow) {
2918
0
    // This should not be used during reflow. We need our TEXT_REFLOW_FLAGS
2919
0
    // to be set correctly.  If our parent wasn't reflowed due to the frame
2920
0
    // tree being too deep then the return value doesn't matter.
2921
0
    NS_ASSERTION(!(GetStateBits() & NS_FRAME_FIRST_REFLOW) ||
2922
0
                 (GetParent()->GetStateBits() &
2923
0
                  NS_FRAME_TOO_DEEP_IN_FRAME_TREE),
2924
0
                 "Can only call this on frames that have been reflowed");
2925
0
    NS_ASSERTION(!(GetStateBits() & NS_FRAME_IN_REFLOW),
2926
0
                 "Can only call this on frames that are not being reflowed");
2927
0
  }
2928
0
2929
0
  TrimmedOffsets offsets = { GetContentOffset(), GetContentLength() };
2930
0
  const nsStyleText* textStyle = StyleText();
2931
0
  // Note that pre-line newlines should still allow us to trim spaces
2932
0
  // for display
2933
0
  if (textStyle->WhiteSpaceIsSignificant())
2934
0
    return offsets;
2935
0
2936
0
  if (!aPostReflow || (GetStateBits() & TEXT_START_OF_LINE)) {
2937
0
    int32_t whitespaceCount =
2938
0
      GetTrimmableWhitespaceCount(aFrag,
2939
0
                                  offsets.mStart, offsets.mLength, 1);
2940
0
    offsets.mStart += whitespaceCount;
2941
0
    offsets.mLength -= whitespaceCount;
2942
0
  }
2943
0
2944
0
  if (aTrimAfter && (!aPostReflow || (GetStateBits() & TEXT_END_OF_LINE))) {
2945
0
    // This treats a trailing 'pre-line' newline as trimmable. That's fine,
2946
0
    // it's actually what we want since we want whitespace before it to
2947
0
    // be trimmed.
2948
0
    int32_t whitespaceCount =
2949
0
      GetTrimmableWhitespaceCount(aFrag,
2950
0
                                  offsets.GetEnd() - 1, offsets.mLength, -1);
2951
0
    offsets.mLength -= whitespaceCount;
2952
0
  }
2953
0
  return offsets;
2954
0
}
2955
2956
static bool IsJustifiableCharacter(const nsStyleText* aTextStyle,
2957
                                   const nsTextFragment* aFrag, int32_t aPos,
2958
                                   bool aLangIsCJ)
2959
0
{
2960
0
  NS_ASSERTION(aPos >= 0, "negative position?!");
2961
0
2962
0
  StyleTextJustify justifyStyle = aTextStyle->mTextJustify;
2963
0
  if (justifyStyle == StyleTextJustify::None) {
2964
0
    return false;
2965
0
  }
2966
0
2967
0
  char16_t ch = aFrag->CharAt(aPos);
2968
0
  if (ch == '\n' || ch == '\t' || ch == '\r') {
2969
0
    return true;
2970
0
  }
2971
0
  if (ch == ' ' || ch == CH_NBSP) {
2972
0
    // Don't justify spaces that are combined with diacriticals
2973
0
    if (!aFrag->Is2b()) {
2974
0
      return true;
2975
0
    }
2976
0
    return !nsTextFrameUtils::IsSpaceCombiningSequenceTail(
2977
0
      aFrag->Get2b() + aPos + 1, aFrag->GetLength() - (aPos + 1));
2978
0
  }
2979
0
2980
0
  if (justifyStyle == StyleTextJustify::InterCharacter) {
2981
0
    return true;
2982
0
  } else if (justifyStyle == StyleTextJustify::InterWord) {
2983
0
    return false;
2984
0
  }
2985
0
2986
0
  // text-justify: auto
2987
0
  if (ch < 0x2150u) {
2988
0
    return false;
2989
0
  }
2990
0
  if (aLangIsCJ) {
2991
0
    if ((0x2150u <= ch && ch <= 0x22ffu) || // Number Forms, Arrows, Mathematical Operators
2992
0
        (0x2460u <= ch && ch <= 0x24ffu) || // Enclosed Alphanumerics
2993
0
        (0x2580u <= ch && ch <= 0x27bfu) || // Block Elements, Geometric Shapes, Miscellaneous Symbols, Dingbats
2994
0
        (0x27f0u <= ch && ch <= 0x2bffu) || // Supplemental Arrows-A, Braille Patterns, Supplemental Arrows-B,
2995
0
                                            // Miscellaneous Mathematical Symbols-B, Supplemental Mathematical Operators,
2996
0
                                            // Miscellaneous Symbols and Arrows
2997
0
        (0x2e80u <= ch && ch <= 0x312fu) || // CJK Radicals Supplement, CJK Radicals Supplement,
2998
0
                                            // Ideographic Description Characters, CJK Symbols and Punctuation,
2999
0
                                            // Hiragana, Katakana, Bopomofo
3000
0
        (0x3190u <= ch && ch <= 0xabffu) || // Kanbun, Bopomofo Extended, Katakana Phonetic Extensions,
3001
0
                                            // Enclosed CJK Letters and Months, CJK Compatibility,
3002
0
                                            // CJK Unified Ideographs Extension A, Yijing Hexagram Symbols,
3003
0
                                            // CJK Unified Ideographs, Yi Syllables, Yi Radicals
3004
0
        (0xf900u <= ch && ch <= 0xfaffu) || // CJK Compatibility Ideographs
3005
0
        (0xff5eu <= ch && ch <= 0xff9fu)    // Halfwidth and Fullwidth Forms(a part)
3006
0
       ) {
3007
0
      return true;
3008
0
    }
3009
0
    char16_t ch2;
3010
0
    if (NS_IS_HIGH_SURROGATE(ch) && aFrag->GetLength() > uint32_t(aPos) + 1 &&
3011
0
        NS_IS_LOW_SURROGATE(ch2 = aFrag->CharAt(aPos + 1))) {
3012
0
      uint32_t u = SURROGATE_TO_UCS4(ch, ch2);
3013
0
      if (0x20000u <= u && u <= 0x2ffffu) { // CJK Unified Ideographs Extension B,
3014
0
                                            // CJK Unified Ideographs Extension C,
3015
0
                                            // CJK Unified Ideographs Extension D,
3016
0
                                            // CJK Compatibility Ideographs Supplement
3017
0
        return true;
3018
0
      }
3019
0
    }
3020
0
  }
3021
0
  return false;
3022
0
}
3023
3024
void
3025
nsTextFrame::ClearMetrics(ReflowOutput& aMetrics)
3026
0
{
3027
0
  aMetrics.ClearSize();
3028
0
  aMetrics.SetBlockStartAscent(0);
3029
0
  mAscent = 0;
3030
0
3031
0
  AddStateBits(TEXT_NO_RENDERED_GLYPHS);
3032
0
}
3033
3034
static int32_t FindChar(const nsTextFragment* frag,
3035
                        int32_t aOffset, int32_t aLength, char16_t ch)
3036
0
{
3037
0
  int32_t i = 0;
3038
0
  if (frag->Is2b()) {
3039
0
    const char16_t* str = frag->Get2b() + aOffset;
3040
0
    for (; i < aLength; ++i) {
3041
0
      if (*str == ch)
3042
0
        return i + aOffset;
3043
0
      ++str;
3044
0
    }
3045
0
  } else {
3046
0
    if (uint16_t(ch) <= 0xFF) {
3047
0
      const char* str = frag->Get1b() + aOffset;
3048
0
      const void* p = memchr(str, ch, aLength);
3049
0
      if (p)
3050
0
        return (static_cast<const char*>(p) - str) + aOffset;
3051
0
    }
3052
0
  }
3053
0
  return -1;
3054
0
}
3055
3056
static bool IsChineseOrJapanese(const nsTextFrame* aFrame)
3057
0
{
3058
0
  if (aFrame->ShouldSuppressLineBreak()) {
3059
0
    // Always treat ruby as CJ language so that those characters can
3060
0
    // be expanded properly even when surrounded by other language.
3061
0
    return true;
3062
0
  }
3063
0
3064
0
  nsAtom* language = aFrame->StyleFont()->mLanguage;
3065
0
  if (!language) {
3066
0
    return false;
3067
0
  }
3068
0
  return nsStyleUtil::MatchesLanguagePrefix(language, u"ja") ||
3069
0
         nsStyleUtil::MatchesLanguagePrefix(language, u"zh");
3070
0
}
3071
3072
#ifdef DEBUG
3073
static bool IsInBounds(const gfxSkipCharsIterator& aStart, int32_t aContentLength,
3074
                       gfxTextRun::Range aRange) {
3075
  if (aStart.GetSkippedOffset() > aRange.start)
3076
    return false;
3077
  if (aContentLength == INT32_MAX)
3078
    return true;
3079
  gfxSkipCharsIterator iter(aStart);
3080
  iter.AdvanceOriginal(aContentLength);
3081
  return iter.GetSkippedOffset() >= aRange.end;
3082
}
3083
#endif
3084
3085
class MOZ_STACK_CLASS PropertyProvider final : public gfxTextRun::PropertyProvider {
3086
  typedef gfxTextRun::Range Range;
3087
  typedef gfxTextRun::HyphenType HyphenType;
3088
3089
public:
3090
  /**
3091
   * Use this constructor for reflow, when we don't know what text is
3092
   * really mapped by the frame and we have a lot of other data around.
3093
   *
3094
   * @param aLength can be INT32_MAX to indicate we cover all the text
3095
   * associated with aFrame up to where its flow chain ends in the given
3096
   * textrun. If INT32_MAX is passed, justification and hyphen-related methods
3097
   * cannot be called, nor can GetOriginalLength().
3098
   */
3099
  PropertyProvider(gfxTextRun* aTextRun, const nsStyleText* aTextStyle,
3100
                   const nsTextFragment* aFrag, nsTextFrame* aFrame,
3101
                   const gfxSkipCharsIterator& aStart, int32_t aLength,
3102
                   nsIFrame* aLineContainer,
3103
                   nscoord aOffsetFromBlockOriginForTabs,
3104
                   nsTextFrame::TextRunType aWhichTextRun)
3105
    : mTextRun(aTextRun), mFontGroup(nullptr),
3106
      mTextStyle(aTextStyle), mFrag(aFrag),
3107
      mLineContainer(aLineContainer),
3108
      mFrame(aFrame), mStart(aStart), mTempIterator(aStart),
3109
      mTabWidths(nullptr), mTabWidthsAnalyzedLimit(0),
3110
      mLength(aLength),
3111
      mWordSpacing(WordSpacing(aFrame, mTextRun, aTextStyle)),
3112
      mLetterSpacing(LetterSpacing(aFrame, aTextStyle)),
3113
      mMinTabAdvance(-1.0),
3114
      mHyphenWidth(-1),
3115
      mOffsetFromBlockOriginForTabs(aOffsetFromBlockOriginForTabs),
3116
      mJustificationArrayStart(0),
3117
      mReflowing(true),
3118
      mWhichTextRun(aWhichTextRun)
3119
0
  {
3120
0
    NS_ASSERTION(mStart.IsInitialized(), "Start not initialized?");
3121
0
  }
3122
3123
  /**
3124
   * Use this constructor after the frame has been reflowed and we don't
3125
   * have other data around. Gets everything from the frame. EnsureTextRun
3126
   * *must* be called before this!!!
3127
   */
3128
  PropertyProvider(nsTextFrame* aFrame, const gfxSkipCharsIterator& aStart,
3129
                   nsTextFrame::TextRunType aWhichTextRun)
3130
    : mTextRun(aFrame->GetTextRun(aWhichTextRun)), mFontGroup(nullptr),
3131
      mTextStyle(aFrame->StyleText()),
3132
      mFrag(aFrame->GetContent()->GetText()),
3133
      mLineContainer(nullptr),
3134
      mFrame(aFrame), mStart(aStart), mTempIterator(aStart),
3135
      mTabWidths(nullptr), mTabWidthsAnalyzedLimit(0),
3136
      mLength(aFrame->GetContentLength()),
3137
      mWordSpacing(WordSpacing(aFrame, mTextRun)),
3138
      mLetterSpacing(LetterSpacing(aFrame)),
3139
      mMinTabAdvance(-1.0),
3140
      mHyphenWidth(-1),
3141
      mOffsetFromBlockOriginForTabs(0),
3142
      mJustificationArrayStart(0),
3143
      mReflowing(false),
3144
      mWhichTextRun(aWhichTextRun)
3145
0
  {
3146
0
    NS_ASSERTION(mTextRun, "Textrun not initialized!");
3147
0
  }
3148
3149
  // Call this after construction if you're not going to reflow the text
3150
  void InitializeForDisplay(bool aTrimAfter);
3151
3152
  void InitializeForMeasure();
3153
3154
  void GetSpacing(Range aRange, Spacing* aSpacing) const final;
3155
  gfxFloat GetHyphenWidth() const final;
3156
  void GetHyphenationBreaks(Range aRange, HyphenType* aBreakBefore) const final;
3157
0
  StyleHyphens GetHyphensOption() const final {
3158
0
    return mTextStyle->mHyphens;
3159
0
  }
3160
3161
0
  already_AddRefed<DrawTarget> GetDrawTarget() const final {
3162
0
    return CreateReferenceDrawTarget(GetFrame());
3163
0
  }
3164
3165
0
  uint32_t GetAppUnitsPerDevUnit() const final {
3166
0
    return mTextRun->GetAppUnitsPerDevUnit();
3167
0
  }
3168
3169
  void GetSpacingInternal(Range aRange, Spacing* aSpacing, bool aIgnoreTabs) const;
3170
3171
  /**
3172
   * Compute the justification information in given DOM range, return
3173
   * justification info and assignments if requested.
3174
   */
3175
  JustificationInfo ComputeJustification(
3176
    Range aRange, nsTArray<JustificationAssignment>* aAssignments = nullptr);
3177
3178
0
  const nsTextFrame* GetFrame() const { return mFrame; }
3179
  // This may not be equal to the frame offset/length in because we may have
3180
  // adjusted for whitespace trimming according to the state bits set in the frame
3181
  // (for the static provider)
3182
0
  const gfxSkipCharsIterator& GetStart() const { return mStart; }
3183
  // May return INT32_MAX if that was given to the constructor
3184
0
  uint32_t GetOriginalLength() const {
3185
0
    NS_ASSERTION(mLength != INT32_MAX, "Length not known");
3186
0
    return mLength;
3187
0
  }
3188
0
  const nsTextFragment* GetFragment() const { return mFrag; }
3189
3190
0
  gfxFontGroup* GetFontGroup() const {
3191
0
    if (!mFontGroup) {
3192
0
      InitFontGroupAndFontMetrics();
3193
0
    }
3194
0
    return mFontGroup;
3195
0
  }
3196
3197
0
  nsFontMetrics* GetFontMetrics() const {
3198
0
    if (!mFontMetrics) {
3199
0
      InitFontGroupAndFontMetrics();
3200
0
    }
3201
0
    return mFontMetrics;
3202
0
  }
3203
3204
  void CalcTabWidths(Range aTransformedRange, gfxFloat aTabWidth) const;
3205
3206
0
  gfxFloat MinTabAdvance() const {
3207
0
    if (mMinTabAdvance < 0.0) {
3208
0
      mMinTabAdvance = GetMinTabAdvanceAppUnits(mTextRun);
3209
0
    }
3210
0
    return mMinTabAdvance;
3211
0
  }
3212
3213
0
  const gfxSkipCharsIterator& GetEndHint() const { return mTempIterator; }
3214
3215
protected:
3216
  void SetupJustificationSpacing(bool aPostReflow);
3217
3218
0
  void InitFontGroupAndFontMetrics() const {
3219
0
    float inflation = (mWhichTextRun == nsTextFrame::eInflated)
3220
0
      ? mFrame->GetFontSizeInflation() : 1.0f;
3221
0
    mFontGroup = GetFontGroupForFrame(mFrame, inflation,
3222
0
                                      getter_AddRefs(mFontMetrics));
3223
0
  }
3224
3225
  const RefPtr<gfxTextRun>        mTextRun;
3226
  mutable gfxFontGroup*           mFontGroup;
3227
  mutable RefPtr<nsFontMetrics>   mFontMetrics;
3228
  const nsStyleText*              mTextStyle;
3229
  const nsTextFragment*           mFrag;
3230
  const nsIFrame*                 mLineContainer;
3231
  nsTextFrame*                    mFrame;
3232
  gfxSkipCharsIterator            mStart;  // Offset in original and transformed string
3233
  const gfxSkipCharsIterator      mTempIterator;
3234
3235
  // Either null, or pointing to the frame's TabWidthProperty.
3236
  mutable TabWidthStore*          mTabWidths;
3237
  // How far we've done tab-width calculation; this is ONLY valid when
3238
  // mTabWidths is nullptr (otherwise rely on mTabWidths->mLimit instead).
3239
  // It's a DOM offset relative to the current frame's offset.
3240
  mutable uint32_t                mTabWidthsAnalyzedLimit;
3241
3242
  int32_t                         mLength;  // DOM string length, may be INT32_MAX
3243
  const gfxFloat                  mWordSpacing; // space for each whitespace char
3244
  const gfxFloat                  mLetterSpacing; // space for each letter
3245
  mutable gfxFloat                mMinTabAdvance; // min advance for <tab> char
3246
  mutable gfxFloat                mHyphenWidth;
3247
  mutable gfxFloat                mOffsetFromBlockOriginForTabs;
3248
3249
  // The values in mJustificationSpacings corresponds to unskipped
3250
  // characters start from mJustificationArrayStart.
3251
  uint32_t                        mJustificationArrayStart;
3252
  nsTArray<Spacing>               mJustificationSpacings;
3253
3254
  const bool                      mReflowing;
3255
  const nsTextFrame::TextRunType  mWhichTextRun;
3256
};
3257
3258
/**
3259
 * Finds the offset of the first character of the cluster containing aPos
3260
 */
3261
static void FindClusterStart(const gfxTextRun* aTextRun,
3262
                             int32_t aOriginalStart,
3263
                             gfxSkipCharsIterator* aPos)
3264
0
{
3265
0
  while (aPos->GetOriginalOffset() > aOriginalStart) {
3266
0
    if (aPos->IsOriginalCharSkipped() ||
3267
0
        aTextRun->IsClusterStart(aPos->GetSkippedOffset())) {
3268
0
      break;
3269
0
    }
3270
0
    aPos->AdvanceOriginal(-1);
3271
0
  }
3272
0
}
3273
3274
/**
3275
 * Finds the offset of the last character of the cluster containing aPos.
3276
 * If aAllowSplitLigature is false, we also check for a ligature-group
3277
 * start.
3278
 */
3279
static void FindClusterEnd(const gfxTextRun* aTextRun,
3280
                           int32_t aOriginalEnd,
3281
                           gfxSkipCharsIterator* aPos,
3282
                           bool aAllowSplitLigature = true)
3283
0
{
3284
0
  MOZ_ASSERT(aPos->GetOriginalOffset() < aOriginalEnd,
3285
0
             "character outside string");
3286
0
3287
0
  aPos->AdvanceOriginal(1);
3288
0
  while (aPos->GetOriginalOffset() < aOriginalEnd) {
3289
0
    if (aPos->IsOriginalCharSkipped() ||
3290
0
        (aTextRun->IsClusterStart(aPos->GetSkippedOffset()) &&
3291
0
         (aAllowSplitLigature ||
3292
0
          aTextRun->IsLigatureGroupStart(aPos->GetSkippedOffset())))) {
3293
0
      break;
3294
0
    }
3295
0
    aPos->AdvanceOriginal(1);
3296
0
  }
3297
0
  aPos->AdvanceOriginal(-1);
3298
0
}
3299
3300
JustificationInfo
3301
PropertyProvider::ComputeJustification(
3302
  Range aRange, nsTArray<JustificationAssignment>* aAssignments)
3303
0
{
3304
0
  JustificationInfo info;
3305
0
3306
0
  // Horizontal-in-vertical frame is orthogonal to the line, so it
3307
0
  // doesn't actually include any justification opportunity inside.
3308
0
  // The spec says such frame should be treated as a U+FFFC. Since we
3309
0
  // do not insert justification opportunities on the sides of that
3310
0
  // character, the sides of this frame are not justifiable either.
3311
0
  if (mFrame->Style()->IsTextCombined()) {
3312
0
    return info;
3313
0
  }
3314
0
3315
0
  bool isCJ = IsChineseOrJapanese(mFrame);
3316
0
  nsSkipCharsRunIterator run(
3317
0
    mStart, nsSkipCharsRunIterator::LENGTH_INCLUDES_SKIPPED, aRange.Length());
3318
0
  run.SetOriginalOffset(aRange.start);
3319
0
  mJustificationArrayStart = run.GetSkippedOffset();
3320
0
3321
0
  nsTArray<JustificationAssignment> assignments;
3322
0
  assignments.SetCapacity(aRange.Length());
3323
0
  while (run.NextRun()) {
3324
0
    uint32_t originalOffset = run.GetOriginalOffset();
3325
0
    uint32_t skippedOffset = run.GetSkippedOffset();
3326
0
    uint32_t length = run.GetRunLength();
3327
0
    assignments.SetLength(skippedOffset + length - mJustificationArrayStart);
3328
0
3329
0
    gfxSkipCharsIterator iter = run.GetPos();
3330
0
    for (uint32_t i = 0; i < length; ++i) {
3331
0
      uint32_t offset = originalOffset + i;
3332
0
      if (!IsJustifiableCharacter(mTextStyle, mFrag, offset, isCJ)) {
3333
0
        continue;
3334
0
      }
3335
0
3336
0
      iter.SetOriginalOffset(offset);
3337
0
3338
0
      FindClusterStart(mTextRun, originalOffset, &iter);
3339
0
      uint32_t firstCharOffset = iter.GetSkippedOffset();
3340
0
      uint32_t firstChar = firstCharOffset > mJustificationArrayStart ?
3341
0
        firstCharOffset - mJustificationArrayStart : 0;
3342
0
      if (!firstChar) {
3343
0
        info.mIsStartJustifiable = true;
3344
0
      } else {
3345
0
        auto& assign = assignments[firstChar];
3346
0
        auto& prevAssign = assignments[firstChar - 1];
3347
0
        if (prevAssign.mGapsAtEnd) {
3348
0
          prevAssign.mGapsAtEnd = 1;
3349
0
          assign.mGapsAtStart = 1;
3350
0
        } else {
3351
0
          assign.mGapsAtStart = 2;
3352
0
          info.mInnerOpportunities++;
3353
0
        }
3354
0
      }
3355
0
3356
0
      FindClusterEnd(mTextRun, originalOffset + length, &iter);
3357
0
      uint32_t lastChar = iter.GetSkippedOffset() - mJustificationArrayStart;
3358
0
      // Assign the two gaps temporary to the last char. If the next cluster is
3359
0
      // justifiable as well, one of the gaps will be removed by code above.
3360
0
      assignments[lastChar].mGapsAtEnd = 2;
3361
0
      info.mInnerOpportunities++;
3362
0
3363
0
      // Skip the whole cluster
3364
0
      i = iter.GetOriginalOffset() - originalOffset;
3365
0
    }
3366
0
  }
3367
0
3368
0
  if (!assignments.IsEmpty() && assignments.LastElement().mGapsAtEnd) {
3369
0
    // We counted the expansion opportunity after the last character,
3370
0
    // but it is not an inner opportunity.
3371
0
    MOZ_ASSERT(info.mInnerOpportunities > 0);
3372
0
    info.mInnerOpportunities--;
3373
0
    info.mIsEndJustifiable = true;
3374
0
  }
3375
0
3376
0
  if (aAssignments) {
3377
0
    *aAssignments = std::move(assignments);
3378
0
  }
3379
0
  return info;
3380
0
}
3381
3382
// aStart, aLength in transformed string offsets
3383
void
3384
PropertyProvider::GetSpacing(Range aRange, Spacing* aSpacing) const
3385
0
{
3386
0
  GetSpacingInternal(aRange, aSpacing,
3387
0
                     !(mTextRun->GetFlags2() & nsTextFrameUtils::Flags::TEXT_HAS_TAB));
3388
0
}
3389
3390
static bool
3391
CanAddSpacingAfter(const gfxTextRun* aTextRun, uint32_t aOffset)
3392
0
{
3393
0
  if (aOffset + 1 >= aTextRun->GetLength())
3394
0
    return true;
3395
0
  return aTextRun->IsClusterStart(aOffset + 1) &&
3396
0
    aTextRun->IsLigatureGroupStart(aOffset + 1) &&
3397
0
    !aTextRun->CharIsFormattingControl(aOffset);
3398
0
}
3399
3400
static gfxFloat
3401
ComputeTabWidthAppUnits(const nsIFrame* aFrame, gfxTextRun* aTextRun)
3402
0
{
3403
0
  const nsStyleText* textStyle = aFrame->StyleText();
3404
0
  if (textStyle->mTabSize.GetUnit() != eStyleUnit_Factor) {
3405
0
    nscoord w = textStyle->mTabSize.GetCoordValue();
3406
0
    MOZ_ASSERT(w >= 0);
3407
0
    return w;
3408
0
  }
3409
0
3410
0
  gfxFloat spaces = textStyle->mTabSize.GetFactorValue();
3411
0
  MOZ_ASSERT(spaces >= 0);
3412
0
3413
0
  // Round the space width when converting to appunits the same way
3414
0
  // textruns do.
3415
0
  gfxFloat spaceWidthAppUnits =
3416
0
    NS_round(GetFirstFontMetrics(aTextRun->GetFontGroup(),
3417
0
                                 aTextRun->IsVertical()).spaceWidth *
3418
0
             aTextRun->GetAppUnitsPerDevUnit());
3419
0
  return spaces * spaceWidthAppUnits;
3420
0
}
3421
3422
void
3423
PropertyProvider::GetSpacingInternal(Range aRange, Spacing* aSpacing,
3424
                                     bool aIgnoreTabs) const
3425
0
{
3426
0
  MOZ_ASSERT(IsInBounds(mStart, mLength, aRange), "Range out of bounds");
3427
0
3428
0
  uint32_t index;
3429
0
  for (index = 0; index < aRange.Length(); ++index) {
3430
0
    aSpacing[index].mBefore = 0.0;
3431
0
    aSpacing[index].mAfter = 0.0;
3432
0
  }
3433
0
3434
0
  if (mFrame->Style()->IsTextCombined()) {
3435
0
    return;
3436
0
  }
3437
0
3438
0
  // Find our offset into the original+transformed string
3439
0
  gfxSkipCharsIterator start(mStart);
3440
0
  start.SetSkippedOffset(aRange.start);
3441
0
3442
0
  // First, compute the word and letter spacing
3443
0
  if (mWordSpacing || mLetterSpacing) {
3444
0
    // Iterate over non-skipped characters
3445
0
    nsSkipCharsRunIterator run(
3446
0
        start, nsSkipCharsRunIterator::LENGTH_UNSKIPPED_ONLY, aRange.Length());
3447
0
    while (run.NextRun()) {
3448
0
      uint32_t runOffsetInSubstring = run.GetSkippedOffset() - aRange.start;
3449
0
      gfxSkipCharsIterator iter = run.GetPos();
3450
0
      for (int32_t i = 0; i < run.GetRunLength(); ++i) {
3451
0
        if (CanAddSpacingAfter(mTextRun, run.GetSkippedOffset() + i)) {
3452
0
          // End of a cluster, not in a ligature: put letter-spacing after it
3453
0
          aSpacing[runOffsetInSubstring + i].mAfter += mLetterSpacing;
3454
0
        }
3455
0
        if (IsCSSWordSpacingSpace(mFrag, i + run.GetOriginalOffset(),
3456
0
                                  mFrame, mTextStyle)) {
3457
0
          // It kinda sucks, but space characters can be part of clusters,
3458
0
          // and even still be whitespace (I think!)
3459
0
          iter.SetSkippedOffset(run.GetSkippedOffset() + i);
3460
0
          FindClusterEnd(mTextRun, run.GetOriginalOffset() + run.GetRunLength(),
3461
0
                         &iter);
3462
0
          uint32_t runOffset = iter.GetSkippedOffset() - aRange.start;
3463
0
          aSpacing[runOffset].mAfter += mWordSpacing;
3464
0
        }
3465
0
      }
3466
0
    }
3467
0
  }
3468
0
3469
0
  // Now add tab spacing, if there is any
3470
0
  if (!aIgnoreTabs) {
3471
0
    gfxFloat tabWidth = ComputeTabWidthAppUnits(mFrame, mTextRun);
3472
0
    if (tabWidth > 0) {
3473
0
      CalcTabWidths(aRange, tabWidth);
3474
0
      if (mTabWidths) {
3475
0
        mTabWidths->ApplySpacing(aSpacing,
3476
0
                                 aRange.start - mStart.GetSkippedOffset(),
3477
0
                                 aRange.Length());
3478
0
      }
3479
0
    }
3480
0
  }
3481
0
3482
0
  // Now add in justification spacing
3483
0
  if (mJustificationSpacings.Length() > 0) {
3484
0
    // If there is any spaces trimmed at the end, aStart + aLength may
3485
0
    // be larger than the flags array. When that happens, we can simply
3486
0
    // ignore those spaces.
3487
0
    auto arrayEnd = mJustificationArrayStart +
3488
0
      static_cast<uint32_t>(mJustificationSpacings.Length());
3489
0
    auto end = std::min(aRange.end, arrayEnd);
3490
0
    MOZ_ASSERT(aRange.start >= mJustificationArrayStart);
3491
0
    for (auto i = aRange.start; i < end; i++) {
3492
0
      const auto& spacing =
3493
0
        mJustificationSpacings[i - mJustificationArrayStart];
3494
0
      uint32_t offset = i - aRange.start;
3495
0
      aSpacing[offset].mBefore += spacing.mBefore;
3496
0
      aSpacing[offset].mAfter += spacing.mAfter;
3497
0
    }
3498
0
  }
3499
0
}
3500
3501
// aX and the result are in whole appunits.
3502
static gfxFloat
3503
AdvanceToNextTab(gfxFloat aX, gfxFloat aTabWidth, gfxFloat aMinAdvance)
3504
0
{
3505
0
  // Advance aX to the next multiple of aTabWidth. We must advance
3506
0
  // by at least aMinAdvance.
3507
0
  return ceil((aX + aMinAdvance) / aTabWidth) * aTabWidth;
3508
0
}
3509
3510
void
3511
PropertyProvider::CalcTabWidths(Range aRange, gfxFloat aTabWidth) const
3512
0
{
3513
0
  MOZ_ASSERT(aTabWidth > 0);
3514
0
3515
0
  if (!mTabWidths) {
3516
0
    if (mReflowing && !mLineContainer) {
3517
0
      // Intrinsic width computation does its own tab processing. We
3518
0
      // just don't do anything here.
3519
0
      return;
3520
0
    }
3521
0
    if (!mReflowing) {
3522
0
      mTabWidths = mFrame->GetProperty(TabWidthProperty());
3523
#ifdef DEBUG
3524
      // If we're not reflowing, we should have already computed the
3525
      // tab widths; check that they're available as far as the last
3526
      // tab character present (if any)
3527
      for (uint32_t i = aRange.end; i > aRange.start; --i) {
3528
        if (mTextRun->CharIsTab(i - 1)) {
3529
          uint32_t startOffset = mStart.GetSkippedOffset();
3530
          NS_ASSERTION(mTabWidths && mTabWidths->mLimit + startOffset >= i,
3531
                       "Precomputed tab widths are missing!");
3532
          break;
3533
        }
3534
      }
3535
#endif
3536
      return;
3537
0
    }
3538
0
  }
3539
0
3540
0
  uint32_t startOffset = mStart.GetSkippedOffset();
3541
0
  MOZ_ASSERT(aRange.start >= startOffset, "wrong start offset");
3542
0
  MOZ_ASSERT(aRange.end <= startOffset + mLength, "beyond the end");
3543
0
  uint32_t tabsEnd =
3544
0
    (mTabWidths ? mTabWidths->mLimit : mTabWidthsAnalyzedLimit) + startOffset;
3545
0
  if (tabsEnd < aRange.end) {
3546
0
    NS_ASSERTION(mReflowing,
3547
0
                 "We need precomputed tab widths, but don't have enough.");
3548
0
3549
0
    for (uint32_t i = tabsEnd; i < aRange.end; ++i) {
3550
0
      Spacing spacing;
3551
0
      GetSpacingInternal(Range(i, i + 1), &spacing, true);
3552
0
      mOffsetFromBlockOriginForTabs += spacing.mBefore;
3553
0
3554
0
      if (!mTextRun->CharIsTab(i)) {
3555
0
        if (mTextRun->IsClusterStart(i)) {
3556
0
          uint32_t clusterEnd = i + 1;
3557
0
          while (clusterEnd < mTextRun->GetLength() &&
3558
0
                 !mTextRun->IsClusterStart(clusterEnd)) {
3559
0
            ++clusterEnd;
3560
0
          }
3561
0
          mOffsetFromBlockOriginForTabs +=
3562
0
            mTextRun->GetAdvanceWidth(Range(i, clusterEnd), nullptr);
3563
0
        }
3564
0
      } else {
3565
0
        if (!mTabWidths) {
3566
0
          mTabWidths = new TabWidthStore(mFrame->GetContentOffset());
3567
0
          mFrame->SetProperty(TabWidthProperty(), mTabWidths);
3568
0
        }
3569
0
        double nextTab = AdvanceToNextTab(mOffsetFromBlockOriginForTabs,
3570
0
                                          aTabWidth, MinTabAdvance());
3571
0
        mTabWidths->mWidths.AppendElement(TabWidth(i - startOffset,
3572
0
                NSToIntRound(nextTab - mOffsetFromBlockOriginForTabs)));
3573
0
        mOffsetFromBlockOriginForTabs = nextTab;
3574
0
      }
3575
0
3576
0
      mOffsetFromBlockOriginForTabs += spacing.mAfter;
3577
0
    }
3578
0
3579
0
    if (mTabWidths) {
3580
0
      mTabWidths->mLimit = aRange.end - startOffset;
3581
0
    }
3582
0
  }
3583
0
3584
0
  if (!mTabWidths) {
3585
0
    // Delete any stale property that may be left on the frame
3586
0
    mFrame->DeleteProperty(TabWidthProperty());
3587
0
    mTabWidthsAnalyzedLimit = std::max(mTabWidthsAnalyzedLimit,
3588
0
                                       aRange.end - startOffset);
3589
0
  }
3590
0
}
3591
3592
gfxFloat
3593
PropertyProvider::GetHyphenWidth() const
3594
0
{
3595
0
  if (mHyphenWidth < 0) {
3596
0
    mHyphenWidth = GetFontGroup()->GetHyphenWidth(this);
3597
0
  }
3598
0
  return mHyphenWidth + mLetterSpacing;
3599
0
}
3600
3601
static inline bool
3602
IS_HYPHEN(char16_t u)
3603
0
{
3604
0
  return (u == char16_t('-') ||
3605
0
          u == 0x058A || // ARMENIAN HYPHEN
3606
0
          u == 0x2010 || // HYPHEN
3607
0
          u == 0x2012 || // FIGURE DASH
3608
0
          u == 0x2013);  // EN DASH
3609
0
}
3610
3611
void
3612
PropertyProvider::GetHyphenationBreaks(Range aRange, HyphenType* aBreakBefore) const
3613
0
{
3614
0
  MOZ_ASSERT(IsInBounds(mStart, mLength, aRange), "Range out of bounds");
3615
0
  MOZ_ASSERT(mLength != INT32_MAX, "Can't call this with undefined length");
3616
0
3617
0
  if (!mTextStyle->WhiteSpaceCanWrap(mFrame) ||
3618
0
      mTextStyle->mHyphens == StyleHyphens::None)
3619
0
  {
3620
0
    memset(aBreakBefore, static_cast<uint8_t>(HyphenType::None),
3621
0
           aRange.Length() * sizeof(HyphenType));
3622
0
    return;
3623
0
  }
3624
0
3625
0
  // Iterate through the original-string character runs
3626
0
  nsSkipCharsRunIterator run(
3627
0
      mStart, nsSkipCharsRunIterator::LENGTH_UNSKIPPED_ONLY, aRange.Length());
3628
0
  run.SetSkippedOffset(aRange.start);
3629
0
  // We need to visit skipped characters so that we can detect SHY
3630
0
  run.SetVisitSkipped();
3631
0
3632
0
  int32_t prevTrailingCharOffset = run.GetPos().GetOriginalOffset() - 1;
3633
0
  bool allowHyphenBreakBeforeNextChar =
3634
0
    prevTrailingCharOffset >= mStart.GetOriginalOffset() &&
3635
0
    prevTrailingCharOffset < mStart.GetOriginalOffset() + mLength &&
3636
0
    mFrag->CharAt(prevTrailingCharOffset) == CH_SHY;
3637
0
3638
0
  while (run.NextRun()) {
3639
0
    NS_ASSERTION(run.GetRunLength() > 0, "Shouldn't return zero-length runs");
3640
0
    if (run.IsSkipped()) {
3641
0
      // Check if there's a soft hyphen which would let us hyphenate before
3642
0
      // the next non-skipped character. Don't look at soft hyphens followed
3643
0
      // by other skipped characters, we won't use them.
3644
0
      allowHyphenBreakBeforeNextChar =
3645
0
        mFrag->CharAt(run.GetOriginalOffset() + run.GetRunLength() - 1) == CH_SHY;
3646
0
    } else {
3647
0
      int32_t runOffsetInSubstring = run.GetSkippedOffset() - aRange.start;
3648
0
      memset(aBreakBefore + runOffsetInSubstring,
3649
0
             static_cast<uint8_t>(HyphenType::None),
3650
0
             run.GetRunLength() * sizeof(HyphenType));
3651
0
      // Don't allow hyphen breaks at the start of the line
3652
0
      aBreakBefore[runOffsetInSubstring] =
3653
0
          allowHyphenBreakBeforeNextChar &&
3654
0
          (!(mFrame->GetStateBits() & TEXT_START_OF_LINE) ||
3655
0
           run.GetSkippedOffset() > mStart.GetSkippedOffset())
3656
0
          ? HyphenType::Soft
3657
0
          : HyphenType::None;
3658
0
      allowHyphenBreakBeforeNextChar = false;
3659
0
    }
3660
0
  }
3661
0
3662
0
  if (mTextStyle->mHyphens == StyleHyphens::Auto) {
3663
0
    gfxSkipCharsIterator skipIter(mStart);
3664
0
    for (uint32_t i = 0; i < aRange.Length(); ++i) {
3665
0
      if (IS_HYPHEN(mFrag->
3666
0
          CharAt(skipIter.ConvertSkippedToOriginal(aRange.start + i)))) {
3667
0
        if (i < aRange.Length() - 1) {
3668
0
          aBreakBefore[i + 1] = HyphenType::Explicit;
3669
0
        }
3670
0
        continue;
3671
0
      }
3672
0
3673
0
      if (mTextRun->CanHyphenateBefore(aRange.start + i) &&
3674
0
          aBreakBefore[i] == HyphenType::None) {
3675
0
        aBreakBefore[i] = HyphenType::AutoWithoutManualInSameWord;
3676
0
      }
3677
0
    }
3678
0
  }
3679
0
}
3680
3681
void
3682
PropertyProvider::InitializeForDisplay(bool aTrimAfter)
3683
0
{
3684
0
  nsTextFrame::TrimmedOffsets trimmed =
3685
0
    mFrame->GetTrimmedOffsets(mFrag, aTrimAfter);
3686
0
  mStart.SetOriginalOffset(trimmed.mStart);
3687
0
  mLength = trimmed.mLength;
3688
0
  SetupJustificationSpacing(true);
3689
0
}
3690
3691
void
3692
PropertyProvider::InitializeForMeasure()
3693
0
{
3694
0
  nsTextFrame::TrimmedOffsets trimmed =
3695
0
    mFrame->GetTrimmedOffsets(mFrag, true, false);
3696
0
  mStart.SetOriginalOffset(trimmed.mStart);
3697
0
  mLength = trimmed.mLength;
3698
0
  SetupJustificationSpacing(false);
3699
0
}
3700
3701
3702
void
3703
PropertyProvider::SetupJustificationSpacing(bool aPostReflow)
3704
0
{
3705
0
  MOZ_ASSERT(mLength != INT32_MAX, "Can't call this with undefined length");
3706
0
3707
0
  if (!(mFrame->GetStateBits() & TEXT_JUSTIFICATION_ENABLED)) {
3708
0
    return;
3709
0
  }
3710
0
3711
0
  gfxSkipCharsIterator start(mStart), end(mStart);
3712
0
  // We can't just use our mLength here; when InitializeForDisplay is
3713
0
  // called with false for aTrimAfter, we still shouldn't be assigning
3714
0
  // justification space to any trailing whitespace.
3715
0
  nsTextFrame::TrimmedOffsets trimmed =
3716
0
    mFrame->GetTrimmedOffsets(mFrag, true, aPostReflow);
3717
0
  end.AdvanceOriginal(trimmed.mLength);
3718
0
  gfxSkipCharsIterator realEnd(end);
3719
0
3720
0
  Range range(uint32_t(start.GetOriginalOffset()),
3721
0
              uint32_t(end.GetOriginalOffset()));
3722
0
  nsTArray<JustificationAssignment> assignments;
3723
0
  JustificationInfo info = ComputeJustification(range, &assignments);
3724
0
3725
0
  auto assign = mFrame->GetJustificationAssignment();
3726
0
  auto totalGaps = JustificationUtils::CountGaps(info, assign);
3727
0
  if (!totalGaps || assignments.IsEmpty()) {
3728
0
    // Nothing to do, nothing is justifiable and we shouldn't have any
3729
0
    // justification space assigned
3730
0
    return;
3731
0
  }
3732
0
3733
0
  // Remember that textrun measurements are in the run's orientation,
3734
0
  // so its advance "width" is actually a height in vertical writing modes,
3735
0
  // corresponding to the inline-direction of the frame.
3736
0
  gfxFloat naturalWidth =
3737
0
    mTextRun->GetAdvanceWidth(Range(mStart.GetSkippedOffset(),
3738
0
                                    realEnd.GetSkippedOffset()), this);
3739
0
  if (mFrame->GetStateBits() & TEXT_HYPHEN_BREAK) {
3740
0
    naturalWidth += GetHyphenWidth();
3741
0
  }
3742
0
  nscoord totalSpacing = mFrame->ISize() - naturalWidth;
3743
0
  if (totalSpacing <= 0) {
3744
0
    // No space available
3745
0
    return;
3746
0
  }
3747
0
3748
0
  assignments[0].mGapsAtStart = assign.mGapsAtStart;
3749
0
  assignments.LastElement().mGapsAtEnd = assign.mGapsAtEnd;
3750
0
3751
0
  MOZ_ASSERT(mJustificationSpacings.IsEmpty());
3752
0
  JustificationApplicationState state(totalGaps, totalSpacing);
3753
0
  mJustificationSpacings.SetCapacity(assignments.Length());
3754
0
  for (const JustificationAssignment& assign : assignments) {
3755
0
    Spacing* spacing = mJustificationSpacings.AppendElement();
3756
0
    spacing->mBefore = state.Consume(assign.mGapsAtStart);
3757
0
    spacing->mAfter = state.Consume(assign.mGapsAtEnd);
3758
0
  }
3759
0
}
3760
3761
//----------------------------------------------------------------------
3762
3763
static nscolor
3764
EnsureDifferentColors(nscolor colorA, nscolor colorB)
3765
0
{
3766
0
  if (colorA == colorB) {
3767
0
    nscolor res;
3768
0
    res = NS_RGB(NS_GET_R(colorA) ^ 0xff,
3769
0
                 NS_GET_G(colorA) ^ 0xff,
3770
0
                 NS_GET_B(colorA) ^ 0xff);
3771
0
    return res;
3772
0
  }
3773
0
  return colorA;
3774
0
}
3775
3776
//-----------------------------------------------------------------------------
3777
3778
nsTextPaintStyle::nsTextPaintStyle(nsTextFrame* aFrame)
3779
  : mFrame(aFrame),
3780
    mPresContext(aFrame->PresContext()),
3781
    mInitCommonColors(false),
3782
    mInitSelectionColorsAndShadow(false),
3783
    mResolveColors(true),
3784
    mSelectionTextColor(NS_RGBA(0, 0, 0, 0)),
3785
    mSelectionBGColor(NS_RGBA(0, 0, 0, 0)),
3786
    mHasSelectionShadow(false),
3787
    mSufficientContrast(0),
3788
    mFrameBackgroundColor(NS_RGBA(0, 0, 0, 0)),
3789
    mSystemFieldForegroundColor(NS_RGBA(0, 0, 0, 0)),
3790
    mSystemFieldBackgroundColor(NS_RGBA(0, 0, 0, 0))
3791
0
{
3792
0
  for (uint32_t i = 0; i < ArrayLength(mSelectionStyle); i++)
3793
0
    mSelectionStyle[i].mInit = false;
3794
0
}
3795
3796
bool
3797
nsTextPaintStyle::EnsureSufficientContrast(nscolor *aForeColor, nscolor *aBackColor)
3798
0
{
3799
0
  InitCommonColors();
3800
0
3801
0
  // If the combination of selection background color and frame background color
3802
0
  // is sufficient contrast, don't exchange the selection colors.
3803
0
  int32_t backLuminosityDifference =
3804
0
            NS_LUMINOSITY_DIFFERENCE(*aBackColor, mFrameBackgroundColor);
3805
0
  if (backLuminosityDifference >= mSufficientContrast)
3806
0
    return false;
3807
0
3808
0
  // Otherwise, we should use the higher-contrast color for the selection
3809
0
  // background color.
3810
0
  int32_t foreLuminosityDifference =
3811
0
            NS_LUMINOSITY_DIFFERENCE(*aForeColor, mFrameBackgroundColor);
3812
0
  if (backLuminosityDifference < foreLuminosityDifference) {
3813
0
    nscolor tmpColor = *aForeColor;
3814
0
    *aForeColor = *aBackColor;
3815
0
    *aBackColor = tmpColor;
3816
0
    return true;
3817
0
  }
3818
0
  return false;
3819
0
}
3820
3821
nscolor
3822
nsTextPaintStyle::GetTextColor()
3823
0
{
3824
0
  if (nsSVGUtils::IsInSVGTextSubtree(mFrame)) {
3825
0
    if (!mResolveColors)
3826
0
      return NS_SAME_AS_FOREGROUND_COLOR;
3827
0
3828
0
    const nsStyleSVG* style = mFrame->StyleSVG();
3829
0
    switch (style->mFill.Type()) {
3830
0
      case eStyleSVGPaintType_None:
3831
0
        return NS_RGBA(0, 0, 0, 0);
3832
0
      case eStyleSVGPaintType_Color:
3833
0
        return nsLayoutUtils::GetColor(mFrame, &nsStyleSVG::mFill);
3834
0
      default:
3835
0
        NS_ERROR("cannot resolve SVG paint to nscolor");
3836
0
        return NS_RGBA(0, 0, 0, 255);
3837
0
    }
3838
0
  }
3839
0
3840
0
  return nsLayoutUtils::GetColor(mFrame, &nsStyleText::mWebkitTextFillColor);
3841
0
}
3842
3843
bool
3844
nsTextPaintStyle::GetSelectionColors(nscolor* aForeColor,
3845
                                     nscolor* aBackColor)
3846
0
{
3847
0
  NS_ASSERTION(aForeColor, "aForeColor is null");
3848
0
  NS_ASSERTION(aBackColor, "aBackColor is null");
3849
0
3850
0
  if (!InitSelectionColorsAndShadow())
3851
0
    return false;
3852
0
3853
0
  *aForeColor = mSelectionTextColor;
3854
0
  *aBackColor = mSelectionBGColor;
3855
0
  return true;
3856
0
}
3857
3858
void
3859
nsTextPaintStyle::GetHighlightColors(nscolor* aForeColor,
3860
                                     nscolor* aBackColor)
3861
0
{
3862
0
  NS_ASSERTION(aForeColor, "aForeColor is null");
3863
0
  NS_ASSERTION(aBackColor, "aBackColor is null");
3864
0
3865
0
  const nsFrameSelection* frameSelection = mFrame->GetConstFrameSelection();
3866
0
  const Selection* selection =
3867
0
    frameSelection->GetSelection(SelectionType::eFind);
3868
0
  const SelectionCustomColors* customColors = nullptr;
3869
0
  if (selection) {
3870
0
    customColors = selection->GetCustomColors();
3871
0
  }
3872
0
3873
0
  if (!customColors) {
3874
0
    nscolor backColor =
3875
0
      LookAndFeel::GetColor(LookAndFeel::eColorID_TextHighlightBackground);
3876
0
    nscolor foreColor =
3877
0
      LookAndFeel::GetColor(LookAndFeel::eColorID_TextHighlightForeground);
3878
0
    EnsureSufficientContrast(&foreColor, &backColor);
3879
0
    *aForeColor = foreColor;
3880
0
    *aBackColor = backColor;
3881
0
3882
0
    return;
3883
0
  }
3884
0
3885
0
  if (customColors->mForegroundColor && customColors->mBackgroundColor) {
3886
0
    nscolor foreColor = *customColors->mForegroundColor;
3887
0
    nscolor backColor = *customColors->mBackgroundColor;
3888
0
3889
0
    if (EnsureSufficientContrast(&foreColor, &backColor) &&
3890
0
        customColors->mAltForegroundColor &&
3891
0
        customColors->mAltBackgroundColor) {
3892
0
      foreColor = *customColors->mAltForegroundColor;
3893
0
      backColor = *customColors->mAltBackgroundColor;
3894
0
    }
3895
0
3896
0
    *aForeColor = foreColor;
3897
0
    *aBackColor = backColor;
3898
0
    return;
3899
0
  }
3900
0
3901
0
  InitCommonColors();
3902
0
3903
0
  if (customColors->mBackgroundColor) {
3904
0
    // !mForegroundColor means "currentColor"; the current color of the text.
3905
0
    nscolor foreColor = GetTextColor();
3906
0
    nscolor backColor = *customColors->mBackgroundColor;
3907
0
3908
0
    int32_t luminosityDifference =
3909
0
              NS_LUMINOSITY_DIFFERENCE(foreColor, backColor);
3910
0
3911
0
    if (mSufficientContrast > luminosityDifference &&
3912
0
        customColors->mAltBackgroundColor) {
3913
0
      int32_t altLuminosityDifference =
3914
0
                NS_LUMINOSITY_DIFFERENCE(foreColor, *customColors->mAltBackgroundColor);
3915
0
3916
0
      if (luminosityDifference < altLuminosityDifference) {
3917
0
        backColor = *customColors->mAltBackgroundColor;
3918
0
      }
3919
0
    }
3920
0
3921
0
    *aForeColor = foreColor;
3922
0
    *aBackColor = backColor;
3923
0
    return;
3924
0
  }
3925
0
3926
0
  if (customColors->mForegroundColor) {
3927
0
    nscolor foreColor = *customColors->mForegroundColor;
3928
0
    // !mBackgroundColor means "transparent"; the current color of the background.
3929
0
3930
0
    int32_t luminosityDifference =
3931
0
              NS_LUMINOSITY_DIFFERENCE(foreColor, mFrameBackgroundColor);
3932
0
3933
0
    if (mSufficientContrast > luminosityDifference &&
3934
0
        customColors->mAltForegroundColor) {
3935
0
      int32_t altLuminosityDifference =
3936
0
                NS_LUMINOSITY_DIFFERENCE(*customColors->mForegroundColor, mFrameBackgroundColor);
3937
0
3938
0
      if (luminosityDifference < altLuminosityDifference) {
3939
0
        foreColor = *customColors->mAltForegroundColor;
3940
0
      }
3941
0
    }
3942
0
3943
0
    *aForeColor = foreColor;
3944
0
    *aBackColor = NS_TRANSPARENT;
3945
0
    return;
3946
0
  }
3947
0
3948
0
  // There are neither mForegroundColor nor mBackgroundColor.
3949
0
  *aForeColor = GetTextColor();
3950
0
  *aBackColor = NS_TRANSPARENT;
3951
0
}
3952
3953
void
3954
nsTextPaintStyle::GetURLSecondaryColor(nscolor* aForeColor)
3955
0
{
3956
0
  NS_ASSERTION(aForeColor, "aForeColor is null");
3957
0
3958
0
  nscolor textColor = GetTextColor();
3959
0
  textColor = NS_RGBA(NS_GET_R(textColor),
3960
0
                      NS_GET_G(textColor),
3961
0
                      NS_GET_B(textColor),
3962
0
                      (uint8_t)(255 * 0.5f));
3963
0
  // Don't use true alpha color for readability.
3964
0
  InitCommonColors();
3965
0
  *aForeColor = NS_ComposeColors(mFrameBackgroundColor, textColor);
3966
0
}
3967
3968
void
3969
nsTextPaintStyle::GetIMESelectionColors(int32_t  aIndex,
3970
                                        nscolor* aForeColor,
3971
                                        nscolor* aBackColor)
3972
0
{
3973
0
  NS_ASSERTION(aForeColor, "aForeColor is null");
3974
0
  NS_ASSERTION(aBackColor, "aBackColor is null");
3975
0
  NS_ASSERTION(aIndex >= 0 && aIndex < 5, "Index out of range");
3976
0
3977
0
  nsSelectionStyle* selectionStyle = GetSelectionStyle(aIndex);
3978
0
  *aForeColor = selectionStyle->mTextColor;
3979
0
  *aBackColor = selectionStyle->mBGColor;
3980
0
}
3981
3982
bool
3983
nsTextPaintStyle::GetSelectionUnderlineForPaint(int32_t  aIndex,
3984
                                                nscolor* aLineColor,
3985
                                                float*   aRelativeSize,
3986
                                                uint8_t* aStyle)
3987
0
{
3988
0
  NS_ASSERTION(aLineColor, "aLineColor is null");
3989
0
  NS_ASSERTION(aRelativeSize, "aRelativeSize is null");
3990
0
  NS_ASSERTION(aIndex >= 0 && aIndex < 5, "Index out of range");
3991
0
3992
0
  nsSelectionStyle* selectionStyle = GetSelectionStyle(aIndex);
3993
0
  if (selectionStyle->mUnderlineStyle == NS_STYLE_TEXT_DECORATION_STYLE_NONE ||
3994
0
      selectionStyle->mUnderlineColor == NS_TRANSPARENT ||
3995
0
      selectionStyle->mUnderlineRelativeSize <= 0.0f)
3996
0
    return false;
3997
0
3998
0
  *aLineColor = selectionStyle->mUnderlineColor;
3999
0
  *aRelativeSize = selectionStyle->mUnderlineRelativeSize;
4000
0
  *aStyle = selectionStyle->mUnderlineStyle;
4001
0
  return true;
4002
0
}
4003
4004
void
4005
nsTextPaintStyle::InitCommonColors()
4006
0
{
4007
0
  if (mInitCommonColors)
4008
0
    return;
4009
0
4010
0
  nsIFrame* bgFrame =
4011
0
    nsCSSRendering::FindNonTransparentBackgroundFrame(mFrame);
4012
0
  NS_ASSERTION(bgFrame, "Cannot find NonTransparentBackgroundFrame.");
4013
0
  nscolor bgColor = bgFrame->
4014
0
    GetVisitedDependentColor(&nsStyleBackground::mBackgroundColor);
4015
0
4016
0
  nscolor defaultBgColor = mPresContext->DefaultBackgroundColor();
4017
0
  mFrameBackgroundColor = NS_ComposeColors(defaultBgColor, bgColor);
4018
0
4019
0
  mSystemFieldForegroundColor =
4020
0
    LookAndFeel::GetColor(LookAndFeel::eColorID__moz_fieldtext);
4021
0
  mSystemFieldBackgroundColor =
4022
0
    LookAndFeel::GetColor(LookAndFeel::eColorID__moz_field);
4023
0
4024
0
  if (bgFrame->IsThemed()) {
4025
0
    // Assume a native widget has sufficient contrast always
4026
0
    mSufficientContrast = 0;
4027
0
    mInitCommonColors = true;
4028
0
    return;
4029
0
  }
4030
0
4031
0
  NS_ASSERTION(NS_GET_A(defaultBgColor) == 255,
4032
0
               "default background color is not opaque");
4033
0
4034
0
  nscolor defaultWindowBackgroundColor =
4035
0
    LookAndFeel::GetColor(LookAndFeel::eColorID_WindowBackground);
4036
0
  nscolor selectionTextColor =
4037
0
    LookAndFeel::GetColor(LookAndFeel::eColorID_TextSelectForeground);
4038
0
  nscolor selectionBGColor =
4039
0
    LookAndFeel::GetColor(LookAndFeel::eColorID_TextSelectBackground);
4040
0
4041
0
  mSufficientContrast =
4042
0
    std::min(std::min(NS_SUFFICIENT_LUMINOSITY_DIFFERENCE,
4043
0
                  NS_LUMINOSITY_DIFFERENCE(selectionTextColor,
4044
0
                                           selectionBGColor)),
4045
0
                  NS_LUMINOSITY_DIFFERENCE(defaultWindowBackgroundColor,
4046
0
                                           selectionBGColor));
4047
0
4048
0
  mInitCommonColors = true;
4049
0
}
4050
4051
nscolor
4052
nsTextPaintStyle::GetSystemFieldForegroundColor()
4053
0
{
4054
0
  InitCommonColors();
4055
0
  return mSystemFieldForegroundColor;
4056
0
}
4057
4058
nscolor
4059
nsTextPaintStyle::GetSystemFieldBackgroundColor()
4060
0
{
4061
0
  InitCommonColors();
4062
0
  return mSystemFieldBackgroundColor;
4063
0
}
4064
4065
bool
4066
nsTextPaintStyle::InitSelectionColorsAndShadow()
4067
0
{
4068
0
  if (mInitSelectionColorsAndShadow)
4069
0
    return true;
4070
0
4071
0
  int16_t selectionFlags;
4072
0
  int16_t selectionStatus = mFrame->GetSelectionStatus(&selectionFlags);
4073
0
  if (!(selectionFlags & nsISelectionDisplay::DISPLAY_TEXT) ||
4074
0
      selectionStatus < nsISelectionController::SELECTION_ON) {
4075
0
    // Not displaying the normal selection.
4076
0
    // We're not caching this fact, so every call to GetSelectionColors
4077
0
    // will come through here. We could avoid this, but it's not really worth it.
4078
0
    return false;
4079
0
  }
4080
0
4081
0
  mInitSelectionColorsAndShadow = true;
4082
0
4083
0
  if (selectionStatus == nsISelectionController::SELECTION_ON) {
4084
0
    // Use ::selection pseudo class.
4085
0
    if (RefPtr<ComputedStyle> style = mFrame->ComputeSelectionStyle()) {
4086
0
      mSelectionBGColor =
4087
0
        style->GetVisitedDependentColor(&nsStyleBackground::mBackgroundColor);
4088
0
      mSelectionTextColor =
4089
0
        style->GetVisitedDependentColor(&nsStyleText::mWebkitTextFillColor);
4090
0
      mHasSelectionShadow = true;
4091
0
      mSelectionShadow = style->StyleText()->mTextShadow;
4092
0
      return true;
4093
0
    }
4094
0
  }
4095
0
4096
0
  nscolor selectionBGColor =
4097
0
    LookAndFeel::GetColor(LookAndFeel::eColorID_TextSelectBackground);
4098
0
4099
0
  if (selectionStatus == nsISelectionController::SELECTION_ATTENTION) {
4100
0
    mSelectionBGColor =
4101
0
      LookAndFeel::GetColor(
4102
0
        LookAndFeel::eColorID_TextSelectBackgroundAttention);
4103
0
    mSelectionBGColor  = EnsureDifferentColors(mSelectionBGColor,
4104
0
                                               selectionBGColor);
4105
0
  } else if (selectionStatus != nsISelectionController::SELECTION_ON) {
4106
0
    mSelectionBGColor =
4107
0
      LookAndFeel::GetColor(LookAndFeel::eColorID_TextSelectBackgroundDisabled);
4108
0
    mSelectionBGColor  = EnsureDifferentColors(mSelectionBGColor,
4109
0
                                               selectionBGColor);
4110
0
  } else {
4111
0
    mSelectionBGColor = selectionBGColor;
4112
0
  }
4113
0
4114
0
  mSelectionTextColor =
4115
0
    LookAndFeel::GetColor(LookAndFeel::eColorID_TextSelectForeground);
4116
0
4117
0
  if (mResolveColors) {
4118
0
    // On MacOS X, we don't exchange text color and BG color.
4119
0
    if (mSelectionTextColor == NS_DONT_CHANGE_COLOR) {
4120
0
      nscolor frameColor = nsSVGUtils::IsInSVGTextSubtree(mFrame)
4121
0
        ? mFrame->GetVisitedDependentColor(&nsStyleSVG::mFill)
4122
0
        : mFrame->GetVisitedDependentColor(&nsStyleText::mWebkitTextFillColor);
4123
0
      mSelectionTextColor = EnsureDifferentColors(frameColor, mSelectionBGColor);
4124
0
    } else if (mSelectionTextColor == NS_CHANGE_COLOR_IF_SAME_AS_BG) {
4125
0
      nscolor frameColor = nsSVGUtils::IsInSVGTextSubtree(mFrame)
4126
0
        ? mFrame->GetVisitedDependentColor(&nsStyleSVG::mFill)
4127
0
        : mFrame->GetVisitedDependentColor(&nsStyleText::mWebkitTextFillColor);
4128
0
      if (frameColor == mSelectionBGColor) {
4129
0
        mSelectionTextColor =
4130
0
          LookAndFeel::GetColor(LookAndFeel::eColorID_TextSelectForegroundCustom);
4131
0
      }
4132
0
    } else {
4133
0
      EnsureSufficientContrast(&mSelectionTextColor, &mSelectionBGColor);
4134
0
    }
4135
0
  } else {
4136
0
    if (mSelectionTextColor == NS_DONT_CHANGE_COLOR) {
4137
0
      mSelectionTextColor = NS_SAME_AS_FOREGROUND_COLOR;
4138
0
    }
4139
0
  }
4140
0
  return true;
4141
0
}
4142
4143
nsTextPaintStyle::nsSelectionStyle*
4144
nsTextPaintStyle::GetSelectionStyle(int32_t aIndex)
4145
0
{
4146
0
  InitSelectionStyle(aIndex);
4147
0
  return &mSelectionStyle[aIndex];
4148
0
}
4149
4150
struct StyleIDs {
4151
  LookAndFeel::ColorID mForeground, mBackground, mLine;
4152
  LookAndFeel::IntID mLineStyle;
4153
  LookAndFeel::FloatID mLineRelativeSize;
4154
};
4155
static StyleIDs SelectionStyleIDs[] = {
4156
  { LookAndFeel::eColorID_IMERawInputForeground,
4157
    LookAndFeel::eColorID_IMERawInputBackground,
4158
    LookAndFeel::eColorID_IMERawInputUnderline,
4159
    LookAndFeel::eIntID_IMERawInputUnderlineStyle,
4160
    LookAndFeel::eFloatID_IMEUnderlineRelativeSize },
4161
  { LookAndFeel::eColorID_IMESelectedRawTextForeground,
4162
    LookAndFeel::eColorID_IMESelectedRawTextBackground,
4163
    LookAndFeel::eColorID_IMESelectedRawTextUnderline,
4164
    LookAndFeel::eIntID_IMESelectedRawTextUnderlineStyle,
4165
    LookAndFeel::eFloatID_IMEUnderlineRelativeSize },
4166
  { LookAndFeel::eColorID_IMEConvertedTextForeground,
4167
    LookAndFeel::eColorID_IMEConvertedTextBackground,
4168
    LookAndFeel::eColorID_IMEConvertedTextUnderline,
4169
    LookAndFeel::eIntID_IMEConvertedTextUnderlineStyle,
4170
    LookAndFeel::eFloatID_IMEUnderlineRelativeSize },
4171
  { LookAndFeel::eColorID_IMESelectedConvertedTextForeground,
4172
    LookAndFeel::eColorID_IMESelectedConvertedTextBackground,
4173
    LookAndFeel::eColorID_IMESelectedConvertedTextUnderline,
4174
    LookAndFeel::eIntID_IMESelectedConvertedTextUnderline,
4175
    LookAndFeel::eFloatID_IMEUnderlineRelativeSize },
4176
  { LookAndFeel::eColorID_LAST_COLOR,
4177
    LookAndFeel::eColorID_LAST_COLOR,
4178
    LookAndFeel::eColorID_SpellCheckerUnderline,
4179
    LookAndFeel::eIntID_SpellCheckerUnderlineStyle,
4180
    LookAndFeel::eFloatID_SpellCheckerUnderlineRelativeSize }
4181
};
4182
4183
void
4184
nsTextPaintStyle::InitSelectionStyle(int32_t aIndex)
4185
0
{
4186
0
  NS_ASSERTION(aIndex >= 0 && aIndex < 5, "aIndex is invalid");
4187
0
  nsSelectionStyle* selectionStyle = &mSelectionStyle[aIndex];
4188
0
  if (selectionStyle->mInit)
4189
0
    return;
4190
0
4191
0
  StyleIDs* styleIDs = &SelectionStyleIDs[aIndex];
4192
0
4193
0
  nscolor foreColor, backColor;
4194
0
  if (styleIDs->mForeground == LookAndFeel::eColorID_LAST_COLOR) {
4195
0
    foreColor = NS_SAME_AS_FOREGROUND_COLOR;
4196
0
  } else {
4197
0
    foreColor = LookAndFeel::GetColor(styleIDs->mForeground);
4198
0
  }
4199
0
  if (styleIDs->mBackground == LookAndFeel::eColorID_LAST_COLOR) {
4200
0
    backColor = NS_TRANSPARENT;
4201
0
  } else {
4202
0
    backColor = LookAndFeel::GetColor(styleIDs->mBackground);
4203
0
  }
4204
0
4205
0
  // Convert special color to actual color
4206
0
  NS_ASSERTION(foreColor != NS_TRANSPARENT,
4207
0
               "foreColor cannot be NS_TRANSPARENT");
4208
0
  NS_ASSERTION(backColor != NS_SAME_AS_FOREGROUND_COLOR,
4209
0
               "backColor cannot be NS_SAME_AS_FOREGROUND_COLOR");
4210
0
  NS_ASSERTION(backColor != NS_40PERCENT_FOREGROUND_COLOR,
4211
0
               "backColor cannot be NS_40PERCENT_FOREGROUND_COLOR");
4212
0
4213
0
  if (mResolveColors) {
4214
0
    foreColor = GetResolvedForeColor(foreColor, GetTextColor(), backColor);
4215
0
4216
0
    if (NS_GET_A(backColor) > 0)
4217
0
      EnsureSufficientContrast(&foreColor, &backColor);
4218
0
  }
4219
0
4220
0
  nscolor lineColor;
4221
0
  float relativeSize;
4222
0
  uint8_t lineStyle;
4223
0
  GetSelectionUnderline(mPresContext, aIndex,
4224
0
                        &lineColor, &relativeSize, &lineStyle);
4225
0
4226
0
  if (mResolveColors)
4227
0
    lineColor = GetResolvedForeColor(lineColor, foreColor, backColor);
4228
0
4229
0
  selectionStyle->mTextColor       = foreColor;
4230
0
  selectionStyle->mBGColor         = backColor;
4231
0
  selectionStyle->mUnderlineColor  = lineColor;
4232
0
  selectionStyle->mUnderlineStyle  = lineStyle;
4233
0
  selectionStyle->mUnderlineRelativeSize = relativeSize;
4234
0
  selectionStyle->mInit            = true;
4235
0
}
4236
4237
/* static */ bool
4238
nsTextPaintStyle::GetSelectionUnderline(nsPresContext* aPresContext,
4239
                                        int32_t aIndex,
4240
                                        nscolor* aLineColor,
4241
                                        float* aRelativeSize,
4242
                                        uint8_t* aStyle)
4243
0
{
4244
0
  NS_ASSERTION(aPresContext, "aPresContext is null");
4245
0
  NS_ASSERTION(aRelativeSize, "aRelativeSize is null");
4246
0
  NS_ASSERTION(aStyle, "aStyle is null");
4247
0
  NS_ASSERTION(aIndex >= 0 && aIndex < 5, "Index out of range");
4248
0
4249
0
  StyleIDs& styleID = SelectionStyleIDs[aIndex];
4250
0
4251
0
  nscolor color = LookAndFeel::GetColor(styleID.mLine);
4252
0
  int32_t style = LookAndFeel::GetInt(styleID.mLineStyle);
4253
0
  if (style > NS_STYLE_TEXT_DECORATION_STYLE_MAX) {
4254
0
    NS_ERROR("Invalid underline style value is specified");
4255
0
    style = NS_STYLE_TEXT_DECORATION_STYLE_SOLID;
4256
0
  }
4257
0
  float size = LookAndFeel::GetFloat(styleID.mLineRelativeSize);
4258
0
4259
0
  NS_ASSERTION(size, "selection underline relative size must be larger than 0");
4260
0
4261
0
  if (aLineColor) {
4262
0
    *aLineColor = color;
4263
0
  }
4264
0
  *aRelativeSize = size;
4265
0
  *aStyle = style;
4266
0
4267
0
  return style != NS_STYLE_TEXT_DECORATION_STYLE_NONE &&
4268
0
         color != NS_TRANSPARENT &&
4269
0
         size > 0.0f;
4270
0
}
4271
4272
bool
4273
nsTextPaintStyle::GetSelectionShadow(nsCSSShadowArray** aShadow)
4274
0
{
4275
0
  if (!InitSelectionColorsAndShadow()) {
4276
0
    return false;
4277
0
  }
4278
0
4279
0
  if (mHasSelectionShadow) {
4280
0
    *aShadow = mSelectionShadow;
4281
0
    return true;
4282
0
  }
4283
0
4284
0
  return false;
4285
0
}
4286
4287
inline nscolor Get40PercentColor(nscolor aForeColor, nscolor aBackColor)
4288
0
{
4289
0
  nscolor foreColor = NS_RGBA(NS_GET_R(aForeColor),
4290
0
                              NS_GET_G(aForeColor),
4291
0
                              NS_GET_B(aForeColor),
4292
0
                              (uint8_t)(255 * 0.4f));
4293
0
  // Don't use true alpha color for readability.
4294
0
  return NS_ComposeColors(aBackColor, foreColor);
4295
0
}
4296
4297
nscolor
4298
nsTextPaintStyle::GetResolvedForeColor(nscolor aColor,
4299
                                       nscolor aDefaultForeColor,
4300
                                       nscolor aBackColor)
4301
0
{
4302
0
  if (aColor == NS_SAME_AS_FOREGROUND_COLOR)
4303
0
    return aDefaultForeColor;
4304
0
4305
0
  if (aColor != NS_40PERCENT_FOREGROUND_COLOR)
4306
0
    return aColor;
4307
0
4308
0
  // Get actual background color
4309
0
  nscolor actualBGColor = aBackColor;
4310
0
  if (actualBGColor == NS_TRANSPARENT) {
4311
0
    InitCommonColors();
4312
0
    actualBGColor = mFrameBackgroundColor;
4313
0
  }
4314
0
  return Get40PercentColor(aDefaultForeColor, actualBGColor);
4315
0
}
4316
4317
//-----------------------------------------------------------------------------
4318
4319
#ifdef ACCESSIBILITY
4320
a11y::AccType
4321
nsTextFrame::AccessibleType()
4322
0
{
4323
0
  if (IsEmpty()) {
4324
0
    RenderedText text = GetRenderedText(0,
4325
0
        UINT32_MAX, TextOffsetType::OFFSETS_IN_CONTENT_TEXT,
4326
0
        TrailingWhitespace::DONT_TRIM_TRAILING_WHITESPACE);
4327
0
    if (text.mString.IsEmpty()) {
4328
0
      return a11y::eNoType;
4329
0
    }
4330
0
  }
4331
0
4332
0
  return a11y::eTextLeafType;
4333
0
}
4334
#endif
4335
4336
4337
//-----------------------------------------------------------------------------
4338
void
4339
nsTextFrame::Init(nsIContent*       aContent,
4340
                  nsContainerFrame* aParent,
4341
                  nsIFrame*         aPrevInFlow)
4342
0
{
4343
0
  NS_ASSERTION(!aPrevInFlow, "Can't be a continuation!");
4344
0
  MOZ_ASSERT(aContent->IsText(), "Bogus content!");
4345
0
4346
0
  // Remove any NewlineOffsetProperty or InFlowContentLengthProperty since they
4347
0
  // might be invalid if the content was modified while there was no frame
4348
0
  if (aContent->HasFlag(NS_HAS_NEWLINE_PROPERTY)) {
4349
0
    aContent->DeleteProperty(nsGkAtoms::newline);
4350
0
    aContent->UnsetFlags(NS_HAS_NEWLINE_PROPERTY);
4351
0
  }
4352
0
  if (aContent->HasFlag(NS_HAS_FLOWLENGTH_PROPERTY)) {
4353
0
    aContent->DeleteProperty(nsGkAtoms::flowlength);
4354
0
    aContent->UnsetFlags(NS_HAS_FLOWLENGTH_PROPERTY);
4355
0
  }
4356
0
4357
0
  // Since our content has a frame now, this flag is no longer needed.
4358
0
  aContent->UnsetFlags(NS_CREATE_FRAME_IF_NON_WHITESPACE);
4359
0
4360
0
  // We're not a continuing frame.
4361
0
  // mContentOffset = 0; not necessary since we get zeroed out at init
4362
0
  nsFrame::Init(aContent, aParent, aPrevInFlow);
4363
0
}
4364
4365
void
4366
nsTextFrame::ClearFrameOffsetCache()
4367
0
{
4368
0
  // See if we need to remove ourselves from the offset cache
4369
0
  if (GetStateBits() & TEXT_IN_OFFSET_CACHE) {
4370
0
    nsIFrame* primaryFrame = mContent->GetPrimaryFrame();
4371
0
    if (primaryFrame) {
4372
0
      // The primary frame might be null here.  For example, nsLineBox::DeleteLineList
4373
0
      // just destroys the frames in order, which means that the primary frame is already
4374
0
      // dead if we're a continuing text frame, in which case, all of its properties are
4375
0
      // gone, and we don't need to worry about deleting this property here.
4376
0
      primaryFrame->DeleteProperty(OffsetToFrameProperty());
4377
0
    }
4378
0
    RemoveStateBits(TEXT_IN_OFFSET_CACHE);
4379
0
  }
4380
0
}
4381
4382
void
4383
nsTextFrame::DestroyFrom(nsIFrame* aDestructRoot, PostDestroyData& aPostDestroyData)
4384
0
{
4385
0
  ClearFrameOffsetCache();
4386
0
4387
0
  // We might want to clear NS_CREATE_FRAME_IF_NON_WHITESPACE or
4388
0
  // NS_REFRAME_IF_WHITESPACE on mContent here, since our parent frame
4389
0
  // type might be changing.  Not clear whether it's worth it.
4390
0
  ClearTextRuns();
4391
0
  if (mNextContinuation) {
4392
0
    mNextContinuation->SetPrevInFlow(nullptr);
4393
0
  }
4394
0
  // Let the base class destroy the frame
4395
0
  nsFrame::DestroyFrom(aDestructRoot, aPostDestroyData);
4396
0
}
4397
4398
class nsContinuingTextFrame final : public nsTextFrame
4399
{
4400
public:
4401
  NS_DECL_FRAMEARENA_HELPERS(nsContinuingTextFrame)
4402
4403
  friend nsIFrame* NS_NewContinuingTextFrame(nsIPresShell* aPresShell, ComputedStyle* aStyle);
4404
4405
  void Init(nsIContent* aContent,
4406
            nsContainerFrame* aParent,
4407
            nsIFrame* aPrevInFlow) final;
4408
4409
  void DestroyFrom(nsIFrame* aDestructRoot, PostDestroyData& aPostDestroyData) final;
4410
4411
  nsTextFrame* GetPrevContinuation() const final
4412
0
  {
4413
0
    return mPrevContinuation;
4414
0
  }
4415
  void SetPrevContinuation(nsIFrame* aPrevContinuation) final
4416
0
  {
4417
0
    NS_ASSERTION(!aPrevContinuation || Type() == aPrevContinuation->Type(),
4418
0
                 "setting a prev continuation with incorrect type!");
4419
0
    NS_ASSERTION (!nsSplittableFrame::IsInPrevContinuationChain(aPrevContinuation, this),
4420
0
                  "creating a loop in continuation chain!");
4421
0
    mPrevContinuation = static_cast<nsTextFrame*>(aPrevContinuation);
4422
0
    RemoveStateBits(NS_FRAME_IS_FLUID_CONTINUATION);
4423
0
  }
4424
0
  nsIFrame* GetPrevInFlowVirtual() const final { return GetPrevInFlow(); }
4425
  nsTextFrame* GetPrevInFlow() const
4426
0
  {
4427
0
    return (GetStateBits() & NS_FRAME_IS_FLUID_CONTINUATION) ? mPrevContinuation : nullptr;
4428
0
  }
4429
  void SetPrevInFlow(nsIFrame* aPrevInFlow) final
4430
0
  {
4431
0
    NS_ASSERTION(!aPrevInFlow || Type() == aPrevInFlow->Type(),
4432
0
                 "setting a prev in flow with incorrect type!");
4433
0
    NS_ASSERTION (!nsSplittableFrame::IsInPrevContinuationChain(aPrevInFlow, this),
4434
0
                  "creating a loop in continuation chain!");
4435
0
    mPrevContinuation = static_cast<nsTextFrame*>(aPrevInFlow);
4436
0
    AddStateBits(NS_FRAME_IS_FLUID_CONTINUATION);
4437
0
  }
4438
  nsIFrame* FirstInFlow() const final;
4439
  nsIFrame* FirstContinuation() const final;
4440
4441
  void AddInlineMinISize(gfxContext* aRenderingContext,
4442
                         InlineMinISizeData* aData) final;
4443
  void AddInlinePrefISize(gfxContext* aRenderingContext,
4444
                          InlinePrefISizeData* aData) final;
4445
4446
protected:
4447
  explicit nsContinuingTextFrame(ComputedStyle* aStyle)
4448
    : nsTextFrame(aStyle, kClassID)
4449
0
  {}
4450
4451
  nsTextFrame* mPrevContinuation;
4452
};
4453
4454
void
4455
nsContinuingTextFrame::Init(nsIContent*       aContent,
4456
                            nsContainerFrame* aParent,
4457
                            nsIFrame*         aPrevInFlow)
4458
0
{
4459
0
  NS_ASSERTION(aPrevInFlow, "Must be a continuation!");
4460
0
  // NOTE: bypassing nsTextFrame::Init!!!
4461
0
  nsFrame::Init(aContent, aParent, aPrevInFlow);
4462
0
4463
0
  nsTextFrame* prev = static_cast<nsTextFrame*>(aPrevInFlow);
4464
0
  nsTextFrame* nextContinuation = prev->GetNextContinuation();
4465
0
  // Hook the frame into the flow
4466
0
  SetPrevInFlow(aPrevInFlow);
4467
0
  aPrevInFlow->SetNextInFlow(this);
4468
0
  mContentOffset = prev->GetContentOffset() + prev->GetContentLengthHint();
4469
0
  NS_ASSERTION(mContentOffset < int32_t(aContent->GetText()->GetLength()),
4470
0
               "Creating ContinuingTextFrame, but there is no more content");
4471
0
  if (prev->Style() != Style()) {
4472
0
    // We're taking part of prev's text, and its style may be different
4473
0
    // so clear its textrun which may no longer be valid (and don't set ours)
4474
0
    prev->ClearTextRuns();
4475
0
  } else {
4476
0
    float inflation = prev->GetFontSizeInflation();
4477
0
    SetFontSizeInflation(inflation);
4478
0
    mTextRun = prev->GetTextRun(nsTextFrame::eInflated);
4479
0
    if (inflation != 1.0f) {
4480
0
      gfxTextRun *uninflatedTextRun =
4481
0
        prev->GetTextRun(nsTextFrame::eNotInflated);
4482
0
      if (uninflatedTextRun) {
4483
0
        SetTextRun(uninflatedTextRun, nsTextFrame::eNotInflated, 1.0f);
4484
0
      }
4485
0
    }
4486
0
  }
4487
0
  if (aPrevInFlow->GetStateBits() & NS_FRAME_IS_BIDI) {
4488
0
    FrameBidiData bidiData = aPrevInFlow->GetBidiData();
4489
0
    bidiData.precedingControl = kBidiLevelNone;
4490
0
    SetProperty(BidiDataProperty(), bidiData);
4491
0
4492
0
    if (nextContinuation) {
4493
0
      SetNextContinuation(nextContinuation);
4494
0
      nextContinuation->SetPrevContinuation(this);
4495
0
      // Adjust next-continuations' content offset as needed.
4496
0
      while (nextContinuation &&
4497
0
             nextContinuation->GetContentOffset() < mContentOffset) {
4498
#ifdef DEBUG
4499
        FrameBidiData nextBidiData = nextContinuation->GetBidiData();
4500
        NS_ASSERTION(bidiData.embeddingLevel == nextBidiData.embeddingLevel &&
4501
                     bidiData.baseLevel == nextBidiData.baseLevel,
4502
                     "stealing text from different type of BIDI continuation");
4503
        MOZ_ASSERT(nextBidiData.precedingControl == kBidiLevelNone,
4504
                   "There shouldn't be any virtual bidi formatting character "
4505
                   "between continuations");
4506
#endif
4507
        nextContinuation->mContentOffset = mContentOffset;
4508
0
        nextContinuation = nextContinuation->GetNextContinuation();
4509
0
      }
4510
0
    }
4511
0
    AddStateBits(NS_FRAME_IS_BIDI);
4512
0
  } // prev frame is bidi
4513
0
}
4514
4515
void
4516
nsContinuingTextFrame::DestroyFrom(nsIFrame* aDestructRoot, PostDestroyData& aPostDestroyData)
4517
0
{
4518
0
  ClearFrameOffsetCache();
4519
0
4520
0
  // The text associated with this frame will become associated with our
4521
0
  // prev-continuation. If that means the text has changed style, then
4522
0
  // we need to wipe out the text run for the text.
4523
0
  // Note that mPrevContinuation can be null if we're destroying the whole
4524
0
  // frame chain from the start to the end.
4525
0
  // If this frame is mentioned in the userData for a textrun (say
4526
0
  // because there's a direction change at the start of this frame), then
4527
0
  // we have to clear the textrun because we're going away and the
4528
0
  // textrun had better not keep a dangling reference to us.
4529
0
  if (IsInTextRunUserData() ||
4530
0
      (mPrevContinuation &&
4531
0
       mPrevContinuation->Style() != Style())) {
4532
0
    ClearTextRuns();
4533
0
    // Clear the previous continuation's text run also, so that it can rebuild
4534
0
    // the text run to include our text.
4535
0
    if (mPrevContinuation) {
4536
0
      mPrevContinuation->ClearTextRuns();
4537
0
    }
4538
0
  }
4539
0
  nsSplittableFrame::RemoveFromFlow(this);
4540
0
  // Let the base class destroy the frame
4541
0
  nsFrame::DestroyFrom(aDestructRoot, aPostDestroyData);
4542
0
}
4543
4544
nsIFrame*
4545
nsContinuingTextFrame::FirstInFlow() const
4546
0
{
4547
0
  // Can't cast to |nsContinuingTextFrame*| because the first one isn't.
4548
0
  nsIFrame *firstInFlow,
4549
0
           *previous = const_cast<nsIFrame*>
4550
0
                                 (static_cast<const nsIFrame*>(this));
4551
0
  do {
4552
0
    firstInFlow = previous;
4553
0
    previous = firstInFlow->GetPrevInFlow();
4554
0
  } while (previous);
4555
0
  MOZ_ASSERT(firstInFlow, "post-condition failed");
4556
0
  return firstInFlow;
4557
0
}
4558
4559
nsIFrame*
4560
nsContinuingTextFrame::FirstContinuation() const
4561
0
{
4562
0
  // Can't cast to |nsContinuingTextFrame*| because the first one isn't.
4563
0
  nsIFrame *firstContinuation,
4564
0
  *previous = const_cast<nsIFrame*>
4565
0
                        (static_cast<const nsIFrame*>(mPrevContinuation));
4566
0
4567
0
  NS_ASSERTION(previous, "How can an nsContinuingTextFrame be the first continuation?");
4568
0
4569
0
  do {
4570
0
    firstContinuation = previous;
4571
0
    previous = firstContinuation->GetPrevContinuation();
4572
0
  } while (previous);
4573
0
  MOZ_ASSERT(firstContinuation, "post-condition failed");
4574
0
  return firstContinuation;
4575
0
}
4576
4577
// XXX Do we want to do all the work for the first-in-flow or do the
4578
// work for each part?  (Be careful of first-letter / first-line, though,
4579
// especially first-line!)  Doing all the work on the first-in-flow has
4580
// the advantage of avoiding the potential for incremental reflow bugs,
4581
// but depends on our maintining the frame tree in reasonable ways even
4582
// for edge cases (block-within-inline splits, nextBidi, etc.)
4583
4584
// XXX We really need to make :first-letter happen during frame
4585
// construction.
4586
4587
// Needed for text frames in XUL.
4588
/* virtual */ nscoord
4589
nsTextFrame::GetMinISize(gfxContext *aRenderingContext)
4590
0
{
4591
0
  return nsLayoutUtils::MinISizeFromInline(this, aRenderingContext);
4592
0
}
4593
4594
// Needed for text frames in XUL.
4595
/* virtual */ nscoord
4596
nsTextFrame::GetPrefISize(gfxContext *aRenderingContext)
4597
0
{
4598
0
  return nsLayoutUtils::PrefISizeFromInline(this, aRenderingContext);
4599
0
}
4600
4601
/* virtual */ void
4602
nsContinuingTextFrame::AddInlineMinISize(gfxContext *aRenderingContext,
4603
                                         InlineMinISizeData *aData)
4604
0
{
4605
0
  // Do nothing, since the first-in-flow accounts for everything.
4606
0
}
4607
4608
/* virtual */ void
4609
nsContinuingTextFrame::AddInlinePrefISize(gfxContext *aRenderingContext,
4610
                                          InlinePrefISizeData *aData)
4611
0
{
4612
0
  // Do nothing, since the first-in-flow accounts for everything.
4613
0
}
4614
4615
//----------------------------------------------------------------------
4616
4617
#if defined(DEBUG_rbs) || defined(DEBUG_bzbarsky)
4618
static void
4619
VerifyNotDirty(nsFrameState state)
4620
{
4621
  bool isZero = state & NS_FRAME_FIRST_REFLOW;
4622
  bool isDirty = state & NS_FRAME_IS_DIRTY;
4623
  if (!isZero && isDirty)
4624
    NS_WARNING("internal offsets may be out-of-sync");
4625
}
4626
#define DEBUG_VERIFY_NOT_DIRTY(state) \
4627
VerifyNotDirty(state)
4628
#else
4629
#define DEBUG_VERIFY_NOT_DIRTY(state)
4630
#endif
4631
4632
nsIFrame*
4633
NS_NewTextFrame(nsIPresShell* aPresShell, ComputedStyle* aStyle)
4634
0
{
4635
0
  return new (aPresShell) nsTextFrame(aStyle);
4636
0
}
4637
4638
NS_IMPL_FRAMEARENA_HELPERS(nsTextFrame)
4639
4640
nsIFrame*
4641
NS_NewContinuingTextFrame(nsIPresShell* aPresShell, ComputedStyle* aStyle)
4642
0
{
4643
0
  return new (aPresShell) nsContinuingTextFrame(aStyle);
4644
0
}
4645
4646
NS_IMPL_FRAMEARENA_HELPERS(nsContinuingTextFrame)
4647
4648
nsTextFrame::~nsTextFrame()
4649
0
{
4650
0
}
4651
4652
nsresult
4653
nsTextFrame::GetCursor(const nsPoint& aPoint,
4654
                       nsIFrame::Cursor& aCursor)
4655
0
{
4656
0
  FillCursorInformationFromStyle(StyleUI(), aCursor);
4657
0
  if (NS_STYLE_CURSOR_AUTO == aCursor.mCursor) {
4658
0
    if (!IsSelectable(nullptr)) {
4659
0
      aCursor.mCursor = NS_STYLE_CURSOR_DEFAULT;
4660
0
    } else {
4661
0
      aCursor.mCursor = GetWritingMode().IsVertical()
4662
0
        ? NS_STYLE_CURSOR_VERTICAL_TEXT : NS_STYLE_CURSOR_TEXT;
4663
0
    }
4664
0
    return NS_OK;
4665
0
  } else {
4666
0
    return nsFrame::GetCursor(aPoint, aCursor);
4667
0
  }
4668
0
}
4669
4670
nsTextFrame*
4671
nsTextFrame::LastInFlow() const
4672
0
{
4673
0
  nsTextFrame* lastInFlow = const_cast<nsTextFrame*>(this);
4674
0
  while (lastInFlow->GetNextInFlow())  {
4675
0
    lastInFlow = lastInFlow->GetNextInFlow();
4676
0
  }
4677
0
  MOZ_ASSERT(lastInFlow, "post-condition failed");
4678
0
  return lastInFlow;
4679
0
}
4680
4681
nsTextFrame*
4682
nsTextFrame::LastContinuation() const
4683
0
{
4684
0
  nsTextFrame* lastContinuation = const_cast<nsTextFrame*>(this);
4685
0
  while (lastContinuation->mNextContinuation)  {
4686
0
    lastContinuation = lastContinuation->mNextContinuation;
4687
0
  }
4688
0
  MOZ_ASSERT(lastContinuation, "post-condition failed");
4689
0
  return lastContinuation;
4690
0
}
4691
4692
void
4693
nsTextFrame::InvalidateFrame(uint32_t aDisplayItemKey, bool aRebuildDisplayItems)
4694
0
{
4695
0
  if (nsSVGUtils::IsInSVGTextSubtree(this)) {
4696
0
    nsIFrame* svgTextFrame = nsLayoutUtils::GetClosestFrameOfType(
4697
0
      GetParent(), LayoutFrameType::SVGText);
4698
0
    svgTextFrame->InvalidateFrame();
4699
0
    return;
4700
0
  }
4701
0
  nsFrame::InvalidateFrame(aDisplayItemKey, aRebuildDisplayItems);
4702
0
}
4703
4704
void
4705
nsTextFrame::InvalidateFrameWithRect(const nsRect& aRect, uint32_t aDisplayItemKey, bool aRebuildDisplayItems)
4706
0
{
4707
0
  if (nsSVGUtils::IsInSVGTextSubtree(this)) {
4708
0
    nsIFrame* svgTextFrame = nsLayoutUtils::GetClosestFrameOfType(
4709
0
      GetParent(), LayoutFrameType::SVGText);
4710
0
    svgTextFrame->InvalidateFrame();
4711
0
    return;
4712
0
  }
4713
0
  nsFrame::InvalidateFrameWithRect(aRect, aDisplayItemKey, aRebuildDisplayItems);
4714
0
}
4715
4716
gfxTextRun*
4717
nsTextFrame::GetUninflatedTextRun()
4718
0
{
4719
0
  return GetProperty(UninflatedTextRunProperty());
4720
0
}
4721
4722
void
4723
nsTextFrame::SetTextRun(gfxTextRun* aTextRun, TextRunType aWhichTextRun,
4724
                        float aInflation)
4725
0
{
4726
0
  NS_ASSERTION(aTextRun, "must have text run");
4727
0
4728
0
  // Our inflated text run is always stored in mTextRun.  In the cases
4729
0
  // where our current inflation is not 1.0, however, we store two text
4730
0
  // runs, and the uninflated one goes in a frame property.  We never
4731
0
  // store a single text run in both.
4732
0
  if (aWhichTextRun == eInflated) {
4733
0
    if (HasFontSizeInflation() && aInflation == 1.0f) {
4734
0
      // FIXME: Probably shouldn't do this within each SetTextRun
4735
0
      // method, but it doesn't hurt.
4736
0
      ClearTextRun(nullptr, nsTextFrame::eNotInflated);
4737
0
    }
4738
0
    SetFontSizeInflation(aInflation);
4739
0
  } else {
4740
0
    MOZ_ASSERT(aInflation == 1.0f, "unexpected inflation");
4741
0
    if (HasFontSizeInflation()) {
4742
0
      // Setting the property will not automatically increment the textrun's
4743
0
      // reference count, so we need to do it here.
4744
0
      aTextRun->AddRef();
4745
0
      SetProperty(UninflatedTextRunProperty(), aTextRun);
4746
0
      return;
4747
0
    }
4748
0
    // fall through to setting mTextRun
4749
0
  }
4750
0
4751
0
  mTextRun = aTextRun;
4752
0
4753
0
  // FIXME: Add assertions testing the relationship between
4754
0
  // GetFontSizeInflation() and whether we have an uninflated text run
4755
0
  // (but be aware that text runs can go away).
4756
0
}
4757
4758
bool
4759
nsTextFrame::RemoveTextRun(gfxTextRun* aTextRun)
4760
0
{
4761
0
  if (aTextRun == mTextRun) {
4762
0
    mTextRun = nullptr;
4763
0
    return true;
4764
0
  }
4765
0
  if ((GetStateBits() & TEXT_HAS_FONT_INFLATION) &&
4766
0
      GetProperty(UninflatedTextRunProperty()) == aTextRun) {
4767
0
    DeleteProperty(UninflatedTextRunProperty());
4768
0
    return true;
4769
0
  }
4770
0
  return false;
4771
0
}
4772
4773
void
4774
nsTextFrame::ClearTextRun(nsTextFrame* aStartContinuation,
4775
                          TextRunType aWhichTextRun)
4776
0
{
4777
0
  RefPtr<gfxTextRun> textRun = GetTextRun(aWhichTextRun);
4778
0
  if (!textRun) {
4779
0
    return;
4780
0
  }
4781
0
4782
0
  DebugOnly<bool> checkmTextrun = textRun == mTextRun;
4783
0
  UnhookTextRunFromFrames(textRun, aStartContinuation);
4784
0
  MOZ_ASSERT(checkmTextrun ? !mTextRun
4785
0
                           : !GetProperty(UninflatedTextRunProperty()));
4786
0
}
4787
4788
void
4789
nsTextFrame::DisconnectTextRuns()
4790
0
{
4791
0
  MOZ_ASSERT(!IsInTextRunUserData(),
4792
0
             "Textrun mentions this frame in its user data so we can't just disconnect");
4793
0
  mTextRun = nullptr;
4794
0
  if ((GetStateBits() & TEXT_HAS_FONT_INFLATION)) {
4795
0
    DeleteProperty(UninflatedTextRunProperty());
4796
0
  }
4797
0
}
4798
4799
nsresult
4800
nsTextFrame::CharacterDataChanged(const CharacterDataChangeInfo& aInfo)
4801
0
{
4802
0
  if (mContent->HasFlag(NS_HAS_NEWLINE_PROPERTY)) {
4803
0
    mContent->DeleteProperty(nsGkAtoms::newline);
4804
0
    mContent->UnsetFlags(NS_HAS_NEWLINE_PROPERTY);
4805
0
  }
4806
0
  if (mContent->HasFlag(NS_HAS_FLOWLENGTH_PROPERTY)) {
4807
0
    mContent->DeleteProperty(nsGkAtoms::flowlength);
4808
0
    mContent->UnsetFlags(NS_HAS_FLOWLENGTH_PROPERTY);
4809
0
  }
4810
0
4811
0
  // Find the first frame whose text has changed. Frames that are entirely
4812
0
  // before the text change are completely unaffected.
4813
0
  nsTextFrame* next;
4814
0
  nsTextFrame* textFrame = this;
4815
0
  while (true) {
4816
0
    next = textFrame->GetNextContinuation();
4817
0
    if (!next || next->GetContentOffset() > int32_t(aInfo.mChangeStart))
4818
0
      break;
4819
0
    textFrame = next;
4820
0
  }
4821
0
4822
0
  int32_t endOfChangedText = aInfo.mChangeStart + aInfo.mReplaceLength;
4823
0
4824
0
  // Parent of the last frame that we passed to FrameNeedsReflow (or noticed
4825
0
  // had already received an earlier FrameNeedsReflow call).
4826
0
  // (For subsequent frames with this same parent, we can just set their
4827
0
  // dirty bit without bothering to call FrameNeedsReflow again.)
4828
0
  nsIFrame* lastDirtiedFrameParent = nullptr;
4829
0
4830
0
  nsIPresShell* shell = PresContext()->GetPresShell();
4831
0
  do {
4832
0
    // textFrame contained deleted text (or the insertion point,
4833
0
    // if this was a pure insertion).
4834
0
    textFrame->RemoveStateBits(TEXT_WHITESPACE_FLAGS);
4835
0
    textFrame->ClearTextRuns();
4836
0
4837
0
    nsIFrame* parentOfTextFrame = textFrame->GetParent();
4838
0
    bool areAncestorsAwareOfReflowRequest = false;
4839
0
    if (lastDirtiedFrameParent == parentOfTextFrame) {
4840
0
      // An earlier iteration of this loop already called
4841
0
      // FrameNeedsReflow for a sibling of |textFrame|.
4842
0
      areAncestorsAwareOfReflowRequest = true;
4843
0
    } else {
4844
0
      lastDirtiedFrameParent = parentOfTextFrame;
4845
0
    }
4846
0
4847
0
    if (textFrame->mReflowRequestedForCharDataChange) {
4848
0
      // We already requested a reflow for this frame; nothing to do.
4849
0
      MOZ_ASSERT(textFrame->HasAnyStateBits(NS_FRAME_IS_DIRTY),
4850
0
                 "mReflowRequestedForCharDataChange should only be set "
4851
0
                 "on dirty frames");
4852
0
    } else {
4853
0
      // Make sure textFrame is queued up for a reflow.  Also set a flag so we
4854
0
      // don't waste time doing this again in repeated calls to this method.
4855
0
      textFrame->mReflowRequestedForCharDataChange = true;
4856
0
      if (!areAncestorsAwareOfReflowRequest) {
4857
0
        // Ask the parent frame to reflow me.
4858
0
        shell->FrameNeedsReflow(textFrame, nsIPresShell::eStyleChange,
4859
0
                                NS_FRAME_IS_DIRTY);
4860
0
      } else {
4861
0
        // We already called FrameNeedsReflow on behalf of an earlier sibling,
4862
0
        // so we can just mark this frame as dirty and don't need to bother
4863
0
        // telling its ancestors.
4864
0
        // Note: if the parent is a block, we're cheating here because we should
4865
0
        // be marking our line dirty, but we're not. nsTextFrame::SetLength will
4866
0
        // do that when it gets called during reflow.
4867
0
        textFrame->AddStateBits(NS_FRAME_IS_DIRTY);
4868
0
      }
4869
0
    }
4870
0
    textFrame->InvalidateFrame();
4871
0
4872
0
    // Below, frames that start after the deleted text will be adjusted so that
4873
0
    // their offsets move with the trailing unchanged text. If this change
4874
0
    // deletes more text than it inserts, those frame offsets will decrease.
4875
0
    // We need to maintain the invariant that mContentOffset is non-decreasing
4876
0
    // along the continuation chain. So we need to ensure that frames that
4877
0
    // started in the deleted text are all still starting before the
4878
0
    // unchanged text.
4879
0
    if (textFrame->mContentOffset > endOfChangedText) {
4880
0
      textFrame->mContentOffset = endOfChangedText;
4881
0
    }
4882
0
4883
0
    textFrame = textFrame->GetNextContinuation();
4884
0
  } while (textFrame && textFrame->GetContentOffset() < int32_t(aInfo.mChangeEnd));
4885
0
4886
0
  // This is how much the length of the string changed by --- i.e.,
4887
0
  // how much the trailing unchanged text moved.
4888
0
  int32_t sizeChange =
4889
0
    aInfo.mChangeStart + aInfo.mReplaceLength - aInfo.mChangeEnd;
4890
0
4891
0
  if (sizeChange) {
4892
0
    // Fix the offsets of the text frames that start in the trailing
4893
0
    // unchanged text.
4894
0
    while (textFrame) {
4895
0
      textFrame->mContentOffset += sizeChange;
4896
0
      // XXX we could rescue some text runs by adjusting their user data
4897
0
      // to reflect the change in DOM offsets
4898
0
      textFrame->ClearTextRuns();
4899
0
      textFrame = textFrame->GetNextContinuation();
4900
0
    }
4901
0
  }
4902
0
4903
0
  return NS_OK;
4904
0
}
4905
4906
class nsDisplayText final : public nsCharClipDisplayItem
4907
{
4908
public:
4909
  nsDisplayText(nsDisplayListBuilder* aBuilder, nsTextFrame* aFrame,
4910
                const Maybe<bool>& aIsSelected);
4911
#ifdef NS_BUILD_REFCNT_LOGGING
4912
  virtual ~nsDisplayText() {
4913
    MOZ_COUNT_DTOR(nsDisplayText);
4914
  }
4915
#endif
4916
4917
  void RestoreState() final
4918
0
  {
4919
0
    nsCharClipDisplayItem::RestoreState();
4920
0
    mOpacity = 1.0f;
4921
0
  }
4922
4923
  nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const final
4924
0
  {
4925
0
    *aSnap = false;
4926
0
    return mBounds;
4927
0
  }
4928
  void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
4929
               HitTestState* aState,
4930
0
               nsTArray<nsIFrame*> *aOutFrames) final {
4931
0
    if (nsRect(ToReferenceFrame(), mFrame->GetSize()).Intersects(aRect)) {
4932
0
      aOutFrames->AppendElement(mFrame);
4933
0
    }
4934
0
  }
4935
  bool CreateWebRenderCommands(mozilla::wr::DisplayListBuilder& aBuilder,
4936
                               mozilla::wr::IpcResourceUpdateQueue& aResources,
4937
                               const StackingContextHelper& aSc,
4938
                               WebRenderLayerManager* aManager,
4939
                               nsDisplayListBuilder* aDisplayListBuilder) final;
4940
  void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) final;
4941
  NS_DISPLAY_DECL_NAME("Text", TYPE_TEXT)
4942
4943
  nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) const final
4944
0
  {
4945
0
    if (gfxPlatform::GetPlatform()->RespectsFontStyleSmoothing()) {
4946
0
      // On OS X, web authors can turn off subpixel text rendering using the
4947
0
      // CSS property -moz-osx-font-smoothing. If they do that, we don't need
4948
0
      // to use component alpha layers for the affected text.
4949
0
      if (mFrame->StyleFont()->mFont.smoothing == NS_FONT_SMOOTHING_GRAYSCALE) {
4950
0
        return nsRect();
4951
0
      }
4952
0
    }
4953
0
    bool snap;
4954
0
    return GetBounds(aBuilder, &snap);
4955
0
  }
4956
4957
  nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) final;
4958
4959
  void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
4960
                                 const nsDisplayItemGeometry* aGeometry,
4961
                                 nsRegion *aInvalidRegion) const final;
4962
4963
  void RenderToContext(gfxContext* aCtx, nsDisplayListBuilder* aBuilder, bool aIsRecording = false);
4964
4965
  bool CanApplyOpacity() const final
4966
0
  {
4967
0
    nsTextFrame* f = static_cast<nsTextFrame*>(mFrame);
4968
0
    if (f->IsSelected()) {
4969
0
      return false;
4970
0
    }
4971
0
4972
0
    const nsStyleText* textStyle = f->StyleText();
4973
0
    if (textStyle->mTextShadow) {
4974
0
      return false;
4975
0
    }
4976
0
4977
0
    nsTextFrame::TextDecorations decorations;
4978
0
    f->GetTextDecorations(f->PresContext(), nsTextFrame::eResolvedColors, decorations);
4979
0
    if (decorations.HasDecorationLines()) {
4980
0
      return false;
4981
0
    }
4982
0
4983
0
    return true;
4984
0
  }
4985
4986
  void ApplyOpacity(nsDisplayListBuilder* aBuilder,
4987
                    float aOpacity,
4988
                    const DisplayItemClipChain* aClip) final
4989
0
  {
4990
0
    NS_ASSERTION(CanApplyOpacity(), "ApplyOpacity should be allowed");
4991
0
    mOpacity = aOpacity;
4992
0
    IntersectClip(aBuilder, aClip, false);
4993
0
  }
4994
4995
  void WriteDebugInfo(std::stringstream& aStream) final
4996
0
  {
4997
#ifdef DEBUG
4998
    aStream << " (\"";
4999
5000
    nsTextFrame* f = static_cast<nsTextFrame*>(mFrame);
5001
    nsCString buf;
5002
    int32_t totalContentLength;
5003
    f->ToCString(buf, &totalContentLength);
5004
5005
    aStream << buf.get() << "\")";
5006
#endif
5007
  }
5008
5009
  nsRect mBounds;
5010
  float mOpacity;
5011
};
5012
5013
class nsDisplayTextGeometry : public nsCharClipGeometry
5014
{
5015
public:
5016
  nsDisplayTextGeometry(nsDisplayText* aItem, nsDisplayListBuilder* aBuilder)
5017
    : nsCharClipGeometry(aItem, aBuilder)
5018
    , mOpacity(aItem->mOpacity)
5019
0
  {
5020
0
    nsTextFrame* f = static_cast<nsTextFrame*>(aItem->Frame());
5021
0
    f->GetTextDecorations(f->PresContext(), nsTextFrame::eResolvedColors, mDecorations);
5022
0
  }
5023
5024
  /**
5025
   * We store the computed text decorations here since they are
5026
   * computed using style data from parent frames. Any changes to these
5027
   * styles will only invalidate the parent frame and not this frame.
5028
   */
5029
  nsTextFrame::TextDecorations mDecorations;
5030
  float mOpacity;
5031
};
5032
5033
nsDisplayItemGeometry*
5034
nsDisplayText::AllocateGeometry(nsDisplayListBuilder* aBuilder)
5035
0
{
5036
0
  return new nsDisplayTextGeometry(this, aBuilder);
5037
0
}
5038
5039
void
5040
nsDisplayText::ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
5041
                                         const nsDisplayItemGeometry* aGeometry,
5042
                                         nsRegion *aInvalidRegion) const
5043
0
{
5044
0
  const nsDisplayTextGeometry* geometry = static_cast<const nsDisplayTextGeometry*>(aGeometry);
5045
0
  nsTextFrame* f = static_cast<nsTextFrame*>(mFrame);
5046
0
5047
0
  nsTextFrame::TextDecorations decorations;
5048
0
  f->GetTextDecorations(f->PresContext(), nsTextFrame::eResolvedColors, decorations);
5049
0
5050
0
  bool snap;
5051
0
  nsRect newRect = geometry->mBounds;
5052
0
  nsRect oldRect = GetBounds(aBuilder, &snap);
5053
0
  if (decorations != geometry->mDecorations ||
5054
0
      mVisIStartEdge != geometry->mVisIStartEdge ||
5055
0
      mVisIEndEdge != geometry->mVisIEndEdge ||
5056
0
      !oldRect.IsEqualInterior(newRect) ||
5057
0
      !geometry->mBorderRect.IsEqualInterior(GetBorderRect()) ||
5058
0
      mOpacity != geometry->mOpacity) {
5059
0
    aInvalidRegion->Or(oldRect, newRect);
5060
0
  }
5061
0
}
5062
5063
NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(TextCombineScaleFactorProperty, float)
5064
5065
static float
5066
GetTextCombineScaleFactor(nsTextFrame* aFrame)
5067
0
{
5068
0
  float factor = aFrame->GetProperty(TextCombineScaleFactorProperty());
5069
0
  return factor ? factor : 1.0f;
5070
0
}
5071
5072
nsDisplayText::nsDisplayText(nsDisplayListBuilder* aBuilder, nsTextFrame* aFrame,
5073
                             const Maybe<bool>& aIsSelected)
5074
  : nsCharClipDisplayItem(aBuilder, aFrame)
5075
  , mOpacity(1.0f)
5076
0
{
5077
0
  MOZ_COUNT_CTOR(nsDisplayText);
5078
0
  mIsFrameSelected = aIsSelected;
5079
0
  mBounds = mFrame->GetVisualOverflowRectRelativeToSelf() + ToReferenceFrame();
5080
0
    // Bug 748228
5081
0
  mBounds.Inflate(mFrame->PresContext()->AppUnitsPerDevPixel());
5082
0
}
5083
5084
void
5085
nsDisplayText::Paint(nsDisplayListBuilder* aBuilder,
5086
0
                     gfxContext* aCtx) {
5087
0
  AUTO_PROFILER_LABEL("nsDisplayText::Paint", GRAPHICS);
5088
0
5089
0
  DrawTargetAutoDisableSubpixelAntialiasing disable(aCtx->GetDrawTarget(),
5090
0
                                                    mDisableSubpixelAA);
5091
0
  RenderToContext(aCtx, aBuilder);
5092
0
}
5093
5094
bool
5095
nsDisplayText::CreateWebRenderCommands(mozilla::wr::DisplayListBuilder& aBuilder,
5096
                                       mozilla::wr::IpcResourceUpdateQueue& aResources,
5097
                                       const StackingContextHelper& aSc,
5098
                                       WebRenderLayerManager* aManager,
5099
                                       nsDisplayListBuilder* aDisplayListBuilder)
5100
0
{
5101
0
  if (mBounds.IsEmpty()) {
5102
0
    return true;
5103
0
  }
5104
0
5105
0
5106
0
  auto appUnitsPerDevPixel = Frame()->PresContext()->AppUnitsPerDevPixel();
5107
0
  gfx::Point deviceOffset = LayoutDevicePoint::FromAppUnits(
5108
0
      mBounds.TopLeft(), appUnitsPerDevPixel).ToUnknownPoint();
5109
0
5110
0
  RefPtr<TextDrawTarget> textDrawer = new TextDrawTarget(aBuilder, aResources, aSc, aManager, this, mBounds);
5111
0
  RefPtr<gfxContext> captureCtx = gfxContext::CreateOrNull(textDrawer, deviceOffset);
5112
0
5113
0
  RenderToContext(captureCtx, aDisplayListBuilder, true);
5114
0
5115
0
  return !textDrawer->HasUnsupportedFeatures();
5116
0
}
5117
5118
void
5119
nsDisplayText::RenderToContext(gfxContext* aCtx, nsDisplayListBuilder* aBuilder, bool aIsRecording)
5120
0
{
5121
0
  nsTextFrame* f = static_cast<nsTextFrame*>(mFrame);
5122
0
5123
0
  // Add 1 pixel of dirty area around mVisibleRect to allow us to paint
5124
0
  // antialiased pixels beyond the measured text extents.
5125
0
  // This is temporary until we do this in the actual calculation of text extents.
5126
0
  auto A2D = mFrame->PresContext()->AppUnitsPerDevPixel();
5127
0
  LayoutDeviceRect extraVisible =
5128
0
    LayoutDeviceRect::FromAppUnits(GetPaintRect(), A2D);
5129
0
  extraVisible.Inflate(1);
5130
0
5131
0
  gfxRect pixelVisible(extraVisible.x, extraVisible.y,
5132
0
                       extraVisible.width, extraVisible.height);
5133
0
  pixelVisible.Inflate(2);
5134
0
  pixelVisible.RoundOut();
5135
0
5136
0
  bool willClip = !aBuilder->IsForGenerateGlyphMask() &&
5137
0
                  !aBuilder->IsForPaintingSelectionBG() &&
5138
0
                  !aIsRecording;
5139
0
  if (willClip) {
5140
0
    aCtx->NewPath();
5141
0
    aCtx->Rectangle(pixelVisible);
5142
0
    aCtx->Clip();
5143
0
  }
5144
0
5145
0
  NS_ASSERTION(mVisIStartEdge >= 0, "illegal start edge");
5146
0
  NS_ASSERTION(mVisIEndEdge >= 0, "illegal end edge");
5147
0
5148
0
  gfxContextMatrixAutoSaveRestore matrixSR;
5149
0
5150
0
  nsPoint framePt = ToReferenceFrame();
5151
0
  if (f->Style()->IsTextCombined()) {
5152
0
    float scaleFactor = GetTextCombineScaleFactor(f);
5153
0
    if (scaleFactor != 1.0f) {
5154
0
      if (auto* textDrawer = aCtx->GetTextDrawer()) {
5155
0
        // WebRender doesn't support scaling text like this yet
5156
0
        textDrawer->FoundUnsupportedFeature();
5157
0
        return;
5158
0
      }
5159
0
      matrixSR.SetContext(aCtx);
5160
0
      // Setup matrix to compress text for text-combine-upright if
5161
0
      // necessary. This is done here because we want selection be
5162
0
      // compressed at the same time as text.
5163
0
      gfxPoint pt = nsLayoutUtils::PointToGfxPoint(framePt, A2D);
5164
0
      gfxMatrix mat = aCtx->CurrentMatrixDouble()
5165
0
        .PreTranslate(pt).PreScale(scaleFactor, 1.0).PreTranslate(-pt);
5166
0
      aCtx->SetMatrixDouble(mat);
5167
0
    }
5168
0
  }
5169
0
  nsTextFrame::PaintTextParams params(aCtx);
5170
0
  params.framePt = gfx::Point(framePt.x, framePt.y);
5171
0
  params.dirtyRect = extraVisible;
5172
0
5173
0
  if (aBuilder->IsForGenerateGlyphMask()) {
5174
0
    MOZ_ASSERT(!aBuilder->IsForPaintingSelectionBG());
5175
0
    params.state = nsTextFrame::PaintTextParams::GenerateTextMask;
5176
0
  } else if (aBuilder->IsForPaintingSelectionBG()) {
5177
0
    params.state = nsTextFrame::PaintTextParams::PaintTextBGColor;
5178
0
  } else {
5179
0
    params.state = nsTextFrame::PaintTextParams::PaintText;
5180
0
  }
5181
0
5182
0
  f->PaintText(params, *this, mOpacity);
5183
0
5184
0
  if (willClip) {
5185
0
    aCtx->PopClip();
5186
0
  }
5187
0
}
5188
5189
void
5190
nsTextFrame::BuildDisplayList(nsDisplayListBuilder*   aBuilder,
5191
                              const nsDisplayListSet& aLists)
5192
0
{
5193
0
  if (!IsVisibleForPainting(aBuilder))
5194
0
    return;
5195
0
5196
0
  DO_GLOBAL_REFLOW_COUNT_DSP("nsTextFrame");
5197
0
5198
0
  const nsStyleText* st = StyleText();
5199
0
  bool isTextTransparent =
5200
0
    NS_GET_A(st->mWebkitTextFillColor.CalcColor(this)) == 0 &&
5201
0
    NS_GET_A(st->mWebkitTextStrokeColor.CalcColor(this)) == 0;
5202
0
  Maybe<bool> isSelected;
5203
0
  if (((GetStateBits() & TEXT_NO_RENDERED_GLYPHS) ||
5204
0
       (isTextTransparent && !StyleText()->HasTextShadow())) &&
5205
0
      aBuilder->IsForPainting() && !nsSVGUtils::IsInSVGTextSubtree(this)) {
5206
0
    isSelected.emplace(IsSelected());
5207
0
    if (!isSelected.value()) {
5208
0
      TextDecorations textDecs;
5209
0
      GetTextDecorations(PresContext(), eResolvedColors, textDecs);
5210
0
      if (!textDecs.HasDecorationLines()) {
5211
0
        return;
5212
0
      }
5213
0
    }
5214
0
  }
5215
0
5216
0
  aLists.Content()->AppendToTop(
5217
0
    MakeDisplayItem<nsDisplayText>(aBuilder, this, isSelected));
5218
0
}
5219
5220
static nsIFrame*
5221
GetGeneratedContentOwner(nsIFrame* aFrame, bool* aIsBefore)
5222
0
{
5223
0
  *aIsBefore = false;
5224
0
  while (aFrame && (aFrame->GetStateBits() & NS_FRAME_GENERATED_CONTENT)) {
5225
0
    if (aFrame->Style()->GetPseudo() == nsCSSPseudoElements::before()) {
5226
0
      *aIsBefore = true;
5227
0
    }
5228
0
    aFrame = aFrame->GetParent();
5229
0
  }
5230
0
  return aFrame;
5231
0
}
5232
5233
UniquePtr<SelectionDetails>
5234
nsTextFrame::GetSelectionDetails()
5235
0
{
5236
0
  const nsFrameSelection* frameSelection = GetConstFrameSelection();
5237
0
  if (frameSelection->GetTableCellSelection()) {
5238
0
    return nullptr;
5239
0
  }
5240
0
  if (!(GetStateBits() & NS_FRAME_GENERATED_CONTENT)) {
5241
0
    UniquePtr<SelectionDetails> details =
5242
0
      frameSelection->LookUpSelection(mContent, GetContentOffset(),
5243
0
                                      GetContentLength(), false);
5244
0
    for (SelectionDetails* sd = details.get(); sd; sd = sd->mNext.get()) {
5245
0
      sd->mStart += mContentOffset;
5246
0
      sd->mEnd += mContentOffset;
5247
0
    }
5248
0
    return details;
5249
0
  }
5250
0
5251
0
  // Check if the beginning or end of the element is selected, depending on
5252
0
  // whether we're :before content or :after content.
5253
0
  bool isBefore;
5254
0
  nsIFrame* owner = GetGeneratedContentOwner(this, &isBefore);
5255
0
  if (!owner || !owner->GetContent())
5256
0
    return nullptr;
5257
0
5258
0
  UniquePtr<SelectionDetails> details =
5259
0
    frameSelection->LookUpSelection(owner->GetContent(),
5260
0
        isBefore ? 0 : owner->GetContent()->GetChildCount(), 0, false);
5261
0
  for (SelectionDetails* sd = details.get(); sd; sd = sd->mNext.get()) {
5262
0
    // The entire text is selected!
5263
0
    sd->mStart = GetContentOffset();
5264
0
    sd->mEnd = GetContentEnd();
5265
0
  }
5266
0
  return details;
5267
0
}
5268
5269
static void
5270
PaintSelectionBackground(DrawTarget& aDrawTarget,
5271
                         nscolor aColor,
5272
                         const LayoutDeviceRect& aDirtyRect,
5273
                         const LayoutDeviceRect& aRect,
5274
                         nsTextFrame::DrawPathCallbacks* aCallbacks)
5275
0
{
5276
0
  Rect rect = aRect.Intersect(aDirtyRect).ToUnknownRect();
5277
0
  MaybeSnapToDevicePixels(rect, aDrawTarget);
5278
0
5279
0
  if (aCallbacks) {
5280
0
    aCallbacks->NotifySelectionBackgroundNeedsFill(rect, aColor, aDrawTarget);
5281
0
  } else {
5282
0
    ColorPattern color(ToDeviceColor(aColor));
5283
0
    aDrawTarget.FillRect(rect, color);
5284
0
  }
5285
0
}
5286
5287
// Attempt to get the LineBaselineOffset property of aChildFrame
5288
// If not set, calculate this value for all child frames of aBlockFrame
5289
static nscoord
5290
LazyGetLineBaselineOffset(nsIFrame* aChildFrame, nsBlockFrame* aBlockFrame)
5291
0
{
5292
0
  bool offsetFound;
5293
0
  nscoord offset = aChildFrame->GetProperty(
5294
0
    nsIFrame::LineBaselineOffset(), &offsetFound);
5295
0
5296
0
  if (!offsetFound) {
5297
0
    for (nsBlockFrame::LineIterator line = aBlockFrame->LinesBegin(),
5298
0
                                    line_end = aBlockFrame->LinesEnd();
5299
0
         line != line_end; line++) {
5300
0
      if (line->IsInline()) {
5301
0
        int32_t n = line->GetChildCount();
5302
0
        nscoord lineBaseline = line->BStart() + line->GetLogicalAscent();
5303
0
        for (nsIFrame* lineFrame = line->mFirstChild;
5304
0
             n > 0; lineFrame = lineFrame->GetNextSibling(), --n) {
5305
0
          offset = lineBaseline - lineFrame->GetNormalPosition().y;
5306
0
          lineFrame->SetProperty(nsIFrame::LineBaselineOffset(), offset);
5307
0
        }
5308
0
      }
5309
0
    }
5310
0
    return aChildFrame->GetProperty(
5311
0
      nsIFrame::LineBaselineOffset(), &offsetFound);
5312
0
  } else {
5313
0
    return offset;
5314
0
  }
5315
0
}
5316
5317
static bool IsUnderlineRight(nsIFrame* aFrame)
5318
0
{
5319
0
  nsAtom* langAtom = aFrame->StyleFont()->mLanguage;
5320
0
  if (!langAtom) {
5321
0
    return false;
5322
0
  }
5323
0
  nsAtomString langStr(langAtom);
5324
0
  return (StringBeginsWith(langStr, NS_LITERAL_STRING("ja")) ||
5325
0
          StringBeginsWith(langStr, NS_LITERAL_STRING("ko"))) &&
5326
0
         (langStr.Length() == 2 || langStr[2] == '-');
5327
0
}
5328
5329
void
5330
nsTextFrame::GetTextDecorations(
5331
                    nsPresContext* aPresContext,
5332
                    nsTextFrame::TextDecorationColorResolution aColorResolution,
5333
                    nsTextFrame::TextDecorations& aDecorations)
5334
0
{
5335
0
  const nsCompatibility compatMode = aPresContext->CompatibilityMode();
5336
0
5337
0
  bool useOverride = false;
5338
0
  nscolor overrideColor = NS_RGBA(0, 0, 0, 0);
5339
0
5340
0
  bool nearestBlockFound = false;
5341
0
  // Use writing mode of parent frame for orthogonal text frame to work.
5342
0
  // See comment in nsTextFrame::DrawTextRunAndDecorations.
5343
0
  WritingMode wm = GetParent()->GetWritingMode();
5344
0
  bool vertical = wm.IsVertical();
5345
0
5346
0
  nscoord ascent = GetLogicalBaseline(wm);
5347
0
  // physicalBlockStartOffset represents the offset from our baseline
5348
0
  // to f's physical block start, which is top in horizontal writing
5349
0
  // mode, and left in vertical writing modes, in our coordinate space.
5350
0
  // This physical block start is logical block start in most cases,
5351
0
  // but for vertical-rl, it is logical block end, and consequently in
5352
0
  // that case, it starts from the descent instead of ascent.
5353
0
  nscoord physicalBlockStartOffset =
5354
0
    wm.IsVerticalRL() ? GetSize().width - ascent : ascent;
5355
0
  // baselineOffset represents the offset from our baseline to f's baseline or
5356
0
  // the nearest block's baseline, in our coordinate space, whichever is closest
5357
0
  // during the particular iteration
5358
0
  nscoord baselineOffset = 0;
5359
0
5360
0
  for (nsIFrame* f = this, *fChild = nullptr;
5361
0
       f;
5362
0
       fChild = f,
5363
0
       f = nsLayoutUtils::GetParentOrPlaceholderFor(f))
5364
0
  {
5365
0
    ComputedStyle *const context = f->Style();
5366
0
    if (!context->HasTextDecorationLines()) {
5367
0
      break;
5368
0
    }
5369
0
5370
0
    const nsStyleTextReset *const styleText = context->StyleTextReset();
5371
0
    const uint8_t textDecorations = styleText->mTextDecorationLine;
5372
0
5373
0
    if (!useOverride &&
5374
0
        (NS_STYLE_TEXT_DECORATION_LINE_OVERRIDE_ALL & textDecorations)) {
5375
0
      // This handles the <a href="blah.html"><font color="green">La
5376
0
      // la la</font></a> case. The link underline should be green.
5377
0
      useOverride = true;
5378
0
      overrideColor =
5379
0
        nsLayoutUtils::GetColor(f, &nsStyleTextReset::mTextDecorationColor);
5380
0
    }
5381
0
5382
0
    nsBlockFrame* fBlock = nsLayoutUtils::GetAsBlock(f);
5383
0
    const bool firstBlock = !nearestBlockFound && fBlock;
5384
0
5385
0
    // Not updating positions once we hit a parent block is equivalent to
5386
0
    // the CSS 2.1 spec that blocks should propagate decorations down to their
5387
0
    // children (albeit the style should be preserved)
5388
0
    // However, if we're vertically aligned within a block, then we need to
5389
0
    // recover the correct baseline from the line by querying the FrameProperty
5390
0
    // that should be set (see nsLineLayout::VerticalAlignLine).
5391
0
    if (firstBlock) {
5392
0
      // At this point, fChild can't be null since TextFrames can't be blocks
5393
0
      if (fChild->VerticalAlignEnum() != NS_STYLE_VERTICAL_ALIGN_BASELINE) {
5394
0
5395
0
        // Since offset is the offset in the child's coordinate space, we have
5396
0
        // to undo the accumulation to bring the transform out of the block's
5397
0
        // coordinate space
5398
0
        const nscoord lineBaselineOffset = LazyGetLineBaselineOffset(fChild,
5399
0
                                                                     fBlock);
5400
0
5401
0
        baselineOffset = physicalBlockStartOffset - lineBaselineOffset -
5402
0
          (vertical ? fChild->GetNormalPosition().x
5403
0
                    : fChild->GetNormalPosition().y);
5404
0
      }
5405
0
    }
5406
0
    else if (!nearestBlockFound) {
5407
0
      // offset here is the offset from f's baseline to f's top/left
5408
0
      // boundary. It's descent for vertical-rl, and ascent otherwise.
5409
0
      nscoord offset = wm.IsVerticalRL() ?
5410
0
        f->GetSize().width - f->GetLogicalBaseline(wm) :
5411
0
        f->GetLogicalBaseline(wm);
5412
0
      baselineOffset = physicalBlockStartOffset - offset;
5413
0
    }
5414
0
5415
0
    nearestBlockFound = nearestBlockFound || firstBlock;
5416
0
    physicalBlockStartOffset +=
5417
0
      vertical ? f->GetNormalPosition().x : f->GetNormalPosition().y;
5418
0
5419
0
    const uint8_t style = styleText->mTextDecorationStyle;
5420
0
    if (textDecorations) {
5421
0
      nscolor color;
5422
0
      if (useOverride) {
5423
0
        color = overrideColor;
5424
0
      } else if (nsSVGUtils::IsInSVGTextSubtree(this)) {
5425
0
        // XXX We might want to do something with text-decoration-color when
5426
0
        //     painting SVG text, but it's not clear what we should do.  We
5427
0
        //     at least need SVG text decorations to paint with 'fill' if
5428
0
        //     text-decoration-color has its initial value currentColor.
5429
0
        //     We could choose to interpret currentColor as "currentFill"
5430
0
        //     for SVG text, and have e.g. text-decoration-color:red to
5431
0
        //     override the fill paint of the decoration.
5432
0
        color = aColorResolution == eResolvedColors ?
5433
0
                  nsLayoutUtils::GetColor(f, &nsStyleSVG::mFill) :
5434
0
                  NS_SAME_AS_FOREGROUND_COLOR;
5435
0
      } else {
5436
0
        color = nsLayoutUtils::
5437
0
          GetColor(f, &nsStyleTextReset::mTextDecorationColor);
5438
0
      }
5439
0
5440
0
      bool swapUnderlineAndOverline = vertical && IsUnderlineRight(f);
5441
0
      const uint8_t kUnderline =
5442
0
        swapUnderlineAndOverline ? NS_STYLE_TEXT_DECORATION_LINE_OVERLINE :
5443
0
                                   NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE;
5444
0
      const uint8_t kOverline =
5445
0
        swapUnderlineAndOverline ? NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE :
5446
0
                                   NS_STYLE_TEXT_DECORATION_LINE_OVERLINE;
5447
0
5448
0
      if (textDecorations & kUnderline) {
5449
0
        aDecorations.mUnderlines.AppendElement(
5450
0
          nsTextFrame::LineDecoration(f, baselineOffset, color, style));
5451
0
      }
5452
0
      if (textDecorations & kOverline) {
5453
0
        aDecorations.mOverlines.AppendElement(
5454
0
          nsTextFrame::LineDecoration(f, baselineOffset, color, style));
5455
0
      }
5456
0
      if (textDecorations & NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH) {
5457
0
        aDecorations.mStrikes.AppendElement(
5458
0
          nsTextFrame::LineDecoration(f, baselineOffset, color, style));
5459
0
      }
5460
0
    }
5461
0
5462
0
    // In all modes, if we're on an inline-block or inline-table (or
5463
0
    // inline-stack, inline-box, inline-grid), we're done.
5464
0
    // If we're on a ruby frame other than ruby text container, we
5465
0
    // should continue.
5466
0
    mozilla::StyleDisplay display = f->GetDisplay();
5467
0
    if (display != mozilla::StyleDisplay::Inline &&
5468
0
        (!nsStyleDisplay::IsRubyDisplayType(display) ||
5469
0
         display == mozilla::StyleDisplay::RubyTextContainer) &&
5470
0
        nsStyleDisplay::IsDisplayTypeInlineOutside(display)) {
5471
0
      break;
5472
0
    }
5473
0
5474
0
    // In quirks mode, if we're on an HTML table element, we're done.
5475
0
    if (compatMode == eCompatibility_NavQuirks &&
5476
0
        f->GetContent()->IsHTMLElement(nsGkAtoms::table)) {
5477
0
      break;
5478
0
    }
5479
0
5480
0
    // If we're on an absolutely-positioned element or a floating
5481
0
    // element, we're done.
5482
0
    if (f->IsFloating() || f->IsAbsolutelyPositioned()) {
5483
0
      break;
5484
0
    }
5485
0
5486
0
    // If we're an outer <svg> element, which is classified as an atomic
5487
0
    // inline-level element, we're done.
5488
0
    if (f->IsSVGOuterSVGFrame()) {
5489
0
      break;
5490
0
    }
5491
0
  }
5492
0
}
5493
5494
static float
5495
GetInflationForTextDecorations(nsIFrame* aFrame, nscoord aInflationMinFontSize)
5496
0
{
5497
0
  if (nsSVGUtils::IsInSVGTextSubtree(aFrame)) {
5498
0
    const nsIFrame* container = aFrame;
5499
0
    while (!container->IsSVGTextFrame()) {
5500
0
      container = container->GetParent();
5501
0
    }
5502
0
    NS_ASSERTION(container, "expected to find an ancestor SVGTextFrame");
5503
0
    return
5504
0
      static_cast<const SVGTextFrame*>(container)->GetFontSizeScaleFactor();
5505
0
  }
5506
0
  return nsLayoutUtils::FontSizeInflationInner(aFrame, aInflationMinFontSize);
5507
0
}
5508
5509
struct EmphasisMarkInfo
5510
{
5511
  RefPtr<gfxTextRun> textRun;
5512
  gfxFloat advance;
5513
  gfxFloat baselineOffset;
5514
};
5515
5516
NS_DECLARE_FRAME_PROPERTY_DELETABLE(EmphasisMarkProperty, EmphasisMarkInfo)
5517
5518
static already_AddRefed<gfxTextRun>
5519
GenerateTextRunForEmphasisMarks(nsTextFrame* aFrame,
5520
                                nsFontMetrics* aFontMetrics,
5521
                                ComputedStyle* aComputedStyle,
5522
                                const nsStyleText* aStyleText)
5523
0
{
5524
0
  const nsString& emphasisString = aStyleText->mTextEmphasisStyleString;
5525
0
  RefPtr<DrawTarget> dt = CreateReferenceDrawTarget(aFrame);
5526
0
  auto appUnitsPerDevUnit = aFrame->PresContext()->AppUnitsPerDevPixel();
5527
0
  gfx::ShapedTextFlags flags = nsLayoutUtils::GetTextRunOrientFlagsForStyle(aComputedStyle);
5528
0
  if (flags == gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED) {
5529
0
    // The emphasis marks should always be rendered upright per spec.
5530
0
    flags = gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT;
5531
0
  }
5532
0
  return aFontMetrics->GetThebesFontGroup()->
5533
0
    MakeTextRun<char16_t>(emphasisString.get(), emphasisString.Length(),
5534
0
                          dt, appUnitsPerDevUnit, flags,
5535
0
                          nsTextFrameUtils::Flags(), nullptr);
5536
0
}
5537
5538
static nsRubyFrame*
5539
FindFurthestInlineRubyAncestor(nsTextFrame* aFrame)
5540
0
{
5541
0
  nsRubyFrame* rubyFrame = nullptr;
5542
0
  for (nsIFrame* frame = aFrame->GetParent();
5543
0
       frame && frame->IsFrameOfType(nsIFrame::eLineParticipant);
5544
0
       frame = frame->GetParent()) {
5545
0
    if (frame->IsRubyFrame()) {
5546
0
      rubyFrame = static_cast<nsRubyFrame*>(frame);
5547
0
    }
5548
0
  }
5549
0
  return rubyFrame;
5550
0
}
5551
5552
nsRect
5553
nsTextFrame::UpdateTextEmphasis(WritingMode aWM, PropertyProvider& aProvider)
5554
0
{
5555
0
  const nsStyleText* styleText = StyleText();
5556
0
  if (!styleText->HasTextEmphasis()) {
5557
0
    DeleteProperty(EmphasisMarkProperty());
5558
0
    return nsRect();
5559
0
  }
5560
0
5561
0
  ComputedStyle* computedStyle = Style();
5562
0
  bool isTextCombined = computedStyle->IsTextCombined();
5563
0
  if (isTextCombined) {
5564
0
    computedStyle = GetParent()->Style();
5565
0
  }
5566
0
  RefPtr<nsFontMetrics> fm =
5567
0
    nsLayoutUtils::GetFontMetricsOfEmphasisMarks(computedStyle,
5568
0
                                                 PresContext(),
5569
0
                                                 GetFontSizeInflation());
5570
0
  EmphasisMarkInfo* info = new EmphasisMarkInfo;
5571
0
  info->textRun =
5572
0
    GenerateTextRunForEmphasisMarks(this, fm, computedStyle, styleText);
5573
0
  info->advance = info->textRun->GetAdvanceWidth();
5574
0
5575
0
  // Calculate the baseline offset
5576
0
  LogicalSide side = styleText->TextEmphasisSide(aWM);
5577
0
  LogicalSize frameSize = GetLogicalSize(aWM);
5578
0
  // The overflow rect is inflated in the inline direction by half
5579
0
  // advance of the emphasis mark on each side, so that even if a mark
5580
0
  // is drawn for a zero-width character, it won't be clipped.
5581
0
  LogicalRect overflowRect(aWM, -info->advance / 2,
5582
0
                           /* BStart to be computed below */ 0,
5583
0
                           frameSize.ISize(aWM) + info->advance,
5584
0
                           fm->MaxAscent() + fm->MaxDescent());
5585
0
  RefPtr<nsFontMetrics> baseFontMetrics = isTextCombined
5586
0
    ? nsLayoutUtils::GetInflatedFontMetricsForFrame(GetParent())
5587
0
    : do_AddRef(aProvider.GetFontMetrics());
5588
0
  // When the writing mode is vertical-lr the line is inverted, and thus
5589
0
  // the ascent and descent are swapped.
5590
0
  nscoord absOffset = (side == eLogicalSideBStart) != aWM.IsLineInverted() ?
5591
0
    baseFontMetrics->MaxAscent() + fm->MaxDescent() :
5592
0
    baseFontMetrics->MaxDescent() + fm->MaxAscent();
5593
0
  RubyBlockLeadings leadings;
5594
0
  if (nsRubyFrame* ruby = FindFurthestInlineRubyAncestor(this)) {
5595
0
    leadings = ruby->GetBlockLeadings();
5596
0
  }
5597
0
  if (side == eLogicalSideBStart) {
5598
0
    info->baselineOffset = -absOffset - leadings.mStart;
5599
0
    overflowRect.BStart(aWM) = -overflowRect.BSize(aWM) - leadings.mStart;
5600
0
  } else {
5601
0
    MOZ_ASSERT(side == eLogicalSideBEnd);
5602
0
    info->baselineOffset = absOffset + leadings.mEnd;
5603
0
    overflowRect.BStart(aWM) = frameSize.BSize(aWM) + leadings.mEnd;
5604
0
  }
5605
0
  // If text combined, fix the gap between the text frame and its parent.
5606
0
  if (isTextCombined) {
5607
0
    nscoord gap = (baseFontMetrics->MaxHeight() - frameSize.BSize(aWM)) / 2;
5608
0
    overflowRect.BStart(aWM) += gap * (side == eLogicalSideBStart ? -1 : 1);
5609
0
  }
5610
0
5611
0
  SetProperty(EmphasisMarkProperty(), info);
5612
0
  return overflowRect.GetPhysicalRect(aWM, frameSize.GetPhysicalSize(aWM));
5613
0
}
5614
5615
void
5616
nsTextFrame::UnionAdditionalOverflow(nsPresContext* aPresContext,
5617
                                     nsIFrame* aBlock,
5618
                                     PropertyProvider& aProvider,
5619
                                     nsRect* aVisualOverflowRect,
5620
                                     bool aIncludeTextDecorations)
5621
0
{
5622
0
  const WritingMode wm = GetWritingMode();
5623
0
  bool verticalRun = mTextRun->IsVertical();
5624
0
  const gfxFloat appUnitsPerDevUnit = aPresContext->AppUnitsPerDevPixel();
5625
0
5626
0
  if (IsFloatingFirstLetterChild()) {
5627
0
    bool inverted = wm.IsLineInverted();
5628
0
    // The underline/overline drawable area must be contained in the overflow
5629
0
    // rect when this is in floating first letter frame at *both* modes.
5630
0
    // In this case, aBlock is the ::first-letter frame.
5631
0
    uint8_t decorationStyle = aBlock->Style()->
5632
0
                                StyleTextReset()->mTextDecorationStyle;
5633
0
    // If the style is none, let's include decoration line rect as solid style
5634
0
    // since changing the style from none to solid/dotted/dashed doesn't cause
5635
0
    // reflow.
5636
0
    if (decorationStyle == NS_STYLE_TEXT_DECORATION_STYLE_NONE) {
5637
0
      decorationStyle = NS_STYLE_TEXT_DECORATION_STYLE_SOLID;
5638
0
    }
5639
0
    nsFontMetrics* fontMetrics = aProvider.GetFontMetrics();
5640
0
    nscoord underlineOffset, underlineSize;
5641
0
    fontMetrics->GetUnderline(underlineOffset, underlineSize);
5642
0
    nscoord maxAscent = inverted ? fontMetrics->MaxDescent()
5643
0
                                 : fontMetrics->MaxAscent();
5644
0
5645
0
    nsCSSRendering::DecorationRectParams params;
5646
0
    Float gfxWidth =
5647
0
      (verticalRun ? aVisualOverflowRect->height
5648
0
                   : aVisualOverflowRect->width) /
5649
0
      appUnitsPerDevUnit;
5650
0
    params.lineSize = Size(gfxWidth, underlineSize / appUnitsPerDevUnit);
5651
0
    params.ascent = gfxFloat(mAscent) / appUnitsPerDevUnit;
5652
0
    params.style = decorationStyle;
5653
0
    params.vertical = verticalRun;
5654
0
    params.sidewaysLeft = mTextRun->IsSidewaysLeft();
5655
0
5656
0
    params.offset = underlineOffset / appUnitsPerDevUnit;
5657
0
    params.decoration = NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE;
5658
0
    nsRect underlineRect =
5659
0
      nsCSSRendering::GetTextDecorationRect(aPresContext, params);
5660
0
    params.offset = maxAscent / appUnitsPerDevUnit;
5661
0
    params.decoration = NS_STYLE_TEXT_DECORATION_LINE_OVERLINE;
5662
0
    nsRect overlineRect =
5663
0
      nsCSSRendering::GetTextDecorationRect(aPresContext, params);
5664
0
5665
0
    aVisualOverflowRect->UnionRect(*aVisualOverflowRect, underlineRect);
5666
0
    aVisualOverflowRect->UnionRect(*aVisualOverflowRect, overlineRect);
5667
0
5668
0
    // XXX If strikeoutSize is much thicker than the underlineSize, it may
5669
0
    //     cause overflowing from the overflow rect.  However, such case
5670
0
    //     isn't realistic, we don't need to compute it now.
5671
0
  }
5672
0
  if (aIncludeTextDecorations) {
5673
0
    // Use writing mode of parent frame for orthogonal text frame to
5674
0
    // work. See comment in nsTextFrame::DrawTextRunAndDecorations.
5675
0
    WritingMode parentWM = GetParent()->GetWritingMode();
5676
0
    bool verticalDec = parentWM.IsVertical();
5677
0
    bool useVerticalMetrics = verticalDec != verticalRun
5678
0
      ? verticalDec : verticalRun && mTextRun->UseCenterBaseline();
5679
0
5680
0
    // Since CSS 2.1 requires that text-decoration defined on ancestors maintain
5681
0
    // style and position, they can be drawn at virtually any y-offset, so
5682
0
    // maxima and minima are required to reliably generate the rectangle for
5683
0
    // them
5684
0
    TextDecorations textDecs;
5685
0
    GetTextDecorations(aPresContext, eResolvedColors, textDecs);
5686
0
    if (textDecs.HasDecorationLines()) {
5687
0
      nscoord inflationMinFontSize =
5688
0
        nsLayoutUtils::InflationMinFontSizeFor(aBlock);
5689
0
5690
0
      const nscoord measure = verticalDec ? GetSize().height : GetSize().width;
5691
0
      gfxFloat gfxWidth = measure / appUnitsPerDevUnit;
5692
0
      gfxFloat ascent = gfxFloat(GetLogicalBaseline(parentWM))
5693
0
                          / appUnitsPerDevUnit;
5694
0
      nscoord frameBStart = 0;
5695
0
      if (parentWM.IsVerticalRL()) {
5696
0
        frameBStart = GetSize().width;
5697
0
        ascent = -ascent;
5698
0
      }
5699
0
5700
0
      nsCSSRendering::DecorationRectParams params;
5701
0
      params.lineSize = Size(gfxWidth, 0);
5702
0
      params.ascent = ascent;
5703
0
      params.vertical = verticalDec;
5704
0
      params.sidewaysLeft = mTextRun->IsSidewaysLeft();
5705
0
5706
0
      nscoord topOrLeft(nscoord_MAX), bottomOrRight(nscoord_MIN);
5707
0
      typedef gfxFont::Metrics Metrics;
5708
0
      auto accumulateDecorationRect = [&](const LineDecoration& dec,
5709
0
                                          gfxFloat Metrics::* lineSize,
5710
0
                                          gfxFloat Metrics::* lineOffset) {
5711
0
        params.style = dec.mStyle;
5712
0
        // If the style is solid, let's include decoration line rect of solid
5713
0
        // style since changing the style from none to solid/dotted/dashed
5714
0
        // doesn't cause reflow.
5715
0
        if (params.style == NS_STYLE_TEXT_DECORATION_STYLE_NONE) {
5716
0
          params.style = NS_STYLE_TEXT_DECORATION_STYLE_SOLID;
5717
0
        }
5718
0
5719
0
        float inflation =
5720
0
          GetInflationForTextDecorations(dec.mFrame, inflationMinFontSize);
5721
0
        const Metrics metrics =
5722
0
          GetFirstFontMetrics(GetFontGroupForFrame(dec.mFrame, inflation),
5723
0
                              useVerticalMetrics);
5724
0
5725
0
        params.lineSize.height = metrics.*lineSize;
5726
0
        params.offset = metrics.*lineOffset;
5727
0
        const nsRect decorationRect =
5728
0
          nsCSSRendering::GetTextDecorationRect(aPresContext, params) +
5729
0
          (verticalDec ? nsPoint(frameBStart - dec.mBaselineOffset, 0)
5730
0
                       : nsPoint(0, -dec.mBaselineOffset));
5731
0
5732
0
        if (verticalDec) {
5733
0
          topOrLeft = std::min(decorationRect.x, topOrLeft);
5734
0
          bottomOrRight = std::max(decorationRect.XMost(), bottomOrRight);
5735
0
        } else {
5736
0
          topOrLeft = std::min(decorationRect.y, topOrLeft);
5737
0
          bottomOrRight = std::max(decorationRect.YMost(), bottomOrRight);
5738
0
        }
5739
0
      };
5740
0
5741
0
      // Below we loop through all text decorations and compute the rectangle
5742
0
      // containing all of them, in this frame's coordinate space
5743
0
      params.decoration = NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE;
5744
0
      for (const LineDecoration& dec : textDecs.mUnderlines) {
5745
0
        accumulateDecorationRect(dec, &Metrics::underlineSize,
5746
0
                                 &Metrics::underlineOffset);
5747
0
      }
5748
0
      params.decoration = NS_STYLE_TEXT_DECORATION_LINE_OVERLINE;
5749
0
      for (const LineDecoration& dec : textDecs.mOverlines) {
5750
0
        accumulateDecorationRect(dec, &Metrics::underlineSize,
5751
0
                                 &Metrics::maxAscent);
5752
0
      }
5753
0
      params.decoration = NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH;
5754
0
      for (const LineDecoration& dec : textDecs.mStrikes) {
5755
0
        accumulateDecorationRect(dec, &Metrics::strikeoutSize,
5756
0
                                 &Metrics::strikeoutOffset);
5757
0
      }
5758
0
5759
0
      aVisualOverflowRect->UnionRect(
5760
0
        *aVisualOverflowRect,
5761
0
        verticalDec ? nsRect(topOrLeft, 0, bottomOrRight - topOrLeft, measure)
5762
0
                    : nsRect(0, topOrLeft, measure, bottomOrRight - topOrLeft));
5763
0
    }
5764
0
5765
0
    aVisualOverflowRect->UnionRect(*aVisualOverflowRect,
5766
0
                                   UpdateTextEmphasis(parentWM, aProvider));
5767
0
  }
5768
0
5769
0
  // text-stroke overflows: add half of text-stroke-width on all sides
5770
0
  nscoord textStrokeWidth = StyleText()->mWebkitTextStrokeWidth;
5771
0
  if (textStrokeWidth > 0) {
5772
0
    // Inflate rect by stroke-width/2; we add an extra pixel to allow for
5773
0
    // antialiasing, rounding errors, etc.
5774
0
    nsRect strokeRect = *aVisualOverflowRect;
5775
0
    strokeRect.Inflate(textStrokeWidth / 2 + appUnitsPerDevUnit);
5776
0
    aVisualOverflowRect->UnionRect(*aVisualOverflowRect, strokeRect);
5777
0
  }
5778
0
5779
0
  // Text-shadow overflows
5780
0
  nsRect shadowRect =
5781
0
    nsLayoutUtils::GetTextShadowRectsUnion(*aVisualOverflowRect, this);
5782
0
  aVisualOverflowRect->UnionRect(*aVisualOverflowRect, shadowRect);
5783
0
5784
0
  // When this frame is not selected, the text-decoration area must be in
5785
0
  // frame bounds.
5786
0
  if (!IsSelected() ||
5787
0
      !CombineSelectionUnderlineRect(aPresContext, *aVisualOverflowRect))
5788
0
    return;
5789
0
  AddStateBits(TEXT_SELECTION_UNDERLINE_OVERFLOWED);
5790
0
}
5791
5792
gfxFloat
5793
nsTextFrame::ComputeDescentLimitForSelectionUnderline(
5794
               nsPresContext* aPresContext,
5795
               const gfxFont::Metrics& aFontMetrics)
5796
0
{
5797
0
  gfxFloat app = aPresContext->AppUnitsPerDevPixel();
5798
0
  nscoord lineHeightApp =
5799
0
    ReflowInput::CalcLineHeight(GetContent(),
5800
0
                                Style(),
5801
0
                                PresContext(),
5802
0
                                NS_AUTOHEIGHT,
5803
0
                                GetFontSizeInflation());
5804
0
  gfxFloat lineHeight = gfxFloat(lineHeightApp) / app;
5805
0
  if (lineHeight <= aFontMetrics.maxHeight) {
5806
0
    return aFontMetrics.maxDescent;
5807
0
  }
5808
0
  return aFontMetrics.maxDescent + (lineHeight - aFontMetrics.maxHeight) / 2;
5809
0
}
5810
5811
// Make sure this stays in sync with DrawSelectionDecorations below
5812
static const SelectionTypeMask kSelectionTypesWithDecorations =
5813
  ToSelectionTypeMask(SelectionType::eSpellCheck) |
5814
  ToSelectionTypeMask(SelectionType::eURLStrikeout) |
5815
  ToSelectionTypeMask(SelectionType::eIMERawClause) |
5816
  ToSelectionTypeMask(SelectionType::eIMESelectedRawClause) |
5817
  ToSelectionTypeMask(SelectionType::eIMEConvertedClause) |
5818
  ToSelectionTypeMask(SelectionType::eIMESelectedClause);
5819
5820
/* static */
5821
gfxFloat
5822
nsTextFrame::ComputeSelectionUnderlineHeight(
5823
               nsPresContext* aPresContext,
5824
               const gfxFont::Metrics& aFontMetrics,
5825
               SelectionType aSelectionType)
5826
0
{
5827
0
  switch (aSelectionType) {
5828
0
    case SelectionType::eIMERawClause:
5829
0
    case SelectionType::eIMESelectedRawClause:
5830
0
    case SelectionType::eIMEConvertedClause:
5831
0
    case SelectionType::eIMESelectedClause:
5832
0
      return aFontMetrics.underlineSize;
5833
0
    case SelectionType::eSpellCheck: {
5834
0
      // The thickness of the spellchecker underline shouldn't honor the font
5835
0
      // metrics.  It should be constant pixels value which is decided from the
5836
0
      // default font size.  Note that if the actual font size is smaller than
5837
0
      // the default font size, we should use the actual font size because the
5838
0
      // computed value from the default font size can be too thick for the
5839
0
      // current font size.
5840
0
      nscoord defaultFontSize = aPresContext->GetDefaultFont(
5841
0
          kPresContext_DefaultVariableFont_ID, nullptr)->size;
5842
0
      int32_t zoomedFontSize = aPresContext->AppUnitsToDevPixels(
5843
0
          nsStyleFont::ZoomText(aPresContext, defaultFontSize));
5844
0
      gfxFloat fontSize = std::min(gfxFloat(zoomedFontSize),
5845
0
                                   aFontMetrics.emHeight);
5846
0
      fontSize = std::max(fontSize, 1.0);
5847
0
      return ceil(fontSize / 20);
5848
0
    }
5849
0
    default:
5850
0
      NS_WARNING("Requested underline style is not valid");
5851
0
      return aFontMetrics.underlineSize;
5852
0
  }
5853
0
}
5854
5855
enum class DecorationType
5856
{
5857
  Normal, Selection
5858
};
5859
struct nsTextFrame::PaintDecorationLineParams
5860
  : nsCSSRendering::DecorationRectParams
5861
{
5862
  gfxContext* context = nullptr;
5863
  LayoutDeviceRect dirtyRect;
5864
  Point pt;
5865
  const nscolor* overrideColor = nullptr;
5866
  nscolor color = NS_RGBA(0, 0, 0, 0);
5867
  gfxFloat icoordInFrame = 0.0f;
5868
  DecorationType decorationType = DecorationType::Normal;
5869
  DrawPathCallbacks* callbacks = nullptr;
5870
};
5871
5872
void
5873
nsTextFrame::PaintDecorationLine(const PaintDecorationLineParams& aParams)
5874
0
{
5875
0
  nsCSSRendering::PaintDecorationLineParams params;
5876
0
  static_cast<nsCSSRendering::DecorationRectParams&>(params) = aParams;
5877
0
  params.dirtyRect = aParams.dirtyRect.ToUnknownRect();
5878
0
  params.pt = aParams.pt;
5879
0
  params.color = aParams.overrideColor ? *aParams.overrideColor : aParams.color;
5880
0
  params.icoordInFrame = Float(aParams.icoordInFrame);
5881
0
  if (aParams.callbacks) {
5882
0
    Rect path = nsCSSRendering::DecorationLineToPath(params);
5883
0
    if (aParams.decorationType == DecorationType::Normal) {
5884
0
      aParams.callbacks->PaintDecorationLine(path, params.color);
5885
0
    } else {
5886
0
      aParams.callbacks->PaintSelectionDecorationLine(path, params.color);
5887
0
    }
5888
0
  } else {
5889
0
    nsCSSRendering::PaintDecorationLine(
5890
0
      this, *aParams.context->GetDrawTarget(), params);
5891
0
  }
5892
0
}
5893
5894
/**
5895
 * This, plus kSelectionTypesWithDecorations, encapsulates all knowledge
5896
 * about drawing text decoration for selections.
5897
 */
5898
void
5899
nsTextFrame::DrawSelectionDecorations(gfxContext* aContext,
5900
                                      const LayoutDeviceRect& aDirtyRect,
5901
                                      SelectionType aSelectionType,
5902
                                      nsTextPaintStyle& aTextPaintStyle,
5903
                                      const TextRangeStyle &aRangeStyle,
5904
                                      const Point& aPt,
5905
                                      gfxFloat aICoordInFrame,
5906
                                      gfxFloat aWidth,
5907
                                      gfxFloat aAscent,
5908
                                      const gfxFont::Metrics& aFontMetrics,
5909
                                      DrawPathCallbacks* aCallbacks,
5910
                                      bool aVertical,
5911
                                      uint8_t aDecoration)
5912
0
{
5913
0
  PaintDecorationLineParams params;
5914
0
  params.context = aContext;
5915
0
  params.dirtyRect = aDirtyRect;
5916
0
  params.pt = aPt;
5917
0
  params.lineSize.width = aWidth;
5918
0
  params.ascent = aAscent;
5919
0
  params.offset = aDecoration == NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE ?
5920
0
                  aFontMetrics.underlineOffset : aFontMetrics.maxAscent;
5921
0
  params.decoration = aDecoration;
5922
0
  params.decorationType = DecorationType::Selection;
5923
0
  params.callbacks = aCallbacks;
5924
0
  params.vertical = aVertical;
5925
0
  params.sidewaysLeft = mTextRun->IsSidewaysLeft();
5926
0
  params.descentLimit =
5927
0
    ComputeDescentLimitForSelectionUnderline(aTextPaintStyle.PresContext(),
5928
0
                                             aFontMetrics);
5929
0
5930
0
  float relativeSize;
5931
0
5932
0
  switch (aSelectionType) {
5933
0
    case SelectionType::eIMERawClause:
5934
0
    case SelectionType::eIMESelectedRawClause:
5935
0
    case SelectionType::eIMEConvertedClause:
5936
0
    case SelectionType::eIMESelectedClause:
5937
0
    case SelectionType::eSpellCheck: {
5938
0
      int32_t index = nsTextPaintStyle::
5939
0
        GetUnderlineStyleIndexForSelectionType(aSelectionType);
5940
0
      bool weDefineSelectionUnderline =
5941
0
        aTextPaintStyle.GetSelectionUnderlineForPaint(index, &params.color,
5942
0
                                                      &relativeSize,
5943
0
                                                      &params.style);
5944
0
      params.lineSize.height =
5945
0
        ComputeSelectionUnderlineHeight(aTextPaintStyle.PresContext(),
5946
0
                                        aFontMetrics, aSelectionType);
5947
0
      bool isIMEType = aSelectionType != SelectionType::eSpellCheck;
5948
0
5949
0
      if (isIMEType) {
5950
0
        // IME decoration lines should not be drawn on the both ends, i.e., we
5951
0
        // need to cut both edges of the decoration lines.  Because same style
5952
0
        // IME selections can adjoin, but the users need to be able to know
5953
0
        // where are the boundaries of the selections.
5954
0
        //
5955
0
        //  X: underline
5956
0
        //
5957
0
        //     IME selection #1        IME selection #2      IME selection #3
5958
0
        //  |                     |                      |
5959
0
        //  | XXXXXXXXXXXXXXXXXXX | XXXXXXXXXXXXXXXXXXXX | XXXXXXXXXXXXXXXXXXX
5960
0
        //  +---------------------+----------------------+--------------------
5961
0
        //   ^                   ^ ^                    ^ ^
5962
0
        //  gap                  gap                    gap
5963
0
        params.pt.x += 1.0;
5964
0
        params.lineSize.width -= 2.0;
5965
0
      }
5966
0
      if (isIMEType && aRangeStyle.IsDefined()) {
5967
0
        // If IME defines the style, that should override our definition.
5968
0
        if (aRangeStyle.IsLineStyleDefined()) {
5969
0
          if (aRangeStyle.mLineStyle == TextRangeStyle::LINESTYLE_NONE) {
5970
0
            return;
5971
0
          }
5972
0
          params.style = aRangeStyle.mLineStyle;
5973
0
          relativeSize = aRangeStyle.mIsBoldLine ? 2.0f : 1.0f;
5974
0
        } else if (!weDefineSelectionUnderline) {
5975
0
          // There is no underline style definition.
5976
0
          return;
5977
0
        }
5978
0
        // If underline color is defined and that doesn't depend on the
5979
0
        // foreground color, we should use the color directly.
5980
0
        if (aRangeStyle.IsUnderlineColorDefined() &&
5981
0
            (!aRangeStyle.IsForegroundColorDefined() ||
5982
0
             aRangeStyle.mUnderlineColor != aRangeStyle.mForegroundColor)) {
5983
0
          params.color = aRangeStyle.mUnderlineColor;
5984
0
        }
5985
0
        // If foreground color or background color is defined, the both colors
5986
0
        // are computed by GetSelectionTextColors().  Then, we should use its
5987
0
        // foreground color always.  The color should have sufficient contrast
5988
0
        // with the background color.
5989
0
        else if (aRangeStyle.IsForegroundColorDefined() ||
5990
0
                 aRangeStyle.IsBackgroundColorDefined()) {
5991
0
          nscolor bg;
5992
0
          GetSelectionTextColors(aSelectionType, aTextPaintStyle,
5993
0
                                 aRangeStyle, &params.color, &bg);
5994
0
        }
5995
0
        // Otherwise, use the foreground color of the frame.
5996
0
        else {
5997
0
          params.color = aTextPaintStyle.GetTextColor();
5998
0
        }
5999
0
      } else if (!weDefineSelectionUnderline) {
6000
0
        // IME doesn't specify the selection style and we don't define selection
6001
0
        // underline.
6002
0
        return;
6003
0
      }
6004
0
      break;
6005
0
    }
6006
0
    case SelectionType::eURLStrikeout: {
6007
0
      nscoord inflationMinFontSize =
6008
0
        nsLayoutUtils::InflationMinFontSizeFor(this);
6009
0
      float inflation =
6010
0
        GetInflationForTextDecorations(this, inflationMinFontSize);
6011
0
      const gfxFont::Metrics metrics =
6012
0
        GetFirstFontMetrics(GetFontGroupForFrame(this, inflation), aVertical);
6013
0
6014
0
      relativeSize = 2.0f;
6015
0
      aTextPaintStyle.GetURLSecondaryColor(&params.color);
6016
0
      params.style = NS_STYLE_TEXT_DECORATION_STYLE_SOLID;
6017
0
      params.lineSize.height = metrics.strikeoutSize;
6018
0
      params.offset = metrics.strikeoutOffset + 0.5;
6019
0
      params.decoration = NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH;
6020
0
      break;
6021
0
    }
6022
0
    default:
6023
0
      NS_WARNING("Requested selection decorations when there aren't any");
6024
0
      return;
6025
0
  }
6026
0
  params.lineSize.height *= relativeSize;
6027
0
  params.icoordInFrame = (aVertical ? params.pt.y - aPt.y
6028
0
                                    : params.pt.x - aPt.x) + aICoordInFrame;
6029
0
  PaintDecorationLine(params);
6030
0
}
6031
6032
/* static */
6033
bool
6034
nsTextFrame::GetSelectionTextColors(SelectionType aSelectionType,
6035
                                    nsTextPaintStyle& aTextPaintStyle,
6036
                                    const TextRangeStyle &aRangeStyle,
6037
                                    nscolor* aForeground,
6038
                                    nscolor* aBackground)
6039
0
{
6040
0
  switch (aSelectionType) {
6041
0
    case SelectionType::eNormal:
6042
0
      return aTextPaintStyle.GetSelectionColors(aForeground, aBackground);
6043
0
    case SelectionType::eFind:
6044
0
      aTextPaintStyle.GetHighlightColors(aForeground, aBackground);
6045
0
      return true;
6046
0
    case SelectionType::eURLSecondary:
6047
0
      aTextPaintStyle.GetURLSecondaryColor(aForeground);
6048
0
      *aBackground = NS_RGBA(0,0,0,0);
6049
0
      return true;
6050
0
    case SelectionType::eIMERawClause:
6051
0
    case SelectionType::eIMESelectedRawClause:
6052
0
    case SelectionType::eIMEConvertedClause:
6053
0
    case SelectionType::eIMESelectedClause:
6054
0
      if (aRangeStyle.IsDefined()) {
6055
0
        if (!aRangeStyle.IsForegroundColorDefined() &&
6056
0
            !aRangeStyle.IsBackgroundColorDefined()) {
6057
0
          *aForeground = aTextPaintStyle.GetTextColor();
6058
0
          *aBackground = NS_RGBA(0,0,0,0);
6059
0
          return false;
6060
0
        }
6061
0
        if (aRangeStyle.IsForegroundColorDefined()) {
6062
0
          *aForeground = aRangeStyle.mForegroundColor;
6063
0
          if (aRangeStyle.IsBackgroundColorDefined()) {
6064
0
            *aBackground = aRangeStyle.mBackgroundColor;
6065
0
          } else {
6066
0
            // If foreground color is defined but background color isn't
6067
0
            // defined, we can guess that IME must expect that the background
6068
0
            // color is system's default field background color.
6069
0
            *aBackground = aTextPaintStyle.GetSystemFieldBackgroundColor();
6070
0
          }
6071
0
        } else { // aRangeStyle.IsBackgroundColorDefined() is true
6072
0
          *aBackground = aRangeStyle.mBackgroundColor;
6073
0
          // If background color is defined but foreground color isn't defined,
6074
0
          // we can assume that IME must expect that the foreground color is
6075
0
          // same as system's field text color.
6076
0
          *aForeground = aTextPaintStyle.GetSystemFieldForegroundColor();
6077
0
        }
6078
0
        return true;
6079
0
      }
6080
0
      aTextPaintStyle.GetIMESelectionColors(
6081
0
        nsTextPaintStyle::GetUnderlineStyleIndexForSelectionType(
6082
0
          aSelectionType),
6083
0
        aForeground, aBackground);
6084
0
      return true;
6085
0
    default:
6086
0
      *aForeground = aTextPaintStyle.GetTextColor();
6087
0
      *aBackground = NS_RGBA(0,0,0,0);
6088
0
      return false;
6089
0
  }
6090
0
}
6091
6092
/**
6093
 * This sets *aShadow to the appropriate shadow, if any, for the given
6094
 * type of selection. Returns true if *aShadow was set.
6095
 * If text-shadow was not specified, *aShadow is left untouched
6096
 * (NOT reset to null), and the function returns false.
6097
 */
6098
static bool GetSelectionTextShadow(nsIFrame* aFrame,
6099
                                   SelectionType aSelectionType,
6100
                                   nsTextPaintStyle& aTextPaintStyle,
6101
                                   nsCSSShadowArray** aShadow)
6102
{
6103
  switch (aSelectionType) {
6104
    case SelectionType::eNormal:
6105
      return aTextPaintStyle.GetSelectionShadow(aShadow);
6106
    default:
6107
      return false;
6108
  }
6109
}
6110
6111
/**
6112
 * This class lets us iterate over chunks of text in a uniform selection state,
6113
 * observing cluster boundaries, in content order, maintaining the current
6114
 * x-offset as we go, and telling whether the text chunk has a hyphen after
6115
 * it or not. The caller is responsible for actually computing the advance
6116
 * width of each chunk.
6117
 */
6118
class SelectionIterator {
6119
public:
6120
  /**
6121
   * aStart and aLength are in the original string. aSelectionDetails is
6122
   * according to the original string.
6123
   * @param aXOffset the offset from the origin of the frame to the start
6124
   * of the text (the left baseline origin for LTR, the right baseline origin
6125
   * for RTL)
6126
   */
6127
  SelectionIterator(SelectionDetails** aSelectionDetails,
6128
                    gfxTextRun::Range aRange, PropertyProvider& aProvider,
6129
                    gfxTextRun* aTextRun, gfxFloat aXOffset);
6130
6131
  /**
6132
   * Returns the next segment of uniformly selected (or not) text.
6133
   * @param aXOffset the offset from the origin of the frame to the start
6134
   * of the text (the left baseline origin for LTR, the right baseline origin
6135
   * for RTL)
6136
   * @param aRange the transformed string range of the text for this segment
6137
   * @param aHyphenWidth if a hyphen is to be rendered after the text, the
6138
   * width of the hyphen, otherwise zero
6139
   * @param aSelectionType the selection type for this segment
6140
   * @param aStyle the selection style for this segment
6141
   * @return false if there are no more segments
6142
   */
6143
  bool GetNextSegment(gfxFloat* aXOffset, gfxTextRun::Range* aRange,
6144
                      gfxFloat* aHyphenWidth,
6145
                      SelectionType* aSelectionType,
6146
                      TextRangeStyle* aStyle);
6147
0
  void UpdateWithAdvance(gfxFloat aAdvance) {
6148
0
    mXOffset += aAdvance*mTextRun->GetDirection();
6149
0
  }
6150
6151
private:
6152
  SelectionDetails**      mSelectionDetails;
6153
  PropertyProvider&       mProvider;
6154
  RefPtr<gfxTextRun>      mTextRun;
6155
  gfxSkipCharsIterator    mIterator;
6156
  gfxTextRun::Range       mOriginalRange;
6157
  gfxFloat                mXOffset;
6158
};
6159
6160
SelectionIterator::SelectionIterator(SelectionDetails** aSelectionDetails,
6161
                                     gfxTextRun::Range aRange,
6162
                                     PropertyProvider& aProvider,
6163
                                     gfxTextRun* aTextRun, gfxFloat aXOffset)
6164
  : mSelectionDetails(aSelectionDetails), mProvider(aProvider),
6165
    mTextRun(aTextRun), mIterator(aProvider.GetStart()),
6166
    mOriginalRange(aRange), mXOffset(aXOffset)
6167
0
{
6168
0
  mIterator.SetOriginalOffset(aRange.start);
6169
0
}
6170
6171
bool SelectionIterator::GetNextSegment(gfxFloat* aXOffset,
6172
                                       gfxTextRun::Range* aRange,
6173
                                       gfxFloat* aHyphenWidth,
6174
                                       SelectionType* aSelectionType,
6175
                                       TextRangeStyle* aStyle)
6176
0
{
6177
0
  if (mIterator.GetOriginalOffset() >= int32_t(mOriginalRange.end))
6178
0
    return false;
6179
0
6180
0
  // save offset into transformed string now
6181
0
  uint32_t runOffset = mIterator.GetSkippedOffset();
6182
0
6183
0
  uint32_t index = mIterator.GetOriginalOffset() - mOriginalRange.start;
6184
0
  SelectionDetails* sdptr = mSelectionDetails[index];
6185
0
  SelectionType selectionType =
6186
0
    sdptr ? sdptr->mSelectionType : SelectionType::eNone;
6187
0
  TextRangeStyle style;
6188
0
  if (sdptr) {
6189
0
    style = sdptr->mTextRangeStyle;
6190
0
  }
6191
0
  for (++index; index < mOriginalRange.Length(); ++index) {
6192
0
    if (sdptr != mSelectionDetails[index])
6193
0
      break;
6194
0
  }
6195
0
  mIterator.SetOriginalOffset(index + mOriginalRange.start);
6196
0
6197
0
  // Advance to the next cluster boundary
6198
0
  while (mIterator.GetOriginalOffset() < int32_t(mOriginalRange.end) &&
6199
0
         !mIterator.IsOriginalCharSkipped() &&
6200
0
         !mTextRun->IsClusterStart(mIterator.GetSkippedOffset())) {
6201
0
    mIterator.AdvanceOriginal(1);
6202
0
  }
6203
0
6204
0
  bool haveHyphenBreak =
6205
0
    (mProvider.GetFrame()->GetStateBits() & TEXT_HYPHEN_BREAK) != 0;
6206
0
  aRange->start = runOffset;
6207
0
  aRange->end = mIterator.GetSkippedOffset();
6208
0
  *aXOffset = mXOffset;
6209
0
  *aHyphenWidth = 0;
6210
0
  if (mIterator.GetOriginalOffset() == int32_t(mOriginalRange.end) &&
6211
0
      haveHyphenBreak) {
6212
0
    *aHyphenWidth = mProvider.GetHyphenWidth();
6213
0
  }
6214
0
  *aSelectionType = selectionType;
6215
0
  *aStyle = style;
6216
0
  return true;
6217
0
}
6218
6219
static void
6220
AddHyphenToMetrics(nsTextFrame* aTextFrame, const gfxTextRun* aBaseTextRun,
6221
                   gfxTextRun::Metrics* aMetrics,
6222
                   gfxFont::BoundingBoxType aBoundingBoxType,
6223
                   DrawTarget* aDrawTarget)
6224
0
{
6225
0
  // Fix up metrics to include hyphen
6226
0
  RefPtr<gfxTextRun> hyphenTextRun =
6227
0
    GetHyphenTextRun(aBaseTextRun, aDrawTarget, aTextFrame);
6228
0
  if (!hyphenTextRun) {
6229
0
    return;
6230
0
  }
6231
0
6232
0
  gfxTextRun::Metrics hyphenMetrics =
6233
0
    hyphenTextRun->MeasureText(aBoundingBoxType, aDrawTarget);
6234
0
  if (aTextFrame->GetWritingMode().IsLineInverted()) {
6235
0
    hyphenMetrics.mBoundingBox.y = -hyphenMetrics.mBoundingBox.YMost();
6236
0
  }
6237
0
  aMetrics->CombineWith(hyphenMetrics, aBaseTextRun->IsRightToLeft());
6238
0
}
6239
6240
void
6241
nsTextFrame::PaintOneShadow(const PaintShadowParams& aParams,
6242
                            nsCSSShadowItem* aShadowDetails,
6243
                            gfxRect& aBoundingBox, uint32_t aBlurFlags)
6244
0
{
6245
0
  AUTO_PROFILER_LABEL("nsTextFrame::PaintOneShadow", GRAPHICS);
6246
0
6247
0
  gfx::Point shadowOffset(aShadowDetails->mXOffset, aShadowDetails->mYOffset);
6248
0
  nscoord blurRadius = std::max(aShadowDetails->mRadius, 0);
6249
0
6250
0
  nscolor shadowColor =
6251
0
    aShadowDetails->mColor.CalcColor(aParams.foregroundColor);
6252
0
6253
0
  if (auto* textDrawer = aParams.context->GetTextDrawer()) {
6254
0
    wr::Shadow wrShadow;
6255
0
6256
0
    wrShadow.offset = {
6257
0
      PresContext()->AppUnitsToFloatDevPixels(aShadowDetails->mXOffset),
6258
0
      PresContext()->AppUnitsToFloatDevPixels(aShadowDetails->mYOffset)
6259
0
    };
6260
0
6261
0
    wrShadow.blur_radius = PresContext()->AppUnitsToFloatDevPixels(blurRadius);
6262
0
    wrShadow.color = wr::ToColorF(ToDeviceColor(shadowColor));
6263
0
6264
0
    textDrawer->AppendShadow(wrShadow);
6265
0
    return;
6266
0
  }
6267
0
6268
0
  // This rect is the box which is equivalent to where the shadow will be painted.
6269
0
  // The origin of aBoundingBox is the text baseline left, so we must translate it by
6270
0
  // that much in order to make the origin the top-left corner of the text bounding box.
6271
0
  // Note that aLeftSideOffset is line-left, so actually means top offset in
6272
0
  // vertical writing modes.
6273
0
  gfxRect shadowGfxRect;
6274
0
  WritingMode wm = GetWritingMode();
6275
0
  if (wm.IsVertical()) {
6276
0
    shadowGfxRect = aBoundingBox;
6277
0
    if (wm.IsVerticalRL()) {
6278
0
      // for vertical-RL, reverse direction of x-coords of bounding box
6279
0
      shadowGfxRect.x = -shadowGfxRect.XMost();
6280
0
    }
6281
0
    shadowGfxRect += gfxPoint(aParams.textBaselinePt.x,
6282
0
                              aParams.framePt.y + aParams.leftSideOffset);
6283
0
  } else {
6284
0
    shadowGfxRect =
6285
0
      aBoundingBox + gfxPoint(aParams.framePt.x + aParams.leftSideOffset,
6286
0
                              aParams.textBaselinePt.y);
6287
0
  }
6288
0
  shadowGfxRect += gfxPoint(shadowOffset.x, shadowOffset.y);
6289
0
6290
0
  nsRect shadowRect(NSToCoordRound(shadowGfxRect.X()),
6291
0
                    NSToCoordRound(shadowGfxRect.Y()),
6292
0
                    NSToCoordRound(shadowGfxRect.Width()),
6293
0
                    NSToCoordRound(shadowGfxRect.Height()));
6294
0
6295
0
  nsContextBoxBlur contextBoxBlur;
6296
0
  const auto A2D = PresContext()->AppUnitsPerDevPixel();
6297
0
  gfxContext* shadowContext = contextBoxBlur.Init(
6298
0
    shadowRect, 0, blurRadius, A2D, aParams.context,
6299
0
    LayoutDevicePixel::ToAppUnits(aParams.dirtyRect, A2D), nullptr, aBlurFlags);
6300
0
  if (!shadowContext)
6301
0
    return;
6302
0
6303
0
  aParams.context->Save();
6304
0
  aParams.context->SetColor(Color::FromABGR(shadowColor));
6305
0
6306
0
  // Draw the text onto our alpha-only surface to capture the alpha values.
6307
0
  // Remember that the box blur context has a device offset on it, so we don't need to
6308
0
  // translate any coordinates to fit on the surface.
6309
0
  gfxFloat advanceWidth;
6310
0
  nsTextPaintStyle textPaintStyle(this);
6311
0
  DrawTextParams params(shadowContext);
6312
0
  params.advanceWidth = &advanceWidth;
6313
0
  params.dirtyRect = aParams.dirtyRect;
6314
0
  params.framePt = aParams.framePt + shadowOffset;
6315
0
  params.provider = aParams.provider;
6316
0
  params.textStyle = &textPaintStyle;
6317
0
  params.textColor =
6318
0
    aParams.context == shadowContext ? shadowColor : NS_RGB(0, 0, 0);
6319
0
  params.clipEdges = aParams.clipEdges;
6320
0
  params.drawSoftHyphen = (GetStateBits() & TEXT_HYPHEN_BREAK) != 0;
6321
0
  // Multi-color shadow is not allowed, so we use the same color of the text color.
6322
0
  params.decorationOverrideColor = &params.textColor;
6323
0
  DrawText(aParams.range, aParams.textBaselinePt + shadowOffset, params);
6324
0
6325
0
  contextBoxBlur.DoPaint();
6326
0
  aParams.context->Restore();
6327
0
}
6328
6329
// Paints selection backgrounds and text in the correct colors. Also computes
6330
// aAllTypes, the union of all selection types that are applying to this text.
6331
bool
6332
nsTextFrame::PaintTextWithSelectionColors(
6333
    const PaintTextSelectionParams& aParams,
6334
    const UniquePtr<SelectionDetails>& aDetails,
6335
    SelectionTypeMask* aAllSelectionTypeMask,
6336
    const nsCharClipDisplayItem::ClipEdges& aClipEdges)
6337
0
{
6338
0
  const gfxTextRun::Range& contentRange = aParams.contentRange;
6339
0
6340
0
  // Figure out which selections control the colors to use for each character.
6341
0
  // Note: prevailingSelectionsBuffer is keeping extra raw pointers to
6342
0
  // uniquely-owned resources, but it's safe because it's temporary and the
6343
0
  // resources are owned by the caller. Therefore, they'll outlive this object.
6344
0
  AutoTArray<SelectionDetails*,BIG_TEXT_NODE_SIZE> prevailingSelectionsBuffer;
6345
0
  SelectionDetails** prevailingSelections =
6346
0
    prevailingSelectionsBuffer.AppendElements(contentRange.Length(), fallible);
6347
0
  if (!prevailingSelections) {
6348
0
    return false;
6349
0
  }
6350
0
6351
0
  SelectionTypeMask allSelectionTypeMask = 0;
6352
0
  for (uint32_t i = 0; i < contentRange.Length(); ++i) {
6353
0
    prevailingSelections[i] = nullptr;
6354
0
  }
6355
0
6356
0
  bool anyBackgrounds = false;
6357
0
  for (SelectionDetails* sdptr = aDetails.get(); sdptr; sdptr = sdptr->mNext.get()) {
6358
0
    int32_t start = std::max(0, sdptr->mStart - int32_t(contentRange.start));
6359
0
    int32_t end = std::min(int32_t(contentRange.Length()),
6360
0
                           sdptr->mEnd - int32_t(contentRange.start));
6361
0
    SelectionType selectionType = sdptr->mSelectionType;
6362
0
    if (start < end) {
6363
0
      allSelectionTypeMask |= ToSelectionTypeMask(selectionType);
6364
0
      // Ignore selections that don't set colors
6365
0
      nscolor foreground, background;
6366
0
      if (GetSelectionTextColors(selectionType, *aParams.textPaintStyle,
6367
0
                                 sdptr->mTextRangeStyle,
6368
0
                                 &foreground, &background)) {
6369
0
        if (NS_GET_A(background) > 0) {
6370
0
          anyBackgrounds = true;
6371
0
        }
6372
0
        for (int32_t i = start; i < end; ++i) {
6373
0
          // Favour normal selection over IME selections
6374
0
          if (!prevailingSelections[i] ||
6375
0
              selectionType < prevailingSelections[i]->mSelectionType) {
6376
0
            prevailingSelections[i] = sdptr;
6377
0
          }
6378
0
        }
6379
0
      }
6380
0
    }
6381
0
  }
6382
0
  *aAllSelectionTypeMask = allSelectionTypeMask;
6383
0
6384
0
  if (!allSelectionTypeMask) {
6385
0
    // Nothing is selected in the given text range. XXX can this still occur?
6386
0
    return false;
6387
0
  }
6388
0
6389
0
  bool vertical = mTextRun->IsVertical();
6390
0
  const gfxFloat startIOffset = vertical ?
6391
0
    aParams.textBaselinePt.y - aParams.framePt.y :
6392
0
    aParams.textBaselinePt.x - aParams.framePt.x;
6393
0
  gfxFloat iOffset, hyphenWidth;
6394
0
  Range range; // in transformed string
6395
0
  TextRangeStyle rangeStyle;
6396
0
  // Draw background colors
6397
0
6398
0
  auto* textDrawer = aParams.context->GetTextDrawer();
6399
0
6400
0
  if (anyBackgrounds && !aParams.IsGenerateTextMask()) {
6401
0
    int32_t appUnitsPerDevPixel =
6402
0
      aParams.textPaintStyle->PresContext()->AppUnitsPerDevPixel();
6403
0
    SelectionIterator iterator(prevailingSelections, contentRange,
6404
0
                               *aParams.provider, mTextRun, startIOffset);
6405
0
    SelectionType selectionType;
6406
0
    while (iterator.GetNextSegment(&iOffset, &range, &hyphenWidth,
6407
0
                                   &selectionType, &rangeStyle)) {
6408
0
      nscolor foreground, background;
6409
0
      GetSelectionTextColors(selectionType, *aParams.textPaintStyle,
6410
0
                             rangeStyle, &foreground, &background);
6411
0
      // Draw background color
6412
0
      gfxFloat advance = hyphenWidth +
6413
0
        mTextRun->GetAdvanceWidth(range, aParams.provider);
6414
0
      if (NS_GET_A(background) > 0) {
6415
0
        nsRect bgRect;
6416
0
        gfxFloat offs = iOffset - (mTextRun->IsInlineReversed() ? advance : 0);
6417
0
        if (vertical) {
6418
0
          bgRect = nsRect(aParams.framePt.x, aParams.framePt.y + offs,
6419
0
                          GetSize().width, advance);
6420
0
        } else {
6421
0
          bgRect = nsRect(aParams.framePt.x + offs, aParams.framePt.y,
6422
0
                          advance, GetSize().height);
6423
0
        }
6424
0
6425
0
        LayoutDeviceRect selectionRect =
6426
0
          LayoutDeviceRect::FromAppUnits(bgRect, appUnitsPerDevPixel);
6427
0
6428
0
        if (textDrawer) {
6429
0
          textDrawer->AppendSelectionRect(selectionRect, ToDeviceColor(background));
6430
0
        } else {
6431
0
          PaintSelectionBackground(
6432
0
            *aParams.context->GetDrawTarget(), background, aParams.dirtyRect,
6433
0
            selectionRect, aParams.callbacks);
6434
0
        }
6435
0
      }
6436
0
      iterator.UpdateWithAdvance(advance);
6437
0
    }
6438
0
  }
6439
0
6440
0
  if (aParams.IsPaintBGColor()) {
6441
0
    return true;
6442
0
  }
6443
0
6444
0
  gfxFloat advance;
6445
0
  DrawTextParams params(aParams.context);
6446
0
  params.dirtyRect = aParams.dirtyRect;
6447
0
  params.framePt = aParams.framePt;
6448
0
  params.provider = aParams.provider;
6449
0
  params.textStyle = aParams.textPaintStyle;
6450
0
  params.clipEdges = &aClipEdges;
6451
0
  params.advanceWidth = &advance;
6452
0
  params.callbacks = aParams.callbacks;
6453
0
6454
0
  PaintShadowParams shadowParams(aParams);
6455
0
  shadowParams.provider = aParams.provider;
6456
0
  shadowParams.clipEdges = &aClipEdges;
6457
0
6458
0
  // Draw text
6459
0
  const nsStyleText* textStyle = StyleText();
6460
0
  SelectionIterator iterator(prevailingSelections, contentRange,
6461
0
                             *aParams.provider, mTextRun, startIOffset);
6462
0
  SelectionType selectionType;
6463
0
  while (iterator.GetNextSegment(&iOffset, &range, &hyphenWidth,
6464
0
                                 &selectionType, &rangeStyle)) {
6465
0
    nscolor foreground, background;
6466
0
    if (aParams.IsGenerateTextMask()) {
6467
0
      foreground = NS_RGBA(0, 0, 0, 255);
6468
0
    } else {
6469
0
      GetSelectionTextColors(selectionType, *aParams.textPaintStyle,
6470
0
                             rangeStyle, &foreground, &background);
6471
0
    }
6472
0
6473
0
    gfx::Point textBaselinePt = vertical ?
6474
0
      gfx::Point(aParams.textBaselinePt.x, aParams.framePt.y + iOffset) :
6475
0
      gfx::Point(aParams.framePt.x + iOffset, aParams.textBaselinePt.y);
6476
0
6477
0
    // Determine what shadow, if any, to draw - either from textStyle
6478
0
    // or from the ::-moz-selection pseudo-class if specified there
6479
0
    nsCSSShadowArray* shadow = textStyle->GetTextShadow();
6480
0
    GetSelectionTextShadow(this, selectionType, *aParams.textPaintStyle,
6481
0
                           &shadow);
6482
0
    if (shadow) {
6483
0
      nscoord startEdge = iOffset;
6484
0
      if (mTextRun->IsInlineReversed()) {
6485
0
        startEdge -= hyphenWidth +
6486
0
          mTextRun->GetAdvanceWidth(range, aParams.provider);
6487
0
      }
6488
0
      shadowParams.range = range;
6489
0
      shadowParams.textBaselinePt = textBaselinePt;
6490
0
      shadowParams.foregroundColor = foreground;
6491
0
      shadowParams.leftSideOffset = startEdge;
6492
0
      PaintShadows(shadow, shadowParams);
6493
0
    }
6494
0
6495
0
    // Draw text segment
6496
0
    params.textColor = foreground;
6497
0
    params.textStrokeColor = aParams.textPaintStyle->GetWebkitTextStrokeColor();
6498
0
    params.textStrokeWidth = aParams.textPaintStyle->GetWebkitTextStrokeWidth();
6499
0
    params.drawSoftHyphen = hyphenWidth > 0;
6500
0
    DrawText(range, textBaselinePt, params);
6501
0
    advance += hyphenWidth;
6502
0
    iterator.UpdateWithAdvance(advance);
6503
0
  }
6504
0
  return true;
6505
0
}
6506
6507
void
6508
nsTextFrame::PaintTextSelectionDecorations(
6509
    const PaintTextSelectionParams& aParams,
6510
    const UniquePtr<SelectionDetails>& aDetails,
6511
    SelectionType aSelectionType)
6512
0
{
6513
0
  // Hide text decorations if we're currently hiding @font-face fallback text
6514
0
  if (aParams.provider->GetFontGroup()->ShouldSkipDrawing())
6515
0
    return;
6516
0
6517
0
  // Figure out which characters will be decorated for this selection.
6518
0
  // Note: selectedCharsBuffer is keeping extra raw pointers to
6519
0
  // uniquely-owned resources, but it's safe because it's temporary and the
6520
0
  // resources are owned by the caller. Therefore, they'll outlive this object.
6521
0
  const gfxTextRun::Range& contentRange = aParams.contentRange;
6522
0
  AutoTArray<SelectionDetails*, BIG_TEXT_NODE_SIZE> selectedCharsBuffer;
6523
0
  SelectionDetails** selectedChars =
6524
0
    selectedCharsBuffer.AppendElements(contentRange.Length(), fallible);
6525
0
  if (!selectedChars) {
6526
0
    return;
6527
0
  }
6528
0
  for (uint32_t i = 0; i < contentRange.Length(); ++i) {
6529
0
    selectedChars[i] = nullptr;
6530
0
  }
6531
0
6532
0
  for (SelectionDetails* sdptr = aDetails.get(); sdptr; sdptr = sdptr->mNext.get()) {
6533
0
    if (sdptr->mSelectionType == aSelectionType) {
6534
0
      int32_t start = std::max(0, sdptr->mStart - int32_t(contentRange.start));
6535
0
      int32_t end = std::min(int32_t(contentRange.Length()),
6536
0
                             sdptr->mEnd - int32_t(contentRange.start));
6537
0
      for (int32_t i = start; i < end; ++i) {
6538
0
        selectedChars[i] = sdptr;
6539
0
      }
6540
0
    }
6541
0
  }
6542
0
6543
0
  gfxFont* firstFont = aParams.provider->GetFontGroup()->GetFirstValidFont();
6544
0
  bool verticalRun = mTextRun->IsVertical();
6545
0
  bool rightUnderline = verticalRun && IsUnderlineRight(this);
6546
0
  const uint8_t kDecoration =
6547
0
    rightUnderline ? NS_STYLE_TEXT_DECORATION_LINE_OVERLINE :
6548
0
                     NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE;
6549
0
  bool useVerticalMetrics = verticalRun && mTextRun->UseCenterBaseline();
6550
0
  gfxFont::Metrics
6551
0
    decorationMetrics(firstFont->GetMetrics(useVerticalMetrics ?
6552
0
      gfxFont::eVertical : gfxFont::eHorizontal));
6553
0
  if (!useVerticalMetrics) {
6554
0
    // The potential adjustment from using gfxFontGroup::GetUnderlineOffset
6555
0
    // is only valid for horizontal font metrics.
6556
0
    decorationMetrics.underlineOffset =
6557
0
      aParams.provider->GetFontGroup()->GetUnderlineOffset();
6558
0
  }
6559
0
6560
0
  gfxFloat startIOffset = verticalRun ?
6561
0
    aParams.textBaselinePt.y - aParams.framePt.y :
6562
0
    aParams.textBaselinePt.x - aParams.framePt.x;
6563
0
  SelectionIterator iterator(selectedChars, contentRange,
6564
0
                             *aParams.provider, mTextRun, startIOffset);
6565
0
  gfxFloat iOffset, hyphenWidth;
6566
0
  Range range;
6567
0
  int32_t app = aParams.textPaintStyle->PresContext()->AppUnitsPerDevPixel();
6568
0
  // XXX aTextBaselinePt is in AppUnits, shouldn't it be nsFloatPoint?
6569
0
  Point pt;
6570
0
  if (verticalRun) {
6571
0
    pt.x = (aParams.textBaselinePt.x - mAscent) / app;
6572
0
  } else {
6573
0
    pt.y = (aParams.textBaselinePt.y - mAscent) / app;
6574
0
  }
6575
0
  SelectionType nextSelectionType;
6576
0
  TextRangeStyle selectedStyle;
6577
0
6578
0
  while (iterator.GetNextSegment(&iOffset, &range, &hyphenWidth,
6579
0
                                 &nextSelectionType, &selectedStyle)) {
6580
0
    gfxFloat advance = hyphenWidth +
6581
0
      mTextRun->GetAdvanceWidth(range, aParams.provider);
6582
0
    if (nextSelectionType == aSelectionType) {
6583
0
      if (verticalRun) {
6584
0
        pt.y = (aParams.framePt.y + iOffset -
6585
0
               (mTextRun->IsInlineReversed() ? advance : 0)) / app;
6586
0
      } else {
6587
0
        pt.x = (aParams.framePt.x + iOffset -
6588
0
               (mTextRun->IsInlineReversed() ? advance : 0)) / app;
6589
0
      }
6590
0
      gfxFloat width = Abs(advance) / app;
6591
0
      gfxFloat xInFrame = pt.x - (aParams.framePt.x / app);
6592
0
      DrawSelectionDecorations(
6593
0
        aParams.context, aParams.dirtyRect, aSelectionType,
6594
0
        *aParams.textPaintStyle, selectedStyle, pt, xInFrame,
6595
0
        width, mAscent / app, decorationMetrics, aParams.callbacks,
6596
0
        verticalRun, kDecoration);
6597
0
    }
6598
0
    iterator.UpdateWithAdvance(advance);
6599
0
  }
6600
0
}
6601
6602
bool
6603
nsTextFrame::PaintTextWithSelection(
6604
    const PaintTextSelectionParams& aParams,
6605
    const nsCharClipDisplayItem::ClipEdges& aClipEdges)
6606
0
{
6607
0
  NS_ASSERTION(GetContent()->IsSelectionDescendant(), "wrong paint path");
6608
0
6609
0
  UniquePtr<SelectionDetails> details = GetSelectionDetails();
6610
0
  if (!details) {
6611
0
    return false;
6612
0
  }
6613
0
6614
0
  SelectionTypeMask allSelectionTypeMask;
6615
0
  if (!PaintTextWithSelectionColors(aParams, details, &allSelectionTypeMask,
6616
0
                                    aClipEdges)) {
6617
0
    return false;
6618
0
  }
6619
0
  // Iterate through just the selection rawSelectionTypes that paint decorations
6620
0
  // and paint decorations for any that actually occur in this frame. Paint
6621
0
  // higher-numbered selection rawSelectionTypes below lower-numered ones on the
6622
0
  // general principal that lower-numbered selections are higher priority.
6623
0
  allSelectionTypeMask &= kSelectionTypesWithDecorations;
6624
0
  MOZ_ASSERT(kPresentSelectionTypes[0] == SelectionType::eNormal,
6625
0
             "The following for loop assumes that the first item of "
6626
0
             "kPresentSelectionTypes is SelectionType::eNormal");
6627
0
  for (size_t i = ArrayLength(kPresentSelectionTypes) - 1; i >= 1; --i) {
6628
0
    SelectionType selectionType = kPresentSelectionTypes[i];
6629
0
    if (ToSelectionTypeMask(selectionType) & allSelectionTypeMask) {
6630
0
      // There is some selection of this selectionType. Try to paint its
6631
0
      // decorations (there might not be any for this type but that's OK,
6632
0
      // PaintTextSelectionDecorations will exit early).
6633
0
      PaintTextSelectionDecorations(aParams, details, selectionType);
6634
0
    }
6635
0
  }
6636
0
6637
0
  return true;
6638
0
}
6639
6640
void
6641
nsTextFrame::DrawEmphasisMarks(gfxContext* aContext,
6642
                               WritingMode aWM,
6643
                               const gfx::Point& aTextBaselinePt,
6644
                               const gfx::Point& aFramePt, Range aRange,
6645
                               const nscolor* aDecorationOverrideColor,
6646
                               PropertyProvider* aProvider)
6647
0
{
6648
0
  const EmphasisMarkInfo* info = GetProperty(EmphasisMarkProperty());
6649
0
  if (!info) {
6650
0
    return;
6651
0
  }
6652
0
6653
0
  bool isTextCombined = Style()->IsTextCombined();
6654
0
  nscolor color = aDecorationOverrideColor ? *aDecorationOverrideColor :
6655
0
    nsLayoutUtils::GetColor(this, &nsStyleText::mTextEmphasisColor);
6656
0
  aContext->SetColor(Color::FromABGR(color));
6657
0
  gfx::Point pt;
6658
0
  if (!isTextCombined) {
6659
0
    pt = aTextBaselinePt;
6660
0
  } else {
6661
0
    MOZ_ASSERT(aWM.IsVertical());
6662
0
    pt = aFramePt;
6663
0
    if (aWM.IsVerticalRL()) {
6664
0
      pt.x += GetSize().width - GetLogicalBaseline(aWM);
6665
0
    } else {
6666
0
      pt.x += GetLogicalBaseline(aWM);
6667
0
    }
6668
0
  }
6669
0
  if (!aWM.IsVertical()) {
6670
0
    pt.y += info->baselineOffset;
6671
0
  } else {
6672
0
    if (aWM.IsVerticalRL()) {
6673
0
      pt.x -= info->baselineOffset;
6674
0
    } else {
6675
0
      pt.x += info->baselineOffset;
6676
0
    }
6677
0
  }
6678
0
  if (!isTextCombined) {
6679
0
    mTextRun->DrawEmphasisMarks(aContext, info->textRun.get(),
6680
0
                                info->advance, pt, aRange, aProvider);
6681
0
  } else {
6682
0
    pt.y += (GetSize().height - info->advance) / 2;
6683
0
    gfxTextRun::DrawParams params(aContext);
6684
0
    info->textRun->Draw(Range(info->textRun.get()), pt,
6685
0
                        params);
6686
0
  }
6687
0
}
6688
6689
nscolor
6690
nsTextFrame::GetCaretColorAt(int32_t aOffset)
6691
0
{
6692
0
  MOZ_ASSERT(aOffset >= 0, "aOffset must be positive");
6693
0
6694
0
  nscolor result = nsFrame::GetCaretColorAt(aOffset);
6695
0
  gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
6696
0
  PropertyProvider provider(this, iter, nsTextFrame::eInflated);
6697
0
  int32_t contentOffset = provider.GetStart().GetOriginalOffset();
6698
0
  int32_t contentLength = provider.GetOriginalLength();
6699
0
  MOZ_ASSERT(aOffset >= contentOffset &&
6700
0
             aOffset <= contentOffset + contentLength,
6701
0
             "aOffset must be in the frame's range");
6702
0
6703
0
  int32_t offsetInFrame = aOffset - contentOffset;
6704
0
  if (offsetInFrame < 0 || offsetInFrame >= contentLength) {
6705
0
    return result;
6706
0
  }
6707
0
6708
0
  bool isSolidTextColor = true;
6709
0
  if (nsSVGUtils::IsInSVGTextSubtree(this)) {
6710
0
    const nsStyleSVG* style = StyleSVG();
6711
0
    if (style->mFill.Type() != eStyleSVGPaintType_None &&
6712
0
        style->mFill.Type() != eStyleSVGPaintType_Color) {
6713
0
      isSolidTextColor = false;
6714
0
    }
6715
0
  }
6716
0
6717
0
  nsTextPaintStyle textPaintStyle(this);
6718
0
  textPaintStyle.SetResolveColors(isSolidTextColor);
6719
0
  UniquePtr<SelectionDetails> details = GetSelectionDetails();
6720
0
  SelectionType selectionType = SelectionType::eNone;
6721
0
  for (SelectionDetails* sdptr = details.get(); sdptr; sdptr = sdptr->mNext.get()) {
6722
0
    int32_t start = std::max(0, sdptr->mStart - contentOffset);
6723
0
    int32_t end = std::min(contentLength, sdptr->mEnd - contentOffset);
6724
0
    if (start <= offsetInFrame && offsetInFrame < end &&
6725
0
        (selectionType == SelectionType::eNone ||
6726
0
         sdptr->mSelectionType < selectionType)) {
6727
0
      nscolor foreground, background;
6728
0
      if (GetSelectionTextColors(sdptr->mSelectionType, textPaintStyle,
6729
0
                                 sdptr->mTextRangeStyle,
6730
0
                                 &foreground, &background)) {
6731
0
        if (!isSolidTextColor &&
6732
0
            NS_IS_SELECTION_SPECIAL_COLOR(foreground)) {
6733
0
          result = NS_RGBA(0, 0, 0, 255);
6734
0
        } else {
6735
0
          result = foreground;
6736
0
        }
6737
0
        selectionType = sdptr->mSelectionType;
6738
0
      }
6739
0
    }
6740
0
  }
6741
0
6742
0
  return result;
6743
0
}
6744
6745
static gfxTextRun::Range
6746
ComputeTransformedRange(PropertyProvider& aProvider)
6747
0
{
6748
0
  gfxSkipCharsIterator iter(aProvider.GetStart());
6749
0
  uint32_t start = iter.GetSkippedOffset();
6750
0
  iter.AdvanceOriginal(aProvider.GetOriginalLength());
6751
0
  return gfxTextRun::Range(start, iter.GetSkippedOffset());
6752
0
}
6753
6754
bool
6755
nsTextFrame::MeasureCharClippedText(nscoord aVisIStartEdge,
6756
                                    nscoord aVisIEndEdge,
6757
                                    nscoord* aSnappedStartEdge,
6758
                                    nscoord* aSnappedEndEdge)
6759
0
{
6760
0
  // We need a *reference* rendering context (not one that might have a
6761
0
  // transform), so we don't have a rendering context argument.
6762
0
  // XXX get the block and line passed to us somehow! This is slow!
6763
0
  gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
6764
0
  if (!mTextRun)
6765
0
    return false;
6766
0
6767
0
  PropertyProvider provider(this, iter, nsTextFrame::eInflated);
6768
0
  // Trim trailing whitespace
6769
0
  provider.InitializeForDisplay(true);
6770
0
6771
0
  Range range = ComputeTransformedRange(provider);
6772
0
  uint32_t startOffset = range.start;
6773
0
  uint32_t maxLength = range.Length();
6774
0
  return MeasureCharClippedText(provider, aVisIStartEdge, aVisIEndEdge,
6775
0
                                &startOffset, &maxLength,
6776
0
                                aSnappedStartEdge, aSnappedEndEdge);
6777
0
}
6778
6779
static uint32_t GetClusterLength(const gfxTextRun* aTextRun,
6780
                                 uint32_t    aStartOffset,
6781
                                 uint32_t    aMaxLength,
6782
                                 bool        aIsRTL)
6783
0
{
6784
0
  uint32_t clusterLength = aIsRTL ? 0 : 1;
6785
0
  while (clusterLength < aMaxLength) {
6786
0
    if (aTextRun->IsClusterStart(aStartOffset + clusterLength)) {
6787
0
      if (aIsRTL) {
6788
0
        ++clusterLength;
6789
0
      }
6790
0
      break;
6791
0
    }
6792
0
    ++clusterLength;
6793
0
  }
6794
0
  return clusterLength;
6795
0
}
6796
6797
bool
6798
nsTextFrame::MeasureCharClippedText(PropertyProvider& aProvider,
6799
                                    nscoord aVisIStartEdge,
6800
                                    nscoord aVisIEndEdge,
6801
                                    uint32_t* aStartOffset,
6802
                                    uint32_t* aMaxLength,
6803
                                    nscoord*  aSnappedStartEdge,
6804
                                    nscoord*  aSnappedEndEdge)
6805
0
{
6806
0
  *aSnappedStartEdge = 0;
6807
0
  *aSnappedEndEdge = 0;
6808
0
  if (aVisIStartEdge <= 0 && aVisIEndEdge <= 0) {
6809
0
    return true;
6810
0
  }
6811
0
6812
0
  uint32_t offset = *aStartOffset;
6813
0
  uint32_t maxLength = *aMaxLength;
6814
0
  const nscoord frameISize = ISize();
6815
0
  const bool rtl = mTextRun->IsRightToLeft();
6816
0
  gfxFloat advanceWidth = 0;
6817
0
  const nscoord startEdge = rtl ? aVisIEndEdge : aVisIStartEdge;
6818
0
  if (startEdge > 0) {
6819
0
    const gfxFloat maxAdvance = gfxFloat(startEdge);
6820
0
    while (maxLength > 0) {
6821
0
      uint32_t clusterLength =
6822
0
        GetClusterLength(mTextRun, offset, maxLength, rtl);
6823
0
      advanceWidth += mTextRun->
6824
0
        GetAdvanceWidth(Range(offset, offset + clusterLength), &aProvider);
6825
0
      maxLength -= clusterLength;
6826
0
      offset += clusterLength;
6827
0
      if (advanceWidth >= maxAdvance) {
6828
0
        break;
6829
0
      }
6830
0
    }
6831
0
    nscoord* snappedStartEdge = rtl ? aSnappedEndEdge : aSnappedStartEdge;
6832
0
    *snappedStartEdge = NSToCoordFloor(advanceWidth);
6833
0
    *aStartOffset = offset;
6834
0
  }
6835
0
6836
0
  const nscoord endEdge = rtl ? aVisIStartEdge : aVisIEndEdge;
6837
0
  if (endEdge > 0) {
6838
0
    const gfxFloat maxAdvance = gfxFloat(frameISize - endEdge);
6839
0
    while (maxLength > 0) {
6840
0
      uint32_t clusterLength =
6841
0
        GetClusterLength(mTextRun, offset, maxLength, rtl);
6842
0
      gfxFloat nextAdvance = advanceWidth + mTextRun->GetAdvanceWidth(
6843
0
          Range(offset, offset + clusterLength), &aProvider);
6844
0
      if (nextAdvance > maxAdvance) {
6845
0
        break;
6846
0
      }
6847
0
      // This cluster fits, include it.
6848
0
      advanceWidth = nextAdvance;
6849
0
      maxLength -= clusterLength;
6850
0
      offset += clusterLength;
6851
0
    }
6852
0
    maxLength = offset - *aStartOffset;
6853
0
    nscoord* snappedEndEdge = rtl ? aSnappedStartEdge : aSnappedEndEdge;
6854
0
    *snappedEndEdge = NSToCoordFloor(gfxFloat(frameISize) - advanceWidth);
6855
0
  }
6856
0
  *aMaxLength = maxLength;
6857
0
  return maxLength != 0;
6858
0
}
6859
6860
void
6861
nsTextFrame::PaintShadows(nsCSSShadowArray* aShadow,
6862
                          const PaintShadowParams& aParams)
6863
0
{
6864
0
  if (!aShadow) {
6865
0
    return;
6866
0
  }
6867
0
6868
0
  gfxTextRun::Metrics shadowMetrics =
6869
0
    mTextRun->MeasureText(aParams.range, gfxFont::LOOSE_INK_EXTENTS,
6870
0
                          nullptr, aParams.provider);
6871
0
  if (GetWritingMode().IsLineInverted()) {
6872
0
    Swap(shadowMetrics.mAscent, shadowMetrics.mDescent);
6873
0
    shadowMetrics.mBoundingBox.y = -shadowMetrics.mBoundingBox.YMost();
6874
0
  }
6875
0
  if (GetStateBits() & TEXT_HYPHEN_BREAK) {
6876
0
    AddHyphenToMetrics(this, mTextRun, &shadowMetrics,
6877
0
                       gfxFont::LOOSE_INK_EXTENTS,
6878
0
                       aParams.context->GetDrawTarget());
6879
0
  }
6880
0
  // Add bounds of text decorations
6881
0
  gfxRect decorationRect(0, -shadowMetrics.mAscent,
6882
0
      shadowMetrics.mAdvanceWidth, shadowMetrics.mAscent + shadowMetrics.mDescent);
6883
0
  shadowMetrics.mBoundingBox.UnionRect(shadowMetrics.mBoundingBox,
6884
0
                                       decorationRect);
6885
0
6886
0
  // If the textrun uses any color or SVG fonts, we need to force use of a mask
6887
0
  // for shadow rendering even if blur radius is zero.
6888
0
  // Force disable hardware acceleration for text shadows since it's usually
6889
0
  // more expensive than just doing it on the CPU.
6890
0
  uint32_t blurFlags = nsContextBoxBlur::DISABLE_HARDWARE_ACCELERATION_BLUR;
6891
0
  uint32_t numGlyphRuns;
6892
0
  const gfxTextRun::GlyphRun* run = mTextRun->GetGlyphRuns(&numGlyphRuns);
6893
0
  while (numGlyphRuns-- > 0) {
6894
0
    if (run->mFont->AlwaysNeedsMaskForShadow()) {
6895
0
      blurFlags |= nsContextBoxBlur::FORCE_MASK;
6896
0
      break;
6897
0
    }
6898
0
    run++;
6899
0
  }
6900
0
6901
0
  if (mTextRun->IsVertical()) {
6902
0
    Swap(shadowMetrics.mBoundingBox.x, shadowMetrics.mBoundingBox.y);
6903
0
    Swap(shadowMetrics.mBoundingBox.width, shadowMetrics.mBoundingBox.height);
6904
0
  }
6905
0
6906
0
  for (uint32_t i = aShadow->Length(); i > 0; --i) {
6907
0
    PaintOneShadow(aParams, aShadow->ShadowAt(i - 1),
6908
0
                   shadowMetrics.mBoundingBox, blurFlags);
6909
0
  }
6910
0
}
6911
6912
static bool
6913
ShouldDrawSelection(const nsIFrame* aFrame)
6914
0
{
6915
0
  // Normal text-with-selection rendering sequence is:
6916
0
  //   * Paint background > Paint text-selection-color > Paint text
6917
0
  // When we have an parent frame with background-clip-text style, rendering
6918
0
  // sequence changes to:
6919
0
  //   * Paint text-selection-color > Paint background > Paint text
6920
0
  //
6921
0
  // If there is a parent frame has background-clip:text style,
6922
0
  // text-selection-color should be drawn with the background of that parent
6923
0
  // frame, so we should not draw it again while painting text frames.
6924
0
6925
0
  if (!aFrame) {
6926
0
    return true;
6927
0
  }
6928
0
6929
0
  const nsStyleBackground* bg = aFrame->Style()->StyleBackground();
6930
0
  const nsStyleImageLayers& layers = bg->mImage;
6931
0
  NS_FOR_VISIBLE_IMAGE_LAYERS_BACK_TO_FRONT(i, layers) {
6932
0
    if (layers.mLayers[i].mClip == StyleGeometryBox::Text) {
6933
0
      return false;
6934
0
    }
6935
0
  }
6936
0
6937
0
  return ShouldDrawSelection(aFrame->GetParent());
6938
0
}
6939
6940
void
6941
nsTextFrame::PaintText(const PaintTextParams& aParams,
6942
                       const nsCharClipDisplayItem& aItem,
6943
                       float aOpacity /* = 1.0f */)
6944
0
{
6945
0
  // Don't pass in the rendering context here, because we need a
6946
0
  // *reference* context and rendering context might have some transform
6947
0
  // in it
6948
0
  // XXX get the block and line passed to us somehow! This is slow!
6949
0
  gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
6950
0
  if (!mTextRun)
6951
0
    return;
6952
0
6953
0
  PropertyProvider provider(this, iter, nsTextFrame::eInflated);
6954
0
  if (aItem.mIsFrameSelected.isNothing()) {
6955
0
    aItem.mIsFrameSelected.emplace(IsSelected());
6956
0
  }
6957
0
  // Trim trailing whitespace, unless we're painting a selection highlight,
6958
0
  // which should include trailing spaces if present (bug 1146754).
6959
0
  provider.InitializeForDisplay(!aItem.mIsFrameSelected.value());
6960
0
6961
0
  const bool reversed = mTextRun->IsInlineReversed();
6962
0
  const bool verticalRun = mTextRun->IsVertical();
6963
0
  WritingMode wm = GetWritingMode();
6964
0
  const float frameWidth = GetSize().width;
6965
0
  const float frameHeight = GetSize().height;
6966
0
  gfx::Point textBaselinePt;
6967
0
  if (verticalRun) {
6968
0
    if (wm.IsVerticalLR()) {
6969
0
      textBaselinePt.x = nsLayoutUtils::GetSnappedBaselineX(
6970
0
        this, aParams.context, nscoord(aParams.framePt.x), mAscent);
6971
0
    } else {
6972
0
      textBaselinePt.x = nsLayoutUtils::GetSnappedBaselineX(
6973
0
        this, aParams.context, nscoord(aParams.framePt.x) + frameWidth,
6974
0
        -mAscent);
6975
0
    }
6976
0
    textBaselinePt.y = reversed ? aParams.framePt.y + frameHeight
6977
0
                                : aParams.framePt.y;
6978
0
  } else {
6979
0
    textBaselinePt =
6980
0
      gfx::Point(reversed ? aParams.framePt.x + frameWidth : aParams.framePt.x,
6981
0
                 nsLayoutUtils::GetSnappedBaselineY(
6982
0
                   this, aParams.context, aParams.framePt.y, mAscent));
6983
0
  }
6984
0
  Range range = ComputeTransformedRange(provider);
6985
0
  uint32_t startOffset = range.start;
6986
0
  uint32_t maxLength = range.Length();
6987
0
  nscoord snappedStartEdge, snappedEndEdge;
6988
0
  if (!MeasureCharClippedText(provider, aItem.mVisIStartEdge, aItem.mVisIEndEdge,
6989
0
         &startOffset, &maxLength, &snappedStartEdge, &snappedEndEdge)) {
6990
0
    return;
6991
0
  }
6992
0
  if (verticalRun) {
6993
0
    textBaselinePt.y += reversed ? -snappedEndEdge : snappedStartEdge;
6994
0
  } else {
6995
0
    textBaselinePt.x += reversed ? -snappedEndEdge : snappedStartEdge;
6996
0
  }
6997
0
  nsCharClipDisplayItem::ClipEdges clipEdges(aItem, snappedStartEdge,
6998
0
                                             snappedEndEdge);
6999
0
  nsTextPaintStyle textPaintStyle(this);
7000
0
  textPaintStyle.SetResolveColors(!aParams.callbacks);
7001
0
7002
0
  // Fork off to the (slower) paint-with-selection path if necessary.
7003
0
  if (aItem.mIsFrameSelected.value() &&
7004
0
      (aParams.IsPaintBGColor() || ShouldDrawSelection(this->GetParent()))) {
7005
0
    MOZ_ASSERT(aOpacity == 1.0f, "We don't support opacity with selections!");
7006
0
    gfxSkipCharsIterator tmp(provider.GetStart());
7007
0
    Range contentRange(
7008
0
      uint32_t(tmp.ConvertSkippedToOriginal(startOffset)),
7009
0
      uint32_t(tmp.ConvertSkippedToOriginal(startOffset + maxLength)));
7010
0
    PaintTextSelectionParams params(aParams);
7011
0
    params.textBaselinePt = textBaselinePt;
7012
0
    params.provider = &provider;
7013
0
    params.contentRange = contentRange;
7014
0
    params.textPaintStyle = &textPaintStyle;
7015
0
    if (PaintTextWithSelection(params, clipEdges)) {
7016
0
      return;
7017
0
    }
7018
0
  }
7019
0
7020
0
  if (aParams.IsPaintBGColor()) {
7021
0
    return;
7022
0
  }
7023
0
7024
0
  nscolor foregroundColor = aParams.IsGenerateTextMask()
7025
0
                            ? NS_RGBA(0, 0, 0, 255)
7026
0
                            : textPaintStyle.GetTextColor();
7027
0
  if (aOpacity != 1.0f) {
7028
0
    gfx::Color gfxColor = gfx::Color::FromABGR(foregroundColor);
7029
0
    gfxColor.a *= aOpacity;
7030
0
    foregroundColor = gfxColor.ToABGR();
7031
0
  }
7032
0
7033
0
  nscolor textStrokeColor = aParams.IsGenerateTextMask()
7034
0
                            ? NS_RGBA(0, 0, 0, 255)
7035
0
                            : textPaintStyle.GetWebkitTextStrokeColor();
7036
0
  if (aOpacity != 1.0f) {
7037
0
    gfx::Color gfxColor = gfx::Color::FromABGR(textStrokeColor);
7038
0
    gfxColor.a *= aOpacity;
7039
0
    textStrokeColor = gfxColor.ToABGR();
7040
0
  }
7041
0
7042
0
  range = Range(startOffset, startOffset + maxLength);
7043
0
  if (!aParams.callbacks && aParams.IsPaintText()) {
7044
0
    const nsStyleText* textStyle = StyleText();
7045
0
    PaintShadowParams shadowParams(aParams);
7046
0
    shadowParams.range = range;
7047
0
    shadowParams.textBaselinePt = textBaselinePt;
7048
0
    shadowParams.leftSideOffset = snappedStartEdge;
7049
0
    shadowParams.provider = &provider;
7050
0
    shadowParams.foregroundColor = foregroundColor;
7051
0
    shadowParams.clipEdges = &clipEdges;
7052
0
    PaintShadows(textStyle->mTextShadow, shadowParams);
7053
0
  }
7054
0
7055
0
  gfxFloat advanceWidth;
7056
0
  DrawTextParams params(aParams.context);
7057
0
  params.dirtyRect = aParams.dirtyRect;
7058
0
  params.framePt = aParams.framePt;
7059
0
  params.provider = &provider;
7060
0
  params.advanceWidth = &advanceWidth;
7061
0
  params.textStyle = &textPaintStyle;
7062
0
  params.textColor = foregroundColor;
7063
0
  params.textStrokeColor = textStrokeColor;
7064
0
  params.textStrokeWidth = textPaintStyle.GetWebkitTextStrokeWidth();
7065
0
  params.clipEdges = &clipEdges;
7066
0
  params.drawSoftHyphen = (GetStateBits() & TEXT_HYPHEN_BREAK) != 0;
7067
0
  params.contextPaint = aParams.contextPaint;
7068
0
  params.callbacks = aParams.callbacks;
7069
0
  DrawText(range, textBaselinePt, params);
7070
0
}
7071
7072
static void
7073
DrawTextRun(const gfxTextRun* aTextRun,
7074
            const gfx::Point& aTextBaselinePt,
7075
            gfxTextRun::Range aRange,
7076
            const nsTextFrame::DrawTextRunParams& aParams,
7077
            nsTextFrame* aFrame)
7078
0
{
7079
0
  gfxTextRun::DrawParams params(aParams.context);
7080
0
  params.provider = aParams.provider;
7081
0
  params.advanceWidth = aParams.advanceWidth;
7082
0
  params.contextPaint = aParams.contextPaint;
7083
0
  params.callbacks = aParams.callbacks;
7084
0
  if (aParams.callbacks) {
7085
0
    aParams.callbacks->NotifyBeforeText(aParams.textColor);
7086
0
    params.drawMode = DrawMode::GLYPH_PATH;
7087
0
    aTextRun->Draw(aRange, aTextBaselinePt, params);
7088
0
    aParams.callbacks->NotifyAfterText();
7089
0
  } else {
7090
0
    auto* textDrawer = aParams.context->GetTextDrawer();
7091
0
    if (NS_GET_A(aParams.textColor) != 0 || textDrawer) {
7092
0
      aParams.context->SetColor(Color::FromABGR(aParams.textColor));
7093
0
    } else {
7094
0
      params.drawMode = DrawMode::GLYPH_STROKE;
7095
0
    }
7096
0
7097
0
    if ((NS_GET_A(aParams.textStrokeColor) != 0 || textDrawer) &&
7098
0
        aParams.textStrokeWidth != 0.0f) {
7099
0
      if (textDrawer) {
7100
0
        textDrawer->FoundUnsupportedFeature();
7101
0
        return;
7102
0
      }
7103
0
      params.drawMode |= DrawMode::GLYPH_STROKE;
7104
0
      if (gfxPrefs::PaintOrderEnabled()) {
7105
0
        // Check the paint-order property; if we find stroke before fill,
7106
0
        // then change mode to GLYPH_STROKE_UNDERNEATH.
7107
0
        uint32_t paintOrder = aFrame->StyleSVG()->mPaintOrder;
7108
0
        if (paintOrder != NS_STYLE_PAINT_ORDER_NORMAL) {
7109
0
          while (paintOrder) {
7110
0
            uint32_t component =
7111
0
              paintOrder & ((1 << NS_STYLE_PAINT_ORDER_BITWIDTH) - 1);
7112
0
            switch (component) {
7113
0
            case NS_STYLE_PAINT_ORDER_FILL:
7114
0
              // Just break the loop, no need to check further
7115
0
              paintOrder = 0;
7116
0
              break;
7117
0
            case NS_STYLE_PAINT_ORDER_STROKE:
7118
0
              params.drawMode |= DrawMode::GLYPH_STROKE_UNDERNEATH;
7119
0
              paintOrder = 0;
7120
0
              break;
7121
0
            }
7122
0
            paintOrder >>= NS_STYLE_PAINT_ORDER_BITWIDTH;
7123
0
          }
7124
0
        }
7125
0
      }
7126
0
      StrokeOptions strokeOpts;
7127
0
      params.textStrokeColor = aParams.textStrokeColor;
7128
0
      strokeOpts.mLineWidth = aParams.textStrokeWidth;
7129
0
      params.strokeOpts = &strokeOpts;
7130
0
      aTextRun->Draw(aRange, aTextBaselinePt, params);
7131
0
    } else {
7132
0
      aTextRun->Draw(aRange, aTextBaselinePt, params);
7133
0
    }
7134
0
  }
7135
0
}
7136
7137
void
7138
nsTextFrame::DrawTextRun(Range aRange, const gfx::Point& aTextBaselinePt,
7139
                         const DrawTextRunParams& aParams)
7140
0
{
7141
0
  MOZ_ASSERT(aParams.advanceWidth, "Must provide advanceWidth");
7142
0
7143
0
  ::DrawTextRun(mTextRun, aTextBaselinePt, aRange, aParams, this);
7144
0
7145
0
  if (aParams.drawSoftHyphen) {
7146
0
    // Don't use ctx as the context, because we need a reference context here,
7147
0
    // ctx may be transformed.
7148
0
    RefPtr<gfxTextRun> hyphenTextRun =
7149
0
      GetHyphenTextRun(mTextRun, nullptr, this);
7150
0
    if (hyphenTextRun) {
7151
0
      // For right-to-left text runs, the soft-hyphen is positioned at the left
7152
0
      // of the text, minus its own width
7153
0
      float hyphenBaselineX = aTextBaselinePt.x +
7154
0
        mTextRun->GetDirection() * (*aParams.advanceWidth) -
7155
0
        (mTextRun->IsRightToLeft() ? hyphenTextRun->GetAdvanceWidth() : 0);
7156
0
      DrawTextRunParams params = aParams;
7157
0
      params.provider = nullptr;
7158
0
      params.advanceWidth = nullptr;
7159
0
      ::DrawTextRun(hyphenTextRun.get(),
7160
0
                    gfx::Point(hyphenBaselineX, aTextBaselinePt.y),
7161
0
                    Range(hyphenTextRun.get()), params, this);
7162
0
    }
7163
0
  }
7164
0
}
7165
7166
void
7167
nsTextFrame::DrawTextRunAndDecorations(Range aRange,
7168
                                       const gfx::Point& aTextBaselinePt,
7169
                                       const DrawTextParams& aParams,
7170
                                       const TextDecorations& aDecorations)
7171
0
{
7172
0
    const gfxFloat app =
7173
0
      aParams.textStyle->PresContext()->AppUnitsPerDevPixel();
7174
0
    // Writing mode of parent frame is used because the text frame may
7175
0
    // be orthogonal to its parent when text-combine-upright is used or
7176
0
    // its parent has "display: contents", and in those cases, we want
7177
0
    // to draw the decoration lines according to parents' direction
7178
0
    // rather than ours.
7179
0
    const WritingMode wm = GetParent()->GetWritingMode();
7180
0
    bool verticalDec = wm.IsVertical();
7181
0
    bool verticalRun = mTextRun->IsVertical();
7182
0
    // If the text run and the decoration is orthogonal, we choose the
7183
0
    // metrics for decoration so that decoration line won't be broken.
7184
0
    bool useVerticalMetrics = verticalDec != verticalRun
7185
0
      ? verticalDec : verticalRun && mTextRun->UseCenterBaseline();
7186
0
7187
0
    // XXX aFramePt is in AppUnits, shouldn't it be nsFloatPoint?
7188
0
    nscoord x = NSToCoordRound(aParams.framePt.x);
7189
0
    nscoord y = NSToCoordRound(aParams.framePt.y);
7190
0
7191
0
    // 'measure' here is textrun-relative, so for a horizontal run it's the
7192
0
    // width, while for a vertical run it's the height of the decoration
7193
0
    const nsSize frameSize = GetSize();
7194
0
    nscoord measure = verticalDec ? frameSize.height : frameSize.width;
7195
0
7196
0
    if (verticalDec) {
7197
0
      aParams.clipEdges->Intersect(&y, &measure);
7198
0
    } else {
7199
0
      aParams.clipEdges->Intersect(&x, &measure);
7200
0
    }
7201
0
7202
0
    // decSize is a textrun-relative size, so its 'width' field is actually
7203
0
    // the run-relative measure, and 'height' will be the line thickness
7204
0
    gfxFloat ascent = gfxFloat(GetLogicalBaseline(wm)) / app;
7205
0
    // The starting edge of the frame in block direction
7206
0
    gfxFloat frameBStart = verticalDec ? aParams.framePt.x : aParams.framePt.y;
7207
0
7208
0
    // In vertical-rl mode, block coordinates are measured from the
7209
0
    // right, so we need to adjust here.
7210
0
    if (wm.IsVerticalRL()) {
7211
0
      frameBStart += frameSize.width;
7212
0
      ascent = -ascent;
7213
0
    }
7214
0
7215
0
    nscoord inflationMinFontSize =
7216
0
      nsLayoutUtils::InflationMinFontSizeFor(this);
7217
0
7218
0
    PaintDecorationLineParams params;
7219
0
    params.context = aParams.context;
7220
0
    params.dirtyRect = aParams.dirtyRect;
7221
0
    params.overrideColor = aParams.decorationOverrideColor;
7222
0
    params.callbacks = aParams.callbacks;
7223
0
    // pt is the physical point where the decoration is to be drawn,
7224
0
    // relative to the frame; one of its coordinates will be updated below.
7225
0
    params.pt = Point(x / app, y / app);
7226
0
    Float& bCoord = verticalDec ? params.pt.x : params.pt.y;
7227
0
    params.lineSize = Size(measure / app, 0);
7228
0
    params.ascent = ascent;
7229
0
    params.vertical = verticalDec;
7230
0
    params.sidewaysLeft = mTextRun->IsSidewaysLeft();
7231
0
7232
0
    // The matrix of the context may have been altered for text-combine-
7233
0
    // upright. However, we want to draw decoration lines unscaled, thus
7234
0
    // we need to revert the scaling here.
7235
0
    gfxContextMatrixAutoSaveRestore scaledRestorer;
7236
0
    if (Style()->IsTextCombined()) {
7237
0
      float scaleFactor = GetTextCombineScaleFactor(this);
7238
0
      if (scaleFactor != 1.0f) {
7239
0
        scaledRestorer.SetContext(aParams.context);
7240
0
        gfxMatrix unscaled = aParams.context->CurrentMatrixDouble();
7241
0
        gfxPoint pt(x / app, y / app);
7242
0
        unscaled.PreTranslate(pt).PreScale(1.0f / scaleFactor, 1.0f).PreTranslate(-pt);
7243
0
        aParams.context->SetMatrixDouble(unscaled);
7244
0
      }
7245
0
    }
7246
0
7247
0
    typedef gfxFont::Metrics Metrics;
7248
0
    auto paintDecorationLine = [&](const LineDecoration& dec,
7249
0
                                   gfxFloat Metrics::* lineSize,
7250
0
                                   gfxFloat Metrics::* lineOffset) {
7251
0
      if (dec.mStyle == NS_STYLE_TEXT_DECORATION_STYLE_NONE) {
7252
0
        return;
7253
0
      }
7254
0
7255
0
      float inflation =
7256
0
        GetInflationForTextDecorations(dec.mFrame, inflationMinFontSize);
7257
0
      const Metrics metrics =
7258
0
        GetFirstFontMetrics(GetFontGroupForFrame(dec.mFrame, inflation),
7259
0
                            useVerticalMetrics);
7260
0
7261
0
      params.lineSize.height = metrics.*lineSize;
7262
0
      bCoord = (frameBStart - dec.mBaselineOffset) / app;
7263
0
7264
0
      params.color = dec.mColor;
7265
0
      params.offset = metrics.*lineOffset;
7266
0
      params.style = dec.mStyle;
7267
0
      PaintDecorationLine(params);
7268
0
    };
7269
0
7270
0
    // We create a clip region in order to draw the decoration lines only in the
7271
0
    // range of the text. Restricting the draw area prevents the decoration lines
7272
0
    // to be drawn multiple times when a part of the text is selected.
7273
0
7274
0
    // We skip clipping for the following cases:
7275
0
    // - drawing the whole text
7276
0
    // - having different orientation of the text and the writing-mode, such as
7277
0
    //   "text-combine-upright" (Bug 1408825)
7278
0
    bool skipClipping = aRange.Length() == mTextRun->GetLength() ||
7279
0
                        verticalDec != verticalRun;
7280
0
7281
0
    gfxRect clipRect;
7282
0
    if (!skipClipping) {
7283
0
      // Get the inline-size according to the specified range.
7284
0
      gfxFloat clipLength = mTextRun->GetAdvanceWidth(aRange, aParams.provider);
7285
0
      nsRect visualRect = GetVisualOverflowRect();
7286
0
7287
0
      const bool isInlineReversed = mTextRun->IsInlineReversed();
7288
0
      if (verticalDec) {
7289
0
        clipRect.x = aParams.framePt.x + visualRect.x;
7290
0
        clipRect.y = isInlineReversed ? aTextBaselinePt.y - clipLength
7291
0
                                      : aTextBaselinePt.y;
7292
0
        clipRect.width = visualRect.width;
7293
0
        clipRect.height = clipLength;
7294
0
      } else {
7295
0
        clipRect.x = isInlineReversed ? aTextBaselinePt.x - clipLength
7296
0
                                      : aTextBaselinePt.x;
7297
0
        clipRect.y = aParams.framePt.y + visualRect.y;
7298
0
        clipRect.width = clipLength;
7299
0
        clipRect.height = visualRect.height;
7300
0
      }
7301
0
7302
0
      clipRect.Scale(1 / app);
7303
0
      clipRect.Round();
7304
0
      params.context->Clip(clipRect);
7305
0
    }
7306
0
7307
0
    // Underlines
7308
0
    params.decoration = NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE;
7309
0
    for (const LineDecoration& dec : Reversed(aDecorations.mUnderlines)) {
7310
0
      paintDecorationLine(dec, &Metrics::underlineSize,
7311
0
                          &Metrics::underlineOffset);
7312
0
    }
7313
0
7314
0
    // Overlines
7315
0
    params.decoration = NS_STYLE_TEXT_DECORATION_LINE_OVERLINE;
7316
0
    for (const LineDecoration& dec : Reversed(aDecorations.mOverlines)) {
7317
0
      paintDecorationLine(dec, &Metrics::underlineSize, &Metrics::maxAscent);
7318
0
    }
7319
0
7320
0
    // Some glyphs and emphasis marks may extend outside the region, so we reset
7321
0
    // the clip region here. For an example, italic glyphs.
7322
0
    if (!skipClipping) {
7323
0
      params.context->PopClip();
7324
0
    }
7325
0
7326
0
    {
7327
0
      gfxContextMatrixAutoSaveRestore unscaledRestorer;
7328
0
      if (scaledRestorer.HasMatrix()) {
7329
0
        unscaledRestorer.SetContext(aParams.context);
7330
0
        aParams.context->SetMatrix(scaledRestorer.Matrix());
7331
0
      }
7332
0
7333
0
      // CSS 2.1 mandates that text be painted after over/underlines,
7334
0
      // and *then* line-throughs
7335
0
      DrawTextRun(aRange, aTextBaselinePt, aParams);
7336
0
    }
7337
0
7338
0
    // Emphasis marks
7339
0
    DrawEmphasisMarks(aParams.context, wm,
7340
0
                      aTextBaselinePt, aParams.framePt, aRange,
7341
0
                      aParams.decorationOverrideColor, aParams.provider);
7342
0
7343
0
    // Re-apply the clip region when the line-through is being drawn.
7344
0
    if (!skipClipping) {
7345
0
      params.context->Clip(clipRect);
7346
0
    }
7347
0
7348
0
    // Line-throughs
7349
0
    params.decoration = NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH;
7350
0
    for (const LineDecoration& dec : Reversed(aDecorations.mStrikes)) {
7351
0
      paintDecorationLine(dec, &Metrics::strikeoutSize,
7352
0
                          &Metrics::strikeoutOffset);
7353
0
    }
7354
0
7355
0
    if (!skipClipping) {
7356
0
      params.context->PopClip();
7357
0
    }
7358
0
}
7359
7360
void
7361
nsTextFrame::DrawText(Range aRange, const gfx::Point& aTextBaselinePt,
7362
                      const DrawTextParams& aParams)
7363
0
{
7364
0
  TextDecorations decorations;
7365
0
  GetTextDecorations(aParams.textStyle->PresContext(),
7366
0
                     aParams.callbacks ? eUnresolvedColors : eResolvedColors,
7367
0
                     decorations);
7368
0
7369
0
  // Hide text decorations if we're currently hiding @font-face fallback text
7370
0
  const bool drawDecorations =
7371
0
    !aParams.provider->GetFontGroup()->ShouldSkipDrawing() &&
7372
0
    (decorations.HasDecorationLines() || StyleText()->HasTextEmphasis());
7373
0
  if (drawDecorations) {
7374
0
    DrawTextRunAndDecorations(aRange, aTextBaselinePt, aParams, decorations);
7375
0
  } else {
7376
0
    DrawTextRun(aRange, aTextBaselinePt, aParams);
7377
0
  }
7378
0
7379
0
  if (auto* textDrawer = aParams.context->GetTextDrawer()) {
7380
0
    textDrawer->TerminateShadows();
7381
0
  }
7382
0
}
7383
7384
int16_t
7385
nsTextFrame::GetSelectionStatus(int16_t* aSelectionFlags)
7386
0
{
7387
0
  // get the selection controller
7388
0
  nsCOMPtr<nsISelectionController> selectionController;
7389
0
  nsresult rv = GetSelectionController(PresContext(),
7390
0
                                       getter_AddRefs(selectionController));
7391
0
  if (NS_FAILED(rv) || !selectionController)
7392
0
    return nsISelectionController::SELECTION_OFF;
7393
0
7394
0
  selectionController->GetSelectionFlags(aSelectionFlags);
7395
0
7396
0
  int16_t selectionValue;
7397
0
  selectionController->GetDisplaySelection(&selectionValue);
7398
0
7399
0
  return selectionValue;
7400
0
}
7401
7402
bool
7403
nsTextFrame::IsVisibleInSelection(Selection* aSelection)
7404
0
{
7405
0
  // Check the quick way first
7406
0
  if (!GetContent()->IsSelectionDescendant())
7407
0
    return false;
7408
0
7409
0
  UniquePtr<SelectionDetails> details = GetSelectionDetails();
7410
0
  bool found = false;
7411
0
7412
0
  // where are the selection points "really"
7413
0
  for (SelectionDetails* sdptr = details.get(); sdptr; sdptr = sdptr->mNext.get()) {
7414
0
    if (sdptr->mEnd > GetContentOffset() &&
7415
0
        sdptr->mStart < GetContentEnd() &&
7416
0
        sdptr->mSelectionType == SelectionType::eNormal) {
7417
0
      found = true;
7418
0
      break;
7419
0
    }
7420
0
  }
7421
0
7422
0
  return found;
7423
0
}
7424
7425
/**
7426
 * Compute the longest prefix of text whose width is <= aWidth. Return
7427
 * the length of the prefix. Also returns the width of the prefix in aFitWidth.
7428
 */
7429
static uint32_t
7430
CountCharsFit(const gfxTextRun* aTextRun, gfxTextRun::Range aRange,
7431
              gfxFloat aWidth, PropertyProvider* aProvider,
7432
              gfxFloat* aFitWidth)
7433
0
{
7434
0
  uint32_t last = 0;
7435
0
  gfxFloat width = 0;
7436
0
  for (uint32_t i = 1; i <= aRange.Length(); ++i) {
7437
0
    if (i == aRange.Length() || aTextRun->IsClusterStart(aRange.start + i)) {
7438
0
      gfxTextRun::Range range(aRange.start + last, aRange.start + i);
7439
0
      gfxFloat nextWidth = width + aTextRun->GetAdvanceWidth(range, aProvider);
7440
0
      if (nextWidth > aWidth)
7441
0
        break;
7442
0
      last = i;
7443
0
      width = nextWidth;
7444
0
    }
7445
0
  }
7446
0
  *aFitWidth = width;
7447
0
  return last;
7448
0
}
7449
7450
nsIFrame::ContentOffsets
7451
nsTextFrame::CalcContentOffsetsFromFramePoint(const nsPoint& aPoint)
7452
0
{
7453
0
  return GetCharacterOffsetAtFramePointInternal(aPoint, true);
7454
0
}
7455
7456
nsIFrame::ContentOffsets
7457
nsTextFrame::GetCharacterOffsetAtFramePoint(const nsPoint &aPoint)
7458
0
{
7459
0
  return GetCharacterOffsetAtFramePointInternal(aPoint, false);
7460
0
}
7461
7462
nsIFrame::ContentOffsets
7463
nsTextFrame::GetCharacterOffsetAtFramePointInternal(const nsPoint& aPoint,
7464
                                                    bool aForInsertionPoint)
7465
0
{
7466
0
  ContentOffsets offsets;
7467
0
7468
0
  gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
7469
0
  if (!mTextRun)
7470
0
    return offsets;
7471
0
7472
0
  PropertyProvider provider(this, iter, nsTextFrame::eInflated);
7473
0
  // Trim leading but not trailing whitespace if possible
7474
0
  provider.InitializeForDisplay(false);
7475
0
  gfxFloat width = mTextRun->IsVertical()
7476
0
    ? (mTextRun->IsInlineReversed() ? mRect.height - aPoint.y : aPoint.y)
7477
0
    : (mTextRun->IsInlineReversed() ? mRect.width - aPoint.x : aPoint.x);
7478
0
  if (Style()->IsTextCombined()) {
7479
0
    width /= GetTextCombineScaleFactor(this);
7480
0
  }
7481
0
  gfxFloat fitWidth;
7482
0
  Range skippedRange = ComputeTransformedRange(provider);
7483
0
7484
0
  uint32_t charsFit = CountCharsFit(mTextRun, skippedRange,
7485
0
                                    width, &provider, &fitWidth);
7486
0
7487
0
  int32_t selectedOffset;
7488
0
  if (charsFit < skippedRange.Length()) {
7489
0
    // charsFit characters fitted, but no more could fit. See if we're
7490
0
    // more than halfway through the cluster.. If we are, choose the next
7491
0
    // cluster.
7492
0
    gfxSkipCharsIterator extraCluster(provider.GetStart());
7493
0
    extraCluster.AdvanceSkipped(charsFit);
7494
0
7495
0
    bool allowSplitLigature = true; // Allow selection of partial ligature...
7496
0
7497
0
    // ...but don't let selection/insertion-point split two Regional Indicator
7498
0
    // chars that are ligated in the textrun to form a single flag symbol.
7499
0
    uint32_t offs = extraCluster.GetOriginalOffset();
7500
0
    const nsTextFragment* frag = GetContent()->GetText();
7501
0
    if (offs + 1 < frag->GetLength() &&
7502
0
        NS_IS_HIGH_SURROGATE(frag->CharAt(offs)) &&
7503
0
        NS_IS_LOW_SURROGATE(frag->CharAt(offs + 1)) &&
7504
0
        gfxFontUtils::IsRegionalIndicator
7505
0
          (SURROGATE_TO_UCS4(frag->CharAt(offs), frag->CharAt(offs + 1)))) {
7506
0
      allowSplitLigature = false;
7507
0
      if (extraCluster.GetSkippedOffset() > 1 &&
7508
0
          !mTextRun->IsLigatureGroupStart(extraCluster.GetSkippedOffset())) {
7509
0
        // CountCharsFit() left us in the middle of the flag; back up over the
7510
0
        // first character of the ligature, and adjust fitWidth accordingly.
7511
0
        extraCluster.AdvanceSkipped(-2); // it's a surrogate pair: 2 code units
7512
0
        fitWidth -= mTextRun->GetAdvanceWidth(
7513
0
          Range(extraCluster.GetSkippedOffset(),
7514
0
                extraCluster.GetSkippedOffset() + 2), &provider);
7515
0
      }
7516
0
    }
7517
0
7518
0
    gfxSkipCharsIterator extraClusterLastChar(extraCluster);
7519
0
    FindClusterEnd(mTextRun,
7520
0
                   provider.GetStart().GetOriginalOffset() + provider.GetOriginalLength(),
7521
0
                   &extraClusterLastChar, allowSplitLigature);
7522
0
    PropertyProvider::Spacing spacing;
7523
0
    Range extraClusterRange(extraCluster.GetSkippedOffset(),
7524
0
                            extraClusterLastChar.GetSkippedOffset() + 1);
7525
0
    gfxFloat charWidth =
7526
0
        mTextRun->GetAdvanceWidth(extraClusterRange, &provider, &spacing);
7527
0
    charWidth -= spacing.mBefore + spacing.mAfter;
7528
0
    selectedOffset = !aForInsertionPoint ||
7529
0
      width <= fitWidth + spacing.mBefore + charWidth/2
7530
0
        ? extraCluster.GetOriginalOffset()
7531
0
        : extraClusterLastChar.GetOriginalOffset() + 1;
7532
0
  } else {
7533
0
    // All characters fitted, we're at (or beyond) the end of the text.
7534
0
    // XXX This could be some pathological situation where negative spacing
7535
0
    // caused characters to move backwards. We can't really handle that
7536
0
    // in the current frame system because frames can't have negative
7537
0
    // intrinsic widths.
7538
0
    selectedOffset =
7539
0
        provider.GetStart().GetOriginalOffset() + provider.GetOriginalLength();
7540
0
    // If we're at the end of a preformatted line which has a terminating
7541
0
    // linefeed, we want to reduce the offset by one to make sure that the
7542
0
    // selection is placed before the linefeed character.
7543
0
    if (HasSignificantTerminalNewline()) {
7544
0
      --selectedOffset;
7545
0
    }
7546
0
  }
7547
0
7548
0
  offsets.content = GetContent();
7549
0
  offsets.offset = offsets.secondaryOffset = selectedOffset;
7550
0
  offsets.associate =
7551
0
    mContentOffset == offsets.offset ? CARET_ASSOCIATE_AFTER : CARET_ASSOCIATE_BEFORE;
7552
0
  return offsets;
7553
0
}
7554
7555
bool
7556
nsTextFrame::CombineSelectionUnderlineRect(nsPresContext* aPresContext,
7557
                                           nsRect& aRect)
7558
0
{
7559
0
  if (aRect.IsEmpty())
7560
0
    return false;
7561
0
7562
0
  nsRect givenRect = aRect;
7563
0
7564
0
  RefPtr<nsFontMetrics> fm =
7565
0
    nsLayoutUtils::GetFontMetricsForFrame(this, GetFontSizeInflation());
7566
0
  gfxFontGroup* fontGroup = fm->GetThebesFontGroup();
7567
0
  gfxFont* firstFont = fontGroup->GetFirstValidFont();
7568
0
  WritingMode wm = GetWritingMode();
7569
0
  bool verticalRun = wm.IsVertical();
7570
0
  bool useVerticalMetrics = verticalRun && !wm.IsSideways();
7571
0
  const gfxFont::Metrics& metrics =
7572
0
    firstFont->GetMetrics(useVerticalMetrics ? gfxFont::eVertical
7573
0
                                             : gfxFont::eHorizontal);
7574
0
7575
0
  nsCSSRendering::DecorationRectParams params;
7576
0
  params.ascent = aPresContext->AppUnitsToGfxUnits(mAscent);
7577
0
  params.offset = fontGroup->GetUnderlineOffset();
7578
0
  params.descentLimit =
7579
0
    ComputeDescentLimitForSelectionUnderline(aPresContext, metrics);
7580
0
  params.vertical = verticalRun;
7581
0
7582
0
  EnsureTextRun(nsTextFrame::eInflated);
7583
0
  params.sidewaysLeft = mTextRun ? mTextRun->IsSidewaysLeft() : false;
7584
0
7585
0
  UniquePtr<SelectionDetails> details = GetSelectionDetails();
7586
0
  for (SelectionDetails* sd = details.get(); sd; sd = sd->mNext.get()) {
7587
0
    if (sd->mStart == sd->mEnd ||
7588
0
        sd->mSelectionType == SelectionType::eInvalid ||
7589
0
        !(ToSelectionTypeMask(sd->mSelectionType) &
7590
0
            kSelectionTypesWithDecorations) ||
7591
0
        // URL strikeout does not use underline.
7592
0
        sd->mSelectionType == SelectionType::eURLStrikeout) {
7593
0
      continue;
7594
0
    }
7595
0
7596
0
    float relativeSize;
7597
0
    int32_t index =
7598
0
      nsTextPaintStyle::GetUnderlineStyleIndexForSelectionType(
7599
0
        sd->mSelectionType);
7600
0
    if (sd->mSelectionType == SelectionType::eSpellCheck) {
7601
0
      if (!nsTextPaintStyle::GetSelectionUnderline(aPresContext, index, nullptr,
7602
0
                                                   &relativeSize,
7603
0
                                                   &params.style)) {
7604
0
        continue;
7605
0
      }
7606
0
    } else {
7607
0
      // IME selections
7608
0
      TextRangeStyle& rangeStyle = sd->mTextRangeStyle;
7609
0
      if (rangeStyle.IsDefined()) {
7610
0
        if (!rangeStyle.IsLineStyleDefined() ||
7611
0
            rangeStyle.mLineStyle == TextRangeStyle::LINESTYLE_NONE) {
7612
0
          continue;
7613
0
        }
7614
0
        params.style = rangeStyle.mLineStyle;
7615
0
        relativeSize = rangeStyle.mIsBoldLine ? 2.0f : 1.0f;
7616
0
      } else if (!nsTextPaintStyle::GetSelectionUnderline(aPresContext, index,
7617
0
                                                          nullptr, &relativeSize,
7618
0
                                                          &params.style)) {
7619
0
        continue;
7620
0
      }
7621
0
    }
7622
0
    nsRect decorationArea;
7623
0
7624
0
    params.lineSize =
7625
0
      Size(aPresContext->AppUnitsToGfxUnits(aRect.width),
7626
0
           ComputeSelectionUnderlineHeight(aPresContext, metrics,
7627
0
                                           sd->mSelectionType));
7628
0
    relativeSize = std::max(relativeSize, 1.0f);
7629
0
    params.lineSize.height *= relativeSize;
7630
0
    decorationArea =
7631
0
      nsCSSRendering::GetTextDecorationRect(aPresContext, params);
7632
0
    aRect.UnionRect(aRect, decorationArea);
7633
0
  }
7634
0
7635
0
  return !aRect.IsEmpty() && !givenRect.Contains(aRect);
7636
0
}
7637
7638
bool
7639
nsTextFrame::IsFrameSelected() const
7640
0
{
7641
0
  NS_ASSERTION(!GetContent() || GetContent()->IsSelectionDescendant(),
7642
0
               "use the public IsSelected() instead");
7643
0
  return nsRange::IsNodeSelected(GetContent(), GetContentOffset(),
7644
0
                                 GetContentEnd());
7645
0
}
7646
7647
void
7648
nsTextFrame::SetSelectedRange(uint32_t aStart, uint32_t aEnd, bool aSelected,
7649
                              SelectionType aSelectionType)
7650
0
{
7651
0
  NS_ASSERTION(!GetPrevContinuation(), "Should only be called for primary frame");
7652
0
  DEBUG_VERIFY_NOT_DIRTY(mState);
7653
0
7654
0
  // Selection is collapsed, which can't affect text frame rendering
7655
0
  if (aStart == aEnd)
7656
0
    return;
7657
0
7658
0
  nsTextFrame* f = this;
7659
0
  while (f && f->GetContentEnd() <= int32_t(aStart)) {
7660
0
    f = f->GetNextContinuation();
7661
0
  }
7662
0
7663
0
  nsPresContext* presContext = PresContext();
7664
0
  while (f && f->GetContentOffset() < int32_t(aEnd)) {
7665
0
    // We may need to reflow to recompute the overflow area for
7666
0
    // spellchecking or IME underline if their underline is thicker than
7667
0
    // the normal decoration line.
7668
0
    if (ToSelectionTypeMask(aSelectionType) & kSelectionTypesWithDecorations) {
7669
0
      bool didHaveOverflowingSelection =
7670
0
        (f->GetStateBits() & TEXT_SELECTION_UNDERLINE_OVERFLOWED) != 0;
7671
0
      nsRect r(nsPoint(0, 0), GetSize());
7672
0
      if (didHaveOverflowingSelection ||
7673
0
          (aSelected && f->CombineSelectionUnderlineRect(presContext, r))) {
7674
0
        presContext->PresShell()->FrameNeedsReflow(f,
7675
0
                                                   nsIPresShell::eStyleChange,
7676
0
                                                   NS_FRAME_IS_DIRTY);
7677
0
      }
7678
0
    }
7679
0
    // Selection might change anything. Invalidate the overflow area.
7680
0
    f->InvalidateFrame();
7681
0
7682
0
    f = f->GetNextContinuation();
7683
0
  }
7684
0
}
7685
7686
void
7687
nsTextFrame::UpdateIteratorFromOffset(const PropertyProvider& aProperties,
7688
                                      int32_t& aInOffset,
7689
                                      gfxSkipCharsIterator& aIter)
7690
0
{
7691
0
  if (aInOffset < GetContentOffset()){
7692
0
    NS_WARNING("offset before this frame's content");
7693
0
    aInOffset = GetContentOffset();
7694
0
  } else if (aInOffset > GetContentEnd()) {
7695
0
    NS_WARNING("offset after this frame's content");
7696
0
    aInOffset = GetContentEnd();
7697
0
  }
7698
0
7699
0
  int32_t trimmedOffset = aProperties.GetStart().GetOriginalOffset();
7700
0
  int32_t trimmedEnd = trimmedOffset + aProperties.GetOriginalLength();
7701
0
  aInOffset = std::max(aInOffset, trimmedOffset);
7702
0
  aInOffset = std::min(aInOffset, trimmedEnd);
7703
0
7704
0
  aIter.SetOriginalOffset(aInOffset);
7705
0
7706
0
  if (aInOffset < trimmedEnd &&
7707
0
      !aIter.IsOriginalCharSkipped() &&
7708
0
      !mTextRun->IsClusterStart(aIter.GetSkippedOffset())) {
7709
0
    NS_WARNING("called for non-cluster boundary");
7710
0
    FindClusterStart(mTextRun, trimmedOffset, &aIter);
7711
0
  }
7712
0
}
7713
7714
nsPoint
7715
nsTextFrame::GetPointFromIterator(const gfxSkipCharsIterator& aIter,
7716
                                  PropertyProvider& aProperties)
7717
0
{
7718
0
  Range range(aProperties.GetStart().GetSkippedOffset(),
7719
0
              aIter.GetSkippedOffset());
7720
0
  gfxFloat advance = mTextRun->GetAdvanceWidth(range, &aProperties);
7721
0
  nscoord iSize = NSToCoordCeilClamped(advance);
7722
0
  nsPoint point;
7723
0
7724
0
  if (mTextRun->IsVertical()) {
7725
0
    point.x = 0;
7726
0
    if (mTextRun->IsInlineReversed()) {
7727
0
      point.y = mRect.height - iSize;
7728
0
    } else {
7729
0
      point.y = iSize;
7730
0
    }
7731
0
  } else {
7732
0
    point.y = 0;
7733
0
    if (mTextRun->IsInlineReversed()) {
7734
0
      point.x = mRect.width - iSize;
7735
0
    } else {
7736
0
      point.x = iSize;
7737
0
    }
7738
0
    if (Style()->IsTextCombined()) {
7739
0
      point.x *= GetTextCombineScaleFactor(this);
7740
0
    }
7741
0
  }
7742
0
  return point;
7743
0
}
7744
7745
nsresult
7746
nsTextFrame::GetPointFromOffset(int32_t inOffset,
7747
                                nsPoint* outPoint)
7748
0
{
7749
0
  if (!outPoint)
7750
0
    return NS_ERROR_NULL_POINTER;
7751
0
7752
0
  DEBUG_VERIFY_NOT_DIRTY(mState);
7753
0
  if (mState & NS_FRAME_IS_DIRTY)
7754
0
    return NS_ERROR_UNEXPECTED;
7755
0
7756
0
  if (GetContentLength() <= 0) {
7757
0
    outPoint->x = 0;
7758
0
    outPoint->y = 0;
7759
0
    return NS_OK;
7760
0
  }
7761
0
7762
0
  gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
7763
0
  if (!mTextRun)
7764
0
    return NS_ERROR_FAILURE;
7765
0
7766
0
  PropertyProvider properties(this, iter, nsTextFrame::eInflated);
7767
0
  // Don't trim trailing whitespace, we want the caret to appear in the right
7768
0
  // place if it's positioned there
7769
0
  properties.InitializeForDisplay(false);
7770
0
7771
0
  UpdateIteratorFromOffset(properties, inOffset, iter);
7772
0
7773
0
  *outPoint = GetPointFromIterator(iter, properties);
7774
0
7775
0
  return NS_OK;
7776
0
}
7777
7778
nsresult
7779
nsTextFrame::GetCharacterRectsInRange(int32_t aInOffset,
7780
                                      int32_t aLength,
7781
                                      nsTArray<nsRect>& aRects)
7782
0
{
7783
0
  DEBUG_VERIFY_NOT_DIRTY(mState);
7784
0
  if (mState & NS_FRAME_IS_DIRTY) {
7785
0
    return NS_ERROR_UNEXPECTED;
7786
0
  }
7787
0
7788
0
  if (GetContentLength() <= 0) {
7789
0
    return NS_OK;
7790
0
  }
7791
0
7792
0
  if (!mTextRun) {
7793
0
    return NS_ERROR_FAILURE;
7794
0
  }
7795
0
7796
0
  gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
7797
0
  PropertyProvider properties(this, iter, nsTextFrame::eInflated);
7798
0
  // Don't trim trailing whitespace, we want the caret to appear in the right
7799
0
  // place if it's positioned there
7800
0
  properties.InitializeForDisplay(false);
7801
0
7802
0
  UpdateIteratorFromOffset(properties, aInOffset, iter);
7803
0
7804
0
  const int32_t kContentEnd = GetContentEnd();
7805
0
  const int32_t kEndOffset = std::min(aInOffset + aLength, kContentEnd);
7806
0
  while (aInOffset < kEndOffset) {
7807
0
    if (!iter.IsOriginalCharSkipped() &&
7808
0
        !mTextRun->IsClusterStart(iter.GetSkippedOffset())) {
7809
0
      FindClusterStart(mTextRun,
7810
0
                       properties.GetStart().GetOriginalOffset() +
7811
0
                         properties.GetOriginalLength(),
7812
0
                       &iter);
7813
0
    }
7814
0
7815
0
    nsPoint point = GetPointFromIterator(iter, properties);
7816
0
    nsRect rect;
7817
0
    rect.x = point.x;
7818
0
    rect.y = point.y;
7819
0
7820
0
    nscoord iSize = 0;
7821
0
    if (aInOffset < kContentEnd) {
7822
0
      gfxSkipCharsIterator nextIter(iter);
7823
0
      nextIter.AdvanceOriginal(1);
7824
0
      if (!nextIter.IsOriginalCharSkipped() &&
7825
0
          !mTextRun->IsClusterStart(nextIter.GetSkippedOffset())) {
7826
0
        FindClusterEnd(mTextRun, kContentEnd, &nextIter);
7827
0
      }
7828
0
7829
0
      gfxFloat advance =
7830
0
        mTextRun->GetAdvanceWidth(Range(iter.GetSkippedOffset(),
7831
0
                                        nextIter.GetSkippedOffset()),
7832
0
                                  &properties);
7833
0
      iSize = NSToCoordCeilClamped(advance);
7834
0
    }
7835
0
7836
0
    if (mTextRun->IsVertical()) {
7837
0
      rect.width = mRect.width;
7838
0
      rect.height = iSize;
7839
0
    } else {
7840
0
      rect.width = iSize;
7841
0
      rect.height = mRect.height;
7842
0
7843
0
      if (Style()->IsTextCombined()) {
7844
0
        rect.width *= GetTextCombineScaleFactor(this);
7845
0
      }
7846
0
    }
7847
0
    aRects.AppendElement(rect);
7848
0
    aInOffset++;
7849
0
    // Don't advance iter if we've reached the end
7850
0
    if (aInOffset < kEndOffset) {
7851
0
      iter.AdvanceOriginal(1);
7852
0
    }
7853
0
  }
7854
0
7855
0
  return NS_OK;
7856
0
}
7857
7858
nsresult
7859
nsTextFrame::GetChildFrameContainingOffset(int32_t   aContentOffset,
7860
                                           bool      aHint,
7861
                                           int32_t*  aOutOffset,
7862
                                           nsIFrame**aOutFrame)
7863
0
{
7864
0
  DEBUG_VERIFY_NOT_DIRTY(mState);
7865
#if 0 //XXXrbs disable due to bug 310227
7866
  if (mState & NS_FRAME_IS_DIRTY)
7867
    return NS_ERROR_UNEXPECTED;
7868
#endif
7869
7870
0
  NS_ASSERTION(aOutOffset && aOutFrame, "Bad out parameters");
7871
0
  NS_ASSERTION(aContentOffset >= 0, "Negative content offset, existing code was very broken!");
7872
0
  nsIFrame* primaryFrame = mContent->GetPrimaryFrame();
7873
0
  if (this != primaryFrame) {
7874
0
    // This call needs to happen on the primary frame
7875
0
    return primaryFrame->GetChildFrameContainingOffset(aContentOffset, aHint,
7876
0
                                                       aOutOffset, aOutFrame);
7877
0
  }
7878
0
7879
0
  nsTextFrame* f = this;
7880
0
  int32_t offset = mContentOffset;
7881
0
7882
0
  // Try to look up the offset to frame property
7883
0
  nsTextFrame* cachedFrame = GetProperty(OffsetToFrameProperty());
7884
0
7885
0
  if (cachedFrame) {
7886
0
    f = cachedFrame;
7887
0
    offset = f->GetContentOffset();
7888
0
7889
0
    f->RemoveStateBits(TEXT_IN_OFFSET_CACHE);
7890
0
  }
7891
0
7892
0
  if ((aContentOffset >= offset) &&
7893
0
      (aHint || aContentOffset != offset)) {
7894
0
    while (true) {
7895
0
      nsTextFrame* next = f->GetNextContinuation();
7896
0
      if (!next || aContentOffset < next->GetContentOffset())
7897
0
        break;
7898
0
      if (aContentOffset == next->GetContentOffset()) {
7899
0
        if (aHint) {
7900
0
          f = next;
7901
0
          if (f->GetContentLength() == 0) {
7902
0
            continue; // use the last of the empty frames with this offset
7903
0
          }
7904
0
        }
7905
0
        break;
7906
0
      }
7907
0
      f = next;
7908
0
    }
7909
0
  } else {
7910
0
    while (true) {
7911
0
      nsTextFrame* prev = f->GetPrevContinuation();
7912
0
      if (!prev || aContentOffset > f->GetContentOffset())
7913
0
        break;
7914
0
      if (aContentOffset == f->GetContentOffset()) {
7915
0
        if (!aHint) {
7916
0
          f = prev;
7917
0
          if (f->GetContentLength() == 0) {
7918
0
            continue; // use the first of the empty frames with this offset
7919
0
          }
7920
0
        }
7921
0
        break;
7922
0
      }
7923
0
      f = prev;
7924
0
    }
7925
0
  }
7926
0
7927
0
  *aOutOffset = aContentOffset - f->GetContentOffset();
7928
0
  *aOutFrame = f;
7929
0
7930
0
  // cache the frame we found
7931
0
  SetProperty(OffsetToFrameProperty(), f);
7932
0
  f->AddStateBits(TEXT_IN_OFFSET_CACHE);
7933
0
7934
0
  return NS_OK;
7935
0
}
7936
7937
nsIFrame::FrameSearchResult
7938
nsTextFrame::PeekOffsetNoAmount(bool aForward, int32_t* aOffset)
7939
0
{
7940
0
  NS_ASSERTION(aOffset && *aOffset <= GetContentLength(), "aOffset out of range");
7941
0
7942
0
  gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
7943
0
  if (!mTextRun)
7944
0
    return CONTINUE_EMPTY;
7945
0
7946
0
  TrimmedOffsets trimmed = GetTrimmedOffsets(mContent->GetText(), true);
7947
0
  // Check whether there are nonskipped characters in the trimmmed range
7948
0
  return (iter.ConvertOriginalToSkipped(trimmed.GetEnd()) >
7949
0
         iter.ConvertOriginalToSkipped(trimmed.mStart)) ? FOUND : CONTINUE;
7950
0
}
7951
7952
/**
7953
 * This class iterates through the clusters before or after the given
7954
 * aPosition (which is a content offset). You can test each cluster
7955
 * to see if it's whitespace (as far as selection/caret movement is concerned),
7956
 * or punctuation, or if there is a word break before the cluster. ("Before"
7957
 * is interpreted according to aDirection, so if aDirection is -1, "before"
7958
 * means actually *after* the cluster content.)
7959
 */
7960
class MOZ_STACK_CLASS ClusterIterator {
7961
public:
7962
  ClusterIterator(nsTextFrame* aTextFrame, int32_t aPosition, int32_t aDirection,
7963
                  nsString& aContext);
7964
7965
  bool NextCluster();
7966
  bool IsWhitespace();
7967
  bool IsPunctuation();
7968
0
  bool HaveWordBreakBefore() { return mHaveWordBreak; }
7969
7970
  // Get the charIndex that corresponds to the "before" side of the current
7971
  // character, according to the direction of iteration: so for a forward
7972
  // iterator, this is simply mCharIndex, while for a reverse iterator it will
7973
  // be mCharIndex + <number of code units in the character>.
7974
  int32_t GetBeforeOffset()
7975
0
  {
7976
0
    MOZ_ASSERT(mCharIndex >= 0);
7977
0
    return mDirection < 0 ? GetAfterInternal() :  mCharIndex;
7978
0
  }
7979
  // Get the charIndex that corresponds to the "before" side of the current
7980
  // character, according to the direction of iteration: the opposite side
7981
  // to what GetBeforeOffset returns.
7982
  int32_t GetAfterOffset()
7983
0
  {
7984
0
    MOZ_ASSERT(mCharIndex >= 0);
7985
0
    return mDirection > 0 ? GetAfterInternal() :  mCharIndex;
7986
0
  }
7987
7988
private:
7989
  // Helper for Get{After,Before}Offset; returns the charIndex after the
7990
  // current position in the text, accounting for surrogate pairs.
7991
  int32_t GetAfterInternal();
7992
7993
  gfxSkipCharsIterator        mIterator;
7994
  const nsTextFragment*       mFrag;
7995
  nsTextFrame*                mTextFrame;
7996
  int32_t                     mDirection; // +1 or -1, or 0 to indicate failure
7997
  int32_t                     mCharIndex;
7998
  nsTextFrame::TrimmedOffsets mTrimmed;
7999
  nsTArray<bool>      mWordBreaks;
8000
  bool                        mHaveWordBreak;
8001
};
8002
8003
static bool
8004
IsAcceptableCaretPosition(const gfxSkipCharsIterator& aIter,
8005
                          bool aRespectClusters,
8006
                          const gfxTextRun* aTextRun,
8007
                          nsIFrame* aFrame)
8008
0
{
8009
0
  if (aIter.IsOriginalCharSkipped())
8010
0
    return false;
8011
0
  uint32_t index = aIter.GetSkippedOffset();
8012
0
  if (aRespectClusters && !aTextRun->IsClusterStart(index))
8013
0
    return false;
8014
0
  if (index > 0) {
8015
0
    // Check whether the proposed position is in between the two halves of a
8016
0
    // surrogate pair, before a Variation Selector character, or within a
8017
0
    // ligated emoji sequence; if so, this is not a valid character boundary.
8018
0
    // (In the case where we are respecting clusters, we won't actually get
8019
0
    // this far because the low surrogate is also marked as non-clusterStart
8020
0
    // so we'll return FALSE above.)
8021
0
    uint32_t offs = aIter.GetOriginalOffset();
8022
0
    const nsTextFragment* frag = aFrame->GetContent()->GetText();
8023
0
    uint32_t ch = frag->CharAt(offs);
8024
0
8025
0
    if (gfxFontUtils::IsVarSelector(ch) ||
8026
0
        (NS_IS_LOW_SURROGATE(ch) && offs > 0 &&
8027
0
         NS_IS_HIGH_SURROGATE(frag->CharAt(offs - 1))) ||
8028
0
        (!aTextRun->IsLigatureGroupStart(index) &&
8029
0
         unicode::GetEmojiPresentation(ch) == unicode::EmojiDefault)) {
8030
0
      return false;
8031
0
    }
8032
0
8033
0
    // If the proposed position is before a high surrogate, we need to decode
8034
0
    // the surrogate pair (if valid) and check the resulting character.
8035
0
    if (NS_IS_HIGH_SURROGATE(ch) && offs + 1 < frag->GetLength()) {
8036
0
      uint32_t ch2 = frag->CharAt(offs + 1);
8037
0
      if (NS_IS_LOW_SURROGATE(ch2)) {
8038
0
        ch = SURROGATE_TO_UCS4(ch, ch2);
8039
0
        // If the character is a (Plane-14) variation selector,
8040
0
        // or an emoji character that is ligated with the previous
8041
0
        // character (i.e. part of a Regional-Indicator flag pair,
8042
0
        // or an emoji-ZWJ sequence), this is not a valid boundary.
8043
0
        if (gfxFontUtils::IsVarSelector(ch) ||
8044
0
            (!aTextRun->IsLigatureGroupStart(index) &&
8045
0
             unicode::GetEmojiPresentation(ch) == unicode::EmojiDefault)) {
8046
0
          return false;
8047
0
        }
8048
0
      }
8049
0
    }
8050
0
  }
8051
0
  return true;
8052
0
}
8053
8054
nsIFrame::FrameSearchResult
8055
nsTextFrame::PeekOffsetCharacter(bool aForward, int32_t* aOffset,
8056
                                 PeekOffsetCharacterOptions aOptions)
8057
0
{
8058
0
  int32_t contentLength = GetContentLength();
8059
0
  NS_ASSERTION(aOffset && *aOffset <= contentLength, "aOffset out of range");
8060
0
8061
0
  if (!aOptions.mIgnoreUserStyleAll) {
8062
0
    StyleUserSelect selectStyle;
8063
0
    IsSelectable(&selectStyle);
8064
0
    if (selectStyle == StyleUserSelect::All) {
8065
0
      return CONTINUE_UNSELECTABLE;
8066
0
    }
8067
0
  }
8068
0
8069
0
  gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
8070
0
  if (!mTextRun)
8071
0
    return CONTINUE_EMPTY;
8072
0
8073
0
  TrimmedOffsets trimmed = GetTrimmedOffsets(mContent->GetText(), false);
8074
0
8075
0
  // A negative offset means "end of frame".
8076
0
  int32_t startOffset = GetContentOffset() + (*aOffset < 0 ? contentLength : *aOffset);
8077
0
8078
0
  if (!aForward) {
8079
0
    // If at the beginning of the line, look at the previous continuation
8080
0
    for (int32_t i = std::min(trimmed.GetEnd(), startOffset) - 1;
8081
0
         i >= trimmed.mStart; --i) {
8082
0
      iter.SetOriginalOffset(i);
8083
0
      if (IsAcceptableCaretPosition(iter, aOptions.mRespectClusters, mTextRun,
8084
0
                                    this)) {
8085
0
        *aOffset = i - mContentOffset;
8086
0
        return FOUND;
8087
0
      }
8088
0
    }
8089
0
    *aOffset = 0;
8090
0
  } else {
8091
0
    // If we're at the end of a line, look at the next continuation
8092
0
    iter.SetOriginalOffset(startOffset);
8093
0
    if (startOffset <= trimmed.GetEnd() &&
8094
0
        !(startOffset < trimmed.GetEnd() &&
8095
0
          StyleText()->NewlineIsSignificant(this) &&
8096
0
          iter.GetSkippedOffset() < mTextRun->GetLength() &&
8097
0
          mTextRun->CharIsNewline(iter.GetSkippedOffset()))) {
8098
0
      for (int32_t i = startOffset + 1; i <= trimmed.GetEnd(); ++i) {
8099
0
        iter.SetOriginalOffset(i);
8100
0
        if (i == trimmed.GetEnd() ||
8101
0
            IsAcceptableCaretPosition(iter, aOptions.mRespectClusters, mTextRun,
8102
0
                                      this)) {
8103
0
          *aOffset = i - mContentOffset;
8104
0
          return FOUND;
8105
0
        }
8106
0
      }
8107
0
    }
8108
0
    *aOffset = contentLength;
8109
0
  }
8110
0
8111
0
  return CONTINUE;
8112
0
}
8113
8114
bool
8115
ClusterIterator::IsWhitespace()
8116
0
{
8117
0
  NS_ASSERTION(mCharIndex >= 0, "No cluster selected");
8118
0
  return IsSelectionSpace(mFrag, mCharIndex);
8119
0
}
8120
8121
bool
8122
ClusterIterator::IsPunctuation()
8123
0
{
8124
0
  NS_ASSERTION(mCharIndex >= 0, "No cluster selected");
8125
0
  // The pref is cached on first call; changes will require a browser restart.
8126
0
  static bool sStopAtUnderscore =
8127
0
    Preferences::GetBool("layout.word_select.stop_at_underscore", false);
8128
0
  // Return true for all Punctuation categories (Unicode general category P?),
8129
0
  // and also for Symbol categories (S?) except for Modifier Symbol, which is
8130
0
  // kept together with any adjacent letter/number. (Bug 1066756)
8131
0
  uint32_t ch = mFrag->CharAt(mCharIndex);
8132
0
  uint8_t cat = unicode::GetGeneralCategory(ch);
8133
0
  switch (cat) {
8134
0
    case HB_UNICODE_GENERAL_CATEGORY_CONNECT_PUNCTUATION: /* Pc */
8135
0
      if (ch == '_' && !sStopAtUnderscore) {
8136
0
        return false;
8137
0
      }
8138
0
      MOZ_FALLTHROUGH;
8139
0
    case HB_UNICODE_GENERAL_CATEGORY_DASH_PUNCTUATION:    /* Pd */
8140
0
    case HB_UNICODE_GENERAL_CATEGORY_CLOSE_PUNCTUATION:   /* Pe */
8141
0
    case HB_UNICODE_GENERAL_CATEGORY_FINAL_PUNCTUATION:   /* Pf */
8142
0
    case HB_UNICODE_GENERAL_CATEGORY_INITIAL_PUNCTUATION: /* Pi */
8143
0
    case HB_UNICODE_GENERAL_CATEGORY_OTHER_PUNCTUATION:   /* Po */
8144
0
    case HB_UNICODE_GENERAL_CATEGORY_OPEN_PUNCTUATION:    /* Ps */
8145
0
    case HB_UNICODE_GENERAL_CATEGORY_CURRENCY_SYMBOL:     /* Sc */
8146
0
    // Deliberately omitted:
8147
0
    // case HB_UNICODE_GENERAL_CATEGORY_MODIFIER_SYMBOL:     /* Sk */
8148
0
    case HB_UNICODE_GENERAL_CATEGORY_MATH_SYMBOL:         /* Sm */
8149
0
    case HB_UNICODE_GENERAL_CATEGORY_OTHER_SYMBOL:        /* So */
8150
0
      return true;
8151
0
    default:
8152
0
      return false;
8153
0
  }
8154
0
}
8155
8156
int32_t
8157
ClusterIterator::GetAfterInternal()
8158
0
{
8159
0
  if (mFrag->Is2b() &&
8160
0
      NS_IS_HIGH_SURROGATE(mFrag->Get2b()[mCharIndex]) &&
8161
0
      uint32_t(mCharIndex) + 1 < mFrag->GetLength() &&
8162
0
      NS_IS_LOW_SURROGATE(mFrag->Get2b()[mCharIndex + 1])) {
8163
0
    return mCharIndex + 2;
8164
0
  }
8165
0
  return mCharIndex + 1;
8166
0
}
8167
8168
bool
8169
ClusterIterator::NextCluster()
8170
0
{
8171
0
  if (!mDirection)
8172
0
    return false;
8173
0
  const gfxTextRun* textRun = mTextFrame->GetTextRun(nsTextFrame::eInflated);
8174
0
8175
0
  mHaveWordBreak = false;
8176
0
  while (true) {
8177
0
    bool keepGoing = false;
8178
0
    if (mDirection > 0) {
8179
0
      if (mIterator.GetOriginalOffset() >= mTrimmed.GetEnd())
8180
0
        return false;
8181
0
      keepGoing = mIterator.IsOriginalCharSkipped() ||
8182
0
          mIterator.GetOriginalOffset() < mTrimmed.mStart ||
8183
0
          !textRun->IsClusterStart(mIterator.GetSkippedOffset());
8184
0
      mCharIndex = mIterator.GetOriginalOffset();
8185
0
      mIterator.AdvanceOriginal(1);
8186
0
    } else {
8187
0
      if (mIterator.GetOriginalOffset() <= mTrimmed.mStart)
8188
0
        return false;
8189
0
      mIterator.AdvanceOriginal(-1);
8190
0
      keepGoing = mIterator.IsOriginalCharSkipped() ||
8191
0
          mIterator.GetOriginalOffset() >= mTrimmed.GetEnd() ||
8192
0
          !textRun->IsClusterStart(mIterator.GetSkippedOffset());
8193
0
      mCharIndex = mIterator.GetOriginalOffset();
8194
0
    }
8195
0
8196
0
    if (mWordBreaks[GetBeforeOffset() - mTextFrame->GetContentOffset()]) {
8197
0
      mHaveWordBreak = true;
8198
0
    }
8199
0
    if (!keepGoing)
8200
0
      return true;
8201
0
  }
8202
0
}
8203
8204
ClusterIterator::ClusterIterator(nsTextFrame* aTextFrame, int32_t aPosition,
8205
                                 int32_t aDirection, nsString& aContext)
8206
  : mTextFrame(aTextFrame), mDirection(aDirection), mCharIndex(-1),
8207
    mHaveWordBreak(false)
8208
0
{
8209
0
  mIterator = aTextFrame->EnsureTextRun(nsTextFrame::eInflated);
8210
0
  if (!aTextFrame->GetTextRun(nsTextFrame::eInflated)) {
8211
0
    mDirection = 0; // signal failure
8212
0
    return;
8213
0
  }
8214
0
  mIterator.SetOriginalOffset(aPosition);
8215
0
8216
0
  mFrag = aTextFrame->GetContent()->GetText();
8217
0
  mTrimmed = aTextFrame->GetTrimmedOffsets(mFrag, true);
8218
0
8219
0
  int32_t textOffset = aTextFrame->GetContentOffset();
8220
0
  int32_t textLen = aTextFrame->GetContentLength();
8221
0
  if (!mWordBreaks.AppendElements(textLen + 1)) {
8222
0
    mDirection = 0; // signal failure
8223
0
    return;
8224
0
  }
8225
0
  memset(mWordBreaks.Elements(), false, (textLen + 1)*sizeof(bool));
8226
0
  int32_t textStart;
8227
0
  if (aDirection > 0) {
8228
0
    if (aContext.IsEmpty()) {
8229
0
      // No previous context, so it must be the start of a line or text run
8230
0
      mWordBreaks[0] = true;
8231
0
    }
8232
0
    textStart = aContext.Length();
8233
0
    mFrag->AppendTo(aContext, textOffset, textLen);
8234
0
  } else {
8235
0
    if (aContext.IsEmpty()) {
8236
0
      // No following context, so it must be the end of a line or text run
8237
0
      mWordBreaks[textLen] = true;
8238
0
    }
8239
0
    textStart = 0;
8240
0
    nsAutoString str;
8241
0
    mFrag->AppendTo(str, textOffset, textLen);
8242
0
    aContext.Insert(str, 0);
8243
0
  }
8244
0
  mozilla::intl::WordBreaker* wordBreaker = nsContentUtils::WordBreaker();
8245
0
  for (int32_t i = 0; i <= textLen; ++i) {
8246
0
    int32_t indexInText = i + textStart;
8247
0
    mWordBreaks[i] |=
8248
0
      wordBreaker->BreakInBetween(aContext.get(), indexInText,
8249
0
                                  aContext.get() + indexInText,
8250
0
                                  aContext.Length() - indexInText);
8251
0
  }
8252
0
}
8253
8254
nsIFrame::FrameSearchResult
8255
nsTextFrame::PeekOffsetWord(bool aForward, bool aWordSelectEatSpace, bool aIsKeyboardSelect,
8256
                            int32_t* aOffset, PeekWordState* aState)
8257
0
{
8258
0
  int32_t contentLength = GetContentLength();
8259
0
  NS_ASSERTION (aOffset && *aOffset <= contentLength, "aOffset out of range");
8260
0
8261
0
  StyleUserSelect selectStyle;
8262
0
  IsSelectable(&selectStyle);
8263
0
  if (selectStyle == StyleUserSelect::All)
8264
0
    return CONTINUE_UNSELECTABLE;
8265
0
8266
0
  int32_t offset = GetContentOffset() + (*aOffset < 0 ? contentLength : *aOffset);
8267
0
  ClusterIterator cIter(this, offset, aForward ? 1 : -1, aState->mContext);
8268
0
8269
0
  if (!cIter.NextCluster())
8270
0
    return CONTINUE_EMPTY;
8271
0
8272
0
  do {
8273
0
    bool isPunctuation = cIter.IsPunctuation();
8274
0
    bool isWhitespace = cIter.IsWhitespace();
8275
0
    bool isWordBreakBefore = cIter.HaveWordBreakBefore();
8276
0
    if (aWordSelectEatSpace == isWhitespace && !aState->mSawBeforeType) {
8277
0
      aState->SetSawBeforeType();
8278
0
      aState->Update(isPunctuation, isWhitespace);
8279
0
      continue;
8280
0
    }
8281
0
    // See if we can break before the current cluster
8282
0
    if (!aState->mAtStart) {
8283
0
      bool canBreak;
8284
0
      if (isPunctuation != aState->mLastCharWasPunctuation) {
8285
0
        canBreak = BreakWordBetweenPunctuation(aState, aForward,
8286
0
                     isPunctuation, isWhitespace, aIsKeyboardSelect);
8287
0
      } else if (!aState->mLastCharWasWhitespace &&
8288
0
                 !isWhitespace && !isPunctuation && isWordBreakBefore) {
8289
0
        // if both the previous and the current character are not white
8290
0
        // space but this can be word break before, we don't need to eat
8291
0
        // a white space in this case. This case happens in some languages
8292
0
        // that their words are not separated by white spaces. E.g.,
8293
0
        // Japanese and Chinese.
8294
0
        canBreak = true;
8295
0
      } else {
8296
0
        canBreak = isWordBreakBefore && aState->mSawBeforeType &&
8297
0
          (aWordSelectEatSpace != isWhitespace);
8298
0
      }
8299
0
      if (canBreak) {
8300
0
        *aOffset = cIter.GetBeforeOffset() - mContentOffset;
8301
0
        return FOUND;
8302
0
      }
8303
0
    }
8304
0
    aState->Update(isPunctuation, isWhitespace);
8305
0
  } while (cIter.NextCluster());
8306
0
8307
0
  *aOffset = cIter.GetAfterOffset() - mContentOffset;
8308
0
  return CONTINUE;
8309
0
}
8310
8311
 // TODO this needs to be deCOMtaminated with the interface fixed in
8312
// nsIFrame.h, but we won't do that until the old textframe is gone.
8313
nsresult
8314
nsTextFrame::CheckVisibility(nsPresContext* aContext, int32_t aStartIndex,
8315
    int32_t aEndIndex, bool aRecurse, bool *aFinished, bool *aRetval)
8316
0
{
8317
0
  if (!aRetval)
8318
0
    return NS_ERROR_NULL_POINTER;
8319
0
8320
0
  // Text in the range is visible if there is at least one character in the range
8321
0
  // that is not skipped and is mapped by this frame (which is the primary frame)
8322
0
  // or one of its continuations.
8323
0
  for (nsTextFrame* f = this; f; f = f->GetNextContinuation()) {
8324
0
    int32_t dummyOffset = 0;
8325
0
    if (f->PeekOffsetNoAmount(true, &dummyOffset) == FOUND) {
8326
0
      *aRetval = true;
8327
0
      return NS_OK;
8328
0
    }
8329
0
  }
8330
0
8331
0
  *aRetval = false;
8332
0
  return NS_OK;
8333
0
}
8334
8335
nsresult
8336
nsTextFrame::GetOffsets(int32_t &start, int32_t &end) const
8337
0
{
8338
0
  start = GetContentOffset();
8339
0
  end = GetContentEnd();
8340
0
  return NS_OK;
8341
0
}
8342
8343
static int32_t
8344
FindEndOfPunctuationRun(const nsTextFragment* aFrag,
8345
                        const gfxTextRun* aTextRun,
8346
                        gfxSkipCharsIterator* aIter,
8347
                        int32_t aOffset,
8348
                        int32_t aStart,
8349
                        int32_t aEnd)
8350
0
{
8351
0
  int32_t i;
8352
0
8353
0
  for (i = aStart; i < aEnd - aOffset; ++i) {
8354
0
    if (nsContentUtils::IsFirstLetterPunctuationAt(aFrag, aOffset + i)) {
8355
0
      aIter->SetOriginalOffset(aOffset + i);
8356
0
      FindClusterEnd(aTextRun, aEnd, aIter);
8357
0
      i = aIter->GetOriginalOffset() - aOffset;
8358
0
    } else {
8359
0
      break;
8360
0
    }
8361
0
  }
8362
0
  return i;
8363
0
}
8364
8365
/**
8366
 * Returns true if this text frame completes the first-letter, false
8367
 * if it does not contain a true "letter".
8368
 * If returns true, then it also updates aLength to cover just the first-letter
8369
 * text.
8370
 *
8371
 * XXX :first-letter should be handled during frame construction
8372
 * (and it has a good bit in common with nextBidi)
8373
 *
8374
 * @param aLength an in/out parameter: on entry contains the maximum length to
8375
 * return, on exit returns length of the first-letter fragment (which may
8376
 * include leading and trailing punctuation, for example)
8377
 */
8378
static bool
8379
FindFirstLetterRange(const nsTextFragment* aFrag,
8380
                     const gfxTextRun* aTextRun,
8381
                     int32_t aOffset, const gfxSkipCharsIterator& aIter,
8382
                     int32_t* aLength)
8383
0
{
8384
0
  int32_t i;
8385
0
  int32_t length = *aLength;
8386
0
  int32_t endOffset = aOffset + length;
8387
0
  gfxSkipCharsIterator iter(aIter);
8388
0
8389
0
  // skip leading whitespace, then consume clusters that start with punctuation
8390
0
  i = FindEndOfPunctuationRun(aFrag, aTextRun, &iter, aOffset,
8391
0
                              GetTrimmableWhitespaceCount(aFrag, aOffset, length, 1),
8392
0
                              endOffset);
8393
0
  if (i == length)
8394
0
    return false;
8395
0
8396
0
  // If the next character is not a letter or number, there is no first-letter.
8397
0
  // Return true so that we don't go on looking, but set aLength to 0.
8398
0
  if (!nsContentUtils::IsAlphanumericAt(aFrag, aOffset + i)) {
8399
0
    *aLength = 0;
8400
0
    return true;
8401
0
  }
8402
0
8403
0
  // consume another cluster (the actual first letter)
8404
0
8405
0
  // For complex scripts such as Indic and SEAsian, where first-letter
8406
0
  // should extend to entire orthographic "syllable" clusters, we don't
8407
0
  // want to allow this to split a ligature.
8408
0
  bool allowSplitLigature;
8409
0
8410
0
  typedef unicode::Script Script;
8411
0
  switch (unicode::GetScriptCode(aFrag->CharAt(aOffset + i))) {
8412
0
    default:
8413
0
      allowSplitLigature = true;
8414
0
      break;
8415
0
8416
0
    // For now, lacking any definitive specification of when to apply this
8417
0
    // behavior, we'll base the decision on the HarfBuzz shaping engine
8418
0
    // used for each script: those that are handled by the Indic, Tibetan,
8419
0
    // Myanmar and SEAsian shapers will apply the "don't split ligatures"
8420
0
    // rule.
8421
0
8422
0
    // Indic
8423
0
    case Script::BENGALI:
8424
0
    case Script::DEVANAGARI:
8425
0
    case Script::GUJARATI:
8426
0
    case Script::GURMUKHI:
8427
0
    case Script::KANNADA:
8428
0
    case Script::MALAYALAM:
8429
0
    case Script::ORIYA:
8430
0
    case Script::TAMIL:
8431
0
    case Script::TELUGU:
8432
0
    case Script::SINHALA:
8433
0
    case Script::BALINESE:
8434
0
    case Script::LEPCHA:
8435
0
    case Script::REJANG:
8436
0
    case Script::SUNDANESE:
8437
0
    case Script::JAVANESE:
8438
0
    case Script::KAITHI:
8439
0
    case Script::MEETEI_MAYEK:
8440
0
    case Script::CHAKMA:
8441
0
    case Script::SHARADA:
8442
0
    case Script::TAKRI:
8443
0
    case Script::KHMER:
8444
0
8445
0
    // Tibetan
8446
0
    case Script::TIBETAN:
8447
0
8448
0
    // Myanmar
8449
0
    case Script::MYANMAR:
8450
0
8451
0
    // Other SEAsian
8452
0
    case Script::BUGINESE:
8453
0
    case Script::NEW_TAI_LUE:
8454
0
    case Script::CHAM:
8455
0
    case Script::TAI_THAM:
8456
0
8457
0
    // What about Thai/Lao - any special handling needed?
8458
0
    // Should we special-case Arabic lam-alef?
8459
0
8460
0
      allowSplitLigature = false;
8461
0
      break;
8462
0
  }
8463
0
8464
0
  iter.SetOriginalOffset(aOffset + i);
8465
0
  FindClusterEnd(aTextRun, endOffset, &iter, allowSplitLigature);
8466
0
8467
0
  i = iter.GetOriginalOffset() - aOffset;
8468
0
  if (i + 1 == length)
8469
0
    return true;
8470
0
8471
0
  // consume clusters that start with punctuation
8472
0
  i = FindEndOfPunctuationRun(aFrag, aTextRun, &iter, aOffset, i + 1, endOffset);
8473
0
  if (i < length)
8474
0
    *aLength = i;
8475
0
  return true;
8476
0
}
8477
8478
static uint32_t
8479
FindStartAfterSkippingWhitespace(PropertyProvider* aProvider,
8480
                                 nsIFrame::InlineIntrinsicISizeData* aData,
8481
                                 const nsStyleText* aTextStyle,
8482
                                 gfxSkipCharsIterator* aIterator,
8483
                                 uint32_t aFlowEndInTextRun)
8484
0
{
8485
0
  if (aData->mSkipWhitespace) {
8486
0
    while (aIterator->GetSkippedOffset() < aFlowEndInTextRun &&
8487
0
           IsTrimmableSpace(aProvider->GetFragment(), aIterator->GetOriginalOffset(), aTextStyle)) {
8488
0
      aIterator->AdvanceOriginal(1);
8489
0
    }
8490
0
  }
8491
0
  return aIterator->GetSkippedOffset();
8492
0
}
8493
8494
float
8495
nsTextFrame::GetFontSizeInflation() const
8496
0
{
8497
0
  if (!HasFontSizeInflation()) {
8498
0
    return 1.0f;
8499
0
  }
8500
0
  return GetProperty(FontSizeInflationProperty());
8501
0
}
8502
8503
void
8504
nsTextFrame::SetFontSizeInflation(float aInflation)
8505
0
{
8506
0
  if (aInflation == 1.0f) {
8507
0
    if (HasFontSizeInflation()) {
8508
0
      RemoveStateBits(TEXT_HAS_FONT_INFLATION);
8509
0
      DeleteProperty(FontSizeInflationProperty());
8510
0
    }
8511
0
    return;
8512
0
  }
8513
0
8514
0
  AddStateBits(TEXT_HAS_FONT_INFLATION);
8515
0
  SetProperty(FontSizeInflationProperty(), aInflation);
8516
0
}
8517
8518
/* virtual */
8519
void nsTextFrame::MarkIntrinsicISizesDirty()
8520
0
{
8521
0
  ClearTextRuns();
8522
0
  nsFrame::MarkIntrinsicISizesDirty();
8523
0
}
8524
8525
// XXX this doesn't handle characters shaped by line endings. We need to
8526
// temporarily override the "current line ending" settings.
8527
void
8528
nsTextFrame::AddInlineMinISizeForFlow(gfxContext *aRenderingContext,
8529
                                      nsIFrame::InlineMinISizeData *aData,
8530
                                      TextRunType aTextRunType)
8531
0
{
8532
0
  uint32_t flowEndInTextRun;
8533
0
  gfxSkipCharsIterator iter =
8534
0
    EnsureTextRun(aTextRunType, aRenderingContext->GetDrawTarget(),
8535
0
                  aData->LineContainer(), aData->mLine, &flowEndInTextRun);
8536
0
  gfxTextRun *textRun = GetTextRun(aTextRunType);
8537
0
  if (!textRun)
8538
0
    return;
8539
0
8540
0
  // Pass null for the line container. This will disable tab spacing, but that's
8541
0
  // OK since we can't really handle tabs for intrinsic sizing anyway.
8542
0
  const nsStyleText* textStyle = StyleText();
8543
0
  const nsTextFragment* frag = mContent->GetText();
8544
0
8545
0
  // If we're hyphenating, the PropertyProvider needs the actual length;
8546
0
  // otherwise we can just pass INT32_MAX to mean "all the text"
8547
0
  int32_t len = INT32_MAX;
8548
0
  bool hyphenating = frag->GetLength() > 0 &&
8549
0
    (textStyle->mHyphens == StyleHyphens::Auto ||
8550
0
     (textStyle->mHyphens == StyleHyphens::Manual &&
8551
0
      !!(textRun->GetFlags() & gfx::ShapedTextFlags::TEXT_ENABLE_HYPHEN_BREAKS)));
8552
0
  if (hyphenating) {
8553
0
    gfxSkipCharsIterator tmp(iter);
8554
0
    len = std::min<int32_t>(GetContentOffset() + GetInFlowContentLength(),
8555
0
                 tmp.ConvertSkippedToOriginal(flowEndInTextRun)) - iter.GetOriginalOffset();
8556
0
  }
8557
0
  PropertyProvider provider(textRun, textStyle, frag, this,
8558
0
                            iter, len, nullptr, 0, aTextRunType);
8559
0
8560
0
  bool collapseWhitespace = !textStyle->WhiteSpaceIsSignificant();
8561
0
  bool preformatNewlines = textStyle->NewlineIsSignificant(this);
8562
0
  bool preformatTabs = textStyle->WhiteSpaceIsSignificant();
8563
0
  gfxFloat tabWidth = -1;
8564
0
  uint32_t start =
8565
0
    FindStartAfterSkippingWhitespace(&provider, aData, textStyle, &iter, flowEndInTextRun);
8566
0
8567
0
  // text-combine-upright frame is constantly 1em on inline-axis.
8568
0
  if (Style()->IsTextCombined()) {
8569
0
    if (start < flowEndInTextRun && textRun->CanBreakLineBefore(start)) {
8570
0
      aData->OptionallyBreak();
8571
0
    }
8572
0
    aData->mCurrentLine += provider.GetFontMetrics()->EmHeight();
8573
0
    aData->mTrailingWhitespace = 0;
8574
0
    return;
8575
0
  }
8576
0
8577
0
  // If overflow-wrap is break-word, we can wrap everywhere.
8578
0
  if (StaticPrefs::layout_css_overflow_break_intrinsic_size() &&
8579
0
      textStyle->WordCanWrap(this)) {
8580
0
    aData->OptionallyBreak();
8581
0
    aData->mCurrentLine +=
8582
0
      textRun->GetMinAdvanceWidth(Range(start, flowEndInTextRun));
8583
0
    aData->mTrailingWhitespace = 0;
8584
0
    aData->mAtStartOfLine = false;
8585
0
    aData->OptionallyBreak();
8586
0
    return;
8587
0
  }
8588
0
8589
0
  AutoTArray<gfxTextRun::HyphenType, BIG_TEXT_NODE_SIZE> hyphBuffer;
8590
0
  if (hyphenating) {
8591
0
    if (hyphBuffer.AppendElements(flowEndInTextRun - start, fallible)) {
8592
0
      provider.GetHyphenationBreaks(Range(start, flowEndInTextRun),
8593
0
                                    hyphBuffer.Elements());
8594
0
    } else {
8595
0
      hyphenating = false;
8596
0
    }
8597
0
  }
8598
0
8599
0
  for (uint32_t i = start, wordStart = start; i <= flowEndInTextRun; ++i) {
8600
0
    bool preformattedNewline = false;
8601
0
    bool preformattedTab = false;
8602
0
    if (i < flowEndInTextRun) {
8603
0
      // XXXldb Shouldn't we be including the newline as part of the
8604
0
      // segment that it ends rather than part of the segment that it
8605
0
      // starts?
8606
0
      preformattedNewline = preformatNewlines && textRun->CharIsNewline(i);
8607
0
      preformattedTab = preformatTabs && textRun->CharIsTab(i);
8608
0
      if (!textRun->CanBreakLineBefore(i) &&
8609
0
          !preformattedNewline &&
8610
0
          !preformattedTab &&
8611
0
          (!hyphenating ||
8612
0
           hyphBuffer[i - start] == gfxTextRun::HyphenType::None))
8613
0
      {
8614
0
        // we can't break here (and it's not the end of the flow)
8615
0
        continue;
8616
0
      }
8617
0
    }
8618
0
8619
0
    if (i > wordStart) {
8620
0
      nscoord width = NSToCoordCeilClamped(
8621
0
        textRun->GetAdvanceWidth(Range(wordStart, i), &provider));
8622
0
      width = std::max(0, width);
8623
0
      aData->mCurrentLine = NSCoordSaturatingAdd(aData->mCurrentLine, width);
8624
0
      aData->mAtStartOfLine = false;
8625
0
8626
0
      if (collapseWhitespace) {
8627
0
        uint32_t trimStart = GetEndOfTrimmedText(frag, textStyle, wordStart, i, &iter);
8628
0
        if (trimStart == start) {
8629
0
          // This is *all* trimmable whitespace, so whatever trailingWhitespace
8630
0
          // we saw previously is still trailing...
8631
0
          aData->mTrailingWhitespace += width;
8632
0
        } else {
8633
0
          // Some non-whitespace so the old trailingWhitespace is no longer trailing
8634
0
          nscoord wsWidth = NSToCoordCeilClamped(
8635
0
            textRun->GetAdvanceWidth(Range(trimStart, i), &provider));
8636
0
          aData->mTrailingWhitespace = std::max(0, wsWidth);
8637
0
        }
8638
0
      } else {
8639
0
        aData->mTrailingWhitespace = 0;
8640
0
      }
8641
0
    }
8642
0
8643
0
    if (preformattedTab) {
8644
0
      PropertyProvider::Spacing spacing;
8645
0
      provider.GetSpacing(Range(i, i + 1), &spacing);
8646
0
      aData->mCurrentLine += nscoord(spacing.mBefore);
8647
0
      if (tabWidth < 0) {
8648
0
        tabWidth = ComputeTabWidthAppUnits(this, textRun);
8649
0
      }
8650
0
      gfxFloat afterTab =
8651
0
        AdvanceToNextTab(aData->mCurrentLine, tabWidth,
8652
0
                         provider.MinTabAdvance());
8653
0
      aData->mCurrentLine = nscoord(afterTab + spacing.mAfter);
8654
0
      wordStart = i + 1;
8655
0
    } else if (i < flowEndInTextRun ||
8656
0
        (i == textRun->GetLength() &&
8657
0
         (textRun->GetFlags2() & nsTextFrameUtils::Flags::TEXT_HAS_TRAILING_BREAK))) {
8658
0
      if (preformattedNewline) {
8659
0
        aData->ForceBreak();
8660
0
      } else if (i < flowEndInTextRun && hyphenating &&
8661
0
                 hyphBuffer[i - start] != gfxTextRun::HyphenType::None) {
8662
0
        aData->OptionallyBreak(NSToCoordRound(provider.GetHyphenWidth()));
8663
0
      } else {
8664
0
        aData->OptionallyBreak();
8665
0
      }
8666
0
      wordStart = i;
8667
0
    }
8668
0
  }
8669
0
8670
0
  if (start < flowEndInTextRun) {
8671
0
    // Check if we have collapsible whitespace at the end
8672
0
    aData->mSkipWhitespace =
8673
0
      IsTrimmableSpace(provider.GetFragment(),
8674
0
                       iter.ConvertSkippedToOriginal(flowEndInTextRun - 1),
8675
0
                       textStyle);
8676
0
  }
8677
0
}
8678
8679
0
bool nsTextFrame::IsCurrentFontInflation(float aInflation) const {
8680
0
  return fabsf(aInflation - GetFontSizeInflation()) < 1e-6;
8681
0
}
8682
8683
// XXX Need to do something here to avoid incremental reflow bugs due to
8684
// first-line and first-letter changing min-width
8685
/* virtual */ void
8686
nsTextFrame::AddInlineMinISize(gfxContext *aRenderingContext,
8687
                               nsIFrame::InlineMinISizeData *aData)
8688
0
{
8689
0
  float inflation = nsLayoutUtils::FontSizeInflationFor(this);
8690
0
  TextRunType trtype = (inflation == 1.0f) ? eNotInflated : eInflated;
8691
0
8692
0
  if (trtype == eInflated && !IsCurrentFontInflation(inflation)) {
8693
0
    // FIXME: Ideally, if we already have a text run, we'd move it to be
8694
0
    // the uninflated text run.
8695
0
    ClearTextRun(nullptr, nsTextFrame::eInflated);
8696
0
  }
8697
0
8698
0
  nsTextFrame* f;
8699
0
  const gfxTextRun* lastTextRun = nullptr;
8700
0
  // nsContinuingTextFrame does nothing for AddInlineMinISize; all text frames
8701
0
  // in the flow are handled right here.
8702
0
  for (f = this; f; f = f->GetNextContinuation()) {
8703
0
    // f->GetTextRun(nsTextFrame::eNotInflated) could be null if we
8704
0
    // haven't set up textruns yet for f.  Except in OOM situations,
8705
0
    // lastTextRun will only be null for the first text frame.
8706
0
    if (f == this || f->GetTextRun(trtype) != lastTextRun) {
8707
0
      nsIFrame* lc;
8708
0
      if (aData->LineContainer() &&
8709
0
          aData->LineContainer() != (lc = FindLineContainer(f))) {
8710
0
        NS_ASSERTION(f != this, "wrong InlineMinISizeData container"
8711
0
                                " for first continuation");
8712
0
        aData->mLine = nullptr;
8713
0
        aData->SetLineContainer(lc);
8714
0
      }
8715
0
8716
0
      // This will process all the text frames that share the same textrun as f.
8717
0
      f->AddInlineMinISizeForFlow(aRenderingContext, aData, trtype);
8718
0
      lastTextRun = f->GetTextRun(trtype);
8719
0
    }
8720
0
  }
8721
0
}
8722
8723
// XXX this doesn't handle characters shaped by line endings. We need to
8724
// temporarily override the "current line ending" settings.
8725
void
8726
nsTextFrame::AddInlinePrefISizeForFlow(gfxContext *aRenderingContext,
8727
                                       nsIFrame::InlinePrefISizeData *aData,
8728
                                       TextRunType aTextRunType)
8729
0
{
8730
0
  uint32_t flowEndInTextRun;
8731
0
  gfxSkipCharsIterator iter =
8732
0
    EnsureTextRun(aTextRunType, aRenderingContext->GetDrawTarget(),
8733
0
                  aData->LineContainer(), aData->mLine, &flowEndInTextRun);
8734
0
  gfxTextRun *textRun = GetTextRun(aTextRunType);
8735
0
  if (!textRun)
8736
0
    return;
8737
0
8738
0
  // Pass null for the line container. This will disable tab spacing, but that's
8739
0
  // OK since we can't really handle tabs for intrinsic sizing anyway.
8740
0
8741
0
  const nsStyleText* textStyle = StyleText();
8742
0
  const nsTextFragment* frag = mContent->GetText();
8743
0
  PropertyProvider provider(textRun, textStyle, frag, this,
8744
0
                            iter, INT32_MAX, nullptr, 0, aTextRunType);
8745
0
8746
0
  // text-combine-upright frame is constantly 1em on inline-axis.
8747
0
  if (Style()->IsTextCombined()) {
8748
0
    aData->mCurrentLine += provider.GetFontMetrics()->EmHeight();
8749
0
    aData->mTrailingWhitespace = 0;
8750
0
    aData->mLineIsEmpty = false;
8751
0
    return;
8752
0
  }
8753
0
8754
0
  bool collapseWhitespace = !textStyle->WhiteSpaceIsSignificant();
8755
0
  bool preformatNewlines = textStyle->NewlineIsSignificant(this);
8756
0
  bool preformatTabs = textStyle->TabIsSignificant();
8757
0
  gfxFloat tabWidth = -1;
8758
0
  uint32_t start =
8759
0
    FindStartAfterSkippingWhitespace(&provider, aData, textStyle, &iter, flowEndInTextRun);
8760
0
8761
0
  // XXX Should we consider hyphenation here?
8762
0
  // If newlines and tabs aren't preformatted, nothing to do inside
8763
0
  // the loop so make i skip to the end
8764
0
  uint32_t loopStart = (preformatNewlines || preformatTabs) ? start : flowEndInTextRun;
8765
0
  for (uint32_t i = loopStart, lineStart = start; i <= flowEndInTextRun; ++i) {
8766
0
    bool preformattedNewline = false;
8767
0
    bool preformattedTab = false;
8768
0
    if (i < flowEndInTextRun) {
8769
0
      // XXXldb Shouldn't we be including the newline as part of the
8770
0
      // segment that it ends rather than part of the segment that it
8771
0
      // starts?
8772
0
      NS_ASSERTION(preformatNewlines || preformatTabs,
8773
0
                   "We can't be here unless newlines are "
8774
0
                   "hard breaks or there are tabs");
8775
0
      preformattedNewline = preformatNewlines && textRun->CharIsNewline(i);
8776
0
      preformattedTab = preformatTabs && textRun->CharIsTab(i);
8777
0
      if (!preformattedNewline && !preformattedTab) {
8778
0
        // we needn't break here (and it's not the end of the flow)
8779
0
        continue;
8780
0
      }
8781
0
    }
8782
0
8783
0
    if (i > lineStart) {
8784
0
      nscoord width = NSToCoordCeilClamped(
8785
0
        textRun->GetAdvanceWidth(Range(lineStart, i), &provider));
8786
0
      width = std::max(0, width);
8787
0
      aData->mCurrentLine = NSCoordSaturatingAdd(aData->mCurrentLine, width);
8788
0
      aData->mLineIsEmpty = false;
8789
0
8790
0
      if (collapseWhitespace) {
8791
0
        uint32_t trimStart = GetEndOfTrimmedText(frag, textStyle, lineStart, i, &iter);
8792
0
        if (trimStart == start) {
8793
0
          // This is *all* trimmable whitespace, so whatever trailingWhitespace
8794
0
          // we saw previously is still trailing...
8795
0
          aData->mTrailingWhitespace += width;
8796
0
        } else {
8797
0
          // Some non-whitespace so the old trailingWhitespace is no longer trailing
8798
0
          nscoord wsWidth = NSToCoordCeilClamped(
8799
0
            textRun->GetAdvanceWidth(Range(trimStart, i), &provider));
8800
0
          aData->mTrailingWhitespace = std::max(0, wsWidth);
8801
0
        }
8802
0
      } else {
8803
0
        aData->mTrailingWhitespace = 0;
8804
0
      }
8805
0
    }
8806
0
8807
0
    if (preformattedTab) {
8808
0
      PropertyProvider::Spacing spacing;
8809
0
      provider.GetSpacing(Range(i, i + 1), &spacing);
8810
0
      aData->mCurrentLine += nscoord(spacing.mBefore);
8811
0
      if (tabWidth < 0) {
8812
0
        tabWidth = ComputeTabWidthAppUnits(this, textRun);
8813
0
      }
8814
0
      gfxFloat afterTab =
8815
0
        AdvanceToNextTab(aData->mCurrentLine, tabWidth,
8816
0
                         provider.MinTabAdvance());
8817
0
      aData->mCurrentLine = nscoord(afterTab + spacing.mAfter);
8818
0
      aData->mLineIsEmpty = false;
8819
0
      lineStart = i + 1;
8820
0
    } else if (preformattedNewline) {
8821
0
      aData->ForceBreak();
8822
0
      lineStart = i;
8823
0
    }
8824
0
  }
8825
0
8826
0
  // Check if we have collapsible whitespace at the end
8827
0
  if (start < flowEndInTextRun) {
8828
0
    aData->mSkipWhitespace =
8829
0
      IsTrimmableSpace(provider.GetFragment(),
8830
0
                       iter.ConvertSkippedToOriginal(flowEndInTextRun - 1),
8831
0
                       textStyle);
8832
0
  }
8833
0
}
8834
8835
// XXX Need to do something here to avoid incremental reflow bugs due to
8836
// first-line and first-letter changing pref-width
8837
/* virtual */ void
8838
nsTextFrame::AddInlinePrefISize(gfxContext *aRenderingContext,
8839
                                nsIFrame::InlinePrefISizeData *aData)
8840
0
{
8841
0
  float inflation = nsLayoutUtils::FontSizeInflationFor(this);
8842
0
  TextRunType trtype = (inflation == 1.0f) ? eNotInflated : eInflated;
8843
0
8844
0
  if (trtype == eInflated && !IsCurrentFontInflation(inflation)) {
8845
0
    // FIXME: Ideally, if we already have a text run, we'd move it to be
8846
0
    // the uninflated text run.
8847
0
    ClearTextRun(nullptr, nsTextFrame::eInflated);
8848
0
  }
8849
0
8850
0
  nsTextFrame* f;
8851
0
  const gfxTextRun* lastTextRun = nullptr;
8852
0
  // nsContinuingTextFrame does nothing for AddInlineMinISize; all text frames
8853
0
  // in the flow are handled right here.
8854
0
  for (f = this; f; f = f->GetNextContinuation()) {
8855
0
    // f->GetTextRun(nsTextFrame::eNotInflated) could be null if we
8856
0
    // haven't set up textruns yet for f.  Except in OOM situations,
8857
0
    // lastTextRun will only be null for the first text frame.
8858
0
    if (f == this || f->GetTextRun(trtype) != lastTextRun) {
8859
0
      nsIFrame* lc;
8860
0
      if (aData->LineContainer() &&
8861
0
          aData->LineContainer() != (lc = FindLineContainer(f))) {
8862
0
        NS_ASSERTION(f != this, "wrong InlinePrefISizeData container"
8863
0
                                " for first continuation");
8864
0
        aData->mLine = nullptr;
8865
0
        aData->SetLineContainer(lc);
8866
0
      }
8867
0
8868
0
      // This will process all the text frames that share the same textrun as f.
8869
0
      f->AddInlinePrefISizeForFlow(aRenderingContext, aData, trtype);
8870
0
      lastTextRun = f->GetTextRun(trtype);
8871
0
    }
8872
0
  }
8873
0
}
8874
8875
/* virtual */
8876
LogicalSize
8877
nsTextFrame::ComputeSize(gfxContext *aRenderingContext,
8878
                         WritingMode aWM,
8879
                         const LogicalSize& aCBSize,
8880
                         nscoord aAvailableISize,
8881
                         const LogicalSize& aMargin,
8882
                         const LogicalSize& aBorder,
8883
                         const LogicalSize& aPadding,
8884
                         ComputeSizeFlags aFlags)
8885
0
{
8886
0
  // Inlines and text don't compute size before reflow.
8887
0
  return LogicalSize(aWM, NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
8888
0
}
8889
8890
static nsRect
8891
RoundOut(const gfxRect& aRect)
8892
0
{
8893
0
  nsRect r;
8894
0
  r.x = NSToCoordFloor(aRect.X());
8895
0
  r.y = NSToCoordFloor(aRect.Y());
8896
0
  r.width = NSToCoordCeil(aRect.XMost()) - r.x;
8897
0
  r.height = NSToCoordCeil(aRect.YMost()) - r.y;
8898
0
  return r;
8899
0
}
8900
8901
nsRect
8902
nsTextFrame::ComputeTightBounds(DrawTarget* aDrawTarget) const
8903
0
{
8904
0
  if (Style()->HasTextDecorationLines() ||
8905
0
      (GetStateBits() & TEXT_HYPHEN_BREAK)) {
8906
0
    // This is conservative, but OK.
8907
0
    return GetVisualOverflowRect();
8908
0
  }
8909
0
8910
0
  gfxSkipCharsIterator iter =
8911
0
    const_cast<nsTextFrame*>(this)->EnsureTextRun(nsTextFrame::eInflated);
8912
0
  if (!mTextRun)
8913
0
    return nsRect(0, 0, 0, 0);
8914
0
8915
0
  PropertyProvider provider(const_cast<nsTextFrame*>(this), iter,
8916
0
                            nsTextFrame::eInflated);
8917
0
  // Trim trailing whitespace
8918
0
  provider.InitializeForDisplay(true);
8919
0
8920
0
  gfxTextRun::Metrics metrics =
8921
0
        mTextRun->MeasureText(ComputeTransformedRange(provider),
8922
0
                              gfxFont::TIGHT_HINTED_OUTLINE_EXTENTS,
8923
0
                              aDrawTarget, &provider);
8924
0
  if (GetWritingMode().IsLineInverted()) {
8925
0
    metrics.mBoundingBox.y = -metrics.mBoundingBox.YMost();
8926
0
  }
8927
0
  // mAscent should be the same as metrics.mAscent, but it's what we use to
8928
0
  // paint so that's the one we'll use.
8929
0
  nsRect boundingBox = RoundOut(metrics.mBoundingBox);
8930
0
  boundingBox += nsPoint(0, mAscent);
8931
0
  if (mTextRun->IsVertical()) {
8932
0
    // Swap line-relative textMetrics dimensions to physical coordinates.
8933
0
    Swap(boundingBox.x, boundingBox.y);
8934
0
    Swap(boundingBox.width, boundingBox.height);
8935
0
  }
8936
0
  return boundingBox;
8937
0
}
8938
8939
/* virtual */ nsresult
8940
nsTextFrame::GetPrefWidthTightBounds(gfxContext* aContext,
8941
                                     nscoord* aX,
8942
                                     nscoord* aXMost)
8943
0
{
8944
0
  gfxSkipCharsIterator iter =
8945
0
    const_cast<nsTextFrame*>(this)->EnsureTextRun(nsTextFrame::eInflated);
8946
0
  if (!mTextRun)
8947
0
    return NS_ERROR_FAILURE;
8948
0
8949
0
  PropertyProvider provider(const_cast<nsTextFrame*>(this), iter,
8950
0
                            nsTextFrame::eInflated);
8951
0
  provider.InitializeForMeasure();
8952
0
8953
0
  gfxTextRun::Metrics metrics =
8954
0
        mTextRun->MeasureText(ComputeTransformedRange(provider),
8955
0
                              gfxFont::TIGHT_HINTED_OUTLINE_EXTENTS,
8956
0
                              aContext->GetDrawTarget(), &provider);
8957
0
  // Round it like nsTextFrame::ComputeTightBounds() to ensure consistency.
8958
0
  *aX = NSToCoordFloor(metrics.mBoundingBox.x);
8959
0
  *aXMost = NSToCoordCeil(metrics.mBoundingBox.XMost());
8960
0
8961
0
  return NS_OK;
8962
0
}
8963
8964
static bool
8965
HasSoftHyphenBefore(const nsTextFragment* aFrag, const gfxTextRun* aTextRun,
8966
                    int32_t aStartOffset, const gfxSkipCharsIterator& aIter)
8967
0
{
8968
0
  if (aIter.GetSkippedOffset() < aTextRun->GetLength() &&
8969
0
      aTextRun->CanHyphenateBefore(aIter.GetSkippedOffset())) {
8970
0
    return true;
8971
0
  }
8972
0
  if (!(aTextRun->GetFlags2() & nsTextFrameUtils::Flags::TEXT_HAS_SHY))
8973
0
    return false;
8974
0
  gfxSkipCharsIterator iter = aIter;
8975
0
  while (iter.GetOriginalOffset() > aStartOffset) {
8976
0
    iter.AdvanceOriginal(-1);
8977
0
    if (!iter.IsOriginalCharSkipped())
8978
0
      break;
8979
0
    if (aFrag->CharAt(iter.GetOriginalOffset()) == CH_SHY)
8980
0
      return true;
8981
0
  }
8982
0
  return false;
8983
0
}
8984
8985
/**
8986
 * Removes all frames from aFrame up to (but not including) aFirstToNotRemove,
8987
 * because their text has all been taken and reflowed by earlier frames.
8988
 */
8989
static void
8990
RemoveEmptyInFlows(nsTextFrame* aFrame, nsTextFrame* aFirstToNotRemove)
8991
0
{
8992
0
  MOZ_ASSERT(aFrame != aFirstToNotRemove, "This will go very badly");
8993
0
  // We have to be careful here, because some RemoveFrame implementations
8994
0
  // remove and destroy not only the passed-in frame but also all its following
8995
0
  // in-flows (and sometimes all its following continuations in general).  So
8996
0
  // we remove |f| and everything up to but not including firstToNotRemove from
8997
0
  // the flow first, to make sure that only the things we want destroyed are
8998
0
  // destroyed.
8999
0
9000
0
  // This sadly duplicates some of the logic from
9001
0
  // nsSplittableFrame::RemoveFromFlow.  We can get away with not duplicating
9002
0
  // all of it, because we know that the prev-continuation links of
9003
0
  // firstToNotRemove and f are fluid, and non-null.
9004
0
  NS_ASSERTION(aFirstToNotRemove->GetPrevContinuation() ==
9005
0
               aFirstToNotRemove->GetPrevInFlow() &&
9006
0
               aFirstToNotRemove->GetPrevInFlow() != nullptr,
9007
0
               "aFirstToNotRemove should have a fluid prev continuation");
9008
0
  NS_ASSERTION(aFrame->GetPrevContinuation() ==
9009
0
               aFrame->GetPrevInFlow() &&
9010
0
               aFrame->GetPrevInFlow() != nullptr,
9011
0
               "aFrame should have a fluid prev continuation");
9012
0
9013
0
  nsTextFrame* prevContinuation = aFrame->GetPrevContinuation();
9014
0
  nsTextFrame* lastRemoved = aFirstToNotRemove->GetPrevContinuation();
9015
0
9016
0
  for (nsTextFrame* f = aFrame; f != aFirstToNotRemove;
9017
0
       f = f->GetNextContinuation()) {
9018
0
    // f is going to be destroyed soon, after it is unlinked from the
9019
0
    // continuation chain. If its textrun is going to be destroyed we need to
9020
0
    // do it now, before we unlink the frames to remove from the flow,
9021
0
    // because DestroyFrom calls ClearTextRuns() and that will start at the
9022
0
    // first frame with the text run and walk the continuations.
9023
0
    if (f->IsInTextRunUserData()) {
9024
0
      f->ClearTextRuns();
9025
0
    } else {
9026
0
      f->DisconnectTextRuns();
9027
0
    }
9028
0
  }
9029
0
9030
0
  prevContinuation->SetNextInFlow(aFirstToNotRemove);
9031
0
  aFirstToNotRemove->SetPrevInFlow(prevContinuation);
9032
0
9033
0
  aFrame->SetPrevInFlow(nullptr);
9034
0
  lastRemoved->SetNextInFlow(nullptr);
9035
0
9036
0
  nsContainerFrame* parent = aFrame->GetParent();
9037
0
  nsBlockFrame* parentBlock = nsLayoutUtils::GetAsBlock(parent);
9038
0
  if (parentBlock) {
9039
0
    // Manually call DoRemoveFrame so we can tell it that we're
9040
0
    // removing empty frames; this will keep it from blowing away
9041
0
    // text runs.
9042
0
    parentBlock->DoRemoveFrame(aFrame, nsBlockFrame::FRAMES_ARE_EMPTY);
9043
0
  } else {
9044
0
    // Just remove it normally; use kNoReflowPrincipalList to avoid posting
9045
0
    // new reflows.
9046
0
    parent->RemoveFrame(nsIFrame::kNoReflowPrincipalList, aFrame);
9047
0
  }
9048
0
}
9049
9050
void
9051
nsTextFrame::SetLength(int32_t aLength, nsLineLayout* aLineLayout,
9052
                       uint32_t aSetLengthFlags)
9053
0
{
9054
0
  mContentLengthHint = aLength;
9055
0
  int32_t end = GetContentOffset() + aLength;
9056
0
  nsTextFrame* f = GetNextInFlow();
9057
0
  if (!f)
9058
0
    return;
9059
0
9060
0
  // If our end offset is moving, then even if frames are not being pushed or
9061
0
  // pulled, content is moving to or from the next line and the next line
9062
0
  // must be reflowed.
9063
0
  // If the next-continuation is dirty, then we should dirty the next line now
9064
0
  // because we may have skipped doing it if we dirtied it in
9065
0
  // CharacterDataChanged. This is ugly but teaching FrameNeedsReflow
9066
0
  // and ChildIsDirty to handle a range of frames would be worse.
9067
0
  if (aLineLayout &&
9068
0
      (end != f->mContentOffset || (f->GetStateBits() & NS_FRAME_IS_DIRTY))) {
9069
0
    aLineLayout->SetDirtyNextLine();
9070
0
  }
9071
0
9072
0
  if (end < f->mContentOffset) {
9073
0
    // Our frame is shrinking. Give the text to our next in flow.
9074
0
    if (aLineLayout && HasSignificantTerminalNewline() &&
9075
0
        !GetParent()->IsLetterFrame() &&
9076
0
        (aSetLengthFlags & ALLOW_FRAME_CREATION_AND_DESTRUCTION)) {
9077
0
      // Whatever text we hand to our next-in-flow will end up in a frame all of
9078
0
      // its own, since it ends in a forced linebreak.  Might as well just put
9079
0
      // it in a separate frame now.  This is important to prevent text run
9080
0
      // churn; if we did not do that, then we'd likely end up rebuilding
9081
0
      // textruns for all our following continuations.
9082
0
      // We skip this optimization when the parent is a first-letter frame
9083
0
      // because it doesn't deal well with more than one child frame.
9084
0
      // We also skip this optimization if we were called during bidi
9085
0
      // resolution, so as not to create a new frame which doesn't appear in
9086
0
      // the bidi resolver's list of frames
9087
0
      nsPresContext* presContext = PresContext();
9088
0
      nsIFrame* newFrame = presContext->PresShell()->FrameConstructor()->
9089
0
        CreateContinuingFrame(presContext, this, GetParent());
9090
0
      nsTextFrame* next = static_cast<nsTextFrame*>(newFrame);
9091
0
      nsFrameList temp(next, next);
9092
0
      GetParent()->InsertFrames(kNoReflowPrincipalList, this, temp);
9093
0
      f = next;
9094
0
    }
9095
0
9096
0
    f->mContentOffset = end;
9097
0
    if (f->GetTextRun(nsTextFrame::eInflated) != mTextRun) {
9098
0
      ClearTextRuns();
9099
0
      f->ClearTextRuns();
9100
0
    }
9101
0
    return;
9102
0
  }
9103
0
  // Our frame is growing. Take text from our in-flow(s).
9104
0
  // We can take text from frames in lines beyond just the next line.
9105
0
  // We don't dirty those lines. That's OK, because when we reflow
9106
0
  // our empty next-in-flow, it will take text from its next-in-flow and
9107
0
  // dirty that line.
9108
0
9109
0
  // Note that in the process we may end up removing some frames from
9110
0
  // the flow if they end up empty.
9111
0
  nsTextFrame* framesToRemove = nullptr;
9112
0
  while (f && f->mContentOffset < end) {
9113
0
    f->mContentOffset = end;
9114
0
    if (f->GetTextRun(nsTextFrame::eInflated) != mTextRun) {
9115
0
      ClearTextRuns();
9116
0
      f->ClearTextRuns();
9117
0
    }
9118
0
    nsTextFrame* next = f->GetNextInFlow();
9119
0
    // Note: the "f->GetNextSibling() == next" check below is to restrict
9120
0
    // this optimization to the case where they are on the same child list.
9121
0
    // Otherwise we might remove the only child of a nsFirstLetterFrame
9122
0
    // for example and it can't handle that.  See bug 597627 for details.
9123
0
    if (next && next->mContentOffset <= end && f->GetNextSibling() == next &&
9124
0
        (aSetLengthFlags & ALLOW_FRAME_CREATION_AND_DESTRUCTION)) {
9125
0
      // |f| is now empty.  We may as well remove it, instead of copying all
9126
0
      // the text from |next| into it instead; the latter leads to use
9127
0
      // rebuilding textruns for all following continuations.
9128
0
      // We skip this optimization if we were called during bidi resolution,
9129
0
      // since the bidi resolver may try to handle the destroyed frame later
9130
0
      // and crash
9131
0
      if (!framesToRemove) {
9132
0
        // Remember that we have to remove this frame.
9133
0
        framesToRemove = f;
9134
0
      }
9135
0
    } else if (framesToRemove) {
9136
0
      RemoveEmptyInFlows(framesToRemove, f);
9137
0
      framesToRemove = nullptr;
9138
0
    }
9139
0
    f = next;
9140
0
  }
9141
0
9142
0
  MOZ_ASSERT(!framesToRemove || (f && f->mContentOffset == end),
9143
0
             "How did we exit the loop if we null out framesToRemove if "
9144
0
             "!next || next->mContentOffset > end ?");
9145
0
9146
0
  if (framesToRemove) {
9147
0
    // We are guaranteed that we exited the loop with f not null, per the
9148
0
    // postcondition above
9149
0
    RemoveEmptyInFlows(framesToRemove, f);
9150
0
  }
9151
0
9152
#ifdef DEBUG
9153
  f = this;
9154
  int32_t iterations = 0;
9155
  while (f && iterations < 10) {
9156
    f->GetContentLength(); // Assert if negative length
9157
    f = f->GetNextContinuation();
9158
    ++iterations;
9159
  }
9160
  f = this;
9161
  iterations = 0;
9162
  while (f && iterations < 10) {
9163
    f->GetContentLength(); // Assert if negative length
9164
    f = f->GetPrevContinuation();
9165
    ++iterations;
9166
  }
9167
#endif
9168
}
9169
9170
bool
9171
nsTextFrame::IsFloatingFirstLetterChild() const
9172
0
{
9173
0
  nsIFrame* frame = GetParent();
9174
0
  return frame && frame->IsFloating() && frame->IsLetterFrame();
9175
0
}
9176
9177
bool
9178
nsTextFrame::IsInitialLetterChild() const
9179
0
{
9180
0
  nsIFrame* frame = GetParent();
9181
0
  return frame && frame->StyleTextReset()->mInitialLetterSize != 0.0f &&
9182
0
         frame->IsLetterFrame();
9183
0
}
9184
9185
struct NewlineProperty {
9186
  int32_t mStartOffset;
9187
  // The offset of the first \n after mStartOffset, or -1 if there is none
9188
  int32_t mNewlineOffset;
9189
};
9190
9191
void
9192
nsTextFrame::Reflow(nsPresContext*           aPresContext,
9193
                    ReflowOutput&     aMetrics,
9194
                    const ReflowInput& aReflowInput,
9195
                    nsReflowStatus&          aStatus)
9196
0
{
9197
0
  MarkInReflow();
9198
0
  DO_GLOBAL_REFLOW_COUNT("nsTextFrame");
9199
0
  DISPLAY_REFLOW(aPresContext, this, aReflowInput, aMetrics, aStatus);
9200
0
  MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
9201
0
9202
0
  // XXX If there's no line layout, we shouldn't even have created this
9203
0
  // frame. This may happen if, for example, this is text inside a table
9204
0
  // but not inside a cell. For now, just don't reflow.
9205
0
  if (!aReflowInput.mLineLayout) {
9206
0
    ClearMetrics(aMetrics);
9207
0
    return;
9208
0
  }
9209
0
9210
0
  ReflowText(*aReflowInput.mLineLayout, aReflowInput.AvailableWidth(),
9211
0
             aReflowInput.mRenderingContext->GetDrawTarget(), aMetrics, aStatus);
9212
0
9213
0
  NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aMetrics);
9214
0
}
9215
9216
#ifdef ACCESSIBILITY
9217
/**
9218
 * Notifies accessibility about text reflow. Used by nsTextFrame::ReflowText.
9219
 */
9220
class MOZ_STACK_CLASS ReflowTextA11yNotifier
9221
{
9222
public:
9223
  ReflowTextA11yNotifier(nsPresContext* aPresContext, nsIContent* aContent) :
9224
    mContent(aContent), mPresContext(aPresContext)
9225
0
  {
9226
0
  }
9227
  ~ReflowTextA11yNotifier()
9228
0
  {
9229
0
    nsAccessibilityService* accService = nsIPresShell::AccService();
9230
0
    if (accService) {
9231
0
      accService->UpdateText(mPresContext->PresShell(), mContent);
9232
0
    }
9233
0
  }
9234
private:
9235
  ReflowTextA11yNotifier();
9236
  ReflowTextA11yNotifier(const ReflowTextA11yNotifier&);
9237
  ReflowTextA11yNotifier& operator =(const ReflowTextA11yNotifier&);
9238
9239
  nsIContent* mContent;
9240
  nsPresContext* mPresContext;
9241
};
9242
#endif
9243
9244
void
9245
nsTextFrame::ReflowText(nsLineLayout& aLineLayout, nscoord aAvailableWidth,
9246
                        DrawTarget* aDrawTarget,
9247
                        ReflowOutput& aMetrics,
9248
                        nsReflowStatus& aStatus)
9249
0
{
9250
0
  MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
9251
0
9252
#ifdef NOISY_REFLOW
9253
  ListTag(stdout);
9254
  printf(": BeginReflow: availableWidth=%d\n", aAvailableWidth);
9255
#endif
9256
9257
0
  nsPresContext* presContext = PresContext();
9258
0
9259
0
#ifdef ACCESSIBILITY
9260
0
  // Schedule the update of accessible tree since rendered text might be changed.
9261
0
  if (StyleVisibility()->IsVisible()) {
9262
0
    ReflowTextA11yNotifier(presContext, mContent);
9263
0
  }
9264
0
#endif
9265
0
9266
0
  /////////////////////////////////////////////////////////////////////
9267
0
  // Set up flags and clear out state
9268
0
  /////////////////////////////////////////////////////////////////////
9269
0
9270
0
  // Clear out the reflow state flags in mState. We also clear the whitespace
9271
0
  // flags because this can change whether the frame maps whitespace-only text
9272
0
  // or not. We also clear the flag that tracks whether we had a pending
9273
0
  // reflow request from CharacterDataChanged (since we're reflowing now).
9274
0
  RemoveStateBits(TEXT_REFLOW_FLAGS | TEXT_WHITESPACE_FLAGS);
9275
0
  mReflowRequestedForCharDataChange = false;
9276
0
9277
0
  // Temporarily map all possible content while we construct our new textrun.
9278
0
  // so that when doing reflow our styles prevail over any part of the
9279
0
  // textrun we look at. Note that next-in-flows may be mapping the same
9280
0
  // content; gfxTextRun construction logic will ensure that we take priority.
9281
0
  int32_t maxContentLength = GetInFlowContentLength();
9282
0
9283
0
  // We don't need to reflow if there is no content.
9284
0
  if (!maxContentLength) {
9285
0
    ClearMetrics(aMetrics);
9286
0
    return;
9287
0
  }
9288
0
9289
#ifdef NOISY_BIDI
9290
    printf("Reflowed textframe\n");
9291
#endif
9292
9293
0
  const nsStyleText* textStyle = StyleText();
9294
0
9295
0
  bool atStartOfLine = aLineLayout.LineAtStart();
9296
0
  if (atStartOfLine) {
9297
0
    AddStateBits(TEXT_START_OF_LINE);
9298
0
  }
9299
0
9300
0
  uint32_t flowEndInTextRun;
9301
0
  nsIFrame* lineContainer = aLineLayout.LineContainerFrame();
9302
0
  const nsTextFragment* frag = mContent->GetText();
9303
0
9304
0
  // DOM offsets of the text range we need to measure, after trimming
9305
0
  // whitespace, restricting to first-letter, and restricting preformatted text
9306
0
  // to nearest newline
9307
0
  int32_t length = maxContentLength;
9308
0
  int32_t offset = GetContentOffset();
9309
0
9310
0
  // Restrict preformatted text to the nearest newline
9311
0
  int32_t newLineOffset = -1; // this will be -1 or a content offset
9312
0
  int32_t contentNewLineOffset = -1;
9313
0
  // Pointer to the nsGkAtoms::newline set on this frame's element
9314
0
  NewlineProperty* cachedNewlineOffset = nullptr;
9315
0
  if (textStyle->NewlineIsSignificant(this)) {
9316
0
    cachedNewlineOffset =
9317
0
      mContent->HasFlag(NS_HAS_NEWLINE_PROPERTY)
9318
0
      ? static_cast<NewlineProperty*>(mContent->GetProperty(nsGkAtoms::newline))
9319
0
      : nullptr;
9320
0
    if (cachedNewlineOffset && cachedNewlineOffset->mStartOffset <= offset &&
9321
0
        (cachedNewlineOffset->mNewlineOffset == -1 ||
9322
0
         cachedNewlineOffset->mNewlineOffset >= offset)) {
9323
0
      contentNewLineOffset = cachedNewlineOffset->mNewlineOffset;
9324
0
    } else {
9325
0
      contentNewLineOffset = FindChar(frag, offset,
9326
0
                                      mContent->TextLength() - offset, '\n');
9327
0
    }
9328
0
    if (contentNewLineOffset < offset + length) {
9329
0
      /*
9330
0
        The new line offset could be outside this frame if the frame has been
9331
0
        split by bidi resolution. In that case we won't use it in this reflow
9332
0
        (newLineOffset will remain -1), but we will still cache it in mContent
9333
0
      */
9334
0
      newLineOffset = contentNewLineOffset;
9335
0
    }
9336
0
    if (newLineOffset >= 0) {
9337
0
      length = newLineOffset + 1 - offset;
9338
0
    }
9339
0
  }
9340
0
  if ((atStartOfLine && !textStyle->WhiteSpaceIsSignificant()) ||
9341
0
      (GetStateBits() & TEXT_IS_IN_TOKEN_MATHML)) {
9342
0
    // Skip leading whitespace. Make sure we don't skip a 'pre-line'
9343
0
    // newline if there is one.
9344
0
    int32_t skipLength = newLineOffset >= 0 ? length - 1 : length;
9345
0
    int32_t whitespaceCount =
9346
0
      GetTrimmableWhitespaceCount(frag, offset, skipLength, 1);
9347
0
    if (whitespaceCount) {
9348
0
      offset += whitespaceCount;
9349
0
      length -= whitespaceCount;
9350
0
      // Make sure this frame maps the trimmable whitespace.
9351
0
      if (MOZ_UNLIKELY(offset > GetContentEnd())) {
9352
0
        SetLength(offset - GetContentOffset(), &aLineLayout,
9353
0
                  ALLOW_FRAME_CREATION_AND_DESTRUCTION);
9354
0
      }
9355
0
    }
9356
0
  }
9357
0
9358
0
  // If trimming whitespace left us with nothing to do, return early.
9359
0
  if (length == 0) {
9360
0
    ClearMetrics(aMetrics);
9361
0
    return;
9362
0
  }
9363
0
9364
0
  bool completedFirstLetter = false;
9365
0
  // Layout dependent styles are a problem because we need to reconstruct
9366
0
  // the gfxTextRun based on our layout.
9367
0
  if (aLineLayout.GetInFirstLetter() || aLineLayout.GetInFirstLine()) {
9368
0
    SetLength(maxContentLength, &aLineLayout,
9369
0
              ALLOW_FRAME_CREATION_AND_DESTRUCTION);
9370
0
9371
0
    if (aLineLayout.GetInFirstLetter()) {
9372
0
      // floating first-letter boundaries are significant in textrun
9373
0
      // construction, so clear the textrun out every time we hit a first-letter
9374
0
      // and have changed our length (which controls the first-letter boundary)
9375
0
      ClearTextRuns();
9376
0
      // Find the length of the first-letter. We need a textrun for this.
9377
0
      // REVIEW: maybe-bogus inflation should be ok (fixed below)
9378
0
      gfxSkipCharsIterator iter =
9379
0
        EnsureTextRun(nsTextFrame::eInflated, aDrawTarget,
9380
0
                      lineContainer, aLineLayout.GetLine(),
9381
0
                      &flowEndInTextRun);
9382
0
9383
0
      if (mTextRun) {
9384
0
        int32_t firstLetterLength = length;
9385
0
        if (aLineLayout.GetFirstLetterStyleOK()) {
9386
0
          completedFirstLetter =
9387
0
            FindFirstLetterRange(frag, mTextRun, offset, iter, &firstLetterLength);
9388
0
          if (newLineOffset >= 0) {
9389
0
            // Don't allow a preformatted newline to be part of a first-letter.
9390
0
            firstLetterLength = std::min(firstLetterLength, length - 1);
9391
0
            if (length == 1) {
9392
0
              // There is no text to be consumed by the first-letter before the
9393
0
              // preformatted newline. Note that the first letter is therefore
9394
0
              // complete (FindFirstLetterRange will have returned false).
9395
0
              completedFirstLetter = true;
9396
0
            }
9397
0
          }
9398
0
        } else {
9399
0
          // We're in a first-letter frame's first in flow, so if there
9400
0
          // was a first-letter, we'd be it. However, for one reason
9401
0
          // or another (e.g., preformatted line break before this text),
9402
0
          // we're not actually supposed to have first-letter style. So
9403
0
          // just make a zero-length first-letter.
9404
0
          firstLetterLength = 0;
9405
0
          completedFirstLetter = true;
9406
0
        }
9407
0
        length = firstLetterLength;
9408
0
        if (length) {
9409
0
          AddStateBits(TEXT_FIRST_LETTER);
9410
0
        }
9411
0
        // Change this frame's length to the first-letter length right now
9412
0
        // so that when we rebuild the textrun it will be built with the
9413
0
        // right first-letter boundary
9414
0
        SetLength(offset + length - GetContentOffset(), &aLineLayout,
9415
0
                  ALLOW_FRAME_CREATION_AND_DESTRUCTION);
9416
0
        // Ensure that the textrun will be rebuilt
9417
0
        ClearTextRuns();
9418
0
      }
9419
0
    }
9420
0
  }
9421
0
9422
0
  float fontSizeInflation = nsLayoutUtils::FontSizeInflationFor(this);
9423
0
9424
0
  if (!IsCurrentFontInflation(fontSizeInflation)) {
9425
0
    // FIXME: Ideally, if we already have a text run, we'd move it to be
9426
0
    // the uninflated text run.
9427
0
    ClearTextRun(nullptr, nsTextFrame::eInflated);
9428
0
  }
9429
0
9430
0
  gfxSkipCharsIterator iter =
9431
0
    EnsureTextRun(nsTextFrame::eInflated, aDrawTarget,
9432
0
                  lineContainer, aLineLayout.GetLine(), &flowEndInTextRun);
9433
0
9434
0
  NS_ASSERTION(IsCurrentFontInflation(fontSizeInflation),
9435
0
               "EnsureTextRun should have set font size inflation");
9436
0
9437
0
  if (mTextRun && iter.GetOriginalEnd() < offset + length) {
9438
0
    // The textrun does not map enough text for this frame. This can happen
9439
0
    // when the textrun was ended in the middle of a text node because a
9440
0
    // preformatted newline was encountered, and prev-in-flow frames have
9441
0
    // consumed all the text of the textrun. We need a new textrun.
9442
0
    ClearTextRuns();
9443
0
    iter = EnsureTextRun(nsTextFrame::eInflated, aDrawTarget,
9444
0
                         lineContainer, aLineLayout.GetLine(),
9445
0
                         &flowEndInTextRun);
9446
0
  }
9447
0
9448
0
  if (!mTextRun) {
9449
0
    ClearMetrics(aMetrics);
9450
0
    return;
9451
0
  }
9452
0
9453
0
  NS_ASSERTION(gfxSkipCharsIterator(iter).ConvertOriginalToSkipped(offset + length)
9454
0
                    <= mTextRun->GetLength(),
9455
0
               "Text run does not map enough text for our reflow");
9456
0
9457
0
  /////////////////////////////////////////////////////////////////////
9458
0
  // See how much text should belong to this text frame, and measure it
9459
0
  /////////////////////////////////////////////////////////////////////
9460
0
9461
0
  iter.SetOriginalOffset(offset);
9462
0
  nscoord xOffsetForTabs = (mTextRun->GetFlags2() & nsTextFrameUtils::Flags::TEXT_HAS_TAB) ?
9463
0
    (aLineLayout.GetCurrentFrameInlineDistanceFromBlock() -
9464
0
       lineContainer->GetUsedBorderAndPadding().left)
9465
0
    : -1;
9466
0
  PropertyProvider provider(mTextRun, textStyle, frag, this, iter, length,
9467
0
      lineContainer, xOffsetForTabs, nsTextFrame::eInflated);
9468
0
9469
0
  uint32_t transformedOffset = provider.GetStart().GetSkippedOffset();
9470
0
9471
0
  // The metrics for the text go in here
9472
0
  gfxTextRun::Metrics textMetrics;
9473
0
  gfxFont::BoundingBoxType boundingBoxType =
9474
0
    IsFloatingFirstLetterChild() || IsInitialLetterChild()
9475
0
    ? gfxFont::TIGHT_HINTED_OUTLINE_EXTENTS
9476
0
    : gfxFont::LOOSE_INK_EXTENTS;
9477
0
9478
0
  int32_t limitLength = length;
9479
0
  int32_t forceBreak = aLineLayout.GetForcedBreakPosition(this);
9480
0
  bool forceBreakAfter = false;
9481
0
  if (forceBreak >= length) {
9482
0
    forceBreakAfter = forceBreak == length;
9483
0
    // The break is not within the text considered for this textframe.
9484
0
    forceBreak = -1;
9485
0
  }
9486
0
  if (forceBreak >= 0) {
9487
0
    limitLength = forceBreak;
9488
0
  }
9489
0
  // This is the heart of text reflow right here! We don't know where
9490
0
  // to break, so we need to see how much text fits in the available width.
9491
0
  uint32_t transformedLength;
9492
0
  if (offset + limitLength >= int32_t(frag->GetLength())) {
9493
0
    NS_ASSERTION(offset + limitLength == int32_t(frag->GetLength()),
9494
0
                 "Content offset/length out of bounds");
9495
0
    NS_ASSERTION(flowEndInTextRun >= transformedOffset,
9496
0
                 "Negative flow length?");
9497
0
    transformedLength = flowEndInTextRun - transformedOffset;
9498
0
  } else {
9499
0
    // we're not looking at all the content, so we need to compute the
9500
0
    // length of the transformed substring we're looking at
9501
0
    gfxSkipCharsIterator iter(provider.GetStart());
9502
0
    iter.SetOriginalOffset(offset + limitLength);
9503
0
    transformedLength = iter.GetSkippedOffset() - transformedOffset;
9504
0
  }
9505
0
  uint32_t transformedLastBreak = 0;
9506
0
  bool usedHyphenation;
9507
0
  gfxFloat trimmedWidth = 0;
9508
0
  gfxFloat availWidth = aAvailableWidth;
9509
0
  if (Style()->IsTextCombined()) {
9510
0
    // If text-combine-upright is 'all', we would compress whatever long
9511
0
    // text into ~1em width, so there is no limited on the avail width.
9512
0
    availWidth = std::numeric_limits<gfxFloat>::infinity();
9513
0
  }
9514
0
  bool canTrimTrailingWhitespace = !textStyle->WhiteSpaceIsSignificant() ||
9515
0
                                   (GetStateBits() & TEXT_IS_IN_TOKEN_MATHML);
9516
0
  // allow whitespace to overflow the container
9517
0
  bool whitespaceCanHang = textStyle->WhiteSpaceCanWrapStyle() &&
9518
0
                           textStyle->WhiteSpaceIsSignificant();
9519
0
  gfxBreakPriority breakPriority = aLineLayout.LastOptionalBreakPriority();
9520
0
  gfxTextRun::SuppressBreak suppressBreak = gfxTextRun::eNoSuppressBreak;
9521
0
  bool shouldSuppressLineBreak = ShouldSuppressLineBreak();
9522
0
  if (shouldSuppressLineBreak) {
9523
0
    suppressBreak = gfxTextRun::eSuppressAllBreaks;
9524
0
  } else if (!aLineLayout.LineIsBreakable()) {
9525
0
    suppressBreak = gfxTextRun::eSuppressInitialBreak;
9526
0
  }
9527
0
  uint32_t transformedCharsFit =
9528
0
    mTextRun->BreakAndMeasureText(transformedOffset, transformedLength,
9529
0
                                  (GetStateBits() & TEXT_START_OF_LINE) != 0,
9530
0
                                  availWidth,
9531
0
                                  &provider, suppressBreak,
9532
0
                                  canTrimTrailingWhitespace ? &trimmedWidth : nullptr,
9533
0
                                  whitespaceCanHang,
9534
0
                                  &textMetrics, boundingBoxType,
9535
0
                                  aDrawTarget,
9536
0
                                  &usedHyphenation, &transformedLastBreak,
9537
0
                                  textStyle->WordCanWrap(this), &breakPriority);
9538
0
  if (!length && !textMetrics.mAscent && !textMetrics.mDescent) {
9539
0
    // If we're measuring a zero-length piece of text, update
9540
0
    // the height manually.
9541
0
    nsFontMetrics* fm = provider.GetFontMetrics();
9542
0
    if (fm) {
9543
0
      textMetrics.mAscent = gfxFloat(fm->MaxAscent());
9544
0
      textMetrics.mDescent = gfxFloat(fm->MaxDescent());
9545
0
    }
9546
0
  }
9547
0
  if (GetWritingMode().IsLineInverted()) {
9548
0
    Swap(textMetrics.mAscent, textMetrics.mDescent);
9549
0
    textMetrics.mBoundingBox.y = -textMetrics.mBoundingBox.YMost();
9550
0
  }
9551
0
  // The "end" iterator points to the first character after the string mapped
9552
0
  // by this frame. Basically, its original-string offset is offset+charsFit
9553
0
  // after we've computed charsFit.
9554
0
  gfxSkipCharsIterator end(provider.GetEndHint());
9555
0
  end.SetSkippedOffset(transformedOffset + transformedCharsFit);
9556
0
  int32_t charsFit = end.GetOriginalOffset() - offset;
9557
0
  if (offset + charsFit == newLineOffset) {
9558
0
    // We broke before a trailing preformatted '\n'. The newline should
9559
0
    // be assigned to this frame. Note that newLineOffset will be -1 if
9560
0
    // there was no preformatted newline, so we wouldn't get here in that
9561
0
    // case.
9562
0
    ++charsFit;
9563
0
  }
9564
0
  // That might have taken us beyond our assigned content range (because
9565
0
  // we might have advanced over some skipped chars that extend outside
9566
0
  // this frame), so get back in.
9567
0
  int32_t lastBreak = -1;
9568
0
  if (charsFit >= limitLength) {
9569
0
    charsFit = limitLength;
9570
0
    if (transformedLastBreak != UINT32_MAX) {
9571
0
      // lastBreak is needed.
9572
0
      // This may set lastBreak greater than 'length', but that's OK
9573
0
      lastBreak = end.ConvertSkippedToOriginal(transformedOffset + transformedLastBreak);
9574
0
    }
9575
0
    end.SetOriginalOffset(offset + charsFit);
9576
0
    // If we were forced to fit, and the break position is after a soft hyphen,
9577
0
    // note that this is a hyphenation break.
9578
0
    if ((forceBreak >= 0 || forceBreakAfter) &&
9579
0
        HasSoftHyphenBefore(frag, mTextRun, offset, end)) {
9580
0
      usedHyphenation = true;
9581
0
    }
9582
0
  }
9583
0
  if (usedHyphenation) {
9584
0
    // Fix up metrics to include hyphen
9585
0
    AddHyphenToMetrics(this, mTextRun, &textMetrics, boundingBoxType,
9586
0
                       aDrawTarget);
9587
0
    AddStateBits(TEXT_HYPHEN_BREAK | TEXT_HAS_NONCOLLAPSED_CHARACTERS);
9588
0
  }
9589
0
  if (textMetrics.mBoundingBox.IsEmpty()) {
9590
0
    AddStateBits(TEXT_NO_RENDERED_GLYPHS);
9591
0
  }
9592
0
9593
0
  gfxFloat trimmableWidth = 0;
9594
0
  bool brokeText = forceBreak >= 0 || transformedCharsFit < transformedLength;
9595
0
  if (canTrimTrailingWhitespace) {
9596
0
    // Optimization: if we trimmed trailing whitespace, and we can be sure
9597
0
    // this frame will be at the end of the line, then leave it trimmed off.
9598
0
    // Otherwise we have to undo the trimming, in case we're not at the end of
9599
0
    // the line. (If we actually do end up at the end of the line, we'll have
9600
0
    // to trim it off again in TrimTrailingWhiteSpace, and we'd like to avoid
9601
0
    // having to re-do it.)
9602
0
    if (brokeText ||
9603
0
        (GetStateBits() & TEXT_IS_IN_TOKEN_MATHML)) {
9604
0
      // We're definitely going to break so our trailing whitespace should
9605
0
      // definitely be trimmed. Record that we've already done it.
9606
0
      AddStateBits(TEXT_TRIMMED_TRAILING_WHITESPACE);
9607
0
    } else if (!(GetStateBits() & TEXT_IS_IN_TOKEN_MATHML)) {
9608
0
      // We might not be at the end of the line. (Note that even if this frame
9609
0
      // ends in breakable whitespace, it might not be at the end of the line
9610
0
      // because it might be followed by breakable, but preformatted, whitespace.)
9611
0
      // Undo the trimming.
9612
0
      textMetrics.mAdvanceWidth += trimmedWidth;
9613
0
      trimmableWidth = trimmedWidth;
9614
0
      if (mTextRun->IsRightToLeft()) {
9615
0
        // Space comes before text, so the bounding box is moved to the
9616
0
        // right by trimmdWidth
9617
0
        textMetrics.mBoundingBox.MoveBy(gfxPoint(trimmedWidth, 0));
9618
0
      }
9619
0
    }
9620
0
  }
9621
0
9622
0
  if (!brokeText && lastBreak >= 0) {
9623
0
    // Since everything fit and no break was forced,
9624
0
    // record the last break opportunity
9625
0
    NS_ASSERTION(textMetrics.mAdvanceWidth - trimmableWidth <= availWidth,
9626
0
                 "If the text doesn't fit, and we have a break opportunity, why didn't MeasureText use it?");
9627
0
    MOZ_ASSERT(lastBreak >= offset, "Strange break position");
9628
0
    aLineLayout.NotifyOptionalBreakPosition(this, lastBreak - offset,
9629
0
                                            true, breakPriority);
9630
0
  }
9631
0
9632
0
  int32_t contentLength = offset + charsFit - GetContentOffset();
9633
0
9634
0
  /////////////////////////////////////////////////////////////////////
9635
0
  // Compute output metrics
9636
0
  /////////////////////////////////////////////////////////////////////
9637
0
9638
0
  // first-letter frames should use the tight bounding box metrics for ascent/descent
9639
0
  // for good drop-cap effects
9640
0
  if (GetStateBits() & TEXT_FIRST_LETTER) {
9641
0
    textMetrics.mAscent = std::max(gfxFloat(0.0), -textMetrics.mBoundingBox.Y());
9642
0
    textMetrics.mDescent = std::max(gfxFloat(0.0), textMetrics.mBoundingBox.YMost());
9643
0
  }
9644
0
9645
0
  // Setup metrics for caller
9646
0
  // Disallow negative widths
9647
0
  WritingMode wm = GetWritingMode();
9648
0
  LogicalSize finalSize(wm);
9649
0
  finalSize.ISize(wm) = NSToCoordCeil(std::max(gfxFloat(0.0),
9650
0
                                               textMetrics.mAdvanceWidth));
9651
0
9652
0
  if (transformedCharsFit == 0 && !usedHyphenation) {
9653
0
    aMetrics.SetBlockStartAscent(0);
9654
0
    finalSize.BSize(wm) = 0;
9655
0
  } else if (boundingBoxType != gfxFont::LOOSE_INK_EXTENTS) {
9656
0
    // Use actual text metrics for floating first letter frame.
9657
0
    aMetrics.SetBlockStartAscent(NSToCoordCeil(textMetrics.mAscent));
9658
0
    finalSize.BSize(wm) = aMetrics.BlockStartAscent() +
9659
0
      NSToCoordCeil(textMetrics.mDescent);
9660
0
  } else {
9661
0
    // Otherwise, ascent should contain the overline drawable area.
9662
0
    // And also descent should contain the underline drawable area.
9663
0
    // nsFontMetrics::GetMaxAscent/GetMaxDescent contains them.
9664
0
    nsFontMetrics* fm = provider.GetFontMetrics();
9665
0
    nscoord fontAscent =
9666
0
      wm.IsLineInverted() ? fm->MaxDescent() : fm->MaxAscent();
9667
0
    nscoord fontDescent =
9668
0
      wm.IsLineInverted() ? fm->MaxAscent() : fm->MaxDescent();
9669
0
    aMetrics.SetBlockStartAscent(std::max(NSToCoordCeil(textMetrics.mAscent), fontAscent));
9670
0
    nscoord descent = std::max(NSToCoordCeil(textMetrics.mDescent), fontDescent);
9671
0
    finalSize.BSize(wm) = aMetrics.BlockStartAscent() + descent;
9672
0
  }
9673
0
  if (Style()->IsTextCombined()) {
9674
0
    nsFontMetrics* fm = provider.GetFontMetrics();
9675
0
    gfxFloat width = finalSize.ISize(wm);
9676
0
    gfxFloat em = fm->EmHeight();
9677
0
    // Compress the characters in horizontal axis if necessary.
9678
0
    if (width <= em) {
9679
0
      RemoveProperty(TextCombineScaleFactorProperty());
9680
0
    } else {
9681
0
      SetProperty(TextCombineScaleFactorProperty(), em / width);
9682
0
      finalSize.ISize(wm) = em;
9683
0
    }
9684
0
    // Make the characters be in an 1em square.
9685
0
    if (finalSize.BSize(wm) != em) {
9686
0
      aMetrics.SetBlockStartAscent(aMetrics.BlockStartAscent() +
9687
0
                                   (em - finalSize.BSize(wm)) / 2);
9688
0
      finalSize.BSize(wm) = em;
9689
0
    }
9690
0
  }
9691
0
  aMetrics.SetSize(wm, finalSize);
9692
0
9693
0
  NS_ASSERTION(aMetrics.BlockStartAscent() >= 0,
9694
0
               "Negative ascent???");
9695
0
  NS_ASSERTION((Style()->IsTextCombined()
9696
0
                ? aMetrics.ISize(aMetrics.GetWritingMode())
9697
0
                : aMetrics.BSize(aMetrics.GetWritingMode())) -
9698
0
               aMetrics.BlockStartAscent() >= 0,
9699
0
               "Negative descent???");
9700
0
9701
0
  mAscent = aMetrics.BlockStartAscent();
9702
0
9703
0
  // Handle text that runs outside its normal bounds.
9704
0
  nsRect boundingBox = RoundOut(textMetrics.mBoundingBox);
9705
0
  if (mTextRun->IsVertical()) {
9706
0
    // Swap line-relative textMetrics dimensions to physical coordinates.
9707
0
    Swap(boundingBox.x, boundingBox.y);
9708
0
    Swap(boundingBox.width, boundingBox.height);
9709
0
    if (GetWritingMode().IsVerticalRL()) {
9710
0
      boundingBox.x = -boundingBox.XMost();
9711
0
      boundingBox.x += aMetrics.Width() - mAscent;
9712
0
    } else {
9713
0
      boundingBox.x += mAscent;
9714
0
    }
9715
0
  } else {
9716
0
    boundingBox.y += mAscent;
9717
0
  }
9718
0
  aMetrics.SetOverflowAreasToDesiredBounds();
9719
0
  aMetrics.VisualOverflow().UnionRect(aMetrics.VisualOverflow(), boundingBox);
9720
0
9721
0
  // When we have text decorations, we don't need to compute their overflow now
9722
0
  // because we're guaranteed to do it later
9723
0
  // (see nsLineLayout::RelativePositionFrames)
9724
0
  UnionAdditionalOverflow(presContext, aLineLayout.LineContainerRI()->mFrame,
9725
0
                          provider, &aMetrics.VisualOverflow(), false);
9726
0
9727
0
  /////////////////////////////////////////////////////////////////////
9728
0
  // Clean up, update state
9729
0
  /////////////////////////////////////////////////////////////////////
9730
0
9731
0
  // If all our characters are discarded or collapsed, then trimmable width
9732
0
  // from the last textframe should be preserved. Otherwise the trimmable width
9733
0
  // from this textframe overrides. (Currently in CSS trimmable width can be
9734
0
  // at most one space so there's no way for trimmable width from a previous
9735
0
  // frame to accumulate with trimmable width from this frame.)
9736
0
  if (transformedCharsFit > 0) {
9737
0
    aLineLayout.SetTrimmableISize(NSToCoordFloor(trimmableWidth));
9738
0
    AddStateBits(TEXT_HAS_NONCOLLAPSED_CHARACTERS);
9739
0
  }
9740
0
  bool breakAfter = forceBreakAfter;
9741
0
  if (!shouldSuppressLineBreak) {
9742
0
    if (charsFit > 0 && charsFit == length &&
9743
0
        textStyle->mHyphens != StyleHyphens::None &&
9744
0
        HasSoftHyphenBefore(frag, mTextRun, offset, end)) {
9745
0
      bool fits =
9746
0
        textMetrics.mAdvanceWidth + provider.GetHyphenWidth() <= availWidth;
9747
0
      // Record a potential break after final soft hyphen
9748
0
      aLineLayout.NotifyOptionalBreakPosition(this, length, fits,
9749
0
                                              gfxBreakPriority::eNormalBreak);
9750
0
    }
9751
0
    // length == 0 means either the text is empty or it's all collapsed away
9752
0
    bool emptyTextAtStartOfLine = atStartOfLine && length == 0;
9753
0
    if (!breakAfter && charsFit == length && !emptyTextAtStartOfLine &&
9754
0
        transformedOffset + transformedLength == mTextRun->GetLength() &&
9755
0
        (mTextRun->GetFlags2() & nsTextFrameUtils::Flags::TEXT_HAS_TRAILING_BREAK)) {
9756
0
      // We placed all the text in the textrun and we have a break opportunity
9757
0
      // at the end of the textrun. We need to record it because the following
9758
0
      // content may not care about nsLineBreaker.
9759
0
9760
0
      // Note that because we didn't break, we can be sure that (thanks to the
9761
0
      // code up above) textMetrics.mAdvanceWidth includes the width of any
9762
0
      // trailing whitespace. So we need to subtract trimmableWidth here
9763
0
      // because if we did break at this point, that much width would be
9764
0
      // trimmed.
9765
0
      if (textMetrics.mAdvanceWidth - trimmableWidth > availWidth) {
9766
0
        breakAfter = true;
9767
0
      } else {
9768
0
        aLineLayout.NotifyOptionalBreakPosition(this, length, true,
9769
0
                                                gfxBreakPriority::eNormalBreak);
9770
0
      }
9771
0
    }
9772
0
  }
9773
0
9774
0
  // Compute reflow status
9775
0
  if (contentLength != maxContentLength) {
9776
0
    aStatus.SetIncomplete();
9777
0
  }
9778
0
9779
0
  if (charsFit == 0 && length > 0 && !usedHyphenation) {
9780
0
    // Couldn't place any text
9781
0
    aStatus.SetInlineLineBreakBeforeAndReset();
9782
0
  } else if (contentLength > 0 && mContentOffset + contentLength - 1 == newLineOffset) {
9783
0
    // Ends in \n
9784
0
    aStatus.SetInlineLineBreakAfter();
9785
0
    aLineLayout.SetLineEndsInBR(true);
9786
0
  } else if (breakAfter) {
9787
0
    aStatus.SetInlineLineBreakAfter();
9788
0
  }
9789
0
  if (completedFirstLetter) {
9790
0
    aLineLayout.SetFirstLetterStyleOK(false);
9791
0
    aStatus.SetFirstLetterComplete();
9792
0
  }
9793
0
9794
0
  // Updated the cached NewlineProperty, or delete it.
9795
0
  if (contentLength < maxContentLength &&
9796
0
      textStyle->NewlineIsSignificant(this) &&
9797
0
      (contentNewLineOffset < 0 ||
9798
0
       mContentOffset + contentLength <= contentNewLineOffset)) {
9799
0
    if (!cachedNewlineOffset) {
9800
0
      cachedNewlineOffset = new NewlineProperty;
9801
0
      if (NS_FAILED(mContent->SetProperty(nsGkAtoms::newline, cachedNewlineOffset,
9802
0
                                          nsINode::DeleteProperty<NewlineProperty>))) {
9803
0
        delete cachedNewlineOffset;
9804
0
        cachedNewlineOffset = nullptr;
9805
0
      }
9806
0
      mContent->SetFlags(NS_HAS_NEWLINE_PROPERTY);
9807
0
    }
9808
0
    if (cachedNewlineOffset) {
9809
0
      cachedNewlineOffset->mStartOffset = offset;
9810
0
      cachedNewlineOffset->mNewlineOffset = contentNewLineOffset;
9811
0
    }
9812
0
  } else if (cachedNewlineOffset) {
9813
0
    mContent->DeleteProperty(nsGkAtoms::newline);
9814
0
    mContent->UnsetFlags(NS_HAS_NEWLINE_PROPERTY);
9815
0
  }
9816
0
9817
0
  // Compute space and letter counts for justification, if required
9818
0
  if (!textStyle->WhiteSpaceIsSignificant() &&
9819
0
      (lineContainer->StyleText()->mTextAlign == NS_STYLE_TEXT_ALIGN_JUSTIFY ||
9820
0
       lineContainer->StyleText()->mTextAlignLast == NS_STYLE_TEXT_ALIGN_JUSTIFY ||
9821
0
       shouldSuppressLineBreak) &&
9822
0
      !nsSVGUtils::IsInSVGTextSubtree(lineContainer)) {
9823
0
    AddStateBits(TEXT_JUSTIFICATION_ENABLED);
9824
0
    Range range(uint32_t(offset), uint32_t(offset + charsFit));
9825
0
    aLineLayout.SetJustificationInfo(provider.ComputeJustification(range));
9826
0
  }
9827
0
9828
0
  SetLength(contentLength, &aLineLayout, ALLOW_FRAME_CREATION_AND_DESTRUCTION);
9829
0
9830
0
  InvalidateFrame();
9831
0
9832
#ifdef NOISY_REFLOW
9833
  ListTag(stdout);
9834
  printf(": desiredSize=%d,%d(b=%d) status=%x\n",
9835
         aMetrics.Width(), aMetrics.Height(), aMetrics.BlockStartAscent(),
9836
         aStatus);
9837
#endif
9838
}
9839
9840
/* virtual */ bool
9841
nsTextFrame::CanContinueTextRun() const
9842
0
{
9843
0
  // We can continue a text run through a text frame
9844
0
  return true;
9845
0
}
9846
9847
nsTextFrame::TrimOutput
9848
nsTextFrame::TrimTrailingWhiteSpace(DrawTarget* aDrawTarget)
9849
0
{
9850
0
  MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_IS_DIRTY | NS_FRAME_FIRST_REFLOW),
9851
0
             "frame should have been reflowed");
9852
0
9853
0
  TrimOutput result;
9854
0
  result.mChanged = false;
9855
0
  result.mDeltaWidth = 0;
9856
0
9857
0
  AddStateBits(TEXT_END_OF_LINE);
9858
0
9859
0
  if (!GetTextRun(nsTextFrame::eInflated)) {
9860
0
    // If reflow didn't create a textrun, there must have been no content once
9861
0
    // leading whitespace was trimmed, so nothing more to do here.
9862
0
    return result;
9863
0
  }
9864
0
9865
0
  int32_t contentLength = GetContentLength();
9866
0
  if (!contentLength)
9867
0
    return result;
9868
0
9869
0
  gfxSkipCharsIterator start =
9870
0
    EnsureTextRun(nsTextFrame::eInflated, aDrawTarget);
9871
0
  NS_ENSURE_TRUE(mTextRun, result);
9872
0
9873
0
  uint32_t trimmedStart = start.GetSkippedOffset();
9874
0
9875
0
  const nsTextFragment* frag = mContent->GetText();
9876
0
  TrimmedOffsets trimmed = GetTrimmedOffsets(frag, true);
9877
0
  gfxSkipCharsIterator trimmedEndIter = start;
9878
0
  const nsStyleText* textStyle = StyleText();
9879
0
  gfxFloat delta = 0;
9880
0
  uint32_t trimmedEnd = trimmedEndIter.ConvertOriginalToSkipped(trimmed.GetEnd());
9881
0
9882
0
  if (!(GetStateBits() & TEXT_TRIMMED_TRAILING_WHITESPACE) &&
9883
0
      trimmed.GetEnd() < GetContentEnd()) {
9884
0
    gfxSkipCharsIterator end = trimmedEndIter;
9885
0
    uint32_t endOffset = end.ConvertOriginalToSkipped(GetContentOffset() + contentLength);
9886
0
    if (trimmedEnd < endOffset) {
9887
0
      // We can't be dealing with tabs here ... they wouldn't be trimmed. So it's
9888
0
      // OK to pass null for the line container.
9889
0
      PropertyProvider provider(mTextRun, textStyle, frag, this, start, contentLength,
9890
0
                                nullptr, 0, nsTextFrame::eInflated);
9891
0
      delta = mTextRun->
9892
0
        GetAdvanceWidth(Range(trimmedEnd, endOffset), &provider);
9893
0
      result.mChanged = true;
9894
0
    }
9895
0
  }
9896
0
9897
0
  gfxFloat advanceDelta;
9898
0
  mTextRun->SetLineBreaks(Range(trimmedStart, trimmedEnd),
9899
0
                          (GetStateBits() & TEXT_START_OF_LINE) != 0, true,
9900
0
                          &advanceDelta);
9901
0
  if (advanceDelta != 0) {
9902
0
    result.mChanged = true;
9903
0
  }
9904
0
9905
0
  // aDeltaWidth is *subtracted* from our width.
9906
0
  // If advanceDelta is positive then setting the line break made us longer,
9907
0
  // so aDeltaWidth could go negative.
9908
0
  result.mDeltaWidth = NSToCoordFloor(delta - advanceDelta);
9909
0
  // If aDeltaWidth goes negative, that means this frame might not actually fit
9910
0
  // anymore!!! We need higher level line layout to recover somehow.
9911
0
  // If it's because the frame has a soft hyphen that is now being displayed,
9912
0
  // this should actually be OK, because our reflow recorded the break
9913
0
  // opportunity that allowed the soft hyphen to be used, and we wouldn't
9914
0
  // have recorded the opportunity unless the hyphen fit (or was the first
9915
0
  // opportunity on the line).
9916
0
  // Otherwise this can/ really only happen when we have glyphs with special
9917
0
  // shapes at the end of lines, I think. Breaking inside a kerning pair won't
9918
0
  // do it because that would mean we broke inside this textrun, and
9919
0
  // BreakAndMeasureText should make sure the resulting shaped substring fits.
9920
0
  // Maybe if we passed a maxTextLength? But that only happens at direction
9921
0
  // changes (so we wouldn't kern across the boundary) or for first-letter
9922
0
  // (which always fits because it starts the line!).
9923
0
  NS_WARNING_ASSERTION(result.mDeltaWidth >= 0,
9924
0
                       "Negative deltawidth, something odd is happening");
9925
0
9926
#ifdef NOISY_TRIM
9927
  ListTag(stdout);
9928
  printf(": trim => %d\n", result.mDeltaWidth);
9929
#endif
9930
  return result;
9931
0
}
9932
9933
nsOverflowAreas
9934
nsTextFrame::RecomputeOverflow(nsIFrame* aBlockFrame)
9935
0
{
9936
0
  nsRect bounds(nsPoint(0, 0), GetSize());
9937
0
  nsOverflowAreas result(bounds, bounds);
9938
0
9939
0
  gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
9940
0
  if (!mTextRun)
9941
0
    return result;
9942
0
9943
0
  PropertyProvider provider(this, iter, nsTextFrame::eInflated);
9944
0
  // Don't trim trailing space, in case we need to paint it as selected.
9945
0
  provider.InitializeForDisplay(false);
9946
0
9947
0
  gfxTextRun::Metrics textMetrics =
9948
0
    mTextRun->MeasureText(ComputeTransformedRange(provider),
9949
0
                          gfxFont::LOOSE_INK_EXTENTS, nullptr,
9950
0
                          &provider);
9951
0
  if (GetWritingMode().IsLineInverted()) {
9952
0
    textMetrics.mBoundingBox.y = -textMetrics.mBoundingBox.YMost();
9953
0
  }
9954
0
  nsRect boundingBox = RoundOut(textMetrics.mBoundingBox);
9955
0
  boundingBox += nsPoint(0, mAscent);
9956
0
  if (mTextRun->IsVertical()) {
9957
0
    // Swap line-relative textMetrics dimensions to physical coordinates.
9958
0
    Swap(boundingBox.x, boundingBox.y);
9959
0
    Swap(boundingBox.width, boundingBox.height);
9960
0
  }
9961
0
  nsRect &vis = result.VisualOverflow();
9962
0
  vis.UnionRect(vis, boundingBox);
9963
0
  UnionAdditionalOverflow(PresContext(), aBlockFrame, provider, &vis, true);
9964
0
  return result;
9965
0
}
9966
9967
static void TransformChars(nsTextFrame* aFrame, const nsStyleText* aStyle,
9968
                           const gfxTextRun* aTextRun, uint32_t aSkippedOffset,
9969
                           const nsTextFragment* aFrag, int32_t aFragOffset,
9970
                           int32_t aFragLen, nsAString& aOut)
9971
0
{
9972
0
  nsAutoString fragString;
9973
0
  char16_t* out;
9974
0
  if (aStyle->mTextTransform == NS_STYLE_TEXT_TRANSFORM_NONE) {
9975
0
    // No text-transform, so we can copy directly to the output string.
9976
0
    aOut.SetLength(aOut.Length() + aFragLen);
9977
0
    out = aOut.EndWriting() - aFragLen;
9978
0
  } else {
9979
0
    // Use a temporary string as source for the transform.
9980
0
    fragString.SetLength(aFragLen);
9981
0
    out = fragString.BeginWriting();
9982
0
  }
9983
0
9984
0
  // Copy the text, with \n and \t replaced by <space> if appropriate.
9985
0
  for (int32_t i = 0; i < aFragLen; ++i) {
9986
0
    char16_t ch = aFrag->CharAt(aFragOffset + i);
9987
0
    if ((ch == '\n' && !aStyle->NewlineIsSignificant(aFrame)) ||
9988
0
        (ch == '\t' && !aStyle->TabIsSignificant())) {
9989
0
      ch = ' ';
9990
0
    }
9991
0
    out[i] = ch;
9992
0
  }
9993
0
9994
0
  if (aStyle->mTextTransform != NS_STYLE_TEXT_TRANSFORM_NONE) {
9995
0
    MOZ_ASSERT(aTextRun->GetFlags2() & nsTextFrameUtils::Flags::TEXT_IS_TRANSFORMED);
9996
0
    if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::TEXT_IS_TRANSFORMED) {
9997
0
      // Apply text-transform according to style in the transformed run.
9998
0
      auto transformedTextRun =
9999
0
        static_cast<const nsTransformedTextRun*>(aTextRun);
10000
0
      nsAutoString convertedString;
10001
0
      AutoTArray<bool,50> charsToMergeArray;
10002
0
      AutoTArray<bool,50> deletedCharsArray;
10003
0
      nsCaseTransformTextRunFactory::TransformString(fragString,
10004
0
                                                     convertedString,
10005
0
                                                     false, nullptr,
10006
0
                                                     charsToMergeArray,
10007
0
                                                     deletedCharsArray,
10008
0
                                                     transformedTextRun,
10009
0
                                                     aSkippedOffset);
10010
0
      aOut.Append(convertedString);
10011
0
    } else {
10012
0
      // Should not happen (see assertion above), but as a fallback...
10013
0
      aOut.Append(fragString);
10014
0
    }
10015
0
  }
10016
0
}
10017
10018
static bool
10019
LineEndsInHardLineBreak(nsTextFrame* aFrame, nsBlockFrame* aLineContainer)
10020
0
{
10021
0
  bool foundValidLine;
10022
0
  nsBlockInFlowLineIterator iter(aLineContainer, aFrame, &foundValidLine);
10023
0
  if (!foundValidLine) {
10024
0
    NS_ERROR("Invalid line!");
10025
0
    return true;
10026
0
  }
10027
0
  return !iter.GetLine()->IsLineWrapped();
10028
0
}
10029
10030
nsIFrame::RenderedText
10031
nsTextFrame::GetRenderedText(uint32_t aStartOffset,
10032
                             uint32_t aEndOffset,
10033
                             TextOffsetType aOffsetType,
10034
                             TrailingWhitespace aTrimTrailingWhitespace)
10035
0
{
10036
0
  MOZ_ASSERT(aStartOffset <= aEndOffset, "bogus offsets");
10037
0
  MOZ_ASSERT(!GetPrevContinuation() ||
10038
0
             (aOffsetType == TextOffsetType::OFFSETS_IN_CONTENT_TEXT &&
10039
0
              aStartOffset >= (uint32_t)GetContentOffset() &&
10040
0
              aEndOffset <= (uint32_t)GetContentEnd()),
10041
0
             "Must be called on first-in-flow, or content offsets must be "
10042
0
             "given and be within this frame.");
10043
0
10044
0
  // The handling of offsets could be more efficient...
10045
0
  RenderedText result;
10046
0
  nsBlockFrame* lineContainer = nullptr;
10047
0
  nsTextFrame* textFrame;
10048
0
  const nsTextFragment* textFrag = mContent->GetText();
10049
0
  uint32_t offsetInRenderedString = 0;
10050
0
  bool haveOffsets = false;
10051
0
10052
0
  Maybe<nsBlockFrame::AutoLineCursorSetup> autoLineCursor;
10053
0
  for (textFrame = this; textFrame;
10054
0
       textFrame = textFrame->GetNextContinuation()) {
10055
0
    if (textFrame->GetStateBits() & NS_FRAME_IS_DIRTY) {
10056
0
      // We don't trust dirty frames, especially when computing rendered text.
10057
0
      break;
10058
0
    }
10059
0
10060
0
    // Ensure the text run and grab the gfxSkipCharsIterator for it
10061
0
    gfxSkipCharsIterator iter =
10062
0
      textFrame->EnsureTextRun(nsTextFrame::eInflated);
10063
0
    if (!textFrame->mTextRun) {
10064
0
      break;
10065
0
    }
10066
0
    gfxSkipCharsIterator tmpIter = iter;
10067
0
10068
0
    // Whether we need to trim whitespaces after the text frame.
10069
0
    bool trimAfter;
10070
0
    if (!textFrame->IsAtEndOfLine() ||
10071
0
        aTrimTrailingWhitespace !=
10072
0
          TrailingWhitespace::TRIM_TRAILING_WHITESPACE) {
10073
0
      trimAfter = false;
10074
0
    } else if (nsBlockFrame* thisLc =
10075
0
               do_QueryFrame(FindLineContainer(textFrame))) {
10076
0
      if (thisLc != lineContainer) {
10077
0
        // Setup line cursor when needed.
10078
0
        lineContainer = thisLc;
10079
0
        autoLineCursor.reset();
10080
0
        autoLineCursor.emplace(lineContainer);
10081
0
      }
10082
0
      trimAfter = LineEndsInHardLineBreak(textFrame, lineContainer);
10083
0
    } else {
10084
0
      // Weird situation where we have a line layout without a block.
10085
0
      // No soft breaks occur in this situation.
10086
0
      trimAfter = true;
10087
0
    }
10088
0
10089
0
    // Skip to the start of the text run, past ignored chars at start of line
10090
0
    TrimmedOffsets trimmedOffsets =
10091
0
        textFrame->GetTrimmedOffsets(textFrag, trimAfter);
10092
0
    bool trimmedSignificantNewline =
10093
0
        trimmedOffsets.GetEnd() < GetContentEnd() &&
10094
0
        HasSignificantTerminalNewline();
10095
0
    uint32_t skippedToRenderedStringOffset = offsetInRenderedString -
10096
0
        tmpIter.ConvertOriginalToSkipped(trimmedOffsets.mStart);
10097
0
    uint32_t nextOffsetInRenderedString =
10098
0
        tmpIter.ConvertOriginalToSkipped(trimmedOffsets.GetEnd()) +
10099
0
        (trimmedSignificantNewline ? 1 : 0) + skippedToRenderedStringOffset;
10100
0
10101
0
    if (aOffsetType == TextOffsetType::OFFSETS_IN_RENDERED_TEXT) {
10102
0
      if (nextOffsetInRenderedString <= aStartOffset) {
10103
0
        offsetInRenderedString = nextOffsetInRenderedString;
10104
0
        continue;
10105
0
      }
10106
0
      if (!haveOffsets) {
10107
0
        result.mOffsetWithinNodeText =
10108
0
            tmpIter.ConvertSkippedToOriginal(aStartOffset - skippedToRenderedStringOffset);
10109
0
        result.mOffsetWithinNodeRenderedText = aStartOffset;
10110
0
        haveOffsets = true;
10111
0
      }
10112
0
      if (offsetInRenderedString >= aEndOffset) {
10113
0
        break;
10114
0
      }
10115
0
    } else {
10116
0
      if (uint32_t(textFrame->GetContentEnd()) <= aStartOffset) {
10117
0
        offsetInRenderedString = nextOffsetInRenderedString;
10118
0
        continue;
10119
0
      }
10120
0
      if (!haveOffsets) {
10121
0
        result.mOffsetWithinNodeText = aStartOffset;
10122
0
        // Skip trimmed space when computed the rendered text offset.
10123
0
        int32_t clamped = std::max<int32_t>(aStartOffset, trimmedOffsets.mStart);
10124
0
        result.mOffsetWithinNodeRenderedText =
10125
0
            tmpIter.ConvertOriginalToSkipped(clamped) + skippedToRenderedStringOffset;
10126
0
        MOZ_ASSERT(result.mOffsetWithinNodeRenderedText >= offsetInRenderedString &&
10127
0
                   result.mOffsetWithinNodeRenderedText <= INT32_MAX,
10128
0
                   "Bad offset within rendered text");
10129
0
        haveOffsets = true;
10130
0
      }
10131
0
      if (uint32_t(textFrame->mContentOffset) >= aEndOffset) {
10132
0
        break;
10133
0
      }
10134
0
    }
10135
0
10136
0
    int32_t startOffset;
10137
0
    int32_t endOffset;
10138
0
    if (aOffsetType == TextOffsetType::OFFSETS_IN_RENDERED_TEXT) {
10139
0
      startOffset =
10140
0
        tmpIter.ConvertSkippedToOriginal(aStartOffset - skippedToRenderedStringOffset);
10141
0
      endOffset =
10142
0
        tmpIter.ConvertSkippedToOriginal(aEndOffset - skippedToRenderedStringOffset);
10143
0
    } else {
10144
0
      startOffset = aStartOffset;
10145
0
      endOffset = std::min<uint32_t>(INT32_MAX, aEndOffset);
10146
0
    }
10147
0
10148
0
    // If startOffset and/or endOffset are inside of trimmedOffsets' range,
10149
0
    // then clamp the edges of trimmedOffsets accordingly.
10150
0
    int32_t origTrimmedOffsetsEnd = trimmedOffsets.GetEnd();
10151
0
    trimmedOffsets.mStart = std::max<uint32_t>(trimmedOffsets.mStart,
10152
0
        startOffset);
10153
0
    trimmedOffsets.mLength = std::min<uint32_t>(origTrimmedOffsetsEnd,
10154
0
        endOffset) - trimmedOffsets.mStart;
10155
0
    if (trimmedOffsets.mLength <= 0) {
10156
0
      offsetInRenderedString = nextOffsetInRenderedString;
10157
0
      continue;
10158
0
    }
10159
0
10160
0
    const nsStyleText* textStyle = textFrame->StyleText();
10161
0
    iter.SetOriginalOffset(trimmedOffsets.mStart);
10162
0
    while (iter.GetOriginalOffset() < trimmedOffsets.GetEnd()) {
10163
0
      int32_t runLength;
10164
0
      bool isSkipped = iter.IsOriginalCharSkipped(&runLength);
10165
0
      runLength = std::min(runLength,
10166
0
                           trimmedOffsets.GetEnd() - iter.GetOriginalOffset());
10167
0
      if (isSkipped) {
10168
0
        for (int32_t i = 0; i < runLength; ++i) {
10169
0
          char16_t ch = textFrag->CharAt(iter.GetOriginalOffset() + i);
10170
0
          if (ch == CH_SHY) {
10171
0
            // We should preserve soft hyphens. They can't be transformed.
10172
0
            result.mString.Append(ch);
10173
0
          }
10174
0
        }
10175
0
      } else {
10176
0
        TransformChars(textFrame, textStyle, textFrame->mTextRun,
10177
0
                       iter.GetSkippedOffset(), textFrag,
10178
0
                       iter.GetOriginalOffset(), runLength, result.mString);
10179
0
      }
10180
0
      iter.AdvanceOriginal(runLength);
10181
0
    }
10182
0
10183
0
    if (trimmedSignificantNewline && GetContentEnd() <= endOffset) {
10184
0
      // A significant newline was trimmed off (we must be
10185
0
      // white-space:pre-line). Put it back.
10186
0
      result.mString.Append('\n');
10187
0
    }
10188
0
    offsetInRenderedString = nextOffsetInRenderedString;
10189
0
  }
10190
0
10191
0
  if (!haveOffsets) {
10192
0
    result.mOffsetWithinNodeText = textFrag->GetLength();
10193
0
    result.mOffsetWithinNodeRenderedText = offsetInRenderedString;
10194
0
  }
10195
0
  return result;
10196
0
}
10197
10198
/* virtual */ bool
10199
nsTextFrame::IsEmpty()
10200
0
{
10201
0
  NS_ASSERTION(!(mState & TEXT_IS_ONLY_WHITESPACE) ||
10202
0
               !(mState & TEXT_ISNOT_ONLY_WHITESPACE),
10203
0
               "Invalid state");
10204
0
10205
0
  // XXXldb Should this check compatibility mode as well???
10206
0
  const nsStyleText* textStyle = StyleText();
10207
0
  if (textStyle->WhiteSpaceIsSignificant()) {
10208
0
    // XXX shouldn't we return true if the length is zero?
10209
0
    return false;
10210
0
  }
10211
0
10212
0
  if (mState & TEXT_ISNOT_ONLY_WHITESPACE) {
10213
0
    return false;
10214
0
  }
10215
0
10216
0
  if (mState & TEXT_IS_ONLY_WHITESPACE) {
10217
0
    return true;
10218
0
  }
10219
0
10220
0
  bool isEmpty =
10221
0
    IsAllWhitespace(mContent->GetText(),
10222
0
                    textStyle->mWhiteSpace != mozilla::StyleWhiteSpace::PreLine);
10223
0
  AddStateBits(isEmpty ? TEXT_IS_ONLY_WHITESPACE : TEXT_ISNOT_ONLY_WHITESPACE);
10224
0
  return isEmpty;
10225
0
}
10226
10227
#ifdef DEBUG_FRAME_DUMP
10228
// Translate the mapped content into a string that's printable
10229
void
10230
nsTextFrame::ToCString(nsCString& aBuf, int32_t* aTotalContentLength) const
10231
{
10232
  // Get the frames text content
10233
  const nsTextFragment* frag = mContent->GetText();
10234
  if (!frag) {
10235
    return;
10236
  }
10237
10238
  // Compute the total length of the text content.
10239
  *aTotalContentLength = frag->GetLength();
10240
10241
  int32_t contentLength = GetContentLength();
10242
  // Set current fragment and current fragment offset
10243
  if (0 == contentLength) {
10244
    return;
10245
  }
10246
  int32_t fragOffset = GetContentOffset();
10247
  int32_t n = fragOffset + contentLength;
10248
  while (fragOffset < n) {
10249
    char16_t ch = frag->CharAt(fragOffset++);
10250
    if (ch == '\r') {
10251
      aBuf.AppendLiteral("\\r");
10252
    } else if (ch == '\n') {
10253
      aBuf.AppendLiteral("\\n");
10254
    } else if (ch == '\t') {
10255
      aBuf.AppendLiteral("\\t");
10256
    } else if ((ch < ' ') || (ch >= 127)) {
10257
      aBuf.Append(nsPrintfCString("\\u%04x", ch));
10258
    } else {
10259
      aBuf.Append(ch);
10260
    }
10261
  }
10262
}
10263
10264
nsresult
10265
nsTextFrame::GetFrameName(nsAString& aResult) const
10266
{
10267
  MakeFrameName(NS_LITERAL_STRING("Text"), aResult);
10268
  int32_t totalContentLength;
10269
  nsAutoCString tmp;
10270
  ToCString(tmp, &totalContentLength);
10271
  tmp.SetLength(std::min(tmp.Length(), 50u));
10272
  aResult += NS_LITERAL_STRING("\"") + NS_ConvertASCIItoUTF16(tmp) + NS_LITERAL_STRING("\"");
10273
  return NS_OK;
10274
}
10275
10276
void
10277
nsTextFrame::List(FILE* out, const char* aPrefix, uint32_t aFlags) const
10278
{
10279
  nsCString str;
10280
  ListGeneric(str, aPrefix, aFlags);
10281
10282
  str += nsPrintfCString(" [run=%p]", static_cast<void*>(mTextRun));
10283
10284
  // Output the first/last content offset and prev/next in flow info
10285
  bool isComplete = uint32_t(GetContentEnd()) == GetContent()->TextLength();
10286
  str += nsPrintfCString("[%d,%d,%c] ", GetContentOffset(), GetContentLength(),
10287
          isComplete ? 'T':'F');
10288
10289
  if (IsSelected()) {
10290
    str += " SELECTED";
10291
  }
10292
  fprintf_stderr(out, "%s\n", str.get());
10293
}
10294
#endif
10295
10296
void
10297
nsTextFrame::AdjustOffsetsForBidi(int32_t aStart, int32_t aEnd)
10298
0
{
10299
0
  AddStateBits(NS_FRAME_IS_BIDI);
10300
0
  if (mContent->HasFlag(NS_HAS_FLOWLENGTH_PROPERTY)) {
10301
0
    mContent->DeleteProperty(nsGkAtoms::flowlength);
10302
0
    mContent->UnsetFlags(NS_HAS_FLOWLENGTH_PROPERTY);
10303
0
  }
10304
0
10305
0
  /*
10306
0
   * After Bidi resolution we may need to reassign text runs.
10307
0
   * This is called during bidi resolution from the block container, so we
10308
0
   * shouldn't be holding a local reference to a textrun anywhere.
10309
0
   */
10310
0
  ClearTextRuns();
10311
0
10312
0
  nsTextFrame* prev = GetPrevContinuation();
10313
0
  if (prev) {
10314
0
    // the bidi resolver can be very evil when columns/pages are involved. Don't
10315
0
    // let it violate our invariants.
10316
0
    int32_t prevOffset = prev->GetContentOffset();
10317
0
    aStart = std::max(aStart, prevOffset);
10318
0
    aEnd = std::max(aEnd, prevOffset);
10319
0
    prev->ClearTextRuns();
10320
0
  }
10321
0
10322
0
  mContentOffset = aStart;
10323
0
  SetLength(aEnd - aStart, nullptr, 0);
10324
0
}
10325
10326
/**
10327
 * @return true if this text frame ends with a newline character.  It should return
10328
 * false if it is not a text frame.
10329
 */
10330
bool
10331
nsTextFrame::HasSignificantTerminalNewline() const
10332
0
{
10333
0
  return ::HasTerminalNewline(this) && StyleText()->NewlineIsSignificant(this);
10334
0
}
10335
10336
bool
10337
nsTextFrame::IsAtEndOfLine() const
10338
0
{
10339
0
  return (GetStateBits() & TEXT_END_OF_LINE) != 0;
10340
0
}
10341
10342
nscoord
10343
nsTextFrame::GetLogicalBaseline(WritingMode aWM) const
10344
0
{
10345
0
  if (!aWM.IsOrthogonalTo(GetWritingMode())) {
10346
0
    return mAscent;
10347
0
  }
10348
0
10349
0
  // When the text frame has a writing mode orthogonal to the desired
10350
0
  // writing mode, return a baseline coincides its parent frame.
10351
0
  nsIFrame* parent = GetParent();
10352
0
  nsPoint position = GetNormalPosition();
10353
0
  nscoord parentAscent = parent->GetLogicalBaseline(aWM);
10354
0
  if (aWM.IsVerticalRL()) {
10355
0
    nscoord parentDescent = parent->GetSize().width - parentAscent;
10356
0
    nscoord descent = parentDescent - position.x;
10357
0
    return GetSize().width - descent;
10358
0
  }
10359
0
  return parentAscent - (aWM.IsVertical() ? position.x : position.y);
10360
0
}
10361
10362
bool
10363
nsTextFrame::HasAnyNoncollapsedCharacters()
10364
0
{
10365
0
  gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
10366
0
  int32_t offset = GetContentOffset(),
10367
0
          offsetEnd = GetContentEnd();
10368
0
  int32_t skippedOffset = iter.ConvertOriginalToSkipped(offset);
10369
0
  int32_t skippedOffsetEnd = iter.ConvertOriginalToSkipped(offsetEnd);
10370
0
  return skippedOffset != skippedOffsetEnd;
10371
0
}
10372
10373
bool
10374
nsTextFrame::ComputeCustomOverflow(nsOverflowAreas& aOverflowAreas)
10375
0
{
10376
0
  if (GetStateBits() & NS_FRAME_FIRST_REFLOW) {
10377
0
    return true;
10378
0
  }
10379
0
10380
0
  nsIFrame* decorationsBlock;
10381
0
  if (IsFloatingFirstLetterChild()) {
10382
0
    decorationsBlock = GetParent();
10383
0
  } else {
10384
0
    nsIFrame* f = this;
10385
0
    for (;;) {
10386
0
      nsBlockFrame* fBlock = nsLayoutUtils::GetAsBlock(f);
10387
0
      if (fBlock) {
10388
0
        decorationsBlock = fBlock;
10389
0
        break;
10390
0
      }
10391
0
10392
0
      f = f->GetParent();
10393
0
      if (!f) {
10394
0
        NS_ERROR("Couldn't find any block ancestor (for text decorations)");
10395
0
        return nsFrame::ComputeCustomOverflow(aOverflowAreas);
10396
0
      }
10397
0
    }
10398
0
  }
10399
0
10400
0
  aOverflowAreas = RecomputeOverflow(decorationsBlock);
10401
0
  return nsFrame::ComputeCustomOverflow(aOverflowAreas);
10402
0
}
10403
10404
NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(JustificationAssignmentProperty, int32_t)
10405
10406
void
10407
nsTextFrame::AssignJustificationGaps(
10408
    const mozilla::JustificationAssignment& aAssign)
10409
0
{
10410
0
  int32_t encoded = (aAssign.mGapsAtStart << 8) | aAssign.mGapsAtEnd;
10411
0
  static_assert(sizeof(aAssign) == 1,
10412
0
                "The encoding might be broken if JustificationAssignment "
10413
0
                "is larger than 1 byte");
10414
0
  SetProperty(JustificationAssignmentProperty(), encoded);
10415
0
}
10416
10417
mozilla::JustificationAssignment
10418
nsTextFrame::GetJustificationAssignment() const
10419
0
{
10420
0
  int32_t encoded = GetProperty(JustificationAssignmentProperty());
10421
0
  mozilla::JustificationAssignment result;
10422
0
  result.mGapsAtStart = encoded >> 8;
10423
0
  result.mGapsAtEnd = encoded & 0xFF;
10424
0
  return result;
10425
0
}
10426
10427
uint32_t
10428
nsTextFrame::CountGraphemeClusters() const
10429
0
{
10430
0
  const nsTextFragment* frag = GetContent()->GetText();
10431
0
  MOZ_ASSERT(frag, "Text frame must have text fragment");
10432
0
  nsAutoString content;
10433
0
  frag->AppendTo(content, GetContentOffset(), GetContentLength());
10434
0
  return unicode::CountGraphemeClusters(content.Data(), content.Length());
10435
0
}
10436
10437
bool
10438
nsTextFrame::HasNonSuppressedText()
10439
0
{
10440
0
  if (HasAnyStateBits(TEXT_ISNOT_ONLY_WHITESPACE |
10441
0
                      // If we haven't reflowed yet, or are currently doing so,
10442
0
                      // just return true because we can't be sure.
10443
0
                      NS_FRAME_FIRST_REFLOW |
10444
0
                      NS_FRAME_IN_REFLOW)) {
10445
0
    return true;
10446
0
  }
10447
0
10448
0
  if (!GetTextRun(nsTextFrame::eInflated)) {
10449
0
    return false;
10450
0
  }
10451
0
10452
0
  TrimmedOffsets offsets = GetTrimmedOffsets(mContent->GetText(), false);
10453
0
  return offsets.mLength != 0;
10454
0
}