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"
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"
33 #include "nsBlockFrame.h"
34 #include "nsFontMetrics.h"
35 #include "nsSplittableFrame.h"
36 #include "nsLineLayout.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"
45 #include "gfxContext.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"
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"
68 #include "nsCSSRendering.h"
69 #include "nsContentUtils.h"
70 #include "nsLineBreaker.h"
71 #include "nsIFrameInlines.h"
72 #include "mozilla/intl/WordBreaker.h"
73 #include "mozilla/ServoStyleSet.h"
77 #include <type_traits>
79 # include "nsAccessibilityService.h"
82 #include "nsPrintfCString.h"
84 #include "mozilla/gfx/DrawTargetRecording.h"
86 #include "mozilla/UniquePtr.h"
87 #include "mozilla/dom/Element.h"
88 #include "mozilla/LookAndFeel.h"
89 #include "mozilla/ProfilerLabels.h"
103 using namespace mozilla
;
104 using namespace mozilla::dom
;
105 using namespace mozilla::gfx
;
107 typedef mozilla::layout::TextDrawTarget TextDrawTarget
;
109 static bool NeedsToMaskPassword(nsTextFrame
* aFrame
) {
111 MOZ_ASSERT(aFrame
->GetContent());
112 return aFrame
->GetContent()->HasFlag(NS_MAYBE_MASKED
);
116 TabWidth(uint32_t aOffset
, uint32_t aWidth
)
117 : mOffset(aOffset
), mWidth(float(aWidth
)) {}
119 uint32_t mOffset
; // DOM offset relative to the current frame's offset.
120 float mWidth
; // extra space to be added at this position (in app units)
123 struct nsTextFrame::TabWidthStore
{
124 explicit TabWidthStore(int32_t aValidForContentOffset
)
125 : mLimit(0), mValidForContentOffset(aValidForContentOffset
) {}
127 // Apply tab widths to the aSpacing array, which corresponds to characters
128 // beginning at aOffset and has length aLength. (Width records outside this
129 // range will be ignored.)
130 void ApplySpacing(gfxTextRun::PropertyProvider::Spacing
* aSpacing
,
131 uint32_t aOffset
, uint32_t aLength
);
133 // Offset up to which tabs have been measured; positions beyond this have not
134 // been calculated yet but may be appended if needed later. It's a DOM
135 // offset relative to the current frame's offset.
138 // Need to recalc tab offsets if frame content offset differs from this.
139 int32_t mValidForContentOffset
;
141 // A TabWidth record for each tab character measured so far.
142 nsTArray
<TabWidth
> mWidths
;
147 struct TabwidthAdaptor
{
148 const nsTArray
<TabWidth
>& mWidths
;
149 explicit TabwidthAdaptor(const nsTArray
<TabWidth
>& aWidths
)
150 : mWidths(aWidths
) {}
151 uint32_t operator[](size_t aIdx
) const { return mWidths
[aIdx
].mOffset
; }
156 void nsTextFrame::TabWidthStore::ApplySpacing(
157 gfxTextRun::PropertyProvider::Spacing
* aSpacing
, uint32_t aOffset
,
160 const size_t len
= mWidths
.Length();
162 // If aOffset is non-zero, do a binary search to find where to start
163 // processing the tab widths, in case the list is really long. (See bug
165 // We need to start from the first entry where mOffset >= aOffset.
167 mozilla::BinarySearch(TabwidthAdaptor(mWidths
), 0, len
, aOffset
, &i
);
170 uint32_t limit
= aOffset
+ aLength
;
172 const TabWidth
& tw
= mWidths
[i
];
173 if (tw
.mOffset
>= limit
) {
176 aSpacing
[tw
.mOffset
- aOffset
].mAfter
+= tw
.mWidth
;
181 NS_DECLARE_FRAME_PROPERTY_DELETABLE(TabWidthProperty
,
182 nsTextFrame::TabWidthStore
)
184 NS_DECLARE_FRAME_PROPERTY_WITHOUT_DTOR(OffsetToFrameProperty
, nsTextFrame
)
186 NS_DECLARE_FRAME_PROPERTY_RELEASABLE(UninflatedTextRunProperty
, gfxTextRun
)
188 NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(FontSizeInflationProperty
, float)
191 * A glyph observer for the change of a font glyph in a text run.
193 * This is stored in {Simple, Complex}TextRunUserData.
195 class GlyphObserver final
: public gfxFont::GlyphChangeObserver
{
197 GlyphObserver(gfxFont
* aFont
, gfxTextRun
* aTextRun
)
198 : gfxFont::GlyphChangeObserver(aFont
), mTextRun(aTextRun
) {
199 MOZ_ASSERT(aTextRun
->GetUserData());
201 void NotifyGlyphsChanged() override
;
204 gfxTextRun
* mTextRun
;
207 static const nsFrameState TEXT_REFLOW_FLAGS
=
208 TEXT_FIRST_LETTER
| TEXT_START_OF_LINE
| TEXT_END_OF_LINE
|
209 TEXT_HYPHEN_BREAK
| TEXT_TRIMMED_TRAILING_WHITESPACE
|
210 TEXT_JUSTIFICATION_ENABLED
| TEXT_HAS_NONCOLLAPSED_CHARACTERS
|
211 TEXT_SELECTION_UNDERLINE_OVERFLOWED
| TEXT_NO_RENDERED_GLYPHS
;
213 static const nsFrameState TEXT_WHITESPACE_FLAGS
=
214 TEXT_IS_ONLY_WHITESPACE
| TEXT_ISNOT_ONLY_WHITESPACE
;
219 * Text frames delegate work to gfxTextRun objects. The gfxTextRun object
220 * transforms text to positioned glyphs. It can report the geometry of the
221 * glyphs and paint them. Text frames configure gfxTextRuns by providing text,
222 * spacing, language, and other information.
224 * A gfxTextRun can cover more than one DOM text node. This is necessary to
225 * get kerning, ligatures and shaping for text that spans multiple text nodes
226 * but is all the same font.
228 * The userdata for a gfxTextRun object can be:
230 * - A nsTextFrame* in the case a text run maps to only one flow. In this
231 * case, the textrun's user data pointer is a pointer to mStartFrame for that
232 * flow, mDOMOffsetToBeforeTransformOffset is zero, and mContentLength is the
233 * length of the text node.
235 * - A SimpleTextRunUserData in the case a text run maps to one flow, but we
236 * still have to keep a list of glyph observers.
238 * - A ComplexTextRunUserData in the case a text run maps to multiple flows,
239 * but we need to keep a list of glyph observers.
241 * - A TextRunUserData in the case a text run maps multiple flows, but it
242 * doesn't have any glyph observer for changes in SVG fonts.
244 * You can differentiate between the four different cases with the
245 * IsSimpleFlow and MightHaveGlyphChanges flags.
247 * We go to considerable effort to make sure things work even if in-flow
248 * siblings have different ComputedStyles (i.e., first-letter and first-line).
250 * Our convention is that unsigned integer character offsets are offsets into
251 * the transformed string. Signed integer character offsets are offsets into
254 * XXX currently we don't handle hyphenated breaks between text frames where the
255 * hyphen occurs at the end of the first text frame, e.g.
260 * This is our user data for the textrun, when textRun->GetFlags2() has
261 * IsSimpleFlow set, and also MightHaveGlyphChanges.
263 * This allows having an array of observers if there are fonts whose glyphs
264 * might change, but also avoid allocation in the simple case that there aren't.
266 struct SimpleTextRunUserData
{
267 nsTArray
<UniquePtr
<GlyphObserver
>> mGlyphObservers
;
269 explicit SimpleTextRunUserData(nsTextFrame
* aFrame
) : mFrame(aFrame
) {}
273 * We use an array of these objects to record which text frames
274 * are associated with the textrun. mStartFrame is the start of a list of
275 * text frames. Some sequence of its continuations are covered by the textrun.
276 * A content textnode can have at most one TextRunMappedFlow associated with it
277 * for a given textrun.
279 * mDOMOffsetToBeforeTransformOffset is added to DOM offsets for those frames to
280 * obtain the offset into the before-transformation text of the textrun. It can
281 * be positive (when a text node starts in the middle of a text run) or negative
282 * (when a text run starts in the middle of a text node). Of course it can also
285 struct TextRunMappedFlow
{
286 nsTextFrame
* mStartFrame
;
287 int32_t mDOMOffsetToBeforeTransformOffset
;
288 // The text mapped starts at mStartFrame->GetContentOffset() and is this long
289 uint32_t mContentLength
;
293 * This is the type in the gfxTextRun's userdata field in the common case that
294 * the text run maps to multiple flows, but no fonts have been found with
297 * This way, we avoid allocating and constructing the extra nsTArray.
299 struct TextRunUserData
{
301 TextRunMappedFlow
* mMappedFlows
;
303 uint32_t mMappedFlowCount
;
304 uint32_t mLastFlowIndex
;
308 * This is our user data for the textrun, when textRun->GetFlags2() does not
309 * have IsSimpleFlow set and has the MightHaveGlyphChanges flag.
311 struct ComplexTextRunUserData
: public TextRunUserData
{
312 nsTArray
<UniquePtr
<GlyphObserver
>> mGlyphObservers
;
316 * This helper object computes colors used for painting, and also IME
317 * underline information. The data is computed lazily and cached as necessary.
318 * These live for just the duration of one paint operation.
320 class nsTextPaintStyle
{
322 explicit nsTextPaintStyle(nsTextFrame
* aFrame
);
324 void SetResolveColors(bool aResolveColors
) {
325 mResolveColors
= aResolveColors
;
328 nscolor
GetTextColor();
330 // SVG text has its own painting process, so we should never get its stroke
331 // property from here.
332 nscolor
GetWebkitTextStrokeColor() {
333 if (SVGUtils::IsInSVGTextSubtree(mFrame
)) {
336 return mFrame
->StyleText()->mWebkitTextStrokeColor
.CalcColor(mFrame
);
338 float GetWebkitTextStrokeWidth() {
339 if (SVGUtils::IsInSVGTextSubtree(mFrame
)) {
342 nscoord coord
= mFrame
->StyleText()->mWebkitTextStrokeWidth
;
343 return mFrame
->PresContext()->AppUnitsToFloatDevPixels(coord
);
347 * Compute the colors for normally-selected text. Returns false if
348 * the normal selection is not being displayed.
350 bool GetSelectionColors(nscolor
* aForeColor
, nscolor
* aBackColor
);
351 void GetHighlightColors(nscolor
* aForeColor
, nscolor
* aBackColor
);
352 void GetURLSecondaryColor(nscolor
* aForeColor
);
353 void GetIMESelectionColors(int32_t aIndex
, nscolor
* aForeColor
,
354 nscolor
* aBackColor
);
355 // if this returns false, we don't need to draw underline.
356 bool GetSelectionUnderlineForPaint(int32_t aIndex
, nscolor
* aLineColor
,
357 float* aRelativeSize
, uint8_t* aStyle
);
359 // if this returns false, we don't need to draw underline.
360 static bool GetSelectionUnderline(nsPresContext
* aPresContext
, int32_t aIndex
,
361 nscolor
* aLineColor
, float* aRelativeSize
,
364 // if this returns false, no text-shadow was specified for the selection
365 // and the *aShadow parameter was not modified.
366 bool GetSelectionShadow(Span
<const StyleSimpleShadow
>* aShadows
);
368 nsPresContext
* PresContext() const { return mPresContext
; }
378 static int32_t GetUnderlineStyleIndexForSelectionType(
379 SelectionType aSelectionType
) {
380 switch (aSelectionType
) {
381 case SelectionType::eIMERawClause
:
382 return eIndexRawInput
;
383 case SelectionType::eIMESelectedRawClause
:
384 return eIndexSelRawText
;
385 case SelectionType::eIMEConvertedClause
:
386 return eIndexConvText
;
387 case SelectionType::eIMESelectedClause
:
388 return eIndexSelConvText
;
389 case SelectionType::eSpellCheck
:
390 return eIndexSpellChecker
;
392 NS_WARNING("non-IME selection type");
393 return eIndexRawInput
;
397 nscolor
GetSystemFieldForegroundColor();
398 nscolor
GetSystemFieldBackgroundColor();
402 nsPresContext
* mPresContext
;
403 bool mInitCommonColors
;
404 bool mInitSelectionColorsAndShadow
;
409 nscolor mSelectionTextColor
;
410 nscolor mSelectionBGColor
;
411 RefPtr
<ComputedStyle
> mSelectionPseudoStyle
;
415 int32_t mSufficientContrast
;
416 nscolor mFrameBackgroundColor
;
417 nscolor mSystemFieldForegroundColor
;
418 nscolor mSystemFieldBackgroundColor
;
420 // selection colors and underline info, the colors are resolved colors if
421 // mResolveColors is true (which is the default), i.e., the foreground color
422 // and background color are swapped if it's needed. And also line color will
423 // be resolved from them.
424 struct nsSelectionStyle
{
428 nscolor mUnderlineColor
;
429 uint8_t mUnderlineStyle
;
430 float mUnderlineRelativeSize
;
432 nsSelectionStyle mSelectionStyle
[5];
434 // Color initializations
435 void InitCommonColors();
436 bool InitSelectionColorsAndShadow();
438 nsSelectionStyle
* GetSelectionStyle(int32_t aIndex
);
439 void InitSelectionStyle(int32_t aIndex
);
441 // Ensures sufficient contrast between the frame background color and the
442 // selection background color, and swaps the selection text and background
443 // colors accordingly.
444 // Only used on platforms where mSelectionTextColor != NS_DONT_CHANGE_COLOR
445 bool EnsureSufficientContrast(nscolor
* aForeColor
, nscolor
* aBackColor
);
447 nscolor
GetResolvedForeColor(nscolor aColor
, nscolor aDefaultForeColor
,
451 static TextRunUserData
* CreateUserData(uint32_t aMappedFlowCount
) {
452 TextRunUserData
* data
= static_cast<TextRunUserData
*>(moz_xmalloc(
453 sizeof(TextRunUserData
) + aMappedFlowCount
* sizeof(TextRunMappedFlow
)));
455 data
->mMappedFlows
= reinterpret_cast<TextRunMappedFlow
*>(data
+ 1);
457 data
->mMappedFlowCount
= aMappedFlowCount
;
458 data
->mLastFlowIndex
= 0;
462 static void DestroyUserData(TextRunUserData
* aUserData
) {
468 static ComplexTextRunUserData
* CreateComplexUserData(
469 uint32_t aMappedFlowCount
) {
470 ComplexTextRunUserData
* data
= static_cast<ComplexTextRunUserData
*>(
471 moz_xmalloc(sizeof(ComplexTextRunUserData
) +
472 aMappedFlowCount
* sizeof(TextRunMappedFlow
)));
473 new (data
) ComplexTextRunUserData();
475 data
->mMappedFlows
= reinterpret_cast<TextRunMappedFlow
*>(data
+ 1);
477 data
->mMappedFlowCount
= aMappedFlowCount
;
478 data
->mLastFlowIndex
= 0;
482 static void DestroyComplexUserData(ComplexTextRunUserData
* aUserData
) {
484 aUserData
->~ComplexTextRunUserData();
489 static void DestroyTextRunUserData(gfxTextRun
* aTextRun
) {
490 MOZ_ASSERT(aTextRun
->GetUserData());
491 if (aTextRun
->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow
) {
492 if (aTextRun
->GetFlags2() &
493 nsTextFrameUtils::Flags::MightHaveGlyphChanges
) {
494 delete static_cast<SimpleTextRunUserData
*>(aTextRun
->GetUserData());
497 if (aTextRun
->GetFlags2() &
498 nsTextFrameUtils::Flags::MightHaveGlyphChanges
) {
499 DestroyComplexUserData(
500 static_cast<ComplexTextRunUserData
*>(aTextRun
->GetUserData()));
502 DestroyUserData(static_cast<TextRunUserData
*>(aTextRun
->GetUserData()));
505 aTextRun
->ClearFlagBits(nsTextFrameUtils::Flags::MightHaveGlyphChanges
);
506 aTextRun
->SetUserData(nullptr);
509 static TextRunMappedFlow
* GetMappedFlows(const gfxTextRun
* aTextRun
) {
510 MOZ_ASSERT(aTextRun
->GetUserData(), "UserData must exist.");
511 MOZ_ASSERT(!(aTextRun
->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow
),
512 "The method should not be called for simple flows.");
513 TextRunMappedFlow
* flows
;
514 if (aTextRun
->GetFlags2() & nsTextFrameUtils::Flags::MightHaveGlyphChanges
) {
515 flows
= reinterpret_cast<TextRunMappedFlow
*>(
516 static_cast<ComplexTextRunUserData
*>(aTextRun
->GetUserData()) + 1);
518 flows
= reinterpret_cast<TextRunMappedFlow
*>(
519 static_cast<TextRunUserData
*>(aTextRun
->GetUserData()) + 1);
522 static_cast<TextRunUserData
*>(aTextRun
->GetUserData())->mMappedFlows
==
524 "GetMappedFlows should return the same pointer as mMappedFlows.");
529 * These are utility functions just for helping with the complexity related with
530 * the text runs user data.
532 static nsTextFrame
* GetFrameForSimpleFlow(const gfxTextRun
* aTextRun
) {
533 MOZ_ASSERT(aTextRun
->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow
,
534 "Not so simple flow?");
535 if (aTextRun
->GetFlags2() & nsTextFrameUtils::Flags::MightHaveGlyphChanges
) {
536 return static_cast<SimpleTextRunUserData
*>(aTextRun
->GetUserData())->mFrame
;
539 return static_cast<nsTextFrame
*>(aTextRun
->GetUserData());
543 * Remove |aTextRun| from the frame continuation chain starting at
544 * |aStartContinuation| if non-null, otherwise starting at |aFrame|.
545 * Unmark |aFrame| as a text run owner if it's the frame we start at.
546 * Return true if |aStartContinuation| is non-null and was found
547 * in the next-continuation chain of |aFrame|.
549 static bool ClearAllTextRunReferences(nsTextFrame
* aFrame
, gfxTextRun
* aTextRun
,
550 nsTextFrame
* aStartContinuation
,
551 nsFrameState aWhichTextRunState
) {
552 MOZ_ASSERT(aFrame
, "null frame");
553 MOZ_ASSERT(!aStartContinuation
||
554 (!aStartContinuation
->GetTextRun(nsTextFrame::eInflated
) ||
555 aStartContinuation
->GetTextRun(nsTextFrame::eInflated
) ==
557 (!aStartContinuation
->GetTextRun(nsTextFrame::eNotInflated
) ||
558 aStartContinuation
->GetTextRun(nsTextFrame::eNotInflated
) ==
560 "wrong aStartContinuation for this text run");
562 if (!aStartContinuation
|| aStartContinuation
== aFrame
) {
563 aFrame
->RemoveStateBits(aWhichTextRunState
);
566 NS_ASSERTION(aFrame
->IsTextFrame(), "Bad frame");
567 aFrame
= aFrame
->GetNextContinuation();
568 } while (aFrame
&& aFrame
!= aStartContinuation
);
570 bool found
= aStartContinuation
== aFrame
;
572 NS_ASSERTION(aFrame
->IsTextFrame(), "Bad frame");
573 if (!aFrame
->RemoveTextRun(aTextRun
)) {
576 aFrame
= aFrame
->GetNextContinuation();
579 MOZ_ASSERT(!found
|| aStartContinuation
, "how did we find null?");
584 * Kill all references to |aTextRun| starting at |aStartContinuation|.
585 * It could be referenced by any of its owners, and all their in-flows.
586 * If |aStartContinuation| is null then process all userdata frames
587 * and their continuations.
588 * @note the caller is expected to take care of possibly destroying the
589 * text run if all userdata frames were reset (userdata is deallocated
590 * by this function though). The caller can detect this has occured by
591 * checking |aTextRun->GetUserData() == nullptr|.
593 static void UnhookTextRunFromFrames(gfxTextRun
* aTextRun
,
594 nsTextFrame
* aStartContinuation
) {
595 if (!aTextRun
->GetUserData()) {
599 if (aTextRun
->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow
) {
600 nsTextFrame
* userDataFrame
= GetFrameForSimpleFlow(aTextRun
);
601 nsFrameState whichTextRunState
=
602 userDataFrame
->GetTextRun(nsTextFrame::eInflated
) == aTextRun
603 ? TEXT_IN_TEXTRUN_USER_DATA
604 : TEXT_IN_UNINFLATED_TEXTRUN_USER_DATA
;
605 DebugOnly
<bool> found
= ClearAllTextRunReferences(
606 userDataFrame
, aTextRun
, aStartContinuation
, whichTextRunState
);
607 NS_ASSERTION(!aStartContinuation
|| found
,
608 "aStartContinuation wasn't found in simple flow text run");
609 if (!userDataFrame
->HasAnyStateBits(whichTextRunState
)) {
610 DestroyTextRunUserData(aTextRun
);
613 auto userData
= static_cast<TextRunUserData
*>(aTextRun
->GetUserData());
614 TextRunMappedFlow
* userMappedFlows
= GetMappedFlows(aTextRun
);
615 int32_t destroyFromIndex
= aStartContinuation
? -1 : 0;
616 for (uint32_t i
= 0; i
< userData
->mMappedFlowCount
; ++i
) {
617 nsTextFrame
* userDataFrame
= userMappedFlows
[i
].mStartFrame
;
618 nsFrameState whichTextRunState
=
619 userDataFrame
->GetTextRun(nsTextFrame::eInflated
) == aTextRun
620 ? TEXT_IN_TEXTRUN_USER_DATA
621 : TEXT_IN_UNINFLATED_TEXTRUN_USER_DATA
;
622 bool found
= ClearAllTextRunReferences(
623 userDataFrame
, aTextRun
, aStartContinuation
, whichTextRunState
);
625 if (userDataFrame
->HasAnyStateBits(whichTextRunState
)) {
626 destroyFromIndex
= i
+ 1;
628 destroyFromIndex
= i
;
630 aStartContinuation
= nullptr;
633 NS_ASSERTION(destroyFromIndex
>= 0,
634 "aStartContinuation wasn't found in multi flow text run");
635 if (destroyFromIndex
== 0) {
636 DestroyTextRunUserData(aTextRun
);
638 userData
->mMappedFlowCount
= uint32_t(destroyFromIndex
);
639 if (userData
->mLastFlowIndex
>= uint32_t(destroyFromIndex
)) {
640 userData
->mLastFlowIndex
= uint32_t(destroyFromIndex
) - 1;
646 static void InvalidateFrameDueToGlyphsChanged(nsIFrame
* aFrame
) {
649 PresShell
* presShell
= aFrame
->PresShell();
650 for (nsIFrame
* f
= aFrame
; f
;
651 f
= nsLayoutUtils::GetNextContinuationOrIBSplitSibling(f
)) {
652 f
->InvalidateFrame();
654 // If this is a non-display text frame within SVG <text>, we need
655 // to reflow the SVGTextFrame. (This is similar to reflowing the
656 // SVGTextFrame in response to style changes, in
657 // SVGTextFrame::DidSetComputedStyle.)
658 if (SVGUtils::IsInSVGTextSubtree(f
) &&
659 f
->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY
)) {
660 auto svgTextFrame
= static_cast<SVGTextFrame
*>(
661 nsLayoutUtils::GetClosestFrameOfType(f
, LayoutFrameType::SVGText
));
662 svgTextFrame
->ScheduleReflowSVGNonDisplayText(IntrinsicDirty::Resize
);
664 // Theoretically we could just update overflow areas, perhaps using
665 // OverflowChangedTracker, but that would do a bunch of work eagerly that
666 // we should probably do lazily here since there could be a lot
667 // of text frames affected and we'd like to coalesce the work. So that's
668 // not easy to do well.
669 presShell
->FrameNeedsReflow(f
, IntrinsicDirty::Resize
, NS_FRAME_IS_DIRTY
);
674 void GlyphObserver::NotifyGlyphsChanged() {
675 if (mTextRun
->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow
) {
676 InvalidateFrameDueToGlyphsChanged(GetFrameForSimpleFlow(mTextRun
));
680 auto data
= static_cast<TextRunUserData
*>(mTextRun
->GetUserData());
681 TextRunMappedFlow
* userMappedFlows
= GetMappedFlows(mTextRun
);
682 for (uint32_t i
= 0; i
< data
->mMappedFlowCount
; ++i
) {
683 InvalidateFrameDueToGlyphsChanged(userMappedFlows
[i
].mStartFrame
);
687 int32_t nsTextFrame::GetContentEnd() const {
688 nsTextFrame
* next
= GetNextContinuation();
689 return next
? next
->GetContentOffset() : TextFragment()->GetLength();
692 struct FlowLengthProperty
{
693 int32_t mStartOffset
;
694 // The offset of the next fixed continuation after mStartOffset, or
695 // of the end of the text if there is none
696 int32_t mEndFlowOffset
;
699 int32_t nsTextFrame::GetInFlowContentLength() {
700 if (!(mState
& NS_FRAME_IS_BIDI
)) {
701 return mContent
->TextLength() - mContentOffset
;
704 FlowLengthProperty
* flowLength
=
705 mContent
->HasFlag(NS_HAS_FLOWLENGTH_PROPERTY
)
706 ? static_cast<FlowLengthProperty
*>(
707 mContent
->GetProperty(nsGkAtoms::flowlength
))
711 * This frame must start inside the cached flow. If the flow starts at
712 * mContentOffset but this frame is empty, logically it might be before the
713 * start of the cached flow.
716 (flowLength
->mStartOffset
< mContentOffset
||
717 (flowLength
->mStartOffset
== mContentOffset
&&
718 GetContentEnd() > mContentOffset
)) &&
719 flowLength
->mEndFlowOffset
> mContentOffset
) {
721 NS_ASSERTION(flowLength
->mEndFlowOffset
>= GetContentEnd(),
722 "frame crosses fixed continuation boundary");
724 return flowLength
->mEndFlowOffset
- mContentOffset
;
727 nsTextFrame
* nextBidi
= LastInFlow()->GetNextContinuation();
729 nextBidi
? nextBidi
->GetContentOffset() : GetContent()->TextLength();
732 flowLength
= new FlowLengthProperty
;
733 if (NS_FAILED(mContent
->SetProperty(
734 nsGkAtoms::flowlength
, flowLength
,
735 nsINode::DeleteProperty
<FlowLengthProperty
>))) {
737 flowLength
= nullptr;
739 mContent
->SetFlags(NS_HAS_FLOWLENGTH_PROPERTY
);
742 flowLength
->mStartOffset
= mContentOffset
;
743 flowLength
->mEndFlowOffset
= endFlow
;
746 return endFlow
- mContentOffset
;
749 // Smarter versions of dom::IsSpaceCharacter.
750 // Unicode is really annoying; sometimes a space character isn't whitespace ---
751 // when it combines with another character
752 // So we have several versions of IsSpace for use in different contexts.
754 static bool IsSpaceCombiningSequenceTail(const nsTextFragment
* aFrag
,
756 NS_ASSERTION(aPos
<= aFrag
->GetLength(), "Bad offset");
757 if (!aFrag
->Is2b()) return false;
758 return nsTextFrameUtils::IsSpaceCombiningSequenceTail(
759 aFrag
->Get2b() + aPos
, aFrag
->GetLength() - aPos
);
762 // Check whether aPos is a space for CSS 'word-spacing' purposes
763 static bool IsCSSWordSpacingSpace(const nsTextFragment
* aFrag
, uint32_t aPos
,
764 const nsTextFrame
* aFrame
,
765 const nsStyleText
* aStyleText
) {
766 NS_ASSERTION(aPos
< aFrag
->GetLength(), "No text for IsSpace!");
768 char16_t ch
= aFrag
->CharAt(aPos
);
772 return !IsSpaceCombiningSequenceTail(aFrag
, aPos
+ 1);
775 return !aStyleText
->WhiteSpaceIsSignificant();
777 return !aStyleText
->NewlineIsSignificant(aFrame
);
783 constexpr char16_t kOghamSpaceMark
= 0x1680;
785 // Check whether the string aChars/aLength starts with space that's
786 // trimmable according to CSS 'white-space:normal/nowrap'.
787 static bool IsTrimmableSpace(const char16_t
* aChars
, uint32_t aLength
) {
788 NS_ASSERTION(aLength
> 0, "No text for IsSpace!");
790 char16_t ch
= *aChars
;
791 if (ch
== ' ' || ch
== kOghamSpaceMark
) {
792 return !nsTextFrameUtils::IsSpaceCombiningSequenceTail(aChars
+ 1,
795 return ch
== '\t' || ch
== '\f' || ch
== '\n' || ch
== '\r';
798 // Check whether the character aCh is trimmable according to CSS
799 // 'white-space:normal/nowrap'
800 static bool IsTrimmableSpace(char aCh
) {
801 return aCh
== ' ' || aCh
== '\t' || aCh
== '\f' || aCh
== '\n' || aCh
== '\r';
804 static bool IsTrimmableSpace(const nsTextFragment
* aFrag
, uint32_t aPos
,
805 const nsStyleText
* aStyleText
,
806 bool aAllowHangingWS
= false) {
807 NS_ASSERTION(aPos
< aFrag
->GetLength(), "No text for IsSpace!");
809 switch (aFrag
->CharAt(aPos
)) {
811 case kOghamSpaceMark
:
812 return (!aStyleText
->WhiteSpaceIsSignificant() || aAllowHangingWS
) &&
813 !IsSpaceCombiningSequenceTail(aFrag
, aPos
+ 1);
815 return !aStyleText
->NewlineIsSignificantStyle() &&
816 aStyleText
->mWhiteSpace
!= mozilla::StyleWhiteSpace::PreSpace
;
820 return !aStyleText
->WhiteSpaceIsSignificant() || aAllowHangingWS
;
826 static bool IsSelectionInlineWhitespace(const nsTextFragment
* aFrag
,
828 NS_ASSERTION(aPos
< aFrag
->GetLength(),
829 "No text for IsSelectionInlineWhitespace!");
830 char16_t ch
= aFrag
->CharAt(aPos
);
831 if (ch
== ' ' || ch
== CH_NBSP
)
832 return !IsSpaceCombiningSequenceTail(aFrag
, aPos
+ 1);
833 return ch
== '\t' || ch
== '\f';
836 static bool IsSelectionNewline(const nsTextFragment
* aFrag
, uint32_t aPos
) {
837 NS_ASSERTION(aPos
< aFrag
->GetLength(), "No text for IsSelectionNewline!");
838 char16_t ch
= aFrag
->CharAt(aPos
);
839 return ch
== '\n' || ch
== '\r';
842 // Count the amount of trimmable whitespace (as per CSS
843 // 'white-space:normal/nowrap') in a text fragment. The first
844 // character is at offset aStartOffset; the maximum number of characters
845 // to check is aLength. aDirection is -1 or 1 depending on whether we should
846 // progress backwards or forwards.
847 static uint32_t GetTrimmableWhitespaceCount(const nsTextFragment
* aFrag
,
848 int32_t aStartOffset
,
850 int32_t aDirection
) {
857 const char16_t
* str
= aFrag
->Get2b() + aStartOffset
;
858 int32_t fragLen
= aFrag
->GetLength() - aStartOffset
;
859 for (; count
< aLength
; ++count
) {
860 if (!IsTrimmableSpace(str
, fragLen
)) break;
862 fragLen
-= aDirection
;
865 const char* str
= aFrag
->Get1b() + aStartOffset
;
866 for (; count
< aLength
; ++count
) {
867 if (!IsTrimmableSpace(*str
)) break;
874 static bool IsAllWhitespace(const nsTextFragment
* aFrag
, bool aAllowNewline
) {
875 if (aFrag
->Is2b()) return false;
876 int32_t len
= aFrag
->GetLength();
877 const char* str
= aFrag
->Get1b();
878 for (int32_t i
= 0; i
< len
; ++i
) {
880 if (ch
== ' ' || ch
== '\t' || ch
== '\r' || (ch
== '\n' && aAllowNewline
))
887 static void ClearObserversFromTextRun(gfxTextRun
* aTextRun
) {
888 if (!(aTextRun
->GetFlags2() &
889 nsTextFrameUtils::Flags::MightHaveGlyphChanges
)) {
893 if (aTextRun
->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow
) {
894 static_cast<SimpleTextRunUserData
*>(aTextRun
->GetUserData())
895 ->mGlyphObservers
.Clear();
897 static_cast<ComplexTextRunUserData
*>(aTextRun
->GetUserData())
898 ->mGlyphObservers
.Clear();
902 static void CreateObserversForAnimatedGlyphs(gfxTextRun
* aTextRun
) {
903 if (!aTextRun
->GetUserData()) {
907 ClearObserversFromTextRun(aTextRun
);
909 nsTArray
<gfxFont
*> fontsWithAnimatedGlyphs
;
910 uint32_t numGlyphRuns
;
911 const gfxTextRun::GlyphRun
* glyphRuns
= aTextRun
->GetGlyphRuns(&numGlyphRuns
);
912 for (uint32_t i
= 0; i
< numGlyphRuns
; ++i
) {
913 gfxFont
* font
= glyphRuns
[i
].mFont
;
914 if (font
->GlyphsMayChange() && !fontsWithAnimatedGlyphs
.Contains(font
)) {
915 fontsWithAnimatedGlyphs
.AppendElement(font
);
918 if (fontsWithAnimatedGlyphs
.IsEmpty()) {
919 // NB: Theoretically, we should clear the MightHaveGlyphChanges
920 // here. That would involve de-allocating the simple user data struct if
921 // present too, and resetting the pointer to the frame. In practice, I
922 // don't think worth doing that work here, given the flag's only purpose is
923 // to distinguish what kind of user data is there.
927 nsTArray
<UniquePtr
<GlyphObserver
>>* observers
;
929 if (aTextRun
->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow
) {
930 // Swap the frame pointer for a just-allocated SimpleTextRunUserData if
932 if (!(aTextRun
->GetFlags2() &
933 nsTextFrameUtils::Flags::MightHaveGlyphChanges
)) {
934 auto frame
= static_cast<nsTextFrame
*>(aTextRun
->GetUserData());
935 aTextRun
->SetUserData(new SimpleTextRunUserData(frame
));
938 auto data
= static_cast<SimpleTextRunUserData
*>(aTextRun
->GetUserData());
939 observers
= &data
->mGlyphObservers
;
941 if (!(aTextRun
->GetFlags2() &
942 nsTextFrameUtils::Flags::MightHaveGlyphChanges
)) {
943 auto oldData
= static_cast<TextRunUserData
*>(aTextRun
->GetUserData());
944 TextRunMappedFlow
* oldMappedFlows
= GetMappedFlows(aTextRun
);
945 ComplexTextRunUserData
* data
=
946 CreateComplexUserData(oldData
->mMappedFlowCount
);
947 TextRunMappedFlow
* dataMappedFlows
=
948 reinterpret_cast<TextRunMappedFlow
*>(data
+ 1);
949 data
->mLastFlowIndex
= oldData
->mLastFlowIndex
;
950 for (uint32_t i
= 0; i
< oldData
->mMappedFlowCount
; ++i
) {
951 dataMappedFlows
[i
] = oldMappedFlows
[i
];
953 DestroyUserData(oldData
);
954 aTextRun
->SetUserData(data
);
956 auto data
= static_cast<ComplexTextRunUserData
*>(aTextRun
->GetUserData());
957 observers
= &data
->mGlyphObservers
;
960 aTextRun
->SetFlagBits(nsTextFrameUtils::Flags::MightHaveGlyphChanges
);
962 for (auto font
: fontsWithAnimatedGlyphs
) {
963 observers
->AppendElement(new GlyphObserver(font
, aTextRun
));
968 * This class accumulates state as we scan a paragraph of text. It detects
969 * textrun boundaries (changes from text to non-text, hard
970 * line breaks, and font changes) and builds a gfxTextRun at each boundary.
971 * It also detects linebreaker run boundaries (changes from text to non-text,
972 * and hard line breaks) and at each boundary runs the linebreaker to compute
973 * potential line breaks. It also records actual line breaks to store them in
976 class BuildTextRunsScanner
{
978 BuildTextRunsScanner(nsPresContext
* aPresContext
, DrawTarget
* aDrawTarget
,
979 nsIFrame
* aLineContainer
,
980 nsTextFrame::TextRunType aWhichTextRun
)
981 : mDrawTarget(aDrawTarget
),
982 mLineContainer(aLineContainer
),
983 mCommonAncestorWithLastFrame(nullptr),
984 mMissingFonts(aPresContext
->MissingFontRecorder()),
985 mBidiEnabled(aPresContext
->BidiEnabled()),
987 mSkipIncompleteTextRuns(false),
988 mCanStopOnThisLine(false),
989 mWhichTextRun(aWhichTextRun
),
990 mNextRunContextInfo(nsTextFrameUtils::INCOMING_NONE
),
991 mCurrentRunContextInfo(nsTextFrameUtils::INCOMING_NONE
) {
994 ~BuildTextRunsScanner() {
995 NS_ASSERTION(mBreakSinks
.IsEmpty(), "Should have been cleared");
996 NS_ASSERTION(mLineBreakBeforeFrames
.IsEmpty(), "Should have been cleared");
997 NS_ASSERTION(mMappedFlows
.IsEmpty(), "Should have been cleared");
1000 void SetAtStartOfLine() {
1001 mStartOfLine
= true;
1002 mCanStopOnThisLine
= false;
1004 void SetSkipIncompleteTextRuns(bool aSkip
) {
1005 mSkipIncompleteTextRuns
= aSkip
;
1007 void SetCommonAncestorWithLastFrame(nsIFrame
* aFrame
) {
1008 mCommonAncestorWithLastFrame
= aFrame
;
1010 bool CanStopOnThisLine() { return mCanStopOnThisLine
; }
1011 nsIFrame
* GetCommonAncestorWithLastFrame() {
1012 return mCommonAncestorWithLastFrame
;
1014 void LiftCommonAncestorWithLastFrameToParent(nsIFrame
* aFrame
) {
1015 if (mCommonAncestorWithLastFrame
&&
1016 mCommonAncestorWithLastFrame
->GetParent() == aFrame
) {
1017 mCommonAncestorWithLastFrame
= aFrame
;
1020 void ScanFrame(nsIFrame
* aFrame
);
1021 bool IsTextRunValidForMappedFlows(const gfxTextRun
* aTextRun
);
1022 void FlushFrames(bool aFlushLineBreaks
, bool aSuppressTrailingBreak
);
1023 void FlushLineBreaks(gfxTextRun
* aTrailingTextRun
);
1024 void ResetRunInfo() {
1025 mLastFrame
= nullptr;
1026 mMappedFlows
.Clear();
1027 mLineBreakBeforeFrames
.Clear();
1029 mDoubleByteText
= false;
1031 void AccumulateRunInfo(nsTextFrame
* aFrame
);
1033 * @return null to indicate either textrun construction failed or
1034 * we constructed just a partial textrun to set up linebreaker and other
1035 * state for following textruns.
1037 already_AddRefed
<gfxTextRun
> BuildTextRunForFrames(void* aTextBuffer
);
1038 bool SetupLineBreakerContext(gfxTextRun
* aTextRun
);
1039 void AssignTextRun(gfxTextRun
* aTextRun
, float aInflation
);
1040 nsTextFrame
* GetNextBreakBeforeFrame(uint32_t* aIndex
);
1041 void SetupBreakSinksForTextRun(gfxTextRun
* aTextRun
, const void* aTextPtr
);
1042 void SetupTextEmphasisForTextRun(gfxTextRun
* aTextRun
, const void* aTextPtr
);
1043 struct FindBoundaryState
{
1044 nsIFrame
* mStopAtFrame
;
1045 nsTextFrame
* mFirstTextFrame
;
1046 nsTextFrame
* mLastTextFrame
;
1047 bool mSeenTextRunBoundaryOnLaterLine
;
1048 bool mSeenTextRunBoundaryOnThisLine
;
1049 bool mSeenSpaceForLineBreakingOnThisLine
;
1050 nsTArray
<char16_t
>& mBuffer
;
1052 enum FindBoundaryResult
{
1054 FB_STOPPED_AT_STOP_FRAME
,
1055 FB_FOUND_VALID_TEXTRUN_BOUNDARY
1057 FindBoundaryResult
FindBoundaries(nsIFrame
* aFrame
,
1058 FindBoundaryState
* aState
);
1060 bool ContinueTextRunAcrossFrames(nsTextFrame
* aFrame1
, nsTextFrame
* aFrame2
);
1062 // Like TextRunMappedFlow but with some differences. mStartFrame to mEndFrame
1063 // (exclusive) are a sequence of in-flow frames (if mEndFrame is null, then
1064 // continuations starting from mStartFrame are a sequence of in-flow frames).
1066 nsTextFrame
* mStartFrame
;
1067 nsTextFrame
* mEndFrame
;
1068 // When we consider breaking between elements, the nearest common
1069 // ancestor of the elements containing the characters is the one whose
1070 // CSS 'white-space' property governs. So this records the nearest common
1071 // ancestor of mStartFrame and the previous text frame, or null if there
1072 // was no previous text frame on this line.
1073 nsIFrame
* mAncestorControllingInitialBreak
;
1075 int32_t GetContentEnd() {
1076 return mEndFrame
? mEndFrame
->GetContentOffset()
1077 : mStartFrame
->TextFragment()->GetLength();
1081 class BreakSink final
: public nsILineBreakSink
{
1083 BreakSink(gfxTextRun
* aTextRun
, DrawTarget
* aDrawTarget
,
1084 uint32_t aOffsetIntoTextRun
)
1085 : mTextRun(aTextRun
),
1086 mDrawTarget(aDrawTarget
),
1087 mOffsetIntoTextRun(aOffsetIntoTextRun
) {}
1089 void SetBreaks(uint32_t aOffset
, uint32_t aLength
,
1090 uint8_t* aBreakBefore
) final
{
1091 gfxTextRun::Range
range(aOffset
+ mOffsetIntoTextRun
,
1092 aOffset
+ mOffsetIntoTextRun
+ aLength
);
1093 if (mTextRun
->SetPotentialLineBreaks(range
, aBreakBefore
)) {
1094 // Be conservative and assume that some breaks have been set
1095 mTextRun
->ClearFlagBits(nsTextFrameUtils::Flags::NoBreaks
);
1099 void SetCapitalization(uint32_t aOffset
, uint32_t aLength
,
1100 bool* aCapitalize
) final
{
1101 MOZ_ASSERT(mTextRun
->GetFlags2() & nsTextFrameUtils::Flags::IsTransformed
,
1102 "Text run should be transformed!");
1103 if (mTextRun
->GetFlags2() & nsTextFrameUtils::Flags::IsTransformed
) {
1104 nsTransformedTextRun
* transformedTextRun
=
1105 static_cast<nsTransformedTextRun
*>(mTextRun
.get());
1106 transformedTextRun
->SetCapitalization(aOffset
+ mOffsetIntoTextRun
,
1107 aLength
, aCapitalize
);
1111 void Finish(gfxMissingFontRecorder
* aMFR
) {
1113 !(mTextRun
->GetFlags2() & nsTextFrameUtils::Flags::UnusedFlags
),
1114 "Flag set that should never be set! (memory safety error?)");
1115 if (mTextRun
->GetFlags2() & nsTextFrameUtils::Flags::IsTransformed
) {
1116 nsTransformedTextRun
* transformedTextRun
=
1117 static_cast<nsTransformedTextRun
*>(mTextRun
.get());
1118 transformedTextRun
->FinishSettingProperties(mDrawTarget
, aMFR
);
1120 // The way nsTransformedTextRun is implemented, its glyph runs aren't
1121 // available until after nsTransformedTextRun::FinishSettingProperties()
1122 // is called. So that's why we defer checking for animated glyphs to here.
1123 CreateObserversForAnimatedGlyphs(mTextRun
);
1126 RefPtr
<gfxTextRun
> mTextRun
;
1127 DrawTarget
* mDrawTarget
;
1128 uint32_t mOffsetIntoTextRun
;
1132 AutoTArray
<MappedFlow
, 10> mMappedFlows
;
1133 AutoTArray
<nsTextFrame
*, 50> mLineBreakBeforeFrames
;
1134 AutoTArray
<UniquePtr
<BreakSink
>, 10> mBreakSinks
;
1135 nsLineBreaker mLineBreaker
;
1136 RefPtr
<gfxTextRun
> mCurrentFramesAllSameTextRun
;
1137 DrawTarget
* mDrawTarget
;
1138 nsIFrame
* mLineContainer
;
1139 nsTextFrame
* mLastFrame
;
1140 // The common ancestor of the current frame and the previous leaf frame
1141 // on the line, or null if there was no previous leaf frame.
1142 nsIFrame
* mCommonAncestorWithLastFrame
;
1143 gfxMissingFontRecorder
* mMissingFonts
;
1144 // mMaxTextLength is an upper bound on the size of the text in all mapped
1145 // frames The value UINT32_MAX represents overflow; text will be discarded
1146 uint32_t mMaxTextLength
;
1147 bool mDoubleByteText
;
1150 bool mSkipIncompleteTextRuns
;
1151 bool mCanStopOnThisLine
;
1152 nsTextFrame::TextRunType mWhichTextRun
;
1153 uint8_t mNextRunContextInfo
;
1154 uint8_t mCurrentRunContextInfo
;
1157 static nsIFrame
* FindLineContainer(nsIFrame
* aFrame
) {
1158 while (aFrame
&& (aFrame
->IsFrameOfType(nsIFrame::eLineParticipant
) ||
1159 aFrame
->CanContinueTextRun())) {
1160 aFrame
= aFrame
->GetParent();
1165 static bool IsLineBreakingWhiteSpace(char16_t aChar
) {
1166 // 0x0A (\n) is not handled as white-space by the line breaker, since
1167 // we break before it, if it isn't transformed to a normal space.
1168 // (If we treat it as normal white-space then we'd only break after it.)
1169 // However, it does induce a line break or is converted to a regular
1170 // space, and either way it can be used to bound the region of text
1171 // that needs to be analyzed for line breaking.
1172 return nsLineBreaker::IsSpace(aChar
) || aChar
== 0x0A;
1175 static bool TextContainsLineBreakerWhiteSpace(const void* aText
,
1177 bool aIsDoubleByte
) {
1178 if (aIsDoubleByte
) {
1179 const char16_t
* chars
= static_cast<const char16_t
*>(aText
);
1180 for (uint32_t i
= 0; i
< aLength
; ++i
) {
1181 if (IsLineBreakingWhiteSpace(chars
[i
])) return true;
1185 const uint8_t* chars
= static_cast<const uint8_t*>(aText
);
1186 for (uint32_t i
= 0; i
< aLength
; ++i
) {
1187 if (IsLineBreakingWhiteSpace(chars
[i
])) return true;
1193 static nsTextFrameUtils::CompressionMode
GetCSSWhitespaceToCompressionMode(
1194 nsTextFrame
* aFrame
, const nsStyleText
* aStyleText
) {
1195 switch (aStyleText
->mWhiteSpace
) {
1196 case StyleWhiteSpace::Normal
:
1197 case StyleWhiteSpace::Nowrap
:
1198 return nsTextFrameUtils::COMPRESS_WHITESPACE_NEWLINE
;
1199 case StyleWhiteSpace::Pre
:
1200 case StyleWhiteSpace::PreWrap
:
1201 case StyleWhiteSpace::BreakSpaces
:
1202 if (!aStyleText
->NewlineIsSignificant(aFrame
)) {
1203 // If newline is set to be preserved, but then suppressed,
1204 // transform newline to space.
1205 return nsTextFrameUtils::COMPRESS_NONE_TRANSFORM_TO_SPACE
;
1207 return nsTextFrameUtils::COMPRESS_NONE
;
1208 case StyleWhiteSpace::PreSpace
:
1209 return nsTextFrameUtils::COMPRESS_NONE_TRANSFORM_TO_SPACE
;
1210 case StyleWhiteSpace::PreLine
:
1211 return nsTextFrameUtils::COMPRESS_WHITESPACE
;
1213 MOZ_ASSERT_UNREACHABLE("Unknown white-space value");
1214 return nsTextFrameUtils::COMPRESS_WHITESPACE_NEWLINE
;
1218 struct FrameTextTraversal
{
1219 FrameTextTraversal()
1220 : mFrameToScan(nullptr),
1221 mOverflowFrameToScan(nullptr),
1222 mScanSiblings(false),
1223 mLineBreakerCanCrossFrameBoundary(false),
1224 mTextRunCanCrossFrameBoundary(false) {}
1226 // These fields identify which frames should be recursively scanned
1227 // The first normal frame to scan (or null, if no such frame should be
1229 nsIFrame
* mFrameToScan
;
1230 // The first overflow frame to scan (or null, if no such frame should be
1232 nsIFrame
* mOverflowFrameToScan
;
1233 // Whether to scan the siblings of
1234 // mFrameToDescendInto/mOverflowFrameToDescendInto
1237 // These identify the boundaries of the context required for
1238 // line breaking or textrun construction
1239 bool mLineBreakerCanCrossFrameBoundary
;
1240 bool mTextRunCanCrossFrameBoundary
;
1242 nsIFrame
* NextFrameToScan() {
1246 mFrameToScan
= mScanSiblings
? f
->GetNextSibling() : nullptr;
1247 } else if (mOverflowFrameToScan
) {
1248 f
= mOverflowFrameToScan
;
1249 mOverflowFrameToScan
= mScanSiblings
? f
->GetNextSibling() : nullptr;
1257 static FrameTextTraversal
CanTextCrossFrameBoundary(nsIFrame
* aFrame
) {
1258 FrameTextTraversal result
;
1260 bool continuesTextRun
= aFrame
->CanContinueTextRun();
1261 if (aFrame
->IsPlaceholderFrame()) {
1262 // placeholders are "invisible", so a text run should be able to span
1263 // across one. But don't descend into the out-of-flow.
1264 result
.mLineBreakerCanCrossFrameBoundary
= true;
1265 if (continuesTextRun
) {
1266 // ... Except for first-letter floats, which are really in-flow
1267 // from the point of view of capitalization etc, so we'd better
1268 // descend into them. But we actually need to break the textrun for
1269 // first-letter floats since things look bad if, say, we try to make a
1270 // ligature across the float boundary.
1271 result
.mFrameToScan
=
1272 (static_cast<nsPlaceholderFrame
*>(aFrame
))->GetOutOfFlowFrame();
1274 result
.mTextRunCanCrossFrameBoundary
= true;
1277 if (continuesTextRun
) {
1278 result
.mFrameToScan
= aFrame
->PrincipalChildList().FirstChild();
1279 result
.mOverflowFrameToScan
=
1280 aFrame
->GetChildList(nsIFrame::kOverflowList
).FirstChild();
1281 NS_WARNING_ASSERTION(
1282 !result
.mOverflowFrameToScan
,
1283 "Scanning overflow inline frames is something we should avoid");
1284 result
.mScanSiblings
= true;
1285 result
.mTextRunCanCrossFrameBoundary
= true;
1286 result
.mLineBreakerCanCrossFrameBoundary
= true;
1288 MOZ_ASSERT(!aFrame
->IsRubyTextContainerFrame(),
1289 "Shouldn't call this method for ruby text container");
1295 BuildTextRunsScanner::FindBoundaryResult
BuildTextRunsScanner::FindBoundaries(
1296 nsIFrame
* aFrame
, FindBoundaryState
* aState
) {
1297 LayoutFrameType frameType
= aFrame
->Type();
1298 if (frameType
== LayoutFrameType::RubyTextContainer
) {
1299 // Don't stop a text run for ruby text container. We want ruby text
1300 // containers to be skipped, but continue the text run across them.
1304 nsTextFrame
* textFrame
= frameType
== LayoutFrameType::Text
1305 ? static_cast<nsTextFrame
*>(aFrame
)
1308 if (aState
->mLastTextFrame
&&
1309 textFrame
!= aState
->mLastTextFrame
->GetNextInFlow() &&
1310 !ContinueTextRunAcrossFrames(aState
->mLastTextFrame
, textFrame
)) {
1311 aState
->mSeenTextRunBoundaryOnThisLine
= true;
1312 if (aState
->mSeenSpaceForLineBreakingOnThisLine
)
1313 return FB_FOUND_VALID_TEXTRUN_BOUNDARY
;
1315 if (!aState
->mFirstTextFrame
) {
1316 aState
->mFirstTextFrame
= textFrame
;
1318 aState
->mLastTextFrame
= textFrame
;
1321 if (aFrame
== aState
->mStopAtFrame
) return FB_STOPPED_AT_STOP_FRAME
;
1324 if (aState
->mSeenSpaceForLineBreakingOnThisLine
) {
1327 const nsTextFragment
* frag
= textFrame
->TextFragment();
1328 uint32_t start
= textFrame
->GetContentOffset();
1329 uint32_t length
= textFrame
->GetContentLength();
1332 // It is possible that we may end up removing all whitespace in
1333 // a piece of text because of The White Space Processing Rules,
1334 // so we need to transform it before we can check existence of
1335 // such whitespaces.
1336 aState
->mBuffer
.EnsureLengthAtLeast(length
);
1337 nsTextFrameUtils::CompressionMode compression
=
1338 GetCSSWhitespaceToCompressionMode(textFrame
, textFrame
->StyleText());
1339 uint8_t incomingFlags
= 0;
1340 gfxSkipChars skipChars
;
1341 nsTextFrameUtils::Flags analysisFlags
;
1342 char16_t
* bufStart
= aState
->mBuffer
.Elements();
1343 char16_t
* bufEnd
= nsTextFrameUtils::TransformText(
1344 frag
->Get2b() + start
, length
, bufStart
, compression
, &incomingFlags
,
1345 &skipChars
, &analysisFlags
);
1347 length
= bufEnd
- bufStart
;
1349 // If the text only contains ASCII characters, it is currently
1350 // impossible that TransformText would remove all whitespaces,
1351 // and thus the check below should return the same result for
1352 // transformed text and original text. So we don't need to try
1353 // transforming it here.
1354 text
= static_cast<const void*>(frag
->Get1b() + start
);
1356 if (TextContainsLineBreakerWhiteSpace(text
, length
, frag
->Is2b())) {
1357 aState
->mSeenSpaceForLineBreakingOnThisLine
= true;
1358 if (aState
->mSeenTextRunBoundaryOnLaterLine
) {
1359 return FB_FOUND_VALID_TEXTRUN_BOUNDARY
;
1365 FrameTextTraversal traversal
= CanTextCrossFrameBoundary(aFrame
);
1366 if (!traversal
.mTextRunCanCrossFrameBoundary
) {
1367 aState
->mSeenTextRunBoundaryOnThisLine
= true;
1368 if (aState
->mSeenSpaceForLineBreakingOnThisLine
)
1369 return FB_FOUND_VALID_TEXTRUN_BOUNDARY
;
1372 for (nsIFrame
* f
= traversal
.NextFrameToScan(); f
;
1373 f
= traversal
.NextFrameToScan()) {
1374 FindBoundaryResult result
= FindBoundaries(f
, aState
);
1375 if (result
!= FB_CONTINUE
) return result
;
1378 if (!traversal
.mTextRunCanCrossFrameBoundary
) {
1379 aState
->mSeenTextRunBoundaryOnThisLine
= true;
1380 if (aState
->mSeenSpaceForLineBreakingOnThisLine
)
1381 return FB_FOUND_VALID_TEXTRUN_BOUNDARY
;
1387 // build text runs for the 200 lines following aForFrame, and stop after that
1388 // when we get a chance.
1389 #define NUM_LINES_TO_BUILD_TEXT_RUNS 200
1392 * General routine for building text runs. This is hairy because of the need
1393 * to build text runs that span content nodes.
1395 * @param aContext The gfxContext we're using to construct this text run.
1396 * @param aForFrame The nsTextFrame for which we're building this text run.
1397 * @param aLineContainer the line container containing aForFrame; if null,
1398 * we'll walk the ancestors to find it. It's required to be non-null
1399 * when aForFrameLine is non-null.
1400 * @param aForFrameLine the line containing aForFrame; if null, we'll figure
1401 * out the line (slowly)
1402 * @param aWhichTextRun The type of text run we want to build. If font inflation
1403 * is enabled, this will be eInflated, otherwise it's eNotInflated.
1405 static void BuildTextRuns(DrawTarget
* aDrawTarget
, nsTextFrame
* aForFrame
,
1406 nsIFrame
* aLineContainer
,
1407 const nsLineList::iterator
* aForFrameLine
,
1408 nsTextFrame::TextRunType aWhichTextRun
) {
1409 MOZ_ASSERT(aForFrame
, "for no frame?");
1410 NS_ASSERTION(!aForFrameLine
|| aLineContainer
, "line but no line container");
1412 nsIFrame
* lineContainerChild
= aForFrame
;
1413 if (!aLineContainer
) {
1414 if (aForFrame
->IsFloatingFirstLetterChild()) {
1415 lineContainerChild
= aForFrame
->GetParent()->GetPlaceholderFrame();
1417 aLineContainer
= FindLineContainer(lineContainerChild
);
1420 (aLineContainer
== FindLineContainer(aForFrame
) ||
1421 (aLineContainer
->IsLetterFrame() && aLineContainer
->IsFloating())),
1422 "Wrong line container hint");
1425 if (aForFrame
->HasAnyStateBits(TEXT_IS_IN_TOKEN_MATHML
)) {
1426 aLineContainer
->AddStateBits(TEXT_IS_IN_TOKEN_MATHML
);
1427 if (aForFrame
->HasAnyStateBits(NS_FRAME_IS_IN_SINGLE_CHAR_MI
)) {
1428 aLineContainer
->AddStateBits(NS_FRAME_IS_IN_SINGLE_CHAR_MI
);
1431 if (aForFrame
->HasAnyStateBits(NS_FRAME_MATHML_SCRIPT_DESCENDANT
)) {
1432 aLineContainer
->AddStateBits(NS_FRAME_MATHML_SCRIPT_DESCENDANT
);
1435 nsPresContext
* presContext
= aLineContainer
->PresContext();
1436 BuildTextRunsScanner
scanner(presContext
, aDrawTarget
, aLineContainer
,
1439 nsBlockFrame
* block
= do_QueryFrame(aLineContainer
);
1442 nsIFrame
* textRunContainer
= aLineContainer
;
1443 if (aLineContainer
->IsRubyTextContainerFrame()) {
1444 textRunContainer
= aForFrame
;
1445 while (textRunContainer
&& !textRunContainer
->IsRubyTextFrame()) {
1446 textRunContainer
= textRunContainer
->GetParent();
1448 MOZ_ASSERT(textRunContainer
&&
1449 textRunContainer
->GetParent() == aLineContainer
);
1452 !aLineContainer
->GetPrevInFlow() && !aLineContainer
->GetNextInFlow(),
1453 "Breakable non-block line containers other than "
1454 "ruby text container is not supported");
1456 // Just loop through all the children of the linecontainer ... it's really
1458 scanner
.SetAtStartOfLine();
1459 scanner
.SetCommonAncestorWithLastFrame(nullptr);
1460 for (nsIFrame
* child
: textRunContainer
->PrincipalChildList()) {
1461 scanner
.ScanFrame(child
);
1463 // Set mStartOfLine so FlushFrames knows its textrun ends a line
1464 scanner
.SetAtStartOfLine();
1465 scanner
.FlushFrames(true, false);
1469 // Find the line containing 'lineContainerChild'.
1471 bool isValid
= true;
1472 nsBlockInFlowLineIterator
backIterator(block
, &isValid
);
1473 if (aForFrameLine
) {
1474 backIterator
= nsBlockInFlowLineIterator(block
, *aForFrameLine
);
1477 nsBlockInFlowLineIterator(block
, lineContainerChild
, &isValid
);
1478 NS_ASSERTION(isValid
, "aForFrame not found in block, someone lied to us");
1479 NS_ASSERTION(backIterator
.GetContainer() == block
,
1480 "Someone lied to us about the block");
1482 nsBlockFrame::LineIterator startLine
= backIterator
.GetLine();
1484 // Find a line where we can start building text runs. We choose the last line
1486 // -- there is a textrun boundary between the start of the line and the
1487 // start of aForFrame
1488 // -- there is a space between the start of the line and the textrun boundary
1489 // (this is so we can be sure the line breaks will be set properly
1490 // on the textruns we construct).
1491 // The possibly-partial text runs up to and including the first space
1492 // are not reconstructed. We construct partial text runs for that text ---
1493 // for the sake of simplifying the code and feeding the linebreaker ---
1494 // but we discard them instead of assigning them to frames.
1495 // This is a little awkward because we traverse lines in the reverse direction
1496 // but we traverse the frames in each line in the forward direction.
1497 nsBlockInFlowLineIterator forwardIterator
= backIterator
;
1498 nsIFrame
* stopAtFrame
= lineContainerChild
;
1499 nsTextFrame
* nextLineFirstTextFrame
= nullptr;
1500 AutoTArray
<char16_t
, BIG_TEXT_NODE_SIZE
> buffer
;
1501 bool seenTextRunBoundaryOnLaterLine
= false;
1502 bool mayBeginInTextRun
= true;
1504 forwardIterator
= backIterator
;
1505 nsBlockFrame::LineIterator line
= backIterator
.GetLine();
1506 if (!backIterator
.Prev() || backIterator
.GetLine()->IsBlock()) {
1507 mayBeginInTextRun
= false;
1511 BuildTextRunsScanner::FindBoundaryState state
= {
1512 stopAtFrame
, nullptr, nullptr, bool(seenTextRunBoundaryOnLaterLine
),
1513 false, false, buffer
};
1514 nsIFrame
* child
= line
->mFirstChild
;
1515 bool foundBoundary
= false;
1516 for (int32_t i
= line
->GetChildCount() - 1; i
>= 0; --i
) {
1517 BuildTextRunsScanner::FindBoundaryResult result
=
1518 scanner
.FindBoundaries(child
, &state
);
1519 if (result
== BuildTextRunsScanner::FB_FOUND_VALID_TEXTRUN_BOUNDARY
) {
1520 foundBoundary
= true;
1522 } else if (result
== BuildTextRunsScanner::FB_STOPPED_AT_STOP_FRAME
) {
1525 child
= child
->GetNextSibling();
1527 if (foundBoundary
) break;
1528 if (!stopAtFrame
&& state
.mLastTextFrame
&& nextLineFirstTextFrame
&&
1529 !scanner
.ContinueTextRunAcrossFrames(state
.mLastTextFrame
,
1530 nextLineFirstTextFrame
)) {
1531 // Found a usable textrun boundary at the end of the line
1532 if (state
.mSeenSpaceForLineBreakingOnThisLine
) break;
1533 seenTextRunBoundaryOnLaterLine
= true;
1534 } else if (state
.mSeenTextRunBoundaryOnThisLine
) {
1535 seenTextRunBoundaryOnLaterLine
= true;
1537 stopAtFrame
= nullptr;
1538 if (state
.mFirstTextFrame
) {
1539 nextLineFirstTextFrame
= state
.mFirstTextFrame
;
1542 scanner
.SetSkipIncompleteTextRuns(mayBeginInTextRun
);
1544 // Now iterate over all text frames starting from the current line.
1545 // First-in-flow text frames will be accumulated into textRunFrames as we go.
1546 // When a text run boundary is required we flush textRunFrames ((re)building
1547 // their gfxTextRuns as necessary).
1548 bool seenStartLine
= false;
1549 uint32_t linesAfterStartLine
= 0;
1551 nsBlockFrame::LineIterator line
= forwardIterator
.GetLine();
1552 if (line
->IsBlock()) break;
1553 line
->SetInvalidateTextRuns(false);
1554 scanner
.SetAtStartOfLine();
1555 scanner
.SetCommonAncestorWithLastFrame(nullptr);
1556 nsIFrame
* child
= line
->mFirstChild
;
1557 for (int32_t i
= line
->GetChildCount() - 1; i
>= 0; --i
) {
1558 scanner
.ScanFrame(child
);
1559 child
= child
->GetNextSibling();
1561 if (line
.get() == startLine
.get()) {
1562 seenStartLine
= true;
1564 if (seenStartLine
) {
1565 ++linesAfterStartLine
;
1566 if (linesAfterStartLine
>= NUM_LINES_TO_BUILD_TEXT_RUNS
&&
1567 scanner
.CanStopOnThisLine()) {
1568 // Don't flush frames; we may be in the middle of a textrun
1569 // that we can't end here. That's OK, we just won't build it.
1570 // Note that we must already have finished the textrun for aForFrame,
1571 // because we've seen the end of a textrun in a line after the line
1572 // containing aForFrame.
1573 scanner
.FlushLineBreaks(nullptr);
1574 // This flushes out mMappedFlows and mLineBreakBeforeFrames, which
1575 // silences assertions in the scanner destructor.
1576 scanner
.ResetRunInfo();
1580 } while (forwardIterator
.Next());
1582 // Set mStartOfLine so FlushFrames knows its textrun ends a line
1583 scanner
.SetAtStartOfLine();
1584 scanner
.FlushFrames(true, false);
1587 static char16_t
* ExpandBuffer(char16_t
* aDest
, uint8_t* aSrc
, uint32_t aCount
) {
1597 bool BuildTextRunsScanner::IsTextRunValidForMappedFlows(
1598 const gfxTextRun
* aTextRun
) {
1599 if (aTextRun
->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow
) {
1600 return mMappedFlows
.Length() == 1 &&
1601 mMappedFlows
[0].mStartFrame
== GetFrameForSimpleFlow(aTextRun
) &&
1602 mMappedFlows
[0].mEndFrame
== nullptr;
1605 auto userData
= static_cast<TextRunUserData
*>(aTextRun
->GetUserData());
1606 TextRunMappedFlow
* userMappedFlows
= GetMappedFlows(aTextRun
);
1607 if (userData
->mMappedFlowCount
!= mMappedFlows
.Length()) return false;
1608 for (uint32_t i
= 0; i
< mMappedFlows
.Length(); ++i
) {
1609 if (userMappedFlows
[i
].mStartFrame
!= mMappedFlows
[i
].mStartFrame
||
1610 int32_t(userMappedFlows
[i
].mContentLength
) !=
1611 mMappedFlows
[i
].GetContentEnd() -
1612 mMappedFlows
[i
].mStartFrame
->GetContentOffset())
1619 * This gets called when we need to make a text run for the current list of
1622 void BuildTextRunsScanner::FlushFrames(bool aFlushLineBreaks
,
1623 bool aSuppressTrailingBreak
) {
1624 RefPtr
<gfxTextRun
> textRun
;
1625 if (!mMappedFlows
.IsEmpty()) {
1626 if (!mSkipIncompleteTextRuns
&& mCurrentFramesAllSameTextRun
&&
1627 !!(mCurrentFramesAllSameTextRun
->GetFlags2() &
1628 nsTextFrameUtils::Flags::IncomingWhitespace
) ==
1629 !!(mCurrentRunContextInfo
&
1630 nsTextFrameUtils::INCOMING_WHITESPACE
) &&
1631 !!(mCurrentFramesAllSameTextRun
->GetFlags() &
1632 gfx::ShapedTextFlags::TEXT_INCOMING_ARABICCHAR
) ==
1633 !!(mCurrentRunContextInfo
&
1634 nsTextFrameUtils::INCOMING_ARABICCHAR
) &&
1635 IsTextRunValidForMappedFlows(mCurrentFramesAllSameTextRun
)) {
1636 // Optimization: We do not need to (re)build the textrun.
1637 textRun
= mCurrentFramesAllSameTextRun
;
1639 // Feed this run's text into the linebreaker to provide context.
1640 if (!SetupLineBreakerContext(textRun
)) {
1644 // Update mNextRunContextInfo appropriately
1645 mNextRunContextInfo
= nsTextFrameUtils::INCOMING_NONE
;
1646 if (textRun
->GetFlags2() & nsTextFrameUtils::Flags::TrailingWhitespace
) {
1647 mNextRunContextInfo
|= nsTextFrameUtils::INCOMING_WHITESPACE
;
1649 if (textRun
->GetFlags() &
1650 gfx::ShapedTextFlags::TEXT_TRAILING_ARABICCHAR
) {
1651 mNextRunContextInfo
|= nsTextFrameUtils::INCOMING_ARABICCHAR
;
1654 AutoTArray
<uint8_t, BIG_TEXT_NODE_SIZE
> buffer
;
1655 uint32_t bufferSize
= mMaxTextLength
* (mDoubleByteText
? 2 : 1);
1656 if (bufferSize
< mMaxTextLength
|| bufferSize
== UINT32_MAX
||
1657 !buffer
.AppendElements(bufferSize
, fallible
)) {
1660 textRun
= BuildTextRunForFrames(buffer
.Elements());
1664 if (aFlushLineBreaks
) {
1665 FlushLineBreaks(aSuppressTrailingBreak
? nullptr : textRun
.get());
1668 mCanStopOnThisLine
= true;
1672 void BuildTextRunsScanner::FlushLineBreaks(gfxTextRun
* aTrailingTextRun
) {
1673 // If the line-breaker is buffering a potentially-unfinished word,
1674 // preserve the state of being in-word so that we don't spuriously
1675 // capitalize the next letter.
1676 bool inWord
= mLineBreaker
.InWord();
1677 bool trailingLineBreak
;
1678 nsresult rv
= mLineBreaker
.Reset(&trailingLineBreak
);
1679 mLineBreaker
.SetWordContinuation(inWord
);
1680 // textRun may be null for various reasons, including because we constructed
1681 // a partial textrun just to get the linebreaker and other state set up
1682 // to build the next textrun.
1683 if (NS_SUCCEEDED(rv
) && trailingLineBreak
&& aTrailingTextRun
) {
1684 aTrailingTextRun
->SetFlagBits(nsTextFrameUtils::Flags::HasTrailingBreak
);
1687 for (uint32_t i
= 0; i
< mBreakSinks
.Length(); ++i
) {
1688 // TODO cause frames associated with the textrun to be reflowed, if they
1689 // aren't being reflowed already!
1690 mBreakSinks
[i
]->Finish(mMissingFonts
);
1692 mBreakSinks
.Clear();
1695 void BuildTextRunsScanner::AccumulateRunInfo(nsTextFrame
* aFrame
) {
1696 if (mMaxTextLength
!= UINT32_MAX
) {
1697 NS_ASSERTION(mMaxTextLength
< UINT32_MAX
- aFrame
->GetContentLength(),
1698 "integer overflow");
1699 if (mMaxTextLength
>= UINT32_MAX
- aFrame
->GetContentLength()) {
1700 mMaxTextLength
= UINT32_MAX
;
1702 mMaxTextLength
+= aFrame
->GetContentLength();
1705 mDoubleByteText
|= aFrame
->TextFragment()->Is2b();
1706 mLastFrame
= aFrame
;
1707 mCommonAncestorWithLastFrame
= aFrame
->GetParent();
1709 MappedFlow
* mappedFlow
= &mMappedFlows
[mMappedFlows
.Length() - 1];
1710 NS_ASSERTION(mappedFlow
->mStartFrame
== aFrame
||
1711 mappedFlow
->GetContentEnd() == aFrame
->GetContentOffset(),
1712 "Overlapping or discontiguous frames => BAD");
1713 mappedFlow
->mEndFrame
= aFrame
->GetNextContinuation();
1714 if (mCurrentFramesAllSameTextRun
!= aFrame
->GetTextRun(mWhichTextRun
)) {
1715 mCurrentFramesAllSameTextRun
= nullptr;
1719 mLineBreakBeforeFrames
.AppendElement(aFrame
);
1720 mStartOfLine
= false;
1724 static bool HasTerminalNewline(const nsTextFrame
* aFrame
) {
1725 if (aFrame
->GetContentLength() == 0) return false;
1726 const nsTextFragment
* frag
= aFrame
->TextFragment();
1727 return frag
->CharAt(aFrame
->GetContentEnd() - 1) == '\n';
1730 static gfxFont::Metrics
GetFirstFontMetrics(gfxFontGroup
* aFontGroup
,
1731 bool aVerticalMetrics
) {
1732 if (!aFontGroup
) return gfxFont::Metrics();
1733 gfxFont
* font
= aFontGroup
->GetFirstValidFont();
1734 return font
->GetMetrics(aVerticalMetrics
? nsFontMetrics::eVertical
1735 : nsFontMetrics::eHorizontal
);
1738 static nscoord
GetSpaceWidthAppUnits(const gfxTextRun
* aTextRun
) {
1739 // Round the space width when converting to appunits the same way textruns
1741 gfxFloat spaceWidthAppUnits
=
1742 NS_round(GetFirstFontMetrics(aTextRun
->GetFontGroup(),
1743 aTextRun
->UseCenterBaseline())
1745 aTextRun
->GetAppUnitsPerDevUnit());
1747 return spaceWidthAppUnits
;
1750 static gfxFloat
GetMinTabAdvanceAppUnits(const gfxTextRun
* aTextRun
) {
1751 gfxFloat chWidthAppUnits
= NS_round(
1752 GetFirstFontMetrics(aTextRun
->GetFontGroup(), aTextRun
->IsVertical())
1753 .ZeroOrAveCharWidth() *
1754 aTextRun
->GetAppUnitsPerDevUnit());
1755 return 0.5 * chWidthAppUnits
;
1758 static float GetSVGFontSizeScaleFactor(nsIFrame
* aFrame
) {
1759 if (!SVGUtils::IsInSVGTextSubtree(aFrame
)) {
1763 nsLayoutUtils::GetClosestFrameOfType(aFrame
, LayoutFrameType::SVGText
);
1764 MOZ_ASSERT(container
);
1765 return static_cast<SVGTextFrame
*>(container
)->GetFontSizeScaleFactor();
1768 static nscoord
LetterSpacing(nsIFrame
* aFrame
,
1769 const nsStyleText
* aStyleText
= nullptr) {
1771 aStyleText
= aFrame
->StyleText();
1774 if (SVGUtils::IsInSVGTextSubtree(aFrame
)) {
1775 // SVG text can have a scaling factor applied so that very small or very
1776 // large font-sizes don't suffer from poor glyph placement due to app unit
1777 // rounding. The used letter-spacing value must be scaled by the same
1779 Length spacing
= aStyleText
->mLetterSpacing
;
1780 spacing
.ScaleBy(GetSVGFontSizeScaleFactor(aFrame
));
1781 return spacing
.ToAppUnits();
1784 return aStyleText
->mLetterSpacing
.ToAppUnits();
1787 // This function converts non-coord values (e.g. percentages) to nscoord.
1788 static nscoord
WordSpacing(nsIFrame
* aFrame
, const gfxTextRun
* aTextRun
,
1789 const nsStyleText
* aStyleText
= nullptr) {
1791 aStyleText
= aFrame
->StyleText();
1794 if (SVGUtils::IsInSVGTextSubtree(aFrame
)) {
1795 // SVG text can have a scaling factor applied so that very small or very
1796 // large font-sizes don't suffer from poor glyph placement due to app unit
1797 // rounding. The used word-spacing value must be scaled by the same
1798 // factor, although any percentage basis has already effectively been
1799 // scaled, since it's the space glyph width, which is based on the already-
1800 // scaled font-size.
1801 auto spacing
= aStyleText
->mWordSpacing
;
1802 spacing
.ScaleLengthsBy(GetSVGFontSizeScaleFactor(aFrame
));
1803 return spacing
.Resolve([&] { return GetSpaceWidthAppUnits(aTextRun
); });
1806 return aStyleText
->mWordSpacing
.Resolve(
1807 [&] { return GetSpaceWidthAppUnits(aTextRun
); });
1810 // Returns gfxTextRunFactory::TEXT_ENABLE_SPACING if non-standard
1811 // letter-spacing or word-spacing is present.
1812 static gfx::ShapedTextFlags
GetSpacingFlags(
1813 nsIFrame
* aFrame
, const nsStyleText
* aStyleText
= nullptr) {
1814 const nsStyleText
* styleText
= aFrame
->StyleText();
1815 const auto& ls
= styleText
->mLetterSpacing
;
1816 const auto& ws
= styleText
->mWordSpacing
;
1818 // It's possible to have a calc() value that computes to zero but for which
1819 // IsDefinitelyZero() is false, in which case we'll return
1820 // TEXT_ENABLE_SPACING unnecessarily. That's ok because such cases are likely
1821 // to be rare, and avoiding TEXT_ENABLE_SPACING is just an optimization.
1822 bool nonStandardSpacing
= !ls
.IsZero() || !ws
.IsDefinitelyZero();
1823 return nonStandardSpacing
? gfx::ShapedTextFlags::TEXT_ENABLE_SPACING
1824 : gfx::ShapedTextFlags();
1827 bool BuildTextRunsScanner::ContinueTextRunAcrossFrames(nsTextFrame
* aFrame1
,
1828 nsTextFrame
* aFrame2
) {
1829 // We don't need to check font size inflation, since
1830 // |FindLineContainer| above (via |nsIFrame::CanContinueTextRun|)
1831 // ensures that text runs never cross block boundaries. This means
1832 // that the font size inflation on all text frames in the text run is
1833 // already guaranteed to be the same as each other (and for the line
1836 FrameBidiData data1
= aFrame1
->GetBidiData();
1837 FrameBidiData data2
= aFrame2
->GetBidiData();
1838 if (data1
.embeddingLevel
!= data2
.embeddingLevel
||
1839 data2
.precedingControl
!= kBidiLevelNone
) {
1844 ComputedStyle
* sc1
= aFrame1
->Style();
1845 ComputedStyle
* sc2
= aFrame2
->Style();
1847 // Any difference in writing-mode/directionality inhibits shaping across
1849 WritingMode
wm(sc1
);
1850 if (wm
!= WritingMode(sc2
)) {
1854 const nsStyleText
* textStyle1
= sc1
->StyleText();
1855 // If the first frame ends in a preformatted newline, then we end the textrun
1856 // here. This avoids creating giant textruns for an entire plain text file.
1857 // Note that we create a single text frame for a preformatted text node,
1858 // even if it has newlines in it, so typically we won't see trailing newlines
1859 // until after reflow has broken up the frame into one (or more) frames per
1860 // line. That's OK though.
1861 if (textStyle1
->NewlineIsSignificant(aFrame1
) &&
1862 HasTerminalNewline(aFrame1
)) {
1866 if (aFrame1
->GetParent()->GetContent() !=
1867 aFrame2
->GetParent()->GetContent()) {
1868 // Does aFrame, or any ancestor between it and aAncestor, have a property
1869 // that should inhibit cross-element-boundary shaping on aSide?
1870 auto PreventCrossBoundaryShaping
= [](const nsIFrame
* aFrame
,
1871 const nsIFrame
* aAncestor
,
1873 while (aFrame
!= aAncestor
) {
1874 ComputedStyle
* ctx
= aFrame
->Style();
1875 // According to https://drafts.csswg.org/css-text/#boundary-shaping:
1877 // Text shaping must be broken at inline box boundaries when any of
1878 // the following are true for any box whose boundary separates the
1879 // two typographic character units:
1881 // 1. Any of margin/border/padding separating the two typographic
1882 // character units in the inline axis is non-zero.
1883 const auto& margin
= ctx
->StyleMargin()->mMargin
.Get(aSide
);
1884 if (!margin
.ConvertsToLength() ||
1885 margin
.AsLengthPercentage().ToLength() != 0) {
1888 const auto& padding
= ctx
->StylePadding()->mPadding
.Get(aSide
);
1889 if (!padding
.ConvertsToLength() || padding
.ToLength() != 0) {
1892 if (ctx
->StyleBorder()->GetComputedBorderWidth(aSide
) != 0) {
1896 // 2. vertical-align is not baseline.
1898 // FIXME: Should this use VerticalAlignEnum()?
1899 const auto& verticalAlign
= ctx
->StyleDisplay()->mVerticalAlign
;
1900 if (!verticalAlign
.IsKeyword() ||
1901 verticalAlign
.AsKeyword() != StyleVerticalAlignKeyword::Baseline
) {
1905 // 3. The boundary is a bidi isolation boundary.
1906 const uint8_t unicodeBidi
= ctx
->StyleTextReset()->mUnicodeBidi
;
1907 if (unicodeBidi
== NS_STYLE_UNICODE_BIDI_ISOLATE
||
1908 unicodeBidi
== NS_STYLE_UNICODE_BIDI_ISOLATE_OVERRIDE
) {
1912 aFrame
= aFrame
->GetParent();
1917 const nsIFrame
* ancestor
=
1918 nsLayoutUtils::FindNearestCommonAncestorFrameWithinBlock(aFrame1
,
1922 // The two frames are within different blocks, e.g. due to block
1923 // fragmentation. In theory we shouldn't prevent cross-frame shaping
1924 // here, but it's an edge case where we should rarely decide to allow
1925 // cross-frame shaping, so we don't try harder here.
1929 // Map inline-end and inline-start to physical sides for checking presence
1930 // of non-zero margin/border/padding.
1931 Side side1
= wm
.PhysicalSide(eLogicalSideIEnd
);
1932 Side side2
= wm
.PhysicalSide(eLogicalSideIStart
);
1933 // If the frames have an embedding level that is opposite to the writing
1934 // mode, we need to swap which sides we're checking.
1935 if (IS_LEVEL_RTL(aFrame1
->GetEmbeddingLevel()) == wm
.IsBidiLTR()) {
1936 std::swap(side1
, side2
);
1939 if (PreventCrossBoundaryShaping(aFrame1
, ancestor
, side1
) ||
1940 PreventCrossBoundaryShaping(aFrame2
, ancestor
, side2
)) {
1945 if (aFrame1
->GetContent() == aFrame2
->GetContent() &&
1946 aFrame1
->GetNextInFlow() != aFrame2
) {
1947 // aFrame2 must be a non-fluid continuation of aFrame1. This can happen
1948 // sometimes when the unicode-bidi property is used; the bidi resolver
1949 // breaks text into different frames even though the text has the same
1950 // direction. We can't allow these two frames to share the same textrun
1951 // because that would violate our invariant that two flows in the same
1952 // textrun have different content elements.
1960 const nsStyleText
* textStyle2
= sc2
->StyleText();
1961 if (textStyle1
->mTextTransform
!= textStyle2
->mTextTransform
||
1962 textStyle1
->EffectiveWordBreak() != textStyle2
->EffectiveWordBreak() ||
1963 textStyle1
->mLineBreak
!= textStyle2
->mLineBreak
) {
1967 nsPresContext
* pc
= aFrame1
->PresContext();
1968 MOZ_ASSERT(pc
== aFrame2
->PresContext());
1970 const nsStyleFont
* fontStyle1
= sc1
->StyleFont();
1971 const nsStyleFont
* fontStyle2
= sc2
->StyleFont();
1972 nscoord letterSpacing1
= LetterSpacing(aFrame1
);
1973 nscoord letterSpacing2
= LetterSpacing(aFrame2
);
1974 return fontStyle1
->mFont
== fontStyle2
->mFont
&&
1975 fontStyle1
->mLanguage
== fontStyle2
->mLanguage
&&
1976 nsLayoutUtils::GetTextRunFlagsForStyle(sc1
, pc
, fontStyle1
, textStyle1
,
1978 nsLayoutUtils::GetTextRunFlagsForStyle(sc2
, pc
, fontStyle2
,
1979 textStyle2
, letterSpacing2
);
1982 void BuildTextRunsScanner::ScanFrame(nsIFrame
* aFrame
) {
1983 LayoutFrameType frameType
= aFrame
->Type();
1984 if (frameType
== LayoutFrameType::RubyTextContainer
) {
1985 // Don't include any ruby text container into the text run.
1989 // First check if we can extend the current mapped frame block. This is
1991 if (mMappedFlows
.Length() > 0) {
1992 MappedFlow
* mappedFlow
= &mMappedFlows
[mMappedFlows
.Length() - 1];
1993 if (mappedFlow
->mEndFrame
== aFrame
&&
1994 aFrame
->HasAnyStateBits(NS_FRAME_IS_FLUID_CONTINUATION
)) {
1995 NS_ASSERTION(frameType
== LayoutFrameType::Text
,
1996 "Flow-sibling of a text frame is not a text frame?");
1998 // Don't do this optimization if mLastFrame has a terminal newline...
1999 // it's quite likely preformatted and we might want to end the textrun
2000 // here. This is almost always true:
2001 if (mLastFrame
->Style() == aFrame
->Style() &&
2002 !HasTerminalNewline(mLastFrame
)) {
2003 AccumulateRunInfo(static_cast<nsTextFrame
*>(aFrame
));
2009 // Now see if we can add a new set of frames to the current textrun
2010 if (frameType
== LayoutFrameType::Text
) {
2011 nsTextFrame
* frame
= static_cast<nsTextFrame
*>(aFrame
);
2014 if (!ContinueTextRunAcrossFrames(mLastFrame
, frame
)) {
2015 FlushFrames(false, false);
2017 if (mLastFrame
->GetContent() == frame
->GetContent()) {
2018 AccumulateRunInfo(frame
);
2024 MappedFlow
* mappedFlow
= mMappedFlows
.AppendElement();
2025 if (!mappedFlow
) return;
2027 mappedFlow
->mStartFrame
= frame
;
2028 mappedFlow
->mAncestorControllingInitialBreak
= mCommonAncestorWithLastFrame
;
2030 AccumulateRunInfo(frame
);
2031 if (mMappedFlows
.Length() == 1) {
2032 mCurrentFramesAllSameTextRun
= frame
->GetTextRun(mWhichTextRun
);
2033 mCurrentRunContextInfo
= mNextRunContextInfo
;
2038 if (frameType
== LayoutFrameType::Placeholder
&&
2039 aFrame
->HasAnyStateBits(PLACEHOLDER_FOR_ABSPOS
|
2040 PLACEHOLDER_FOR_FIXEDPOS
)) {
2041 // Somewhat hacky fix for bug 1418472:
2042 // If this is a placeholder for an absolute-positioned frame, we need to
2043 // flush the line-breaker to prevent the placeholder becoming separated
2044 // from the immediately-following content.
2045 // XXX This will interrupt text shaping (ligatures, etc) if an abs-pos
2046 // element occurs within a word where shaping should be in effect, but
2047 // that's an edge case, unlikely to occur in real content. A more precise
2048 // fix might require better separation of line-breaking from textrun setup,
2049 // but that's a big invasive change (and potentially expensive for perf, as
2050 // it might introduce an additional pass over all the frames).
2051 FlushFrames(true, false);
2054 FrameTextTraversal traversal
= CanTextCrossFrameBoundary(aFrame
);
2055 bool isBR
= frameType
== LayoutFrameType::Br
;
2056 if (!traversal
.mLineBreakerCanCrossFrameBoundary
) {
2057 // BR frames are special. We do not need or want to record a break
2058 // opportunity before a BR frame.
2059 FlushFrames(true, isBR
);
2060 mCommonAncestorWithLastFrame
= aFrame
;
2061 mNextRunContextInfo
&= ~nsTextFrameUtils::INCOMING_WHITESPACE
;
2062 mStartOfLine
= false;
2063 } else if (!traversal
.mTextRunCanCrossFrameBoundary
) {
2064 FlushFrames(false, false);
2067 for (nsIFrame
* f
= traversal
.NextFrameToScan(); f
;
2068 f
= traversal
.NextFrameToScan()) {
2072 if (!traversal
.mLineBreakerCanCrossFrameBoundary
) {
2073 // Really if we're a BR frame this is unnecessary since descendInto will be
2074 // false. In fact this whole "if" statement should move into the
2076 FlushFrames(true, isBR
);
2077 mCommonAncestorWithLastFrame
= aFrame
;
2078 mNextRunContextInfo
&= ~nsTextFrameUtils::INCOMING_WHITESPACE
;
2079 } else if (!traversal
.mTextRunCanCrossFrameBoundary
) {
2080 FlushFrames(false, false);
2083 LiftCommonAncestorWithLastFrameToParent(aFrame
->GetParent());
2086 nsTextFrame
* BuildTextRunsScanner::GetNextBreakBeforeFrame(uint32_t* aIndex
) {
2087 uint32_t index
= *aIndex
;
2088 if (index
>= mLineBreakBeforeFrames
.Length()) return nullptr;
2089 *aIndex
= index
+ 1;
2090 return static_cast<nsTextFrame
*>(mLineBreakBeforeFrames
.ElementAt(index
));
2093 static gfxFontGroup
* GetFontGroupForFrame(
2094 const nsIFrame
* aFrame
, float aFontSizeInflation
,
2095 nsFontMetrics
** aOutFontMetrics
= nullptr) {
2096 RefPtr
<nsFontMetrics
> metrics
=
2097 nsLayoutUtils::GetFontMetricsForFrame(aFrame
, aFontSizeInflation
);
2098 gfxFontGroup
* fontGroup
= metrics
->GetThebesFontGroup();
2100 // Populate outparam before we return:
2101 if (aOutFontMetrics
) {
2102 metrics
.forget(aOutFontMetrics
);
2104 // XXX this is a bit bogus, we're releasing 'metrics' so the
2105 // returned font-group might actually be torn down, although because
2106 // of the way the device context caches font metrics, this seems to
2107 // not actually happen. But we should fix this.
2111 static gfxFontGroup
* GetInflatedFontGroupForFrame(nsTextFrame
* aFrame
) {
2112 gfxTextRun
* textRun
= aFrame
->GetTextRun(nsTextFrame::eInflated
);
2114 return textRun
->GetFontGroup();
2116 if (!aFrame
->InflatedFontMetrics()) {
2117 float inflation
= nsLayoutUtils::FontSizeInflationFor(aFrame
);
2118 RefPtr
<nsFontMetrics
> metrics
=
2119 nsLayoutUtils::GetFontMetricsForFrame(aFrame
, inflation
);
2120 aFrame
->SetInflatedFontMetrics(metrics
);
2122 return aFrame
->InflatedFontMetrics()->GetThebesFontGroup();
2125 static already_AddRefed
<DrawTarget
> CreateReferenceDrawTarget(
2126 const nsTextFrame
* aTextFrame
) {
2127 RefPtr
<gfxContext
> ctx
=
2128 aTextFrame
->PresShell()->CreateReferenceRenderingContext();
2129 RefPtr
<DrawTarget
> dt
= ctx
->GetDrawTarget();
2133 static already_AddRefed
<gfxTextRun
> GetHyphenTextRun(const gfxTextRun
* aTextRun
,
2134 DrawTarget
* aDrawTarget
,
2135 nsTextFrame
* aTextFrame
) {
2136 RefPtr
<DrawTarget
> dt
= aDrawTarget
;
2138 dt
= CreateReferenceDrawTarget(aTextFrame
);
2144 return aTextRun
->GetFontGroup()->MakeHyphenTextRun(
2145 dt
, aTextRun
->GetAppUnitsPerDevUnit());
2148 already_AddRefed
<gfxTextRun
> BuildTextRunsScanner::BuildTextRunForFrames(
2149 void* aTextBuffer
) {
2150 gfxSkipChars skipChars
;
2152 const void* textPtr
= aTextBuffer
;
2153 bool anyTextTransformStyle
= false;
2154 bool anyMathMLStyling
= false;
2155 bool anyTextEmphasis
= false;
2156 uint8_t sstyScriptLevel
= 0;
2157 uint32_t mathFlags
= 0;
2158 gfx::ShapedTextFlags flags
= gfx::ShapedTextFlags();
2159 nsTextFrameUtils::Flags flags2
= nsTextFrameUtils::Flags::NoBreaks
;
2161 if (mCurrentRunContextInfo
& nsTextFrameUtils::INCOMING_WHITESPACE
) {
2162 flags2
|= nsTextFrameUtils::Flags::IncomingWhitespace
;
2164 if (mCurrentRunContextInfo
& nsTextFrameUtils::INCOMING_ARABICCHAR
) {
2165 flags
|= gfx::ShapedTextFlags::TEXT_INCOMING_ARABICCHAR
;
2168 AutoTArray
<int32_t, 50> textBreakPoints
;
2169 TextRunUserData dummyData
;
2170 TextRunMappedFlow dummyMappedFlow
;
2171 TextRunMappedFlow
* userMappedFlows
;
2172 TextRunUserData
* userData
;
2173 TextRunUserData
* userDataToDestroy
;
2174 // If the situation is particularly simple (and common) we don't need to
2175 // allocate userData.
2176 if (mMappedFlows
.Length() == 1 && !mMappedFlows
[0].mEndFrame
&&
2177 mMappedFlows
[0].mStartFrame
->GetContentOffset() == 0) {
2178 userData
= &dummyData
;
2179 userMappedFlows
= &dummyMappedFlow
;
2180 userDataToDestroy
= nullptr;
2181 dummyData
.mMappedFlowCount
= mMappedFlows
.Length();
2182 dummyData
.mLastFlowIndex
= 0;
2184 userData
= CreateUserData(mMappedFlows
.Length());
2185 userMappedFlows
= reinterpret_cast<TextRunMappedFlow
*>(userData
+ 1);
2186 userDataToDestroy
= userData
;
2189 uint32_t currentTransformedTextOffset
= 0;
2191 uint32_t nextBreakIndex
= 0;
2192 nsTextFrame
* nextBreakBeforeFrame
= GetNextBreakBeforeFrame(&nextBreakIndex
);
2193 bool isSVG
= SVGUtils::IsInSVGTextSubtree(mLineContainer
);
2194 bool enabledJustification
=
2195 (mLineContainer
->StyleText()->mTextAlign
== StyleTextAlign::Justify
||
2196 mLineContainer
->StyleText()->mTextAlignLast
==
2197 StyleTextAlignLast::Justify
);
2199 const nsStyleText
* textStyle
= nullptr;
2200 const nsStyleFont
* fontStyle
= nullptr;
2201 ComputedStyle
* lastComputedStyle
= nullptr;
2202 for (uint32_t i
= 0; i
< mMappedFlows
.Length(); ++i
) {
2203 MappedFlow
* mappedFlow
= &mMappedFlows
[i
];
2204 nsTextFrame
* f
= mappedFlow
->mStartFrame
;
2206 lastComputedStyle
= f
->Style();
2207 // Detect use of text-transform or font-variant anywhere in the run
2208 textStyle
= f
->StyleText();
2209 if (!textStyle
->mTextTransform
.IsNone() ||
2210 // text-combine-upright requires converting from full-width
2211 // characters to non-full-width correspendent in some cases.
2212 lastComputedStyle
->IsTextCombined()) {
2213 anyTextTransformStyle
= true;
2215 if (textStyle
->HasEffectiveTextEmphasis()) {
2216 anyTextEmphasis
= true;
2218 flags
|= GetSpacingFlags(f
);
2219 nsTextFrameUtils::CompressionMode compression
=
2220 GetCSSWhitespaceToCompressionMode(f
, textStyle
);
2221 if ((enabledJustification
|| f
->ShouldSuppressLineBreak()) &&
2222 !textStyle
->WhiteSpaceIsSignificant() && !isSVG
) {
2223 flags
|= gfx::ShapedTextFlags::TEXT_ENABLE_SPACING
;
2225 fontStyle
= f
->StyleFont();
2226 nsIFrame
* parent
= mLineContainer
->GetParent();
2227 if (NS_MATHML_MATHVARIANT_NONE
!= fontStyle
->mMathVariant
) {
2228 if (NS_MATHML_MATHVARIANT_NORMAL
!= fontStyle
->mMathVariant
) {
2229 anyMathMLStyling
= true;
2231 } else if (mLineContainer
->HasAnyStateBits(NS_FRAME_IS_IN_SINGLE_CHAR_MI
)) {
2232 flags2
|= nsTextFrameUtils::Flags::IsSingleCharMi
;
2233 anyMathMLStyling
= true;
2234 // Test for fontstyle attribute as StyleFont() may not be accurate
2235 // To be consistent in terms of ignoring CSS style changes, fontweight
2236 // gets checked too.
2238 nsIContent
* content
= parent
->GetContent();
2239 if (content
&& content
->IsElement()) {
2240 if (content
->AsElement()->AttrValueIs(kNameSpaceID_None
,
2241 nsGkAtoms::fontstyle_
,
2242 u
"normal"_ns
, eCaseMatters
)) {
2243 mathFlags
|= MathMLTextRunFactory::MATH_FONT_STYLING_NORMAL
;
2245 if (content
->AsElement()->AttrValueIs(kNameSpaceID_None
,
2246 nsGkAtoms::fontweight_
,
2247 u
"bold"_ns
, eCaseMatters
)) {
2248 mathFlags
|= MathMLTextRunFactory::MATH_FONT_WEIGHT_BOLD
;
2253 if (mLineContainer
->HasAnyStateBits(TEXT_IS_IN_TOKEN_MATHML
)) {
2254 // All MathML tokens except <mtext> use 'math' script.
2255 if (!(parent
&& parent
->GetContent() &&
2256 parent
->GetContent()->IsMathMLElement(nsGkAtoms::mtext_
))) {
2257 flags
|= gfx::ShapedTextFlags::TEXT_USE_MATH_SCRIPT
;
2259 nsIMathMLFrame
* mathFrame
= do_QueryFrame(parent
);
2261 nsPresentationData presData
;
2262 mathFrame
->GetPresentationData(presData
);
2263 if (NS_MATHML_IS_DTLS_SET(presData
.flags
)) {
2264 mathFlags
|= MathMLTextRunFactory::MATH_FONT_FEATURE_DTLS
;
2265 anyMathMLStyling
= true;
2269 nsIFrame
* child
= mLineContainer
;
2270 uint8_t oldScriptLevel
= 0;
2272 child
->HasAnyStateBits(NS_FRAME_MATHML_SCRIPT_DESCENDANT
)) {
2273 // Reconstruct the script level ignoring any user overrides. It is
2274 // calculated this way instead of using scriptlevel to ensure the
2275 // correct ssty font feature setting is used even if the user sets a
2276 // different (especially negative) scriptlevel.
2277 nsIMathMLFrame
* mathFrame
= do_QueryFrame(parent
);
2279 sstyScriptLevel
+= mathFrame
->ScriptIncrement(child
);
2281 if (sstyScriptLevel
< oldScriptLevel
) {
2283 sstyScriptLevel
= UINT8_MAX
;
2287 parent
= parent
->GetParent();
2288 oldScriptLevel
= sstyScriptLevel
;
2290 if (sstyScriptLevel
) {
2291 anyMathMLStyling
= true;
2294 // Figure out what content is included in this flow.
2295 nsIContent
* content
= f
->GetContent();
2296 const nsTextFragment
* frag
= f
->TextFragment();
2297 int32_t contentStart
= mappedFlow
->mStartFrame
->GetContentOffset();
2298 int32_t contentEnd
= mappedFlow
->GetContentEnd();
2299 int32_t contentLength
= contentEnd
- contentStart
;
2301 TextRunMappedFlow
* newFlow
= &userMappedFlows
[i
];
2302 newFlow
->mStartFrame
= mappedFlow
->mStartFrame
;
2303 newFlow
->mDOMOffsetToBeforeTransformOffset
=
2304 skipChars
.GetOriginalCharCount() -
2305 mappedFlow
->mStartFrame
->GetContentOffset();
2306 newFlow
->mContentLength
= contentLength
;
2308 while (nextBreakBeforeFrame
&&
2309 nextBreakBeforeFrame
->GetContent() == content
) {
2310 textBreakPoints
.AppendElement(nextBreakBeforeFrame
->GetContentOffset() +
2311 newFlow
->mDOMOffsetToBeforeTransformOffset
);
2312 nextBreakBeforeFrame
= GetNextBreakBeforeFrame(&nextBreakIndex
);
2315 nsTextFrameUtils::Flags analysisFlags
;
2317 NS_ASSERTION(mDoubleByteText
, "Wrong buffer char size!");
2318 char16_t
* bufStart
= static_cast<char16_t
*>(aTextBuffer
);
2319 char16_t
* bufEnd
= nsTextFrameUtils::TransformText(
2320 frag
->Get2b() + contentStart
, contentLength
, bufStart
, compression
,
2321 &mNextRunContextInfo
, &skipChars
, &analysisFlags
);
2322 aTextBuffer
= bufEnd
;
2323 currentTransformedTextOffset
=
2324 bufEnd
- static_cast<const char16_t
*>(textPtr
);
2326 if (mDoubleByteText
) {
2327 // Need to expand the text. First transform it into a temporary buffer,
2329 AutoTArray
<uint8_t, BIG_TEXT_NODE_SIZE
> tempBuf
;
2330 uint8_t* bufStart
= tempBuf
.AppendElements(contentLength
, fallible
);
2332 DestroyUserData(userDataToDestroy
);
2335 uint8_t* end
= nsTextFrameUtils::TransformText(
2336 reinterpret_cast<const uint8_t*>(frag
->Get1b()) + contentStart
,
2337 contentLength
, bufStart
, compression
, &mNextRunContextInfo
,
2338 &skipChars
, &analysisFlags
);
2340 ExpandBuffer(static_cast<char16_t
*>(aTextBuffer
),
2341 tempBuf
.Elements(), end
- tempBuf
.Elements());
2342 currentTransformedTextOffset
= static_cast<char16_t
*>(aTextBuffer
) -
2343 static_cast<const char16_t
*>(textPtr
);
2345 uint8_t* bufStart
= static_cast<uint8_t*>(aTextBuffer
);
2346 uint8_t* end
= nsTextFrameUtils::TransformText(
2347 reinterpret_cast<const uint8_t*>(frag
->Get1b()) + contentStart
,
2348 contentLength
, bufStart
, compression
, &mNextRunContextInfo
,
2349 &skipChars
, &analysisFlags
);
2351 currentTransformedTextOffset
=
2352 end
- static_cast<const uint8_t*>(textPtr
);
2355 flags2
|= analysisFlags
;
2358 void* finalUserData
;
2359 if (userData
== &dummyData
) {
2360 flags2
|= nsTextFrameUtils::Flags::IsSimpleFlow
;
2362 finalUserData
= mMappedFlows
[0].mStartFrame
;
2364 finalUserData
= userData
;
2367 uint32_t transformedLength
= currentTransformedTextOffset
;
2369 // Now build the textrun
2370 nsTextFrame
* firstFrame
= mMappedFlows
[0].mStartFrame
;
2371 float fontInflation
;
2372 gfxFontGroup
* fontGroup
;
2373 if (mWhichTextRun
== nsTextFrame::eNotInflated
) {
2374 fontInflation
= 1.0f
;
2375 fontGroup
= GetFontGroupForFrame(firstFrame
, fontInflation
);
2377 fontInflation
= nsLayoutUtils::FontSizeInflationFor(firstFrame
);
2378 fontGroup
= GetInflatedFontGroupForFrame(firstFrame
);
2382 // Refresh fontgroup if necessary, before trying to build textruns.
2383 fontGroup
->CheckForUpdatedPlatformList();
2385 DestroyUserData(userDataToDestroy
);
2389 if (flags2
& nsTextFrameUtils::Flags::HasTab
) {
2390 flags
|= gfx::ShapedTextFlags::TEXT_ENABLE_SPACING
;
2392 if (flags2
& nsTextFrameUtils::Flags::HasShy
) {
2393 flags
|= gfx::ShapedTextFlags::TEXT_ENABLE_HYPHEN_BREAKS
;
2395 if (mBidiEnabled
&& (IS_LEVEL_RTL(firstFrame
->GetEmbeddingLevel()))) {
2396 flags
|= gfx::ShapedTextFlags::TEXT_IS_RTL
;
2398 if (mNextRunContextInfo
& nsTextFrameUtils::INCOMING_WHITESPACE
) {
2399 flags2
|= nsTextFrameUtils::Flags::TrailingWhitespace
;
2401 if (mNextRunContextInfo
& nsTextFrameUtils::INCOMING_ARABICCHAR
) {
2402 flags
|= gfx::ShapedTextFlags::TEXT_TRAILING_ARABICCHAR
;
2404 // ContinueTextRunAcrossFrames guarantees that it doesn't matter which
2405 // frame's style is used, so we use a mixture of the first frame and
2406 // last frame's style
2407 flags
|= nsLayoutUtils::GetTextRunFlagsForStyle(
2408 lastComputedStyle
, firstFrame
->PresContext(), fontStyle
, textStyle
,
2409 LetterSpacing(firstFrame
, textStyle
));
2410 // XXX this is a bit of a hack. For performance reasons, if we're favouring
2411 // performance over quality, don't try to get accurate glyph extents.
2412 if (!(flags
& gfx::ShapedTextFlags::TEXT_OPTIMIZE_SPEED
)) {
2413 flags
|= gfx::ShapedTextFlags::TEXT_NEED_BOUNDING_BOX
;
2416 // Convert linebreak coordinates to transformed string offsets
2417 NS_ASSERTION(nextBreakIndex
== mLineBreakBeforeFrames
.Length(),
2418 "Didn't find all the frames to break-before...");
2419 gfxSkipCharsIterator
iter(skipChars
);
2420 AutoTArray
<uint32_t, 50> textBreakPointsAfterTransform
;
2421 for (uint32_t i
= 0; i
< textBreakPoints
.Length(); ++i
) {
2422 nsTextFrameUtils::AppendLineBreakOffset(
2423 &textBreakPointsAfterTransform
,
2424 iter
.ConvertOriginalToSkipped(textBreakPoints
[i
]));
2427 nsTextFrameUtils::AppendLineBreakOffset(&textBreakPointsAfterTransform
,
2431 // Setup factory chain
2432 bool needsToMaskPassword
= NeedsToMaskPassword(firstFrame
);
2433 UniquePtr
<nsTransformingTextRunFactory
> transformingFactory
;
2434 if (anyTextTransformStyle
|| needsToMaskPassword
) {
2435 transformingFactory
= MakeUnique
<nsCaseTransformTextRunFactory
>(
2436 std::move(transformingFactory
));
2438 if (anyMathMLStyling
) {
2439 transformingFactory
= MakeUnique
<MathMLTextRunFactory
>(
2440 std::move(transformingFactory
), mathFlags
, sstyScriptLevel
,
2443 nsTArray
<RefPtr
<nsTransformedCharStyle
>> styles
;
2444 if (transformingFactory
) {
2445 uint32_t unmaskStart
= 0, unmaskEnd
= UINT32_MAX
;
2446 if (needsToMaskPassword
) {
2447 unmaskStart
= unmaskEnd
= UINT32_MAX
;
2448 TextEditor
* passwordEditor
=
2449 nsContentUtils::GetTextEditorFromAnonymousNodeWithoutCreation(
2450 firstFrame
->GetContent());
2451 if (passwordEditor
&& !passwordEditor
->IsAllMasked()) {
2452 unmaskStart
= passwordEditor
->UnmaskedStart();
2453 unmaskEnd
= passwordEditor
->UnmaskedEnd();
2457 iter
.SetOriginalOffset(0);
2458 for (uint32_t i
= 0; i
< mMappedFlows
.Length(); ++i
) {
2459 MappedFlow
* mappedFlow
= &mMappedFlows
[i
];
2461 ComputedStyle
* sc
= nullptr;
2462 RefPtr
<nsTransformedCharStyle
> defaultStyle
;
2463 RefPtr
<nsTransformedCharStyle
> unmaskStyle
;
2464 for (f
= mappedFlow
->mStartFrame
; f
!= mappedFlow
->mEndFrame
;
2465 f
= f
->GetNextContinuation()) {
2466 uint32_t skippedOffset
= iter
.GetSkippedOffset();
2467 // Text-combined frames have content-dependent transform, so we
2468 // want to create new nsTransformedCharStyle for them anyway.
2469 if (sc
!= f
->Style() || sc
->IsTextCombined()) {
2471 defaultStyle
= new nsTransformedCharStyle(sc
, f
->PresContext());
2472 if (sc
->IsTextCombined() && f
->CountGraphemeClusters() > 1) {
2473 defaultStyle
->mForceNonFullWidth
= true;
2475 if (needsToMaskPassword
) {
2476 defaultStyle
->mMaskPassword
= true;
2477 if (unmaskStart
!= unmaskEnd
) {
2478 unmaskStyle
= new nsTransformedCharStyle(sc
, f
->PresContext());
2479 unmaskStyle
->mForceNonFullWidth
=
2480 defaultStyle
->mForceNonFullWidth
;
2484 iter
.AdvanceOriginal(f
->GetContentLength());
2485 uint32_t skippedEnd
= iter
.GetSkippedOffset();
2487 uint32_t skippedUnmaskStart
=
2488 iter
.ConvertOriginalToSkipped(unmaskStart
);
2489 uint32_t skippedUnmaskEnd
= iter
.ConvertOriginalToSkipped(unmaskEnd
);
2490 iter
.SetSkippedOffset(skippedEnd
);
2491 for (; skippedOffset
< std::min(skippedEnd
, skippedUnmaskStart
);
2493 styles
.AppendElement(defaultStyle
);
2495 for (; skippedOffset
< std::min(skippedEnd
, skippedUnmaskEnd
);
2497 styles
.AppendElement(unmaskStyle
);
2499 for (; skippedOffset
< skippedEnd
; ++skippedOffset
) {
2500 styles
.AppendElement(defaultStyle
);
2503 for (; skippedOffset
< skippedEnd
; ++skippedOffset
) {
2504 styles
.AppendElement(defaultStyle
);
2509 flags2
|= nsTextFrameUtils::Flags::IsTransformed
;
2510 NS_ASSERTION(iter
.GetSkippedOffset() == transformedLength
,
2511 "We didn't cover all the characters in the text run!");
2514 RefPtr
<gfxTextRun
> textRun
;
2515 gfxTextRunFactory::Parameters params
= {
2519 textBreakPointsAfterTransform
.Elements(),
2520 uint32_t(textBreakPointsAfterTransform
.Length()),
2521 int32_t(firstFrame
->PresContext()->AppUnitsPerDevPixel())};
2523 if (mDoubleByteText
) {
2524 const char16_t
* text
= static_cast<const char16_t
*>(textPtr
);
2525 if (transformingFactory
) {
2526 textRun
= transformingFactory
->MakeTextRun(
2527 text
, transformedLength
, ¶ms
, fontGroup
, flags
, flags2
,
2528 std::move(styles
), true);
2530 // ownership of the factory has passed to the textrun
2531 // TODO: bug 1285316: clean up ownership transfer from the factory to
2533 Unused
<< transformingFactory
.release();
2536 textRun
= fontGroup
->MakeTextRun(text
, transformedLength
, ¶ms
, flags
,
2537 flags2
, mMissingFonts
);
2540 const uint8_t* text
= static_cast<const uint8_t*>(textPtr
);
2541 flags
|= gfx::ShapedTextFlags::TEXT_IS_8BIT
;
2542 if (transformingFactory
) {
2543 textRun
= transformingFactory
->MakeTextRun(
2544 text
, transformedLength
, ¶ms
, fontGroup
, flags
, flags2
,
2545 std::move(styles
), true);
2547 // ownership of the factory has passed to the textrun
2548 // TODO: bug 1285316: clean up ownership transfer from the factory to
2550 Unused
<< transformingFactory
.release();
2553 textRun
= fontGroup
->MakeTextRun(text
, transformedLength
, ¶ms
, flags
,
2554 flags2
, mMissingFonts
);
2558 DestroyUserData(userDataToDestroy
);
2562 // We have to set these up after we've created the textrun, because
2563 // the breaks may be stored in the textrun during this very call.
2564 // This is a bit annoying because it requires another loop over the frames
2565 // making up the textrun, but I don't see a way to avoid this.
2566 SetupBreakSinksForTextRun(textRun
.get(), textPtr
);
2568 if (anyTextEmphasis
) {
2569 SetupTextEmphasisForTextRun(textRun
.get(), textPtr
);
2572 if (mSkipIncompleteTextRuns
) {
2573 mSkipIncompleteTextRuns
= !TextContainsLineBreakerWhiteSpace(
2574 textPtr
, transformedLength
, mDoubleByteText
);
2575 // Since we're doing to destroy the user data now, avoid a dangling
2576 // pointer. Strictly speaking we don't need to do this since it should
2577 // not be used (since this textrun will not be used and will be
2578 // itself deleted soon), but it's always better to not have dangling
2580 textRun
->SetUserData(nullptr);
2581 DestroyUserData(userDataToDestroy
);
2585 // Actually wipe out the textruns associated with the mapped frames and
2586 // associate those frames with this text run.
2587 AssignTextRun(textRun
.get(), fontInflation
);
2588 return textRun
.forget();
2591 // This is a cut-down version of BuildTextRunForFrames used to set up
2592 // context for the line-breaker, when the textrun has already been created.
2593 // So it does the same walk over the mMappedFlows, but doesn't actually
2594 // build a new textrun.
2595 bool BuildTextRunsScanner::SetupLineBreakerContext(gfxTextRun
* aTextRun
) {
2596 AutoTArray
<uint8_t, BIG_TEXT_NODE_SIZE
> buffer
;
2597 uint32_t bufferSize
= mMaxTextLength
* (mDoubleByteText
? 2 : 1);
2598 if (bufferSize
< mMaxTextLength
|| bufferSize
== UINT32_MAX
) {
2601 void* textPtr
= buffer
.AppendElements(bufferSize
, fallible
);
2606 gfxSkipChars skipChars
;
2608 for (uint32_t i
= 0; i
< mMappedFlows
.Length(); ++i
) {
2609 MappedFlow
* mappedFlow
= &mMappedFlows
[i
];
2610 nsTextFrame
* f
= mappedFlow
->mStartFrame
;
2612 const nsStyleText
* textStyle
= f
->StyleText();
2613 nsTextFrameUtils::CompressionMode compression
=
2614 GetCSSWhitespaceToCompressionMode(f
, textStyle
);
2616 // Figure out what content is included in this flow.
2617 const nsTextFragment
* frag
= f
->TextFragment();
2618 int32_t contentStart
= mappedFlow
->mStartFrame
->GetContentOffset();
2619 int32_t contentEnd
= mappedFlow
->GetContentEnd();
2620 int32_t contentLength
= contentEnd
- contentStart
;
2622 nsTextFrameUtils::Flags analysisFlags
;
2624 NS_ASSERTION(mDoubleByteText
, "Wrong buffer char size!");
2625 char16_t
* bufStart
= static_cast<char16_t
*>(textPtr
);
2626 char16_t
* bufEnd
= nsTextFrameUtils::TransformText(
2627 frag
->Get2b() + contentStart
, contentLength
, bufStart
, compression
,
2628 &mNextRunContextInfo
, &skipChars
, &analysisFlags
);
2631 if (mDoubleByteText
) {
2632 // Need to expand the text. First transform it into a temporary buffer,
2634 AutoTArray
<uint8_t, BIG_TEXT_NODE_SIZE
> tempBuf
;
2635 uint8_t* bufStart
= tempBuf
.AppendElements(contentLength
, fallible
);
2639 uint8_t* end
= nsTextFrameUtils::TransformText(
2640 reinterpret_cast<const uint8_t*>(frag
->Get1b()) + contentStart
,
2641 contentLength
, bufStart
, compression
, &mNextRunContextInfo
,
2642 &skipChars
, &analysisFlags
);
2643 textPtr
= ExpandBuffer(static_cast<char16_t
*>(textPtr
),
2644 tempBuf
.Elements(), end
- tempBuf
.Elements());
2646 uint8_t* bufStart
= static_cast<uint8_t*>(textPtr
);
2647 uint8_t* end
= nsTextFrameUtils::TransformText(
2648 reinterpret_cast<const uint8_t*>(frag
->Get1b()) + contentStart
,
2649 contentLength
, bufStart
, compression
, &mNextRunContextInfo
,
2650 &skipChars
, &analysisFlags
);
2656 // We have to set these up after we've created the textrun, because
2657 // the breaks may be stored in the textrun during this very call.
2658 // This is a bit annoying because it requires another loop over the frames
2659 // making up the textrun, but I don't see a way to avoid this.
2660 SetupBreakSinksForTextRun(aTextRun
, buffer
.Elements());
2665 static bool HasCompressedLeadingWhitespace(
2666 nsTextFrame
* aFrame
, const nsStyleText
* aStyleText
,
2667 int32_t aContentEndOffset
, const gfxSkipCharsIterator
& aIterator
) {
2668 if (!aIterator
.IsOriginalCharSkipped()) return false;
2670 gfxSkipCharsIterator iter
= aIterator
;
2671 int32_t frameContentOffset
= aFrame
->GetContentOffset();
2672 const nsTextFragment
* frag
= aFrame
->TextFragment();
2673 while (frameContentOffset
< aContentEndOffset
&&
2674 iter
.IsOriginalCharSkipped()) {
2675 if (IsTrimmableSpace(frag
, frameContentOffset
, aStyleText
)) return true;
2676 ++frameContentOffset
;
2677 iter
.AdvanceOriginal(1);
2682 void BuildTextRunsScanner::SetupBreakSinksForTextRun(gfxTextRun
* aTextRun
,
2683 const void* aTextPtr
) {
2684 using mozilla::intl::LineBreaker
;
2686 // textruns have uniform language
2687 const nsStyleFont
* styleFont
= mMappedFlows
[0].mStartFrame
->StyleFont();
2688 // We should only use a language for hyphenation if it was specified
2690 nsAtom
* hyphenationLanguage
=
2691 styleFont
->mExplicitLanguage
? styleFont
->mLanguage
.get() : nullptr;
2692 // We keep this pointed at the skip-chars data for the current mappedFlow.
2693 // This lets us cheaply check whether the flow has compressed initial
2695 gfxSkipCharsIterator
iter(aTextRun
->GetSkipChars());
2697 for (uint32_t i
= 0; i
< mMappedFlows
.Length(); ++i
) {
2698 MappedFlow
* mappedFlow
= &mMappedFlows
[i
];
2699 // The CSS word-break value may change within a word, so we reset it for
2700 // each MappedFlow. The line-breaker will flush its text if the property
2701 // actually changes.
2702 const auto* styleText
= mappedFlow
->mStartFrame
->StyleText();
2703 auto wordBreak
= styleText
->EffectiveWordBreak();
2704 switch (wordBreak
) {
2705 case StyleWordBreak::BreakAll
:
2706 mLineBreaker
.SetWordBreak(LineBreaker::WordBreak::BreakAll
);
2708 case StyleWordBreak::KeepAll
:
2709 mLineBreaker
.SetWordBreak(LineBreaker::WordBreak::KeepAll
);
2711 case StyleWordBreak::Normal
:
2713 MOZ_ASSERT(wordBreak
== StyleWordBreak::Normal
);
2714 mLineBreaker
.SetWordBreak(LineBreaker::WordBreak::Normal
);
2717 switch (styleText
->mLineBreak
) {
2718 case StyleLineBreak::Auto
:
2719 mLineBreaker
.SetStrictness(LineBreaker::Strictness::Auto
);
2721 case StyleLineBreak::Normal
:
2722 mLineBreaker
.SetStrictness(LineBreaker::Strictness::Normal
);
2724 case StyleLineBreak::Loose
:
2725 mLineBreaker
.SetStrictness(LineBreaker::Strictness::Loose
);
2727 case StyleLineBreak::Strict
:
2728 mLineBreaker
.SetStrictness(LineBreaker::Strictness::Strict
);
2730 case StyleLineBreak::Anywhere
:
2731 mLineBreaker
.SetStrictness(LineBreaker::Strictness::Anywhere
);
2735 uint32_t offset
= iter
.GetSkippedOffset();
2736 gfxSkipCharsIterator iterNext
= iter
;
2737 iterNext
.AdvanceOriginal(mappedFlow
->GetContentEnd() -
2738 mappedFlow
->mStartFrame
->GetContentOffset());
2740 UniquePtr
<BreakSink
>* breakSink
= mBreakSinks
.AppendElement(
2741 MakeUnique
<BreakSink
>(aTextRun
, mDrawTarget
, offset
));
2742 if (!breakSink
|| !*breakSink
) return;
2744 uint32_t length
= iterNext
.GetSkippedOffset() - offset
;
2746 nsIFrame
* initialBreakController
=
2747 mappedFlow
->mAncestorControllingInitialBreak
;
2748 if (!initialBreakController
) {
2749 initialBreakController
= mLineContainer
;
2751 if (!initialBreakController
->StyleText()->WhiteSpaceCanWrap(
2752 initialBreakController
)) {
2753 flags
|= nsLineBreaker::BREAK_SUPPRESS_INITIAL
;
2755 nsTextFrame
* startFrame
= mappedFlow
->mStartFrame
;
2756 const nsStyleText
* textStyle
= startFrame
->StyleText();
2757 if (!textStyle
->WhiteSpaceCanWrap(startFrame
)) {
2758 flags
|= nsLineBreaker::BREAK_SUPPRESS_INSIDE
;
2760 if (aTextRun
->GetFlags2() & nsTextFrameUtils::Flags::NoBreaks
) {
2761 flags
|= nsLineBreaker::BREAK_SKIP_SETTING_NO_BREAKS
;
2763 if (textStyle
->mTextTransform
.case_
== StyleTextTransformCase::Capitalize
) {
2764 flags
|= nsLineBreaker::BREAK_NEED_CAPITALIZATION
;
2766 if (textStyle
->mHyphens
== StyleHyphens::Auto
&&
2767 textStyle
->mLineBreak
!= StyleLineBreak::Anywhere
) {
2768 flags
|= nsLineBreaker::BREAK_USE_AUTO_HYPHENATION
;
2771 if (HasCompressedLeadingWhitespace(startFrame
, textStyle
,
2772 mappedFlow
->GetContentEnd(), iter
)) {
2773 mLineBreaker
.AppendInvisibleWhitespace(flags
);
2777 BreakSink
* sink
= mSkipIncompleteTextRuns
? nullptr : (*breakSink
).get();
2778 if (mDoubleByteText
) {
2779 const char16_t
* text
= reinterpret_cast<const char16_t
*>(aTextPtr
);
2780 mLineBreaker
.AppendText(hyphenationLanguage
, text
+ offset
, length
,
2783 const uint8_t* text
= reinterpret_cast<const uint8_t*>(aTextPtr
);
2784 mLineBreaker
.AppendText(hyphenationLanguage
, text
+ offset
, length
,
2793 static bool MayCharacterHaveEmphasisMark(uint32_t aCh
) {
2794 auto category
= unicode::GetGeneralCategory(aCh
);
2795 // Comparing an unsigned variable against zero is a compile error,
2796 // so we use static assert here to ensure we really don't need to
2797 // compare it with the given constant.
2798 static_assert(std::is_unsigned_v
<decltype(category
)> &&
2799 HB_UNICODE_GENERAL_CATEGORY_CONTROL
== 0,
2800 "if this constant is not zero, or category is signed, "
2801 "we need to explicitly do the comparison below");
2802 return !(category
<= HB_UNICODE_GENERAL_CATEGORY_UNASSIGNED
||
2803 (category
>= HB_UNICODE_GENERAL_CATEGORY_LINE_SEPARATOR
&&
2804 category
<= HB_UNICODE_GENERAL_CATEGORY_SPACE_SEPARATOR
));
2807 static bool MayCharacterHaveEmphasisMark(uint8_t aCh
) {
2808 // 0x00~0x1f and 0x7f~0x9f are in category Cc
2809 // 0x20 and 0xa0 are in category Zs
2810 bool result
= !(aCh
<= 0x20 || (aCh
>= 0x7f && aCh
<= 0xa0));
2811 MOZ_ASSERT(result
== MayCharacterHaveEmphasisMark(uint32_t(aCh
)),
2812 "result for uint8_t should match result for uint32_t");
2816 void BuildTextRunsScanner::SetupTextEmphasisForTextRun(gfxTextRun
* aTextRun
,
2817 const void* aTextPtr
) {
2818 if (!mDoubleByteText
) {
2819 auto text
= reinterpret_cast<const uint8_t*>(aTextPtr
);
2820 for (auto i
: IntegerRange(aTextRun
->GetLength())) {
2821 if (!MayCharacterHaveEmphasisMark(text
[i
])) {
2822 aTextRun
->SetNoEmphasisMark(i
);
2826 auto text
= reinterpret_cast<const char16_t
*>(aTextPtr
);
2827 auto length
= aTextRun
->GetLength();
2828 for (size_t i
= 0; i
< length
; ++i
) {
2829 if (i
+ 1 < length
&& NS_IS_SURROGATE_PAIR(text
[i
], text
[i
+ 1])) {
2830 uint32_t ch
= SURROGATE_TO_UCS4(text
[i
], text
[i
+ 1]);
2831 if (!MayCharacterHaveEmphasisMark(ch
)) {
2832 aTextRun
->SetNoEmphasisMark(i
);
2833 aTextRun
->SetNoEmphasisMark(i
+ 1);
2837 if (!MayCharacterHaveEmphasisMark(uint32_t(text
[i
]))) {
2838 aTextRun
->SetNoEmphasisMark(i
);
2845 // Find the flow corresponding to aContent in aUserData
2846 static inline TextRunMappedFlow
* FindFlowForContent(
2847 TextRunUserData
* aUserData
, nsIContent
* aContent
,
2848 TextRunMappedFlow
* userMappedFlows
) {
2849 // Find the flow that contains us
2850 int32_t i
= aUserData
->mLastFlowIndex
;
2853 // Search starting at the current position and examine close-by
2854 // positions first, moving further and further away as we go.
2855 while (i
>= 0 && uint32_t(i
) < aUserData
->mMappedFlowCount
) {
2856 TextRunMappedFlow
* flow
= &userMappedFlows
[i
];
2857 if (flow
->mStartFrame
->GetContent() == aContent
) {
2863 delta
= -delta
+ sign
;
2866 // We ran into an array edge. Add |delta| to |i| once more to get
2867 // back to the side where we still need to search, then step in
2868 // the |sign| direction.
2871 for (; i
< int32_t(aUserData
->mMappedFlowCount
); ++i
) {
2872 TextRunMappedFlow
* flow
= &userMappedFlows
[i
];
2873 if (flow
->mStartFrame
->GetContent() == aContent
) {
2878 for (; i
>= 0; --i
) {
2879 TextRunMappedFlow
* flow
= &userMappedFlows
[i
];
2880 if (flow
->mStartFrame
->GetContent() == aContent
) {
2889 void BuildTextRunsScanner::AssignTextRun(gfxTextRun
* aTextRun
,
2891 for (uint32_t i
= 0; i
< mMappedFlows
.Length(); ++i
) {
2892 MappedFlow
* mappedFlow
= &mMappedFlows
[i
];
2893 nsTextFrame
* startFrame
= mappedFlow
->mStartFrame
;
2894 nsTextFrame
* endFrame
= mappedFlow
->mEndFrame
;
2896 for (f
= startFrame
; f
!= endFrame
; f
= f
->GetNextContinuation()) {
2898 if (f
->GetTextRun(mWhichTextRun
)) {
2899 gfxTextRun
* textRun
= f
->GetTextRun(mWhichTextRun
);
2900 if (textRun
->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow
) {
2901 if (mMappedFlows
[0].mStartFrame
!= GetFrameForSimpleFlow(textRun
)) {
2902 NS_WARNING("REASSIGNING SIMPLE FLOW TEXT RUN!");
2906 static_cast<TextRunUserData
*>(aTextRun
->GetUserData());
2907 TextRunMappedFlow
* userMappedFlows
= GetMappedFlows(aTextRun
);
2908 if (userData
->mMappedFlowCount
>= mMappedFlows
.Length() ||
2909 userMappedFlows
[userData
->mMappedFlowCount
- 1].mStartFrame
!=
2910 mMappedFlows
[userdata
->mMappedFlowCount
- 1].mStartFrame
) {
2911 NS_WARNING("REASSIGNING MULTIFLOW TEXT RUN (not append)!");
2917 gfxTextRun
* oldTextRun
= f
->GetTextRun(mWhichTextRun
);
2919 nsTextFrame
* firstFrame
= nullptr;
2920 uint32_t startOffset
= 0;
2921 if (oldTextRun
->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow
) {
2922 firstFrame
= GetFrameForSimpleFlow(oldTextRun
);
2925 static_cast<TextRunUserData
*>(oldTextRun
->GetUserData());
2926 TextRunMappedFlow
* userMappedFlows
= GetMappedFlows(oldTextRun
);
2927 firstFrame
= userMappedFlows
[0].mStartFrame
;
2928 if (MOZ_UNLIKELY(f
!= firstFrame
)) {
2929 TextRunMappedFlow
* flow
=
2930 FindFlowForContent(userData
, f
->GetContent(), userMappedFlows
);
2932 startOffset
= flow
->mDOMOffsetToBeforeTransformOffset
;
2934 NS_ERROR("Can't find flow containing frame 'f'");
2939 // Optimization: if |f| is the first frame in the flow then there are no
2940 // prev-continuations that use |oldTextRun|.
2941 nsTextFrame
* clearFrom
= nullptr;
2942 if (MOZ_UNLIKELY(f
!= firstFrame
)) {
2943 // If all the frames in the mapped flow starting at |f| (inclusive)
2944 // are empty then we let the prev-continuations keep the old text run.
2945 gfxSkipCharsIterator
iter(oldTextRun
->GetSkipChars(), startOffset
,
2946 f
->GetContentOffset());
2947 uint32_t textRunOffset
=
2948 iter
.ConvertOriginalToSkipped(f
->GetContentOffset());
2949 clearFrom
= textRunOffset
== oldTextRun
->GetLength() ? f
: nullptr;
2951 f
->ClearTextRun(clearFrom
, mWhichTextRun
);
2954 if (firstFrame
&& !firstFrame
->GetTextRun(mWhichTextRun
)) {
2955 // oldTextRun was destroyed - assert that we don't reference it.
2956 for (uint32_t j
= 0; j
< mBreakSinks
.Length(); ++j
) {
2957 NS_ASSERTION(oldTextRun
!= mBreakSinks
[j
]->mTextRun
,
2958 "destroyed text run is still in use");
2963 f
->SetTextRun(aTextRun
, mWhichTextRun
, aInflation
);
2965 // Set this bit now; we can't set it any earlier because
2966 // f->ClearTextRun() might clear it out.
2967 nsFrameState whichTextRunState
=
2968 startFrame
->GetTextRun(nsTextFrame::eInflated
) == aTextRun
2969 ? TEXT_IN_TEXTRUN_USER_DATA
2970 : TEXT_IN_UNINFLATED_TEXTRUN_USER_DATA
;
2971 startFrame
->AddStateBits(whichTextRunState
);
2975 NS_QUERYFRAME_HEAD(nsTextFrame
)
2976 NS_QUERYFRAME_ENTRY(nsTextFrame
)
2977 NS_QUERYFRAME_TAIL_INHERITING(nsIFrame
)
2979 gfxSkipCharsIterator
nsTextFrame::EnsureTextRun(
2980 TextRunType aWhichTextRun
, DrawTarget
* aRefDrawTarget
,
2981 nsIFrame
* aLineContainer
, const nsLineList::iterator
* aLine
,
2982 uint32_t* aFlowEndInTextRun
) {
2983 gfxTextRun
* textRun
= GetTextRun(aWhichTextRun
);
2984 if (!textRun
|| (aLine
&& (*aLine
)->GetInvalidateTextRuns())) {
2985 RefPtr
<DrawTarget
> refDT
= aRefDrawTarget
;
2987 refDT
= CreateReferenceDrawTarget(this);
2990 BuildTextRuns(refDT
, this, aLineContainer
, aLine
, aWhichTextRun
);
2992 textRun
= GetTextRun(aWhichTextRun
);
2994 // A text run was not constructed for this frame. This is bad. The caller
2995 // will check mTextRun.
2996 return gfxSkipCharsIterator(gfxPlatform::GetPlatform()->EmptySkipChars(),
2999 TabWidthStore
* tabWidths
= GetProperty(TabWidthProperty());
3000 if (tabWidths
&& tabWidths
->mValidForContentOffset
!= GetContentOffset()) {
3001 RemoveProperty(TabWidthProperty());
3005 if (textRun
->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow
) {
3006 if (aFlowEndInTextRun
) {
3007 *aFlowEndInTextRun
= textRun
->GetLength();
3009 return gfxSkipCharsIterator(textRun
->GetSkipChars(), 0, mContentOffset
);
3012 auto userData
= static_cast<TextRunUserData
*>(textRun
->GetUserData());
3013 TextRunMappedFlow
* userMappedFlows
= GetMappedFlows(textRun
);
3014 TextRunMappedFlow
* flow
=
3015 FindFlowForContent(userData
, mContent
, userMappedFlows
);
3017 // Since textruns can only contain one flow for a given content element,
3018 // this must be our flow.
3019 uint32_t flowIndex
= flow
- userMappedFlows
;
3020 userData
->mLastFlowIndex
= flowIndex
;
3021 gfxSkipCharsIterator
iter(textRun
->GetSkipChars(),
3022 flow
->mDOMOffsetToBeforeTransformOffset
,
3024 if (aFlowEndInTextRun
) {
3025 if (flowIndex
+ 1 < userData
->mMappedFlowCount
) {
3026 gfxSkipCharsIterator
end(textRun
->GetSkipChars());
3027 *aFlowEndInTextRun
= end
.ConvertOriginalToSkipped(
3028 flow
[1].mStartFrame
->GetContentOffset() +
3029 flow
[1].mDOMOffsetToBeforeTransformOffset
);
3031 *aFlowEndInTextRun
= textRun
->GetLength();
3037 NS_ERROR("Can't find flow containing this frame???");
3038 return gfxSkipCharsIterator(gfxPlatform::GetPlatform()->EmptySkipChars(), 0);
3041 static uint32_t GetEndOfTrimmedText(const nsTextFragment
* aFrag
,
3042 const nsStyleText
* aStyleText
,
3043 uint32_t aStart
, uint32_t aEnd
,
3044 gfxSkipCharsIterator
* aIterator
,
3045 bool aAllowHangingWS
= false) {
3046 aIterator
->SetSkippedOffset(aEnd
);
3047 while (aIterator
->GetSkippedOffset() > aStart
) {
3048 aIterator
->AdvanceSkipped(-1);
3049 if (!IsTrimmableSpace(aFrag
, aIterator
->GetOriginalOffset(), aStyleText
,
3051 return aIterator
->GetSkippedOffset() + 1;
3056 nsTextFrame::TrimmedOffsets
nsTextFrame::GetTrimmedOffsets(
3057 const nsTextFragment
* aFrag
, TrimmedOffsetFlags aFlags
) const {
3058 NS_ASSERTION(mTextRun
, "Need textrun here");
3059 if (!(aFlags
& TrimmedOffsetFlags::NotPostReflow
)) {
3060 // This should not be used during reflow. We need our TEXT_REFLOW_FLAGS
3061 // to be set correctly. If our parent wasn't reflowed due to the frame
3062 // tree being too deep then the return value doesn't matter.
3064 !HasAnyStateBits(NS_FRAME_FIRST_REFLOW
) ||
3065 GetParent()->HasAnyStateBits(NS_FRAME_TOO_DEEP_IN_FRAME_TREE
),
3066 "Can only call this on frames that have been reflowed");
3067 NS_ASSERTION(!HasAnyStateBits(NS_FRAME_IN_REFLOW
),
3068 "Can only call this on frames that are not being reflowed");
3071 TrimmedOffsets offsets
= {GetContentOffset(), GetContentLength()};
3072 const nsStyleText
* textStyle
= StyleText();
3073 // Note that pre-line newlines should still allow us to trim spaces
3075 if (textStyle
->WhiteSpaceIsSignificant()) return offsets
;
3077 if (!(aFlags
& TrimmedOffsetFlags::NoTrimBefore
) &&
3078 ((aFlags
& TrimmedOffsetFlags::NotPostReflow
) ||
3079 HasAnyStateBits(TEXT_START_OF_LINE
))) {
3080 int32_t whitespaceCount
=
3081 GetTrimmableWhitespaceCount(aFrag
, offsets
.mStart
, offsets
.mLength
, 1);
3082 offsets
.mStart
+= whitespaceCount
;
3083 offsets
.mLength
-= whitespaceCount
;
3086 if (!(aFlags
& TrimmedOffsetFlags::NoTrimAfter
) &&
3087 ((aFlags
& TrimmedOffsetFlags::NotPostReflow
) ||
3088 HasAnyStateBits(TEXT_END_OF_LINE
))) {
3089 // This treats a trailing 'pre-line' newline as trimmable. That's fine,
3090 // it's actually what we want since we want whitespace before it to
3092 int32_t whitespaceCount
= GetTrimmableWhitespaceCount(
3093 aFrag
, offsets
.GetEnd() - 1, offsets
.mLength
, -1);
3094 offsets
.mLength
-= whitespaceCount
;
3099 static bool IsJustifiableCharacter(const nsStyleText
* aTextStyle
,
3100 const nsTextFragment
* aFrag
, int32_t aPos
,
3102 NS_ASSERTION(aPos
>= 0, "negative position?!");
3104 StyleTextJustify justifyStyle
= aTextStyle
->mTextJustify
;
3105 if (justifyStyle
== StyleTextJustify::None
) {
3109 char16_t ch
= aFrag
->CharAt(aPos
);
3110 if (ch
== '\n' || ch
== '\t' || ch
== '\r') {
3113 if (ch
== ' ' || ch
== CH_NBSP
) {
3114 // Don't justify spaces that are combined with diacriticals
3115 if (!aFrag
->Is2b()) {
3118 return !nsTextFrameUtils::IsSpaceCombiningSequenceTail(
3119 aFrag
->Get2b() + aPos
+ 1, aFrag
->GetLength() - (aPos
+ 1));
3122 if (justifyStyle
== StyleTextJustify::InterCharacter
) {
3124 } else if (justifyStyle
== StyleTextJustify::InterWord
) {
3128 // text-justify: auto
3133 if ( // Number Forms, Arrows, Mathematical Operators
3134 (0x2150u
<= ch
&& ch
<= 0x22ffu
) ||
3135 // Enclosed Alphanumerics
3136 (0x2460u
<= ch
&& ch
<= 0x24ffu
) ||
3137 // Block Elements, Geometric Shapes, Miscellaneous Symbols, Dingbats
3138 (0x2580u
<= ch
&& ch
<= 0x27bfu
) ||
3139 // Supplemental Arrows-A, Braille Patterns, Supplemental Arrows-B,
3140 // Miscellaneous Mathematical Symbols-B,
3141 // Supplemental Mathematical Operators, Miscellaneous Symbols and Arrows
3142 (0x27f0u
<= ch
&& ch
<= 0x2bffu
) ||
3143 // CJK Radicals Supplement, CJK Radicals Supplement, Ideographic
3144 // Description Characters, CJK Symbols and Punctuation, Hiragana,
3145 // Katakana, Bopomofo
3146 (0x2e80u
<= ch
&& ch
<= 0x312fu
) ||
3147 // Kanbun, Bopomofo Extended, Katakana Phonetic Extensions,
3148 // Enclosed CJK Letters and Months, CJK Compatibility,
3149 // CJK Unified Ideographs Extension A, Yijing Hexagram Symbols,
3150 // CJK Unified Ideographs, Yi Syllables, Yi Radicals
3151 (0x3190u
<= ch
&& ch
<= 0xabffu
) ||
3152 // CJK Compatibility Ideographs
3153 (0xf900u
<= ch
&& ch
<= 0xfaffu
) ||
3154 // Halfwidth and Fullwidth Forms (a part)
3155 (0xff5eu
<= ch
&& ch
<= 0xff9fu
)) {
3158 if (NS_IS_HIGH_SURROGATE(ch
)) {
3159 if (char32_t u
= aFrag
->ScalarValueAt(aPos
)) {
3160 // CJK Unified Ideographs Extension B,
3161 // CJK Unified Ideographs Extension C,
3162 // CJK Unified Ideographs Extension D,
3163 // CJK Compatibility Ideographs Supplement
3164 if (0x20000u
<= u
&& u
<= 0x2ffffu
) {
3173 void nsTextFrame::ClearMetrics(ReflowOutput
& aMetrics
) {
3174 aMetrics
.ClearSize();
3175 aMetrics
.SetBlockStartAscent(0);
3178 AddStateBits(TEXT_NO_RENDERED_GLYPHS
);
3181 static int32_t FindChar(const nsTextFragment
* frag
, int32_t aOffset
,
3182 int32_t aLength
, char16_t ch
) {
3185 const char16_t
* str
= frag
->Get2b() + aOffset
;
3186 for (; i
< aLength
; ++i
) {
3187 if (*str
== ch
) return i
+ aOffset
;
3191 if (uint16_t(ch
) <= 0xFF) {
3192 const char* str
= frag
->Get1b() + aOffset
;
3193 const void* p
= memchr(str
, ch
, aLength
);
3194 if (p
) return (static_cast<const char*>(p
) - str
) + aOffset
;
3200 static bool IsChineseOrJapanese(const nsTextFrame
* aFrame
) {
3201 if (aFrame
->ShouldSuppressLineBreak()) {
3202 // Always treat ruby as CJ language so that those characters can
3203 // be expanded properly even when surrounded by other language.
3207 nsAtom
* language
= aFrame
->StyleFont()->mLanguage
;
3211 return nsStyleUtil::MatchesLanguagePrefix(language
, u
"ja") ||
3212 nsStyleUtil::MatchesLanguagePrefix(language
, u
"zh");
3216 static bool IsInBounds(const gfxSkipCharsIterator
& aStart
,
3217 int32_t aContentLength
, gfxTextRun::Range aRange
) {
3218 if (aStart
.GetSkippedOffset() > aRange
.start
) return false;
3219 if (aContentLength
== INT32_MAX
) return true;
3220 gfxSkipCharsIterator
iter(aStart
);
3221 iter
.AdvanceOriginal(aContentLength
);
3222 return iter
.GetSkippedOffset() >= aRange
.end
;
3226 nsTextFrame::PropertyProvider::PropertyProvider(
3227 gfxTextRun
* aTextRun
, const nsStyleText
* aTextStyle
,
3228 const nsTextFragment
* aFrag
, nsTextFrame
* aFrame
,
3229 const gfxSkipCharsIterator
& aStart
, int32_t aLength
,
3230 nsIFrame
* aLineContainer
, nscoord aOffsetFromBlockOriginForTabs
,
3231 nsTextFrame::TextRunType aWhichTextRun
)
3232 : mTextRun(aTextRun
),
3233 mFontGroup(nullptr),
3234 mTextStyle(aTextStyle
),
3236 mLineContainer(aLineContainer
),
3239 mTempIterator(aStart
),
3240 mTabWidths(nullptr),
3241 mTabWidthsAnalyzedLimit(0),
3243 mWordSpacing(WordSpacing(aFrame
, mTextRun
, aTextStyle
)),
3244 mLetterSpacing(LetterSpacing(aFrame
, aTextStyle
)),
3245 mMinTabAdvance(-1.0),
3247 mOffsetFromBlockOriginForTabs(aOffsetFromBlockOriginForTabs
),
3248 mJustificationArrayStart(0),
3250 mWhichTextRun(aWhichTextRun
) {
3251 NS_ASSERTION(mStart
.IsInitialized(), "Start not initialized?");
3254 nsTextFrame::PropertyProvider::PropertyProvider(
3255 nsTextFrame
* aFrame
, const gfxSkipCharsIterator
& aStart
,
3256 nsTextFrame::TextRunType aWhichTextRun
, nsFontMetrics
* aFontMetrics
)
3257 : mTextRun(aFrame
->GetTextRun(aWhichTextRun
)),
3258 mFontGroup(nullptr),
3259 mFontMetrics(aFontMetrics
),
3260 mTextStyle(aFrame
->StyleText()),
3261 mFrag(aFrame
->TextFragment()),
3262 mLineContainer(nullptr),
3265 mTempIterator(aStart
),
3266 mTabWidths(nullptr),
3267 mTabWidthsAnalyzedLimit(0),
3268 mLength(aFrame
->GetContentLength()),
3269 mWordSpacing(WordSpacing(aFrame
, mTextRun
)),
3270 mLetterSpacing(LetterSpacing(aFrame
)),
3271 mMinTabAdvance(-1.0),
3273 mOffsetFromBlockOriginForTabs(0),
3274 mJustificationArrayStart(0),
3276 mWhichTextRun(aWhichTextRun
) {
3277 NS_ASSERTION(mTextRun
, "Textrun not initialized!");
3280 already_AddRefed
<DrawTarget
> nsTextFrame::PropertyProvider::GetDrawTarget()
3282 return CreateReferenceDrawTarget(GetFrame());
3285 gfxFloat
nsTextFrame::PropertyProvider::MinTabAdvance() const {
3286 if (mMinTabAdvance
< 0.0) {
3287 mMinTabAdvance
= GetMinTabAdvanceAppUnits(mTextRun
);
3289 return mMinTabAdvance
;
3293 * Finds the offset of the first character of the cluster containing aPos
3295 static void FindClusterStart(const gfxTextRun
* aTextRun
, int32_t aOriginalStart
,
3296 gfxSkipCharsIterator
* aPos
) {
3297 while (aPos
->GetOriginalOffset() > aOriginalStart
) {
3298 if (aPos
->IsOriginalCharSkipped() ||
3299 aTextRun
->IsClusterStart(aPos
->GetSkippedOffset())) {
3302 aPos
->AdvanceOriginal(-1);
3307 * Finds the offset of the last character of the cluster containing aPos.
3308 * If aAllowSplitLigature is false, we also check for a ligature-group
3311 static void FindClusterEnd(const gfxTextRun
* aTextRun
, int32_t aOriginalEnd
,
3312 gfxSkipCharsIterator
* aPos
,
3313 bool aAllowSplitLigature
= true) {
3314 MOZ_ASSERT(aPos
->GetOriginalOffset() < aOriginalEnd
,
3315 "character outside string");
3317 aPos
->AdvanceOriginal(1);
3318 while (aPos
->GetOriginalOffset() < aOriginalEnd
) {
3319 if (aPos
->IsOriginalCharSkipped() ||
3320 (aTextRun
->IsClusterStart(aPos
->GetSkippedOffset()) &&
3321 (aAllowSplitLigature
||
3322 aTextRun
->IsLigatureGroupStart(aPos
->GetSkippedOffset())))) {
3325 aPos
->AdvanceOriginal(1);
3327 aPos
->AdvanceOriginal(-1);
3330 JustificationInfo
nsTextFrame::PropertyProvider::ComputeJustification(
3331 Range aRange
, nsTArray
<JustificationAssignment
>* aAssignments
) {
3332 JustificationInfo info
;
3334 // Horizontal-in-vertical frame is orthogonal to the line, so it
3335 // doesn't actually include any justification opportunity inside.
3336 // The spec says such frame should be treated as a U+FFFC. Since we
3337 // do not insert justification opportunities on the sides of that
3338 // character, the sides of this frame are not justifiable either.
3339 if (mFrame
->Style()->IsTextCombined()) {
3343 bool isCJ
= IsChineseOrJapanese(mFrame
);
3344 nsSkipCharsRunIterator
run(
3345 mStart
, nsSkipCharsRunIterator::LENGTH_INCLUDES_SKIPPED
, aRange
.Length());
3346 run
.SetOriginalOffset(aRange
.start
);
3347 mJustificationArrayStart
= run
.GetSkippedOffset();
3349 nsTArray
<JustificationAssignment
> assignments
;
3350 assignments
.SetCapacity(aRange
.Length());
3351 while (run
.NextRun()) {
3352 uint32_t originalOffset
= run
.GetOriginalOffset();
3353 uint32_t skippedOffset
= run
.GetSkippedOffset();
3354 uint32_t length
= run
.GetRunLength();
3355 assignments
.SetLength(skippedOffset
+ length
- mJustificationArrayStart
);
3357 gfxSkipCharsIterator iter
= run
.GetPos();
3358 for (uint32_t i
= 0; i
< length
; ++i
) {
3359 uint32_t offset
= originalOffset
+ i
;
3360 if (!IsJustifiableCharacter(mTextStyle
, mFrag
, offset
, isCJ
)) {
3364 iter
.SetOriginalOffset(offset
);
3366 FindClusterStart(mTextRun
, originalOffset
, &iter
);
3367 uint32_t firstCharOffset
= iter
.GetSkippedOffset();
3368 uint32_t firstChar
= firstCharOffset
> mJustificationArrayStart
3369 ? firstCharOffset
- mJustificationArrayStart
3372 info
.mIsStartJustifiable
= true;
3374 auto& assign
= assignments
[firstChar
];
3375 auto& prevAssign
= assignments
[firstChar
- 1];
3376 if (prevAssign
.mGapsAtEnd
) {
3377 prevAssign
.mGapsAtEnd
= 1;
3378 assign
.mGapsAtStart
= 1;
3380 assign
.mGapsAtStart
= 2;
3381 info
.mInnerOpportunities
++;
3385 FindClusterEnd(mTextRun
, originalOffset
+ length
, &iter
);
3386 uint32_t lastChar
= iter
.GetSkippedOffset() - mJustificationArrayStart
;
3387 // Assign the two gaps temporary to the last char. If the next cluster is
3388 // justifiable as well, one of the gaps will be removed by code above.
3389 assignments
[lastChar
].mGapsAtEnd
= 2;
3390 info
.mInnerOpportunities
++;
3392 // Skip the whole cluster
3393 i
= iter
.GetOriginalOffset() - originalOffset
;
3397 if (!assignments
.IsEmpty() && assignments
.LastElement().mGapsAtEnd
) {
3398 // We counted the expansion opportunity after the last character,
3399 // but it is not an inner opportunity.
3400 MOZ_ASSERT(info
.mInnerOpportunities
> 0);
3401 info
.mInnerOpportunities
--;
3402 info
.mIsEndJustifiable
= true;
3406 *aAssignments
= std::move(assignments
);
3411 // aStart, aLength in transformed string offsets
3412 void nsTextFrame::PropertyProvider::GetSpacing(Range aRange
,
3413 Spacing
* aSpacing
) const {
3416 !(mTextRun
->GetFlags2() & nsTextFrameUtils::Flags::HasTab
));
3419 static bool CanAddSpacingAfter(const gfxTextRun
* aTextRun
, uint32_t aOffset
,
3420 bool aNewlineIsSignificant
) {
3421 if (aOffset
+ 1 >= aTextRun
->GetLength()) return true;
3422 return aTextRun
->IsClusterStart(aOffset
+ 1) &&
3423 aTextRun
->IsLigatureGroupStart(aOffset
+ 1) &&
3424 !aTextRun
->CharIsFormattingControl(aOffset
) &&
3425 !(aNewlineIsSignificant
&& aTextRun
->CharIsNewline(aOffset
));
3428 static gfxFloat
ComputeTabWidthAppUnits(const nsIFrame
* aFrame
,
3429 gfxTextRun
* aTextRun
) {
3430 const auto& tabSize
= aFrame
->StyleText()->mMozTabSize
;
3431 if (tabSize
.IsLength()) {
3432 nscoord w
= tabSize
.length
._0
.ToAppUnits();
3437 MOZ_ASSERT(tabSize
.IsNumber());
3438 gfxFloat spaces
= tabSize
.number
._0
;
3439 MOZ_ASSERT(spaces
>= 0);
3441 // Round the space width when converting to appunits the same way
3443 gfxFloat spaceWidthAppUnits
= NS_round(
3444 GetFirstFontMetrics(aTextRun
->GetFontGroup(), aTextRun
->IsVertical())
3446 aTextRun
->GetAppUnitsPerDevUnit());
3447 return spaces
* spaceWidthAppUnits
;
3450 void nsTextFrame::PropertyProvider::GetSpacingInternal(Range aRange
,
3452 bool aIgnoreTabs
) const {
3453 MOZ_ASSERT(IsInBounds(mStart
, mLength
, aRange
), "Range out of bounds");
3456 for (index
= 0; index
< aRange
.Length(); ++index
) {
3457 aSpacing
[index
].mBefore
= 0.0;
3458 aSpacing
[index
].mAfter
= 0.0;
3461 if (mFrame
->Style()->IsTextCombined()) {
3465 // Find our offset into the original+transformed string
3466 gfxSkipCharsIterator
start(mStart
);
3467 start
.SetSkippedOffset(aRange
.start
);
3469 // First, compute the word and letter spacing
3470 if (mWordSpacing
|| mLetterSpacing
) {
3471 // Iterate over non-skipped characters
3472 nsSkipCharsRunIterator
run(
3473 start
, nsSkipCharsRunIterator::LENGTH_UNSKIPPED_ONLY
, aRange
.Length());
3474 bool newlineIsSignificant
= mTextStyle
->NewlineIsSignificant(mFrame
);
3475 while (run
.NextRun()) {
3476 uint32_t runOffsetInSubstring
= run
.GetSkippedOffset() - aRange
.start
;
3477 gfxSkipCharsIterator iter
= run
.GetPos();
3478 for (int32_t i
= 0; i
< run
.GetRunLength(); ++i
) {
3479 if (CanAddSpacingAfter(mTextRun
, run
.GetSkippedOffset() + i
,
3480 newlineIsSignificant
)) {
3481 // End of a cluster, not in a ligature: put letter-spacing after it
3482 aSpacing
[runOffsetInSubstring
+ i
].mAfter
+= mLetterSpacing
;
3484 if (IsCSSWordSpacingSpace(mFrag
, i
+ run
.GetOriginalOffset(), mFrame
,
3486 // It kinda sucks, but space characters can be part of clusters,
3487 // and even still be whitespace (I think!)
3488 iter
.SetSkippedOffset(run
.GetSkippedOffset() + i
);
3489 FindClusterEnd(mTextRun
, run
.GetOriginalOffset() + run
.GetRunLength(),
3491 uint32_t runOffset
= iter
.GetSkippedOffset() - aRange
.start
;
3492 aSpacing
[runOffset
].mAfter
+= mWordSpacing
;
3498 // Now add tab spacing, if there is any
3500 gfxFloat tabWidth
= ComputeTabWidthAppUnits(mFrame
, mTextRun
);
3502 CalcTabWidths(aRange
, tabWidth
);
3504 mTabWidths
->ApplySpacing(aSpacing
,
3505 aRange
.start
- mStart
.GetSkippedOffset(),
3511 // Now add in justification spacing
3512 if (mJustificationSpacings
.Length() > 0) {
3513 // If there is any spaces trimmed at the end, aStart + aLength may
3514 // be larger than the flags array. When that happens, we can simply
3515 // ignore those spaces.
3516 auto arrayEnd
= mJustificationArrayStart
+
3517 static_cast<uint32_t>(mJustificationSpacings
.Length());
3518 auto end
= std::min(aRange
.end
, arrayEnd
);
3519 MOZ_ASSERT(aRange
.start
>= mJustificationArrayStart
);
3520 for (auto i
= aRange
.start
; i
< end
; i
++) {
3521 const auto& spacing
=
3522 mJustificationSpacings
[i
- mJustificationArrayStart
];
3523 uint32_t offset
= i
- aRange
.start
;
3524 aSpacing
[offset
].mBefore
+= spacing
.mBefore
;
3525 aSpacing
[offset
].mAfter
+= spacing
.mAfter
;
3530 // aX and the result are in whole appunits.
3531 static gfxFloat
AdvanceToNextTab(gfxFloat aX
, gfxFloat aTabWidth
,
3532 gfxFloat aMinAdvance
) {
3533 // Advance aX to the next multiple of aTabWidth. We must advance
3534 // by at least aMinAdvance.
3535 return ceil((aX
+ aMinAdvance
) / aTabWidth
) * aTabWidth
;
3538 void nsTextFrame::PropertyProvider::CalcTabWidths(Range aRange
,
3539 gfxFloat aTabWidth
) const {
3540 MOZ_ASSERT(aTabWidth
> 0);
3543 if (mReflowing
&& !mLineContainer
) {
3544 // Intrinsic width computation does its own tab processing. We
3545 // just don't do anything here.
3549 mTabWidths
= mFrame
->GetProperty(TabWidthProperty());
3551 // If we're not reflowing, we should have already computed the
3552 // tab widths; check that they're available as far as the last
3553 // tab character present (if any)
3554 for (uint32_t i
= aRange
.end
; i
> aRange
.start
; --i
) {
3555 if (mTextRun
->CharIsTab(i
- 1)) {
3556 uint32_t startOffset
= mStart
.GetSkippedOffset();
3557 NS_ASSERTION(mTabWidths
&& mTabWidths
->mLimit
+ startOffset
>= i
,
3558 "Precomputed tab widths are missing!");
3567 uint32_t startOffset
= mStart
.GetSkippedOffset();
3568 MOZ_ASSERT(aRange
.start
>= startOffset
, "wrong start offset");
3569 MOZ_ASSERT(aRange
.end
<= startOffset
+ mLength
, "beyond the end");
3571 (mTabWidths
? mTabWidths
->mLimit
: mTabWidthsAnalyzedLimit
) + startOffset
;
3572 if (tabsEnd
< aRange
.end
) {
3573 NS_ASSERTION(mReflowing
,
3574 "We need precomputed tab widths, but don't have enough.");
3576 for (uint32_t i
= tabsEnd
; i
< aRange
.end
; ++i
) {
3578 GetSpacingInternal(Range(i
, i
+ 1), &spacing
, true);
3579 mOffsetFromBlockOriginForTabs
+= spacing
.mBefore
;
3581 if (!mTextRun
->CharIsTab(i
)) {
3582 if (mTextRun
->IsClusterStart(i
)) {
3583 uint32_t clusterEnd
= i
+ 1;
3584 while (clusterEnd
< mTextRun
->GetLength() &&
3585 !mTextRun
->IsClusterStart(clusterEnd
)) {
3588 mOffsetFromBlockOriginForTabs
+=
3589 mTextRun
->GetAdvanceWidth(Range(i
, clusterEnd
), nullptr);
3593 mTabWidths
= new TabWidthStore(mFrame
->GetContentOffset());
3594 mFrame
->SetProperty(TabWidthProperty(), mTabWidths
);
3596 double nextTab
= AdvanceToNextTab(mOffsetFromBlockOriginForTabs
,
3597 aTabWidth
, MinTabAdvance());
3598 mTabWidths
->mWidths
.AppendElement(
3599 TabWidth(i
- startOffset
,
3600 NSToIntRound(nextTab
- mOffsetFromBlockOriginForTabs
)));
3601 mOffsetFromBlockOriginForTabs
= nextTab
;
3604 mOffsetFromBlockOriginForTabs
+= spacing
.mAfter
;
3608 mTabWidths
->mLimit
= aRange
.end
- startOffset
;
3613 // Delete any stale property that may be left on the frame
3614 mFrame
->RemoveProperty(TabWidthProperty());
3615 mTabWidthsAnalyzedLimit
=
3616 std::max(mTabWidthsAnalyzedLimit
, aRange
.end
- startOffset
);
3620 gfxFloat
nsTextFrame::PropertyProvider::GetHyphenWidth() const {
3621 if (mHyphenWidth
< 0) {
3622 mHyphenWidth
= GetFontGroup()->GetHyphenWidth(this);
3624 return mHyphenWidth
+ mLetterSpacing
;
3627 static inline bool IS_HYPHEN(char16_t u
) {
3628 return u
== char16_t('-') || // HYPHEN-MINUS
3629 u
== 0x058A || // ARMENIAN HYPHEN
3630 u
== 0x2010 || // HYPHEN
3631 u
== 0x2012 || // FIGURE DASH
3632 u
== 0x2013; // EN DASH
3635 void nsTextFrame::PropertyProvider::GetHyphenationBreaks(
3636 Range aRange
, HyphenType
* aBreakBefore
) const {
3637 MOZ_ASSERT(IsInBounds(mStart
, mLength
, aRange
), "Range out of bounds");
3638 MOZ_ASSERT(mLength
!= INT32_MAX
, "Can't call this with undefined length");
3640 if (!mTextStyle
->WhiteSpaceCanWrap(mFrame
) ||
3641 mTextStyle
->mHyphens
== StyleHyphens::None
) {
3642 memset(aBreakBefore
, static_cast<uint8_t>(HyphenType::None
),
3643 aRange
.Length() * sizeof(HyphenType
));
3647 // Iterate through the original-string character runs
3648 nsSkipCharsRunIterator
run(
3649 mStart
, nsSkipCharsRunIterator::LENGTH_UNSKIPPED_ONLY
, aRange
.Length());
3650 run
.SetSkippedOffset(aRange
.start
);
3651 // We need to visit skipped characters so that we can detect SHY
3652 run
.SetVisitSkipped();
3654 int32_t prevTrailingCharOffset
= run
.GetPos().GetOriginalOffset() - 1;
3655 bool allowHyphenBreakBeforeNextChar
=
3656 prevTrailingCharOffset
>= mStart
.GetOriginalOffset() &&
3657 prevTrailingCharOffset
< mStart
.GetOriginalOffset() + mLength
&&
3658 mFrag
->CharAt(prevTrailingCharOffset
) == CH_SHY
;
3660 while (run
.NextRun()) {
3661 NS_ASSERTION(run
.GetRunLength() > 0, "Shouldn't return zero-length runs");
3662 if (run
.IsSkipped()) {
3663 // Check if there's a soft hyphen which would let us hyphenate before
3664 // the next non-skipped character. Don't look at soft hyphens followed
3665 // by other skipped characters, we won't use them.
3666 allowHyphenBreakBeforeNextChar
=
3667 mFrag
->CharAt(run
.GetOriginalOffset() + run
.GetRunLength() - 1) ==
3670 int32_t runOffsetInSubstring
= run
.GetSkippedOffset() - aRange
.start
;
3671 memset(aBreakBefore
+ runOffsetInSubstring
,
3672 static_cast<uint8_t>(HyphenType::None
),
3673 run
.GetRunLength() * sizeof(HyphenType
));
3674 // Don't allow hyphen breaks at the start of the line
3675 aBreakBefore
[runOffsetInSubstring
] =
3676 allowHyphenBreakBeforeNextChar
&&
3677 (!mFrame
->HasAnyStateBits(TEXT_START_OF_LINE
) ||
3678 run
.GetSkippedOffset() > mStart
.GetSkippedOffset())
3681 allowHyphenBreakBeforeNextChar
= false;
3685 if (mTextStyle
->mHyphens
== StyleHyphens::Auto
) {
3686 gfxSkipCharsIterator
skipIter(mStart
);
3687 for (uint32_t i
= 0; i
< aRange
.Length(); ++i
) {
3688 if (IS_HYPHEN(mFrag
->CharAt(
3689 skipIter
.ConvertSkippedToOriginal(aRange
.start
+ i
)))) {
3690 if (i
< aRange
.Length() - 1) {
3691 aBreakBefore
[i
+ 1] = HyphenType::Explicit
;
3696 if (mTextRun
->CanHyphenateBefore(aRange
.start
+ i
) &&
3697 aBreakBefore
[i
] == HyphenType::None
) {
3698 aBreakBefore
[i
] = HyphenType::AutoWithoutManualInSameWord
;
3704 void nsTextFrame::PropertyProvider::InitializeForDisplay(bool aTrimAfter
) {
3705 nsTextFrame::TrimmedOffsets trimmed
= mFrame
->GetTrimmedOffsets(
3706 mFrag
, (aTrimAfter
? nsTextFrame::TrimmedOffsetFlags::Default
3707 : nsTextFrame::TrimmedOffsetFlags::NoTrimAfter
));
3708 mStart
.SetOriginalOffset(trimmed
.mStart
);
3709 mLength
= trimmed
.mLength
;
3710 SetupJustificationSpacing(true);
3713 void nsTextFrame::PropertyProvider::InitializeForMeasure() {
3714 nsTextFrame::TrimmedOffsets trimmed
= mFrame
->GetTrimmedOffsets(
3715 mFrag
, nsTextFrame::TrimmedOffsetFlags::NotPostReflow
);
3716 mStart
.SetOriginalOffset(trimmed
.mStart
);
3717 mLength
= trimmed
.mLength
;
3718 SetupJustificationSpacing(false);
3721 void nsTextFrame::PropertyProvider::SetupJustificationSpacing(
3723 MOZ_ASSERT(mLength
!= INT32_MAX
, "Can't call this with undefined length");
3725 if (!mFrame
->HasAnyStateBits(TEXT_JUSTIFICATION_ENABLED
)) {
3729 gfxSkipCharsIterator
start(mStart
), end(mStart
);
3730 // We can't just use our mLength here; when InitializeForDisplay is
3731 // called with false for aTrimAfter, we still shouldn't be assigning
3732 // justification space to any trailing whitespace.
3733 nsTextFrame::TrimmedOffsets trimmed
= mFrame
->GetTrimmedOffsets(
3734 mFrag
, (aPostReflow
? nsTextFrame::TrimmedOffsetFlags::Default
3735 : nsTextFrame::TrimmedOffsetFlags::NotPostReflow
));
3736 end
.AdvanceOriginal(trimmed
.mLength
);
3737 gfxSkipCharsIterator
realEnd(end
);
3739 Range
range(uint32_t(start
.GetOriginalOffset()),
3740 uint32_t(end
.GetOriginalOffset()));
3741 nsTArray
<JustificationAssignment
> assignments
;
3742 JustificationInfo info
= ComputeJustification(range
, &assignments
);
3744 auto assign
= mFrame
->GetJustificationAssignment();
3745 auto totalGaps
= JustificationUtils::CountGaps(info
, assign
);
3746 if (!totalGaps
|| assignments
.IsEmpty()) {
3747 // Nothing to do, nothing is justifiable and we shouldn't have any
3748 // justification space assigned
3752 // Remember that textrun measurements are in the run's orientation,
3753 // so its advance "width" is actually a height in vertical writing modes,
3754 // corresponding to the inline-direction of the frame.
3755 gfxFloat naturalWidth
= mTextRun
->GetAdvanceWidth(
3756 Range(mStart
.GetSkippedOffset(), realEnd
.GetSkippedOffset()), this);
3757 if (mFrame
->HasAnyStateBits(TEXT_HYPHEN_BREAK
)) {
3758 naturalWidth
+= GetHyphenWidth();
3760 nscoord totalSpacing
= mFrame
->ISize() - naturalWidth
;
3761 if (totalSpacing
<= 0) {
3762 // No space available
3766 assignments
[0].mGapsAtStart
= assign
.mGapsAtStart
;
3767 assignments
.LastElement().mGapsAtEnd
= assign
.mGapsAtEnd
;
3769 MOZ_ASSERT(mJustificationSpacings
.IsEmpty());
3770 JustificationApplicationState
state(totalGaps
, totalSpacing
);
3771 mJustificationSpacings
.SetCapacity(assignments
.Length());
3772 for (const JustificationAssignment
& assign
: assignments
) {
3773 Spacing
* spacing
= mJustificationSpacings
.AppendElement();
3774 spacing
->mBefore
= state
.Consume(assign
.mGapsAtStart
);
3775 spacing
->mAfter
= state
.Consume(assign
.mGapsAtEnd
);
3779 void nsTextFrame::PropertyProvider::InitFontGroupAndFontMetrics() const {
3780 if (!mFontMetrics
) {
3781 if (mWhichTextRun
== nsTextFrame::eInflated
) {
3782 if (!mFrame
->InflatedFontMetrics()) {
3783 float inflation
= mFrame
->GetFontSizeInflation();
3784 mFontMetrics
= nsLayoutUtils::GetFontMetricsForFrame(mFrame
, inflation
);
3785 mFrame
->SetInflatedFontMetrics(mFontMetrics
);
3787 mFontMetrics
= mFrame
->InflatedFontMetrics();
3790 mFontMetrics
= nsLayoutUtils::GetFontMetricsForFrame(mFrame
, 1.0f
);
3793 mFontGroup
= mFontMetrics
->GetThebesFontGroup();
3796 //----------------------------------------------------------------------
3798 static nscolor
EnsureDifferentColors(nscolor colorA
, nscolor colorB
) {
3799 if (colorA
== colorB
) {
3801 res
= NS_RGB(NS_GET_R(colorA
) ^ 0xff, NS_GET_G(colorA
) ^ 0xff,
3802 NS_GET_B(colorA
) ^ 0xff);
3808 //-----------------------------------------------------------------------------
3810 nsTextPaintStyle::nsTextPaintStyle(nsTextFrame
* aFrame
)
3812 mPresContext(aFrame
->PresContext()),
3813 mInitCommonColors(false),
3814 mInitSelectionColorsAndShadow(false),
3815 mResolveColors(true),
3816 mSelectionTextColor(NS_RGBA(0, 0, 0, 0)),
3817 mSelectionBGColor(NS_RGBA(0, 0, 0, 0)),
3818 mSufficientContrast(0),
3819 mFrameBackgroundColor(NS_RGBA(0, 0, 0, 0)),
3820 mSystemFieldForegroundColor(NS_RGBA(0, 0, 0, 0)),
3821 mSystemFieldBackgroundColor(NS_RGBA(0, 0, 0, 0)) {
3822 for (uint32_t i
= 0; i
< ArrayLength(mSelectionStyle
); i
++)
3823 mSelectionStyle
[i
].mInit
= false;
3826 bool nsTextPaintStyle::EnsureSufficientContrast(nscolor
* aForeColor
,
3827 nscolor
* aBackColor
) {
3830 // If the combination of selection background color and frame background color
3831 // is sufficient contrast, don't exchange the selection colors.
3832 int32_t backLuminosityDifference
=
3833 NS_LUMINOSITY_DIFFERENCE(*aBackColor
, mFrameBackgroundColor
);
3834 if (backLuminosityDifference
>= mSufficientContrast
) return false;
3836 // Otherwise, we should use the higher-contrast color for the selection
3837 // background color.
3838 int32_t foreLuminosityDifference
=
3839 NS_LUMINOSITY_DIFFERENCE(*aForeColor
, mFrameBackgroundColor
);
3840 if (backLuminosityDifference
< foreLuminosityDifference
) {
3841 nscolor tmpColor
= *aForeColor
;
3842 *aForeColor
= *aBackColor
;
3843 *aBackColor
= tmpColor
;
3849 nscolor
nsTextPaintStyle::GetTextColor() {
3850 if (SVGUtils::IsInSVGTextSubtree(mFrame
)) {
3851 if (!mResolveColors
) return NS_SAME_AS_FOREGROUND_COLOR
;
3853 const nsStyleSVG
* style
= mFrame
->StyleSVG();
3854 switch (style
->mFill
.kind
.tag
) {
3855 case StyleSVGPaintKind::Tag::None
:
3856 return NS_RGBA(0, 0, 0, 0);
3857 case StyleSVGPaintKind::Tag::Color
:
3858 return nsLayoutUtils::GetColor(mFrame
, &nsStyleSVG::mFill
);
3860 NS_ERROR("cannot resolve SVG paint to nscolor");
3861 return NS_RGBA(0, 0, 0, 255);
3865 return nsLayoutUtils::GetColor(mFrame
, &nsStyleText::mWebkitTextFillColor
);
3868 bool nsTextPaintStyle::GetSelectionColors(nscolor
* aForeColor
,
3869 nscolor
* aBackColor
) {
3870 NS_ASSERTION(aForeColor
, "aForeColor is null");
3871 NS_ASSERTION(aBackColor
, "aBackColor is null");
3873 if (!InitSelectionColorsAndShadow()) return false;
3875 *aForeColor
= mSelectionTextColor
;
3876 *aBackColor
= mSelectionBGColor
;
3880 void nsTextPaintStyle::GetHighlightColors(nscolor
* aForeColor
,
3881 nscolor
* aBackColor
) {
3882 NS_ASSERTION(aForeColor
, "aForeColor is null");
3883 NS_ASSERTION(aBackColor
, "aBackColor is null");
3885 const nsFrameSelection
* frameSelection
= mFrame
->GetConstFrameSelection();
3886 const Selection
* selection
=
3887 frameSelection
->GetSelection(SelectionType::eFind
);
3888 const SelectionCustomColors
* customColors
= nullptr;
3890 customColors
= selection
->GetCustomColors();
3893 if (!customColors
) {
3895 LookAndFeel::GetColor(LookAndFeel::ColorID::TextHighlightBackground
);
3897 LookAndFeel::GetColor(LookAndFeel::ColorID::TextHighlightForeground
);
3898 EnsureSufficientContrast(&foreColor
, &backColor
);
3899 *aForeColor
= foreColor
;
3900 *aBackColor
= backColor
;
3905 if (customColors
->mForegroundColor
&& customColors
->mBackgroundColor
) {
3906 nscolor foreColor
= *customColors
->mForegroundColor
;
3907 nscolor backColor
= *customColors
->mBackgroundColor
;
3909 if (EnsureSufficientContrast(&foreColor
, &backColor
) &&
3910 customColors
->mAltForegroundColor
&&
3911 customColors
->mAltBackgroundColor
) {
3912 foreColor
= *customColors
->mAltForegroundColor
;
3913 backColor
= *customColors
->mAltBackgroundColor
;
3916 *aForeColor
= foreColor
;
3917 *aBackColor
= backColor
;
3923 if (customColors
->mBackgroundColor
) {
3924 // !mForegroundColor means "currentColor"; the current color of the text.
3925 nscolor foreColor
= GetTextColor();
3926 nscolor backColor
= *customColors
->mBackgroundColor
;
3928 int32_t luminosityDifference
=
3929 NS_LUMINOSITY_DIFFERENCE(foreColor
, backColor
);
3931 if (mSufficientContrast
> luminosityDifference
&&
3932 customColors
->mAltBackgroundColor
) {
3933 int32_t altLuminosityDifference
= NS_LUMINOSITY_DIFFERENCE(
3934 foreColor
, *customColors
->mAltBackgroundColor
);
3936 if (luminosityDifference
< altLuminosityDifference
) {
3937 backColor
= *customColors
->mAltBackgroundColor
;
3941 *aForeColor
= foreColor
;
3942 *aBackColor
= backColor
;
3946 if (customColors
->mForegroundColor
) {
3947 nscolor foreColor
= *customColors
->mForegroundColor
;
3948 // !mBackgroundColor means "transparent"; the current color of the
3951 int32_t luminosityDifference
=
3952 NS_LUMINOSITY_DIFFERENCE(foreColor
, mFrameBackgroundColor
);
3954 if (mSufficientContrast
> luminosityDifference
&&
3955 customColors
->mAltForegroundColor
) {
3956 int32_t altLuminosityDifference
= NS_LUMINOSITY_DIFFERENCE(
3957 *customColors
->mForegroundColor
, mFrameBackgroundColor
);
3959 if (luminosityDifference
< altLuminosityDifference
) {
3960 foreColor
= *customColors
->mAltForegroundColor
;
3964 *aForeColor
= foreColor
;
3965 *aBackColor
= NS_TRANSPARENT
;
3969 // There are neither mForegroundColor nor mBackgroundColor.
3970 *aForeColor
= GetTextColor();
3971 *aBackColor
= NS_TRANSPARENT
;
3974 void nsTextPaintStyle::GetURLSecondaryColor(nscolor
* aForeColor
) {
3975 NS_ASSERTION(aForeColor
, "aForeColor is null");
3977 nscolor textColor
= GetTextColor();
3978 textColor
= NS_RGBA(NS_GET_R(textColor
), NS_GET_G(textColor
),
3979 NS_GET_B(textColor
), (uint8_t)(255 * 0.5f
));
3980 // Don't use true alpha color for readability.
3982 *aForeColor
= NS_ComposeColors(mFrameBackgroundColor
, textColor
);
3985 void nsTextPaintStyle::GetIMESelectionColors(int32_t aIndex
,
3986 nscolor
* aForeColor
,
3987 nscolor
* aBackColor
) {
3988 NS_ASSERTION(aForeColor
, "aForeColor is null");
3989 NS_ASSERTION(aBackColor
, "aBackColor is null");
3990 NS_ASSERTION(aIndex
>= 0 && aIndex
< 5, "Index out of range");
3992 nsSelectionStyle
* selectionStyle
= GetSelectionStyle(aIndex
);
3993 *aForeColor
= selectionStyle
->mTextColor
;
3994 *aBackColor
= selectionStyle
->mBGColor
;
3997 bool nsTextPaintStyle::GetSelectionUnderlineForPaint(int32_t aIndex
,
3998 nscolor
* aLineColor
,
3999 float* aRelativeSize
,
4001 NS_ASSERTION(aLineColor
, "aLineColor is null");
4002 NS_ASSERTION(aRelativeSize
, "aRelativeSize is null");
4003 NS_ASSERTION(aIndex
>= 0 && aIndex
< 5, "Index out of range");
4005 nsSelectionStyle
* selectionStyle
= GetSelectionStyle(aIndex
);
4006 if (selectionStyle
->mUnderlineStyle
== NS_STYLE_TEXT_DECORATION_STYLE_NONE
||
4007 selectionStyle
->mUnderlineColor
== NS_TRANSPARENT
||
4008 selectionStyle
->mUnderlineRelativeSize
<= 0.0f
)
4011 *aLineColor
= selectionStyle
->mUnderlineColor
;
4012 *aRelativeSize
= selectionStyle
->mUnderlineRelativeSize
;
4013 *aStyle
= selectionStyle
->mUnderlineStyle
;
4017 void nsTextPaintStyle::InitCommonColors() {
4018 if (mInitCommonColors
) return;
4020 nsIFrame
* bgFrame
= nsCSSRendering::FindNonTransparentBackgroundFrame(mFrame
);
4021 NS_ASSERTION(bgFrame
, "Cannot find NonTransparentBackgroundFrame.");
4023 bgFrame
->GetVisitedDependentColor(&nsStyleBackground::mBackgroundColor
);
4025 nscolor defaultBgColor
= mPresContext
->DefaultBackgroundColor();
4026 mFrameBackgroundColor
= NS_ComposeColors(defaultBgColor
, bgColor
);
4028 mSystemFieldForegroundColor
=
4029 LookAndFeel::GetColor(LookAndFeel::ColorID::Fieldtext
);
4030 mSystemFieldBackgroundColor
=
4031 LookAndFeel::GetColor(LookAndFeel::ColorID::Field
);
4033 if (bgFrame
->IsThemed()) {
4034 // Assume a native widget has sufficient contrast always
4035 mSufficientContrast
= 0;
4036 mInitCommonColors
= true;
4040 NS_ASSERTION(NS_GET_A(defaultBgColor
) == 255,
4041 "default background color is not opaque");
4043 nscolor defaultWindowBackgroundColor
=
4044 LookAndFeel::GetColor(LookAndFeel::ColorID::WindowBackground
);
4045 nscolor selectionTextColor
=
4046 LookAndFeel::GetColor(LookAndFeel::ColorID::TextSelectForeground
);
4047 nscolor selectionBGColor
=
4048 LookAndFeel::GetColor(LookAndFeel::ColorID::TextSelectBackground
);
4050 mSufficientContrast
= std::min(
4051 std::min(NS_SUFFICIENT_LUMINOSITY_DIFFERENCE
,
4052 NS_LUMINOSITY_DIFFERENCE(selectionTextColor
, selectionBGColor
)),
4053 NS_LUMINOSITY_DIFFERENCE(defaultWindowBackgroundColor
, selectionBGColor
));
4055 mInitCommonColors
= true;
4058 nscolor
nsTextPaintStyle::GetSystemFieldForegroundColor() {
4060 return mSystemFieldForegroundColor
;
4063 nscolor
nsTextPaintStyle::GetSystemFieldBackgroundColor() {
4065 return mSystemFieldBackgroundColor
;
4068 bool nsTextPaintStyle::InitSelectionColorsAndShadow() {
4069 if (mInitSelectionColorsAndShadow
) return true;
4071 int16_t selectionFlags
;
4072 const int16_t selectionStatus
= mFrame
->GetSelectionStatus(&selectionFlags
);
4073 if (!(selectionFlags
& nsISelectionDisplay::DISPLAY_TEXT
) ||
4074 selectionStatus
< nsISelectionController::SELECTION_ON
) {
4075 // Not displaying the normal selection.
4076 // We're not caching this fact, so every call to GetSelectionColors
4077 // will come through here. We could avoid this, but it's not really worth
4082 mInitSelectionColorsAndShadow
= true;
4084 // Use ::selection pseudo class if applicable.
4085 if (RefPtr
<ComputedStyle
> style
=
4086 mFrame
->ComputeSelectionStyle(selectionStatus
)) {
4088 style
->GetVisitedDependentColor(&nsStyleBackground::mBackgroundColor
);
4089 mSelectionTextColor
=
4090 style
->GetVisitedDependentColor(&nsStyleText::mWebkitTextFillColor
);
4091 mSelectionPseudoStyle
= std::move(style
);
4095 nscolor selectionBGColor
=
4096 LookAndFeel::GetColor(LookAndFeel::ColorID::TextSelectBackground
);
4098 switch (selectionStatus
) {
4099 case nsISelectionController::SELECTION_ATTENTION
: {
4100 mSelectionBGColor
= LookAndFeel::GetColor(
4101 LookAndFeel::ColorID::TextSelectBackgroundAttention
);
4103 EnsureDifferentColors(mSelectionBGColor
, selectionBGColor
);
4106 case nsISelectionController::SELECTION_ON
: {
4107 mSelectionBGColor
= selectionBGColor
;
4111 mSelectionBGColor
= LookAndFeel::GetColor(
4112 LookAndFeel::ColorID::TextSelectBackgroundDisabled
);
4114 EnsureDifferentColors(mSelectionBGColor
, selectionBGColor
);
4119 mSelectionTextColor
=
4120 LookAndFeel::GetColor(LookAndFeel::ColorID::TextSelectForeground
);
4122 if (mResolveColors
) {
4123 // On MacOS X, only the background color gets set,
4124 // the text color remains intact.
4125 if (mSelectionTextColor
== NS_DONT_CHANGE_COLOR
) {
4126 nscolor frameColor
=
4127 SVGUtils::IsInSVGTextSubtree(mFrame
)
4128 ? mFrame
->GetVisitedDependentColor(&nsStyleSVG::mFill
)
4129 : mFrame
->GetVisitedDependentColor(
4130 &nsStyleText::mWebkitTextFillColor
);
4131 mSelectionTextColor
=
4132 EnsureDifferentColors(frameColor
, mSelectionBGColor
);
4133 } else if (mSelectionTextColor
== NS_CHANGE_COLOR_IF_SAME_AS_BG
) {
4134 nscolor frameColor
=
4135 SVGUtils::IsInSVGTextSubtree(mFrame
)
4136 ? mFrame
->GetVisitedDependentColor(&nsStyleSVG::mFill
)
4137 : mFrame
->GetVisitedDependentColor(
4138 &nsStyleText::mWebkitTextFillColor
);
4139 if (frameColor
== mSelectionBGColor
) {
4140 mSelectionTextColor
= LookAndFeel::GetColor(
4141 LookAndFeel::ColorID::TextSelectForegroundCustom
);
4144 EnsureSufficientContrast(&mSelectionTextColor
, &mSelectionBGColor
);
4147 if (mSelectionTextColor
== NS_DONT_CHANGE_COLOR
) {
4148 mSelectionTextColor
= NS_SAME_AS_FOREGROUND_COLOR
;
4154 nsTextPaintStyle::nsSelectionStyle
* nsTextPaintStyle::GetSelectionStyle(
4156 InitSelectionStyle(aIndex
);
4157 return &mSelectionStyle
[aIndex
];
4161 LookAndFeel::ColorID mForeground
, mBackground
, mLine
;
4162 LookAndFeel::IntID mLineStyle
;
4163 LookAndFeel::FloatID mLineRelativeSize
;
4165 static StyleIDs SelectionStyleIDs
[] = {
4166 {LookAndFeel::ColorID::IMERawInputForeground
,
4167 LookAndFeel::ColorID::IMERawInputBackground
,
4168 LookAndFeel::ColorID::IMERawInputUnderline
,
4169 LookAndFeel::IntID::IMERawInputUnderlineStyle
,
4170 LookAndFeel::FloatID::IMEUnderlineRelativeSize
},
4171 {LookAndFeel::ColorID::IMESelectedRawTextForeground
,
4172 LookAndFeel::ColorID::IMESelectedRawTextBackground
,
4173 LookAndFeel::ColorID::IMESelectedRawTextUnderline
,
4174 LookAndFeel::IntID::IMESelectedRawTextUnderlineStyle
,
4175 LookAndFeel::FloatID::IMEUnderlineRelativeSize
},
4176 {LookAndFeel::ColorID::IMEConvertedTextForeground
,
4177 LookAndFeel::ColorID::IMEConvertedTextBackground
,
4178 LookAndFeel::ColorID::IMEConvertedTextUnderline
,
4179 LookAndFeel::IntID::IMEConvertedTextUnderlineStyle
,
4180 LookAndFeel::FloatID::IMEUnderlineRelativeSize
},
4181 {LookAndFeel::ColorID::IMESelectedConvertedTextForeground
,
4182 LookAndFeel::ColorID::IMESelectedConvertedTextBackground
,
4183 LookAndFeel::ColorID::IMESelectedConvertedTextUnderline
,
4184 LookAndFeel::IntID::IMESelectedConvertedTextUnderline
,
4185 LookAndFeel::FloatID::IMEUnderlineRelativeSize
},
4186 {LookAndFeel::ColorID::End
, LookAndFeel::ColorID::End
,
4187 LookAndFeel::ColorID::SpellCheckerUnderline
,
4188 LookAndFeel::IntID::SpellCheckerUnderlineStyle
,
4189 LookAndFeel::FloatID::SpellCheckerUnderlineRelativeSize
}};
4191 void nsTextPaintStyle::InitSelectionStyle(int32_t aIndex
) {
4192 NS_ASSERTION(aIndex
>= 0 && aIndex
< 5, "aIndex is invalid");
4193 nsSelectionStyle
* selectionStyle
= &mSelectionStyle
[aIndex
];
4194 if (selectionStyle
->mInit
) return;
4196 StyleIDs
* styleIDs
= &SelectionStyleIDs
[aIndex
];
4198 nscolor foreColor
, backColor
;
4199 if (styleIDs
->mForeground
== LookAndFeel::ColorID::End
) {
4200 foreColor
= NS_SAME_AS_FOREGROUND_COLOR
;
4202 foreColor
= LookAndFeel::GetColor(styleIDs
->mForeground
);
4204 if (styleIDs
->mBackground
== LookAndFeel::ColorID::End
) {
4205 backColor
= NS_TRANSPARENT
;
4207 backColor
= LookAndFeel::GetColor(styleIDs
->mBackground
);
4210 // Convert special color to actual color
4211 NS_ASSERTION(foreColor
!= NS_TRANSPARENT
,
4212 "foreColor cannot be NS_TRANSPARENT");
4213 NS_ASSERTION(backColor
!= NS_SAME_AS_FOREGROUND_COLOR
,
4214 "backColor cannot be NS_SAME_AS_FOREGROUND_COLOR");
4215 NS_ASSERTION(backColor
!= NS_40PERCENT_FOREGROUND_COLOR
,
4216 "backColor cannot be NS_40PERCENT_FOREGROUND_COLOR");
4218 if (mResolveColors
) {
4219 foreColor
= GetResolvedForeColor(foreColor
, GetTextColor(), backColor
);
4221 if (NS_GET_A(backColor
) > 0)
4222 EnsureSufficientContrast(&foreColor
, &backColor
);
4228 GetSelectionUnderline(mPresContext
, aIndex
, &lineColor
, &relativeSize
,
4232 lineColor
= GetResolvedForeColor(lineColor
, foreColor
, backColor
);
4234 selectionStyle
->mTextColor
= foreColor
;
4235 selectionStyle
->mBGColor
= backColor
;
4236 selectionStyle
->mUnderlineColor
= lineColor
;
4237 selectionStyle
->mUnderlineStyle
= lineStyle
;
4238 selectionStyle
->mUnderlineRelativeSize
= relativeSize
;
4239 selectionStyle
->mInit
= true;
4243 bool nsTextPaintStyle::GetSelectionUnderline(nsPresContext
* aPresContext
,
4245 nscolor
* aLineColor
,
4246 float* aRelativeSize
,
4248 NS_ASSERTION(aPresContext
, "aPresContext is null");
4249 NS_ASSERTION(aRelativeSize
, "aRelativeSize is null");
4250 NS_ASSERTION(aStyle
, "aStyle is null");
4251 NS_ASSERTION(aIndex
>= 0 && aIndex
< 5, "Index out of range");
4253 StyleIDs
& styleID
= SelectionStyleIDs
[aIndex
];
4255 nscolor color
= LookAndFeel::GetColor(styleID
.mLine
);
4256 int32_t style
= LookAndFeel::GetInt(styleID
.mLineStyle
);
4257 if (style
> NS_STYLE_TEXT_DECORATION_STYLE_MAX
) {
4258 NS_ERROR("Invalid underline style value is specified");
4259 style
= NS_STYLE_TEXT_DECORATION_STYLE_SOLID
;
4261 float size
= LookAndFeel::GetFloat(styleID
.mLineRelativeSize
);
4263 NS_ASSERTION(size
, "selection underline relative size must be larger than 0");
4266 *aLineColor
= color
;
4268 *aRelativeSize
= size
;
4271 return style
!= NS_STYLE_TEXT_DECORATION_STYLE_NONE
&&
4272 color
!= NS_TRANSPARENT
&& size
> 0.0f
;
4275 bool nsTextPaintStyle::GetSelectionShadow(
4276 Span
<const StyleSimpleShadow
>* aShadows
) {
4277 if (!InitSelectionColorsAndShadow()) {
4281 if (mSelectionPseudoStyle
) {
4282 *aShadows
= mSelectionPseudoStyle
->StyleText()->mTextShadow
.AsSpan();
4289 inline nscolor
Get40PercentColor(nscolor aForeColor
, nscolor aBackColor
) {
4290 nscolor foreColor
= NS_RGBA(NS_GET_R(aForeColor
), NS_GET_G(aForeColor
),
4291 NS_GET_B(aForeColor
), (uint8_t)(255 * 0.4f
));
4292 // Don't use true alpha color for readability.
4293 return NS_ComposeColors(aBackColor
, foreColor
);
4296 nscolor
nsTextPaintStyle::GetResolvedForeColor(nscolor aColor
,
4297 nscolor aDefaultForeColor
,
4298 nscolor aBackColor
) {
4299 if (aColor
== NS_SAME_AS_FOREGROUND_COLOR
) return aDefaultForeColor
;
4301 if (aColor
!= NS_40PERCENT_FOREGROUND_COLOR
) return aColor
;
4303 // Get actual background color
4304 nscolor actualBGColor
= aBackColor
;
4305 if (actualBGColor
== NS_TRANSPARENT
) {
4307 actualBGColor
= mFrameBackgroundColor
;
4309 return Get40PercentColor(aDefaultForeColor
, actualBGColor
);
4312 //-----------------------------------------------------------------------------
4314 #ifdef ACCESSIBILITY
4315 a11y::AccType
nsTextFrame::AccessibleType() {
4318 GetRenderedText(0, UINT32_MAX
, TextOffsetType::OffsetsInContentText
,
4319 TrailingWhitespace::DontTrim
);
4320 if (text
.mString
.IsEmpty()) {
4321 return a11y::eNoType
;
4325 return a11y::eTextLeafType
;
4329 //-----------------------------------------------------------------------------
4330 void nsTextFrame::Init(nsIContent
* aContent
, nsContainerFrame
* aParent
,
4331 nsIFrame
* aPrevInFlow
) {
4332 NS_ASSERTION(!aPrevInFlow
, "Can't be a continuation!");
4333 MOZ_ASSERT(aContent
->IsText(), "Bogus content!");
4335 // Remove any NewlineOffsetProperty or InFlowContentLengthProperty since they
4336 // might be invalid if the content was modified while there was no frame
4337 if (aContent
->HasFlag(NS_HAS_NEWLINE_PROPERTY
)) {
4338 aContent
->RemoveProperty(nsGkAtoms::newline
);
4339 aContent
->UnsetFlags(NS_HAS_NEWLINE_PROPERTY
);
4341 if (aContent
->HasFlag(NS_HAS_FLOWLENGTH_PROPERTY
)) {
4342 aContent
->RemoveProperty(nsGkAtoms::flowlength
);
4343 aContent
->UnsetFlags(NS_HAS_FLOWLENGTH_PROPERTY
);
4346 // Since our content has a frame now, this flag is no longer needed.
4347 aContent
->UnsetFlags(NS_CREATE_FRAME_IF_NON_WHITESPACE
);
4349 // We're not a continuing frame.
4350 // mContentOffset = 0; not necessary since we get zeroed out at init
4351 nsIFrame::Init(aContent
, aParent
, aPrevInFlow
);
4354 void nsTextFrame::ClearFrameOffsetCache() {
4355 // See if we need to remove ourselves from the offset cache
4356 if (HasAnyStateBits(TEXT_IN_OFFSET_CACHE
)) {
4357 nsIFrame
* primaryFrame
= mContent
->GetPrimaryFrame();
4359 // The primary frame might be null here. For example,
4360 // nsLineBox::DeleteLineList just destroys the frames in order, which
4361 // means that the primary frame is already dead if we're a continuing text
4362 // frame, in which case, all of its properties are gone, and we don't need
4363 // to worry about deleting this property here.
4364 primaryFrame
->RemoveProperty(OffsetToFrameProperty());
4366 RemoveStateBits(TEXT_IN_OFFSET_CACHE
);
4370 void nsTextFrame::DestroyFrom(nsIFrame
* aDestructRoot
,
4371 PostDestroyData
& aPostDestroyData
) {
4372 ClearFrameOffsetCache();
4374 // We might want to clear NS_CREATE_FRAME_IF_NON_WHITESPACE or
4375 // NS_REFRAME_IF_WHITESPACE on mContent here, since our parent frame
4376 // type might be changing. Not clear whether it's worth it.
4378 if (mNextContinuation
) {
4379 mNextContinuation
->SetPrevInFlow(nullptr);
4381 // Let the base class destroy the frame
4382 nsIFrame::DestroyFrom(aDestructRoot
, aPostDestroyData
);
4385 class nsContinuingTextFrame final
: public nsTextFrame
{
4387 NS_DECL_FRAMEARENA_HELPERS(nsContinuingTextFrame
)
4389 friend nsIFrame
* NS_NewContinuingTextFrame(mozilla::PresShell
* aPresShell
,
4390 ComputedStyle
* aStyle
);
4392 void Init(nsIContent
* aContent
, nsContainerFrame
* aParent
,
4393 nsIFrame
* aPrevInFlow
) final
;
4395 void DestroyFrom(nsIFrame
* aDestructRoot
,
4396 PostDestroyData
& aPostDestroyData
) final
;
4398 nsTextFrame
* GetPrevContinuation() const final
{ return mPrevContinuation
; }
4399 void SetPrevContinuation(nsIFrame
* aPrevContinuation
) final
{
4400 NS_ASSERTION(!aPrevContinuation
|| Type() == aPrevContinuation
->Type(),
4401 "setting a prev continuation with incorrect type!");
4403 !nsSplittableFrame::IsInPrevContinuationChain(aPrevContinuation
, this),
4404 "creating a loop in continuation chain!");
4405 mPrevContinuation
= static_cast<nsTextFrame
*>(aPrevContinuation
);
4406 RemoveStateBits(NS_FRAME_IS_FLUID_CONTINUATION
);
4408 nsTextFrame
* GetPrevInFlow() const final
{
4409 return HasAnyStateBits(NS_FRAME_IS_FLUID_CONTINUATION
) ? mPrevContinuation
4412 void SetPrevInFlow(nsIFrame
* aPrevInFlow
) final
{
4413 NS_ASSERTION(!aPrevInFlow
|| Type() == aPrevInFlow
->Type(),
4414 "setting a prev in flow with incorrect type!");
4416 !nsSplittableFrame::IsInPrevContinuationChain(aPrevInFlow
, this),
4417 "creating a loop in continuation chain!");
4418 mPrevContinuation
= static_cast<nsTextFrame
*>(aPrevInFlow
);
4419 AddStateBits(NS_FRAME_IS_FLUID_CONTINUATION
);
4421 nsIFrame
* FirstInFlow() const final
;
4422 nsIFrame
* FirstContinuation() const final
;
4424 void AddInlineMinISize(gfxContext
* aRenderingContext
,
4425 InlineMinISizeData
* aData
) final
;
4426 void AddInlinePrefISize(gfxContext
* aRenderingContext
,
4427 InlinePrefISizeData
* aData
) final
;
4430 explicit nsContinuingTextFrame(ComputedStyle
* aStyle
,
4431 nsPresContext
* aPresContext
)
4432 : nsTextFrame(aStyle
, aPresContext
, kClassID
) {}
4434 nsTextFrame
* mPrevContinuation
;
4437 void nsContinuingTextFrame::Init(nsIContent
* aContent
,
4438 nsContainerFrame
* aParent
,
4439 nsIFrame
* aPrevInFlow
) {
4440 NS_ASSERTION(aPrevInFlow
, "Must be a continuation!");
4442 // Hook the frame into the flow
4443 nsTextFrame
* prev
= static_cast<nsTextFrame
*>(aPrevInFlow
);
4444 nsTextFrame
* nextContinuation
= prev
->GetNextContinuation();
4445 SetPrevInFlow(aPrevInFlow
);
4446 aPrevInFlow
->SetNextInFlow(this);
4448 // NOTE: bypassing nsTextFrame::Init!!!
4449 nsIFrame::Init(aContent
, aParent
, aPrevInFlow
);
4451 mContentOffset
= prev
->GetContentOffset() + prev
->GetContentLengthHint();
4452 NS_ASSERTION(mContentOffset
< int32_t(aContent
->GetText()->GetLength()),
4453 "Creating ContinuingTextFrame, but there is no more content");
4454 if (prev
->Style() != Style()) {
4455 // We're taking part of prev's text, and its style may be different
4456 // so clear its textrun which may no longer be valid (and don't set ours)
4457 prev
->ClearTextRuns();
4459 float inflation
= prev
->GetFontSizeInflation();
4460 SetFontSizeInflation(inflation
);
4461 mTextRun
= prev
->GetTextRun(nsTextFrame::eInflated
);
4462 if (inflation
!= 1.0f
) {
4463 gfxTextRun
* uninflatedTextRun
=
4464 prev
->GetTextRun(nsTextFrame::eNotInflated
);
4465 if (uninflatedTextRun
) {
4466 SetTextRun(uninflatedTextRun
, nsTextFrame::eNotInflated
, 1.0f
);
4470 if (aPrevInFlow
->HasAnyStateBits(NS_FRAME_IS_BIDI
)) {
4471 FrameBidiData bidiData
= aPrevInFlow
->GetBidiData();
4472 bidiData
.precedingControl
= kBidiLevelNone
;
4473 SetProperty(BidiDataProperty(), bidiData
);
4475 if (nextContinuation
) {
4476 SetNextContinuation(nextContinuation
);
4477 nextContinuation
->SetPrevContinuation(this);
4478 // Adjust next-continuations' content offset as needed.
4479 while (nextContinuation
&&
4480 nextContinuation
->GetContentOffset() < mContentOffset
) {
4482 FrameBidiData nextBidiData
= nextContinuation
->GetBidiData();
4483 NS_ASSERTION(bidiData
.embeddingLevel
== nextBidiData
.embeddingLevel
&&
4484 bidiData
.baseLevel
== nextBidiData
.baseLevel
,
4485 "stealing text from different type of BIDI continuation");
4486 MOZ_ASSERT(nextBidiData
.precedingControl
== kBidiLevelNone
,
4487 "There shouldn't be any virtual bidi formatting character "
4488 "between continuations");
4490 nextContinuation
->mContentOffset
= mContentOffset
;
4491 nextContinuation
= nextContinuation
->GetNextContinuation();
4494 AddStateBits(NS_FRAME_IS_BIDI
);
4495 } // prev frame is bidi
4498 void nsContinuingTextFrame::DestroyFrom(nsIFrame
* aDestructRoot
,
4499 PostDestroyData
& aPostDestroyData
) {
4500 ClearFrameOffsetCache();
4502 // The text associated with this frame will become associated with our
4503 // prev-continuation. If that means the text has changed style, then
4504 // we need to wipe out the text run for the text.
4505 // Note that mPrevContinuation can be null if we're destroying the whole
4506 // frame chain from the start to the end.
4507 // If this frame is mentioned in the userData for a textrun (say
4508 // because there's a direction change at the start of this frame), then
4509 // we have to clear the textrun because we're going away and the
4510 // textrun had better not keep a dangling reference to us.
4511 if (IsInTextRunUserData() ||
4512 (mPrevContinuation
&& mPrevContinuation
->Style() != Style())) {
4514 // Clear the previous continuation's text run also, so that it can rebuild
4515 // the text run to include our text.
4516 if (mPrevContinuation
) {
4517 mPrevContinuation
->ClearTextRuns();
4520 nsSplittableFrame::RemoveFromFlow(this);
4521 // Let the base class destroy the frame
4522 nsIFrame::DestroyFrom(aDestructRoot
, aPostDestroyData
);
4525 nsIFrame
* nsContinuingTextFrame::FirstInFlow() const {
4526 // Can't cast to |nsContinuingTextFrame*| because the first one isn't.
4527 nsIFrame
*firstInFlow
,
4528 *previous
= const_cast<nsIFrame
*>(static_cast<const nsIFrame
*>(this));
4530 firstInFlow
= previous
;
4531 previous
= firstInFlow
->GetPrevInFlow();
4533 MOZ_ASSERT(firstInFlow
, "post-condition failed");
4537 nsIFrame
* nsContinuingTextFrame::FirstContinuation() const {
4538 // Can't cast to |nsContinuingTextFrame*| because the first one isn't.
4539 nsIFrame
*firstContinuation
,
4540 *previous
= const_cast<nsIFrame
*>(
4541 static_cast<const nsIFrame
*>(mPrevContinuation
));
4543 NS_ASSERTION(previous
,
4544 "How can an nsContinuingTextFrame be the first continuation?");
4547 firstContinuation
= previous
;
4548 previous
= firstContinuation
->GetPrevContinuation();
4550 MOZ_ASSERT(firstContinuation
, "post-condition failed");
4551 return firstContinuation
;
4554 // XXX Do we want to do all the work for the first-in-flow or do the
4555 // work for each part? (Be careful of first-letter / first-line, though,
4556 // especially first-line!) Doing all the work on the first-in-flow has
4557 // the advantage of avoiding the potential for incremental reflow bugs,
4558 // but depends on our maintining the frame tree in reasonable ways even
4559 // for edge cases (block-within-inline splits, nextBidi, etc.)
4561 // XXX We really need to make :first-letter happen during frame
4564 // Needed for text frames in XUL.
4566 nscoord
nsTextFrame::GetMinISize(gfxContext
* aRenderingContext
) {
4567 return nsLayoutUtils::MinISizeFromInline(this, aRenderingContext
);
4570 // Needed for text frames in XUL.
4572 nscoord
nsTextFrame::GetPrefISize(gfxContext
* aRenderingContext
) {
4573 return nsLayoutUtils::PrefISizeFromInline(this, aRenderingContext
);
4577 void nsContinuingTextFrame::AddInlineMinISize(gfxContext
* aRenderingContext
,
4578 InlineMinISizeData
* aData
) {
4579 // Do nothing, since the first-in-flow accounts for everything.
4583 void nsContinuingTextFrame::AddInlinePrefISize(gfxContext
* aRenderingContext
,
4584 InlinePrefISizeData
* aData
) {
4585 // Do nothing, since the first-in-flow accounts for everything.
4588 //----------------------------------------------------------------------
4590 #if defined(DEBUG_rbs) || defined(DEBUG_bzbarsky)
4591 static void VerifyNotDirty(nsFrameState state
) {
4592 bool isZero
= state
& NS_FRAME_FIRST_REFLOW
;
4593 bool isDirty
= state
& NS_FRAME_IS_DIRTY
;
4594 if (!isZero
&& isDirty
) NS_WARNING("internal offsets may be out-of-sync");
4596 # define DEBUG_VERIFY_NOT_DIRTY(state) VerifyNotDirty(state)
4598 # define DEBUG_VERIFY_NOT_DIRTY(state)
4601 nsIFrame
* NS_NewTextFrame(PresShell
* aPresShell
, ComputedStyle
* aStyle
) {
4602 return new (aPresShell
) nsTextFrame(aStyle
, aPresShell
->GetPresContext());
4605 NS_IMPL_FRAMEARENA_HELPERS(nsTextFrame
)
4607 nsIFrame
* NS_NewContinuingTextFrame(PresShell
* aPresShell
,
4608 ComputedStyle
* aStyle
) {
4609 return new (aPresShell
)
4610 nsContinuingTextFrame(aStyle
, aPresShell
->GetPresContext());
4613 NS_IMPL_FRAMEARENA_HELPERS(nsContinuingTextFrame
)
4615 nsTextFrame::~nsTextFrame() = default;
4617 Maybe
<nsIFrame::Cursor
> nsTextFrame::GetCursor(const nsPoint
& aPoint
) {
4618 StyleCursorKind kind
= StyleUI()->mCursor
.keyword
;
4619 if (kind
== StyleCursorKind::Auto
) {
4620 if (!IsSelectable(nullptr)) {
4621 kind
= StyleCursorKind::Default
;
4623 kind
= GetWritingMode().IsVertical() ? StyleCursorKind::VerticalText
4624 : StyleCursorKind::Text
;
4627 return Some(Cursor
{kind
, AllowCustomCursorImage::Yes
});
4630 nsTextFrame
* nsTextFrame::LastInFlow() const {
4631 nsTextFrame
* lastInFlow
= const_cast<nsTextFrame
*>(this);
4632 while (lastInFlow
->GetNextInFlow()) {
4633 lastInFlow
= lastInFlow
->GetNextInFlow();
4635 MOZ_ASSERT(lastInFlow
, "post-condition failed");
4639 nsTextFrame
* nsTextFrame::LastContinuation() const {
4640 nsTextFrame
* lastContinuation
= const_cast<nsTextFrame
*>(this);
4641 while (lastContinuation
->mNextContinuation
) {
4642 lastContinuation
= lastContinuation
->mNextContinuation
;
4644 MOZ_ASSERT(lastContinuation
, "post-condition failed");
4645 return lastContinuation
;
4648 void nsTextFrame::InvalidateFrame(uint32_t aDisplayItemKey
,
4649 bool aRebuildDisplayItems
) {
4650 if (SVGUtils::IsInSVGTextSubtree(this)) {
4651 nsIFrame
* svgTextFrame
= nsLayoutUtils::GetClosestFrameOfType(
4652 GetParent(), LayoutFrameType::SVGText
);
4653 svgTextFrame
->InvalidateFrame();
4656 nsIFrame::InvalidateFrame(aDisplayItemKey
, aRebuildDisplayItems
);
4659 void nsTextFrame::InvalidateFrameWithRect(const nsRect
& aRect
,
4660 uint32_t aDisplayItemKey
,
4661 bool aRebuildDisplayItems
) {
4662 if (SVGUtils::IsInSVGTextSubtree(this)) {
4663 nsIFrame
* svgTextFrame
= nsLayoutUtils::GetClosestFrameOfType(
4664 GetParent(), LayoutFrameType::SVGText
);
4665 svgTextFrame
->InvalidateFrame();
4668 nsIFrame::InvalidateFrameWithRect(aRect
, aDisplayItemKey
,
4669 aRebuildDisplayItems
);
4672 gfxTextRun
* nsTextFrame::GetUninflatedTextRun() const {
4673 return GetProperty(UninflatedTextRunProperty());
4676 void nsTextFrame::SetTextRun(gfxTextRun
* aTextRun
, TextRunType aWhichTextRun
,
4678 NS_ASSERTION(aTextRun
, "must have text run");
4680 // Our inflated text run is always stored in mTextRun. In the cases
4681 // where our current inflation is not 1.0, however, we store two text
4682 // runs, and the uninflated one goes in a frame property. We never
4683 // store a single text run in both.
4684 if (aWhichTextRun
== eInflated
) {
4685 if (HasFontSizeInflation() && aInflation
== 1.0f
) {
4686 // FIXME: Probably shouldn't do this within each SetTextRun
4687 // method, but it doesn't hurt.
4688 ClearTextRun(nullptr, nsTextFrame::eNotInflated
);
4690 SetFontSizeInflation(aInflation
);
4692 MOZ_ASSERT(aInflation
== 1.0f
, "unexpected inflation");
4693 if (HasFontSizeInflation()) {
4694 // Setting the property will not automatically increment the textrun's
4695 // reference count, so we need to do it here.
4697 SetProperty(UninflatedTextRunProperty(), aTextRun
);
4700 // fall through to setting mTextRun
4703 mTextRun
= aTextRun
;
4705 // FIXME: Add assertions testing the relationship between
4706 // GetFontSizeInflation() and whether we have an uninflated text run
4707 // (but be aware that text runs can go away).
4710 bool nsTextFrame::RemoveTextRun(gfxTextRun
* aTextRun
) {
4711 if (aTextRun
== mTextRun
) {
4713 mFontMetrics
= nullptr;
4716 if (HasAnyStateBits(TEXT_HAS_FONT_INFLATION
) &&
4717 GetProperty(UninflatedTextRunProperty()) == aTextRun
) {
4718 RemoveProperty(UninflatedTextRunProperty());
4724 void nsTextFrame::ClearTextRun(nsTextFrame
* aStartContinuation
,
4725 TextRunType aWhichTextRun
) {
4726 RefPtr
<gfxTextRun
> textRun
= GetTextRun(aWhichTextRun
);
4731 if (aWhichTextRun
== nsTextFrame::eInflated
) {
4732 mFontMetrics
= nullptr;
4735 DebugOnly
<bool> checkmTextrun
= textRun
== mTextRun
;
4736 UnhookTextRunFromFrames(textRun
, aStartContinuation
);
4737 MOZ_ASSERT(checkmTextrun
? !mTextRun
4738 : !GetProperty(UninflatedTextRunProperty()));
4741 void nsTextFrame::DisconnectTextRuns() {
4742 MOZ_ASSERT(!IsInTextRunUserData(),
4743 "Textrun mentions this frame in its user data so we can't just "
4746 if (HasAnyStateBits(TEXT_HAS_FONT_INFLATION
)) {
4747 RemoveProperty(UninflatedTextRunProperty());
4751 void nsTextFrame::NotifyNativeAnonymousTextnodeChange(uint32_t aOldLength
) {
4752 MOZ_ASSERT(mContent
->IsInNativeAnonymousSubtree());
4754 MarkIntrinsicISizesDirty();
4756 // This is to avoid making a new Reflow request in CharacterDataChanged:
4757 for (nsTextFrame
* f
= this; f
; f
= f
->GetNextContinuation()) {
4758 f
->MarkSubtreeDirty();
4759 f
->mReflowRequestedForCharDataChange
= true;
4762 // Pretend that all the text changed.
4763 CharacterDataChangeInfo info
;
4764 info
.mAppend
= false;
4765 info
.mChangeStart
= 0;
4766 info
.mChangeEnd
= aOldLength
;
4767 info
.mReplaceLength
= GetContent()->TextLength();
4768 CharacterDataChanged(info
);
4771 nsresult
nsTextFrame::CharacterDataChanged(
4772 const CharacterDataChangeInfo
& aInfo
) {
4773 if (mContent
->HasFlag(NS_HAS_NEWLINE_PROPERTY
)) {
4774 mContent
->RemoveProperty(nsGkAtoms::newline
);
4775 mContent
->UnsetFlags(NS_HAS_NEWLINE_PROPERTY
);
4777 if (mContent
->HasFlag(NS_HAS_FLOWLENGTH_PROPERTY
)) {
4778 mContent
->RemoveProperty(nsGkAtoms::flowlength
);
4779 mContent
->UnsetFlags(NS_HAS_FLOWLENGTH_PROPERTY
);
4782 // Find the first frame whose text has changed. Frames that are entirely
4783 // before the text change are completely unaffected.
4785 nsTextFrame
* textFrame
= this;
4787 next
= textFrame
->GetNextContinuation();
4788 if (!next
|| next
->GetContentOffset() > int32_t(aInfo
.mChangeStart
)) break;
4792 int32_t endOfChangedText
= aInfo
.mChangeStart
+ aInfo
.mReplaceLength
;
4794 // Parent of the last frame that we passed to FrameNeedsReflow (or noticed
4795 // had already received an earlier FrameNeedsReflow call).
4796 // (For subsequent frames with this same parent, we can just set their
4797 // dirty bit without bothering to call FrameNeedsReflow again.)
4798 nsIFrame
* lastDirtiedFrameParent
= nullptr;
4800 mozilla::PresShell
* presShell
= PresContext()->GetPresShell();
4802 // textFrame contained deleted text (or the insertion point,
4803 // if this was a pure insertion).
4804 textFrame
->RemoveStateBits(TEXT_WHITESPACE_FLAGS
);
4805 textFrame
->ClearTextRuns();
4807 nsIFrame
* parentOfTextFrame
= textFrame
->GetParent();
4808 bool areAncestorsAwareOfReflowRequest
= false;
4809 if (lastDirtiedFrameParent
== parentOfTextFrame
) {
4810 // An earlier iteration of this loop already called
4811 // FrameNeedsReflow for a sibling of |textFrame|.
4812 areAncestorsAwareOfReflowRequest
= true;
4814 lastDirtiedFrameParent
= parentOfTextFrame
;
4817 if (textFrame
->mReflowRequestedForCharDataChange
) {
4818 // We already requested a reflow for this frame; nothing to do.
4819 MOZ_ASSERT(textFrame
->HasAnyStateBits(NS_FRAME_IS_DIRTY
),
4820 "mReflowRequestedForCharDataChange should only be set "
4823 // Make sure textFrame is queued up for a reflow. Also set a flag so we
4824 // don't waste time doing this again in repeated calls to this method.
4825 textFrame
->mReflowRequestedForCharDataChange
= true;
4826 if (!areAncestorsAwareOfReflowRequest
) {
4827 // Ask the parent frame to reflow me.
4828 presShell
->FrameNeedsReflow(textFrame
, IntrinsicDirty::StyleChange
,
4831 // We already called FrameNeedsReflow on behalf of an earlier sibling,
4832 // so we can just mark this frame as dirty and don't need to bother
4833 // telling its ancestors.
4834 // Note: if the parent is a block, we're cheating here because we should
4835 // be marking our line dirty, but we're not. nsTextFrame::SetLength will
4836 // do that when it gets called during reflow.
4837 textFrame
->MarkSubtreeDirty();
4840 textFrame
->InvalidateFrame();
4842 // Below, frames that start after the deleted text will be adjusted so that
4843 // their offsets move with the trailing unchanged text. If this change
4844 // deletes more text than it inserts, those frame offsets will decrease.
4845 // We need to maintain the invariant that mContentOffset is non-decreasing
4846 // along the continuation chain. So we need to ensure that frames that
4847 // started in the deleted text are all still starting before the
4849 if (textFrame
->mContentOffset
> endOfChangedText
) {
4850 textFrame
->mContentOffset
= endOfChangedText
;
4853 textFrame
= textFrame
->GetNextContinuation();
4854 } while (textFrame
&&
4855 textFrame
->GetContentOffset() < int32_t(aInfo
.mChangeEnd
));
4857 // This is how much the length of the string changed by --- i.e.,
4858 // how much the trailing unchanged text moved.
4859 int32_t sizeChange
=
4860 aInfo
.mChangeStart
+ aInfo
.mReplaceLength
- aInfo
.mChangeEnd
;
4863 // Fix the offsets of the text frames that start in the trailing
4866 textFrame
->mContentOffset
+= sizeChange
;
4867 // XXX we could rescue some text runs by adjusting their user data
4868 // to reflect the change in DOM offsets
4869 textFrame
->ClearTextRuns();
4870 textFrame
= textFrame
->GetNextContinuation();
4877 NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(TextCombineScaleFactorProperty
, float)
4879 float nsTextFrame::GetTextCombineScaleFactor(nsTextFrame
* aFrame
) {
4880 float factor
= aFrame
->GetProperty(TextCombineScaleFactorProperty());
4881 return factor
? factor
: 1.0f
;
4884 void nsTextFrame::BuildDisplayList(nsDisplayListBuilder
* aBuilder
,
4885 const nsDisplayListSet
& aLists
) {
4886 if (!IsVisibleForPainting()) return;
4888 DO_GLOBAL_REFLOW_COUNT_DSP("nsTextFrame");
4890 const nsStyleText
* st
= StyleText();
4891 bool isTextTransparent
=
4892 NS_GET_A(st
->mWebkitTextFillColor
.CalcColor(this)) == 0 &&
4893 NS_GET_A(st
->mWebkitTextStrokeColor
.CalcColor(this)) == 0;
4894 Maybe
<bool> isSelected
;
4895 if ((HasAnyStateBits(TEXT_NO_RENDERED_GLYPHS
) ||
4896 (isTextTransparent
&& !StyleText()->HasTextShadow())) &&
4897 aBuilder
->IsForPainting() && !SVGUtils::IsInSVGTextSubtree(this)) {
4898 isSelected
.emplace(IsSelected());
4899 if (!isSelected
.value()) {
4900 TextDecorations textDecs
;
4901 GetTextDecorations(PresContext(), eResolvedColors
, textDecs
);
4902 if (!textDecs
.HasDecorationLines()) {
4903 if (auto* currentPresContext
= aBuilder
->CurrentPresContext()) {
4904 currentPresContext
->SetBuiltInvisibleText();
4911 aLists
.Content()->AppendNewToTop
<nsDisplayText
>(aBuilder
, this, isSelected
);
4914 UniquePtr
<SelectionDetails
> nsTextFrame::GetSelectionDetails() {
4915 const nsFrameSelection
* frameSelection
= GetConstFrameSelection();
4916 if (frameSelection
->IsInTableSelectionMode()) {
4919 UniquePtr
<SelectionDetails
> details
= frameSelection
->LookUpSelection(
4920 mContent
, GetContentOffset(), GetContentLength(), false);
4921 for (SelectionDetails
* sd
= details
.get(); sd
; sd
= sd
->mNext
.get()) {
4922 sd
->mStart
+= mContentOffset
;
4923 sd
->mEnd
+= mContentOffset
;
4928 static void PaintSelectionBackground(
4929 DrawTarget
& aDrawTarget
, nscolor aColor
, const LayoutDeviceRect
& aDirtyRect
,
4930 const LayoutDeviceRect
& aRect
, nsTextFrame::DrawPathCallbacks
* aCallbacks
) {
4931 Rect rect
= aRect
.Intersect(aDirtyRect
).ToUnknownRect();
4932 MaybeSnapToDevicePixels(rect
, aDrawTarget
);
4935 aCallbacks
->NotifySelectionBackgroundNeedsFill(rect
, aColor
, aDrawTarget
);
4937 ColorPattern
color(ToDeviceColor(aColor
));
4938 aDrawTarget
.FillRect(rect
, color
);
4942 // Attempt to get the LineBaselineOffset property of aChildFrame
4943 // If not set, calculate this value for all child frames of aBlockFrame
4944 static nscoord
LazyGetLineBaselineOffset(nsIFrame
* aChildFrame
,
4945 nsBlockFrame
* aBlockFrame
) {
4948 aChildFrame
->GetProperty(nsIFrame::LineBaselineOffset(), &offsetFound
);
4951 for (const auto& line
: aBlockFrame
->Lines()) {
4952 if (line
.IsInline()) {
4953 int32_t n
= line
.GetChildCount();
4954 nscoord lineBaseline
= line
.BStart() + line
.GetLogicalAscent();
4955 for (auto* lineFrame
= line
.mFirstChild
; n
> 0;
4956 lineFrame
= lineFrame
->GetNextSibling(), --n
) {
4957 offset
= lineBaseline
- lineFrame
->GetNormalPosition().y
;
4958 lineFrame
->SetProperty(nsIFrame::LineBaselineOffset(), offset
);
4962 return aChildFrame
->GetProperty(nsIFrame::LineBaselineOffset(),
4969 static bool IsUnderlineRight(const ComputedStyle
& aStyle
) {
4970 // Check for 'left' or 'right' explicitly specified in the property;
4971 // if neither is there, we use auto positioning based on lang.
4972 const auto position
= aStyle
.StyleText()->mTextUnderlinePosition
;
4973 if (position
.IsLeft()) {
4976 if (position
.IsRight()) {
4979 // If neither 'left' nor 'right' was specified, check the language.
4980 nsAtom
* langAtom
= aStyle
.StyleFont()->mLanguage
;
4984 nsDependentAtomString
langStr(langAtom
);
4985 return (StringBeginsWith(langStr
, u
"ja"_ns
) ||
4986 StringBeginsWith(langStr
, u
"ko"_ns
)) &&
4987 (langStr
.Length() == 2 || langStr
[2] == '-');
4990 void nsTextFrame::GetTextDecorations(
4991 nsPresContext
* aPresContext
,
4992 nsTextFrame::TextDecorationColorResolution aColorResolution
,
4993 nsTextFrame::TextDecorations
& aDecorations
) {
4994 const nsCompatibility compatMode
= aPresContext
->CompatibilityMode();
4996 bool useOverride
= false;
4997 nscolor overrideColor
= NS_RGBA(0, 0, 0, 0);
4999 bool nearestBlockFound
= false;
5000 // Use writing mode of parent frame for orthogonal text frame to work.
5001 // See comment in nsTextFrame::DrawTextRunAndDecorations.
5002 WritingMode wm
= GetParent()->GetWritingMode();
5003 bool vertical
= wm
.IsVertical();
5005 nscoord ascent
= GetLogicalBaseline(wm
);
5006 // physicalBlockStartOffset represents the offset from our baseline
5007 // to f's physical block start, which is top in horizontal writing
5008 // mode, and left in vertical writing modes, in our coordinate space.
5009 // This physical block start is logical block start in most cases,
5010 // but for vertical-rl, it is logical block end, and consequently in
5011 // that case, it starts from the descent instead of ascent.
5012 nscoord physicalBlockStartOffset
=
5013 wm
.IsVerticalRL() ? GetSize().width
- ascent
: ascent
;
5014 // baselineOffset represents the offset from our baseline to f's baseline or
5015 // the nearest block's baseline, in our coordinate space, whichever is closest
5016 // during the particular iteration
5017 nscoord baselineOffset
= 0;
5019 for (nsIFrame
*f
= this, *fChild
= nullptr; f
;
5020 fChild
= f
, f
= nsLayoutUtils::GetParentOrPlaceholderFor(f
)) {
5021 ComputedStyle
* const context
= f
->Style();
5022 if (!context
->HasTextDecorationLines()) {
5026 const nsStyleTextReset
* const styleTextReset
= context
->StyleTextReset();
5027 const StyleTextDecorationLine textDecorations
=
5028 styleTextReset
->mTextDecorationLine
;
5031 (StyleTextDecorationLine::COLOR_OVERRIDE
& textDecorations
)) {
5032 // This handles the <a href="blah.html"><font color="green">La
5033 // la la</font></a> case. The link underline should be green.
5036 nsLayoutUtils::GetColor(f
, &nsStyleTextReset::mTextDecorationColor
);
5039 nsBlockFrame
* fBlock
= do_QueryFrame(f
);
5040 const bool firstBlock
= !nearestBlockFound
&& fBlock
;
5042 // Not updating positions once we hit a parent block is equivalent to
5043 // the CSS 2.1 spec that blocks should propagate decorations down to their
5044 // children (albeit the style should be preserved)
5045 // However, if we're vertically aligned within a block, then we need to
5046 // recover the correct baseline from the line by querying the FrameProperty
5047 // that should be set (see nsLineLayout::VerticalAlignLine).
5049 // At this point, fChild can't be null since TextFrames can't be blocks
5050 Maybe
<StyleVerticalAlignKeyword
> verticalAlign
=
5051 fChild
->VerticalAlignEnum();
5052 if (verticalAlign
!= Some(StyleVerticalAlignKeyword::Baseline
)) {
5053 // Since offset is the offset in the child's coordinate space, we have
5054 // to undo the accumulation to bring the transform out of the block's
5056 const nscoord lineBaselineOffset
=
5057 LazyGetLineBaselineOffset(fChild
, fBlock
);
5059 baselineOffset
= physicalBlockStartOffset
- lineBaselineOffset
-
5060 (vertical
? fChild
->GetNormalPosition().x
5061 : fChild
->GetNormalPosition().y
);
5063 } else if (!nearestBlockFound
) {
5064 // offset here is the offset from f's baseline to f's top/left
5065 // boundary. It's descent for vertical-rl, and ascent otherwise.
5066 nscoord offset
= wm
.IsVerticalRL()
5067 ? f
->GetSize().width
- f
->GetLogicalBaseline(wm
)
5068 : f
->GetLogicalBaseline(wm
);
5069 baselineOffset
= physicalBlockStartOffset
- offset
;
5072 nearestBlockFound
= nearestBlockFound
|| firstBlock
;
5073 physicalBlockStartOffset
+=
5074 vertical
? f
->GetNormalPosition().x
: f
->GetNormalPosition().y
;
5076 const uint8_t style
= styleTextReset
->mTextDecorationStyle
;
5077 if (textDecorations
) {
5080 color
= overrideColor
;
5081 } else if (SVGUtils::IsInSVGTextSubtree(this)) {
5082 // XXX We might want to do something with text-decoration-color when
5083 // painting SVG text, but it's not clear what we should do. We
5084 // at least need SVG text decorations to paint with 'fill' if
5085 // text-decoration-color has its initial value currentColor.
5086 // We could choose to interpret currentColor as "currentFill"
5087 // for SVG text, and have e.g. text-decoration-color:red to
5088 // override the fill paint of the decoration.
5089 color
= aColorResolution
== eResolvedColors
5090 ? nsLayoutUtils::GetColor(f
, &nsStyleSVG::mFill
)
5091 : NS_SAME_AS_FOREGROUND_COLOR
;
5094 nsLayoutUtils::GetColor(f
, &nsStyleTextReset::mTextDecorationColor
);
5097 bool swapUnderlineAndOverline
=
5098 wm
.IsCentralBaseline() && IsUnderlineRight(*context
);
5099 const auto kUnderline
= swapUnderlineAndOverline
5100 ? StyleTextDecorationLine::OVERLINE
5101 : StyleTextDecorationLine::UNDERLINE
;
5102 const auto kOverline
= swapUnderlineAndOverline
5103 ? StyleTextDecorationLine::UNDERLINE
5104 : StyleTextDecorationLine::OVERLINE
;
5106 const nsStyleText
* const styleText
= context
->StyleText();
5107 if (textDecorations
& kUnderline
) {
5108 aDecorations
.mUnderlines
.AppendElement(nsTextFrame::LineDecoration(
5109 f
, baselineOffset
, styleText
->mTextUnderlinePosition
,
5110 styleText
->mTextUnderlineOffset
,
5111 styleTextReset
->mTextDecorationThickness
, color
, style
));
5113 if (textDecorations
& kOverline
) {
5114 aDecorations
.mOverlines
.AppendElement(nsTextFrame::LineDecoration(
5115 f
, baselineOffset
, styleText
->mTextUnderlinePosition
,
5116 styleText
->mTextUnderlineOffset
,
5117 styleTextReset
->mTextDecorationThickness
, color
, style
));
5119 if (textDecorations
& StyleTextDecorationLine::LINE_THROUGH
) {
5120 aDecorations
.mStrikes
.AppendElement(nsTextFrame::LineDecoration(
5121 f
, baselineOffset
, styleText
->mTextUnderlinePosition
,
5122 styleText
->mTextUnderlineOffset
,
5123 styleTextReset
->mTextDecorationThickness
, color
, style
));
5127 // In all modes, if we're on an inline-block/table/grid/flex (or
5128 // -moz-inline-box), we're done.
5129 // If we're on a ruby frame other than ruby text container, we
5131 mozilla::StyleDisplay display
= f
->GetDisplay();
5132 if (!nsStyleDisplay::IsInlineFlow(display
) &&
5133 (!nsStyleDisplay::IsRubyDisplayType(display
) ||
5134 display
== mozilla::StyleDisplay::RubyTextContainer
) &&
5135 nsStyleDisplay::IsDisplayTypeInlineOutside(display
)) {
5139 // In quirks mode, if we're on an HTML table element, we're done.
5140 if (compatMode
== eCompatibility_NavQuirks
&&
5141 f
->GetContent()->IsHTMLElement(nsGkAtoms::table
)) {
5145 // If we're on an absolutely-positioned element or a floating
5146 // element, we're done.
5147 if (f
->IsFloating() || f
->IsAbsolutelyPositioned()) {
5151 // If we're an outer <svg> element, which is classified as an atomic
5152 // inline-level element, we're done.
5153 if (f
->IsSVGOuterSVGFrame()) {
5159 static float GetInflationForTextDecorations(nsIFrame
* aFrame
,
5160 nscoord aInflationMinFontSize
) {
5161 if (SVGUtils::IsInSVGTextSubtree(aFrame
)) {
5163 nsLayoutUtils::GetClosestFrameOfType(aFrame
, LayoutFrameType::SVGText
);
5164 MOZ_ASSERT(container
);
5165 return static_cast<SVGTextFrame
*>(container
)->GetFontSizeScaleFactor();
5167 return nsLayoutUtils::FontSizeInflationInner(aFrame
, aInflationMinFontSize
);
5170 struct EmphasisMarkInfo
{
5171 RefPtr
<gfxTextRun
> textRun
;
5173 gfxFloat baselineOffset
;
5176 NS_DECLARE_FRAME_PROPERTY_DELETABLE(EmphasisMarkProperty
, EmphasisMarkInfo
)
5178 static void ComputeTextEmphasisStyleString(const StyleTextEmphasisStyle
& aStyle
,
5180 MOZ_ASSERT(!aStyle
.IsNone());
5181 if (aStyle
.IsString()) {
5182 nsDependentCSubstring string
= aStyle
.AsString().AsString();
5183 AppendUTF8toUTF16(string
, aOut
);
5186 const auto& keyword
= aStyle
.AsKeyword();
5187 const bool fill
= keyword
.fill
== StyleTextEmphasisFillMode::Filled
;
5188 switch (keyword
.shape
) {
5189 case StyleTextEmphasisShapeKeyword::Dot
:
5190 return aOut
.AppendLiteral(fill
? u
"\u2022" : u
"\u25e6");
5191 case StyleTextEmphasisShapeKeyword::Circle
:
5192 return aOut
.AppendLiteral(fill
? u
"\u25cf" : u
"\u25cb");
5193 case StyleTextEmphasisShapeKeyword::DoubleCircle
:
5194 return aOut
.AppendLiteral(fill
? u
"\u25c9" : u
"\u25ce");
5195 case StyleTextEmphasisShapeKeyword::Triangle
:
5196 return aOut
.AppendLiteral(fill
? u
"\u25b2" : u
"\u25b3");
5197 case StyleTextEmphasisShapeKeyword::Sesame
:
5198 return aOut
.AppendLiteral(fill
? u
"\ufe45" : u
"\ufe46");
5200 MOZ_ASSERT_UNREACHABLE("Unknown emphasis style shape");
5204 static already_AddRefed
<gfxTextRun
> GenerateTextRunForEmphasisMarks(
5205 nsTextFrame
* aFrame
, gfxFontGroup
* aFontGroup
,
5206 ComputedStyle
* aComputedStyle
, const nsStyleText
* aStyleText
) {
5207 nsAutoString string
;
5208 ComputeTextEmphasisStyleString(aStyleText
->mTextEmphasisStyle
, string
);
5210 RefPtr
<DrawTarget
> dt
= CreateReferenceDrawTarget(aFrame
);
5211 auto appUnitsPerDevUnit
= aFrame
->PresContext()->AppUnitsPerDevPixel();
5212 gfx::ShapedTextFlags flags
=
5213 nsLayoutUtils::GetTextRunOrientFlagsForStyle(aComputedStyle
);
5214 if (flags
== gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED
) {
5215 // The emphasis marks should always be rendered upright per spec.
5216 flags
= gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT
;
5218 return aFontGroup
->MakeTextRun
<char16_t
>(string
.get(), string
.Length(), dt
,
5219 appUnitsPerDevUnit
, flags
,
5220 nsTextFrameUtils::Flags(), nullptr);
5223 static nsRubyFrame
* FindFurthestInlineRubyAncestor(nsTextFrame
* aFrame
) {
5224 nsRubyFrame
* rubyFrame
= nullptr;
5225 for (nsIFrame
* frame
= aFrame
->GetParent();
5226 frame
&& frame
->IsFrameOfType(nsIFrame::eLineParticipant
);
5227 frame
= frame
->GetParent()) {
5228 if (frame
->IsRubyFrame()) {
5229 rubyFrame
= static_cast<nsRubyFrame
*>(frame
);
5235 nsRect
nsTextFrame::UpdateTextEmphasis(WritingMode aWM
,
5236 PropertyProvider
& aProvider
) {
5237 const nsStyleText
* styleText
= StyleText();
5238 if (!styleText
->HasEffectiveTextEmphasis()) {
5239 RemoveProperty(EmphasisMarkProperty());
5243 ComputedStyle
* computedStyle
= Style();
5244 bool isTextCombined
= computedStyle
->IsTextCombined();
5245 if (isTextCombined
) {
5246 computedStyle
= GetParent()->Style();
5248 RefPtr
<nsFontMetrics
> fm
= nsLayoutUtils::GetFontMetricsOfEmphasisMarks(
5249 computedStyle
, PresContext(), GetFontSizeInflation());
5250 EmphasisMarkInfo
* info
= new EmphasisMarkInfo
;
5251 info
->textRun
= GenerateTextRunForEmphasisMarks(
5252 this, fm
->GetThebesFontGroup(), computedStyle
, styleText
);
5253 info
->advance
= info
->textRun
->GetAdvanceWidth();
5255 // Calculate the baseline offset
5256 LogicalSide side
= styleText
->TextEmphasisSide(aWM
);
5257 LogicalSize frameSize
= GetLogicalSize(aWM
);
5258 // The overflow rect is inflated in the inline direction by half
5259 // advance of the emphasis mark on each side, so that even if a mark
5260 // is drawn for a zero-width character, it won't be clipped.
5261 LogicalRect
overflowRect(aWM
, -info
->advance
/ 2,
5262 /* BStart to be computed below */ 0,
5263 frameSize
.ISize(aWM
) + info
->advance
,
5264 fm
->MaxAscent() + fm
->MaxDescent());
5265 RefPtr
<nsFontMetrics
> baseFontMetrics
=
5267 ? nsLayoutUtils::GetInflatedFontMetricsForFrame(GetParent())
5268 : do_AddRef(aProvider
.GetFontMetrics());
5269 // When the writing mode is vertical-lr the line is inverted, and thus
5270 // the ascent and descent are swapped.
5271 nscoord absOffset
= (side
== eLogicalSideBStart
) != aWM
.IsLineInverted()
5272 ? baseFontMetrics
->MaxAscent() + fm
->MaxDescent()
5273 : baseFontMetrics
->MaxDescent() + fm
->MaxAscent();
5274 RubyBlockLeadings leadings
;
5275 if (nsRubyFrame
* ruby
= FindFurthestInlineRubyAncestor(this)) {
5276 leadings
= ruby
->GetBlockLeadings();
5278 if (side
== eLogicalSideBStart
) {
5279 info
->baselineOffset
= -absOffset
- leadings
.mStart
;
5280 overflowRect
.BStart(aWM
) = -overflowRect
.BSize(aWM
) - leadings
.mStart
;
5282 MOZ_ASSERT(side
== eLogicalSideBEnd
);
5283 info
->baselineOffset
= absOffset
+ leadings
.mEnd
;
5284 overflowRect
.BStart(aWM
) = frameSize
.BSize(aWM
) + leadings
.mEnd
;
5286 // If text combined, fix the gap between the text frame and its parent.
5287 if (isTextCombined
) {
5288 nscoord gap
= (baseFontMetrics
->MaxHeight() - frameSize
.BSize(aWM
)) / 2;
5289 overflowRect
.BStart(aWM
) += gap
* (side
== eLogicalSideBStart
? -1 : 1);
5292 SetProperty(EmphasisMarkProperty(), info
);
5293 return overflowRect
.GetPhysicalRect(aWM
, frameSize
.GetPhysicalSize(aWM
));
5296 // helper function for implementing text-decoration-thickness
5297 // https://drafts.csswg.org/css-text-decor-4/#text-decoration-width-property
5298 // Returns the thickness in device pixels.
5299 static gfxFloat
ComputeDecorationLineThickness(
5300 const StyleTextDecorationLength
& aThickness
, const gfxFloat aAutoValue
,
5301 const gfxFont::Metrics
& aFontMetrics
, const gfxFloat aAppUnitsPerDevPixel
) {
5302 if (aThickness
.IsAuto()) {
5306 if (aThickness
.IsFromFont()) {
5307 return aFontMetrics
.underlineSize
;
5310 return nscoord(NS_round(aFontMetrics
.emHeight
* aAppUnitsPerDevPixel
));
5312 return aThickness
.AsLengthPercentage().Resolve(em
) / aAppUnitsPerDevPixel
;
5315 // Helper function for implementing text-underline-offset and -position
5316 // https://drafts.csswg.org/css-text-decor-4/#underline-offset
5317 // Returns the offset in device pixels.
5318 static gfxFloat
ComputeDecorationLineOffset(
5319 StyleTextDecorationLine aLineType
,
5320 const StyleTextUnderlinePosition
& aPosition
,
5321 const LengthPercentageOrAuto
& aOffset
, const gfxFont::Metrics
& aFontMetrics
,
5322 const gfxFloat aAppUnitsPerDevPixel
, bool aIsCentralBaseline
,
5323 bool aSwappedUnderline
) {
5324 // Em value to use if we need to resolve a percentage length.
5326 return nscoord(NS_round(aFontMetrics
.emHeight
* aAppUnitsPerDevPixel
));
5329 // If we're in vertical-upright typographic mode, we need to compute the
5330 // offset of the decoration line from the default central baseline.
5331 if (aIsCentralBaseline
) {
5332 // Line-through simply goes at the (central) baseline.
5333 if (aLineType
== StyleTextDecorationLine::LINE_THROUGH
) {
5337 // Compute "zero position" for the under- or overline.
5338 gfxFloat zeroPos
= 0.5 * aFontMetrics
.emHeight
;
5340 // aOffset applies to underline only; for overline (or offset:auto) we use
5341 // a somewhat arbitrary offset of half the font's (horziontal-mode) value
5342 // for underline-offset, to get a little bit of separation between glyph
5343 // edges and the line in typical cases.
5344 // If we have swapped under-/overlines for text-underline-position:right,
5345 // we need to take account of this to determine which decoration lines are
5346 // "real" underlines which should respect the text-underline-* values.
5348 (aLineType
== StyleTextDecorationLine::UNDERLINE
) != aSwappedUnderline
;
5350 isUnderline
&& !aOffset
.IsAuto()
5351 ? aOffset
.AsLengthPercentage().Resolve(em
) / aAppUnitsPerDevPixel
5352 : aFontMetrics
.underlineOffset
* -0.5;
5354 // Direction of the decoration line's offset from the central baseline.
5355 gfxFloat dir
= aLineType
== StyleTextDecorationLine::OVERLINE
? 1.0 : -1.0;
5356 return dir
* (zeroPos
+ offset
);
5359 // Compute line offset for horizontal typographic mode.
5360 if (aLineType
== StyleTextDecorationLine::UNDERLINE
) {
5361 if (aPosition
.IsFromFont()) {
5362 gfxFloat zeroPos
= aFontMetrics
.underlineOffset
;
5366 : aOffset
.AsLengthPercentage().Resolve(em
) / aAppUnitsPerDevPixel
;
5367 return zeroPos
- offset
;
5370 if (aPosition
.IsUnder()) {
5371 gfxFloat zeroPos
= -aFontMetrics
.maxDescent
;
5374 ? -0.5 * aFontMetrics
.underlineOffset
5375 : aOffset
.AsLengthPercentage().Resolve(em
) / aAppUnitsPerDevPixel
;
5376 return zeroPos
- offset
;
5379 // text-underline-position must be 'auto', so zero position is the
5380 // baseline and 'auto' offset will apply the font's underline-offset.
5382 // If offset is `auto`, we clamp the offset (in horizontal typographic mode)
5383 // to a minimum of 1/16 em (equivalent to 1px at font-size 16px) to mitigate
5384 // skip-ink issues with fonts that leave the underlineOffset field as zero.
5385 MOZ_ASSERT(aPosition
.IsAuto());
5386 return aOffset
.IsAuto() ? std::min(aFontMetrics
.underlineOffset
,
5387 -aFontMetrics
.emHeight
/ 16.0)
5388 : -aOffset
.AsLengthPercentage().Resolve(em
) /
5389 aAppUnitsPerDevPixel
;
5392 if (aLineType
== StyleTextDecorationLine::OVERLINE
) {
5393 return aFontMetrics
.maxAscent
;
5396 if (aLineType
== StyleTextDecorationLine::LINE_THROUGH
) {
5397 return aFontMetrics
.strikeoutOffset
;
5400 MOZ_ASSERT_UNREACHABLE("unknown decoration line type");
5404 void nsTextFrame::UnionAdditionalOverflow(nsPresContext
* aPresContext
,
5406 PropertyProvider
& aProvider
,
5407 nsRect
* aInkOverflowRect
,
5408 bool aIncludeTextDecorations
,
5409 bool aIncludeShadows
) {
5410 const WritingMode wm
= GetWritingMode();
5411 bool verticalRun
= mTextRun
->IsVertical();
5412 const gfxFloat appUnitsPerDevUnit
= aPresContext
->AppUnitsPerDevPixel();
5414 if (IsFloatingFirstLetterChild()) {
5415 bool inverted
= wm
.IsLineInverted();
5416 // The underline/overline drawable area must be contained in the overflow
5417 // rect when this is in floating first letter frame at *both* modes.
5418 // In this case, aBlock is the ::first-letter frame.
5419 uint8_t decorationStyle
=
5420 aBlock
->Style()->StyleTextReset()->mTextDecorationStyle
;
5421 // If the style is none, let's include decoration line rect as solid style
5422 // since changing the style from none to solid/dotted/dashed doesn't cause
5424 if (decorationStyle
== NS_STYLE_TEXT_DECORATION_STYLE_NONE
) {
5425 decorationStyle
= NS_STYLE_TEXT_DECORATION_STYLE_SOLID
;
5427 nsCSSRendering::DecorationRectParams params
;
5429 bool useVerticalMetrics
= verticalRun
&& mTextRun
->UseCenterBaseline();
5430 nsFontMetrics
* fontMetrics
= aProvider
.GetFontMetrics();
5431 const gfxFont::Metrics
& metrics
=
5432 fontMetrics
->GetThebesFontGroup()->GetFirstValidFont()->GetMetrics(
5433 useVerticalMetrics
? nsFontMetrics::eVertical
5434 : nsFontMetrics::eHorizontal
);
5436 params
.defaultLineThickness
= metrics
.underlineSize
;
5437 params
.lineSize
.height
= ComputeDecorationLineThickness(
5438 aBlock
->Style()->StyleTextReset()->mTextDecorationThickness
,
5439 params
.defaultLineThickness
, metrics
, appUnitsPerDevUnit
);
5441 const auto* styleText
= aBlock
->StyleText();
5442 bool swapUnderline
=
5443 wm
.IsCentralBaseline() && IsUnderlineRight(*aBlock
->Style());
5444 params
.offset
= ComputeDecorationLineOffset(
5445 StyleTextDecorationLine::UNDERLINE
, styleText
->mTextUnderlinePosition
,
5446 styleText
->mTextUnderlineOffset
, metrics
, appUnitsPerDevUnit
,
5447 wm
.IsCentralBaseline(), swapUnderline
);
5450 inverted
? fontMetrics
->MaxDescent() : fontMetrics
->MaxAscent();
5453 (verticalRun
? aInkOverflowRect
->height
: aInkOverflowRect
->width
) /
5455 params
.lineSize
.width
= gfxWidth
;
5456 params
.ascent
= gfxFloat(mAscent
) / appUnitsPerDevUnit
;
5457 params
.style
= decorationStyle
;
5458 params
.vertical
= verticalRun
;
5459 params
.sidewaysLeft
= mTextRun
->IsSidewaysLeft();
5460 params
.decoration
= StyleTextDecorationLine::UNDERLINE
;
5461 nsRect underlineRect
=
5462 nsCSSRendering::GetTextDecorationRect(aPresContext
, params
);
5465 // Should we actually be calling ComputeDecorationLineOffset again here?
5466 params
.offset
= maxAscent
/ appUnitsPerDevUnit
;
5467 params
.decoration
= StyleTextDecorationLine::OVERLINE
;
5468 nsRect overlineRect
=
5469 nsCSSRendering::GetTextDecorationRect(aPresContext
, params
);
5471 aInkOverflowRect
->UnionRect(*aInkOverflowRect
, underlineRect
);
5472 aInkOverflowRect
->UnionRect(*aInkOverflowRect
, overlineRect
);
5474 // XXX If strikeoutSize is much thicker than the underlineSize, it may
5475 // cause overflowing from the overflow rect. However, such case
5476 // isn't realistic, we don't need to compute it now.
5478 if (aIncludeTextDecorations
) {
5479 // Use writing mode of parent frame for orthogonal text frame to
5480 // work. See comment in nsTextFrame::DrawTextRunAndDecorations.
5481 WritingMode parentWM
= GetParent()->GetWritingMode();
5482 bool verticalDec
= parentWM
.IsVertical();
5483 bool useVerticalMetrics
=
5484 verticalDec
!= verticalRun
5486 : verticalRun
&& mTextRun
->UseCenterBaseline();
5488 // Since CSS 2.1 requires that text-decoration defined on ancestors maintain
5489 // style and position, they can be drawn at virtually any y-offset, so
5490 // maxima and minima are required to reliably generate the rectangle for
5492 TextDecorations textDecs
;
5493 GetTextDecorations(aPresContext
, eResolvedColors
, textDecs
);
5494 if (textDecs
.HasDecorationLines()) {
5495 nscoord inflationMinFontSize
=
5496 nsLayoutUtils::InflationMinFontSizeFor(aBlock
);
5498 const nscoord measure
= verticalDec
? GetSize().height
: GetSize().width
;
5499 gfxFloat gfxWidth
= measure
/ appUnitsPerDevUnit
;
5501 gfxFloat(GetLogicalBaseline(parentWM
)) / appUnitsPerDevUnit
;
5502 nscoord frameBStart
= 0;
5503 if (parentWM
.IsVerticalRL()) {
5504 frameBStart
= GetSize().width
;
5508 nsCSSRendering::DecorationRectParams params
;
5509 params
.lineSize
= Size(gfxWidth
, 0);
5510 params
.ascent
= ascent
;
5511 params
.vertical
= verticalDec
;
5512 params
.sidewaysLeft
= mTextRun
->IsSidewaysLeft();
5514 nscoord
topOrLeft(nscoord_MAX
), bottomOrRight(nscoord_MIN
);
5515 typedef gfxFont::Metrics Metrics
;
5516 auto accumulateDecorationRect
=
5517 [&](const LineDecoration
& dec
, gfxFloat
Metrics::*lineSize
,
5518 mozilla::StyleTextDecorationLine lineType
) {
5519 params
.style
= dec
.mStyle
;
5520 // If the style is solid, let's include decoration line rect of
5521 // solid style since changing the style from none to
5522 // solid/dotted/dashed doesn't cause reflow.
5523 if (params
.style
== NS_STYLE_TEXT_DECORATION_STYLE_NONE
) {
5524 params
.style
= NS_STYLE_TEXT_DECORATION_STYLE_SOLID
;
5527 float inflation
= GetInflationForTextDecorations(
5528 dec
.mFrame
, inflationMinFontSize
);
5529 const Metrics metrics
=
5530 GetFirstFontMetrics(GetFontGroupForFrame(dec
.mFrame
, inflation
),
5531 useVerticalMetrics
);
5533 params
.defaultLineThickness
= metrics
.*lineSize
;
5534 params
.lineSize
.height
= ComputeDecorationLineThickness(
5535 dec
.mTextDecorationThickness
, params
.defaultLineThickness
,
5536 metrics
, appUnitsPerDevUnit
);
5538 bool swapUnderline
=
5539 parentWM
.IsCentralBaseline() && IsUnderlineRight(*Style());
5540 params
.offset
= ComputeDecorationLineOffset(
5541 lineType
, dec
.mTextUnderlinePosition
, dec
.mTextUnderlineOffset
,
5542 metrics
, appUnitsPerDevUnit
, parentWM
.IsCentralBaseline(),
5545 const nsRect decorationRect
=
5546 nsCSSRendering::GetTextDecorationRect(aPresContext
, params
) +
5547 (verticalDec
? nsPoint(frameBStart
- dec
.mBaselineOffset
, 0)
5548 : nsPoint(0, -dec
.mBaselineOffset
));
5551 topOrLeft
= std::min(decorationRect
.x
, topOrLeft
);
5552 bottomOrRight
= std::max(decorationRect
.XMost(), bottomOrRight
);
5554 topOrLeft
= std::min(decorationRect
.y
, topOrLeft
);
5555 bottomOrRight
= std::max(decorationRect
.YMost(), bottomOrRight
);
5559 // Below we loop through all text decorations and compute the rectangle
5560 // containing all of them, in this frame's coordinate space
5561 params
.decoration
= StyleTextDecorationLine::UNDERLINE
;
5562 for (const LineDecoration
& dec
: textDecs
.mUnderlines
) {
5563 accumulateDecorationRect(dec
, &Metrics::underlineSize
,
5566 params
.decoration
= StyleTextDecorationLine::OVERLINE
;
5567 for (const LineDecoration
& dec
: textDecs
.mOverlines
) {
5568 accumulateDecorationRect(dec
, &Metrics::underlineSize
,
5571 params
.decoration
= StyleTextDecorationLine::LINE_THROUGH
;
5572 for (const LineDecoration
& dec
: textDecs
.mStrikes
) {
5573 accumulateDecorationRect(dec
, &Metrics::strikeoutSize
,
5577 aInkOverflowRect
->UnionRect(
5580 ? nsRect(topOrLeft
, 0, bottomOrRight
- topOrLeft
, measure
)
5581 : nsRect(0, topOrLeft
, measure
, bottomOrRight
- topOrLeft
));
5584 aInkOverflowRect
->UnionRect(*aInkOverflowRect
,
5585 UpdateTextEmphasis(parentWM
, aProvider
));
5588 // text-stroke overflows: add half of text-stroke-width on all sides
5589 nscoord textStrokeWidth
= StyleText()->mWebkitTextStrokeWidth
;
5590 if (textStrokeWidth
> 0) {
5591 // Inflate rect by stroke-width/2; we add an extra pixel to allow for
5592 // antialiasing, rounding errors, etc.
5593 nsRect strokeRect
= *aInkOverflowRect
;
5594 strokeRect
.Inflate(textStrokeWidth
/ 2 + appUnitsPerDevUnit
);
5595 aInkOverflowRect
->UnionRect(*aInkOverflowRect
, strokeRect
);
5598 // Text-shadow overflows
5599 if (aIncludeShadows
) {
5601 nsLayoutUtils::GetTextShadowRectsUnion(*aInkOverflowRect
, this);
5602 aInkOverflowRect
->UnionRect(*aInkOverflowRect
, shadowRect
);
5605 // When this frame is not selected, the text-decoration area must be in
5607 if (!IsSelected() ||
5608 !CombineSelectionUnderlineRect(aPresContext
, *aInkOverflowRect
))
5610 AddStateBits(TEXT_SELECTION_UNDERLINE_OVERFLOWED
);
5613 gfxFloat
nsTextFrame::ComputeDescentLimitForSelectionUnderline(
5614 nsPresContext
* aPresContext
, const gfxFont::Metrics
& aFontMetrics
) {
5615 gfxFloat app
= aPresContext
->AppUnitsPerDevPixel();
5616 nscoord lineHeightApp
=
5617 ReflowInput::CalcLineHeight(GetContent(), Style(), PresContext(),
5618 NS_UNCONSTRAINEDSIZE
, GetFontSizeInflation());
5619 gfxFloat lineHeight
= gfxFloat(lineHeightApp
) / app
;
5620 if (lineHeight
<= aFontMetrics
.maxHeight
) {
5621 return aFontMetrics
.maxDescent
;
5623 return aFontMetrics
.maxDescent
+ (lineHeight
- aFontMetrics
.maxHeight
) / 2;
5626 // Make sure this stays in sync with DrawSelectionDecorations below
5627 static const SelectionTypeMask kSelectionTypesWithDecorations
=
5628 ToSelectionTypeMask(SelectionType::eSpellCheck
) |
5629 ToSelectionTypeMask(SelectionType::eURLStrikeout
) |
5630 ToSelectionTypeMask(SelectionType::eIMERawClause
) |
5631 ToSelectionTypeMask(SelectionType::eIMESelectedRawClause
) |
5632 ToSelectionTypeMask(SelectionType::eIMEConvertedClause
) |
5633 ToSelectionTypeMask(SelectionType::eIMESelectedClause
);
5636 gfxFloat
nsTextFrame::ComputeSelectionUnderlineHeight(
5637 nsPresContext
* aPresContext
, const gfxFont::Metrics
& aFontMetrics
,
5638 SelectionType aSelectionType
) {
5639 switch (aSelectionType
) {
5640 case SelectionType::eIMERawClause
:
5641 case SelectionType::eIMESelectedRawClause
:
5642 case SelectionType::eIMEConvertedClause
:
5643 case SelectionType::eIMESelectedClause
:
5644 return aFontMetrics
.underlineSize
;
5645 case SelectionType::eSpellCheck
: {
5646 // The thickness of the spellchecker underline shouldn't honor the font
5647 // metrics. It should be constant pixels value which is decided from the
5648 // default font size. Note that if the actual font size is smaller than
5649 // the default font size, we should use the actual font size because the
5650 // computed value from the default font size can be too thick for the
5651 // current font size.
5652 Length defaultFontSize
=
5653 aPresContext
->Document()
5654 ->GetFontPrefsForLang(nullptr)
5655 ->GetDefaultFont(StyleGenericFontFamily::None
)
5657 int32_t zoomedFontSize
= aPresContext
->CSSPixelsToDevPixels(
5658 nsStyleFont::ZoomText(*aPresContext
->Document(), defaultFontSize
)
5661 std::min(gfxFloat(zoomedFontSize
), aFontMetrics
.emHeight
);
5662 fontSize
= std::max(fontSize
, 1.0);
5663 return ceil(fontSize
/ 20);
5666 NS_WARNING("Requested underline style is not valid");
5667 return aFontMetrics
.underlineSize
;
5671 enum class DecorationType
{ Normal
, Selection
};
5672 struct nsTextFrame::PaintDecorationLineParams
5673 : nsCSSRendering::DecorationRectParams
{
5674 gfxContext
* context
= nullptr;
5675 LayoutDeviceRect dirtyRect
;
5677 const nscolor
* overrideColor
= nullptr;
5678 nscolor color
= NS_RGBA(0, 0, 0, 0);
5679 gfxFloat icoordInFrame
= 0.0f
;
5680 gfxFloat baselineOffset
= 0.0f
;
5681 DecorationType decorationType
= DecorationType::Normal
;
5682 DrawPathCallbacks
* callbacks
= nullptr;
5685 void nsTextFrame::PaintDecorationLine(
5686 const PaintDecorationLineParams
& aParams
) {
5687 nsCSSRendering::PaintDecorationLineParams params
;
5688 static_cast<nsCSSRendering::DecorationRectParams
&>(params
) = aParams
;
5689 params
.dirtyRect
= aParams
.dirtyRect
.ToUnknownRect();
5690 params
.pt
= aParams
.pt
;
5691 params
.color
= aParams
.overrideColor
? *aParams
.overrideColor
: aParams
.color
;
5692 params
.icoordInFrame
= Float(aParams
.icoordInFrame
);
5693 params
.baselineOffset
= Float(aParams
.baselineOffset
);
5694 if (aParams
.callbacks
) {
5695 Rect path
= nsCSSRendering::DecorationLineToPath(params
);
5696 if (aParams
.decorationType
== DecorationType::Normal
) {
5697 aParams
.callbacks
->PaintDecorationLine(path
, params
.color
);
5699 aParams
.callbacks
->PaintSelectionDecorationLine(path
, params
.color
);
5702 nsCSSRendering::PaintDecorationLine(this, *aParams
.context
->GetDrawTarget(),
5707 static uint8_t ToStyleLineStyle(const TextRangeStyle
& aStyle
) {
5708 switch (aStyle
.mLineStyle
) {
5709 case TextRangeStyle::LineStyle::None
:
5710 return NS_STYLE_TEXT_DECORATION_STYLE_NONE
;
5711 case TextRangeStyle::LineStyle::Solid
:
5712 return NS_STYLE_TEXT_DECORATION_STYLE_SOLID
;
5713 case TextRangeStyle::LineStyle::Dotted
:
5714 return NS_STYLE_TEXT_DECORATION_STYLE_DOTTED
;
5715 case TextRangeStyle::LineStyle::Dashed
:
5716 return NS_STYLE_TEXT_DECORATION_STYLE_DASHED
;
5717 case TextRangeStyle::LineStyle::Double
:
5718 return NS_STYLE_TEXT_DECORATION_STYLE_DOUBLE
;
5719 case TextRangeStyle::LineStyle::Wavy
:
5720 return NS_STYLE_TEXT_DECORATION_STYLE_WAVY
;
5722 MOZ_ASSERT_UNREACHABLE("Invalid line style");
5723 return NS_STYLE_TEXT_DECORATION_STYLE_NONE
;
5727 * This, plus kSelectionTypesWithDecorations, encapsulates all knowledge
5728 * about drawing text decoration for selections.
5730 void nsTextFrame::DrawSelectionDecorations(
5731 gfxContext
* aContext
, const LayoutDeviceRect
& aDirtyRect
,
5732 SelectionType aSelectionType
, nsTextPaintStyle
& aTextPaintStyle
,
5733 const TextRangeStyle
& aRangeStyle
, const Point
& aPt
,
5734 gfxFloat aICoordInFrame
, gfxFloat aWidth
, gfxFloat aAscent
,
5735 const gfxFont::Metrics
& aFontMetrics
, DrawPathCallbacks
* aCallbacks
,
5736 bool aVertical
, StyleTextDecorationLine aDecoration
) {
5737 PaintDecorationLineParams params
;
5738 params
.context
= aContext
;
5739 params
.dirtyRect
= aDirtyRect
;
5741 params
.lineSize
.width
= aWidth
;
5742 params
.ascent
= aAscent
;
5743 params
.decoration
= aDecoration
;
5744 params
.decorationType
= DecorationType::Selection
;
5745 params
.callbacks
= aCallbacks
;
5746 params
.vertical
= aVertical
;
5747 params
.sidewaysLeft
= mTextRun
->IsSidewaysLeft();
5748 params
.descentLimit
= ComputeDescentLimitForSelectionUnderline(
5749 aTextPaintStyle
.PresContext(), aFontMetrics
);
5752 const auto& decThickness
= StyleTextReset()->mTextDecorationThickness
;
5753 const gfxFloat appUnitsPerDevPixel
=
5754 aTextPaintStyle
.PresContext()->AppUnitsPerDevPixel();
5756 const WritingMode wm
= GetWritingMode();
5757 switch (aSelectionType
) {
5758 case SelectionType::eIMERawClause
:
5759 case SelectionType::eIMESelectedRawClause
:
5760 case SelectionType::eIMEConvertedClause
:
5761 case SelectionType::eIMESelectedClause
:
5762 case SelectionType::eSpellCheck
: {
5763 int32_t index
= nsTextPaintStyle::GetUnderlineStyleIndexForSelectionType(
5765 bool weDefineSelectionUnderline
=
5766 aTextPaintStyle
.GetSelectionUnderlineForPaint(
5767 index
, ¶ms
.color
, &relativeSize
, ¶ms
.style
);
5768 params
.defaultLineThickness
= ComputeSelectionUnderlineHeight(
5769 aTextPaintStyle
.PresContext(), aFontMetrics
, aSelectionType
);
5770 params
.lineSize
.height
= ComputeDecorationLineThickness(
5771 decThickness
, params
.defaultLineThickness
, aFontMetrics
,
5772 appUnitsPerDevPixel
);
5774 bool swapUnderline
= wm
.IsCentralBaseline() && IsUnderlineRight(*Style());
5775 const auto* styleText
= StyleText();
5776 params
.offset
= ComputeDecorationLineOffset(
5777 aDecoration
, styleText
->mTextUnderlinePosition
,
5778 styleText
->mTextUnderlineOffset
, aFontMetrics
, appUnitsPerDevPixel
,
5779 wm
.IsCentralBaseline(), swapUnderline
);
5781 bool isIMEType
= aSelectionType
!= SelectionType::eSpellCheck
;
5784 // IME decoration lines should not be drawn on the both ends, i.e., we
5785 // need to cut both edges of the decoration lines. Because same style
5786 // IME selections can adjoin, but the users need to be able to know
5787 // where are the boundaries of the selections.
5791 // IME selection #1 IME selection #2 IME selection #3
5793 // | XXXXXXXXXXXXXXXXXXX | XXXXXXXXXXXXXXXXXXXX | XXXXXXXXXXXXXXXXXXX
5794 // +---------------------+----------------------+--------------------
5798 params
.lineSize
.width
-= 2.0;
5800 if (isIMEType
&& aRangeStyle
.IsDefined()) {
5801 // If IME defines the style, that should override our definition.
5802 if (aRangeStyle
.IsLineStyleDefined()) {
5803 if (aRangeStyle
.mLineStyle
== TextRangeStyle::LineStyle::None
) {
5806 params
.style
= ToStyleLineStyle(aRangeStyle
);
5807 relativeSize
= aRangeStyle
.mIsBoldLine
? 2.0f
: 1.0f
;
5808 } else if (!weDefineSelectionUnderline
) {
5809 // There is no underline style definition.
5812 // If underline color is defined and that doesn't depend on the
5813 // foreground color, we should use the color directly.
5814 if (aRangeStyle
.IsUnderlineColorDefined() &&
5815 (!aRangeStyle
.IsForegroundColorDefined() ||
5816 aRangeStyle
.mUnderlineColor
!= aRangeStyle
.mForegroundColor
)) {
5817 params
.color
= aRangeStyle
.mUnderlineColor
;
5819 // If foreground color or background color is defined, the both colors
5820 // are computed by GetSelectionTextColors(). Then, we should use its
5821 // foreground color always. The color should have sufficient contrast
5822 // with the background color.
5823 else if (aRangeStyle
.IsForegroundColorDefined() ||
5824 aRangeStyle
.IsBackgroundColorDefined()) {
5826 GetSelectionTextColors(aSelectionType
, aTextPaintStyle
, aRangeStyle
,
5827 ¶ms
.color
, &bg
);
5829 // Otherwise, use the foreground color of the frame.
5831 params
.color
= aTextPaintStyle
.GetTextColor();
5833 } else if (!weDefineSelectionUnderline
) {
5834 // IME doesn't specify the selection style and we don't define selection
5840 case SelectionType::eURLStrikeout
: {
5841 nscoord inflationMinFontSize
=
5842 nsLayoutUtils::InflationMinFontSizeFor(this);
5844 GetInflationForTextDecorations(this, inflationMinFontSize
);
5845 const gfxFont::Metrics metrics
=
5846 GetFirstFontMetrics(GetFontGroupForFrame(this, inflation
), aVertical
);
5848 relativeSize
= 2.0f
;
5849 aTextPaintStyle
.GetURLSecondaryColor(¶ms
.color
);
5850 params
.style
= NS_STYLE_TEXT_DECORATION_STYLE_SOLID
;
5851 params
.defaultLineThickness
= metrics
.strikeoutSize
;
5852 params
.lineSize
.height
= ComputeDecorationLineThickness(
5853 decThickness
, params
.defaultLineThickness
, metrics
,
5854 appUnitsPerDevPixel
);
5855 // TODO(jfkthame): ComputeDecorationLineOffset? check vertical mode!
5856 params
.offset
= metrics
.strikeoutOffset
+ 0.5;
5857 params
.decoration
= StyleTextDecorationLine::LINE_THROUGH
;
5861 NS_WARNING("Requested selection decorations when there aren't any");
5864 params
.lineSize
.height
*= relativeSize
;
5865 params
.defaultLineThickness
*= relativeSize
;
5866 params
.icoordInFrame
=
5867 (aVertical
? params
.pt
.y
- aPt
.y
: params
.pt
.x
- aPt
.x
) + aICoordInFrame
;
5868 PaintDecorationLine(params
);
5872 bool nsTextFrame::GetSelectionTextColors(SelectionType aSelectionType
,
5873 nsTextPaintStyle
& aTextPaintStyle
,
5874 const TextRangeStyle
& aRangeStyle
,
5875 nscolor
* aForeground
,
5876 nscolor
* aBackground
) {
5877 switch (aSelectionType
) {
5878 case SelectionType::eNormal
:
5879 return aTextPaintStyle
.GetSelectionColors(aForeground
, aBackground
);
5880 case SelectionType::eFind
:
5881 aTextPaintStyle
.GetHighlightColors(aForeground
, aBackground
);
5883 case SelectionType::eURLSecondary
:
5884 aTextPaintStyle
.GetURLSecondaryColor(aForeground
);
5885 *aBackground
= NS_RGBA(0, 0, 0, 0);
5887 case SelectionType::eIMERawClause
:
5888 case SelectionType::eIMESelectedRawClause
:
5889 case SelectionType::eIMEConvertedClause
:
5890 case SelectionType::eIMESelectedClause
:
5891 if (aRangeStyle
.IsDefined()) {
5892 if (!aRangeStyle
.IsForegroundColorDefined() &&
5893 !aRangeStyle
.IsBackgroundColorDefined()) {
5894 *aForeground
= aTextPaintStyle
.GetTextColor();
5895 *aBackground
= NS_RGBA(0, 0, 0, 0);
5898 if (aRangeStyle
.IsForegroundColorDefined()) {
5899 *aForeground
= aRangeStyle
.mForegroundColor
;
5900 if (aRangeStyle
.IsBackgroundColorDefined()) {
5901 *aBackground
= aRangeStyle
.mBackgroundColor
;
5903 // If foreground color is defined but background color isn't
5904 // defined, we can guess that IME must expect that the background
5905 // color is system's default field background color.
5906 *aBackground
= aTextPaintStyle
.GetSystemFieldBackgroundColor();
5908 } else { // aRangeStyle.IsBackgroundColorDefined() is true
5909 *aBackground
= aRangeStyle
.mBackgroundColor
;
5910 // If background color is defined but foreground color isn't defined,
5911 // we can assume that IME must expect that the foreground color is
5912 // same as system's field text color.
5913 *aForeground
= aTextPaintStyle
.GetSystemFieldForegroundColor();
5917 aTextPaintStyle
.GetIMESelectionColors(
5918 nsTextPaintStyle::GetUnderlineStyleIndexForSelectionType(
5920 aForeground
, aBackground
);
5923 *aForeground
= aTextPaintStyle
.GetTextColor();
5924 *aBackground
= NS_RGBA(0, 0, 0, 0);
5930 * This sets *aShadows to the appropriate shadows, if any, for the given
5931 * type of selection.
5932 * If text-shadow was not specified, *aShadows is left untouched.
5934 static void GetSelectionTextShadow(nsIFrame
* aFrame
,
5935 SelectionType aSelectionType
,
5936 nsTextPaintStyle
& aTextPaintStyle
,
5937 Span
<const StyleSimpleShadow
>* aShadows
) {
5938 if (aSelectionType
!= SelectionType::eNormal
) {
5941 aTextPaintStyle
.GetSelectionShadow(aShadows
);
5945 * This class lets us iterate over chunks of text in a uniform selection state,
5946 * observing cluster boundaries, in content order, maintaining the current
5947 * x-offset as we go, and telling whether the text chunk has a hyphen after
5948 * it or not. The caller is responsible for actually computing the advance
5949 * width of each chunk.
5951 class SelectionIterator
{
5952 typedef nsTextFrame::PropertyProvider PropertyProvider
;
5956 * aStart and aLength are in the original string. aSelectionDetails is
5957 * according to the original string.
5958 * @param aXOffset the offset from the origin of the frame to the start
5959 * of the text (the left baseline origin for LTR, the right baseline origin
5962 SelectionIterator(SelectionDetails
** aSelectionDetails
,
5963 gfxTextRun::Range aRange
, PropertyProvider
& aProvider
,
5964 gfxTextRun
* aTextRun
, gfxFloat aXOffset
);
5967 * Returns the next segment of uniformly selected (or not) text.
5968 * @param aXOffset the offset from the origin of the frame to the start
5969 * of the text (the left baseline origin for LTR, the right baseline origin
5971 * @param aRange the transformed string range of the text for this segment
5972 * @param aHyphenWidth if a hyphen is to be rendered after the text, the
5973 * width of the hyphen, otherwise zero
5974 * @param aSelectionType the selection type for this segment
5975 * @param aStyle the selection style for this segment
5976 * @return false if there are no more segments
5978 bool GetNextSegment(gfxFloat
* aXOffset
, gfxTextRun::Range
* aRange
,
5979 gfxFloat
* aHyphenWidth
, SelectionType
* aSelectionType
,
5980 TextRangeStyle
* aStyle
);
5981 void UpdateWithAdvance(gfxFloat aAdvance
) {
5982 mXOffset
+= aAdvance
* mTextRun
->GetDirection();
5986 SelectionDetails
** mSelectionDetails
;
5987 PropertyProvider
& mProvider
;
5988 RefPtr
<gfxTextRun
> mTextRun
;
5989 gfxSkipCharsIterator mIterator
;
5990 gfxTextRun::Range mOriginalRange
;
5994 SelectionIterator::SelectionIterator(SelectionDetails
** aSelectionDetails
,
5995 gfxTextRun::Range aRange
,
5996 PropertyProvider
& aProvider
,
5997 gfxTextRun
* aTextRun
, gfxFloat aXOffset
)
5998 : mSelectionDetails(aSelectionDetails
),
5999 mProvider(aProvider
),
6001 mIterator(aProvider
.GetStart()),
6002 mOriginalRange(aRange
),
6003 mXOffset(aXOffset
) {
6004 mIterator
.SetOriginalOffset(aRange
.start
);
6007 bool SelectionIterator::GetNextSegment(gfxFloat
* aXOffset
,
6008 gfxTextRun::Range
* aRange
,
6009 gfxFloat
* aHyphenWidth
,
6010 SelectionType
* aSelectionType
,
6011 TextRangeStyle
* aStyle
) {
6012 if (mIterator
.GetOriginalOffset() >= int32_t(mOriginalRange
.end
))
6015 // save offset into transformed string now
6016 uint32_t runOffset
= mIterator
.GetSkippedOffset();
6018 uint32_t index
= mIterator
.GetOriginalOffset() - mOriginalRange
.start
;
6019 SelectionDetails
* sdptr
= mSelectionDetails
[index
];
6020 SelectionType selectionType
=
6021 sdptr
? sdptr
->mSelectionType
: SelectionType::eNone
;
6022 TextRangeStyle style
;
6024 style
= sdptr
->mTextRangeStyle
;
6026 for (++index
; index
< mOriginalRange
.Length(); ++index
) {
6027 if (sdptr
!= mSelectionDetails
[index
]) break;
6029 mIterator
.SetOriginalOffset(index
+ mOriginalRange
.start
);
6031 // Advance to the next cluster boundary
6032 while (mIterator
.GetOriginalOffset() < int32_t(mOriginalRange
.end
) &&
6033 !mIterator
.IsOriginalCharSkipped() &&
6034 !mTextRun
->IsClusterStart(mIterator
.GetSkippedOffset())) {
6035 mIterator
.AdvanceOriginal(1);
6038 bool haveHyphenBreak
=
6039 mProvider
.GetFrame()->HasAnyStateBits(TEXT_HYPHEN_BREAK
);
6040 aRange
->start
= runOffset
;
6041 aRange
->end
= mIterator
.GetSkippedOffset();
6042 *aXOffset
= mXOffset
;
6044 if (mIterator
.GetOriginalOffset() == int32_t(mOriginalRange
.end
) &&
6046 *aHyphenWidth
= mProvider
.GetHyphenWidth();
6048 *aSelectionType
= selectionType
;
6053 static void AddHyphenToMetrics(nsTextFrame
* aTextFrame
,
6054 const gfxTextRun
* aBaseTextRun
,
6055 gfxTextRun::Metrics
* aMetrics
,
6056 gfxFont::BoundingBoxType aBoundingBoxType
,
6057 DrawTarget
* aDrawTarget
) {
6058 // Fix up metrics to include hyphen
6059 RefPtr
<gfxTextRun
> hyphenTextRun
=
6060 GetHyphenTextRun(aBaseTextRun
, aDrawTarget
, aTextFrame
);
6061 if (!hyphenTextRun
) {
6065 gfxTextRun::Metrics hyphenMetrics
=
6066 hyphenTextRun
->MeasureText(aBoundingBoxType
, aDrawTarget
);
6067 if (aTextFrame
->GetWritingMode().IsLineInverted()) {
6068 hyphenMetrics
.mBoundingBox
.y
= -hyphenMetrics
.mBoundingBox
.YMost();
6070 aMetrics
->CombineWith(hyphenMetrics
, aBaseTextRun
->IsRightToLeft());
6073 void nsTextFrame::PaintOneShadow(const PaintShadowParams
& aParams
,
6074 const StyleSimpleShadow
& aShadowDetails
,
6075 gfxRect
& aBoundingBox
, uint32_t aBlurFlags
) {
6076 AUTO_PROFILER_LABEL("nsTextFrame::PaintOneShadow", GRAPHICS
);
6078 nsPoint
shadowOffset(aShadowDetails
.horizontal
.ToAppUnits(),
6079 aShadowDetails
.vertical
.ToAppUnits());
6080 nscoord blurRadius
= std::max(aShadowDetails
.blur
.ToAppUnits(), 0);
6082 nscolor shadowColor
= aShadowDetails
.color
.CalcColor(aParams
.foregroundColor
);
6084 if (auto* textDrawer
= aParams
.context
->GetTextDrawer()) {
6085 wr::Shadow wrShadow
;
6087 wrShadow
.offset
= {PresContext()->AppUnitsToFloatDevPixels(shadowOffset
.x
),
6088 PresContext()->AppUnitsToFloatDevPixels(shadowOffset
.y
)};
6090 wrShadow
.blur_radius
= PresContext()->AppUnitsToFloatDevPixels(blurRadius
);
6091 wrShadow
.color
= wr::ToColorF(ToDeviceColor(shadowColor
));
6093 bool inflate
= true;
6094 textDrawer
->AppendShadow(wrShadow
, inflate
);
6098 // This rect is the box which is equivalent to where the shadow will be
6099 // painted. The origin of aBoundingBox is the text baseline left, so we must
6100 // translate it by that much in order to make the origin the top-left corner
6101 // of the text bounding box. Note that aLeftSideOffset is line-left, so
6102 // actually means top offset in vertical writing modes.
6103 gfxRect shadowGfxRect
;
6104 WritingMode wm
= GetWritingMode();
6105 if (wm
.IsVertical()) {
6106 shadowGfxRect
= aBoundingBox
;
6107 if (wm
.IsVerticalRL()) {
6108 // for vertical-RL, reverse direction of x-coords of bounding box
6109 shadowGfxRect
.x
= -shadowGfxRect
.XMost();
6111 shadowGfxRect
+= gfxPoint(aParams
.textBaselinePt
.x
,
6112 aParams
.framePt
.y
+ aParams
.leftSideOffset
);
6115 aBoundingBox
+ gfxPoint(aParams
.framePt
.x
+ aParams
.leftSideOffset
,
6116 aParams
.textBaselinePt
.y
);
6118 Point
shadowGfxOffset(shadowOffset
.x
, shadowOffset
.y
);
6119 shadowGfxRect
+= gfxPoint(shadowGfxOffset
.x
, shadowOffset
.y
);
6121 nsRect
shadowRect(NSToCoordRound(shadowGfxRect
.X()),
6122 NSToCoordRound(shadowGfxRect
.Y()),
6123 NSToCoordRound(shadowGfxRect
.Width()),
6124 NSToCoordRound(shadowGfxRect
.Height()));
6126 nsContextBoxBlur contextBoxBlur
;
6127 const auto A2D
= PresContext()->AppUnitsPerDevPixel();
6128 gfxContext
* shadowContext
=
6129 contextBoxBlur
.Init(shadowRect
, 0, blurRadius
, A2D
, aParams
.context
,
6130 LayoutDevicePixel::ToAppUnits(aParams
.dirtyRect
, A2D
),
6131 nullptr, aBlurFlags
);
6132 if (!shadowContext
) return;
6134 aParams
.context
->Save();
6135 aParams
.context
->SetColor(sRGBColor::FromABGR(shadowColor
));
6137 // Draw the text onto our alpha-only surface to capture the alpha values.
6138 // Remember that the box blur context has a device offset on it, so we don't
6139 // need to translate any coordinates to fit on the surface.
6140 gfxFloat advanceWidth
;
6141 nsTextPaintStyle
textPaintStyle(this);
6142 DrawTextParams
params(shadowContext
);
6143 params
.advanceWidth
= &advanceWidth
;
6144 params
.dirtyRect
= aParams
.dirtyRect
;
6145 params
.framePt
= aParams
.framePt
+ shadowGfxOffset
;
6146 params
.provider
= aParams
.provider
;
6147 params
.textStyle
= &textPaintStyle
;
6149 aParams
.context
== shadowContext
? shadowColor
: NS_RGB(0, 0, 0);
6150 params
.clipEdges
= aParams
.clipEdges
;
6151 params
.drawSoftHyphen
= HasAnyStateBits(TEXT_HYPHEN_BREAK
);
6152 // Multi-color shadow is not allowed, so we use the same color of the text
6154 params
.decorationOverrideColor
= ¶ms
.textColor
;
6155 DrawText(aParams
.range
, aParams
.textBaselinePt
+ shadowGfxOffset
, params
);
6157 contextBoxBlur
.DoPaint();
6158 aParams
.context
->Restore();
6161 // Paints selection backgrounds and text in the correct colors. Also computes
6162 // aAllTypes, the union of all selection types that are applying to this text.
6163 bool nsTextFrame::PaintTextWithSelectionColors(
6164 const PaintTextSelectionParams
& aParams
,
6165 const UniquePtr
<SelectionDetails
>& aDetails
,
6166 SelectionTypeMask
* aAllSelectionTypeMask
,
6167 const nsDisplayText::ClipEdges
& aClipEdges
) {
6168 const gfxTextRun::Range
& contentRange
= aParams
.contentRange
;
6170 // Figure out which selections control the colors to use for each character.
6171 // Note: prevailingSelectionsBuffer is keeping extra raw pointers to
6172 // uniquely-owned resources, but it's safe because it's temporary and the
6173 // resources are owned by the caller. Therefore, they'll outlive this object.
6174 AutoTArray
<SelectionDetails
*, BIG_TEXT_NODE_SIZE
> prevailingSelectionsBuffer
;
6175 SelectionDetails
** prevailingSelections
=
6176 prevailingSelectionsBuffer
.AppendElements(contentRange
.Length(),
6178 if (!prevailingSelections
) {
6182 SelectionTypeMask allSelectionTypeMask
= 0;
6183 for (uint32_t i
= 0; i
< contentRange
.Length(); ++i
) {
6184 prevailingSelections
[i
] = nullptr;
6187 bool anyBackgrounds
= false;
6188 for (SelectionDetails
* sdptr
= aDetails
.get(); sdptr
;
6189 sdptr
= sdptr
->mNext
.get()) {
6190 int32_t start
= std::max(0, sdptr
->mStart
- int32_t(contentRange
.start
));
6191 int32_t end
= std::min(int32_t(contentRange
.Length()),
6192 sdptr
->mEnd
- int32_t(contentRange
.start
));
6193 SelectionType selectionType
= sdptr
->mSelectionType
;
6195 allSelectionTypeMask
|= ToSelectionTypeMask(selectionType
);
6196 // Ignore selections that don't set colors
6197 nscolor foreground
, background
;
6198 if (GetSelectionTextColors(selectionType
, *aParams
.textPaintStyle
,
6199 sdptr
->mTextRangeStyle
, &foreground
,
6201 if (NS_GET_A(background
) > 0) {
6202 anyBackgrounds
= true;
6204 for (int32_t i
= start
; i
< end
; ++i
) {
6205 // Favour normal selection over IME selections
6206 if (!prevailingSelections
[i
] ||
6207 selectionType
< prevailingSelections
[i
]->mSelectionType
) {
6208 prevailingSelections
[i
] = sdptr
;
6214 *aAllSelectionTypeMask
= allSelectionTypeMask
;
6216 if (!allSelectionTypeMask
) {
6217 // Nothing is selected in the given text range. XXX can this still occur?
6221 bool vertical
= mTextRun
->IsVertical();
6222 const gfxFloat startIOffset
=
6223 vertical
? aParams
.textBaselinePt
.y
- aParams
.framePt
.y
6224 : aParams
.textBaselinePt
.x
- aParams
.framePt
.x
;
6225 gfxFloat iOffset
, hyphenWidth
;
6226 Range range
; // in transformed string
6227 TextRangeStyle rangeStyle
;
6228 // Draw background colors
6230 auto* textDrawer
= aParams
.context
->GetTextDrawer();
6232 if (anyBackgrounds
&& !aParams
.IsGenerateTextMask()) {
6233 int32_t appUnitsPerDevPixel
=
6234 aParams
.textPaintStyle
->PresContext()->AppUnitsPerDevPixel();
6235 SelectionIterator
iterator(prevailingSelections
, contentRange
,
6236 *aParams
.provider
, mTextRun
, startIOffset
);
6237 SelectionType selectionType
;
6238 while (iterator
.GetNextSegment(&iOffset
, &range
, &hyphenWidth
,
6239 &selectionType
, &rangeStyle
)) {
6240 nscolor foreground
, background
;
6241 GetSelectionTextColors(selectionType
, *aParams
.textPaintStyle
, rangeStyle
,
6242 &foreground
, &background
);
6243 // Draw background color
6245 hyphenWidth
+ mTextRun
->GetAdvanceWidth(range
, aParams
.provider
);
6246 if (NS_GET_A(background
) > 0) {
6248 gfxFloat offs
= iOffset
- (mTextRun
->IsInlineReversed() ? advance
: 0);
6250 bgRect
= nsRect(aParams
.framePt
.x
, aParams
.framePt
.y
+ offs
,
6251 GetSize().width
, advance
);
6253 bgRect
= nsRect(aParams
.framePt
.x
+ offs
, aParams
.framePt
.y
, advance
,
6257 LayoutDeviceRect selectionRect
=
6258 LayoutDeviceRect::FromAppUnits(bgRect
, appUnitsPerDevPixel
);
6261 textDrawer
->AppendSelectionRect(selectionRect
,
6262 ToDeviceColor(background
));
6264 PaintSelectionBackground(*aParams
.context
->GetDrawTarget(),
6265 background
, aParams
.dirtyRect
, selectionRect
,
6269 iterator
.UpdateWithAdvance(advance
);
6274 DrawTextParams
params(aParams
.context
);
6275 params
.dirtyRect
= aParams
.dirtyRect
;
6276 params
.framePt
= aParams
.framePt
;
6277 params
.provider
= aParams
.provider
;
6278 params
.textStyle
= aParams
.textPaintStyle
;
6279 params
.clipEdges
= &aClipEdges
;
6280 params
.advanceWidth
= &advance
;
6281 params
.callbacks
= aParams
.callbacks
;
6282 params
.glyphRange
= aParams
.glyphRange
;
6284 PaintShadowParams
shadowParams(aParams
);
6285 shadowParams
.provider
= aParams
.provider
;
6286 shadowParams
.clipEdges
= &aClipEdges
;
6289 const nsStyleText
* textStyle
= StyleText();
6290 SelectionIterator
iterator(prevailingSelections
, contentRange
,
6291 *aParams
.provider
, mTextRun
, startIOffset
);
6292 SelectionType selectionType
;
6293 while (iterator
.GetNextSegment(&iOffset
, &range
, &hyphenWidth
, &selectionType
,
6295 nscolor foreground
, background
;
6296 if (aParams
.IsGenerateTextMask()) {
6297 foreground
= NS_RGBA(0, 0, 0, 255);
6299 GetSelectionTextColors(selectionType
, *aParams
.textPaintStyle
, rangeStyle
,
6300 &foreground
, &background
);
6303 gfx::Point textBaselinePt
=
6305 ? gfx::Point(aParams
.textBaselinePt
.x
, aParams
.framePt
.y
+ iOffset
)
6306 : gfx::Point(aParams
.framePt
.x
+ iOffset
, aParams
.textBaselinePt
.y
);
6308 // Determine what shadow, if any, to draw - either from textStyle
6309 // or from the ::-moz-selection pseudo-class if specified there
6310 Span
<const StyleSimpleShadow
> shadows
= textStyle
->mTextShadow
.AsSpan();
6311 GetSelectionTextShadow(this, selectionType
, *aParams
.textPaintStyle
,
6313 if (!shadows
.IsEmpty()) {
6314 nscoord startEdge
= iOffset
;
6315 if (mTextRun
->IsInlineReversed()) {
6317 hyphenWidth
+ mTextRun
->GetAdvanceWidth(range
, aParams
.provider
);
6319 shadowParams
.range
= range
;
6320 shadowParams
.textBaselinePt
= textBaselinePt
;
6321 shadowParams
.foregroundColor
= foreground
;
6322 shadowParams
.leftSideOffset
= startEdge
;
6323 PaintShadows(shadows
, shadowParams
);
6326 // Draw text segment
6327 params
.textColor
= foreground
;
6328 params
.textStrokeColor
= aParams
.textPaintStyle
->GetWebkitTextStrokeColor();
6329 params
.textStrokeWidth
= aParams
.textPaintStyle
->GetWebkitTextStrokeWidth();
6330 params
.drawSoftHyphen
= hyphenWidth
> 0;
6331 DrawText(range
, textBaselinePt
, params
);
6332 advance
+= hyphenWidth
;
6333 iterator
.UpdateWithAdvance(advance
);
6338 void nsTextFrame::PaintTextSelectionDecorations(
6339 const PaintTextSelectionParams
& aParams
,
6340 const UniquePtr
<SelectionDetails
>& aDetails
, SelectionType aSelectionType
) {
6341 // Hide text decorations if we're currently hiding @font-face fallback text
6342 if (aParams
.provider
->GetFontGroup()->ShouldSkipDrawing()) return;
6344 // Figure out which characters will be decorated for this selection.
6345 // Note: selectedCharsBuffer is keeping extra raw pointers to
6346 // uniquely-owned resources, but it's safe because it's temporary and the
6347 // resources are owned by the caller. Therefore, they'll outlive this object.
6348 const gfxTextRun::Range
& contentRange
= aParams
.contentRange
;
6349 AutoTArray
<SelectionDetails
*, BIG_TEXT_NODE_SIZE
> selectedCharsBuffer
;
6350 SelectionDetails
** selectedChars
=
6351 selectedCharsBuffer
.AppendElements(contentRange
.Length(), fallible
);
6352 if (!selectedChars
) {
6355 for (uint32_t i
= 0; i
< contentRange
.Length(); ++i
) {
6356 selectedChars
[i
] = nullptr;
6359 for (SelectionDetails
* sdptr
= aDetails
.get(); sdptr
;
6360 sdptr
= sdptr
->mNext
.get()) {
6361 if (sdptr
->mSelectionType
== aSelectionType
) {
6362 int32_t start
= std::max(0, sdptr
->mStart
- int32_t(contentRange
.start
));
6363 int32_t end
= std::min(int32_t(contentRange
.Length()),
6364 sdptr
->mEnd
- int32_t(contentRange
.start
));
6365 for (int32_t i
= start
; i
< end
; ++i
) {
6366 selectedChars
[i
] = sdptr
;
6371 gfxFont
* firstFont
= aParams
.provider
->GetFontGroup()->GetFirstValidFont();
6372 bool verticalRun
= mTextRun
->IsVertical();
6373 bool useVerticalMetrics
= verticalRun
&& mTextRun
->UseCenterBaseline();
6374 bool rightUnderline
= useVerticalMetrics
&& IsUnderlineRight(*Style());
6375 const auto kDecoration
= rightUnderline
? StyleTextDecorationLine::OVERLINE
6376 : StyleTextDecorationLine::UNDERLINE
;
6377 gfxFont::Metrics
decorationMetrics(
6378 firstFont
->GetMetrics(useVerticalMetrics
? nsFontMetrics::eVertical
6379 : nsFontMetrics::eHorizontal
));
6380 decorationMetrics
.underlineOffset
=
6381 aParams
.provider
->GetFontGroup()->GetUnderlineOffset();
6383 gfxFloat startIOffset
= verticalRun
6384 ? aParams
.textBaselinePt
.y
- aParams
.framePt
.y
6385 : aParams
.textBaselinePt
.x
- aParams
.framePt
.x
;
6386 SelectionIterator
iterator(selectedChars
, contentRange
, *aParams
.provider
,
6387 mTextRun
, startIOffset
);
6388 gfxFloat iOffset
, hyphenWidth
;
6390 int32_t app
= aParams
.textPaintStyle
->PresContext()->AppUnitsPerDevPixel();
6391 // XXX aTextBaselinePt is in AppUnits, shouldn't it be nsFloatPoint?
6394 pt
.x
= (aParams
.textBaselinePt
.x
- mAscent
) / app
;
6396 pt
.y
= (aParams
.textBaselinePt
.y
- mAscent
) / app
;
6398 SelectionType nextSelectionType
;
6399 TextRangeStyle selectedStyle
;
6401 while (iterator
.GetNextSegment(&iOffset
, &range
, &hyphenWidth
,
6402 &nextSelectionType
, &selectedStyle
)) {
6404 hyphenWidth
+ mTextRun
->GetAdvanceWidth(range
, aParams
.provider
);
6405 if (nextSelectionType
== aSelectionType
) {
6407 pt
.y
= (aParams
.framePt
.y
+ iOffset
-
6408 (mTextRun
->IsInlineReversed() ? advance
: 0)) /
6411 pt
.x
= (aParams
.framePt
.x
+ iOffset
-
6412 (mTextRun
->IsInlineReversed() ? advance
: 0)) /
6415 gfxFloat width
= Abs(advance
) / app
;
6416 gfxFloat xInFrame
= pt
.x
- (aParams
.framePt
.x
/ app
);
6417 DrawSelectionDecorations(aParams
.context
, aParams
.dirtyRect
,
6418 aSelectionType
, *aParams
.textPaintStyle
,
6419 selectedStyle
, pt
, xInFrame
, width
,
6420 mAscent
/ app
, decorationMetrics
,
6421 aParams
.callbacks
, verticalRun
, kDecoration
);
6423 iterator
.UpdateWithAdvance(advance
);
6427 bool nsTextFrame::PaintTextWithSelection(
6428 const PaintTextSelectionParams
& aParams
,
6429 const nsDisplayText::ClipEdges
& aClipEdges
) {
6430 NS_ASSERTION(GetContent()->IsMaybeSelected(), "wrong paint path");
6432 UniquePtr
<SelectionDetails
> details
= GetSelectionDetails();
6437 SelectionTypeMask allSelectionTypeMask
;
6438 if (!PaintTextWithSelectionColors(aParams
, details
, &allSelectionTypeMask
,
6442 // Iterate through just the selection rawSelectionTypes that paint decorations
6443 // and paint decorations for any that actually occur in this frame. Paint
6444 // higher-numbered selection rawSelectionTypes below lower-numered ones on the
6445 // general principal that lower-numbered selections are higher priority.
6446 allSelectionTypeMask
&= kSelectionTypesWithDecorations
;
6447 MOZ_ASSERT(kPresentSelectionTypes
[0] == SelectionType::eNormal
,
6448 "The following for loop assumes that the first item of "
6449 "kPresentSelectionTypes is SelectionType::eNormal");
6450 for (size_t i
= ArrayLength(kPresentSelectionTypes
) - 1; i
>= 1; --i
) {
6451 SelectionType selectionType
= kPresentSelectionTypes
[i
];
6452 if (ToSelectionTypeMask(selectionType
) & allSelectionTypeMask
) {
6453 // There is some selection of this selectionType. Try to paint its
6454 // decorations (there might not be any for this type but that's OK,
6455 // PaintTextSelectionDecorations will exit early).
6456 PaintTextSelectionDecorations(aParams
, details
, selectionType
);
6463 void nsTextFrame::DrawEmphasisMarks(gfxContext
* aContext
, WritingMode aWM
,
6464 const gfx::Point
& aTextBaselinePt
,
6465 const gfx::Point
& aFramePt
, Range aRange
,
6466 const nscolor
* aDecorationOverrideColor
,
6467 PropertyProvider
* aProvider
) {
6468 const EmphasisMarkInfo
* info
= GetProperty(EmphasisMarkProperty());
6473 bool isTextCombined
= Style()->IsTextCombined();
6474 if (isTextCombined
&& !aWM
.IsVertical()) {
6475 // XXX This only happens when the parent is display:contents with an
6476 // orthogonal writing mode. This should be rare, and don't have use
6477 // cases, so we don't care. It is non-trivial to implement a sane
6478 // behavior for that case: if you treat the text as not combined,
6479 // the marks would spread wider than the text (which is rendered as
6480 // combined); if you try to draw a single mark, selecting part of
6481 // the text could dynamically create multiple new marks.
6482 NS_WARNING("Give up on combined text with horizontal wm");
6486 aDecorationOverrideColor
6487 ? *aDecorationOverrideColor
6488 : nsLayoutUtils::GetColor(this, &nsStyleText::mTextEmphasisColor
);
6489 aContext
->SetColor(sRGBColor::FromABGR(color
));
6491 if (!isTextCombined
) {
6492 pt
= aTextBaselinePt
;
6494 MOZ_ASSERT(aWM
.IsVertical());
6496 if (aWM
.IsVerticalRL()) {
6497 pt
.x
+= GetSize().width
- GetLogicalBaseline(aWM
);
6499 pt
.x
+= GetLogicalBaseline(aWM
);
6502 if (!aWM
.IsVertical()) {
6503 pt
.y
+= info
->baselineOffset
;
6505 if (aWM
.IsVerticalRL()) {
6506 pt
.x
-= info
->baselineOffset
;
6508 pt
.x
+= info
->baselineOffset
;
6511 if (!isTextCombined
) {
6512 mTextRun
->DrawEmphasisMarks(aContext
, info
->textRun
.get(), info
->advance
,
6513 pt
, aRange
, aProvider
);
6515 pt
.y
+= (GetSize().height
- info
->advance
) / 2;
6516 gfxTextRun::DrawParams
params(aContext
);
6517 info
->textRun
->Draw(Range(info
->textRun
.get()), pt
, params
);
6521 nscolor
nsTextFrame::GetCaretColorAt(int32_t aOffset
) {
6522 MOZ_ASSERT(aOffset
>= 0, "aOffset must be positive");
6524 nscolor result
= nsIFrame::GetCaretColorAt(aOffset
);
6525 gfxSkipCharsIterator iter
= EnsureTextRun(nsTextFrame::eInflated
);
6526 PropertyProvider
provider(this, iter
, nsTextFrame::eInflated
, mFontMetrics
);
6527 int32_t contentOffset
= provider
.GetStart().GetOriginalOffset();
6528 int32_t contentLength
= provider
.GetOriginalLength();
6530 aOffset
>= contentOffset
&& aOffset
<= contentOffset
+ contentLength
,
6531 "aOffset must be in the frame's range");
6533 int32_t offsetInFrame
= aOffset
- contentOffset
;
6534 if (offsetInFrame
< 0 || offsetInFrame
>= contentLength
) {
6538 bool isSolidTextColor
= true;
6539 if (SVGUtils::IsInSVGTextSubtree(this)) {
6540 const nsStyleSVG
* style
= StyleSVG();
6541 if (!style
->mFill
.kind
.IsNone() && !style
->mFill
.kind
.IsColor()) {
6542 isSolidTextColor
= false;
6546 nsTextPaintStyle
textPaintStyle(this);
6547 textPaintStyle
.SetResolveColors(isSolidTextColor
);
6548 UniquePtr
<SelectionDetails
> details
= GetSelectionDetails();
6549 SelectionType selectionType
= SelectionType::eNone
;
6550 for (SelectionDetails
* sdptr
= details
.get(); sdptr
;
6551 sdptr
= sdptr
->mNext
.get()) {
6552 int32_t start
= std::max(0, sdptr
->mStart
- contentOffset
);
6553 int32_t end
= std::min(contentLength
, sdptr
->mEnd
- contentOffset
);
6554 if (start
<= offsetInFrame
&& offsetInFrame
< end
&&
6555 (selectionType
== SelectionType::eNone
||
6556 sdptr
->mSelectionType
< selectionType
)) {
6557 nscolor foreground
, background
;
6558 if (GetSelectionTextColors(sdptr
->mSelectionType
, textPaintStyle
,
6559 sdptr
->mTextRangeStyle
, &foreground
,
6561 if (!isSolidTextColor
&& NS_IS_SELECTION_SPECIAL_COLOR(foreground
)) {
6562 result
= NS_RGBA(0, 0, 0, 255);
6564 result
= foreground
;
6566 selectionType
= sdptr
->mSelectionType
;
6574 static gfxTextRun::Range
ComputeTransformedRange(
6575 nsTextFrame::PropertyProvider
& aProvider
) {
6576 gfxSkipCharsIterator
iter(aProvider
.GetStart());
6577 uint32_t start
= iter
.GetSkippedOffset();
6578 iter
.AdvanceOriginal(aProvider
.GetOriginalLength());
6579 return gfxTextRun::Range(start
, iter
.GetSkippedOffset());
6582 bool nsTextFrame::MeasureCharClippedText(nscoord aVisIStartEdge
,
6583 nscoord aVisIEndEdge
,
6584 nscoord
* aSnappedStartEdge
,
6585 nscoord
* aSnappedEndEdge
) {
6586 // We need a *reference* rendering context (not one that might have a
6587 // transform), so we don't have a rendering context argument.
6588 // XXX get the block and line passed to us somehow! This is slow!
6589 gfxSkipCharsIterator iter
= EnsureTextRun(nsTextFrame::eInflated
);
6590 if (!mTextRun
) return false;
6592 PropertyProvider
provider(this, iter
, nsTextFrame::eInflated
, mFontMetrics
);
6593 // Trim trailing whitespace
6594 provider
.InitializeForDisplay(true);
6596 Range range
= ComputeTransformedRange(provider
);
6597 uint32_t startOffset
= range
.start
;
6598 uint32_t maxLength
= range
.Length();
6599 return MeasureCharClippedText(provider
, aVisIStartEdge
, aVisIEndEdge
,
6600 &startOffset
, &maxLength
, aSnappedStartEdge
,
6604 static uint32_t GetClusterLength(const gfxTextRun
* aTextRun
,
6605 uint32_t aStartOffset
, uint32_t aMaxLength
,
6607 uint32_t clusterLength
= aIsRTL
? 0 : 1;
6608 while (clusterLength
< aMaxLength
) {
6609 if (aTextRun
->IsClusterStart(aStartOffset
+ clusterLength
)) {
6617 return clusterLength
;
6620 bool nsTextFrame::MeasureCharClippedText(
6621 PropertyProvider
& aProvider
, nscoord aVisIStartEdge
, nscoord aVisIEndEdge
,
6622 uint32_t* aStartOffset
, uint32_t* aMaxLength
, nscoord
* aSnappedStartEdge
,
6623 nscoord
* aSnappedEndEdge
) {
6624 *aSnappedStartEdge
= 0;
6625 *aSnappedEndEdge
= 0;
6626 if (aVisIStartEdge
<= 0 && aVisIEndEdge
<= 0) {
6630 uint32_t offset
= *aStartOffset
;
6631 uint32_t maxLength
= *aMaxLength
;
6632 const nscoord frameISize
= ISize();
6633 const bool rtl
= mTextRun
->IsRightToLeft();
6634 gfxFloat advanceWidth
= 0;
6635 const nscoord startEdge
= rtl
? aVisIEndEdge
: aVisIStartEdge
;
6636 if (startEdge
> 0) {
6637 const gfxFloat maxAdvance
= gfxFloat(startEdge
);
6638 while (maxLength
> 0) {
6639 uint32_t clusterLength
=
6640 GetClusterLength(mTextRun
, offset
, maxLength
, rtl
);
6641 advanceWidth
+= mTextRun
->GetAdvanceWidth(
6642 Range(offset
, offset
+ clusterLength
), &aProvider
);
6643 maxLength
-= clusterLength
;
6644 offset
+= clusterLength
;
6645 if (advanceWidth
>= maxAdvance
) {
6649 nscoord
* snappedStartEdge
= rtl
? aSnappedEndEdge
: aSnappedStartEdge
;
6650 *snappedStartEdge
= NSToCoordFloor(advanceWidth
);
6651 *aStartOffset
= offset
;
6654 const nscoord endEdge
= rtl
? aVisIStartEdge
: aVisIEndEdge
;
6656 const gfxFloat maxAdvance
= gfxFloat(frameISize
- endEdge
);
6657 while (maxLength
> 0) {
6658 uint32_t clusterLength
=
6659 GetClusterLength(mTextRun
, offset
, maxLength
, rtl
);
6660 gfxFloat nextAdvance
=
6661 advanceWidth
+ mTextRun
->GetAdvanceWidth(
6662 Range(offset
, offset
+ clusterLength
), &aProvider
);
6663 if (nextAdvance
> maxAdvance
) {
6666 // This cluster fits, include it.
6667 advanceWidth
= nextAdvance
;
6668 maxLength
-= clusterLength
;
6669 offset
+= clusterLength
;
6671 maxLength
= offset
- *aStartOffset
;
6672 nscoord
* snappedEndEdge
= rtl
? aSnappedStartEdge
: aSnappedEndEdge
;
6673 *snappedEndEdge
= NSToCoordFloor(gfxFloat(frameISize
) - advanceWidth
);
6675 *aMaxLength
= maxLength
;
6676 return maxLength
!= 0;
6679 void nsTextFrame::PaintShadows(Span
<const StyleSimpleShadow
> aShadows
,
6680 const PaintShadowParams
& aParams
) {
6681 if (aShadows
.IsEmpty()) {
6685 gfxTextRun::Metrics shadowMetrics
= mTextRun
->MeasureText(
6686 aParams
.range
, gfxFont::LOOSE_INK_EXTENTS
, nullptr, aParams
.provider
);
6687 if (GetWritingMode().IsLineInverted()) {
6688 std::swap(shadowMetrics
.mAscent
, shadowMetrics
.mDescent
);
6689 shadowMetrics
.mBoundingBox
.y
= -shadowMetrics
.mBoundingBox
.YMost();
6691 if (HasAnyStateBits(TEXT_HYPHEN_BREAK
)) {
6692 AddHyphenToMetrics(this, mTextRun
, &shadowMetrics
,
6693 gfxFont::LOOSE_INK_EXTENTS
,
6694 aParams
.context
->GetDrawTarget());
6696 // Add bounds of text decorations
6697 gfxRect
decorationRect(0, -shadowMetrics
.mAscent
, shadowMetrics
.mAdvanceWidth
,
6698 shadowMetrics
.mAscent
+ shadowMetrics
.mDescent
);
6699 shadowMetrics
.mBoundingBox
.UnionRect(shadowMetrics
.mBoundingBox
,
6702 // If the textrun uses any color or SVG fonts, we need to force use of a mask
6703 // for shadow rendering even if blur radius is zero.
6704 // Force disable hardware acceleration for text shadows since it's usually
6705 // more expensive than just doing it on the CPU.
6706 uint32_t blurFlags
= nsContextBoxBlur::DISABLE_HARDWARE_ACCELERATION_BLUR
;
6707 uint32_t numGlyphRuns
;
6708 const gfxTextRun::GlyphRun
* run
= mTextRun
->GetGlyphRuns(&numGlyphRuns
);
6709 while (numGlyphRuns
-- > 0) {
6710 if (run
->mFont
->AlwaysNeedsMaskForShadow()) {
6711 blurFlags
|= nsContextBoxBlur::FORCE_MASK
;
6717 if (mTextRun
->IsVertical()) {
6718 std::swap(shadowMetrics
.mBoundingBox
.x
, shadowMetrics
.mBoundingBox
.y
);
6719 std::swap(shadowMetrics
.mBoundingBox
.width
,
6720 shadowMetrics
.mBoundingBox
.height
);
6723 for (const auto& shadow
: Reversed(aShadows
)) {
6724 PaintOneShadow(aParams
, shadow
, shadowMetrics
.mBoundingBox
, blurFlags
);
6728 void nsTextFrame::PaintText(const PaintTextParams
& aParams
,
6729 const nscoord aVisIStartEdge
,
6730 const nscoord aVisIEndEdge
,
6731 const nsPoint
& aToReferenceFrame
,
6732 const bool aIsSelected
,
6733 float aOpacity
/* = 1.0f */) {
6734 // Don't pass in the rendering context here, because we need a
6735 // *reference* context and rendering context might have some transform
6737 // XXX get the block and line passed to us somehow! This is slow!
6738 gfxSkipCharsIterator iter
= EnsureTextRun(nsTextFrame::eInflated
);
6739 if (!mTextRun
) return;
6741 PropertyProvider
provider(this, iter
, nsTextFrame::eInflated
, mFontMetrics
);
6743 // Trim trailing whitespace, unless we're painting a selection highlight,
6744 // which should include trailing spaces if present (bug 1146754).
6745 provider
.InitializeForDisplay(!aIsSelected
);
6747 const bool reversed
= mTextRun
->IsInlineReversed();
6748 const bool verticalRun
= mTextRun
->IsVertical();
6749 WritingMode wm
= GetWritingMode();
6750 const float frameWidth
= GetSize().width
;
6751 const float frameHeight
= GetSize().height
;
6752 gfx::Point textBaselinePt
;
6754 if (wm
.IsVerticalLR()) {
6755 textBaselinePt
.x
= nsLayoutUtils::GetSnappedBaselineX(
6756 this, aParams
.context
, nscoord(aParams
.framePt
.x
), mAscent
);
6758 textBaselinePt
.x
= nsLayoutUtils::GetSnappedBaselineX(
6759 this, aParams
.context
, nscoord(aParams
.framePt
.x
) + frameWidth
,
6763 reversed
? aParams
.framePt
.y
+ frameHeight
: aParams
.framePt
.y
;
6765 textBaselinePt
= gfx::Point(
6766 reversed
? aParams
.framePt
.x
+ frameWidth
: aParams
.framePt
.x
,
6767 nsLayoutUtils::GetSnappedBaselineY(this, aParams
.context
,
6768 aParams
.framePt
.y
, mAscent
));
6770 Range range
= ComputeTransformedRange(provider
);
6771 uint32_t startOffset
= range
.start
;
6772 uint32_t maxLength
= range
.Length();
6773 nscoord snappedStartEdge
, snappedEndEdge
;
6774 if (!MeasureCharClippedText(provider
, aVisIStartEdge
, aVisIEndEdge
,
6775 &startOffset
, &maxLength
, &snappedStartEdge
,
6780 textBaselinePt
.y
+= reversed
? -snappedEndEdge
: snappedStartEdge
;
6782 textBaselinePt
.x
+= reversed
? -snappedEndEdge
: snappedStartEdge
;
6784 nsDisplayText::ClipEdges
clipEdges(this, aToReferenceFrame
, snappedStartEdge
,
6786 nsTextPaintStyle
textPaintStyle(this);
6787 textPaintStyle
.SetResolveColors(!aParams
.callbacks
);
6789 // Fork off to the (slower) paint-with-selection path if necessary.
6791 MOZ_ASSERT(aOpacity
== 1.0f
, "We don't support opacity with selections!");
6792 gfxSkipCharsIterator
tmp(provider
.GetStart());
6794 uint32_t(tmp
.ConvertSkippedToOriginal(startOffset
)),
6795 uint32_t(tmp
.ConvertSkippedToOriginal(startOffset
+ maxLength
)));
6796 PaintTextSelectionParams
params(aParams
);
6797 params
.textBaselinePt
= textBaselinePt
;
6798 params
.provider
= &provider
;
6799 params
.contentRange
= contentRange
;
6800 params
.textPaintStyle
= &textPaintStyle
;
6801 params
.glyphRange
= range
;
6802 if (PaintTextWithSelection(params
, clipEdges
)) {
6807 nscolor foregroundColor
= aParams
.IsGenerateTextMask()
6808 ? NS_RGBA(0, 0, 0, 255)
6809 : textPaintStyle
.GetTextColor();
6810 if (aOpacity
!= 1.0f
) {
6811 gfx::sRGBColor gfxColor
= gfx::sRGBColor::FromABGR(foregroundColor
);
6812 gfxColor
.a
*= aOpacity
;
6813 foregroundColor
= gfxColor
.ToABGR();
6816 nscolor textStrokeColor
= aParams
.IsGenerateTextMask()
6817 ? NS_RGBA(0, 0, 0, 255)
6818 : textPaintStyle
.GetWebkitTextStrokeColor();
6819 if (aOpacity
!= 1.0f
) {
6820 gfx::sRGBColor gfxColor
= gfx::sRGBColor::FromABGR(textStrokeColor
);
6821 gfxColor
.a
*= aOpacity
;
6822 textStrokeColor
= gfxColor
.ToABGR();
6825 range
= Range(startOffset
, startOffset
+ maxLength
);
6826 if (!aParams
.callbacks
&& aParams
.IsPaintText()) {
6827 const nsStyleText
* textStyle
= StyleText();
6828 PaintShadowParams
shadowParams(aParams
);
6829 shadowParams
.range
= range
;
6830 shadowParams
.textBaselinePt
= textBaselinePt
;
6831 shadowParams
.leftSideOffset
= snappedStartEdge
;
6832 shadowParams
.provider
= &provider
;
6833 shadowParams
.foregroundColor
= foregroundColor
;
6834 shadowParams
.clipEdges
= &clipEdges
;
6835 PaintShadows(textStyle
->mTextShadow
.AsSpan(), shadowParams
);
6838 gfxFloat advanceWidth
;
6839 DrawTextParams
params(aParams
.context
);
6840 params
.dirtyRect
= aParams
.dirtyRect
;
6841 params
.framePt
= aParams
.framePt
;
6842 params
.provider
= &provider
;
6843 params
.advanceWidth
= &advanceWidth
;
6844 params
.textStyle
= &textPaintStyle
;
6845 params
.textColor
= foregroundColor
;
6846 params
.textStrokeColor
= textStrokeColor
;
6847 params
.textStrokeWidth
= textPaintStyle
.GetWebkitTextStrokeWidth();
6848 params
.clipEdges
= &clipEdges
;
6849 params
.drawSoftHyphen
= HasAnyStateBits(TEXT_HYPHEN_BREAK
);
6850 params
.contextPaint
= aParams
.contextPaint
;
6851 params
.callbacks
= aParams
.callbacks
;
6852 params
.glyphRange
= range
;
6853 DrawText(range
, textBaselinePt
, params
);
6856 static void DrawTextRun(const gfxTextRun
* aTextRun
,
6857 const gfx::Point
& aTextBaselinePt
,
6858 gfxTextRun::Range aRange
,
6859 const nsTextFrame::DrawTextRunParams
& aParams
,
6860 nsTextFrame
* aFrame
) {
6861 gfxTextRun::DrawParams
params(aParams
.context
);
6862 params
.provider
= aParams
.provider
;
6863 params
.advanceWidth
= aParams
.advanceWidth
;
6864 params
.contextPaint
= aParams
.contextPaint
;
6865 params
.callbacks
= aParams
.callbacks
;
6866 if (aParams
.callbacks
) {
6867 aParams
.callbacks
->NotifyBeforeText(aParams
.textColor
);
6868 params
.drawMode
= DrawMode::GLYPH_PATH
;
6869 aTextRun
->Draw(aRange
, aTextBaselinePt
, params
);
6870 aParams
.callbacks
->NotifyAfterText();
6872 auto* textDrawer
= aParams
.context
->GetTextDrawer();
6873 if (NS_GET_A(aParams
.textColor
) != 0 || textDrawer
||
6874 aParams
.textStrokeWidth
== 0.0f
) {
6875 aParams
.context
->SetColor(sRGBColor::FromABGR(aParams
.textColor
));
6877 params
.drawMode
= DrawMode::GLYPH_STROKE
;
6880 if ((NS_GET_A(aParams
.textStrokeColor
) != 0 || textDrawer
) &&
6881 aParams
.textStrokeWidth
!= 0.0f
) {
6883 textDrawer
->FoundUnsupportedFeature();
6886 params
.drawMode
|= DrawMode::GLYPH_STROKE
;
6888 // Check the paint-order property; if we find stroke before fill,
6889 // then change mode to GLYPH_STROKE_UNDERNEATH.
6890 uint32_t paintOrder
= aFrame
->StyleSVG()->mPaintOrder
;
6891 while (paintOrder
) {
6892 auto component
= StylePaintOrder(paintOrder
& kPaintOrderMask
);
6893 switch (component
) {
6894 case StylePaintOrder::Fill
:
6895 // Just break the loop, no need to check further
6898 case StylePaintOrder::Stroke
:
6899 params
.drawMode
|= DrawMode::GLYPH_STROKE_UNDERNEATH
;
6903 MOZ_FALLTHROUGH_ASSERT("Unknown paint-order variant, how?");
6904 case StylePaintOrder::Markers
:
6905 case StylePaintOrder::Normal
:
6908 paintOrder
>>= kPaintOrderShift
;
6911 // Use ROUND joins as they are less likely to produce ugly artifacts
6912 // when stroking glyphs with sharp angles (see bug 1546985).
6913 StrokeOptions
strokeOpts(aParams
.textStrokeWidth
, JoinStyle::ROUND
);
6914 params
.textStrokeColor
= aParams
.textStrokeColor
;
6915 params
.strokeOpts
= &strokeOpts
;
6916 aTextRun
->Draw(aRange
, aTextBaselinePt
, params
);
6918 aTextRun
->Draw(aRange
, aTextBaselinePt
, params
);
6923 void nsTextFrame::DrawTextRun(Range aRange
, const gfx::Point
& aTextBaselinePt
,
6924 const DrawTextRunParams
& aParams
) {
6925 MOZ_ASSERT(aParams
.advanceWidth
, "Must provide advanceWidth");
6927 ::DrawTextRun(mTextRun
, aTextBaselinePt
, aRange
, aParams
, this);
6929 if (aParams
.drawSoftHyphen
) {
6930 // Don't use ctx as the context, because we need a reference context here,
6931 // ctx may be transformed.
6932 RefPtr
<gfxTextRun
> hyphenTextRun
=
6933 GetHyphenTextRun(mTextRun
, nullptr, this);
6934 if (hyphenTextRun
) {
6935 // For right-to-left text runs, the soft-hyphen is positioned at the left
6936 // of the text, minus its own width
6937 float hyphenBaselineX
=
6939 mTextRun
->GetDirection() * (*aParams
.advanceWidth
) -
6940 (mTextRun
->IsRightToLeft() ? hyphenTextRun
->GetAdvanceWidth() : 0);
6941 DrawTextRunParams params
= aParams
;
6942 params
.provider
= nullptr;
6943 params
.advanceWidth
= nullptr;
6944 ::DrawTextRun(hyphenTextRun
.get(),
6945 gfx::Point(hyphenBaselineX
, aTextBaselinePt
.y
),
6946 Range(hyphenTextRun
.get()), params
, this);
6951 void nsTextFrame::DrawTextRunAndDecorations(
6952 Range aRange
, const gfx::Point
& aTextBaselinePt
,
6953 const DrawTextParams
& aParams
, const TextDecorations
& aDecorations
) {
6954 const gfxFloat app
= aParams
.textStyle
->PresContext()->AppUnitsPerDevPixel();
6955 // Writing mode of parent frame is used because the text frame may
6956 // be orthogonal to its parent when text-combine-upright is used or
6957 // its parent has "display: contents", and in those cases, we want
6958 // to draw the decoration lines according to parents' direction
6959 // rather than ours.
6960 const WritingMode wm
= GetParent()->GetWritingMode();
6961 bool verticalDec
= wm
.IsVertical();
6962 bool verticalRun
= mTextRun
->IsVertical();
6963 // If the text run and the decoration is orthogonal, we choose the
6964 // metrics for decoration so that decoration line won't be broken.
6965 bool useVerticalMetrics
= verticalDec
!= verticalRun
6967 : verticalRun
&& mTextRun
->UseCenterBaseline();
6969 // XXX aFramePt is in AppUnits, shouldn't it be nsFloatPoint?
6970 nscoord x
= NSToCoordRound(aParams
.framePt
.x
);
6971 nscoord y
= NSToCoordRound(aParams
.framePt
.y
);
6973 // 'measure' here is textrun-relative, so for a horizontal run it's the
6974 // width, while for a vertical run it's the height of the decoration
6975 const nsSize frameSize
= GetSize();
6976 nscoord measure
= verticalDec
? frameSize
.height
: frameSize
.width
;
6979 aParams
.clipEdges
->Intersect(&y
, &measure
);
6981 aParams
.clipEdges
->Intersect(&x
, &measure
);
6984 // decSize is a textrun-relative size, so its 'width' field is actually
6985 // the run-relative measure, and 'height' will be the line thickness
6986 gfxFloat ascent
= gfxFloat(GetLogicalBaseline(wm
)) / app
;
6987 // The starting edge of the frame in block direction
6988 gfxFloat frameBStart
= verticalDec
? aParams
.framePt
.x
: aParams
.framePt
.y
;
6990 // In vertical-rl mode, block coordinates are measured from the
6991 // right, so we need to adjust here.
6992 if (wm
.IsVerticalRL()) {
6993 frameBStart
+= frameSize
.width
;
6997 nscoord inflationMinFontSize
= nsLayoutUtils::InflationMinFontSizeFor(this);
6999 PaintDecorationLineParams params
;
7000 params
.context
= aParams
.context
;
7001 params
.dirtyRect
= aParams
.dirtyRect
;
7002 params
.overrideColor
= aParams
.decorationOverrideColor
;
7003 params
.callbacks
= aParams
.callbacks
;
7004 params
.glyphRange
= aParams
.glyphRange
;
7005 params
.provider
= aParams
.provider
;
7006 // pt is the physical point where the decoration is to be drawn,
7007 // relative to the frame; one of its coordinates will be updated below.
7008 params
.pt
= Point(x
/ app
, y
/ app
);
7009 Float
& bCoord
= verticalDec
? params
.pt
.x
: params
.pt
.y
;
7010 params
.lineSize
= Size(measure
/ app
, 0);
7011 params
.ascent
= ascent
;
7012 params
.vertical
= verticalDec
;
7013 params
.sidewaysLeft
= mTextRun
->IsSidewaysLeft();
7015 // The matrix of the context may have been altered for text-combine-
7016 // upright. However, we want to draw decoration lines unscaled, thus
7017 // we need to revert the scaling here.
7018 gfxContextMatrixAutoSaveRestore scaledRestorer
;
7019 if (Style()->IsTextCombined()) {
7020 float scaleFactor
= GetTextCombineScaleFactor(this);
7021 if (scaleFactor
!= 1.0f
) {
7022 scaledRestorer
.SetContext(aParams
.context
);
7023 gfxMatrix unscaled
= aParams
.context
->CurrentMatrixDouble();
7024 gfxPoint
pt(x
/ app
, y
/ app
);
7025 unscaled
.PreTranslate(pt
)
7026 .PreScale(1.0f
/ scaleFactor
, 1.0f
)
7028 aParams
.context
->SetMatrixDouble(unscaled
);
7032 typedef gfxFont::Metrics Metrics
;
7033 auto paintDecorationLine
= [&](const LineDecoration
& dec
,
7034 gfxFloat
Metrics::*lineSize
,
7035 mozilla::StyleTextDecorationLine lineType
) {
7036 if (dec
.mStyle
== NS_STYLE_TEXT_DECORATION_STYLE_NONE
) {
7041 GetInflationForTextDecorations(dec
.mFrame
, inflationMinFontSize
);
7042 const Metrics metrics
= GetFirstFontMetrics(
7043 GetFontGroupForFrame(dec
.mFrame
, inflation
), useVerticalMetrics
);
7045 bCoord
= (frameBStart
- dec
.mBaselineOffset
) / app
;
7047 params
.color
= dec
.mColor
;
7048 params
.baselineOffset
= dec
.mBaselineOffset
/ app
;
7049 params
.defaultLineThickness
= metrics
.*lineSize
;
7050 params
.lineSize
.height
= ComputeDecorationLineThickness(
7051 dec
.mTextDecorationThickness
, params
.defaultLineThickness
, metrics
,
7054 bool swapUnderline
= wm
.IsCentralBaseline() && IsUnderlineRight(*Style());
7055 params
.offset
= ComputeDecorationLineOffset(
7056 lineType
, dec
.mTextUnderlinePosition
, dec
.mTextUnderlineOffset
, metrics
,
7057 app
, wm
.IsCentralBaseline(), swapUnderline
);
7059 params
.style
= dec
.mStyle
;
7060 PaintDecorationLine(params
);
7063 // We create a clip region in order to draw the decoration lines only in the
7064 // range of the text. Restricting the draw area prevents the decoration lines
7065 // to be drawn multiple times when a part of the text is selected.
7067 // We skip clipping for the following cases:
7068 // - drawing the whole text
7069 // - having different orientation of the text and the writing-mode, such as
7070 // "text-combine-upright" (Bug 1408825)
7072 aRange
.Length() == mTextRun
->GetLength() || verticalDec
!= verticalRun
;
7075 if (!skipClipping
) {
7076 // Get the inline-size according to the specified range.
7077 gfxFloat clipLength
= mTextRun
->GetAdvanceWidth(aRange
, aParams
.provider
);
7078 nsRect visualRect
= InkOverflowRect();
7080 const bool isInlineReversed
= mTextRun
->IsInlineReversed();
7082 clipRect
.x
= aParams
.framePt
.x
+ visualRect
.x
;
7084 isInlineReversed
? aTextBaselinePt
.y
- clipLength
: aTextBaselinePt
.y
;
7085 clipRect
.width
= visualRect
.width
;
7086 clipRect
.height
= clipLength
;
7089 isInlineReversed
? aTextBaselinePt
.x
- clipLength
: aTextBaselinePt
.x
;
7090 clipRect
.y
= aParams
.framePt
.y
+ visualRect
.y
;
7091 clipRect
.width
= clipLength
;
7092 clipRect
.height
= visualRect
.height
;
7095 clipRect
.Scale(1 / app
);
7097 params
.context
->Clip(clipRect
);
7101 params
.decoration
= StyleTextDecorationLine::UNDERLINE
;
7102 for (const LineDecoration
& dec
: Reversed(aDecorations
.mUnderlines
)) {
7103 paintDecorationLine(dec
, &Metrics::underlineSize
, params
.decoration
);
7107 params
.decoration
= StyleTextDecorationLine::OVERLINE
;
7108 for (const LineDecoration
& dec
: Reversed(aDecorations
.mOverlines
)) {
7109 paintDecorationLine(dec
, &Metrics::underlineSize
, params
.decoration
);
7112 // Some glyphs and emphasis marks may extend outside the region, so we reset
7113 // the clip region here. For an example, italic glyphs.
7114 if (!skipClipping
) {
7115 params
.context
->PopClip();
7119 gfxContextMatrixAutoSaveRestore unscaledRestorer
;
7120 if (scaledRestorer
.HasMatrix()) {
7121 unscaledRestorer
.SetContext(aParams
.context
);
7122 aParams
.context
->SetMatrix(scaledRestorer
.Matrix());
7125 // CSS 2.1 mandates that text be painted after over/underlines,
7126 // and *then* line-throughs
7127 DrawTextRun(aRange
, aTextBaselinePt
, aParams
);
7131 DrawEmphasisMarks(aParams
.context
, wm
, aTextBaselinePt
, aParams
.framePt
,
7132 aRange
, aParams
.decorationOverrideColor
, aParams
.provider
);
7134 // Re-apply the clip region when the line-through is being drawn.
7135 if (!skipClipping
) {
7136 params
.context
->Clip(clipRect
);
7140 params
.decoration
= StyleTextDecorationLine::LINE_THROUGH
;
7141 for (const LineDecoration
& dec
: Reversed(aDecorations
.mStrikes
)) {
7142 paintDecorationLine(dec
, &Metrics::strikeoutSize
, params
.decoration
);
7145 if (!skipClipping
) {
7146 params
.context
->PopClip();
7150 void nsTextFrame::DrawText(Range aRange
, const gfx::Point
& aTextBaselinePt
,
7151 const DrawTextParams
& aParams
) {
7152 TextDecorations decorations
;
7153 GetTextDecorations(aParams
.textStyle
->PresContext(),
7154 aParams
.callbacks
? eUnresolvedColors
: eResolvedColors
,
7157 // Hide text decorations if we're currently hiding @font-face fallback text
7158 const bool drawDecorations
=
7159 !aParams
.provider
->GetFontGroup()->ShouldSkipDrawing() &&
7160 (decorations
.HasDecorationLines() ||
7161 StyleText()->HasEffectiveTextEmphasis());
7162 if (drawDecorations
) {
7163 DrawTextRunAndDecorations(aRange
, aTextBaselinePt
, aParams
, decorations
);
7165 DrawTextRun(aRange
, aTextBaselinePt
, aParams
);
7168 if (auto* textDrawer
= aParams
.context
->GetTextDrawer()) {
7169 textDrawer
->TerminateShadows();
7173 NS_DECLARE_FRAME_PROPERTY_DELETABLE(WebRenderTextBounds
, nsRect
)
7175 nsRect
nsTextFrame::WebRenderBounds() {
7176 nsRect
* cachedBounds
= GetProperty(WebRenderTextBounds());
7177 if (!cachedBounds
) {
7178 OverflowAreas overflowAreas
;
7179 ComputeCustomOverflowInternal(overflowAreas
, false);
7180 cachedBounds
= new nsRect();
7181 *cachedBounds
= overflowAreas
.InkOverflow();
7182 SetProperty(WebRenderTextBounds(), cachedBounds
);
7184 return *cachedBounds
;
7187 int16_t nsTextFrame::GetSelectionStatus(int16_t* aSelectionFlags
) {
7188 // get the selection controller
7189 nsCOMPtr
<nsISelectionController
> selectionController
;
7190 nsresult rv
= GetSelectionController(PresContext(),
7191 getter_AddRefs(selectionController
));
7192 if (NS_FAILED(rv
) || !selectionController
)
7193 return nsISelectionController::SELECTION_OFF
;
7195 selectionController
->GetSelectionFlags(aSelectionFlags
);
7197 int16_t selectionValue
;
7198 selectionController
->GetDisplaySelection(&selectionValue
);
7200 return selectionValue
;
7204 * Compute the longest prefix of text whose width is <= aWidth. Return
7205 * the length of the prefix. Also returns the width of the prefix in aFitWidth.
7207 static uint32_t CountCharsFit(const gfxTextRun
* aTextRun
,
7208 gfxTextRun::Range aRange
, gfxFloat aWidth
,
7209 nsTextFrame::PropertyProvider
* aProvider
,
7210 gfxFloat
* aFitWidth
) {
7213 for (uint32_t i
= 1; i
<= aRange
.Length(); ++i
) {
7214 if (i
== aRange
.Length() || aTextRun
->IsClusterStart(aRange
.start
+ i
)) {
7215 gfxTextRun::Range
range(aRange
.start
+ last
, aRange
.start
+ i
);
7216 gfxFloat nextWidth
= width
+ aTextRun
->GetAdvanceWidth(range
, aProvider
);
7217 if (nextWidth
> aWidth
) break;
7226 nsIFrame::ContentOffsets
nsTextFrame::CalcContentOffsetsFromFramePoint(
7227 const nsPoint
& aPoint
) {
7228 return GetCharacterOffsetAtFramePointInternal(aPoint
, true);
7231 nsIFrame::ContentOffsets
nsTextFrame::GetCharacterOffsetAtFramePoint(
7232 const nsPoint
& aPoint
) {
7233 return GetCharacterOffsetAtFramePointInternal(aPoint
, false);
7236 nsIFrame::ContentOffsets
nsTextFrame::GetCharacterOffsetAtFramePointInternal(
7237 const nsPoint
& aPoint
, bool aForInsertionPoint
) {
7238 ContentOffsets offsets
;
7240 gfxSkipCharsIterator iter
= EnsureTextRun(nsTextFrame::eInflated
);
7241 if (!mTextRun
) return offsets
;
7243 PropertyProvider
provider(this, iter
, nsTextFrame::eInflated
, mFontMetrics
);
7244 // Trim leading but not trailing whitespace if possible
7245 provider
.InitializeForDisplay(false);
7247 mTextRun
->IsVertical()
7248 ? (mTextRun
->IsInlineReversed() ? mRect
.height
- aPoint
.y
: aPoint
.y
)
7249 : (mTextRun
->IsInlineReversed() ? mRect
.width
- aPoint
.x
: aPoint
.x
);
7250 if (Style()->IsTextCombined()) {
7251 width
/= GetTextCombineScaleFactor(this);
7254 Range skippedRange
= ComputeTransformedRange(provider
);
7257 CountCharsFit(mTextRun
, skippedRange
, width
, &provider
, &fitWidth
);
7259 int32_t selectedOffset
;
7260 if (charsFit
< skippedRange
.Length()) {
7261 // charsFit characters fitted, but no more could fit. See if we're
7262 // more than halfway through the cluster.. If we are, choose the next
7264 gfxSkipCharsIterator
extraCluster(provider
.GetStart());
7265 extraCluster
.AdvanceSkipped(charsFit
);
7267 bool allowSplitLigature
= true; // Allow selection of partial ligature...
7269 // ...but don't let selection/insertion-point split two Regional Indicator
7270 // chars that are ligated in the textrun to form a single flag symbol.
7271 uint32_t offs
= extraCluster
.GetOriginalOffset();
7272 const nsTextFragment
* frag
= TextFragment();
7273 if (frag
->IsHighSurrogateFollowedByLowSurrogateAt(offs
) &&
7274 gfxFontUtils::IsRegionalIndicator(frag
->ScalarValueAt(offs
))) {
7275 allowSplitLigature
= false;
7276 if (extraCluster
.GetSkippedOffset() > 1 &&
7277 !mTextRun
->IsLigatureGroupStart(extraCluster
.GetSkippedOffset())) {
7278 // CountCharsFit() left us in the middle of the flag; back up over the
7279 // first character of the ligature, and adjust fitWidth accordingly.
7280 extraCluster
.AdvanceSkipped(-2); // it's a surrogate pair: 2 code units
7281 fitWidth
-= mTextRun
->GetAdvanceWidth(
7282 Range(extraCluster
.GetSkippedOffset(),
7283 extraCluster
.GetSkippedOffset() + 2),
7288 gfxSkipCharsIterator
extraClusterLastChar(extraCluster
);
7291 provider
.GetStart().GetOriginalOffset() + provider
.GetOriginalLength(),
7292 &extraClusterLastChar
, allowSplitLigature
);
7293 PropertyProvider::Spacing spacing
;
7294 Range
extraClusterRange(extraCluster
.GetSkippedOffset(),
7295 extraClusterLastChar
.GetSkippedOffset() + 1);
7296 gfxFloat charWidth
=
7297 mTextRun
->GetAdvanceWidth(extraClusterRange
, &provider
, &spacing
);
7298 charWidth
-= spacing
.mBefore
+ spacing
.mAfter
;
7299 selectedOffset
= !aForInsertionPoint
||
7300 width
<= fitWidth
+ spacing
.mBefore
+ charWidth
/ 2
7301 ? extraCluster
.GetOriginalOffset()
7302 : extraClusterLastChar
.GetOriginalOffset() + 1;
7304 // All characters fitted, we're at (or beyond) the end of the text.
7305 // XXX This could be some pathological situation where negative spacing
7306 // caused characters to move backwards. We can't really handle that
7307 // in the current frame system because frames can't have negative
7308 // intrinsic widths.
7310 provider
.GetStart().GetOriginalOffset() + provider
.GetOriginalLength();
7311 // If we're at the end of a preformatted line which has a terminating
7312 // linefeed, we want to reduce the offset by one to make sure that the
7313 // selection is placed before the linefeed character.
7314 if (HasSignificantTerminalNewline()) {
7319 offsets
.content
= GetContent();
7320 offsets
.offset
= offsets
.secondaryOffset
= selectedOffset
;
7321 offsets
.associate
= mContentOffset
== offsets
.offset
? CARET_ASSOCIATE_AFTER
7322 : CARET_ASSOCIATE_BEFORE
;
7326 bool nsTextFrame::CombineSelectionUnderlineRect(nsPresContext
* aPresContext
,
7328 if (aRect
.IsEmpty()) return false;
7330 nsRect givenRect
= aRect
;
7332 gfxFontGroup
* fontGroup
= GetInflatedFontGroupForFrame(this);
7333 gfxFont
* firstFont
= fontGroup
->GetFirstValidFont();
7334 WritingMode wm
= GetWritingMode();
7335 bool verticalRun
= wm
.IsVertical();
7336 bool useVerticalMetrics
= verticalRun
&& !wm
.IsSideways();
7337 const gfxFont::Metrics
& metrics
=
7338 firstFont
->GetMetrics(useVerticalMetrics
? nsFontMetrics::eVertical
7339 : nsFontMetrics::eHorizontal
);
7341 nsCSSRendering::DecorationRectParams params
;
7342 params
.ascent
= aPresContext
->AppUnitsToGfxUnits(mAscent
);
7344 params
.offset
= fontGroup
->GetUnderlineOffset();
7346 TextDecorations textDecs
;
7347 GetTextDecorations(aPresContext
, eResolvedColors
, textDecs
);
7349 params
.descentLimit
=
7350 ComputeDescentLimitForSelectionUnderline(aPresContext
, metrics
);
7351 params
.vertical
= verticalRun
;
7353 EnsureTextRun(nsTextFrame::eInflated
);
7354 params
.sidewaysLeft
= mTextRun
? mTextRun
->IsSidewaysLeft() : false;
7356 UniquePtr
<SelectionDetails
> details
= GetSelectionDetails();
7357 for (SelectionDetails
* sd
= details
.get(); sd
; sd
= sd
->mNext
.get()) {
7358 if (sd
->mStart
== sd
->mEnd
||
7359 sd
->mSelectionType
== SelectionType::eInvalid
||
7360 !(ToSelectionTypeMask(sd
->mSelectionType
) &
7361 kSelectionTypesWithDecorations
) ||
7362 // URL strikeout does not use underline.
7363 sd
->mSelectionType
== SelectionType::eURLStrikeout
) {
7368 int32_t index
= nsTextPaintStyle::GetUnderlineStyleIndexForSelectionType(
7369 sd
->mSelectionType
);
7370 if (sd
->mSelectionType
== SelectionType::eSpellCheck
) {
7371 if (!nsTextPaintStyle::GetSelectionUnderline(
7372 aPresContext
, index
, nullptr, &relativeSize
, ¶ms
.style
)) {
7377 TextRangeStyle
& rangeStyle
= sd
->mTextRangeStyle
;
7378 if (rangeStyle
.IsDefined()) {
7379 if (!rangeStyle
.IsLineStyleDefined() ||
7380 rangeStyle
.mLineStyle
== TextRangeStyle::LineStyle::None
) {
7383 params
.style
= ToStyleLineStyle(rangeStyle
);
7384 relativeSize
= rangeStyle
.mIsBoldLine
? 2.0f
: 1.0f
;
7385 } else if (!nsTextPaintStyle::GetSelectionUnderline(
7386 aPresContext
, index
, nullptr, &relativeSize
,
7391 nsRect decorationArea
;
7393 const auto& decThickness
= StyleTextReset()->mTextDecorationThickness
;
7394 params
.lineSize
.width
= aPresContext
->AppUnitsToGfxUnits(aRect
.width
);
7395 params
.defaultLineThickness
= ComputeSelectionUnderlineHeight(
7396 aPresContext
, metrics
, sd
->mSelectionType
);
7398 params
.lineSize
.height
= ComputeDecorationLineThickness(
7399 decThickness
, params
.defaultLineThickness
, metrics
,
7400 aPresContext
->AppUnitsPerDevPixel());
7402 bool swapUnderline
= wm
.IsCentralBaseline() && IsUnderlineRight(*Style());
7403 const auto* styleText
= StyleText();
7404 params
.offset
= ComputeDecorationLineOffset(
7405 textDecs
.HasUnderline() ? StyleTextDecorationLine::UNDERLINE
7406 : StyleTextDecorationLine::OVERLINE
,
7407 styleText
->mTextUnderlinePosition
, styleText
->mTextUnderlineOffset
,
7408 metrics
, aPresContext
->AppUnitsPerDevPixel(), wm
.IsCentralBaseline(),
7411 relativeSize
= std::max(relativeSize
, 1.0f
);
7412 params
.lineSize
.height
*= relativeSize
;
7413 params
.defaultLineThickness
*= relativeSize
;
7415 nsCSSRendering::GetTextDecorationRect(aPresContext
, params
);
7416 aRect
.UnionRect(aRect
, decorationArea
);
7419 return !aRect
.IsEmpty() && !givenRect
.Contains(aRect
);
7422 bool nsTextFrame::IsFrameSelected() const {
7423 NS_ASSERTION(!GetContent() || GetContent()->IsMaybeSelected(),
7424 "use the public IsSelected() instead");
7425 return GetContent()->IsSelected(GetContentOffset(), GetContentEnd());
7428 void nsTextFrame::SelectionStateChanged(uint32_t aStart
, uint32_t aEnd
,
7430 SelectionType aSelectionType
) {
7431 NS_ASSERTION(!GetPrevContinuation(),
7432 "Should only be called for primary frame");
7433 DEBUG_VERIFY_NOT_DIRTY(mState
);
7435 // Selection is collapsed, which can't affect text frame rendering
7436 if (aStart
== aEnd
) return;
7438 nsTextFrame
* f
= this;
7439 while (f
&& f
->GetContentEnd() <= int32_t(aStart
)) {
7440 f
= f
->GetNextContinuation();
7443 nsPresContext
* presContext
= PresContext();
7444 while (f
&& f
->GetContentOffset() < int32_t(aEnd
)) {
7445 // We may need to reflow to recompute the overflow area for
7446 // spellchecking or IME underline if their underline is thicker than
7447 // the normal decoration line.
7448 if (ToSelectionTypeMask(aSelectionType
) & kSelectionTypesWithDecorations
) {
7449 bool didHaveOverflowingSelection
=
7450 f
->HasAnyStateBits(TEXT_SELECTION_UNDERLINE_OVERFLOWED
);
7451 nsRect
r(nsPoint(0, 0), GetSize());
7452 if (didHaveOverflowingSelection
||
7453 (aSelected
&& f
->CombineSelectionUnderlineRect(presContext
, r
))) {
7454 presContext
->PresShell()->FrameNeedsReflow(
7455 f
, IntrinsicDirty::StyleChange
, NS_FRAME_IS_DIRTY
);
7458 // Selection might change anything. Invalidate the overflow area.
7459 f
->InvalidateFrame();
7461 f
= f
->GetNextContinuation();
7465 void nsTextFrame::UpdateIteratorFromOffset(const PropertyProvider
& aProperties
,
7467 gfxSkipCharsIterator
& aIter
) {
7468 if (aInOffset
< GetContentOffset()) {
7469 NS_WARNING("offset before this frame's content");
7470 aInOffset
= GetContentOffset();
7471 } else if (aInOffset
> GetContentEnd()) {
7472 NS_WARNING("offset after this frame's content");
7473 aInOffset
= GetContentEnd();
7476 int32_t trimmedOffset
= aProperties
.GetStart().GetOriginalOffset();
7477 int32_t trimmedEnd
= trimmedOffset
+ aProperties
.GetOriginalLength();
7478 aInOffset
= std::max(aInOffset
, trimmedOffset
);
7479 aInOffset
= std::min(aInOffset
, trimmedEnd
);
7481 aIter
.SetOriginalOffset(aInOffset
);
7483 if (aInOffset
< trimmedEnd
&& !aIter
.IsOriginalCharSkipped() &&
7484 !mTextRun
->IsClusterStart(aIter
.GetSkippedOffset())) {
7485 // Called for non-cluster boundary
7486 FindClusterStart(mTextRun
, trimmedOffset
, &aIter
);
7490 nsPoint
nsTextFrame::GetPointFromIterator(const gfxSkipCharsIterator
& aIter
,
7491 PropertyProvider
& aProperties
) {
7492 Range
range(aProperties
.GetStart().GetSkippedOffset(),
7493 aIter
.GetSkippedOffset());
7494 gfxFloat advance
= mTextRun
->GetAdvanceWidth(range
, &aProperties
);
7495 nscoord iSize
= NSToCoordCeilClamped(advance
);
7498 if (mTextRun
->IsVertical()) {
7500 if (mTextRun
->IsInlineReversed()) {
7501 point
.y
= mRect
.height
- iSize
;
7507 if (mTextRun
->IsInlineReversed()) {
7508 point
.x
= mRect
.width
- iSize
;
7512 if (Style()->IsTextCombined()) {
7513 point
.x
*= GetTextCombineScaleFactor(this);
7519 nsresult
nsTextFrame::GetPointFromOffset(int32_t inOffset
, nsPoint
* outPoint
) {
7520 if (!outPoint
) return NS_ERROR_NULL_POINTER
;
7522 DEBUG_VERIFY_NOT_DIRTY(mState
);
7523 if (mState
& NS_FRAME_IS_DIRTY
) return NS_ERROR_UNEXPECTED
;
7525 if (GetContentLength() <= 0) {
7531 gfxSkipCharsIterator iter
= EnsureTextRun(nsTextFrame::eInflated
);
7532 if (!mTextRun
) return NS_ERROR_FAILURE
;
7534 PropertyProvider
properties(this, iter
, nsTextFrame::eInflated
, mFontMetrics
);
7535 // Don't trim trailing whitespace, we want the caret to appear in the right
7536 // place if it's positioned there
7537 properties
.InitializeForDisplay(false);
7539 UpdateIteratorFromOffset(properties
, inOffset
, iter
);
7541 *outPoint
= GetPointFromIterator(iter
, properties
);
7546 nsresult
nsTextFrame::GetCharacterRectsInRange(int32_t aInOffset
,
7548 nsTArray
<nsRect
>& aRects
) {
7549 DEBUG_VERIFY_NOT_DIRTY(mState
);
7550 if (mState
& NS_FRAME_IS_DIRTY
) {
7551 return NS_ERROR_UNEXPECTED
;
7554 if (GetContentLength() <= 0) {
7559 return NS_ERROR_FAILURE
;
7562 gfxSkipCharsIterator iter
= EnsureTextRun(nsTextFrame::eInflated
);
7563 PropertyProvider
properties(this, iter
, nsTextFrame::eInflated
, mFontMetrics
);
7564 // Don't trim trailing whitespace, we want the caret to appear in the right
7565 // place if it's positioned there
7566 properties
.InitializeForDisplay(false);
7568 UpdateIteratorFromOffset(properties
, aInOffset
, iter
);
7570 const int32_t kContentEnd
= GetContentEnd();
7571 const int32_t kEndOffset
= std::min(aInOffset
+ aLength
, kContentEnd
);
7572 while (aInOffset
< kEndOffset
) {
7573 if (!iter
.IsOriginalCharSkipped() &&
7574 !mTextRun
->IsClusterStart(iter
.GetSkippedOffset())) {
7575 FindClusterStart(mTextRun
,
7576 properties
.GetStart().GetOriginalOffset() +
7577 properties
.GetOriginalLength(),
7581 nsPoint point
= GetPointFromIterator(iter
, properties
);
7587 if (aInOffset
< kContentEnd
) {
7588 gfxSkipCharsIterator
nextIter(iter
);
7589 nextIter
.AdvanceOriginal(1);
7590 if (!nextIter
.IsOriginalCharSkipped() &&
7591 !mTextRun
->IsClusterStart(nextIter
.GetSkippedOffset()) &&
7592 nextIter
.GetOriginalOffset() < kContentEnd
) {
7593 FindClusterEnd(mTextRun
, kContentEnd
, &nextIter
);
7596 gfxFloat advance
= mTextRun
->GetAdvanceWidth(
7597 Range(iter
.GetSkippedOffset(), nextIter
.GetSkippedOffset()),
7599 iSize
= NSToCoordCeilClamped(advance
);
7602 if (mTextRun
->IsVertical()) {
7603 rect
.width
= mRect
.width
;
7604 rect
.height
= iSize
;
7607 rect
.height
= mRect
.height
;
7609 if (Style()->IsTextCombined()) {
7610 rect
.width
*= GetTextCombineScaleFactor(this);
7613 aRects
.AppendElement(rect
);
7615 // Don't advance iter if we've reached the end
7616 if (aInOffset
< kEndOffset
) {
7617 iter
.AdvanceOriginal(1);
7624 nsresult
nsTextFrame::GetChildFrameContainingOffset(int32_t aContentOffset
,
7626 int32_t* aOutOffset
,
7627 nsIFrame
** aOutFrame
) {
7628 DEBUG_VERIFY_NOT_DIRTY(mState
);
7629 #if 0 // XXXrbs disable due to bug 310227
7630 if (mState
& NS_FRAME_IS_DIRTY
)
7631 return NS_ERROR_UNEXPECTED
;
7634 NS_ASSERTION(aOutOffset
&& aOutFrame
, "Bad out parameters");
7635 NS_ASSERTION(aContentOffset
>= 0,
7636 "Negative content offset, existing code was very broken!");
7637 nsIFrame
* primaryFrame
= mContent
->GetPrimaryFrame();
7638 if (this != primaryFrame
) {
7639 // This call needs to happen on the primary frame
7640 return primaryFrame
->GetChildFrameContainingOffset(aContentOffset
, aHint
,
7641 aOutOffset
, aOutFrame
);
7644 nsTextFrame
* f
= this;
7645 int32_t offset
= mContentOffset
;
7647 // Try to look up the offset to frame property
7648 nsTextFrame
* cachedFrame
= GetProperty(OffsetToFrameProperty());
7652 offset
= f
->GetContentOffset();
7654 f
->RemoveStateBits(TEXT_IN_OFFSET_CACHE
);
7657 if ((aContentOffset
>= offset
) && (aHint
|| aContentOffset
!= offset
)) {
7659 nsTextFrame
* next
= f
->GetNextContinuation();
7660 if (!next
|| aContentOffset
< next
->GetContentOffset()) break;
7661 if (aContentOffset
== next
->GetContentOffset()) {
7664 if (f
->GetContentLength() == 0) {
7665 continue; // use the last of the empty frames with this offset
7674 nsTextFrame
* prev
= f
->GetPrevContinuation();
7675 if (!prev
|| aContentOffset
> f
->GetContentOffset()) break;
7676 if (aContentOffset
== f
->GetContentOffset()) {
7679 if (f
->GetContentLength() == 0) {
7680 continue; // use the first of the empty frames with this offset
7689 *aOutOffset
= aContentOffset
- f
->GetContentOffset();
7692 // cache the frame we found
7693 SetProperty(OffsetToFrameProperty(), f
);
7694 f
->AddStateBits(TEXT_IN_OFFSET_CACHE
);
7699 nsIFrame::FrameSearchResult
nsTextFrame::PeekOffsetNoAmount(bool aForward
,
7701 NS_ASSERTION(aOffset
&& *aOffset
<= GetContentLength(),
7702 "aOffset out of range");
7704 gfxSkipCharsIterator iter
= EnsureTextRun(nsTextFrame::eInflated
);
7705 if (!mTextRun
) return CONTINUE_EMPTY
;
7707 TrimmedOffsets trimmed
= GetTrimmedOffsets(TextFragment());
7708 // Check whether there are nonskipped characters in the trimmmed range
7709 return (iter
.ConvertOriginalToSkipped(trimmed
.GetEnd()) >
7710 iter
.ConvertOriginalToSkipped(trimmed
.mStart
))
7716 * This class iterates through the clusters before or after the given
7717 * aPosition (which is a content offset). You can test each cluster
7718 * to see if it's whitespace (as far as selection/caret movement is concerned),
7719 * or punctuation, or if there is a word break before the cluster. ("Before"
7720 * is interpreted according to aDirection, so if aDirection is -1, "before"
7721 * means actually *after* the cluster content.)
7723 class MOZ_STACK_CLASS ClusterIterator
{
7725 ClusterIterator(nsTextFrame
* aTextFrame
, int32_t aPosition
,
7726 int32_t aDirection
, nsString
& aContext
,
7727 bool aTrimSpaces
= true);
7730 bool IsInlineWhitespace() const;
7731 bool IsNewline() const;
7732 bool IsPunctuation() const;
7733 bool HaveWordBreakBefore() const { return mHaveWordBreak
; }
7735 // Get the charIndex that corresponds to the "before" side of the current
7736 // character, according to the direction of iteration: so for a forward
7737 // iterator, this is simply mCharIndex, while for a reverse iterator it will
7738 // be mCharIndex + <number of code units in the character>.
7739 int32_t GetBeforeOffset() const {
7740 MOZ_ASSERT(mCharIndex
>= 0);
7741 return mDirection
< 0 ? GetAfterInternal() : mCharIndex
;
7743 // Get the charIndex that corresponds to the "before" side of the current
7744 // character, according to the direction of iteration: the opposite side
7745 // to what GetBeforeOffset returns.
7746 int32_t GetAfterOffset() const {
7747 MOZ_ASSERT(mCharIndex
>= 0);
7748 return mDirection
> 0 ? GetAfterInternal() : mCharIndex
;
7752 // Helper for Get{After,Before}Offset; returns the charIndex after the
7753 // current position in the text, accounting for surrogate pairs.
7754 int32_t GetAfterInternal() const;
7756 gfxSkipCharsIterator mIterator
;
7757 // Usually, mFrag is pointer to `dom::CharacterData::mText`. However, if
7758 // we're in a password field, this points `mMaskedFrag`.
7759 const nsTextFragment
* mFrag
;
7760 // If we're in a password field, this is initialized with mask characters.
7761 nsTextFragment mMaskedFrag
;
7762 nsTextFrame
* mTextFrame
;
7763 int32_t mDirection
; // +1 or -1, or 0 to indicate failure
7765 nsTextFrame::TrimmedOffsets mTrimmed
;
7766 nsTArray
<bool> mWordBreaks
;
7767 bool mHaveWordBreak
;
7770 static bool IsAcceptableCaretPosition(const gfxSkipCharsIterator
& aIter
,
7771 bool aRespectClusters
,
7772 const gfxTextRun
* aTextRun
,
7773 nsTextFrame
* aFrame
) {
7774 if (aIter
.IsOriginalCharSkipped()) return false;
7775 uint32_t index
= aIter
.GetSkippedOffset();
7776 if (aRespectClusters
&& !aTextRun
->IsClusterStart(index
)) return false;
7778 // Check whether the proposed position is in between the two halves of a
7779 // surrogate pair, before a Variation Selector character, or within a
7780 // ligated emoji sequence; if so, this is not a valid character boundary.
7781 // (In the case where we are respecting clusters, we won't actually get
7782 // this far because the low surrogate is also marked as non-clusterStart
7783 // so we'll return FALSE above.)
7784 uint32_t offs
= aIter
.GetOriginalOffset();
7785 const nsTextFragment
* frag
= aFrame
->TextFragment();
7786 uint32_t ch
= frag
->CharAt(offs
);
7788 if (gfxFontUtils::IsVarSelector(ch
) ||
7789 frag
->IsLowSurrogateFollowingHighSurrogateAt(offs
) ||
7790 (!aTextRun
->IsLigatureGroupStart(index
) &&
7791 (unicode::GetEmojiPresentation(ch
) == unicode::EmojiDefault
||
7792 (unicode::GetEmojiPresentation(ch
) == unicode::TextDefault
&&
7793 offs
+ 1 < frag
->GetLength() &&
7794 frag
->CharAt(offs
+ 1) == gfxFontUtils::kUnicodeVS16
)))) {
7798 // If the proposed position is before a high surrogate, we need to decode
7799 // the surrogate pair (if valid) and check the resulting character.
7800 if (NS_IS_HIGH_SURROGATE(ch
)) {
7801 if (char32_t ucs4
= frag
->ScalarValueAt(offs
)) {
7802 // If the character is a (Plane-14) variation selector,
7803 // or an emoji character that is ligated with the previous
7804 // character (i.e. part of a Regional-Indicator flag pair,
7805 // or an emoji-ZWJ sequence), this is not a valid boundary.
7806 if (gfxFontUtils::IsVarSelector(ucs4
) ||
7807 (!aTextRun
->IsLigatureGroupStart(index
) &&
7808 unicode::GetEmojiPresentation(ucs4
) == unicode::EmojiDefault
)) {
7817 nsIFrame::FrameSearchResult
nsTextFrame::PeekOffsetCharacter(
7818 bool aForward
, int32_t* aOffset
, PeekOffsetCharacterOptions aOptions
) {
7819 int32_t contentLength
= GetContentLength();
7820 NS_ASSERTION(aOffset
&& *aOffset
<= contentLength
, "aOffset out of range");
7822 if (!aOptions
.mIgnoreUserStyleAll
) {
7823 StyleUserSelect selectStyle
;
7824 Unused
<< IsSelectable(&selectStyle
);
7825 if (selectStyle
== StyleUserSelect::All
) {
7826 return CONTINUE_UNSELECTABLE
;
7830 gfxSkipCharsIterator iter
= EnsureTextRun(nsTextFrame::eInflated
);
7831 if (!mTextRun
) return CONTINUE_EMPTY
;
7833 TrimmedOffsets trimmed
=
7834 GetTrimmedOffsets(TextFragment(), TrimmedOffsetFlags::NoTrimAfter
);
7836 // A negative offset means "end of frame".
7837 int32_t startOffset
=
7838 GetContentOffset() + (*aOffset
< 0 ? contentLength
: *aOffset
);
7841 // If at the beginning of the line, look at the previous continuation
7842 for (int32_t i
= std::min(trimmed
.GetEnd(), startOffset
) - 1;
7843 i
>= trimmed
.mStart
; --i
) {
7844 iter
.SetOriginalOffset(i
);
7845 if (IsAcceptableCaretPosition(iter
, aOptions
.mRespectClusters
, mTextRun
,
7847 *aOffset
= i
- mContentOffset
;
7853 // If we're at the end of a line, look at the next continuation
7854 iter
.SetOriginalOffset(startOffset
);
7855 if (startOffset
<= trimmed
.GetEnd() &&
7856 !(startOffset
< trimmed
.GetEnd() &&
7857 StyleText()->NewlineIsSignificant(this) &&
7858 iter
.GetSkippedOffset() < mTextRun
->GetLength() &&
7859 mTextRun
->CharIsNewline(iter
.GetSkippedOffset()))) {
7860 for (int32_t i
= startOffset
+ 1; i
<= trimmed
.GetEnd(); ++i
) {
7861 iter
.SetOriginalOffset(i
);
7862 if (i
== trimmed
.GetEnd() ||
7863 IsAcceptableCaretPosition(iter
, aOptions
.mRespectClusters
, mTextRun
,
7865 *aOffset
= i
- mContentOffset
;
7870 *aOffset
= contentLength
;
7876 bool ClusterIterator::IsInlineWhitespace() const {
7877 NS_ASSERTION(mCharIndex
>= 0, "No cluster selected");
7878 return IsSelectionInlineWhitespace(mFrag
, mCharIndex
);
7881 bool ClusterIterator::IsNewline() const {
7882 NS_ASSERTION(mCharIndex
>= 0, "No cluster selected");
7883 return IsSelectionNewline(mFrag
, mCharIndex
);
7886 bool ClusterIterator::IsPunctuation() const {
7887 NS_ASSERTION(mCharIndex
>= 0, "No cluster selected");
7888 // Return true for all Punctuation categories (Unicode general category P?),
7889 // and also for Symbol categories (S?) except for Modifier Symbol, which is
7890 // kept together with any adjacent letter/number. (Bug 1066756)
7891 uint32_t ch
= mFrag
->CharAt(mCharIndex
);
7892 uint8_t cat
= unicode::GetGeneralCategory(ch
);
7894 case HB_UNICODE_GENERAL_CATEGORY_CONNECT_PUNCTUATION
: /* Pc */
7895 if (ch
== '_' && !StaticPrefs::layout_word_select_stop_at_underscore()) {
7899 case HB_UNICODE_GENERAL_CATEGORY_DASH_PUNCTUATION
: /* Pd */
7900 case HB_UNICODE_GENERAL_CATEGORY_CLOSE_PUNCTUATION
: /* Pe */
7901 case HB_UNICODE_GENERAL_CATEGORY_FINAL_PUNCTUATION
: /* Pf */
7902 case HB_UNICODE_GENERAL_CATEGORY_INITIAL_PUNCTUATION
: /* Pi */
7903 case HB_UNICODE_GENERAL_CATEGORY_OTHER_PUNCTUATION
: /* Po */
7904 case HB_UNICODE_GENERAL_CATEGORY_OPEN_PUNCTUATION
: /* Ps */
7905 case HB_UNICODE_GENERAL_CATEGORY_CURRENCY_SYMBOL
: /* Sc */
7906 // Deliberately omitted:
7907 // case HB_UNICODE_GENERAL_CATEGORY_MODIFIER_SYMBOL: /* Sk */
7908 case HB_UNICODE_GENERAL_CATEGORY_MATH_SYMBOL
: /* Sm */
7909 case HB_UNICODE_GENERAL_CATEGORY_OTHER_SYMBOL
: /* So */
7916 int32_t ClusterIterator::GetAfterInternal() const {
7917 if (mFrag
->IsHighSurrogateFollowedByLowSurrogateAt(mCharIndex
)) {
7918 return mCharIndex
+ 2;
7920 return mCharIndex
+ 1;
7923 bool ClusterIterator::NextCluster() {
7924 if (!mDirection
) return false;
7925 const gfxTextRun
* textRun
= mTextFrame
->GetTextRun(nsTextFrame::eInflated
);
7927 mHaveWordBreak
= false;
7929 bool keepGoing
= false;
7930 if (mDirection
> 0) {
7931 if (mIterator
.GetOriginalOffset() >= mTrimmed
.GetEnd()) return false;
7932 keepGoing
= mIterator
.IsOriginalCharSkipped() ||
7933 mIterator
.GetOriginalOffset() < mTrimmed
.mStart
||
7934 !textRun
->IsClusterStart(mIterator
.GetSkippedOffset());
7935 mCharIndex
= mIterator
.GetOriginalOffset();
7936 mIterator
.AdvanceOriginal(1);
7938 if (mIterator
.GetOriginalOffset() <= mTrimmed
.mStart
) {
7939 // Trimming can skip backward word breakers, see bug 1667138
7940 return mHaveWordBreak
;
7942 mIterator
.AdvanceOriginal(-1);
7943 keepGoing
= mIterator
.IsOriginalCharSkipped() ||
7944 mIterator
.GetOriginalOffset() >= mTrimmed
.GetEnd() ||
7945 !textRun
->IsClusterStart(mIterator
.GetSkippedOffset());
7946 mCharIndex
= mIterator
.GetOriginalOffset();
7949 if (mWordBreaks
[GetBeforeOffset() - mTextFrame
->GetContentOffset()]) {
7950 mHaveWordBreak
= true;
7952 if (!keepGoing
) return true;
7956 ClusterIterator::ClusterIterator(nsTextFrame
* aTextFrame
, int32_t aPosition
,
7957 int32_t aDirection
, nsString
& aContext
,
7959 : mTextFrame(aTextFrame
),
7960 mDirection(aDirection
),
7962 mHaveWordBreak(false) {
7963 mIterator
= aTextFrame
->EnsureTextRun(nsTextFrame::eInflated
);
7964 gfxTextRun
* textRun
= aTextFrame
->GetTextRun(nsTextFrame::eInflated
);
7966 mDirection
= 0; // signal failure
7970 mFrag
= aTextFrame
->TextFragment();
7971 // If we're in a password field, some characters may be masked. In such
7972 // case, we need to treat each masked character is a mask character since
7973 // we shouldn't expose word boundary which is hidden by the masking.
7974 if (aTextFrame
->GetContent() && mFrag
->GetLength() > 0 &&
7975 aTextFrame
->GetContent()->HasFlag(NS_MAYBE_MASKED
) &&
7976 (textRun
->GetFlags2() & nsTextFrameUtils::Flags::IsTransformed
)) {
7977 const char16_t kPasswordMask
= TextEditor::PasswordMask();
7978 const nsTransformedTextRun
* transformedTextRun
=
7979 static_cast<const nsTransformedTextRun
*>(textRun
);
7980 // Use nsString and not nsAutoString so that we get a nsStringBuffer which
7981 // can be just AddRefed in `mMaskedFrag`.
7982 nsString maskedText
;
7983 maskedText
.SetCapacity(mFrag
->GetLength());
7984 for (uint32_t i
= 0; i
< mFrag
->GetLength(); ++i
) {
7985 mIterator
.SetOriginalOffset(i
);
7986 uint32_t skippedOffset
= mIterator
.GetSkippedOffset();
7987 if (mFrag
->IsHighSurrogateFollowedByLowSurrogateAt(i
)) {
7988 if (transformedTextRun
->mStyles
[skippedOffset
]->mMaskPassword
) {
7989 maskedText
.Append(kPasswordMask
);
7990 maskedText
.Append(kPasswordMask
);
7992 maskedText
.Append(mFrag
->CharAt(i
));
7993 maskedText
.Append(mFrag
->CharAt(i
+ 1));
7998 transformedTextRun
->mStyles
[skippedOffset
]->mMaskPassword
8000 : mFrag
->CharAt(i
));
8003 mMaskedFrag
.SetTo(maskedText
, mFrag
->IsBidi(), true);
8004 mFrag
= &mMaskedFrag
;
8007 mIterator
.SetOriginalOffset(aPosition
);
8008 mTrimmed
= aTextFrame
->GetTrimmedOffsets(
8009 mFrag
, aTrimSpaces
? nsTextFrame::TrimmedOffsetFlags::Default
8010 : nsTextFrame::TrimmedOffsetFlags::NoTrimAfter
|
8011 nsTextFrame::TrimmedOffsetFlags::NoTrimBefore
);
8013 int32_t textOffset
= aTextFrame
->GetContentOffset();
8014 int32_t textLen
= aTextFrame
->GetContentLength();
8015 // XXX(Bug 1631371) Check if this should use a fallible operation as it
8016 // pretended earlier.
8017 mWordBreaks
.AppendElements(textLen
+ 1);
8018 memset(mWordBreaks
.Elements(), false, (textLen
+ 1) * sizeof(bool));
8020 if (aDirection
> 0) {
8021 if (aContext
.IsEmpty()) {
8022 // No previous context, so it must be the start of a line or text run
8023 mWordBreaks
[0] = true;
8025 textStart
= aContext
.Length();
8026 mFrag
->AppendTo(aContext
, textOffset
, textLen
);
8028 if (aContext
.IsEmpty()) {
8029 // No following context, so it must be the end of a line or text run
8030 mWordBreaks
[textLen
] = true;
8034 mFrag
->AppendTo(str
, textOffset
, textLen
);
8035 aContext
.Insert(str
, 0);
8037 mozilla::intl::WordBreaker
* wordBreaker
= nsContentUtils::WordBreaker();
8038 int32_t nextWord
= textStart
> 0 ? textStart
- 1 : textStart
;
8041 wordBreaker
->NextWord(aContext
.get(), aContext
.Length(), nextWord
);
8042 if (NS_WORDBREAKER_NEED_MORE_TEXT
== nextWord
||
8043 nextWord
> textStart
+ textLen
) {
8046 mWordBreaks
[nextWord
- textStart
] = true;
8050 nsIFrame::FrameSearchResult
nsTextFrame::PeekOffsetWord(
8051 bool aForward
, bool aWordSelectEatSpace
, bool aIsKeyboardSelect
,
8052 int32_t* aOffset
, PeekWordState
* aState
, bool aTrimSpaces
) {
8053 int32_t contentLength
= GetContentLength();
8054 NS_ASSERTION(aOffset
&& *aOffset
<= contentLength
, "aOffset out of range");
8056 StyleUserSelect selectStyle
;
8057 Unused
<< IsSelectable(&selectStyle
);
8058 if (selectStyle
== StyleUserSelect::All
) return CONTINUE_UNSELECTABLE
;
8061 GetContentOffset() + (*aOffset
< 0 ? contentLength
: *aOffset
);
8062 ClusterIterator
cIter(this, offset
, aForward
? 1 : -1, aState
->mContext
,
8065 if (!cIter
.NextCluster()) return CONTINUE_EMPTY
;
8068 bool isPunctuation
= cIter
.IsPunctuation();
8069 bool isInlineWhitespace
= cIter
.IsInlineWhitespace();
8070 bool isWhitespace
= isInlineWhitespace
|| cIter
.IsNewline();
8071 bool isWordBreakBefore
= cIter
.HaveWordBreakBefore();
8072 if (!isWhitespace
|| isInlineWhitespace
) {
8073 aState
->SetSawInlineCharacter();
8075 if (aWordSelectEatSpace
== isWhitespace
&& !aState
->mSawBeforeType
) {
8076 aState
->SetSawBeforeType();
8077 aState
->Update(isPunctuation
, isWhitespace
);
8080 // See if we can break before the current cluster
8081 if (!aState
->mAtStart
) {
8083 if (isPunctuation
!= aState
->mLastCharWasPunctuation
) {
8084 canBreak
= BreakWordBetweenPunctuation(aState
, aForward
, isPunctuation
,
8085 isWhitespace
, aIsKeyboardSelect
);
8086 } else if (!aState
->mLastCharWasWhitespace
&& !isWhitespace
&&
8087 !isPunctuation
&& isWordBreakBefore
) {
8088 // if both the previous and the current character are not white
8089 // space but this can be word break before, we don't need to eat
8090 // a white space in this case. This case happens in some languages
8091 // that their words are not separated by white spaces. E.g.,
8092 // Japanese and Chinese.
8095 canBreak
= isWordBreakBefore
&& aState
->mSawBeforeType
&&
8096 (aWordSelectEatSpace
!= isWhitespace
);
8099 *aOffset
= cIter
.GetBeforeOffset() - mContentOffset
;
8103 aState
->Update(isPunctuation
, isWhitespace
);
8104 } while (cIter
.NextCluster());
8106 *aOffset
= cIter
.GetAfterOffset() - mContentOffset
;
8110 // TODO this needs to be deCOMtaminated with the interface fixed in
8111 // nsIFrame.h, but we won't do that until the old textframe is gone.
8112 nsresult
nsTextFrame::CheckVisibility(nsPresContext
* aContext
,
8113 int32_t aStartIndex
, int32_t aEndIndex
,
8114 bool aRecurse
, bool* aFinished
,
8116 if (!aRetval
) return NS_ERROR_NULL_POINTER
;
8118 // Text in the range is visible if there is at least one character in the
8119 // range that is not skipped and is mapped by this frame (which is the primary
8120 // frame) or one of its continuations.
8121 for (nsTextFrame
* f
= this; f
; f
= f
->GetNextContinuation()) {
8122 int32_t dummyOffset
= 0;
8123 if (f
->PeekOffsetNoAmount(true, &dummyOffset
) == FOUND
) {
8133 nsresult
nsTextFrame::GetOffsets(int32_t& start
, int32_t& end
) const {
8134 start
= GetContentOffset();
8135 end
= GetContentEnd();
8139 static int32_t FindEndOfPunctuationRun(const nsTextFragment
* aFrag
,
8140 const gfxTextRun
* aTextRun
,
8141 gfxSkipCharsIterator
* aIter
,
8142 int32_t aOffset
, int32_t aStart
,
8146 for (i
= aStart
; i
< aEnd
- aOffset
; ++i
) {
8147 if (nsContentUtils::IsFirstLetterPunctuation(
8148 aFrag
->ScalarValueAt(aOffset
+ i
))) {
8149 aIter
->SetOriginalOffset(aOffset
+ i
);
8150 FindClusterEnd(aTextRun
, aEnd
, aIter
);
8151 i
= aIter
->GetOriginalOffset() - aOffset
;
8160 * Returns true if this text frame completes the first-letter, false
8161 * if it does not contain a true "letter".
8162 * If returns true, then it also updates aLength to cover just the first-letter
8165 * XXX :first-letter should be handled during frame construction
8166 * (and it has a good bit in common with nextBidi)
8168 * @param aLength an in/out parameter: on entry contains the maximum length to
8169 * return, on exit returns length of the first-letter fragment (which may
8170 * include leading and trailing punctuation, for example)
8172 static bool FindFirstLetterRange(const nsTextFragment
* aFrag
,
8173 const nsAtom
* aLang
,
8174 const gfxTextRun
* aTextRun
, int32_t aOffset
,
8175 const gfxSkipCharsIterator
& aIter
,
8178 int32_t length
= *aLength
;
8179 int32_t endOffset
= aOffset
+ length
;
8180 gfxSkipCharsIterator
iter(aIter
);
8182 // Currently the only language-specific special case we handle here is the
8183 // Dutch "IJ" digraph.
8184 auto LangTagIsDutch
= [](const nsAtom
* aLang
) -> bool {
8188 if (aLang
== nsGkAtoms::nl
) {
8191 // We don't need to fully parse as a Locale; just check the initial subtag.
8192 nsDependentAtomString
langStr(aLang
);
8193 int32_t index
= langStr
.FindChar('-');
8195 langStr
.Truncate(index
);
8196 return langStr
.EqualsLiteral("nl");
8201 // skip leading whitespace, then consume clusters that start with punctuation
8202 i
= FindEndOfPunctuationRun(
8203 aFrag
, aTextRun
, &iter
, aOffset
,
8204 GetTrimmableWhitespaceCount(aFrag
, aOffset
, length
, 1), endOffset
);
8209 // If the next character is not a letter or number, there is no first-letter.
8210 // Return true so that we don't go on looking, but set aLength to 0.
8211 if (!nsContentUtils::IsAlphanumericAt(aFrag
, aOffset
+ i
)) {
8216 // consume another cluster (the actual first letter)
8218 // For complex scripts such as Indic and SEAsian, where first-letter
8219 // should extend to entire orthographic "syllable" clusters, we don't
8220 // want to allow this to split a ligature.
8221 bool allowSplitLigature
;
8223 typedef unicode::Script Script
;
8224 Script script
= unicode::GetScriptCode(aFrag
->CharAt(aOffset
+ i
));
8227 allowSplitLigature
= true;
8230 // For now, lacking any definitive specification of when to apply this
8231 // behavior, we'll base the decision on the HarfBuzz shaping engine
8232 // used for each script: those that are handled by the Indic, Tibetan,
8233 // Myanmar and SEAsian shapers will apply the "don't split ligatures"
8237 case Script::BENGALI
:
8238 case Script::DEVANAGARI
:
8239 case Script::GUJARATI
:
8240 case Script::GURMUKHI
:
8241 case Script::KANNADA
:
8242 case Script::MALAYALAM
:
8245 case Script::TELUGU
:
8246 case Script::SINHALA
:
8247 case Script::BALINESE
:
8248 case Script::LEPCHA
:
8249 case Script::REJANG
:
8250 case Script::SUNDANESE
:
8251 case Script::JAVANESE
:
8252 case Script::KAITHI
:
8253 case Script::MEETEI_MAYEK
:
8254 case Script::CHAKMA
:
8255 case Script::SHARADA
:
8260 case Script::TIBETAN
:
8263 case Script::MYANMAR
:
8266 case Script::BUGINESE
:
8267 case Script::NEW_TAI_LUE
:
8269 case Script::TAI_THAM
:
8271 // What about Thai/Lao - any special handling needed?
8272 // Should we special-case Arabic lam-alef?
8274 allowSplitLigature
= false;
8278 iter
.SetOriginalOffset(aOffset
+ i
);
8279 FindClusterEnd(aTextRun
, endOffset
, &iter
, allowSplitLigature
);
8281 i
= iter
.GetOriginalOffset() - aOffset
;
8282 if (i
+ 1 == length
) {
8286 // Check for Dutch "ij" digraph special case.
8287 if (script
== Script::LATIN
&& LangTagIsDutch(aLang
)) {
8288 if (ToLowerCase(aFrag
->CharAt(aOffset
+ i
)) == 'i' &&
8289 ToLowerCase(aFrag
->CharAt(aOffset
+ i
+ 1)) == 'j') {
8290 iter
.SetOriginalOffset(aOffset
+ i
+ 1);
8291 FindClusterEnd(aTextRun
, endOffset
, &iter
, allowSplitLigature
);
8292 i
= iter
.GetOriginalOffset() - aOffset
;
8293 if (i
+ 1 == length
) {
8299 // consume clusters that start with punctuation
8300 i
= FindEndOfPunctuationRun(aFrag
, aTextRun
, &iter
, aOffset
, i
+ 1,
8308 static uint32_t FindStartAfterSkippingWhitespace(
8309 nsTextFrame::PropertyProvider
* aProvider
,
8310 nsIFrame::InlineIntrinsicISizeData
* aData
, const nsStyleText
* aTextStyle
,
8311 gfxSkipCharsIterator
* aIterator
, uint32_t aFlowEndInTextRun
) {
8312 if (aData
->mSkipWhitespace
) {
8313 while (aIterator
->GetSkippedOffset() < aFlowEndInTextRun
&&
8314 IsTrimmableSpace(aProvider
->GetFragment(),
8315 aIterator
->GetOriginalOffset(), aTextStyle
)) {
8316 aIterator
->AdvanceOriginal(1);
8319 return aIterator
->GetSkippedOffset();
8322 float nsTextFrame::GetFontSizeInflation() const {
8323 if (!HasFontSizeInflation()) {
8326 return GetProperty(FontSizeInflationProperty());
8329 void nsTextFrame::SetFontSizeInflation(float aInflation
) {
8330 if (aInflation
== 1.0f
) {
8331 if (HasFontSizeInflation()) {
8332 RemoveStateBits(TEXT_HAS_FONT_INFLATION
);
8333 RemoveProperty(FontSizeInflationProperty());
8338 AddStateBits(TEXT_HAS_FONT_INFLATION
);
8339 SetProperty(FontSizeInflationProperty(), aInflation
);
8343 void nsTextFrame::MarkIntrinsicISizesDirty() {
8345 nsIFrame::MarkIntrinsicISizesDirty();
8348 // XXX this doesn't handle characters shaped by line endings. We need to
8349 // temporarily override the "current line ending" settings.
8350 void nsTextFrame::AddInlineMinISizeForFlow(gfxContext
* aRenderingContext
,
8351 nsIFrame::InlineMinISizeData
* aData
,
8352 TextRunType aTextRunType
) {
8353 uint32_t flowEndInTextRun
;
8354 gfxSkipCharsIterator iter
=
8355 EnsureTextRun(aTextRunType
, aRenderingContext
->GetDrawTarget(),
8356 aData
->LineContainer(), aData
->mLine
, &flowEndInTextRun
);
8357 gfxTextRun
* textRun
= GetTextRun(aTextRunType
);
8358 if (!textRun
) return;
8360 // Pass null for the line container. This will disable tab spacing, but that's
8361 // OK since we can't really handle tabs for intrinsic sizing anyway.
8362 const nsStyleText
* textStyle
= StyleText();
8363 const nsTextFragment
* frag
= TextFragment();
8365 // If we're hyphenating, the PropertyProvider needs the actual length;
8366 // otherwise we can just pass INT32_MAX to mean "all the text"
8367 int32_t len
= INT32_MAX
;
8368 bool hyphenating
= frag
->GetLength() > 0 &&
8369 (textStyle
->mHyphens
== StyleHyphens::Auto
||
8370 (textStyle
->mHyphens
== StyleHyphens::Manual
&&
8371 !!(textRun
->GetFlags() &
8372 gfx::ShapedTextFlags::TEXT_ENABLE_HYPHEN_BREAKS
)));
8374 gfxSkipCharsIterator
tmp(iter
);
8375 len
= std::min
<int32_t>(GetContentOffset() + GetInFlowContentLength(),
8376 tmp
.ConvertSkippedToOriginal(flowEndInTextRun
)) -
8377 iter
.GetOriginalOffset();
8379 PropertyProvider
provider(textRun
, textStyle
, frag
, this, iter
, len
, nullptr,
8382 bool collapseWhitespace
= !textStyle
->WhiteSpaceIsSignificant();
8383 bool preformatNewlines
= textStyle
->NewlineIsSignificant(this);
8384 bool preformatTabs
= textStyle
->WhiteSpaceIsSignificant();
8385 bool whitespaceCanHang
= textStyle
->WhiteSpaceCanHangOrVisuallyCollapse();
8386 gfxFloat tabWidth
= -1;
8387 uint32_t start
= FindStartAfterSkippingWhitespace(&provider
, aData
, textStyle
,
8388 &iter
, flowEndInTextRun
);
8390 // text-combine-upright frame is constantly 1em on inline-axis.
8391 if (Style()->IsTextCombined()) {
8392 if (start
< flowEndInTextRun
&& textRun
->CanBreakLineBefore(start
)) {
8393 aData
->OptionallyBreak();
8395 aData
->mCurrentLine
+= provider
.GetFontMetrics()->EmHeight();
8396 aData
->mTrailingWhitespace
= 0;
8400 if (textStyle
->EffectiveOverflowWrap() == StyleOverflowWrap::Anywhere
&&
8401 textStyle
->WordCanWrap(this)) {
8402 aData
->OptionallyBreak();
8403 aData
->mCurrentLine
+=
8404 textRun
->GetMinAdvanceWidth(Range(start
, flowEndInTextRun
));
8405 aData
->mTrailingWhitespace
= 0;
8406 aData
->mAtStartOfLine
= false;
8407 aData
->OptionallyBreak();
8411 AutoTArray
<gfxTextRun::HyphenType
, BIG_TEXT_NODE_SIZE
> hyphBuffer
;
8413 if (hyphBuffer
.AppendElements(flowEndInTextRun
- start
, fallible
)) {
8414 provider
.GetHyphenationBreaks(Range(start
, flowEndInTextRun
),
8415 hyphBuffer
.Elements());
8417 hyphenating
= false;
8421 for (uint32_t i
= start
, wordStart
= start
; i
<= flowEndInTextRun
; ++i
) {
8422 bool preformattedNewline
= false;
8423 bool preformattedTab
= false;
8424 if (i
< flowEndInTextRun
) {
8425 // XXXldb Shouldn't we be including the newline as part of the
8426 // segment that it ends rather than part of the segment that it
8428 preformattedNewline
= preformatNewlines
&& textRun
->CharIsNewline(i
);
8429 preformattedTab
= preformatTabs
&& textRun
->CharIsTab(i
);
8430 if (!textRun
->CanBreakLineBefore(i
) && !preformattedNewline
&&
8433 hyphBuffer
[i
- start
] == gfxTextRun::HyphenType::None
)) {
8434 // we can't break here (and it's not the end of the flow)
8439 if (i
> wordStart
) {
8440 nscoord width
= NSToCoordCeilClamped(
8441 textRun
->GetAdvanceWidth(Range(wordStart
, i
), &provider
));
8442 width
= std::max(0, width
);
8443 aData
->mCurrentLine
= NSCoordSaturatingAdd(aData
->mCurrentLine
, width
);
8444 aData
->mAtStartOfLine
= false;
8446 if (collapseWhitespace
|| whitespaceCanHang
) {
8447 uint32_t trimStart
= GetEndOfTrimmedText(frag
, textStyle
, wordStart
, i
,
8448 &iter
, whitespaceCanHang
);
8449 if (trimStart
== start
) {
8450 // This is *all* trimmable whitespace, so whatever trailingWhitespace
8451 // we saw previously is still trailing...
8452 aData
->mTrailingWhitespace
+= width
;
8454 // Some non-whitespace so the old trailingWhitespace is no longer
8456 nscoord wsWidth
= NSToCoordCeilClamped(
8457 textRun
->GetAdvanceWidth(Range(trimStart
, i
), &provider
));
8458 aData
->mTrailingWhitespace
= std::max(0, wsWidth
);
8461 aData
->mTrailingWhitespace
= 0;
8465 if (preformattedTab
) {
8466 PropertyProvider::Spacing spacing
;
8467 provider
.GetSpacing(Range(i
, i
+ 1), &spacing
);
8468 aData
->mCurrentLine
+= nscoord(spacing
.mBefore
);
8470 tabWidth
= ComputeTabWidthAppUnits(this, textRun
);
8472 gfxFloat afterTab
= AdvanceToNextTab(aData
->mCurrentLine
, tabWidth
,
8473 provider
.MinTabAdvance());
8474 aData
->mCurrentLine
= nscoord(afterTab
+ spacing
.mAfter
);
8476 } else if (i
< flowEndInTextRun
||
8477 (i
== textRun
->GetLength() &&
8478 (textRun
->GetFlags2() &
8479 nsTextFrameUtils::Flags::HasTrailingBreak
))) {
8480 if (preformattedNewline
) {
8481 aData
->ForceBreak();
8482 } else if (i
< flowEndInTextRun
&& hyphenating
&&
8483 hyphBuffer
[i
- start
] != gfxTextRun::HyphenType::None
) {
8484 aData
->OptionallyBreak(NSToCoordRound(provider
.GetHyphenWidth()));
8486 aData
->OptionallyBreak();
8488 if (aData
->mSkipWhitespace
) {
8489 iter
.SetSkippedOffset(i
);
8490 wordStart
= FindStartAfterSkippingWhitespace(
8491 &provider
, aData
, textStyle
, &iter
, flowEndInTextRun
);
8498 if (start
< flowEndInTextRun
) {
8499 // Check if we have collapsible whitespace at the end
8500 aData
->mSkipWhitespace
= IsTrimmableSpace(
8501 provider
.GetFragment(),
8502 iter
.ConvertSkippedToOriginal(flowEndInTextRun
- 1), textStyle
);
8506 bool nsTextFrame::IsCurrentFontInflation(float aInflation
) const {
8507 return fabsf(aInflation
- GetFontSizeInflation()) < 1e-6;
8510 // XXX Need to do something here to avoid incremental reflow bugs due to
8511 // first-line and first-letter changing min-width
8513 void nsTextFrame::AddInlineMinISize(gfxContext
* aRenderingContext
,
8514 nsIFrame::InlineMinISizeData
* aData
) {
8515 float inflation
= nsLayoutUtils::FontSizeInflationFor(this);
8516 TextRunType trtype
= (inflation
== 1.0f
) ? eNotInflated
: eInflated
;
8518 if (trtype
== eInflated
&& !IsCurrentFontInflation(inflation
)) {
8519 // FIXME: Ideally, if we already have a text run, we'd move it to be
8520 // the uninflated text run.
8521 ClearTextRun(nullptr, nsTextFrame::eInflated
);
8522 mFontMetrics
= nullptr;
8526 const gfxTextRun
* lastTextRun
= nullptr;
8527 // nsContinuingTextFrame does nothing for AddInlineMinISize; all text frames
8528 // in the flow are handled right here.
8529 for (f
= this; f
; f
= f
->GetNextContinuation()) {
8530 // f->GetTextRun(nsTextFrame::eNotInflated) could be null if we
8531 // haven't set up textruns yet for f. Except in OOM situations,
8532 // lastTextRun will only be null for the first text frame.
8533 if (f
== this || f
->GetTextRun(trtype
) != lastTextRun
) {
8535 if (aData
->LineContainer() &&
8536 aData
->LineContainer() != (lc
= FindLineContainer(f
))) {
8537 NS_ASSERTION(f
!= this,
8538 "wrong InlineMinISizeData container"
8539 " for first continuation");
8540 aData
->mLine
= nullptr;
8541 aData
->SetLineContainer(lc
);
8544 // This will process all the text frames that share the same textrun as f.
8545 f
->AddInlineMinISizeForFlow(aRenderingContext
, aData
, trtype
);
8546 lastTextRun
= f
->GetTextRun(trtype
);
8551 // XXX this doesn't handle characters shaped by line endings. We need to
8552 // temporarily override the "current line ending" settings.
8553 void nsTextFrame::AddInlinePrefISizeForFlow(
8554 gfxContext
* aRenderingContext
, nsIFrame::InlinePrefISizeData
* aData
,
8555 TextRunType aTextRunType
) {
8556 uint32_t flowEndInTextRun
;
8557 gfxSkipCharsIterator iter
=
8558 EnsureTextRun(aTextRunType
, aRenderingContext
->GetDrawTarget(),
8559 aData
->LineContainer(), aData
->mLine
, &flowEndInTextRun
);
8560 gfxTextRun
* textRun
= GetTextRun(aTextRunType
);
8561 if (!textRun
) return;
8563 // Pass null for the line container. This will disable tab spacing, but that's
8564 // OK since we can't really handle tabs for intrinsic sizing anyway.
8566 const nsStyleText
* textStyle
= StyleText();
8567 const nsTextFragment
* frag
= TextFragment();
8568 PropertyProvider
provider(textRun
, textStyle
, frag
, this, iter
, INT32_MAX
,
8569 nullptr, 0, aTextRunType
);
8571 // text-combine-upright frame is constantly 1em on inline-axis.
8572 if (Style()->IsTextCombined()) {
8573 aData
->mCurrentLine
+= provider
.GetFontMetrics()->EmHeight();
8574 aData
->mTrailingWhitespace
= 0;
8575 aData
->mLineIsEmpty
= false;
8579 bool collapseWhitespace
= !textStyle
->WhiteSpaceIsSignificant();
8580 bool preformatNewlines
= textStyle
->NewlineIsSignificant(this);
8581 bool preformatTabs
= textStyle
->TabIsSignificant();
8582 gfxFloat tabWidth
= -1;
8583 uint32_t start
= FindStartAfterSkippingWhitespace(&provider
, aData
, textStyle
,
8584 &iter
, flowEndInTextRun
);
8586 // XXX Should we consider hyphenation here?
8587 // If newlines and tabs aren't preformatted, nothing to do inside
8588 // the loop so make i skip to the end
8589 uint32_t loopStart
=
8590 (preformatNewlines
|| preformatTabs
) ? start
: flowEndInTextRun
;
8591 for (uint32_t i
= loopStart
, lineStart
= start
; i
<= flowEndInTextRun
; ++i
) {
8592 bool preformattedNewline
= false;
8593 bool preformattedTab
= false;
8594 if (i
< flowEndInTextRun
) {
8595 // XXXldb Shouldn't we be including the newline as part of the
8596 // segment that it ends rather than part of the segment that it
8598 NS_ASSERTION(preformatNewlines
|| preformatTabs
,
8599 "We can't be here unless newlines are "
8600 "hard breaks or there are tabs");
8601 preformattedNewline
= preformatNewlines
&& textRun
->CharIsNewline(i
);
8602 preformattedTab
= preformatTabs
&& textRun
->CharIsTab(i
);
8603 if (!preformattedNewline
&& !preformattedTab
) {
8604 // we needn't break here (and it's not the end of the flow)
8609 if (i
> lineStart
) {
8610 nscoord width
= NSToCoordCeilClamped(
8611 textRun
->GetAdvanceWidth(Range(lineStart
, i
), &provider
));
8612 width
= std::max(0, width
);
8613 aData
->mCurrentLine
= NSCoordSaturatingAdd(aData
->mCurrentLine
, width
);
8614 aData
->mLineIsEmpty
= false;
8616 if (collapseWhitespace
) {
8617 uint32_t trimStart
=
8618 GetEndOfTrimmedText(frag
, textStyle
, lineStart
, i
, &iter
);
8619 if (trimStart
== start
) {
8620 // This is *all* trimmable whitespace, so whatever trailingWhitespace
8621 // we saw previously is still trailing...
8622 aData
->mTrailingWhitespace
+= width
;
8624 // Some non-whitespace so the old trailingWhitespace is no longer
8626 nscoord wsWidth
= NSToCoordCeilClamped(
8627 textRun
->GetAdvanceWidth(Range(trimStart
, i
), &provider
));
8628 aData
->mTrailingWhitespace
= std::max(0, wsWidth
);
8631 aData
->mTrailingWhitespace
= 0;
8635 if (preformattedTab
) {
8636 PropertyProvider::Spacing spacing
;
8637 provider
.GetSpacing(Range(i
, i
+ 1), &spacing
);
8638 aData
->mCurrentLine
+= nscoord(spacing
.mBefore
);
8640 tabWidth
= ComputeTabWidthAppUnits(this, textRun
);
8642 gfxFloat afterTab
= AdvanceToNextTab(aData
->mCurrentLine
, tabWidth
,
8643 provider
.MinTabAdvance());
8644 aData
->mCurrentLine
= nscoord(afterTab
+ spacing
.mAfter
);
8645 aData
->mLineIsEmpty
= false;
8647 } else if (preformattedNewline
) {
8648 aData
->ForceBreak();
8653 // Check if we have collapsible whitespace at the end
8654 if (start
< flowEndInTextRun
) {
8655 aData
->mSkipWhitespace
= IsTrimmableSpace(
8656 provider
.GetFragment(),
8657 iter
.ConvertSkippedToOriginal(flowEndInTextRun
- 1), textStyle
);
8661 // XXX Need to do something here to avoid incremental reflow bugs due to
8662 // first-line and first-letter changing pref-width
8664 void nsTextFrame::AddInlinePrefISize(gfxContext
* aRenderingContext
,
8665 nsIFrame::InlinePrefISizeData
* aData
) {
8666 float inflation
= nsLayoutUtils::FontSizeInflationFor(this);
8667 TextRunType trtype
= (inflation
== 1.0f
) ? eNotInflated
: eInflated
;
8669 if (trtype
== eInflated
&& !IsCurrentFontInflation(inflation
)) {
8670 // FIXME: Ideally, if we already have a text run, we'd move it to be
8671 // the uninflated text run.
8672 ClearTextRun(nullptr, nsTextFrame::eInflated
);
8673 mFontMetrics
= nullptr;
8677 const gfxTextRun
* lastTextRun
= nullptr;
8678 // nsContinuingTextFrame does nothing for AddInlineMinISize; all text frames
8679 // in the flow are handled right here.
8680 for (f
= this; f
; f
= f
->GetNextContinuation()) {
8681 // f->GetTextRun(nsTextFrame::eNotInflated) could be null if we
8682 // haven't set up textruns yet for f. Except in OOM situations,
8683 // lastTextRun will only be null for the first text frame.
8684 if (f
== this || f
->GetTextRun(trtype
) != lastTextRun
) {
8686 if (aData
->LineContainer() &&
8687 aData
->LineContainer() != (lc
= FindLineContainer(f
))) {
8688 NS_ASSERTION(f
!= this,
8689 "wrong InlinePrefISizeData container"
8690 " for first continuation");
8691 aData
->mLine
= nullptr;
8692 aData
->SetLineContainer(lc
);
8695 // This will process all the text frames that share the same textrun as f.
8696 f
->AddInlinePrefISizeForFlow(aRenderingContext
, aData
, trtype
);
8697 lastTextRun
= f
->GetTextRun(trtype
);
8703 nsIFrame::SizeComputationResult
nsTextFrame::ComputeSize(
8704 gfxContext
* aRenderingContext
, WritingMode aWM
, const LogicalSize
& aCBSize
,
8705 nscoord aAvailableISize
, const LogicalSize
& aMargin
,
8706 const LogicalSize
& aBorderPadding
, const StyleSizeOverrides
& aSizeOverrides
,
8707 ComputeSizeFlags aFlags
) {
8708 // Inlines and text don't compute size before reflow.
8709 return {LogicalSize(aWM
, NS_UNCONSTRAINEDSIZE
, NS_UNCONSTRAINEDSIZE
),
8710 AspectRatioUsage::None
};
8713 static nsRect
RoundOut(const gfxRect
& aRect
) {
8715 r
.x
= NSToCoordFloor(aRect
.X());
8716 r
.y
= NSToCoordFloor(aRect
.Y());
8717 r
.width
= NSToCoordCeil(aRect
.XMost()) - r
.x
;
8718 r
.height
= NSToCoordCeil(aRect
.YMost()) - r
.y
;
8722 nsRect
nsTextFrame::ComputeTightBounds(DrawTarget
* aDrawTarget
) const {
8723 if (Style()->HasTextDecorationLines() || HasAnyStateBits(TEXT_HYPHEN_BREAK
)) {
8724 // This is conservative, but OK.
8725 return InkOverflowRect();
8728 gfxSkipCharsIterator iter
=
8729 const_cast<nsTextFrame
*>(this)->EnsureTextRun(nsTextFrame::eInflated
);
8730 if (!mTextRun
) return nsRect(0, 0, 0, 0);
8732 PropertyProvider
provider(const_cast<nsTextFrame
*>(this), iter
,
8733 nsTextFrame::eInflated
, mFontMetrics
);
8734 // Trim trailing whitespace
8735 provider
.InitializeForDisplay(true);
8737 gfxTextRun::Metrics metrics
= mTextRun
->MeasureText(
8738 ComputeTransformedRange(provider
), gfxFont::TIGHT_HINTED_OUTLINE_EXTENTS
,
8739 aDrawTarget
, &provider
);
8740 if (GetWritingMode().IsLineInverted()) {
8741 metrics
.mBoundingBox
.y
= -metrics
.mBoundingBox
.YMost();
8743 // mAscent should be the same as metrics.mAscent, but it's what we use to
8744 // paint so that's the one we'll use.
8745 nsRect boundingBox
= RoundOut(metrics
.mBoundingBox
);
8746 boundingBox
+= nsPoint(0, mAscent
);
8747 if (mTextRun
->IsVertical()) {
8748 // Swap line-relative textMetrics dimensions to physical coordinates.
8749 std::swap(boundingBox
.x
, boundingBox
.y
);
8750 std::swap(boundingBox
.width
, boundingBox
.height
);
8756 nsresult
nsTextFrame::GetPrefWidthTightBounds(gfxContext
* aContext
, nscoord
* aX
,
8758 gfxSkipCharsIterator iter
=
8759 const_cast<nsTextFrame
*>(this)->EnsureTextRun(nsTextFrame::eInflated
);
8760 if (!mTextRun
) return NS_ERROR_FAILURE
;
8762 PropertyProvider
provider(const_cast<nsTextFrame
*>(this), iter
,
8763 nsTextFrame::eInflated
, mFontMetrics
);
8764 provider
.InitializeForMeasure();
8766 gfxTextRun::Metrics metrics
= mTextRun
->MeasureText(
8767 ComputeTransformedRange(provider
), gfxFont::TIGHT_HINTED_OUTLINE_EXTENTS
,
8768 aContext
->GetDrawTarget(), &provider
);
8769 // Round it like nsTextFrame::ComputeTightBounds() to ensure consistency.
8770 *aX
= NSToCoordFloor(metrics
.mBoundingBox
.x
);
8771 *aXMost
= NSToCoordCeil(metrics
.mBoundingBox
.XMost());
8776 static bool HasSoftHyphenBefore(const nsTextFragment
* aFrag
,
8777 const gfxTextRun
* aTextRun
,
8778 int32_t aStartOffset
,
8779 const gfxSkipCharsIterator
& aIter
) {
8780 if (aIter
.GetSkippedOffset() < aTextRun
->GetLength() &&
8781 aTextRun
->CanHyphenateBefore(aIter
.GetSkippedOffset())) {
8784 if (!(aTextRun
->GetFlags2() & nsTextFrameUtils::Flags::HasShy
)) return false;
8785 gfxSkipCharsIterator iter
= aIter
;
8786 while (iter
.GetOriginalOffset() > aStartOffset
) {
8787 iter
.AdvanceOriginal(-1);
8788 if (!iter
.IsOriginalCharSkipped()) break;
8789 if (aFrag
->CharAt(iter
.GetOriginalOffset()) == CH_SHY
) return true;
8795 * Removes all frames from aFrame up to (but not including) aFirstToNotRemove,
8796 * because their text has all been taken and reflowed by earlier frames.
8798 static void RemoveEmptyInFlows(nsTextFrame
* aFrame
,
8799 nsTextFrame
* aFirstToNotRemove
) {
8800 MOZ_ASSERT(aFrame
!= aFirstToNotRemove
, "This will go very badly");
8801 // We have to be careful here, because some RemoveFrame implementations
8802 // remove and destroy not only the passed-in frame but also all its following
8803 // in-flows (and sometimes all its following continuations in general). So
8804 // we remove |f| and everything up to but not including firstToNotRemove from
8805 // the flow first, to make sure that only the things we want destroyed are
8808 // This sadly duplicates some of the logic from
8809 // nsSplittableFrame::RemoveFromFlow. We can get away with not duplicating
8810 // all of it, because we know that the prev-continuation links of
8811 // firstToNotRemove and f are fluid, and non-null.
8812 NS_ASSERTION(aFirstToNotRemove
->GetPrevContinuation() ==
8813 aFirstToNotRemove
->GetPrevInFlow() &&
8814 aFirstToNotRemove
->GetPrevInFlow() != nullptr,
8815 "aFirstToNotRemove should have a fluid prev continuation");
8816 NS_ASSERTION(aFrame
->GetPrevContinuation() == aFrame
->GetPrevInFlow() &&
8817 aFrame
->GetPrevInFlow() != nullptr,
8818 "aFrame should have a fluid prev continuation");
8820 nsTextFrame
* prevContinuation
= aFrame
->GetPrevContinuation();
8821 nsTextFrame
* lastRemoved
= aFirstToNotRemove
->GetPrevContinuation();
8823 for (nsTextFrame
* f
= aFrame
; f
!= aFirstToNotRemove
;
8824 f
= f
->GetNextContinuation()) {
8825 // f is going to be destroyed soon, after it is unlinked from the
8826 // continuation chain. If its textrun is going to be destroyed we need to
8827 // do it now, before we unlink the frames to remove from the flow,
8828 // because DestroyFrom calls ClearTextRuns() and that will start at the
8829 // first frame with the text run and walk the continuations.
8830 if (f
->IsInTextRunUserData()) {
8833 f
->DisconnectTextRuns();
8837 prevContinuation
->SetNextInFlow(aFirstToNotRemove
);
8838 aFirstToNotRemove
->SetPrevInFlow(prevContinuation
);
8840 aFrame
->SetPrevInFlow(nullptr);
8841 lastRemoved
->SetNextInFlow(nullptr);
8843 nsContainerFrame
* parent
= aFrame
->GetParent();
8844 nsBlockFrame
* parentBlock
= do_QueryFrame(parent
);
8846 // Manually call DoRemoveFrame so we can tell it that we're
8847 // removing empty frames; this will keep it from blowing away
8849 parentBlock
->DoRemoveFrame(aFrame
, nsBlockFrame::FRAMES_ARE_EMPTY
);
8851 // Just remove it normally; use kNoReflowPrincipalList to avoid posting
8853 parent
->RemoveFrame(nsIFrame::kNoReflowPrincipalList
, aFrame
);
8857 void nsTextFrame::SetLength(int32_t aLength
, nsLineLayout
* aLineLayout
,
8858 uint32_t aSetLengthFlags
) {
8859 mContentLengthHint
= aLength
;
8860 int32_t end
= GetContentOffset() + aLength
;
8861 nsTextFrame
* f
= GetNextInFlow();
8864 // If our end offset is moving, then even if frames are not being pushed or
8865 // pulled, content is moving to or from the next line and the next line
8866 // must be reflowed.
8867 // If the next-continuation is dirty, then we should dirty the next line now
8868 // because we may have skipped doing it if we dirtied it in
8869 // CharacterDataChanged. This is ugly but teaching FrameNeedsReflow
8870 // and ChildIsDirty to handle a range of frames would be worse.
8872 (end
!= f
->mContentOffset
|| f
->HasAnyStateBits(NS_FRAME_IS_DIRTY
))) {
8873 aLineLayout
->SetDirtyNextLine();
8876 if (end
< f
->mContentOffset
) {
8877 // Our frame is shrinking. Give the text to our next in flow.
8878 if (aLineLayout
&& HasSignificantTerminalNewline() &&
8879 !GetParent()->IsLetterFrame() &&
8880 (aSetLengthFlags
& ALLOW_FRAME_CREATION_AND_DESTRUCTION
)) {
8881 // Whatever text we hand to our next-in-flow will end up in a frame all of
8882 // its own, since it ends in a forced linebreak. Might as well just put
8883 // it in a separate frame now. This is important to prevent text run
8884 // churn; if we did not do that, then we'd likely end up rebuilding
8885 // textruns for all our following continuations.
8886 // We skip this optimization when the parent is a first-letter frame
8887 // because it doesn't deal well with more than one child frame.
8888 // We also skip this optimization if we were called during bidi
8889 // resolution, so as not to create a new frame which doesn't appear in
8890 // the bidi resolver's list of frames
8891 nsIFrame
* newFrame
=
8892 PresShell()->FrameConstructor()->CreateContinuingFrame(this,
8894 nsTextFrame
* next
= static_cast<nsTextFrame
*>(newFrame
);
8895 nsFrameList
temp(next
, next
);
8896 GetParent()->InsertFrames(kNoReflowPrincipalList
, this,
8897 aLineLayout
->GetLine(), temp
);
8901 f
->mContentOffset
= end
;
8902 if (f
->GetTextRun(nsTextFrame::eInflated
) != mTextRun
) {
8908 // Our frame is growing. Take text from our in-flow(s).
8909 // We can take text from frames in lines beyond just the next line.
8910 // We don't dirty those lines. That's OK, because when we reflow
8911 // our empty next-in-flow, it will take text from its next-in-flow and
8914 // Note that in the process we may end up removing some frames from
8915 // the flow if they end up empty.
8916 nsTextFrame
* framesToRemove
= nullptr;
8917 while (f
&& f
->mContentOffset
< end
) {
8918 f
->mContentOffset
= end
;
8919 if (f
->GetTextRun(nsTextFrame::eInflated
) != mTextRun
) {
8923 nsTextFrame
* next
= f
->GetNextInFlow();
8924 // Note: the "f->GetNextSibling() == next" check below is to restrict
8925 // this optimization to the case where they are on the same child list.
8926 // Otherwise we might remove the only child of a nsFirstLetterFrame
8927 // for example and it can't handle that. See bug 597627 for details.
8928 if (next
&& next
->mContentOffset
<= end
&& f
->GetNextSibling() == next
&&
8929 (aSetLengthFlags
& ALLOW_FRAME_CREATION_AND_DESTRUCTION
)) {
8930 // |f| is now empty. We may as well remove it, instead of copying all
8931 // the text from |next| into it instead; the latter leads to use
8932 // rebuilding textruns for all following continuations.
8933 // We skip this optimization if we were called during bidi resolution,
8934 // since the bidi resolver may try to handle the destroyed frame later
8936 if (!framesToRemove
) {
8937 // Remember that we have to remove this frame.
8940 } else if (framesToRemove
) {
8941 RemoveEmptyInFlows(framesToRemove
, f
);
8942 framesToRemove
= nullptr;
8947 MOZ_ASSERT(!framesToRemove
|| (f
&& f
->mContentOffset
== end
),
8948 "How did we exit the loop if we null out framesToRemove if "
8949 "!next || next->mContentOffset > end ?");
8951 if (framesToRemove
) {
8952 // We are guaranteed that we exited the loop with f not null, per the
8953 // postcondition above
8954 RemoveEmptyInFlows(framesToRemove
, f
);
8959 int32_t iterations
= 0;
8960 while (f
&& iterations
< 10) {
8961 f
->GetContentLength(); // Assert if negative length
8962 f
= f
->GetNextContinuation();
8967 while (f
&& iterations
< 10) {
8968 f
->GetContentLength(); // Assert if negative length
8969 f
= f
->GetPrevContinuation();
8975 bool nsTextFrame::IsFloatingFirstLetterChild() const {
8976 nsIFrame
* frame
= GetParent();
8977 return frame
&& frame
->IsFloating() && frame
->IsLetterFrame();
8980 bool nsTextFrame::IsInitialLetterChild() const {
8981 nsIFrame
* frame
= GetParent();
8982 return frame
&& frame
->StyleTextReset()->mInitialLetterSize
!= 0.0f
&&
8983 frame
->IsLetterFrame();
8986 struct NewlineProperty
{
8987 int32_t mStartOffset
;
8988 // The offset of the first \n after mStartOffset, or -1 if there is none
8989 int32_t mNewlineOffset
;
8992 void nsTextFrame::Reflow(nsPresContext
* aPresContext
, ReflowOutput
& aMetrics
,
8993 const ReflowInput
& aReflowInput
,
8994 nsReflowStatus
& aStatus
) {
8996 DO_GLOBAL_REFLOW_COUNT("nsTextFrame");
8997 DISPLAY_REFLOW(aPresContext
, this, aReflowInput
, aMetrics
, aStatus
);
8998 MOZ_ASSERT(aStatus
.IsEmpty(), "Caller should pass a fresh reflow status!");
9000 // XXX If there's no line layout, we shouldn't even have created this
9001 // frame. This may happen if, for example, this is text inside a table
9002 // but not inside a cell. For now, just don't reflow.
9003 if (!aReflowInput
.mLineLayout
) {
9004 ClearMetrics(aMetrics
);
9008 ReflowText(*aReflowInput
.mLineLayout
, aReflowInput
.AvailableWidth(),
9009 aReflowInput
.mRenderingContext
->GetDrawTarget(), aMetrics
,
9012 NS_FRAME_SET_TRUNCATION(aStatus
, aReflowInput
, aMetrics
);
9015 #ifdef ACCESSIBILITY
9017 * Notifies accessibility about text reflow. Used by nsTextFrame::ReflowText.
9019 class MOZ_STACK_CLASS ReflowTextA11yNotifier
{
9021 ReflowTextA11yNotifier(nsPresContext
* aPresContext
, nsIContent
* aContent
)
9022 : mContent(aContent
), mPresContext(aPresContext
) {}
9023 ~ReflowTextA11yNotifier() {
9024 if (nsAccessibilityService
* accService
=
9025 PresShell::GetAccessibilityService()) {
9026 accService
->UpdateText(mPresContext
->PresShell(), mContent
);
9031 ReflowTextA11yNotifier();
9032 ReflowTextA11yNotifier(const ReflowTextA11yNotifier
&);
9033 ReflowTextA11yNotifier
& operator=(const ReflowTextA11yNotifier
&);
9035 nsIContent
* mContent
;
9036 nsPresContext
* mPresContext
;
9040 void nsTextFrame::ReflowText(nsLineLayout
& aLineLayout
, nscoord aAvailableWidth
,
9041 DrawTarget
* aDrawTarget
, ReflowOutput
& aMetrics
,
9042 nsReflowStatus
& aStatus
) {
9043 MOZ_ASSERT(aStatus
.IsEmpty(), "Caller should pass a fresh reflow status!");
9047 printf(": BeginReflow: availableWidth=%d\n", aAvailableWidth
);
9050 nsPresContext
* presContext
= PresContext();
9052 #ifdef ACCESSIBILITY
9053 // Schedule the update of accessible tree since rendered text might be
9055 if (StyleVisibility()->IsVisible()) {
9056 ReflowTextA11yNotifier(presContext
, mContent
);
9060 /////////////////////////////////////////////////////////////////////
9061 // Set up flags and clear out state
9062 /////////////////////////////////////////////////////////////////////
9064 // Clear out the reflow input flags in mState. We also clear the whitespace
9065 // flags because this can change whether the frame maps whitespace-only text
9066 // or not. We also clear the flag that tracks whether we had a pending
9067 // reflow request from CharacterDataChanged (since we're reflowing now).
9068 RemoveStateBits(TEXT_REFLOW_FLAGS
| TEXT_WHITESPACE_FLAGS
);
9069 mReflowRequestedForCharDataChange
= false;
9070 RemoveProperty(WebRenderTextBounds());
9071 // Temporarily map all possible content while we construct our new textrun.
9072 // so that when doing reflow our styles prevail over any part of the
9073 // textrun we look at. Note that next-in-flows may be mapping the same
9074 // content; gfxTextRun construction logic will ensure that we take priority.
9075 int32_t maxContentLength
= GetInFlowContentLength();
9077 // We don't need to reflow if there is no content.
9078 if (!maxContentLength
) {
9079 ClearMetrics(aMetrics
);
9084 printf("Reflowed textframe\n");
9087 const nsStyleText
* textStyle
= StyleText();
9089 bool atStartOfLine
= aLineLayout
.LineAtStart();
9090 if (atStartOfLine
) {
9091 AddStateBits(TEXT_START_OF_LINE
);
9094 uint32_t flowEndInTextRun
;
9095 nsIFrame
* lineContainer
= aLineLayout
.LineContainerFrame();
9096 const nsTextFragment
* frag
= TextFragment();
9098 // DOM offsets of the text range we need to measure, after trimming
9099 // whitespace, restricting to first-letter, and restricting preformatted text
9100 // to nearest newline
9101 int32_t length
= maxContentLength
;
9102 int32_t offset
= GetContentOffset();
9104 // Restrict preformatted text to the nearest newline
9105 int32_t newLineOffset
= -1; // this will be -1 or a content offset
9106 int32_t contentNewLineOffset
= -1;
9107 // Pointer to the nsGkAtoms::newline set on this frame's element
9108 NewlineProperty
* cachedNewlineOffset
= nullptr;
9109 if (textStyle
->NewlineIsSignificant(this)) {
9110 cachedNewlineOffset
= mContent
->HasFlag(NS_HAS_NEWLINE_PROPERTY
)
9111 ? static_cast<NewlineProperty
*>(
9112 mContent
->GetProperty(nsGkAtoms::newline
))
9114 if (cachedNewlineOffset
&& cachedNewlineOffset
->mStartOffset
<= offset
&&
9115 (cachedNewlineOffset
->mNewlineOffset
== -1 ||
9116 cachedNewlineOffset
->mNewlineOffset
>= offset
)) {
9117 contentNewLineOffset
= cachedNewlineOffset
->mNewlineOffset
;
9119 contentNewLineOffset
=
9120 FindChar(frag
, offset
, GetContent()->TextLength() - offset
, '\n');
9122 if (contentNewLineOffset
< offset
+ length
) {
9124 The new line offset could be outside this frame if the frame has been
9125 split by bidi resolution. In that case we won't use it in this reflow
9126 (newLineOffset will remain -1), but we will still cache it in mContent
9128 newLineOffset
= contentNewLineOffset
;
9130 if (newLineOffset
>= 0) {
9131 length
= newLineOffset
+ 1 - offset
;
9134 if ((atStartOfLine
&& !textStyle
->WhiteSpaceIsSignificant()) ||
9135 HasAnyStateBits(TEXT_IS_IN_TOKEN_MATHML
)) {
9136 // Skip leading whitespace. Make sure we don't skip a 'pre-line'
9137 // newline if there is one.
9138 int32_t skipLength
= newLineOffset
>= 0 ? length
- 1 : length
;
9139 int32_t whitespaceCount
=
9140 GetTrimmableWhitespaceCount(frag
, offset
, skipLength
, 1);
9141 if (whitespaceCount
) {
9142 offset
+= whitespaceCount
;
9143 length
-= whitespaceCount
;
9144 // Make sure this frame maps the trimmable whitespace.
9145 if (MOZ_UNLIKELY(offset
> GetContentEnd())) {
9146 SetLength(offset
- GetContentOffset(), &aLineLayout
,
9147 ALLOW_FRAME_CREATION_AND_DESTRUCTION
);
9152 // If trimming whitespace left us with nothing to do, return early.
9154 ClearMetrics(aMetrics
);
9158 bool completedFirstLetter
= false;
9159 // Layout dependent styles are a problem because we need to reconstruct
9160 // the gfxTextRun based on our layout.
9161 if (aLineLayout
.GetInFirstLetter() || aLineLayout
.GetInFirstLine()) {
9162 SetLength(maxContentLength
, &aLineLayout
,
9163 ALLOW_FRAME_CREATION_AND_DESTRUCTION
);
9165 if (aLineLayout
.GetInFirstLetter()) {
9166 // floating first-letter boundaries are significant in textrun
9167 // construction, so clear the textrun out every time we hit a first-letter
9168 // and have changed our length (which controls the first-letter boundary)
9170 // Find the length of the first-letter. We need a textrun for this.
9171 // REVIEW: maybe-bogus inflation should be ok (fixed below)
9172 gfxSkipCharsIterator iter
=
9173 EnsureTextRun(nsTextFrame::eInflated
, aDrawTarget
, lineContainer
,
9174 aLineLayout
.GetLine(), &flowEndInTextRun
);
9177 int32_t firstLetterLength
= length
;
9178 if (aLineLayout
.GetFirstLetterStyleOK()) {
9179 // We only pass a language code to FindFirstLetterRange if it was
9180 // explicit in the content.
9181 const nsStyleFont
* styleFont
= StyleFont();
9182 const nsAtom
* lang
= styleFont
->mExplicitLanguage
9183 ? styleFont
->mLanguage
.get()
9185 completedFirstLetter
= FindFirstLetterRange(
9186 frag
, lang
, mTextRun
, offset
, iter
, &firstLetterLength
);
9187 if (newLineOffset
>= 0) {
9188 // Don't allow a preformatted newline to be part of a first-letter.
9189 firstLetterLength
= std::min(firstLetterLength
, length
- 1);
9191 // There is no text to be consumed by the first-letter before the
9192 // preformatted newline. Note that the first letter is therefore
9193 // complete (FindFirstLetterRange will have returned false).
9194 completedFirstLetter
= true;
9198 // We're in a first-letter frame's first in flow, so if there
9199 // was a first-letter, we'd be it. However, for one reason
9200 // or another (e.g., preformatted line break before this text),
9201 // we're not actually supposed to have first-letter style. So
9202 // just make a zero-length first-letter.
9203 firstLetterLength
= 0;
9204 completedFirstLetter
= true;
9206 length
= firstLetterLength
;
9208 AddStateBits(TEXT_FIRST_LETTER
);
9210 // Change this frame's length to the first-letter length right now
9211 // so that when we rebuild the textrun it will be built with the
9212 // right first-letter boundary
9213 SetLength(offset
+ length
- GetContentOffset(), &aLineLayout
,
9214 ALLOW_FRAME_CREATION_AND_DESTRUCTION
);
9215 // Ensure that the textrun will be rebuilt
9221 float fontSizeInflation
= nsLayoutUtils::FontSizeInflationFor(this);
9223 if (!IsCurrentFontInflation(fontSizeInflation
)) {
9224 // FIXME: Ideally, if we already have a text run, we'd move it to be
9225 // the uninflated text run.
9226 ClearTextRun(nullptr, nsTextFrame::eInflated
);
9227 mFontMetrics
= nullptr;
9230 gfxSkipCharsIterator iter
=
9231 EnsureTextRun(nsTextFrame::eInflated
, aDrawTarget
, lineContainer
,
9232 aLineLayout
.GetLine(), &flowEndInTextRun
);
9234 NS_ASSERTION(IsCurrentFontInflation(fontSizeInflation
),
9235 "EnsureTextRun should have set font size inflation");
9237 if (mTextRun
&& iter
.GetOriginalEnd() < offset
+ length
) {
9238 // The textrun does not map enough text for this frame. This can happen
9239 // when the textrun was ended in the middle of a text node because a
9240 // preformatted newline was encountered, and prev-in-flow frames have
9241 // consumed all the text of the textrun. We need a new textrun.
9243 iter
= EnsureTextRun(nsTextFrame::eInflated
, aDrawTarget
, lineContainer
,
9244 aLineLayout
.GetLine(), &flowEndInTextRun
);
9248 ClearMetrics(aMetrics
);
9252 NS_ASSERTION(gfxSkipCharsIterator(iter
).ConvertOriginalToSkipped(
9253 offset
+ length
) <= mTextRun
->GetLength(),
9254 "Text run does not map enough text for our reflow");
9256 /////////////////////////////////////////////////////////////////////
9257 // See how much text should belong to this text frame, and measure it
9258 /////////////////////////////////////////////////////////////////////
9260 iter
.SetOriginalOffset(offset
);
9261 nscoord xOffsetForTabs
=
9262 (mTextRun
->GetFlags2() & nsTextFrameUtils::Flags::HasTab
)
9263 ? (aLineLayout
.GetCurrentFrameInlineDistanceFromBlock() -
9264 lineContainer
->GetUsedBorderAndPadding().left
)
9266 PropertyProvider
provider(mTextRun
, textStyle
, frag
, this, iter
, length
,
9267 lineContainer
, xOffsetForTabs
,
9268 nsTextFrame::eInflated
);
9270 uint32_t transformedOffset
= provider
.GetStart().GetSkippedOffset();
9272 // The metrics for the text go in here
9273 gfxTextRun::Metrics textMetrics
;
9274 gfxFont::BoundingBoxType boundingBoxType
=
9275 IsFloatingFirstLetterChild() || IsInitialLetterChild()
9276 ? gfxFont::TIGHT_HINTED_OUTLINE_EXTENTS
9277 : gfxFont::LOOSE_INK_EXTENTS
;
9279 int32_t limitLength
= length
;
9280 int32_t forceBreak
= aLineLayout
.GetForcedBreakPosition(this);
9281 bool forceBreakAfter
= false;
9282 if (forceBreak
>= length
) {
9283 forceBreakAfter
= forceBreak
== length
;
9284 // The break is not within the text considered for this textframe.
9287 if (forceBreak
>= 0) {
9288 limitLength
= forceBreak
;
9290 // This is the heart of text reflow right here! We don't know where
9291 // to break, so we need to see how much text fits in the available width.
9292 uint32_t transformedLength
;
9293 if (offset
+ limitLength
>= int32_t(frag
->GetLength())) {
9294 NS_ASSERTION(offset
+ limitLength
== int32_t(frag
->GetLength()),
9295 "Content offset/length out of bounds");
9296 NS_ASSERTION(flowEndInTextRun
>= transformedOffset
,
9297 "Negative flow length?");
9298 transformedLength
= flowEndInTextRun
- transformedOffset
;
9300 // we're not looking at all the content, so we need to compute the
9301 // length of the transformed substring we're looking at
9302 gfxSkipCharsIterator
iter(provider
.GetStart());
9303 iter
.SetOriginalOffset(offset
+ limitLength
);
9304 transformedLength
= iter
.GetSkippedOffset() - transformedOffset
;
9306 uint32_t transformedLastBreak
= 0;
9307 bool usedHyphenation
;
9308 gfxFloat trimmedWidth
= 0;
9309 gfxFloat availWidth
= aAvailableWidth
;
9310 if (Style()->IsTextCombined()) {
9311 // If text-combine-upright is 'all', we would compress whatever long
9312 // text into ~1em width, so there is no limited on the avail width.
9313 availWidth
= std::numeric_limits
<gfxFloat
>::infinity();
9315 bool canTrimTrailingWhitespace
= !textStyle
->WhiteSpaceIsSignificant() ||
9316 HasAnyStateBits(TEXT_IS_IN_TOKEN_MATHML
);
9318 bool isBreakSpaces
= textStyle
->mWhiteSpace
== StyleWhiteSpace::BreakSpaces
;
9319 // allow whitespace to overflow the container
9320 bool whitespaceCanHang
= textStyle
->WhiteSpaceCanHangOrVisuallyCollapse();
9321 gfxBreakPriority breakPriority
= aLineLayout
.LastOptionalBreakPriority();
9322 gfxTextRun::SuppressBreak suppressBreak
= gfxTextRun::eNoSuppressBreak
;
9323 bool shouldSuppressLineBreak
= ShouldSuppressLineBreak();
9324 if (shouldSuppressLineBreak
) {
9325 suppressBreak
= gfxTextRun::eSuppressAllBreaks
;
9326 } else if (!aLineLayout
.LineIsBreakable()) {
9327 suppressBreak
= gfxTextRun::eSuppressInitialBreak
;
9329 uint32_t transformedCharsFit
= mTextRun
->BreakAndMeasureText(
9330 transformedOffset
, transformedLength
, HasAnyStateBits(TEXT_START_OF_LINE
),
9331 availWidth
, &provider
, suppressBreak
,
9332 canTrimTrailingWhitespace
? &trimmedWidth
: nullptr, whitespaceCanHang
,
9333 &textMetrics
, boundingBoxType
, aDrawTarget
, &usedHyphenation
,
9334 &transformedLastBreak
, textStyle
->WordCanWrap(this), isBreakSpaces
,
9336 if (!length
&& !textMetrics
.mAscent
&& !textMetrics
.mDescent
) {
9337 // If we're measuring a zero-length piece of text, update
9338 // the height manually.
9339 nsFontMetrics
* fm
= provider
.GetFontMetrics();
9341 textMetrics
.mAscent
= gfxFloat(fm
->MaxAscent());
9342 textMetrics
.mDescent
= gfxFloat(fm
->MaxDescent());
9345 if (GetWritingMode().IsLineInverted()) {
9346 std::swap(textMetrics
.mAscent
, textMetrics
.mDescent
);
9347 textMetrics
.mBoundingBox
.y
= -textMetrics
.mBoundingBox
.YMost();
9349 // The "end" iterator points to the first character after the string mapped
9350 // by this frame. Basically, its original-string offset is offset+charsFit
9351 // after we've computed charsFit.
9352 gfxSkipCharsIterator
end(provider
.GetEndHint());
9353 end
.SetSkippedOffset(transformedOffset
+ transformedCharsFit
);
9354 int32_t charsFit
= end
.GetOriginalOffset() - offset
;
9355 if (offset
+ charsFit
== newLineOffset
) {
9356 // We broke before a trailing preformatted '\n'. The newline should
9357 // be assigned to this frame. Note that newLineOffset will be -1 if
9358 // there was no preformatted newline, so we wouldn't get here in that
9362 // That might have taken us beyond our assigned content range (because
9363 // we might have advanced over some skipped chars that extend outside
9364 // this frame), so get back in.
9365 int32_t lastBreak
= -1;
9366 if (charsFit
>= limitLength
) {
9367 charsFit
= limitLength
;
9368 if (transformedLastBreak
!= UINT32_MAX
) {
9369 // lastBreak is needed.
9370 // This may set lastBreak greater than 'length', but that's OK
9371 lastBreak
= end
.ConvertSkippedToOriginal(transformedOffset
+
9372 transformedLastBreak
);
9374 end
.SetOriginalOffset(offset
+ charsFit
);
9375 // If we were forced to fit, and the break position is after a soft hyphen,
9376 // note that this is a hyphenation break.
9377 if ((forceBreak
>= 0 || forceBreakAfter
) &&
9378 HasSoftHyphenBefore(frag
, mTextRun
, offset
, end
)) {
9379 usedHyphenation
= true;
9382 if (usedHyphenation
) {
9383 // Fix up metrics to include hyphen
9384 AddHyphenToMetrics(this, mTextRun
, &textMetrics
, boundingBoxType
,
9386 AddStateBits(TEXT_HYPHEN_BREAK
| TEXT_HAS_NONCOLLAPSED_CHARACTERS
);
9388 if (textMetrics
.mBoundingBox
.IsEmpty()) {
9389 AddStateBits(TEXT_NO_RENDERED_GLYPHS
);
9392 gfxFloat trimmableWidth
= 0;
9393 bool brokeText
= forceBreak
>= 0 || transformedCharsFit
< transformedLength
;
9394 if (canTrimTrailingWhitespace
) {
9395 // Optimization: if we trimmed trailing whitespace, and we can be sure
9396 // this frame will be at the end of the line, then leave it trimmed off.
9397 // Otherwise we have to undo the trimming, in case we're not at the end of
9398 // the line. (If we actually do end up at the end of the line, we'll have
9399 // to trim it off again in TrimTrailingWhiteSpace, and we'd like to avoid
9400 // having to re-do it.)
9401 if (brokeText
|| HasAnyStateBits(TEXT_IS_IN_TOKEN_MATHML
)) {
9402 // We're definitely going to break so our trailing whitespace should
9403 // definitely be trimmed. Record that we've already done it.
9404 AddStateBits(TEXT_TRIMMED_TRAILING_WHITESPACE
);
9405 } else if (!HasAnyStateBits(TEXT_IS_IN_TOKEN_MATHML
)) {
9406 // We might not be at the end of the line. (Note that even if this frame
9407 // ends in breakable whitespace, it might not be at the end of the line
9408 // because it might be followed by breakable, but preformatted,
9409 // whitespace.) Undo the trimming.
9410 textMetrics
.mAdvanceWidth
+= trimmedWidth
;
9411 trimmableWidth
= trimmedWidth
;
9412 if (mTextRun
->IsRightToLeft()) {
9413 // Space comes before text, so the bounding box is moved to the
9414 // right by trimmdWidth
9415 textMetrics
.mBoundingBox
.MoveBy(gfxPoint(trimmedWidth
, 0));
9420 if (!brokeText
&& lastBreak
>= 0) {
9421 // Since everything fit and no break was forced,
9422 // record the last break opportunity
9423 NS_ASSERTION(textMetrics
.mAdvanceWidth
- trimmableWidth
<= availWidth
,
9424 "If the text doesn't fit, and we have a break opportunity, "
9425 "why didn't MeasureText use it?");
9426 MOZ_ASSERT(lastBreak
>= offset
, "Strange break position");
9427 aLineLayout
.NotifyOptionalBreakPosition(this, lastBreak
- offset
, true,
9431 int32_t contentLength
= offset
+ charsFit
- GetContentOffset();
9433 /////////////////////////////////////////////////////////////////////
9434 // Compute output metrics
9435 /////////////////////////////////////////////////////////////////////
9437 // first-letter frames should use the tight bounding box metrics for
9438 // ascent/descent for good drop-cap effects
9439 if (HasAnyStateBits(TEXT_FIRST_LETTER
)) {
9440 textMetrics
.mAscent
=
9441 std::max(gfxFloat(0.0), -textMetrics
.mBoundingBox
.Y());
9442 textMetrics
.mDescent
=
9443 std::max(gfxFloat(0.0), textMetrics
.mBoundingBox
.YMost());
9446 // Setup metrics for caller
9447 // Disallow negative widths
9448 WritingMode wm
= GetWritingMode();
9449 LogicalSize
finalSize(wm
);
9450 finalSize
.ISize(wm
) =
9451 NSToCoordCeil(std::max(gfxFloat(0.0), textMetrics
.mAdvanceWidth
));
9453 if (transformedCharsFit
== 0 && !usedHyphenation
) {
9454 aMetrics
.SetBlockStartAscent(0);
9455 finalSize
.BSize(wm
) = 0;
9456 } else if (boundingBoxType
!= gfxFont::LOOSE_INK_EXTENTS
) {
9457 // Use actual text metrics for floating first letter frame.
9458 aMetrics
.SetBlockStartAscent(NSToCoordCeil(textMetrics
.mAscent
));
9459 finalSize
.BSize(wm
) =
9460 aMetrics
.BlockStartAscent() + NSToCoordCeil(textMetrics
.mDescent
);
9462 // Otherwise, ascent should contain the overline drawable area.
9463 // And also descent should contain the underline drawable area.
9464 // nsFontMetrics::GetMaxAscent/GetMaxDescent contains them.
9465 nsFontMetrics
* fm
= provider
.GetFontMetrics();
9466 nscoord fontAscent
=
9467 wm
.IsLineInverted() ? fm
->MaxDescent() : fm
->MaxAscent();
9468 nscoord fontDescent
=
9469 wm
.IsLineInverted() ? fm
->MaxAscent() : fm
->MaxDescent();
9470 aMetrics
.SetBlockStartAscent(
9471 std::max(NSToCoordCeil(textMetrics
.mAscent
), fontAscent
));
9473 std::max(NSToCoordCeil(textMetrics
.mDescent
), fontDescent
);
9474 finalSize
.BSize(wm
) = aMetrics
.BlockStartAscent() + descent
;
9476 if (Style()->IsTextCombined()) {
9477 nsFontMetrics
* fm
= provider
.GetFontMetrics();
9478 gfxFloat width
= finalSize
.ISize(wm
);
9479 gfxFloat em
= fm
->EmHeight();
9480 // Compress the characters in horizontal axis if necessary.
9482 RemoveProperty(TextCombineScaleFactorProperty());
9484 SetProperty(TextCombineScaleFactorProperty(), em
/ width
);
9485 finalSize
.ISize(wm
) = em
;
9487 // Make the characters be in an 1em square.
9488 if (finalSize
.BSize(wm
) != em
) {
9489 aMetrics
.SetBlockStartAscent(aMetrics
.BlockStartAscent() +
9490 (em
- finalSize
.BSize(wm
)) / 2);
9491 finalSize
.BSize(wm
) = em
;
9494 aMetrics
.SetSize(wm
, finalSize
);
9496 NS_ASSERTION(aMetrics
.BlockStartAscent() >= 0, "Negative ascent???");
9498 (Style()->IsTextCombined() ? aMetrics
.ISize(aMetrics
.GetWritingMode())
9499 : aMetrics
.BSize(aMetrics
.GetWritingMode())) -
9500 aMetrics
.BlockStartAscent() >=
9502 "Negative descent???");
9504 mAscent
= aMetrics
.BlockStartAscent();
9506 // Handle text that runs outside its normal bounds.
9507 nsRect boundingBox
= RoundOut(textMetrics
.mBoundingBox
);
9508 if (mTextRun
->IsVertical()) {
9509 // Swap line-relative textMetrics dimensions to physical coordinates.
9510 std::swap(boundingBox
.x
, boundingBox
.y
);
9511 std::swap(boundingBox
.width
, boundingBox
.height
);
9512 if (GetWritingMode().IsVerticalRL()) {
9513 boundingBox
.x
= -boundingBox
.XMost();
9514 boundingBox
.x
+= aMetrics
.Width() - mAscent
;
9516 boundingBox
.x
+= mAscent
;
9519 boundingBox
.y
+= mAscent
;
9521 aMetrics
.SetOverflowAreasToDesiredBounds();
9522 aMetrics
.InkOverflow().UnionRect(aMetrics
.InkOverflow(), boundingBox
);
9524 // When we have text decorations, we don't need to compute their overflow now
9525 // because we're guaranteed to do it later
9526 // (see nsLineLayout::RelativePositionFrames)
9527 UnionAdditionalOverflow(presContext
, aLineLayout
.LineContainerRI()->mFrame
,
9528 provider
, &aMetrics
.InkOverflow(), false, true);
9530 /////////////////////////////////////////////////////////////////////
9531 // Clean up, update state
9532 /////////////////////////////////////////////////////////////////////
9534 // If all our characters are discarded or collapsed, then trimmable width
9535 // from the last textframe should be preserved. Otherwise the trimmable width
9536 // from this textframe overrides. (Currently in CSS trimmable width can be
9537 // at most one space so there's no way for trimmable width from a previous
9538 // frame to accumulate with trimmable width from this frame.)
9539 if (transformedCharsFit
> 0) {
9540 aLineLayout
.SetTrimmableISize(NSToCoordFloor(trimmableWidth
));
9541 AddStateBits(TEXT_HAS_NONCOLLAPSED_CHARACTERS
);
9543 bool breakAfter
= forceBreakAfter
;
9544 if (!shouldSuppressLineBreak
) {
9545 if (charsFit
> 0 && charsFit
== length
&&
9546 textStyle
->mHyphens
!= StyleHyphens::None
&&
9547 HasSoftHyphenBefore(frag
, mTextRun
, offset
, end
)) {
9549 textMetrics
.mAdvanceWidth
+ provider
.GetHyphenWidth() <= availWidth
;
9550 // Record a potential break after final soft hyphen
9551 aLineLayout
.NotifyOptionalBreakPosition(this, length
, fits
,
9552 gfxBreakPriority::eNormalBreak
);
9554 // length == 0 means either the text is empty or it's all collapsed away
9555 bool emptyTextAtStartOfLine
= atStartOfLine
&& length
== 0;
9556 if (!breakAfter
&& charsFit
== length
&& !emptyTextAtStartOfLine
&&
9557 transformedOffset
+ transformedLength
== mTextRun
->GetLength() &&
9558 (mTextRun
->GetFlags2() & nsTextFrameUtils::Flags::HasTrailingBreak
)) {
9559 // We placed all the text in the textrun and we have a break opportunity
9560 // at the end of the textrun. We need to record it because the following
9561 // content may not care about nsLineBreaker.
9563 // Note that because we didn't break, we can be sure that (thanks to the
9564 // code up above) textMetrics.mAdvanceWidth includes the width of any
9565 // trailing whitespace. So we need to subtract trimmableWidth here
9566 // because if we did break at this point, that much width would be
9568 if (textMetrics
.mAdvanceWidth
- trimmableWidth
> availWidth
) {
9571 aLineLayout
.NotifyOptionalBreakPosition(this, length
, true,
9572 gfxBreakPriority::eNormalBreak
);
9577 // Compute reflow status
9578 if (contentLength
!= maxContentLength
) {
9579 aStatus
.SetIncomplete();
9582 if (charsFit
== 0 && length
> 0 && !usedHyphenation
) {
9583 // Couldn't place any text
9584 aStatus
.SetInlineLineBreakBeforeAndReset();
9585 } else if (contentLength
> 0 &&
9586 mContentOffset
+ contentLength
- 1 == newLineOffset
) {
9588 aStatus
.SetInlineLineBreakAfter();
9589 aLineLayout
.SetLineEndsInBR(true);
9590 } else if (breakAfter
) {
9591 aStatus
.SetInlineLineBreakAfter();
9593 if (completedFirstLetter
) {
9594 aLineLayout
.SetFirstLetterStyleOK(false);
9595 aStatus
.SetFirstLetterComplete();
9598 // Updated the cached NewlineProperty, or delete it.
9599 if (contentLength
< maxContentLength
&&
9600 textStyle
->NewlineIsSignificant(this) &&
9601 (contentNewLineOffset
< 0 ||
9602 mContentOffset
+ contentLength
<= contentNewLineOffset
)) {
9603 if (!cachedNewlineOffset
) {
9604 cachedNewlineOffset
= new NewlineProperty
;
9605 if (NS_FAILED(mContent
->SetProperty(
9606 nsGkAtoms::newline
, cachedNewlineOffset
,
9607 nsINode::DeleteProperty
<NewlineProperty
>))) {
9608 delete cachedNewlineOffset
;
9609 cachedNewlineOffset
= nullptr;
9611 mContent
->SetFlags(NS_HAS_NEWLINE_PROPERTY
);
9613 if (cachedNewlineOffset
) {
9614 cachedNewlineOffset
->mStartOffset
= offset
;
9615 cachedNewlineOffset
->mNewlineOffset
= contentNewLineOffset
;
9617 } else if (cachedNewlineOffset
) {
9618 mContent
->RemoveProperty(nsGkAtoms::newline
);
9619 mContent
->UnsetFlags(NS_HAS_NEWLINE_PROPERTY
);
9622 // Compute space and letter counts for justification, if required
9623 if (!textStyle
->WhiteSpaceIsSignificant() &&
9624 (lineContainer
->StyleText()->mTextAlign
== StyleTextAlign::Justify
||
9625 lineContainer
->StyleText()->mTextAlignLast
==
9626 StyleTextAlignLast::Justify
||
9627 shouldSuppressLineBreak
) &&
9628 !SVGUtils::IsInSVGTextSubtree(lineContainer
)) {
9629 AddStateBits(TEXT_JUSTIFICATION_ENABLED
);
9630 Range
range(uint32_t(offset
), uint32_t(offset
+ charsFit
));
9631 aLineLayout
.SetJustificationInfo(provider
.ComputeJustification(range
));
9634 SetLength(contentLength
, &aLineLayout
, ALLOW_FRAME_CREATION_AND_DESTRUCTION
);
9640 printf(": desiredSize=%d,%d(b=%d) status=%x\n", aMetrics
.Width(),
9641 aMetrics
.Height(), aMetrics
.BlockStartAscent(), aStatus
);
9646 bool nsTextFrame::CanContinueTextRun() const {
9647 // We can continue a text run through a text frame
9651 nsTextFrame::TrimOutput
nsTextFrame::TrimTrailingWhiteSpace(
9652 DrawTarget
* aDrawTarget
) {
9653 MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_IS_DIRTY
| NS_FRAME_FIRST_REFLOW
),
9654 "frame should have been reflowed");
9657 result
.mChanged
= false;
9658 result
.mDeltaWidth
= 0;
9660 AddStateBits(TEXT_END_OF_LINE
);
9662 if (!GetTextRun(nsTextFrame::eInflated
)) {
9663 // If reflow didn't create a textrun, there must have been no content once
9664 // leading whitespace was trimmed, so nothing more to do here.
9668 int32_t contentLength
= GetContentLength();
9669 if (!contentLength
) return result
;
9671 gfxSkipCharsIterator start
=
9672 EnsureTextRun(nsTextFrame::eInflated
, aDrawTarget
);
9673 NS_ENSURE_TRUE(mTextRun
, result
);
9675 uint32_t trimmedStart
= start
.GetSkippedOffset();
9677 const nsTextFragment
* frag
= TextFragment();
9678 TrimmedOffsets trimmed
= GetTrimmedOffsets(frag
);
9679 gfxSkipCharsIterator trimmedEndIter
= start
;
9680 const nsStyleText
* textStyle
= StyleText();
9682 uint32_t trimmedEnd
=
9683 trimmedEndIter
.ConvertOriginalToSkipped(trimmed
.GetEnd());
9685 if (!HasAnyStateBits(TEXT_TRIMMED_TRAILING_WHITESPACE
) &&
9686 trimmed
.GetEnd() < GetContentEnd()) {
9687 gfxSkipCharsIterator end
= trimmedEndIter
;
9688 uint32_t endOffset
=
9689 end
.ConvertOriginalToSkipped(GetContentOffset() + contentLength
);
9690 if (trimmedEnd
< endOffset
) {
9691 // We can't be dealing with tabs here ... they wouldn't be trimmed. So
9692 // it's OK to pass null for the line container.
9693 PropertyProvider
provider(mTextRun
, textStyle
, frag
, this, start
,
9694 contentLength
, nullptr, 0,
9695 nsTextFrame::eInflated
);
9697 mTextRun
->GetAdvanceWidth(Range(trimmedEnd
, endOffset
), &provider
);
9698 result
.mChanged
= true;
9702 gfxFloat advanceDelta
;
9703 mTextRun
->SetLineBreaks(Range(trimmedStart
, trimmedEnd
),
9704 HasAnyStateBits(TEXT_START_OF_LINE
), true,
9706 if (advanceDelta
!= 0) {
9707 result
.mChanged
= true;
9710 // aDeltaWidth is *subtracted* from our width.
9711 // If advanceDelta is positive then setting the line break made us longer,
9712 // so aDeltaWidth could go negative.
9713 result
.mDeltaWidth
= NSToCoordFloor(delta
- advanceDelta
);
9714 // If aDeltaWidth goes negative, that means this frame might not actually fit
9715 // anymore!!! We need higher level line layout to recover somehow.
9716 // If it's because the frame has a soft hyphen that is now being displayed,
9717 // this should actually be OK, because our reflow recorded the break
9718 // opportunity that allowed the soft hyphen to be used, and we wouldn't
9719 // have recorded the opportunity unless the hyphen fit (or was the first
9720 // opportunity on the line).
9721 // Otherwise this can/ really only happen when we have glyphs with special
9722 // shapes at the end of lines, I think. Breaking inside a kerning pair won't
9723 // do it because that would mean we broke inside this textrun, and
9724 // BreakAndMeasureText should make sure the resulting shaped substring fits.
9725 // Maybe if we passed a maxTextLength? But that only happens at direction
9726 // changes (so we wouldn't kern across the boundary) or for first-letter
9727 // (which always fits because it starts the line!).
9728 NS_WARNING_ASSERTION(result
.mDeltaWidth
>= 0,
9729 "Negative deltawidth, something odd is happening");
9733 printf(": trim => %d\n", result
.mDeltaWidth
);
9738 OverflowAreas
nsTextFrame::RecomputeOverflow(nsIFrame
* aBlockFrame
,
9739 bool aIncludeShadows
) {
9740 RemoveProperty(WebRenderTextBounds());
9742 nsRect
bounds(nsPoint(0, 0), GetSize());
9743 OverflowAreas
result(bounds
, bounds
);
9745 gfxSkipCharsIterator iter
= EnsureTextRun(nsTextFrame::eInflated
);
9746 if (!mTextRun
) return result
;
9748 PropertyProvider
provider(this, iter
, nsTextFrame::eInflated
, mFontMetrics
);
9749 // Don't trim trailing space, in case we need to paint it as selected.
9750 provider
.InitializeForDisplay(false);
9752 gfxTextRun::Metrics textMetrics
=
9753 mTextRun
->MeasureText(ComputeTransformedRange(provider
),
9754 gfxFont::LOOSE_INK_EXTENTS
, nullptr, &provider
);
9755 if (GetWritingMode().IsLineInverted()) {
9756 textMetrics
.mBoundingBox
.y
= -textMetrics
.mBoundingBox
.YMost();
9758 nsRect boundingBox
= RoundOut(textMetrics
.mBoundingBox
);
9759 boundingBox
+= nsPoint(0, mAscent
);
9760 if (mTextRun
->IsVertical()) {
9761 // Swap line-relative textMetrics dimensions to physical coordinates.
9762 std::swap(boundingBox
.x
, boundingBox
.y
);
9763 std::swap(boundingBox
.width
, boundingBox
.height
);
9765 nsRect
& vis
= result
.InkOverflow();
9766 vis
.UnionRect(vis
, boundingBox
);
9767 UnionAdditionalOverflow(PresContext(), aBlockFrame
, provider
, &vis
, true,
9772 static void TransformChars(nsTextFrame
* aFrame
, const nsStyleText
* aStyle
,
9773 const gfxTextRun
* aTextRun
, uint32_t aSkippedOffset
,
9774 const nsTextFragment
* aFrag
, int32_t aFragOffset
,
9775 int32_t aFragLen
, nsAString
& aOut
) {
9776 nsAutoString fragString
;
9778 if (aStyle
->mTextTransform
.IsNone() && !NeedsToMaskPassword(aFrame
)) {
9779 // No text-transform, so we can copy directly to the output string.
9780 aOut
.SetLength(aOut
.Length() + aFragLen
);
9781 out
= aOut
.EndWriting() - aFragLen
;
9783 // Use a temporary string as source for the transform.
9784 fragString
.SetLength(aFragLen
);
9785 out
= fragString
.BeginWriting();
9788 // Copy the text, with \n and \t replaced by <space> if appropriate.
9789 for (int32_t i
= 0; i
< aFragLen
; ++i
) {
9790 char16_t ch
= aFrag
->CharAt(aFragOffset
+ i
);
9791 if ((ch
== '\n' && !aStyle
->NewlineIsSignificant(aFrame
)) ||
9792 (ch
== '\t' && !aStyle
->TabIsSignificant())) {
9798 if (!aStyle
->mTextTransform
.IsNone() || NeedsToMaskPassword(aFrame
)) {
9799 MOZ_ASSERT(aTextRun
->GetFlags2() & nsTextFrameUtils::Flags::IsTransformed
);
9800 if (aTextRun
->GetFlags2() & nsTextFrameUtils::Flags::IsTransformed
) {
9801 // Apply text-transform according to style in the transformed run.
9802 auto transformedTextRun
=
9803 static_cast<const nsTransformedTextRun
*>(aTextRun
);
9804 nsAutoString convertedString
;
9805 AutoTArray
<bool, 50> charsToMergeArray
;
9806 AutoTArray
<bool, 50> deletedCharsArray
;
9807 nsCaseTransformTextRunFactory::TransformString(
9808 fragString
, convertedString
, /* aAllUppercase = */ false,
9809 /* aCaseTransformsOnly = */ true, nullptr, charsToMergeArray
,
9810 deletedCharsArray
, transformedTextRun
, aSkippedOffset
);
9811 aOut
.Append(convertedString
);
9813 // Should not happen (see assertion above), but as a fallback...
9814 aOut
.Append(fragString
);
9819 static void LineStartsOrEndsAtHardLineBreak(nsTextFrame
* aFrame
,
9820 nsBlockFrame
* aLineContainer
,
9821 bool* aStartsAtHardBreak
,
9822 bool* aEndsAtHardBreak
) {
9823 bool foundValidLine
;
9824 nsBlockInFlowLineIterator
iter(aLineContainer
, aFrame
, &foundValidLine
);
9825 if (!foundValidLine
) {
9826 NS_ERROR("Invalid line!");
9827 *aStartsAtHardBreak
= *aEndsAtHardBreak
= true;
9831 *aEndsAtHardBreak
= !iter
.GetLine()->IsLineWrapped();
9833 *aStartsAtHardBreak
= !iter
.GetLine()->IsLineWrapped();
9835 // Hit block boundary
9836 *aStartsAtHardBreak
= true;
9840 nsIFrame::RenderedText
nsTextFrame::GetRenderedText(
9841 uint32_t aStartOffset
, uint32_t aEndOffset
, TextOffsetType aOffsetType
,
9842 TrailingWhitespace aTrimTrailingWhitespace
) {
9843 MOZ_ASSERT(aStartOffset
<= aEndOffset
, "bogus offsets");
9844 MOZ_ASSERT(!GetPrevContinuation() ||
9845 (aOffsetType
== TextOffsetType::OffsetsInContentText
&&
9846 aStartOffset
>= (uint32_t)GetContentOffset() &&
9847 aEndOffset
<= (uint32_t)GetContentEnd()),
9848 "Must be called on first-in-flow, or content offsets must be "
9849 "given and be within this frame.");
9851 // The handling of offsets could be more efficient...
9852 RenderedText result
;
9853 nsBlockFrame
* lineContainer
= nullptr;
9854 nsTextFrame
* textFrame
;
9855 const nsTextFragment
* textFrag
= TextFragment();
9856 uint32_t offsetInRenderedString
= 0;
9857 bool haveOffsets
= false;
9859 Maybe
<nsBlockFrame::AutoLineCursorSetup
> autoLineCursor
;
9860 for (textFrame
= this; textFrame
;
9861 textFrame
= textFrame
->GetNextContinuation()) {
9862 if (textFrame
->HasAnyStateBits(NS_FRAME_IS_DIRTY
)) {
9863 // We don't trust dirty frames, especially when computing rendered text.
9867 // Ensure the text run and grab the gfxSkipCharsIterator for it
9868 gfxSkipCharsIterator iter
=
9869 textFrame
->EnsureTextRun(nsTextFrame::eInflated
);
9870 if (!textFrame
->mTextRun
) {
9873 gfxSkipCharsIterator tmpIter
= iter
;
9875 // Check if the frame starts/ends at a hard line break, to determine
9876 // whether whitespace should be trimmed.
9877 bool startsAtHardBreak
, endsAtHardBreak
;
9878 if (!HasAnyStateBits(TEXT_START_OF_LINE
| TEXT_END_OF_LINE
)) {
9879 startsAtHardBreak
= endsAtHardBreak
= false;
9880 } else if (nsBlockFrame
* thisLc
=
9881 do_QueryFrame(FindLineContainer(textFrame
))) {
9882 if (thisLc
!= lineContainer
) {
9883 // Setup line cursor when needed.
9884 lineContainer
= thisLc
;
9885 autoLineCursor
.reset();
9886 autoLineCursor
.emplace(lineContainer
);
9888 LineStartsOrEndsAtHardLineBreak(textFrame
, lineContainer
,
9889 &startsAtHardBreak
, &endsAtHardBreak
);
9891 // Weird situation where we have a line layout without a block.
9892 // No soft breaks occur in this situation.
9893 startsAtHardBreak
= endsAtHardBreak
= true;
9896 // Whether we need to trim whitespaces after the text frame.
9897 // TrimmedOffsetFlags::Default will allow trimming; we set NoTrim* flags
9898 // in the cases where this should not occur.
9899 TrimmedOffsetFlags trimFlags
= TrimmedOffsetFlags::Default
;
9900 if (!textFrame
->IsAtEndOfLine() ||
9901 aTrimTrailingWhitespace
!= TrailingWhitespace::Trim
||
9903 trimFlags
|= TrimmedOffsetFlags::NoTrimAfter
;
9906 // Whether to trim whitespaces before the text frame.
9907 if (!startsAtHardBreak
) {
9908 trimFlags
|= TrimmedOffsetFlags::NoTrimBefore
;
9911 TrimmedOffsets trimmedOffsets
=
9912 textFrame
->GetTrimmedOffsets(textFrag
, trimFlags
);
9913 bool trimmedSignificantNewline
=
9914 trimmedOffsets
.GetEnd() < GetContentEnd() &&
9915 HasSignificantTerminalNewline();
9916 uint32_t skippedToRenderedStringOffset
=
9917 offsetInRenderedString
-
9918 tmpIter
.ConvertOriginalToSkipped(trimmedOffsets
.mStart
);
9919 uint32_t nextOffsetInRenderedString
=
9920 tmpIter
.ConvertOriginalToSkipped(trimmedOffsets
.GetEnd()) +
9921 (trimmedSignificantNewline
? 1 : 0) + skippedToRenderedStringOffset
;
9923 if (aOffsetType
== TextOffsetType::OffsetsInRenderedText
) {
9924 if (nextOffsetInRenderedString
<= aStartOffset
) {
9925 offsetInRenderedString
= nextOffsetInRenderedString
;
9929 result
.mOffsetWithinNodeText
= tmpIter
.ConvertSkippedToOriginal(
9930 aStartOffset
- skippedToRenderedStringOffset
);
9931 result
.mOffsetWithinNodeRenderedText
= aStartOffset
;
9934 if (offsetInRenderedString
>= aEndOffset
) {
9938 if (uint32_t(textFrame
->GetContentEnd()) <= aStartOffset
) {
9939 offsetInRenderedString
= nextOffsetInRenderedString
;
9943 result
.mOffsetWithinNodeText
= aStartOffset
;
9944 // Skip trimmed space when computed the rendered text offset.
9946 std::max
<int32_t>(aStartOffset
, trimmedOffsets
.mStart
);
9947 result
.mOffsetWithinNodeRenderedText
=
9948 tmpIter
.ConvertOriginalToSkipped(clamped
) +
9949 skippedToRenderedStringOffset
;
9951 result
.mOffsetWithinNodeRenderedText
>= offsetInRenderedString
&&
9952 result
.mOffsetWithinNodeRenderedText
<= INT32_MAX
,
9953 "Bad offset within rendered text");
9956 if (uint32_t(textFrame
->mContentOffset
) >= aEndOffset
) {
9961 int32_t startOffset
;
9963 if (aOffsetType
== TextOffsetType::OffsetsInRenderedText
) {
9964 startOffset
= tmpIter
.ConvertSkippedToOriginal(
9965 aStartOffset
- skippedToRenderedStringOffset
);
9966 endOffset
= tmpIter
.ConvertSkippedToOriginal(
9967 aEndOffset
- skippedToRenderedStringOffset
);
9969 startOffset
= aStartOffset
;
9970 endOffset
= std::min
<uint32_t>(INT32_MAX
, aEndOffset
);
9973 // If startOffset and/or endOffset are inside of trimmedOffsets' range,
9974 // then clamp the edges of trimmedOffsets accordingly.
9975 int32_t origTrimmedOffsetsEnd
= trimmedOffsets
.GetEnd();
9976 trimmedOffsets
.mStart
=
9977 std::max
<uint32_t>(trimmedOffsets
.mStart
, startOffset
);
9978 trimmedOffsets
.mLength
=
9979 std::min
<uint32_t>(origTrimmedOffsetsEnd
, endOffset
) -
9980 trimmedOffsets
.mStart
;
9981 if (trimmedOffsets
.mLength
<= 0) {
9982 offsetInRenderedString
= nextOffsetInRenderedString
;
9986 const nsStyleText
* textStyle
= textFrame
->StyleText();
9987 iter
.SetOriginalOffset(trimmedOffsets
.mStart
);
9988 while (iter
.GetOriginalOffset() < trimmedOffsets
.GetEnd()) {
9990 bool isSkipped
= iter
.IsOriginalCharSkipped(&runLength
);
9991 runLength
= std::min(runLength
,
9992 trimmedOffsets
.GetEnd() - iter
.GetOriginalOffset());
9994 for (int32_t i
= 0; i
< runLength
; ++i
) {
9995 char16_t ch
= textFrag
->CharAt(iter
.GetOriginalOffset() + i
);
9997 // We should preserve soft hyphens. They can't be transformed.
9998 result
.mString
.Append(ch
);
10002 TransformChars(textFrame
, textStyle
, textFrame
->mTextRun
,
10003 iter
.GetSkippedOffset(), textFrag
,
10004 iter
.GetOriginalOffset(), runLength
, result
.mString
);
10006 iter
.AdvanceOriginal(runLength
);
10009 if (trimmedSignificantNewline
&& GetContentEnd() <= endOffset
) {
10010 // A significant newline was trimmed off (we must be
10011 // white-space:pre-line). Put it back.
10012 result
.mString
.Append('\n');
10014 offsetInRenderedString
= nextOffsetInRenderedString
;
10017 if (!haveOffsets
) {
10018 result
.mOffsetWithinNodeText
= textFrag
->GetLength();
10019 result
.mOffsetWithinNodeRenderedText
= offsetInRenderedString
;
10025 bool nsTextFrame::IsEmpty() {
10026 NS_ASSERTION(!(mState
& TEXT_IS_ONLY_WHITESPACE
) ||
10027 !(mState
& TEXT_ISNOT_ONLY_WHITESPACE
),
10030 // XXXldb Should this check compatibility mode as well???
10031 const nsStyleText
* textStyle
= StyleText();
10032 if (textStyle
->WhiteSpaceIsSignificant()) {
10033 // XXX shouldn't we return true if the length is zero?
10037 if (mState
& TEXT_ISNOT_ONLY_WHITESPACE
) {
10041 if (mState
& TEXT_IS_ONLY_WHITESPACE
) {
10046 IsAllWhitespace(TextFragment(), textStyle
->mWhiteSpace
!=
10047 mozilla::StyleWhiteSpace::PreLine
);
10048 AddStateBits(isEmpty
? TEXT_IS_ONLY_WHITESPACE
: TEXT_ISNOT_ONLY_WHITESPACE
);
10052 #ifdef DEBUG_FRAME_DUMP
10053 // Translate the mapped content into a string that's printable
10054 void nsTextFrame::ToCString(nsCString
& aBuf
,
10055 int32_t* aTotalContentLength
) const {
10056 // Get the frames text content
10057 const nsTextFragment
* frag
= TextFragment();
10062 // Compute the total length of the text content.
10063 *aTotalContentLength
= frag
->GetLength();
10065 int32_t contentLength
= GetContentLength();
10066 // Set current fragment and current fragment offset
10067 if (0 == contentLength
) {
10070 int32_t fragOffset
= GetContentOffset();
10071 int32_t n
= fragOffset
+ contentLength
;
10072 while (fragOffset
< n
) {
10073 char16_t ch
= frag
->CharAt(fragOffset
++);
10075 aBuf
.AppendLiteral("\\r");
10076 } else if (ch
== '\n') {
10077 aBuf
.AppendLiteral("\\n");
10078 } else if (ch
== '\t') {
10079 aBuf
.AppendLiteral("\\t");
10080 } else if ((ch
< ' ') || (ch
>= 127)) {
10081 aBuf
.Append(nsPrintfCString("\\u%04x", ch
));
10088 nsresult
nsTextFrame::GetFrameName(nsAString
& aResult
) const {
10089 MakeFrameName(u
"Text"_ns
, aResult
);
10090 int32_t totalContentLength
;
10092 ToCString(tmp
, &totalContentLength
);
10093 tmp
.SetLength(std::min(tmp
.Length(), 50u));
10094 aResult
+= u
"\""_ns
+ NS_ConvertASCIItoUTF16(tmp
) + u
"\""_ns
;
10098 void nsTextFrame::List(FILE* out
, const char* aPrefix
, ListFlags aFlags
) const {
10100 ListGeneric(str
, aPrefix
, aFlags
);
10102 str
+= nsPrintfCString(" [run=%p]", static_cast<void*>(mTextRun
));
10104 // Output the first/last content offset and prev/next in flow info
10105 bool isComplete
= uint32_t(GetContentEnd()) == GetContent()->TextLength();
10106 str
+= nsPrintfCString("[%d,%d,%c] ", GetContentOffset(), GetContentLength(),
10107 isComplete
? 'T' : 'F');
10109 if (IsSelected()) {
10110 str
+= " SELECTED";
10112 fprintf_stderr(out
, "%s\n", str
.get());
10115 void nsTextFrame::ListTextRuns(FILE* out
,
10116 nsTHashtable
<nsVoidPtrHashKey
>& aSeen
) const {
10117 if (!mTextRun
|| aSeen
.Contains(mTextRun
)) {
10120 aSeen
.PutEntry(mTextRun
);
10121 mTextRun
->Dump(out
);
10125 void nsTextFrame::AdjustOffsetsForBidi(int32_t aStart
, int32_t aEnd
) {
10126 AddStateBits(NS_FRAME_IS_BIDI
);
10127 if (mContent
->HasFlag(NS_HAS_FLOWLENGTH_PROPERTY
)) {
10128 mContent
->RemoveProperty(nsGkAtoms::flowlength
);
10129 mContent
->UnsetFlags(NS_HAS_FLOWLENGTH_PROPERTY
);
10133 * After Bidi resolution we may need to reassign text runs.
10134 * This is called during bidi resolution from the block container, so we
10135 * shouldn't be holding a local reference to a textrun anywhere.
10139 nsTextFrame
* prev
= GetPrevContinuation();
10141 // the bidi resolver can be very evil when columns/pages are involved. Don't
10142 // let it violate our invariants.
10143 int32_t prevOffset
= prev
->GetContentOffset();
10144 aStart
= std::max(aStart
, prevOffset
);
10145 aEnd
= std::max(aEnd
, prevOffset
);
10146 prev
->ClearTextRuns();
10149 mContentOffset
= aStart
;
10150 SetLength(aEnd
- aStart
, nullptr, 0);
10154 * @return true if this text frame ends with a newline character. It should
10155 * return false if it is not a text frame.
10157 bool nsTextFrame::HasSignificantTerminalNewline() const {
10158 return ::HasTerminalNewline(this) && StyleText()->NewlineIsSignificant(this);
10161 bool nsTextFrame::IsAtEndOfLine() const {
10162 return HasAnyStateBits(TEXT_END_OF_LINE
);
10165 nscoord
nsTextFrame::GetLogicalBaseline(WritingMode aWM
) const {
10166 if (!aWM
.IsOrthogonalTo(GetWritingMode())) {
10170 // When the text frame has a writing mode orthogonal to the desired
10171 // writing mode, return a baseline coincides its parent frame.
10172 nsIFrame
* parent
= GetParent();
10173 nsPoint position
= GetNormalPosition();
10174 nscoord parentAscent
= parent
->GetLogicalBaseline(aWM
);
10175 if (aWM
.IsVerticalRL()) {
10176 nscoord parentDescent
= parent
->GetSize().width
- parentAscent
;
10177 nscoord descent
= parentDescent
- position
.x
;
10178 return GetSize().width
- descent
;
10180 return parentAscent
- (aWM
.IsVertical() ? position
.x
: position
.y
);
10183 bool nsTextFrame::HasAnyNoncollapsedCharacters() {
10184 gfxSkipCharsIterator iter
= EnsureTextRun(nsTextFrame::eInflated
);
10185 int32_t offset
= GetContentOffset(), offsetEnd
= GetContentEnd();
10186 int32_t skippedOffset
= iter
.ConvertOriginalToSkipped(offset
);
10187 int32_t skippedOffsetEnd
= iter
.ConvertOriginalToSkipped(offsetEnd
);
10188 return skippedOffset
!= skippedOffsetEnd
;
10191 bool nsTextFrame::ComputeCustomOverflow(OverflowAreas
& aOverflowAreas
) {
10192 return ComputeCustomOverflowInternal(aOverflowAreas
, true);
10195 bool nsTextFrame::ComputeCustomOverflowInternal(OverflowAreas
& aOverflowAreas
,
10196 bool aIncludeShadows
) {
10197 if (HasAnyStateBits(NS_FRAME_FIRST_REFLOW
)) {
10201 nsIFrame
* decorationsBlock
;
10202 if (IsFloatingFirstLetterChild()) {
10203 decorationsBlock
= GetParent();
10205 nsIFrame
* f
= this;
10207 nsBlockFrame
* fBlock
= do_QueryFrame(f
);
10209 decorationsBlock
= fBlock
;
10213 f
= f
->GetParent();
10215 NS_ERROR("Couldn't find any block ancestor (for text decorations)");
10216 return nsIFrame::ComputeCustomOverflow(aOverflowAreas
);
10221 aOverflowAreas
= RecomputeOverflow(decorationsBlock
, aIncludeShadows
);
10222 return nsIFrame::ComputeCustomOverflow(aOverflowAreas
);
10225 NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(JustificationAssignmentProperty
, int32_t)
10227 void nsTextFrame::AssignJustificationGaps(
10228 const mozilla::JustificationAssignment
& aAssign
) {
10229 int32_t encoded
= (aAssign
.mGapsAtStart
<< 8) | aAssign
.mGapsAtEnd
;
10230 static_assert(sizeof(aAssign
) == 1,
10231 "The encoding might be broken if JustificationAssignment "
10232 "is larger than 1 byte");
10233 SetProperty(JustificationAssignmentProperty(), encoded
);
10236 mozilla::JustificationAssignment
nsTextFrame::GetJustificationAssignment()
10238 int32_t encoded
= GetProperty(JustificationAssignmentProperty());
10239 mozilla::JustificationAssignment result
;
10240 result
.mGapsAtStart
= encoded
>> 8;
10241 result
.mGapsAtEnd
= encoded
& 0xFF;
10245 uint32_t nsTextFrame::CountGraphemeClusters() const {
10246 const nsTextFragment
* frag
= TextFragment();
10247 MOZ_ASSERT(frag
, "Text frame must have text fragment");
10248 nsAutoString content
;
10249 frag
->AppendTo(content
, GetContentOffset(), GetContentLength());
10250 return unicode::CountGraphemeClusters(content
.Data(), content
.Length());
10253 bool nsTextFrame::HasNonSuppressedText() const {
10254 if (HasAnyStateBits(TEXT_ISNOT_ONLY_WHITESPACE
|
10255 // If we haven't reflowed yet, or are currently doing so,
10256 // just return true because we can't be sure.
10257 NS_FRAME_FIRST_REFLOW
| NS_FRAME_IN_REFLOW
)) {
10261 if (!GetTextRun(nsTextFrame::eInflated
)) {
10265 TrimmedOffsets offsets
=
10266 GetTrimmedOffsets(TextFragment(), TrimmedOffsetFlags::NoTrimAfter
);
10267 return offsets
.mLength
!= 0;