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