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