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