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