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