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