Bug Summary

File:root/firefox-clang/obj-x86_64-pc-linux-gnu/layout/generic/./../../../layout/generic/nsTextFrame.cpp
Warning:line 10791, column 5
Value stored to 'next' is never read

Annotated Source Code

Press '?' to see keyboard shortcuts

clang -cc1 -cc1 -triple x86_64-pc-linux-gnu -O2 -analyze -disable-free -clear-ast-before-backend -disable-llvm-verifier -discard-value-names -main-file-name Unified_cpp_layout_generic4.cpp -analyzer-checker=core -analyzer-checker=apiModeling -analyzer-checker=unix -analyzer-checker=deadcode -analyzer-checker=cplusplus -analyzer-checker=security.insecureAPI.UncheckedReturn -analyzer-checker=security.insecureAPI.getpw -analyzer-checker=security.insecureAPI.gets -analyzer-checker=security.insecureAPI.mktemp -analyzer-checker=security.insecureAPI.mkstemp -analyzer-checker=security.insecureAPI.vfork -analyzer-checker=nullability.NullPassedToNonnull -analyzer-checker=nullability.NullReturnedFromNonnull -analyzer-output plist -w -setup-static-analyzer -analyzer-config-compatibility-mode=true -mrelocation-model pic -pic-level 2 -fhalf-no-semantic-interposition -mframe-pointer=all -relaxed-aliasing -ffp-contract=off -fno-rounding-math -mconstructor-aliases -funwind-tables=2 -target-cpu x86-64 -tune-cpu generic -debugger-tuning=gdb -fdebug-compilation-dir=/root/firefox-clang/obj-x86_64-pc-linux-gnu/layout/generic -fcoverage-compilation-dir=/root/firefox-clang/obj-x86_64-pc-linux-gnu/layout/generic -resource-dir /usr/lib/llvm-23/lib/clang/23 -include /root/firefox-clang/config/gcc_hidden.h -include /root/firefox-clang/obj-x86_64-pc-linux-gnu/mozilla-config.h -I /root/firefox-clang/obj-x86_64-pc-linux-gnu/dist/stl_wrappers -D _GLIBCXX_ASSERTIONS=1 -I /root/firefox-clang/obj-x86_64-pc-linux-gnu/dist/system_wrappers -U _FORTIFY_SOURCE -D _FORTIFY_SOURCE=2 -D DEBUG=1 -D MOZ_HAS_MOZGLUE -D MOZILLA_INTERNAL_API -D IMPL_LIBXUL -D MOZ_SUPPORT_LEAKCHECKING -D STATIC_EXPORTABLE_JS_API -I /root/firefox-clang/layout/generic -I /root/firefox-clang/obj-x86_64-pc-linux-gnu/layout/generic -I /root/firefox-clang/obj-x86_64-pc-linux-gnu/ipc/ipdl/_ipdlheaders -I /root/firefox-clang/ipc/chromium/src -I /root/firefox-clang/third_party/abseil-cpp -I /root/firefox-clang/layout/base -I /root/firefox-clang/layout/forms -I /root/firefox-clang/layout/painting -I /root/firefox-clang/layout/style -I /root/firefox-clang/layout/tables -I /root/firefox-clang/layout/xul -I /root/firefox-clang/docshell/base -I /root/firefox-clang/dom/base -I /root/firefox-clang/dom/html -I /root/firefox-clang/dom/xul -I /root/firefox-clang/gfx/cairo/cairo/src -I /root/firefox-clang/obj-x86_64-pc-linux-gnu/dist/include -I /root/firefox-clang/obj-x86_64-pc-linux-gnu/dist/include/nspr -I /root/firefox-clang/obj-x86_64-pc-linux-gnu/dist/include/nss -D MOZILLA_CLIENT -I /usr/include/gtk-3.0/unix-print -I /usr/include/gtk-3.0 -I /usr/include/pango-1.0 -I /usr/include/cloudproviders -I /usr/include/at-spi2-atk/2.0 -I /usr/include/at-spi-2.0 -I /usr/include/dbus-1.0 -I /usr/lib/x86_64-linux-gnu/dbus-1.0/include -I /usr/include/fribidi -I /usr/include/harfbuzz -I /usr/include/atk-1.0 -I /usr/include/cairo -I /usr/include/pixman-1 -I /usr/include/gdk-pixbuf-2.0 -I /usr/include/glycin-2 -I /usr/include/freetype2 -I /usr/include/libpng16 -I /usr/include/gio-unix-2.0 -I /usr/include/glib-2.0 -I /usr/lib/x86_64-linux-gnu/glib-2.0/include -I /usr/include/libmount -I /usr/include/blkid -I /usr/include/sysprof-6 -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/16/../../../../include/c++/16 -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/16/../../../../include/x86_64-linux-gnu/c++/16 -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/16/../../../../include/c++/16/backward -internal-isystem /usr/lib/llvm-23/lib/clang/23/include -internal-isystem /usr/local/include -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/16/../../../../x86_64-linux-gnu/include -internal-externc-isystem /usr/include/x86_64-linux-gnu -internal-externc-isystem /include -internal-externc-isystem /usr/include -Wno-error=pessimizing-move -Wno-error=large-by-value-copy=128 -Wno-error=implicit-int-float-conversion -Wno-error=thread-safety-analysis -Wno-error=tautological-type-limit-compare -Wno-invalid-offsetof -Wno-range-loop-analysis -Wno-deprecated-anon-enum-enum-conversion -Wno-deprecated-enum-enum-conversion -Wno-inline-new-delete -Wno-error=deprecated-declarations -Wno-error=array-bounds -Wno-error=free-nonheap-object -Wno-error=atomic-alignment -Wno-error=deprecated-builtins -Wno-psabi -Wno-error=builtin-macro-redefined -Wno-vla-cxx-extension -Wno-unknown-warning-option -Wno-character-conversion -std=gnu++20 -fdeprecated-macro -ferror-limit 19 -fstrict-flex-arrays=1 -stack-protector 2 -fstack-clash-protection -ftrivial-auto-var-init=pattern -fno-rtti -fgnuc-version=4.2.1 -fno-implicit-modules -fskip-odr-check-in-gmf -fno-sized-deallocation -fno-aligned-allocation -fdiagnostics-absolute-paths -vectorize-loops -vectorize-slp -analyzer-checker optin.performance.Padding -analyzer-output=html -analyzer-config stable-report-filename=true -mllvm -dwarf-linkage-names=Abstract -faddrsig -fdwarf2-cfi-asm -o /tmp/scan-build-2026-10-17-231001-351276-1 -x c++ Unified_cpp_layout_generic4.cpp
1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5/* rendering object for textual content of elements */
6
7#include "nsTextFrame.h"
8
9#include <algorithm>
10#include <limits>
11
12#include "MathMLTextRunFactory.h"
13#include "PresShellInlines.h"
14#include "PseudoStyleType.h"
15#include "TextDrawTarget.h"
16#include "gfx2DGlue.h"
17#include "gfxContext.h"
18#include "gfxUtils.h"
19#include "mozilla/Attributes.h"
20#include "mozilla/BinarySearch.h"
21#include "mozilla/CaretAssociationHint.h"
22#include "mozilla/ComputedStyle.h"
23#include "mozilla/DebugOnly.h"
24#include "mozilla/GeckoBindings.h"
25#include "mozilla/IntegerRange.h"
26#include "mozilla/Likely.h"
27#include "mozilla/MathAlgorithms.h"
28#include "mozilla/PodOperations.h"
29#include "mozilla/PresShell.h"
30#include "mozilla/SVGTextFrame.h"
31#include "mozilla/ServoStyleSet.h"
32#include "mozilla/StaticPrefs_layout.h"
33#include "mozilla/StaticPresData.h"
34#include "mozilla/TextEditor.h"
35#include "mozilla/TextEvents.h"
36#include "mozilla/TextUtils.h"
37#include "mozilla/dom/CharacterDataBuffer.h"
38#include "mozilla/dom/PerformanceMainThread.h"
39#include "mozilla/dom/Range.h"
40#include "mozilla/gfx/2D.h"
41#include "mozilla/intl/Bidi.h"
42#include "mozilla/intl/Segmenter.h"
43#include "mozilla/intl/UnicodeProperties.h"
44#include "mozilla/widget/ThemeDrawing.h"
45#include "nsBlockFrame.h"
46#include "nsCOMPtr.h"
47#include "nsCSSColorUtils.h"
48#include "nsCSSFrameConstructor.h"
49#include "nsCSSRendering.h"
50#include "nsCompatibility.h"
51#include "nsContentUtils.h"
52#include "nsCoord.h"
53#include "nsDisplayList.h"
54#include "nsFirstLetterFrame.h"
55#include "nsFontMetrics.h"
56#include "nsFrameSelection.h"
57#include "nsGkAtoms.h"
58#include "nsIContent.h"
59#include "nsIFrame.h"
60#include "nsIFrameInlines.h"
61#include "nsIMathMLFrame.h"
62#include "nsLayoutUtils.h"
63#include "nsLineBreaker.h"
64#include "nsLineLayout.h"
65#include "nsPlaceholderFrame.h"
66#include "nsPresContext.h"
67#include "nsRubyFrame.h"
68#include "nsSplittableFrame.h"
69#include "nsString.h"
70#include "nsStyleConsts.h"
71#include "nsStyleStruct.h"
72#include "nsStyleStructInlines.h"
73#include "nsStyleUtil.h"
74#include "nsTArray.h"
75#include "nsTextFrameUtils.h"
76#include "nsTextPaintStyle.h"
77#include "nsTextRunTransformations.h"
78#include "nsUnicharUtils.h"
79#include "nsUnicodeProperties.h"
80#ifdef ACCESSIBILITY1
81# include "nsAccessibilityService.h"
82#endif
83
84#include "mozilla/LookAndFeel.h"
85#include "mozilla/ProfilerLabels.h"
86#include "mozilla/UniquePtr.h"
87#include "mozilla/dom/Element.h"
88#include "mozilla/gfx/DrawTargetRecording.h"
89#include "nsPrintfCString.h"
90
91#ifdef DEBUG1
92# undef NOISY_REFLOW
93# undef NOISY_TRIM
94#else
95# undef NOISY_REFLOW
96# undef NOISY_TRIM
97#endif
98
99#ifdef DrawText
100# undef DrawText
101#endif
102
103using namespace mozilla;
104using namespace mozilla::dom;
105using namespace mozilla::gfx;
106
107static bool NeedsToMaskPassword(const nsTextFrame* aFrame) {
108 MOZ_ASSERT(aFrame)do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aFrame)>::isValid, "invalid assertion condition")
; if ((__builtin_expect(!!(!(!!(aFrame))), 0))) { do { } while
(false); MOZ_ReportAssertionFailure("aFrame", "./../../../layout/generic/nsTextFrame.cpp"
, 108); AnnotateMozCrashReason("MOZ_ASSERT" "(" "aFrame" ")")
; do { MOZ_CrashSequence(__null, 108); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
109 MOZ_ASSERT(aFrame->GetContent())do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aFrame->GetContent())>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(aFrame->GetContent()))), 0
))) { do { } while (false); MOZ_ReportAssertionFailure("aFrame->GetContent()"
, "./../../../layout/generic/nsTextFrame.cpp", 109); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "aFrame->GetContent()" ")"); do { MOZ_CrashSequence
(__null, 109); __attribute__((nomerge)) ::abort(); } while (false
); } } while (false)
;
110 if (!aFrame->GetContent()->HasFlag(NS_MAYBE_MASKED)) {
111 return false;
112 }
113 const nsIFrame* frame =
114 nsLayoutUtils::GetClosestFrameOfType(aFrame, LayoutFrameType::TextInput);
115 MOZ_ASSERT(frame, "How do we have a masked text node without a text input?")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(frame)>::isValid, "invalid assertion condition");
if ((__builtin_expect(!!(!(!!(frame))), 0))) { do { } while (
false); MOZ_ReportAssertionFailure("frame" " (" "How do we have a masked text node without a text input?"
")", "./../../../layout/generic/nsTextFrame.cpp", 115); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "frame" ") (" "How do we have a masked text node without a text input?"
")"); do { MOZ_CrashSequence(__null, 115); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
116 return !frame || !frame->GetContent()->AsElement()->State().HasState(
117 ElementState::REVEALED);
118}
119
120namespace mozilla {
121
122bool TextAutospace::ShouldSuppressLetterNumeralSpacing(const nsIFrame* aFrame) {
123 const auto wm = aFrame->GetWritingMode();
124 if (wm.IsUpright()) {
125 // The characters are in vertical writing mode with forced upright glyph
126 // orientation.
127 return true;
128 }
129 if (aFrame->Style()->IsTextCombined()) {
130 // The characters have combined forced upright glyph orientation.
131 return true;
132 }
133 if (aFrame->StyleText()->mTextTransform & StyleTextTransform::FULL_WIDTH) {
134 // The characters are transformed to full-width, so non-ideographic
135 // letters/numerals look like ideograph letter/numerals.
136 return true;
137 }
138 return false;
139}
140
141bool TextAutospace::Enabled(const StyleTextAutospace& aStyleTextAutospace,
142 const nsTextFrame* aFrame) {
143 if (aStyleTextAutospace == StyleTextAutospace::NO_AUTOSPACE) {
144 return false;
145 }
146
147 if (aStyleTextAutospace == StyleTextAutospace::AUTO) {
148 // 'text-autospace: auto' is UA-defined. Currently, WebKit parses `auto` but
149 // does not add spacing; Blink does not parse 'auto' (treated as invalid).
150 // To align with other engines, we treat 'auto' the same as a no-op.
151 return false;
152 }
153
154 if (ShouldSuppressLetterNumeralSpacing(aFrame)) {
155 // If we suppress the spacing for aFrame, ideograph-alpha or
156 // ideograph-numeric boundaries cannot occur.
157 return false;
158 }
159
160 if (NeedsToMaskPassword(aFrame)) {
161 // Don't allow autospacing in masked password fields, as it could reveal
162 // hints about the types of characters present.
163 return false;
164 }
165
166 return true;
167}
168
169TextAutospace::TextAutospace(const StyleTextAutospace& aStyleTextAutospace,
170 nscoord aInterScriptSpacing)
171 : mBoundarySet(InitBoundarySet(aStyleTextAutospace)),
172 mInterScriptSpacing(aInterScriptSpacing) {}
173
174bool TextAutospace::ShouldApplySpacing(CharClass aPrevClass,
175 CharClass aCurrClass) const {
176 const EnumSet<CharClass> classes{aPrevClass, aCurrClass};
177 if (mBoundarySet.contains(Boundary::IdeographAlpha)) {
178 constexpr EnumSet<CharClass> kIdeographAlphaMask{
179 CharClass::Ideograph, CharClass::NonIdeographicLetter};
180 if (classes == kIdeographAlphaMask) {
181 return true;
182 }
183 }
184
185 if (mBoundarySet.contains(Boundary::IdeographNumeric)) {
186 constexpr EnumSet<CharClass> kIdeographNumericMask{
187 CharClass::Ideograph, CharClass::NonIdeographicNumeral};
188 if (classes == kIdeographNumericMask) {
189 return true;
190 }
191 }
192
193 return false;
194}
195
196bool TextAutospace::IsIdeograph(char32_t aChar) {
197 // All characters in the range of U+3041 to U+30FF, except those that belong
198 // to Unicode Punctuation [P*] general category.
199 if (0x3041 <= aChar && aChar <= 0x30FF) {
200 return !intl::UnicodeProperties::IsPunctuation(aChar);
201 }
202
203 // CJK Strokes (U+31C0 to U+31EF) and Katakana Phonetic Extensions (U+31F0 to
204 // U+31FF).
205 if (0x31C0 <= aChar && aChar <= 0x31FF) {
206 return true;
207 }
208
209 // All characters that have the Han script property.
210 if (intl::UnicodeProperties::GetScriptCode(aChar) == intl::Script::HAN) {
211 return true;
212 }
213
214 return false;
215}
216
217TextAutospace::CharClass TextAutospace::GetCharClass(char32_t aChar) {
218 if (IsAsciiAlpha(aChar)) {
219 return CharClass::NonIdeographicLetter;
220 }
221
222 if (IsAsciiDigit(aChar)) {
223 return CharClass::NonIdeographicNumeral;
224 }
225
226 if (IsIdeograph(aChar)) {
227 return CharClass::Ideograph;
228 }
229
230 // From now on, aChar is *not* an ideograph.
231 if (intl::UnicodeProperties::IsCombiningMark(aChar)) {
232 // Do not treat combining mark as letter.
233 return CharClass::CombiningMark;
234 }
235
236 if (intl::UnicodeProperties::IsLetter(aChar) &&
237 !intl::UnicodeProperties::IsEastAsianFullWidth(aChar)) {
238 return CharClass::NonIdeographicLetter;
239 }
240
241 if (intl::UnicodeProperties::CharType(aChar) ==
242 intl::GeneralCategory::Decimal_Number) {
243 if (!intl::UnicodeProperties::IsEastAsianFullWidth(aChar)) {
244 return CharClass::NonIdeographicNumeral;
245 }
246 }
247
248 return CharClass::Other;
249}
250
251TextAutospace::BoundarySet TextAutospace::InitBoundarySet(
252 const StyleTextAutospace& aStyleTextAutospace) const {
253 if (aStyleTextAutospace == StyleTextAutospace::NORMAL) {
254 return {Boundary::IdeographAlpha, Boundary::IdeographNumeric};
255 }
256
257 if (aStyleTextAutospace == StyleTextAutospace::IDEOGRAPH_ALPHA) {
258 return {Boundary::IdeographAlpha};
259 }
260
261 if (aStyleTextAutospace == StyleTextAutospace::IDEOGRAPH_NUMERIC) {
262 return {Boundary::IdeographNumeric};
263 }
264
265 return {};
266}
267
268} // namespace mozilla
269
270struct TabWidth {
271 TabWidth(uint32_t aOffset, uint32_t aWidth)
272 : mOffset(aOffset), mWidth(float(aWidth)) {}
273
274 uint32_t mOffset; // DOM offset relative to the current frame's offset.
275 float mWidth; // extra space to be added at this position (in app units)
276};
277
278struct nsTextFrame::TabWidthStore {
279 explicit TabWidthStore(int32_t aValidForContentOffset)
280 : mLimit(0), mValidForContentOffset(aValidForContentOffset) {}
281
282 // Apply tab widths to the aSpacing array, which corresponds to characters
283 // beginning at aOffset and has length aLength. (Width records outside this
284 // range will be ignored.)
285 void ApplySpacing(gfxTextRun::PropertyProvider::Spacing* aSpacing,
286 uint32_t aOffset, uint32_t aLength);
287
288 // Offset up to which tabs have been measured; positions beyond this have not
289 // been calculated yet but may be appended if needed later. It's a DOM
290 // offset relative to the current frame's offset.
291 uint32_t mLimit;
292
293 // Need to recalc tab offsets if frame content offset differs from this.
294 int32_t mValidForContentOffset;
295
296 // A TabWidth record for each tab character measured so far.
297 nsTArray<TabWidth> mWidths;
298};
299
300namespace {
301
302struct TabwidthAdaptor {
303 const nsTArray<TabWidth>& mWidths;
304 explicit TabwidthAdaptor(const nsTArray<TabWidth>& aWidths)
305 : mWidths(aWidths) {}
306 uint32_t operator[](size_t aIdx) const { return mWidths[aIdx].mOffset; }
307};
308
309} // namespace
310
311void nsTextFrame::TabWidthStore::ApplySpacing(
312 gfxTextRun::PropertyProvider::Spacing* aSpacing, uint32_t aOffset,
313 uint32_t aLength) {
314 size_t i = 0;
315 const size_t len = mWidths.Length();
316
317 // If aOffset is non-zero, do a binary search to find where to start
318 // processing the tab widths, in case the list is really long. (See bug
319 // 953247.)
320 // We need to start from the first entry where mOffset >= aOffset.
321 if (aOffset > 0) {
322 mozilla::BinarySearch(TabwidthAdaptor(mWidths), 0, len, aOffset, &i);
323 }
324
325 uint32_t limit = aOffset + aLength;
326 while (i < len) {
327 const TabWidth& tw = mWidths[i];
328 if (tw.mOffset >= limit) {
329 break;
330 }
331 aSpacing[tw.mOffset - aOffset].mAfter += tw.mWidth;
332 i++;
333 }
334}
335
336NS_DECLARE_FRAME_PROPERTY_DELETABLE(TabWidthProperty,static const mozilla::FramePropertyDescriptor<nsTextFrame::
TabWidthStore>* TabWidthProperty() { static const auto descriptor
= mozilla::FramePropertyDescriptor<nsTextFrame::TabWidthStore
>::NewWithDestructor<DeleteValue>(); return &descriptor
; }
337 nsTextFrame::TabWidthStore)static const mozilla::FramePropertyDescriptor<nsTextFrame::
TabWidthStore>* TabWidthProperty() { static const auto descriptor
= mozilla::FramePropertyDescriptor<nsTextFrame::TabWidthStore
>::NewWithDestructor<DeleteValue>(); return &descriptor
; }
338
339NS_DECLARE_FRAME_PROPERTY_WITHOUT_DTOR(OffsetToFrameProperty, nsTextFrame)static const mozilla::FramePropertyDescriptor<nsTextFrame>
* OffsetToFrameProperty() { static const auto descriptor = mozilla
::FramePropertyDescriptor<nsTextFrame>::NewWithoutDestructor
(); return &descriptor; }
340
341NS_DECLARE_FRAME_PROPERTY_RELEASABLE(UninflatedTextRunProperty, gfxTextRun)static const mozilla::FramePropertyDescriptor<gfxTextRun>
* UninflatedTextRunProperty() { static const auto descriptor =
mozilla::FramePropertyDescriptor<gfxTextRun>::NewWithDestructor
<ReleaseValue>(); return &descriptor; }
342
343NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(FontSizeInflationProperty, float)static const mozilla::FramePropertyDescriptor<mozilla::SmallValueHolder
<float> >* FontSizeInflationProperty() { static const
auto descriptor = mozilla::FramePropertyDescriptor<mozilla
::SmallValueHolder<float> >::NewWithoutDestructor();
return &descriptor; }
344
345NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(HangableWhitespaceProperty, nscoord)static const mozilla::FramePropertyDescriptor<mozilla::SmallValueHolder
<nscoord> >* HangableWhitespaceProperty() { static const
auto descriptor = mozilla::FramePropertyDescriptor<mozilla
::SmallValueHolder<nscoord> >::NewWithoutDestructor(
); return &descriptor; }
346NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(TrimmableWhitespaceProperty,static const mozilla::FramePropertyDescriptor<mozilla::SmallValueHolder
<gfxTextRun::TrimmableWS> >* TrimmableWhitespaceProperty
() { static const auto descriptor = mozilla::FramePropertyDescriptor
<mozilla::SmallValueHolder<gfxTextRun::TrimmableWS> >
::NewWithoutDestructor(); return &descriptor; }
347 gfxTextRun::TrimmableWS)static const mozilla::FramePropertyDescriptor<mozilla::SmallValueHolder
<gfxTextRun::TrimmableWS> >* TrimmableWhitespaceProperty
() { static const auto descriptor = mozilla::FramePropertyDescriptor
<mozilla::SmallValueHolder<gfxTextRun::TrimmableWS> >
::NewWithoutDestructor(); return &descriptor; }
348
349struct nsTextFrame::PaintTextSelectionParams : nsTextFrame::PaintTextParams {
350 Point textBaselinePt;
351 PropertyProvider* provider = nullptr;
352 Range contentRange;
353 nsTextPaintStyle* textPaintStyle = nullptr;
354 Range glyphRange;
355 explicit PaintTextSelectionParams(const PaintTextParams& aParams)
356 : PaintTextParams(aParams) {}
357};
358
359struct nsTextFrame::DrawTextRunParams {
360 gfxContext* context;
361 mozilla::gfx::PaletteCache& paletteCache;
362 PropertyProvider* provider = nullptr;
363 gfxFloat* advanceWidth = nullptr;
364 mozilla::SVGContextPaint* contextPaint = nullptr;
365 DrawPathCallbacks* callbacks = nullptr;
366 nscolor textColor = NS_RGBA(0, 0, 0, 0)((nscolor)(((0) << 24) | ((0) << 16) | ((0) <<
8) | (0)))
;
367 nscolor textStrokeColor = NS_RGBA(0, 0, 0, 0)((nscolor)(((0) << 24) | ((0) << 16) | ((0) <<
8) | (0)))
;
368 nsAtom* fontPalette = nullptr;
369 float textStrokeWidth = 0.0f;
370 bool drawSoftHyphen = false;
371 bool hasTextShadow = false;
372 bool paintingShadows = false;
373 DrawTextRunParams(gfxContext* aContext,
374 mozilla::gfx::PaletteCache& aPaletteCache)
375 : context(aContext), paletteCache(aPaletteCache) {}
376};
377
378struct nsTextFrame::ClipEdges {
379 ClipEdges(const nsIFrame* aFrame, const nsPoint& aToReferenceFrame,
380 nscoord aVisIStartEdge, nscoord aVisIEndEdge) {
381 nsRect r = aFrame->ScrollableOverflowRect() + aToReferenceFrame;
382 if (aFrame->GetWritingMode().IsVertical()) {
383 mVisIStart = aVisIStartEdge > 0 ? r.y + aVisIStartEdge : nscoord_MIN;
384 mVisIEnd = aVisIEndEdge > 0
385 ? std::max(r.YMost() - aVisIEndEdge, mVisIStart)
386 : nscoord_MAX;
387 } else {
388 mVisIStart = aVisIStartEdge > 0 ? r.x + aVisIStartEdge : nscoord_MIN;
389 mVisIEnd = aVisIEndEdge > 0
390 ? std::max(r.XMost() - aVisIEndEdge, mVisIStart)
391 : nscoord_MAX;
392 }
393 }
394
395 void Intersect(nscoord* aVisIStart, nscoord* aVisISize) const {
396 nscoord end = *aVisIStart + *aVisISize;
397 *aVisIStart = std::max(*aVisIStart, mVisIStart);
398 *aVisISize = std::max(std::min(end, mVisIEnd) - *aVisIStart, 0);
399 }
400
401 nscoord mVisIStart;
402 nscoord mVisIEnd;
403};
404
405struct nsTextFrame::DrawTextParams : nsTextFrame::DrawTextRunParams {
406 Point framePt;
407 LayoutDeviceRect dirtyRect;
408 const nsTextPaintStyle* textStyle = nullptr;
409 const ClipEdges* clipEdges = nullptr;
410 const nscolor* decorationOverrideColor = nullptr;
411 Range glyphRange;
412 DrawTextParams(gfxContext* aContext,
413 mozilla::gfx::PaletteCache& aPaletteCache)
414 : DrawTextRunParams(aContext, aPaletteCache) {}
415};
416
417struct nsTextFrame::PaintShadowParams {
418 gfxTextRun::Range range;
419 LayoutDeviceRect dirtyRect;
420 Point framePt;
421 Point textBaselinePt;
422 gfxContext* context;
423 DrawPathCallbacks* callbacks = nullptr;
424 nscolor foregroundColor = NS_RGBA(0, 0, 0, 0)((nscolor)(((0) << 24) | ((0) << 16) | ((0) <<
8) | (0)))
;
425 const ClipEdges* clipEdges = nullptr;
426 PropertyProvider* provider = nullptr;
427 nscoord leftSideOffset = 0;
428 explicit PaintShadowParams(const PaintTextParams& aParams)
429 : dirtyRect(aParams.dirtyRect),
430 framePt(aParams.framePt),
431 context(aParams.context) {}
432};
433
434/**
435 * A glyph observer for the change of a font glyph in a text run.
436 *
437 * This is stored in {Simple, Complex}TextRunUserData.
438 */
439class GlyphObserver final : public gfxFont::GlyphChangeObserver {
440 public:
441 GlyphObserver(gfxFont* aFont, gfxTextRun* aTextRun)
442 : gfxFont::GlyphChangeObserver(aFont), mTextRun(aTextRun) {
443 MOZ_ASSERT(aTextRun->GetUserData())do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aTextRun->GetUserData())>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(aTextRun->GetUserData()))
), 0))) { do { } while (false); MOZ_ReportAssertionFailure("aTextRun->GetUserData()"
, "./../../../layout/generic/nsTextFrame.cpp", 443); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "aTextRun->GetUserData()" ")"); do { MOZ_CrashSequence
(__null, 443); __attribute__((nomerge)) ::abort(); } while (false
); } } while (false)
;
444 }
445 void NotifyGlyphsChanged() override;
446
447 private:
448 gfxTextRun* mTextRun;
449};
450
451static const nsFrameState TEXT_REFLOW_FLAGS =
452 TEXT_FIRST_LETTER | TEXT_START_OF_LINE | TEXT_END_OF_LINE |
453 TEXT_HYPHEN_BREAK | TEXT_TRIMMED_TRAILING_WHITESPACE |
454 TEXT_JUSTIFICATION_ENABLED | TEXT_HAS_NONCOLLAPSED_CHARACTERS |
455 TEXT_SELECTION_UNDERLINE_OVERFLOWED | TEXT_NO_RENDERED_GLYPHS;
456
457static const nsFrameState TEXT_WHITESPACE_FLAGS =
458 TEXT_IS_ONLY_WHITESPACE | TEXT_ISNOT_ONLY_WHITESPACE;
459
460/*
461 * Some general notes
462 *
463 * Text frames delegate work to gfxTextRun objects. The gfxTextRun object
464 * transforms text to positioned glyphs. It can report the geometry of the
465 * glyphs and paint them. Text frames configure gfxTextRuns by providing text,
466 * spacing, language, and other information.
467 *
468 * A gfxTextRun can cover more than one DOM text node. This is necessary to
469 * get kerning, ligatures and shaping for text that spans multiple text nodes
470 * but is all the same font.
471 *
472 * The userdata for a gfxTextRun object can be:
473 *
474 * - A nsTextFrame* in the case a text run maps to only one flow. In this
475 * case, the textrun's user data pointer is a pointer to mStartFrame for that
476 * flow, mDOMOffsetToBeforeTransformOffset is zero, and mContentLength is the
477 * length of the text node.
478 *
479 * - A SimpleTextRunUserData in the case a text run maps to one flow, but we
480 * still have to keep a list of glyph observers.
481 *
482 * - A ComplexTextRunUserData in the case a text run maps to multiple flows,
483 * but we need to keep a list of glyph observers.
484 *
485 * - A TextRunUserData in the case a text run maps multiple flows, but it
486 * doesn't have any glyph observer for changes in SVG fonts.
487 *
488 * You can differentiate between the four different cases with the
489 * IsSimpleFlow and MightHaveGlyphChanges flags.
490 *
491 * We go to considerable effort to make sure things work even if in-flow
492 * siblings have different ComputedStyles (i.e., first-letter and first-line).
493 *
494 * Our convention is that unsigned integer character offsets are offsets into
495 * the transformed string. Signed integer character offsets are offsets into
496 * the DOM string.
497 *
498 * XXX currently we don't handle hyphenated breaks between text frames where the
499 * hyphen occurs at the end of the first text frame, e.g.
500 * <b>Kit&shy;</b>ty
501 */
502
503/**
504 * This is our user data for the textrun, when textRun->GetFlags2() has
505 * IsSimpleFlow set, and also MightHaveGlyphChanges.
506 *
507 * This allows having an array of observers if there are fonts whose glyphs
508 * might change, but also avoid allocation in the simple case that there aren't.
509 */
510struct SimpleTextRunUserData {
511 nsTArray<UniquePtr<GlyphObserver>> mGlyphObservers;
512 nsTextFrame* mFrame;
513 explicit SimpleTextRunUserData(nsTextFrame* aFrame) : mFrame(aFrame) {}
514};
515
516/**
517 * We use an array of these objects to record which text frames
518 * are associated with the textrun. mStartFrame is the start of a list of
519 * text frames. Some sequence of its continuations are covered by the textrun.
520 * A content textnode can have at most one TextRunMappedFlow associated with it
521 * for a given textrun.
522 *
523 * mDOMOffsetToBeforeTransformOffset is added to DOM offsets for those frames to
524 * obtain the offset into the before-transformation text of the textrun. It can
525 * be positive (when a text node starts in the middle of a text run) or negative
526 * (when a text run starts in the middle of a text node). Of course it can also
527 * be zero.
528 */
529struct TextRunMappedFlow {
530 nsTextFrame* mStartFrame;
531 int32_t mDOMOffsetToBeforeTransformOffset;
532 // The text mapped starts at mStartFrame->GetContentOffset() and is this long
533 uint32_t mContentLength;
534};
535
536/**
537 * This is the type in the gfxTextRun's userdata field in the common case that
538 * the text run maps to multiple flows, but no fonts have been found with
539 * animatable glyphs.
540 *
541 * This way, we avoid allocating and constructing the extra nsTArray.
542 */
543struct TextRunUserData {
544#ifdef DEBUG1
545 TextRunMappedFlow* mMappedFlows;
546#endif
547 uint32_t mMappedFlowCount;
548 uint32_t mLastFlowIndex;
549};
550
551/**
552 * This is our user data for the textrun, when textRun->GetFlags2() does not
553 * have IsSimpleFlow set and has the MightHaveGlyphChanges flag.
554 */
555struct ComplexTextRunUserData : public TextRunUserData {
556 nsTArray<UniquePtr<GlyphObserver>> mGlyphObservers;
557};
558
559static TextRunUserData* CreateUserData(uint32_t aMappedFlowCount) {
560 TextRunUserData* data = static_cast<TextRunUserData*>(moz_xmalloc(
561 sizeof(TextRunUserData) + aMappedFlowCount * sizeof(TextRunMappedFlow)));
562#ifdef DEBUG1
563 data->mMappedFlows = reinterpret_cast<TextRunMappedFlow*>(data + 1);
564#endif
565 data->mMappedFlowCount = aMappedFlowCount;
566 data->mLastFlowIndex = 0;
567 return data;
568}
569
570static void DestroyUserData(TextRunUserData* aUserData) {
571 if (aUserData) {
572 free(aUserData);
573 }
574}
575
576static ComplexTextRunUserData* CreateComplexUserData(
577 uint32_t aMappedFlowCount) {
578 ComplexTextRunUserData* data = static_cast<ComplexTextRunUserData*>(
579 moz_xmalloc(sizeof(ComplexTextRunUserData) +
580 aMappedFlowCount * sizeof(TextRunMappedFlow)));
581 new (data) ComplexTextRunUserData();
582#ifdef DEBUG1
583 data->mMappedFlows = reinterpret_cast<TextRunMappedFlow*>(data + 1);
584#endif
585 data->mMappedFlowCount = aMappedFlowCount;
586 data->mLastFlowIndex = 0;
587 return data;
588}
589
590static void DestroyComplexUserData(ComplexTextRunUserData* aUserData) {
591 if (aUserData) {
592 aUserData->~ComplexTextRunUserData();
593 free(aUserData);
594 }
595}
596
597static void DestroyTextRunUserData(gfxTextRun* aTextRun) {
598 MOZ_ASSERT(aTextRun->GetUserData())do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aTextRun->GetUserData())>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(aTextRun->GetUserData()))
), 0))) { do { } while (false); MOZ_ReportAssertionFailure("aTextRun->GetUserData()"
, "./../../../layout/generic/nsTextFrame.cpp", 598); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "aTextRun->GetUserData()" ")"); do { MOZ_CrashSequence
(__null, 598); __attribute__((nomerge)) ::abort(); } while (false
); } } while (false)
;
599 if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow) {
600 if (aTextRun->GetFlags2() &
601 nsTextFrameUtils::Flags::MightHaveGlyphChanges) {
602 delete static_cast<SimpleTextRunUserData*>(aTextRun->GetUserData());
603 }
604 } else {
605 if (aTextRun->GetFlags2() &
606 nsTextFrameUtils::Flags::MightHaveGlyphChanges) {
607 DestroyComplexUserData(
608 static_cast<ComplexTextRunUserData*>(aTextRun->GetUserData()));
609 } else {
610 DestroyUserData(static_cast<TextRunUserData*>(aTextRun->GetUserData()));
611 }
612 }
613 aTextRun->ClearFlagBits(nsTextFrameUtils::Flags::MightHaveGlyphChanges);
614 aTextRun->SetUserData(nullptr);
615}
616
617static TextRunMappedFlow* GetMappedFlows(const gfxTextRun* aTextRun) {
618 MOZ_ASSERT(aTextRun->GetUserData(), "UserData must exist.")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aTextRun->GetUserData())>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(aTextRun->GetUserData()))
), 0))) { do { } while (false); MOZ_ReportAssertionFailure("aTextRun->GetUserData()"
" (" "UserData must exist." ")", "./../../../layout/generic/nsTextFrame.cpp"
, 618); AnnotateMozCrashReason("MOZ_ASSERT" "(" "aTextRun->GetUserData()"
") (" "UserData must exist." ")"); do { MOZ_CrashSequence(__null
, 618); __attribute__((nomerge)) ::abort(); } while (false); }
} while (false)
;
619 MOZ_ASSERT(!(aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow),do { static_assert( mozilla::detail::AssertionConditionType<
decltype(!(aTextRun->GetFlags2() & nsTextFrameUtils::Flags
::IsSimpleFlow))>::isValid, "invalid assertion condition")
; if ((__builtin_expect(!!(!(!!(!(aTextRun->GetFlags2() &
nsTextFrameUtils::Flags::IsSimpleFlow)))), 0))) { do { } while
(false); MOZ_ReportAssertionFailure("!(aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow)"
" (" "The method should not be called for simple flows." ")"
, "./../../../layout/generic/nsTextFrame.cpp", 620); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "!(aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow)"
") (" "The method should not be called for simple flows." ")"
); do { MOZ_CrashSequence(__null, 620); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
620 "The method should not be called for simple flows.")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(!(aTextRun->GetFlags2() & nsTextFrameUtils::Flags
::IsSimpleFlow))>::isValid, "invalid assertion condition")
; if ((__builtin_expect(!!(!(!!(!(aTextRun->GetFlags2() &
nsTextFrameUtils::Flags::IsSimpleFlow)))), 0))) { do { } while
(false); MOZ_ReportAssertionFailure("!(aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow)"
" (" "The method should not be called for simple flows." ")"
, "./../../../layout/generic/nsTextFrame.cpp", 620); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "!(aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow)"
") (" "The method should not be called for simple flows." ")"
); do { MOZ_CrashSequence(__null, 620); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
621 TextRunMappedFlow* flows;
622 if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::MightHaveGlyphChanges) {
623 flows = reinterpret_cast<TextRunMappedFlow*>(
624 static_cast<ComplexTextRunUserData*>(aTextRun->GetUserData()) + 1);
625 } else {
626 flows = reinterpret_cast<TextRunMappedFlow*>(
627 static_cast<TextRunUserData*>(aTextRun->GetUserData()) + 1);
628 }
629 MOZ_ASSERT(do { static_assert( mozilla::detail::AssertionConditionType<
decltype(static_cast<TextRunUserData*>(aTextRun->GetUserData
())->mMappedFlows == flows)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(static_cast<TextRunUserData
*>(aTextRun->GetUserData())->mMappedFlows == flows))
), 0))) { do { } while (false); MOZ_ReportAssertionFailure("static_cast<TextRunUserData*>(aTextRun->GetUserData())->mMappedFlows == flows"
" (" "GetMappedFlows should return the same pointer as mMappedFlows."
")", "./../../../layout/generic/nsTextFrame.cpp", 632); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "static_cast<TextRunUserData*>(aTextRun->GetUserData())->mMappedFlows == flows"
") (" "GetMappedFlows should return the same pointer as mMappedFlows."
")"); do { MOZ_CrashSequence(__null, 632); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
630 static_cast<TextRunUserData*>(aTextRun->GetUserData())->mMappedFlows ==do { static_assert( mozilla::detail::AssertionConditionType<
decltype(static_cast<TextRunUserData*>(aTextRun->GetUserData
())->mMappedFlows == flows)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(static_cast<TextRunUserData
*>(aTextRun->GetUserData())->mMappedFlows == flows))
), 0))) { do { } while (false); MOZ_ReportAssertionFailure("static_cast<TextRunUserData*>(aTextRun->GetUserData())->mMappedFlows == flows"
" (" "GetMappedFlows should return the same pointer as mMappedFlows."
")", "./../../../layout/generic/nsTextFrame.cpp", 632); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "static_cast<TextRunUserData*>(aTextRun->GetUserData())->mMappedFlows == flows"
") (" "GetMappedFlows should return the same pointer as mMappedFlows."
")"); do { MOZ_CrashSequence(__null, 632); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
631 flows,do { static_assert( mozilla::detail::AssertionConditionType<
decltype(static_cast<TextRunUserData*>(aTextRun->GetUserData
())->mMappedFlows == flows)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(static_cast<TextRunUserData
*>(aTextRun->GetUserData())->mMappedFlows == flows))
), 0))) { do { } while (false); MOZ_ReportAssertionFailure("static_cast<TextRunUserData*>(aTextRun->GetUserData())->mMappedFlows == flows"
" (" "GetMappedFlows should return the same pointer as mMappedFlows."
")", "./../../../layout/generic/nsTextFrame.cpp", 632); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "static_cast<TextRunUserData*>(aTextRun->GetUserData())->mMappedFlows == flows"
") (" "GetMappedFlows should return the same pointer as mMappedFlows."
")"); do { MOZ_CrashSequence(__null, 632); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
632 "GetMappedFlows should return the same pointer as mMappedFlows.")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(static_cast<TextRunUserData*>(aTextRun->GetUserData
())->mMappedFlows == flows)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(static_cast<TextRunUserData
*>(aTextRun->GetUserData())->mMappedFlows == flows))
), 0))) { do { } while (false); MOZ_ReportAssertionFailure("static_cast<TextRunUserData*>(aTextRun->GetUserData())->mMappedFlows == flows"
" (" "GetMappedFlows should return the same pointer as mMappedFlows."
")", "./../../../layout/generic/nsTextFrame.cpp", 632); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "static_cast<TextRunUserData*>(aTextRun->GetUserData())->mMappedFlows == flows"
") (" "GetMappedFlows should return the same pointer as mMappedFlows."
")"); do { MOZ_CrashSequence(__null, 632); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
633 return flows;
634}
635
636/**
637 * These are utility functions just for helping with the complexity related with
638 * the text runs user data.
639 */
640static nsTextFrame* GetFrameForSimpleFlow(const gfxTextRun* aTextRun) {
641 MOZ_ASSERT(aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow,do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aTextRun->GetFlags2() & nsTextFrameUtils::Flags
::IsSimpleFlow)>::isValid, "invalid assertion condition");
if ((__builtin_expect(!!(!(!!(aTextRun->GetFlags2() &
nsTextFrameUtils::Flags::IsSimpleFlow))), 0))) { do { } while
(false); MOZ_ReportAssertionFailure("aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow"
" (" "Not so simple flow?" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 642); AnnotateMozCrashReason("MOZ_ASSERT" "(" "aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow"
") (" "Not so simple flow?" ")"); do { MOZ_CrashSequence(__null
, 642); __attribute__((nomerge)) ::abort(); } while (false); }
} while (false)
642 "Not so simple flow?")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aTextRun->GetFlags2() & nsTextFrameUtils::Flags
::IsSimpleFlow)>::isValid, "invalid assertion condition");
if ((__builtin_expect(!!(!(!!(aTextRun->GetFlags2() &
nsTextFrameUtils::Flags::IsSimpleFlow))), 0))) { do { } while
(false); MOZ_ReportAssertionFailure("aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow"
" (" "Not so simple flow?" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 642); AnnotateMozCrashReason("MOZ_ASSERT" "(" "aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow"
") (" "Not so simple flow?" ")"); do { MOZ_CrashSequence(__null
, 642); __attribute__((nomerge)) ::abort(); } while (false); }
} while (false)
;
643 if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::MightHaveGlyphChanges) {
644 return static_cast<SimpleTextRunUserData*>(aTextRun->GetUserData())->mFrame;
645 }
646
647 return static_cast<nsTextFrame*>(aTextRun->GetUserData());
648}
649
650/**
651 * Remove |aTextRun| from the frame continuation chain starting at
652 * |aStartContinuation| if non-null, otherwise starting at |aFrame|.
653 * Unmark |aFrame| as a text run owner if it's the frame we start at.
654 * Return true if |aStartContinuation| is non-null and was found
655 * in the next-continuation chain of |aFrame|.
656 */
657static bool ClearAllTextRunReferences(nsTextFrame* aFrame, gfxTextRun* aTextRun,
658 nsTextFrame* aStartContinuation,
659 nsFrameState aWhichTextRunState) {
660 MOZ_ASSERT(aFrame, "null frame")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aFrame)>::isValid, "invalid assertion condition")
; if ((__builtin_expect(!!(!(!!(aFrame))), 0))) { do { } while
(false); MOZ_ReportAssertionFailure("aFrame" " (" "null frame"
")", "./../../../layout/generic/nsTextFrame.cpp", 660); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "aFrame" ") (" "null frame" ")"); do { MOZ_CrashSequence
(__null, 660); __attribute__((nomerge)) ::abort(); } while (false
); } } while (false)
;
661 MOZ_ASSERT(!aStartContinuation ||do { static_assert( mozilla::detail::AssertionConditionType<
decltype(!aStartContinuation || (!aStartContinuation->GetTextRun
(nsTextFrame::eInflated) || aStartContinuation->GetTextRun
(nsTextFrame::eInflated) == aTextRun) || (!aStartContinuation
->GetTextRun(nsTextFrame::eNotInflated) || aStartContinuation
->GetTextRun(nsTextFrame::eNotInflated) == aTextRun))>::
isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(!aStartContinuation || (!aStartContinuation->GetTextRun
(nsTextFrame::eInflated) || aStartContinuation->GetTextRun
(nsTextFrame::eInflated) == aTextRun) || (!aStartContinuation
->GetTextRun(nsTextFrame::eNotInflated) || aStartContinuation
->GetTextRun(nsTextFrame::eNotInflated) == aTextRun)))), 0
))) { do { } while (false); MOZ_ReportAssertionFailure("!aStartContinuation || (!aStartContinuation->GetTextRun(nsTextFrame::eInflated) || aStartContinuation->GetTextRun(nsTextFrame::eInflated) == aTextRun) || (!aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) || aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) == aTextRun)"
" (" "wrong aStartContinuation for this text run" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 668); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!aStartContinuation || (!aStartContinuation->GetTextRun(nsTextFrame::eInflated) || aStartContinuation->GetTextRun(nsTextFrame::eInflated) == aTextRun) || (!aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) || aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) == aTextRun)"
") (" "wrong aStartContinuation for this text run" ")"); do {
MOZ_CrashSequence(__null, 668); __attribute__((nomerge)) ::abort
(); } while (false); } } while (false)
662 (!aStartContinuation->GetTextRun(nsTextFrame::eInflated) ||do { static_assert( mozilla::detail::AssertionConditionType<
decltype(!aStartContinuation || (!aStartContinuation->GetTextRun
(nsTextFrame::eInflated) || aStartContinuation->GetTextRun
(nsTextFrame::eInflated) == aTextRun) || (!aStartContinuation
->GetTextRun(nsTextFrame::eNotInflated) || aStartContinuation
->GetTextRun(nsTextFrame::eNotInflated) == aTextRun))>::
isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(!aStartContinuation || (!aStartContinuation->GetTextRun
(nsTextFrame::eInflated) || aStartContinuation->GetTextRun
(nsTextFrame::eInflated) == aTextRun) || (!aStartContinuation
->GetTextRun(nsTextFrame::eNotInflated) || aStartContinuation
->GetTextRun(nsTextFrame::eNotInflated) == aTextRun)))), 0
))) { do { } while (false); MOZ_ReportAssertionFailure("!aStartContinuation || (!aStartContinuation->GetTextRun(nsTextFrame::eInflated) || aStartContinuation->GetTextRun(nsTextFrame::eInflated) == aTextRun) || (!aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) || aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) == aTextRun)"
" (" "wrong aStartContinuation for this text run" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 668); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!aStartContinuation || (!aStartContinuation->GetTextRun(nsTextFrame::eInflated) || aStartContinuation->GetTextRun(nsTextFrame::eInflated) == aTextRun) || (!aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) || aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) == aTextRun)"
") (" "wrong aStartContinuation for this text run" ")"); do {
MOZ_CrashSequence(__null, 668); __attribute__((nomerge)) ::abort
(); } while (false); } } while (false)
663 aStartContinuation->GetTextRun(nsTextFrame::eInflated) ==do { static_assert( mozilla::detail::AssertionConditionType<
decltype(!aStartContinuation || (!aStartContinuation->GetTextRun
(nsTextFrame::eInflated) || aStartContinuation->GetTextRun
(nsTextFrame::eInflated) == aTextRun) || (!aStartContinuation
->GetTextRun(nsTextFrame::eNotInflated) || aStartContinuation
->GetTextRun(nsTextFrame::eNotInflated) == aTextRun))>::
isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(!aStartContinuation || (!aStartContinuation->GetTextRun
(nsTextFrame::eInflated) || aStartContinuation->GetTextRun
(nsTextFrame::eInflated) == aTextRun) || (!aStartContinuation
->GetTextRun(nsTextFrame::eNotInflated) || aStartContinuation
->GetTextRun(nsTextFrame::eNotInflated) == aTextRun)))), 0
))) { do { } while (false); MOZ_ReportAssertionFailure("!aStartContinuation || (!aStartContinuation->GetTextRun(nsTextFrame::eInflated) || aStartContinuation->GetTextRun(nsTextFrame::eInflated) == aTextRun) || (!aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) || aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) == aTextRun)"
" (" "wrong aStartContinuation for this text run" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 668); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!aStartContinuation || (!aStartContinuation->GetTextRun(nsTextFrame::eInflated) || aStartContinuation->GetTextRun(nsTextFrame::eInflated) == aTextRun) || (!aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) || aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) == aTextRun)"
") (" "wrong aStartContinuation for this text run" ")"); do {
MOZ_CrashSequence(__null, 668); __attribute__((nomerge)) ::abort
(); } while (false); } } while (false)
664 aTextRun) ||do { static_assert( mozilla::detail::AssertionConditionType<
decltype(!aStartContinuation || (!aStartContinuation->GetTextRun
(nsTextFrame::eInflated) || aStartContinuation->GetTextRun
(nsTextFrame::eInflated) == aTextRun) || (!aStartContinuation
->GetTextRun(nsTextFrame::eNotInflated) || aStartContinuation
->GetTextRun(nsTextFrame::eNotInflated) == aTextRun))>::
isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(!aStartContinuation || (!aStartContinuation->GetTextRun
(nsTextFrame::eInflated) || aStartContinuation->GetTextRun
(nsTextFrame::eInflated) == aTextRun) || (!aStartContinuation
->GetTextRun(nsTextFrame::eNotInflated) || aStartContinuation
->GetTextRun(nsTextFrame::eNotInflated) == aTextRun)))), 0
))) { do { } while (false); MOZ_ReportAssertionFailure("!aStartContinuation || (!aStartContinuation->GetTextRun(nsTextFrame::eInflated) || aStartContinuation->GetTextRun(nsTextFrame::eInflated) == aTextRun) || (!aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) || aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) == aTextRun)"
" (" "wrong aStartContinuation for this text run" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 668); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!aStartContinuation || (!aStartContinuation->GetTextRun(nsTextFrame::eInflated) || aStartContinuation->GetTextRun(nsTextFrame::eInflated) == aTextRun) || (!aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) || aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) == aTextRun)"
") (" "wrong aStartContinuation for this text run" ")"); do {
MOZ_CrashSequence(__null, 668); __attribute__((nomerge)) ::abort
(); } while (false); } } while (false)
665 (!aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) ||do { static_assert( mozilla::detail::AssertionConditionType<
decltype(!aStartContinuation || (!aStartContinuation->GetTextRun
(nsTextFrame::eInflated) || aStartContinuation->GetTextRun
(nsTextFrame::eInflated) == aTextRun) || (!aStartContinuation
->GetTextRun(nsTextFrame::eNotInflated) || aStartContinuation
->GetTextRun(nsTextFrame::eNotInflated) == aTextRun))>::
isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(!aStartContinuation || (!aStartContinuation->GetTextRun
(nsTextFrame::eInflated) || aStartContinuation->GetTextRun
(nsTextFrame::eInflated) == aTextRun) || (!aStartContinuation
->GetTextRun(nsTextFrame::eNotInflated) || aStartContinuation
->GetTextRun(nsTextFrame::eNotInflated) == aTextRun)))), 0
))) { do { } while (false); MOZ_ReportAssertionFailure("!aStartContinuation || (!aStartContinuation->GetTextRun(nsTextFrame::eInflated) || aStartContinuation->GetTextRun(nsTextFrame::eInflated) == aTextRun) || (!aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) || aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) == aTextRun)"
" (" "wrong aStartContinuation for this text run" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 668); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!aStartContinuation || (!aStartContinuation->GetTextRun(nsTextFrame::eInflated) || aStartContinuation->GetTextRun(nsTextFrame::eInflated) == aTextRun) || (!aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) || aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) == aTextRun)"
") (" "wrong aStartContinuation for this text run" ")"); do {
MOZ_CrashSequence(__null, 668); __attribute__((nomerge)) ::abort
(); } while (false); } } while (false)
666 aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) ==do { static_assert( mozilla::detail::AssertionConditionType<
decltype(!aStartContinuation || (!aStartContinuation->GetTextRun
(nsTextFrame::eInflated) || aStartContinuation->GetTextRun
(nsTextFrame::eInflated) == aTextRun) || (!aStartContinuation
->GetTextRun(nsTextFrame::eNotInflated) || aStartContinuation
->GetTextRun(nsTextFrame::eNotInflated) == aTextRun))>::
isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(!aStartContinuation || (!aStartContinuation->GetTextRun
(nsTextFrame::eInflated) || aStartContinuation->GetTextRun
(nsTextFrame::eInflated) == aTextRun) || (!aStartContinuation
->GetTextRun(nsTextFrame::eNotInflated) || aStartContinuation
->GetTextRun(nsTextFrame::eNotInflated) == aTextRun)))), 0
))) { do { } while (false); MOZ_ReportAssertionFailure("!aStartContinuation || (!aStartContinuation->GetTextRun(nsTextFrame::eInflated) || aStartContinuation->GetTextRun(nsTextFrame::eInflated) == aTextRun) || (!aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) || aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) == aTextRun)"
" (" "wrong aStartContinuation for this text run" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 668); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!aStartContinuation || (!aStartContinuation->GetTextRun(nsTextFrame::eInflated) || aStartContinuation->GetTextRun(nsTextFrame::eInflated) == aTextRun) || (!aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) || aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) == aTextRun)"
") (" "wrong aStartContinuation for this text run" ")"); do {
MOZ_CrashSequence(__null, 668); __attribute__((nomerge)) ::abort
(); } while (false); } } while (false)
667 aTextRun),do { static_assert( mozilla::detail::AssertionConditionType<
decltype(!aStartContinuation || (!aStartContinuation->GetTextRun
(nsTextFrame::eInflated) || aStartContinuation->GetTextRun
(nsTextFrame::eInflated) == aTextRun) || (!aStartContinuation
->GetTextRun(nsTextFrame::eNotInflated) || aStartContinuation
->GetTextRun(nsTextFrame::eNotInflated) == aTextRun))>::
isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(!aStartContinuation || (!aStartContinuation->GetTextRun
(nsTextFrame::eInflated) || aStartContinuation->GetTextRun
(nsTextFrame::eInflated) == aTextRun) || (!aStartContinuation
->GetTextRun(nsTextFrame::eNotInflated) || aStartContinuation
->GetTextRun(nsTextFrame::eNotInflated) == aTextRun)))), 0
))) { do { } while (false); MOZ_ReportAssertionFailure("!aStartContinuation || (!aStartContinuation->GetTextRun(nsTextFrame::eInflated) || aStartContinuation->GetTextRun(nsTextFrame::eInflated) == aTextRun) || (!aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) || aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) == aTextRun)"
" (" "wrong aStartContinuation for this text run" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 668); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!aStartContinuation || (!aStartContinuation->GetTextRun(nsTextFrame::eInflated) || aStartContinuation->GetTextRun(nsTextFrame::eInflated) == aTextRun) || (!aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) || aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) == aTextRun)"
") (" "wrong aStartContinuation for this text run" ")"); do {
MOZ_CrashSequence(__null, 668); __attribute__((nomerge)) ::abort
(); } while (false); } } while (false)
668 "wrong aStartContinuation for this text run")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(!aStartContinuation || (!aStartContinuation->GetTextRun
(nsTextFrame::eInflated) || aStartContinuation->GetTextRun
(nsTextFrame::eInflated) == aTextRun) || (!aStartContinuation
->GetTextRun(nsTextFrame::eNotInflated) || aStartContinuation
->GetTextRun(nsTextFrame::eNotInflated) == aTextRun))>::
isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(!aStartContinuation || (!aStartContinuation->GetTextRun
(nsTextFrame::eInflated) || aStartContinuation->GetTextRun
(nsTextFrame::eInflated) == aTextRun) || (!aStartContinuation
->GetTextRun(nsTextFrame::eNotInflated) || aStartContinuation
->GetTextRun(nsTextFrame::eNotInflated) == aTextRun)))), 0
))) { do { } while (false); MOZ_ReportAssertionFailure("!aStartContinuation || (!aStartContinuation->GetTextRun(nsTextFrame::eInflated) || aStartContinuation->GetTextRun(nsTextFrame::eInflated) == aTextRun) || (!aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) || aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) == aTextRun)"
" (" "wrong aStartContinuation for this text run" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 668); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!aStartContinuation || (!aStartContinuation->GetTextRun(nsTextFrame::eInflated) || aStartContinuation->GetTextRun(nsTextFrame::eInflated) == aTextRun) || (!aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) || aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) == aTextRun)"
") (" "wrong aStartContinuation for this text run" ")"); do {
MOZ_CrashSequence(__null, 668); __attribute__((nomerge)) ::abort
(); } while (false); } } while (false)
;
669
670 if (!aStartContinuation || aStartContinuation == aFrame) {
671 aFrame->RemoveStateBits(aWhichTextRunState);
672 } else {
673 do {
674 NS_ASSERTION(aFrame->IsTextFrame(), "Bad frame")do { if (!(aFrame->IsTextFrame())) { NS_DebugBreak(NS_DEBUG_ASSERTION
, "Bad frame", "aFrame->IsTextFrame()", "./../../../layout/generic/nsTextFrame.cpp"
, 674); MOZ_PretendNoReturn(); } } while (0)
;
675 aFrame = aFrame->GetNextContinuation();
676 } while (aFrame && aFrame != aStartContinuation);
677 }
678 bool found = aStartContinuation == aFrame;
679 while (aFrame) {
680 NS_ASSERTION(aFrame->IsTextFrame(), "Bad frame")do { if (!(aFrame->IsTextFrame())) { NS_DebugBreak(NS_DEBUG_ASSERTION
, "Bad frame", "aFrame->IsTextFrame()", "./../../../layout/generic/nsTextFrame.cpp"
, 680); MOZ_PretendNoReturn(); } } while (0)
;
681 if (!aFrame->RemoveTextRun(aTextRun)) {
682 break;
683 }
684 aFrame = aFrame->GetNextContinuation();
685 }
686
687 MOZ_ASSERT(!found || aStartContinuation, "how did we find null?")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(!found || aStartContinuation)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(!found || aStartContinuation
))), 0))) { do { } while (false); MOZ_ReportAssertionFailure(
"!found || aStartContinuation" " (" "how did we find null?" ")"
, "./../../../layout/generic/nsTextFrame.cpp", 687); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "!found || aStartContinuation" ") (" "how did we find null?"
")"); do { MOZ_CrashSequence(__null, 687); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
688 return found;
689}
690
691/**
692 * Kill all references to |aTextRun| starting at |aStartContinuation|.
693 * It could be referenced by any of its owners, and all their in-flows.
694 * If |aStartContinuation| is null then process all userdata frames
695 * and their continuations.
696 * @note the caller is expected to take care of possibly destroying the
697 * text run if all userdata frames were reset (userdata is deallocated
698 * by this function though). The caller can detect this has occured by
699 * checking |aTextRun->GetUserData() == nullptr|.
700 */
701static void UnhookTextRunFromFrames(gfxTextRun* aTextRun,
702 nsTextFrame* aStartContinuation) {
703 if (!aTextRun->GetUserData()) {
704 return;
705 }
706
707 if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow) {
708 nsTextFrame* userDataFrame = GetFrameForSimpleFlow(aTextRun);
709 nsFrameState whichTextRunState =
710 userDataFrame->GetTextRun(nsTextFrame::eInflated) == aTextRun
711 ? TEXT_IN_TEXTRUN_USER_DATA
712 : TEXT_IN_UNINFLATED_TEXTRUN_USER_DATA;
713 DebugOnly<bool> found = ClearAllTextRunReferences(
714 userDataFrame, aTextRun, aStartContinuation, whichTextRunState);
715 NS_ASSERTION(!aStartContinuation || found,do { if (!(!aStartContinuation || found)) { NS_DebugBreak(NS_DEBUG_ASSERTION
, "aStartContinuation wasn't found in simple flow text run", "!aStartContinuation || found"
, "./../../../layout/generic/nsTextFrame.cpp", 716); MOZ_PretendNoReturn
(); } } while (0)
716 "aStartContinuation wasn't found in simple flow text run")do { if (!(!aStartContinuation || found)) { NS_DebugBreak(NS_DEBUG_ASSERTION
, "aStartContinuation wasn't found in simple flow text run", "!aStartContinuation || found"
, "./../../../layout/generic/nsTextFrame.cpp", 716); MOZ_PretendNoReturn
(); } } while (0)
;
717 if (!userDataFrame->HasAnyStateBits(whichTextRunState)) {
718 DestroyTextRunUserData(aTextRun);
719 }
720 } else {
721 auto userData = static_cast<TextRunUserData*>(aTextRun->GetUserData());
722 TextRunMappedFlow* userMappedFlows = GetMappedFlows(aTextRun);
723 int32_t destroyFromIndex = aStartContinuation ? -1 : 0;
724 for (uint32_t i = 0; i < userData->mMappedFlowCount; ++i) {
725 nsTextFrame* userDataFrame = userMappedFlows[i].mStartFrame;
726 nsFrameState whichTextRunState =
727 userDataFrame->GetTextRun(nsTextFrame::eInflated) == aTextRun
728 ? TEXT_IN_TEXTRUN_USER_DATA
729 : TEXT_IN_UNINFLATED_TEXTRUN_USER_DATA;
730 bool found = ClearAllTextRunReferences(
731 userDataFrame, aTextRun, aStartContinuation, whichTextRunState);
732 if (found) {
733 if (userDataFrame->HasAnyStateBits(whichTextRunState)) {
734 destroyFromIndex = i + 1;
735 } else {
736 destroyFromIndex = i;
737 }
738 aStartContinuation = nullptr;
739 }
740 }
741 NS_ASSERTION(destroyFromIndex >= 0,do { if (!(destroyFromIndex >= 0)) { NS_DebugBreak(NS_DEBUG_ASSERTION
, "aStartContinuation wasn't found in multi flow text run", "destroyFromIndex >= 0"
, "./../../../layout/generic/nsTextFrame.cpp", 742); MOZ_PretendNoReturn
(); } } while (0)
742 "aStartContinuation wasn't found in multi flow text run")do { if (!(destroyFromIndex >= 0)) { NS_DebugBreak(NS_DEBUG_ASSERTION
, "aStartContinuation wasn't found in multi flow text run", "destroyFromIndex >= 0"
, "./../../../layout/generic/nsTextFrame.cpp", 742); MOZ_PretendNoReturn
(); } } while (0)
;
743 if (destroyFromIndex == 0) {
744 DestroyTextRunUserData(aTextRun);
745 } else {
746 userData->mMappedFlowCount = uint32_t(destroyFromIndex);
747 if (userData->mLastFlowIndex >= uint32_t(destroyFromIndex)) {
748 userData->mLastFlowIndex = uint32_t(destroyFromIndex) - 1;
749 }
750 }
751 }
752}
753
754static void InvalidateFrameDueToGlyphsChanged(nsIFrame* aFrame) {
755 MOZ_ASSERT(aFrame)do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aFrame)>::isValid, "invalid assertion condition")
; if ((__builtin_expect(!!(!(!!(aFrame))), 0))) { do { } while
(false); MOZ_ReportAssertionFailure("aFrame", "./../../../layout/generic/nsTextFrame.cpp"
, 755); AnnotateMozCrashReason("MOZ_ASSERT" "(" "aFrame" ")")
; do { MOZ_CrashSequence(__null, 755); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
756
757 PresShell* presShell = aFrame->PresShell();
758 for (nsIFrame* f = aFrame; f;
759 f = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(f)) {
760 f->InvalidateFrame();
761
762 // If this is a non-display text frame within SVG <text>, we need
763 // to reflow the SVGTextFrame. (This is similar to reflowing the
764 // SVGTextFrame in response to style changes, in
765 // SVGTextFrame::DidSetComputedStyle.)
766 if (f->IsInSVGTextSubtree() && f->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) {
767 auto* svgTextFrame = static_cast<SVGTextFrame*>(
768 nsLayoutUtils::GetClosestFrameOfType(f, LayoutFrameType::SVGText));
769 svgTextFrame->ScheduleReflowSVGNonDisplayText(IntrinsicDirty::None);
770 } else {
771 // Theoretically we could just update overflow areas, perhaps using
772 // OverflowChangedTracker, but that would do a bunch of work eagerly that
773 // we should probably do lazily here since there could be a lot
774 // of text frames affected and we'd like to coalesce the work. So that's
775 // not easy to do well.
776 // TODO(emilio): If we allow text to store changes or so, this could use
777 // PostRestyleEvent(..., UpdateOverflow);
778 presShell->FrameNeedsReflow(f, IntrinsicDirty::None, NS_FRAME_IS_DIRTY);
779 }
780 }
781}
782
783void GlyphObserver::NotifyGlyphsChanged() {
784 if (mTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow) {
785 InvalidateFrameDueToGlyphsChanged(GetFrameForSimpleFlow(mTextRun));
786 return;
787 }
788
789 auto data = static_cast<TextRunUserData*>(mTextRun->GetUserData());
790 TextRunMappedFlow* userMappedFlows = GetMappedFlows(mTextRun);
791 for (uint32_t i = 0; i < data->mMappedFlowCount; ++i) {
792 InvalidateFrameDueToGlyphsChanged(userMappedFlows[i].mStartFrame);
793 }
794}
795
796int32_t nsTextFrame::GetContentEnd() const {
797 nsTextFrame* next = GetNextContinuation();
798 // In case of allocation failure when setting/modifying the text buffer,
799 // it's possible our text might be missing. So we check the buffer length,
800 // in addition to the offset of the next continuation (if any).
801 int32_t bufferLen = CharacterDataBuffer().GetLength();
802 return next ? std::min(bufferLen, next->GetContentOffset()) : bufferLen;
803}
804
805struct FlowLengthProperty {
806 int32_t mStartOffset;
807 // The offset of the next fixed continuation after mStartOffset, or
808 // of the end of the text if there is none
809 int32_t mEndFlowOffset;
810};
811
812int32_t nsTextFrame::GetInFlowContentLength() {
813 if (!HasAnyStateBits(NS_FRAME_IS_BIDI)) {
814 return mContent->TextLength() - mContentOffset;
815 }
816
817 FlowLengthProperty* flowLength =
818 mContent->HasFlag(NS_HAS_FLOWLENGTH_PROPERTY)
819 ? static_cast<FlowLengthProperty*>(
820 mContent->GetProperty(nsGkAtoms::flowlength))
821 : nullptr;
822 MOZ_ASSERT(mContent->HasFlag(NS_HAS_FLOWLENGTH_PROPERTY) == !!flowLength,do { static_assert( mozilla::detail::AssertionConditionType<
decltype(mContent->HasFlag(NS_HAS_FLOWLENGTH_PROPERTY) == !
!flowLength)>::isValid, "invalid assertion condition"); if
((__builtin_expect(!!(!(!!(mContent->HasFlag(NS_HAS_FLOWLENGTH_PROPERTY
) == !!flowLength))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("mContent->HasFlag(NS_HAS_FLOWLENGTH_PROPERTY) == !!flowLength"
" (" "incorrect NS_HAS_FLOWLENGTH_PROPERTY flag" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 823); AnnotateMozCrashReason("MOZ_ASSERT" "(" "mContent->HasFlag(NS_HAS_FLOWLENGTH_PROPERTY) == !!flowLength"
") (" "incorrect NS_HAS_FLOWLENGTH_PROPERTY flag" ")"); do {
MOZ_CrashSequence(__null, 823); __attribute__((nomerge)) ::abort
(); } while (false); } } while (false)
823 "incorrect NS_HAS_FLOWLENGTH_PROPERTY flag")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(mContent->HasFlag(NS_HAS_FLOWLENGTH_PROPERTY) == !
!flowLength)>::isValid, "invalid assertion condition"); if
((__builtin_expect(!!(!(!!(mContent->HasFlag(NS_HAS_FLOWLENGTH_PROPERTY
) == !!flowLength))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("mContent->HasFlag(NS_HAS_FLOWLENGTH_PROPERTY) == !!flowLength"
" (" "incorrect NS_HAS_FLOWLENGTH_PROPERTY flag" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 823); AnnotateMozCrashReason("MOZ_ASSERT" "(" "mContent->HasFlag(NS_HAS_FLOWLENGTH_PROPERTY) == !!flowLength"
") (" "incorrect NS_HAS_FLOWLENGTH_PROPERTY flag" ")"); do {
MOZ_CrashSequence(__null, 823); __attribute__((nomerge)) ::abort
(); } while (false); } } while (false)
;
824 /**
825 * This frame must start inside the cached flow. If the flow starts at
826 * mContentOffset but this frame is empty, logically it might be before the
827 * start of the cached flow.
828 */
829 if (flowLength &&
830 (flowLength->mStartOffset < mContentOffset ||
831 (flowLength->mStartOffset == mContentOffset &&
832 GetContentEnd() > mContentOffset)) &&
833 flowLength->mEndFlowOffset > mContentOffset) {
834#ifdef DEBUG1
835 NS_ASSERTION(flowLength->mEndFlowOffset >= GetContentEnd(),do { if (!(flowLength->mEndFlowOffset >= GetContentEnd(
))) { NS_DebugBreak(NS_DEBUG_ASSERTION, "frame crosses fixed continuation boundary"
, "flowLength->mEndFlowOffset >= GetContentEnd()", "./../../../layout/generic/nsTextFrame.cpp"
, 836); MOZ_PretendNoReturn(); } } while (0)
836 "frame crosses fixed continuation boundary")do { if (!(flowLength->mEndFlowOffset >= GetContentEnd(
))) { NS_DebugBreak(NS_DEBUG_ASSERTION, "frame crosses fixed continuation boundary"
, "flowLength->mEndFlowOffset >= GetContentEnd()", "./../../../layout/generic/nsTextFrame.cpp"
, 836); MOZ_PretendNoReturn(); } } while (0)
;
837#endif
838 return flowLength->mEndFlowOffset - mContentOffset;
839 }
840
841 nsTextFrame* nextBidi = LastInFlow()->GetNextContinuation();
842 int32_t endFlow =
843 nextBidi ? nextBidi->GetContentOffset() : GetContent()->TextLength();
844
845 if (!flowLength) {
846 flowLength = new FlowLengthProperty;
847 if (NS_FAILED(mContent->SetProperty(((bool)(__builtin_expect(!!(NS_FAILED_impl(mContent->SetProperty
( nsGkAtoms::flowlength, flowLength, nsINode::DeleteProperty<
FlowLengthProperty>))), 0)))
848 nsGkAtoms::flowlength, flowLength,((bool)(__builtin_expect(!!(NS_FAILED_impl(mContent->SetProperty
( nsGkAtoms::flowlength, flowLength, nsINode::DeleteProperty<
FlowLengthProperty>))), 0)))
849 nsINode::DeleteProperty<FlowLengthProperty>))((bool)(__builtin_expect(!!(NS_FAILED_impl(mContent->SetProperty
( nsGkAtoms::flowlength, flowLength, nsINode::DeleteProperty<
FlowLengthProperty>))), 0)))
) {
850 delete flowLength;
851 flowLength = nullptr;
852 } else {
853 mContent->SetFlags(NS_HAS_FLOWLENGTH_PROPERTY);
854 }
855 }
856 if (flowLength) {
857 flowLength->mStartOffset = mContentOffset;
858 flowLength->mEndFlowOffset = endFlow;
859 }
860
861 return endFlow - mContentOffset;
862}
863
864// Smarter versions of dom::IsSpaceCharacter.
865// Unicode is really annoying; sometimes a space character isn't whitespace ---
866// when it combines with another character
867// So we have several versions of IsSpace for use in different contexts.
868
869static bool IsSpaceCombiningSequenceTail(const CharacterDataBuffer& aBuffer,
870 uint32_t aPos) {
871 NS_ASSERTION(aPos <= aBuffer.GetLength(), "Bad offset")do { if (!(aPos <= aBuffer.GetLength())) { NS_DebugBreak(NS_DEBUG_ASSERTION
, "Bad offset", "aPos <= aBuffer.GetLength()", "./../../../layout/generic/nsTextFrame.cpp"
, 871); MOZ_PretendNoReturn(); } } while (0)
;
872 if (!aBuffer.Is2b()) {
873 return false;
874 }
875 return nsTextFrameUtils::IsSpaceCombiningSequenceTail(
876 aBuffer.Get2b() + aPos, aBuffer.GetLength() - aPos);
877}
878
879// Check whether aPos is a space for CSS 'word-spacing' purposes
880static bool IsCSSWordSpacingSpace(const CharacterDataBuffer& aBuffer,
881 uint32_t aPos, const nsTextFrame* aFrame,
882 const nsStyleText* aStyleText) {
883 NS_ASSERTION(aPos < aBuffer.GetLength(), "No text for IsSpace!")do { if (!(aPos < aBuffer.GetLength())) { NS_DebugBreak(NS_DEBUG_ASSERTION
, "No text for IsSpace!", "aPos < aBuffer.GetLength()", "./../../../layout/generic/nsTextFrame.cpp"
, 883); MOZ_PretendNoReturn(); } } while (0)
;
884
885 char16_t ch = aBuffer.CharAt(aPos);
886 switch (ch) {
887 case ' ':
888 case CH_NBSP160:
889 return !IsSpaceCombiningSequenceTail(aBuffer, aPos + 1);
890 case '\r':
891 case '\t':
892 return !aStyleText->WhiteSpaceIsSignificant();
893 case '\n':
894 return !aStyleText->NewlineIsSignificant(aFrame);
895 default:
896 return false;
897 }
898}
899
900constexpr char16_t kOghamSpaceMark = 0x1680;
901
902// Check whether the string aChars/aLength starts with space that's
903// trimmable according to CSS 'white-space:normal/nowrap'.
904static bool IsTrimmableSpace(const char16_t* aChars, uint32_t aLength) {
905 NS_ASSERTION(aLength > 0, "No text for IsSpace!")do { if (!(aLength > 0)) { NS_DebugBreak(NS_DEBUG_ASSERTION
, "No text for IsSpace!", "aLength > 0", "./../../../layout/generic/nsTextFrame.cpp"
, 905); MOZ_PretendNoReturn(); } } while (0)
;
906
907 char16_t ch = *aChars;
908 if (ch == ' ' || ch == kOghamSpaceMark) {
909 return !nsTextFrameUtils::IsSpaceCombiningSequenceTail(aChars + 1,
910 aLength - 1);
911 }
912 return ch == '\t' || ch == '\f' || ch == '\n' || ch == '\r';
913}
914
915// Check whether the character aCh is trimmable according to CSS
916// 'white-space:normal/nowrap'
917static bool IsTrimmableSpace(char aCh) {
918 return aCh == ' ' || aCh == '\t' || aCh == '\f' || aCh == '\n' || aCh == '\r';
919}
920
921static bool IsTrimmableSpace(const CharacterDataBuffer& aBuffer, uint32_t aPos,
922 const nsStyleText* aStyleText,
923 bool aAllowHangingWS = false) {
924 NS_ASSERTION(aPos < aBuffer.GetLength(), "No text for IsSpace!")do { if (!(aPos < aBuffer.GetLength())) { NS_DebugBreak(NS_DEBUG_ASSERTION
, "No text for IsSpace!", "aPos < aBuffer.GetLength()", "./../../../layout/generic/nsTextFrame.cpp"
, 924); MOZ_PretendNoReturn(); } } while (0)
;
925
926 switch (aBuffer.CharAt(aPos)) {
927 case ' ':
928 case kOghamSpaceMark:
929 return (!aStyleText->WhiteSpaceIsSignificant() || aAllowHangingWS) &&
930 !IsSpaceCombiningSequenceTail(aBuffer, aPos + 1);
931 case '\n':
932 return !aStyleText->NewlineIsSignificantStyle() &&
933 aStyleText->mWhiteSpaceCollapse !=
934 StyleWhiteSpaceCollapse::PreserveSpaces;
935 case '\t':
936 case '\r':
937 case '\f':
938 return !aStyleText->WhiteSpaceIsSignificant() || aAllowHangingWS;
939 default:
940 return false;
941 }
942}
943
944static bool IsSelectionInlineWhitespace(const CharacterDataBuffer& aBuffer,
945 uint32_t aPos) {
946 NS_ASSERTION(aPos < aBuffer.GetLength(),do { if (!(aPos < aBuffer.GetLength())) { NS_DebugBreak(NS_DEBUG_ASSERTION
, "No text for IsSelectionInlineWhitespace!", "aPos < aBuffer.GetLength()"
, "./../../../layout/generic/nsTextFrame.cpp", 947); MOZ_PretendNoReturn
(); } } while (0)
947 "No text for IsSelectionInlineWhitespace!")do { if (!(aPos < aBuffer.GetLength())) { NS_DebugBreak(NS_DEBUG_ASSERTION
, "No text for IsSelectionInlineWhitespace!", "aPos < aBuffer.GetLength()"
, "./../../../layout/generic/nsTextFrame.cpp", 947); MOZ_PretendNoReturn
(); } } while (0)
;
948 char16_t ch = aBuffer.CharAt(aPos);
949 if (ch == ' ' || ch == CH_NBSP160) {
950 return !IsSpaceCombiningSequenceTail(aBuffer, aPos + 1);
951 }
952 return ch == '\t' || ch == '\f';
953}
954
955static bool IsSelectionNewline(const CharacterDataBuffer& aBuffer,
956 uint32_t aPos) {
957 NS_ASSERTION(aPos < aBuffer.GetLength(), "No text for IsSelectionNewline!")do { if (!(aPos < aBuffer.GetLength())) { NS_DebugBreak(NS_DEBUG_ASSERTION
, "No text for IsSelectionNewline!", "aPos < aBuffer.GetLength()"
, "./../../../layout/generic/nsTextFrame.cpp", 957); MOZ_PretendNoReturn
(); } } while (0)
;
958 char16_t ch = aBuffer.CharAt(aPos);
959 return ch == '\n' || ch == '\r';
960}
961
962// Count the amount of trimmable whitespace (as per CSS
963// 'white-space:normal/nowrap') in a character buffer. The first
964// character is at offset aStartOffset; the maximum number of characters
965// to check is aLength. aDirection is -1 or 1 depending on whether we should
966// progress backwards or forwards.
967static uint32_t GetTrimmableWhitespaceCount(const CharacterDataBuffer& aBuffer,
968 int32_t aStartOffset,
969 int32_t aLength,
970 int32_t aDirection) {
971 if (!aLength) {
972 return 0;
973 }
974
975 int32_t count = 0;
976 if (aBuffer.Is2b()) {
977 const char16_t* str = aBuffer.Get2b() + aStartOffset;
978 int32_t bufferLen = aBuffer.GetLength() - aStartOffset;
979 for (; count < aLength; ++count) {
980 if (!IsTrimmableSpace(str, bufferLen)) {
981 break;
982 }
983 str += aDirection;
984 bufferLen -= aDirection;
985 }
986 } else {
987 const char* str = aBuffer.Get1b() + aStartOffset;
988 for (; count < aLength; ++count) {
989 if (!IsTrimmableSpace(*str)) {
990 break;
991 }
992 str += aDirection;
993 }
994 }
995 return count;
996}
997
998static bool IsAllWhitespace(const CharacterDataBuffer& aBuffer,
999 bool aAllowNewline) {
1000 if (aBuffer.Is2b()) {
1001 return false;
1002 }
1003 int32_t len = aBuffer.GetLength();
1004 const char* str = aBuffer.Get1b();
1005 for (int32_t i = 0; i < len; ++i) {
1006 char ch = str[i];
1007 if (ch == ' ' || ch == '\t' || ch == '\r' ||
1008 (ch == '\n' && aAllowNewline)) {
1009 continue;
1010 }
1011 return false;
1012 }
1013 return true;
1014}
1015
1016static void ClearObserversFromTextRun(gfxTextRun* aTextRun) {
1017 if (!(aTextRun->GetFlags2() &
1018 nsTextFrameUtils::Flags::MightHaveGlyphChanges)) {
1019 return;
1020 }
1021
1022 if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow) {
1023 static_cast<SimpleTextRunUserData*>(aTextRun->GetUserData())
1024 ->mGlyphObservers.Clear();
1025 } else {
1026 static_cast<ComplexTextRunUserData*>(aTextRun->GetUserData())
1027 ->mGlyphObservers.Clear();
1028 }
1029}
1030
1031static void CreateObserversForAnimatedGlyphs(gfxTextRun* aTextRun) {
1032 if (!aTextRun->GetUserData()) {
1033 return;
1034 }
1035
1036 ClearObserversFromTextRun(aTextRun);
1037
1038 AutoTArray<gfxFont*, 4> fontsWithAnimatedGlyphs;
1039 uint32_t numGlyphRuns;
1040 const gfxTextRun::GlyphRun* run = aTextRun->GetGlyphRuns(&numGlyphRuns);
1041 while (numGlyphRuns--) {
1042 gfxFont* font = run->mFont;
1043 if (font->GlyphsMayChange() && !fontsWithAnimatedGlyphs.Contains(font)) {
1044 fontsWithAnimatedGlyphs.AppendElement(font);
1045 }
1046 run++;
1047 }
1048 if (fontsWithAnimatedGlyphs.IsEmpty()) {
1049 // NB: Theoretically, we should clear the MightHaveGlyphChanges
1050 // here. That would involve de-allocating the simple user data struct if
1051 // present too, and resetting the pointer to the frame. In practice, I
1052 // don't think worth doing that work here, given the flag's only purpose is
1053 // to distinguish what kind of user data is there.
1054 return;
1055 }
1056
1057 nsTArray<UniquePtr<GlyphObserver>>* observers;
1058
1059 if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow) {
1060 // Swap the frame pointer for a just-allocated SimpleTextRunUserData if
1061 // appropriate.
1062 if (!(aTextRun->GetFlags2() &
1063 nsTextFrameUtils::Flags::MightHaveGlyphChanges)) {
1064 auto frame = static_cast<nsTextFrame*>(aTextRun->GetUserData());
1065 aTextRun->SetUserData(new SimpleTextRunUserData(frame));
1066 }
1067
1068 auto data = static_cast<SimpleTextRunUserData*>(aTextRun->GetUserData());
1069 observers = &data->mGlyphObservers;
1070 } else {
1071 if (!(aTextRun->GetFlags2() &
1072 nsTextFrameUtils::Flags::MightHaveGlyphChanges)) {
1073 auto oldData = static_cast<TextRunUserData*>(aTextRun->GetUserData());
1074 TextRunMappedFlow* oldMappedFlows = GetMappedFlows(aTextRun);
1075 ComplexTextRunUserData* data =
1076 CreateComplexUserData(oldData->mMappedFlowCount);
1077 TextRunMappedFlow* dataMappedFlows =
1078 reinterpret_cast<TextRunMappedFlow*>(data + 1);
1079 data->mLastFlowIndex = oldData->mLastFlowIndex;
1080 for (uint32_t i = 0; i < oldData->mMappedFlowCount; ++i) {
1081 dataMappedFlows[i] = oldMappedFlows[i];
1082 }
1083 DestroyUserData(oldData);
1084 aTextRun->SetUserData(data);
1085 }
1086 auto data = static_cast<ComplexTextRunUserData*>(aTextRun->GetUserData());
1087 observers = &data->mGlyphObservers;
1088 }
1089
1090 aTextRun->SetFlagBits(nsTextFrameUtils::Flags::MightHaveGlyphChanges);
1091
1092 observers->SetCapacity(observers->Length() +
1093 fontsWithAnimatedGlyphs.Length());
1094 for (auto font : fontsWithAnimatedGlyphs) {
1095 observers->AppendElement(MakeUnique<GlyphObserver>(font, aTextRun));
1096 }
1097}
1098
1099/**
1100 * This class accumulates state as we scan a paragraph of text. It detects
1101 * textrun boundaries (changes from text to non-text, hard
1102 * line breaks, and font changes) and builds a gfxTextRun at each boundary.
1103 * It also detects linebreaker run boundaries (changes from text to non-text,
1104 * and hard line breaks) and at each boundary runs the linebreaker to compute
1105 * potential line breaks. It also records actual line breaks to store them in
1106 * the textruns.
1107 */
1108class BuildTextRunsScanner {
1109 public:
1110 BuildTextRunsScanner(nsPresContext* aPresContext, DrawTarget* aDrawTarget,
1111 nsIFrame* aLineContainer,
1112 nsTextFrame::TextRunType aWhichTextRun,
1113 bool aDoLineBreaking)
1114 : mDrawTarget(aDrawTarget),
1115 mLineContainer(aLineContainer),
1116 mCommonAncestorWithLastFrame(nullptr),
1117 mMissingFonts(aPresContext->MissingFontRecorder()),
1118 mBidiEnabled(aPresContext->BidiEnabled()),
1119 mStartOfLine(true),
1120 mSkipIncompleteTextRuns(false),
1121 mCanStopOnThisLine(false),
1122 mDoLineBreaking(aDoLineBreaking),
1123 mWhichTextRun(aWhichTextRun),
1124 mNextRunContextInfo(nsTextFrameUtils::INCOMING_NONE),
1125 mCurrentRunContextInfo(nsTextFrameUtils::INCOMING_NONE) {
1126 ResetRunInfo();
1127 }
1128 ~BuildTextRunsScanner() {
1129 for (auto& run : mCreatedTextRuns) {
1130 CreateObserversForAnimatedGlyphs(run);
1131 }
1132 NS_ASSERTION(mBreakSinks.IsEmpty(), "Should have been cleared")do { if (!(mBreakSinks.IsEmpty())) { NS_DebugBreak(NS_DEBUG_ASSERTION
, "Should have been cleared", "mBreakSinks.IsEmpty()", "./../../../layout/generic/nsTextFrame.cpp"
, 1132); MOZ_PretendNoReturn(); } } while (0)
;
1133 NS_ASSERTION(mLineBreakBeforeFrames.IsEmpty(), "Should have been cleared")do { if (!(mLineBreakBeforeFrames.IsEmpty())) { NS_DebugBreak
(NS_DEBUG_ASSERTION, "Should have been cleared", "mLineBreakBeforeFrames.IsEmpty()"
, "./../../../layout/generic/nsTextFrame.cpp", 1133); MOZ_PretendNoReturn
(); } } while (0)
;
1134 NS_ASSERTION(mMappedFlows.IsEmpty(), "Should have been cleared")do { if (!(mMappedFlows.IsEmpty())) { NS_DebugBreak(NS_DEBUG_ASSERTION
, "Should have been cleared", "mMappedFlows.IsEmpty()", "./../../../layout/generic/nsTextFrame.cpp"
, 1134); MOZ_PretendNoReturn(); } } while (0)
;
1135 }
1136
1137 void SetAtStartOfLine() {
1138 mStartOfLine = true;
1139 mCanStopOnThisLine = false;
1140 }
1141 void SetSkipIncompleteTextRuns(bool aSkip) {
1142 mSkipIncompleteTextRuns = aSkip;
1143 }
1144 void SetCommonAncestorWithLastFrame(nsIFrame* aFrame) {
1145 mCommonAncestorWithLastFrame = aFrame;
1146 }
1147 bool CanStopOnThisLine() { return mCanStopOnThisLine; }
1148 nsIFrame* GetCommonAncestorWithLastFrame() {
1149 return mCommonAncestorWithLastFrame;
1150 }
1151 void LiftCommonAncestorWithLastFrameToParent(nsIFrame* aFrame) {
1152 if (mCommonAncestorWithLastFrame &&
1153 mCommonAncestorWithLastFrame->GetParent() == aFrame) {
1154 mCommonAncestorWithLastFrame = aFrame;
1155 }
1156 }
1157 void ScanFrame(nsIFrame* aFrame);
1158 bool IsTextRunValidForMappedFlows(const gfxTextRun* aTextRun);
1159 void FlushFrames(bool aFlushLineBreaks, bool aSuppressTrailingBreak);
1160 void FlushLineBreaks(gfxTextRun* aTrailingTextRun);
1161 void ResetRunInfo() {
1162 mLastFrame = nullptr;
1163 mMappedFlows.Clear();
1164 mLineBreakBeforeFrames.Clear();
1165 mMaxTextLength = 0;
1166 mDoubleByteText = false;
1167 }
1168 void AccumulateRunInfo(nsTextFrame* aFrame);
1169 /**
1170 * @return null to indicate either textrun construction failed or
1171 * we constructed just a partial textrun to set up linebreaker and other
1172 * state for following textruns.
1173 */
1174 already_AddRefed<gfxTextRun> BuildTextRunForFrames(void* aTextBuffer);
1175 bool SetupLineBreakerContext(gfxTextRun* aTextRun);
1176 void AssignTextRun(gfxTextRun* aTextRun, float aInflation);
1177 nsTextFrame* GetNextBreakBeforeFrame(uint32_t* aIndex);
1178 void SetupBreakSinksForTextRun(gfxTextRun* aTextRun, const void* aTextPtr);
1179 void SetupTextEmphasisForTextRun(gfxTextRun* aTextRun, const void* aTextPtr);
1180 struct FindBoundaryState {
1181 nsIFrame* mStopAtFrame;
1182 nsTextFrame* mFirstTextFrame;
1183 nsTextFrame* mLastTextFrame;
1184 bool mSeenTextRunBoundaryOnLaterLine;
1185 bool mSeenTextRunBoundaryOnThisLine;
1186 bool mSeenSpaceForLineBreakingOnThisLine;
1187 nsTArray<char16_t>& mBuffer;
1188 };
1189 enum FindBoundaryResult {
1190 FB_CONTINUE,
1191 FB_STOPPED_AT_STOP_FRAME,
1192 FB_FOUND_VALID_TEXTRUN_BOUNDARY
1193 };
1194 FindBoundaryResult FindBoundaries(nsIFrame* aFrame,
1195 FindBoundaryState* aState);
1196
1197 bool ContinueTextRunAcrossFrames(nsTextFrame* aFrame1, nsTextFrame* aFrame2);
1198
1199 // Like TextRunMappedFlow but with some differences. mStartFrame to mEndFrame
1200 // (exclusive) are a sequence of in-flow frames (if mEndFrame is null, then
1201 // continuations starting from mStartFrame are a sequence of in-flow frames).
1202 struct MappedFlow {
1203 nsTextFrame* mStartFrame;
1204 nsTextFrame* mEndFrame;
1205 // When we consider breaking between elements, the nearest common
1206 // ancestor of the elements containing the characters is the one whose
1207 // CSS 'white-space' property governs. So this records the nearest common
1208 // ancestor of mStartFrame and the previous text frame, or null if there
1209 // was no previous text frame on this line.
1210 nsIFrame* mAncestorControllingInitialBreak;
1211
1212 int32_t GetContentEnd() const {
1213 int32_t bufferLen = mStartFrame->CharacterDataBuffer().GetLength();
1214 return mEndFrame ? std::min(bufferLen, mEndFrame->GetContentOffset())
1215 : bufferLen;
1216 }
1217 };
1218
1219 class BreakSink final : public nsILineBreakSink {
1220 public:
1221 BreakSink(gfxTextRun* aTextRun, DrawTarget* aDrawTarget,
1222 uint32_t aOffsetIntoTextRun)
1223 : mTextRun(aTextRun),
1224 mDrawTarget(aDrawTarget),
1225 mOffsetIntoTextRun(aOffsetIntoTextRun) {}
1226
1227 void SetBreaks(uint32_t aOffset, uint32_t aLength,
1228 uint8_t* aBreakBefore) final {
1229 gfxTextRun::Range range(aOffset + mOffsetIntoTextRun,
1230 aOffset + mOffsetIntoTextRun + aLength);
1231 if (mTextRun->SetPotentialLineBreaks(range, aBreakBefore)) {
1232 // Be conservative and assume that some breaks have been set
1233 mTextRun->ClearFlagBits(nsTextFrameUtils::Flags::NoBreaks);
1234 }
1235 }
1236
1237 void SetCapitalization(uint32_t aOffset, uint32_t aLength,
1238 bool* aCapitalize) final {
1239 MOZ_ASSERT(mTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsTransformed,do { static_assert( mozilla::detail::AssertionConditionType<
decltype(mTextRun->GetFlags2() & nsTextFrameUtils::Flags
::IsTransformed)>::isValid, "invalid assertion condition")
; if ((__builtin_expect(!!(!(!!(mTextRun->GetFlags2() &
nsTextFrameUtils::Flags::IsTransformed))), 0))) { do { } while
(false); MOZ_ReportAssertionFailure("mTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsTransformed"
" (" "Text run should be transformed!" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 1240); AnnotateMozCrashReason("MOZ_ASSERT" "(" "mTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsTransformed"
") (" "Text run should be transformed!" ")"); do { MOZ_CrashSequence
(__null, 1240); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
1240 "Text run should be transformed!")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(mTextRun->GetFlags2() & nsTextFrameUtils::Flags
::IsTransformed)>::isValid, "invalid assertion condition")
; if ((__builtin_expect(!!(!(!!(mTextRun->GetFlags2() &
nsTextFrameUtils::Flags::IsTransformed))), 0))) { do { } while
(false); MOZ_ReportAssertionFailure("mTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsTransformed"
" (" "Text run should be transformed!" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 1240); AnnotateMozCrashReason("MOZ_ASSERT" "(" "mTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsTransformed"
") (" "Text run should be transformed!" ")"); do { MOZ_CrashSequence
(__null, 1240); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
1241 if (mTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsTransformed) {
1242 nsTransformedTextRun* transformedTextRun =
1243 static_cast<nsTransformedTextRun*>(mTextRun.get());
1244 transformedTextRun->SetCapitalization(aOffset + mOffsetIntoTextRun,
1245 aLength, aCapitalize);
1246 }
1247 }
1248
1249 void Finish(gfxMissingFontRecorder* aMFR) {
1250 if (mTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsTransformed) {
1251 nsTransformedTextRun* transformedTextRun =
1252 static_cast<nsTransformedTextRun*>(mTextRun.get());
1253 transformedTextRun->FinishSettingProperties(mDrawTarget, aMFR);
1254 }
1255 }
1256
1257 RefPtr<gfxTextRun> mTextRun;
1258 DrawTarget* mDrawTarget;
1259 uint32_t mOffsetIntoTextRun;
1260 };
1261
1262 private:
1263 AutoTArray<MappedFlow, 10> mMappedFlows;
1264 AutoTArray<nsTextFrame*, 50> mLineBreakBeforeFrames;
1265 AutoTArray<UniquePtr<BreakSink>, 10> mBreakSinks;
1266 AutoTArray<RefPtr<gfxTextRun>, 10> mCreatedTextRuns;
1267 nsLineBreaker mLineBreaker;
1268 RefPtr<gfxTextRun> mCurrentFramesAllSameTextRun;
1269 DrawTarget* mDrawTarget;
1270 nsIFrame* mLineContainer;
1271 nsTextFrame* mLastFrame;
1272 // The common ancestor of the current frame and the previous leaf frame
1273 // on the line, or null if there was no previous leaf frame.
1274 nsIFrame* mCommonAncestorWithLastFrame;
1275 gfxMissingFontRecorder* mMissingFonts;
1276 // mMaxTextLength is an upper bound on the size of the text in all mapped
1277 // frames The value UINT32_MAX represents overflow; text will be discarded
1278 uint32_t mMaxTextLength;
1279 bool mDoubleByteText;
1280 bool mBidiEnabled;
1281 bool mStartOfLine;
1282 bool mSkipIncompleteTextRuns;
1283 bool mCanStopOnThisLine;
1284 bool mDoLineBreaking;
1285 nsTextFrame::TextRunType mWhichTextRun;
1286 uint8_t mNextRunContextInfo;
1287 uint8_t mCurrentRunContextInfo;
1288};
1289
1290static const nsIFrame* FindLineContainer(const nsIFrame* aFrame) {
1291 while (aFrame &&
1292 (aFrame->IsLineParticipant() || aFrame->CanContinueTextRun())) {
1293 aFrame = aFrame->GetParent();
1294 }
1295 return aFrame;
1296}
1297
1298static nsIFrame* FindLineContainer(nsIFrame* aFrame) {
1299 return const_cast<nsIFrame*>(
1300 FindLineContainer(const_cast<const nsIFrame*>(aFrame)));
1301}
1302
1303static bool IsLineBreakingWhiteSpace(char16_t aChar) {
1304 // 0x0A (\n) is not handled as white-space by the line breaker, since
1305 // we break before it, if it isn't transformed to a normal space.
1306 // (If we treat it as normal white-space then we'd only break after it.)
1307 // However, it does induce a line break or is converted to a regular
1308 // space, and either way it can be used to bound the region of text
1309 // that needs to be analyzed for line breaking.
1310 return nsLineBreaker::IsSpace(aChar) || aChar == 0x0A;
1311}
1312
1313static bool TextContainsLineBreakerWhiteSpace(const void* aText,
1314 uint32_t aLength,
1315 bool aIsDoubleByte) {
1316 if (aIsDoubleByte) {
1317 const char16_t* chars = static_cast<const char16_t*>(aText);
1318 for (uint32_t i = 0; i < aLength; ++i) {
1319 if (IsLineBreakingWhiteSpace(chars[i])) {
1320 return true;
1321 }
1322 }
1323 return false;
1324 } else {
1325 const uint8_t* chars = static_cast<const uint8_t*>(aText);
1326 for (uint32_t i = 0; i < aLength; ++i) {
1327 if (IsLineBreakingWhiteSpace(chars[i])) {
1328 return true;
1329 }
1330 }
1331 return false;
1332 }
1333}
1334
1335static nsTextFrameUtils::CompressionMode GetCSSWhitespaceToCompressionMode(
1336 nsTextFrame* aFrame, const nsStyleText* aStyleText) {
1337 switch (aStyleText->mWhiteSpaceCollapse) {
1338 case StyleWhiteSpaceCollapse::Collapse:
1339 return nsTextFrameUtils::COMPRESS_WHITESPACE_NEWLINE;
1340 case StyleWhiteSpaceCollapse::PreserveBreaks:
1341 return nsTextFrameUtils::COMPRESS_WHITESPACE;
1342 case StyleWhiteSpaceCollapse::Preserve:
1343 case StyleWhiteSpaceCollapse::PreserveSpaces:
1344 case StyleWhiteSpaceCollapse::BreakSpaces:
1345 if (!aStyleText->NewlineIsSignificant(aFrame)) {
1346 // If newline is set to be preserved, but then suppressed,
1347 // transform newline to space.
1348 return nsTextFrameUtils::COMPRESS_NONE_TRANSFORM_TO_SPACE;
1349 }
1350 return nsTextFrameUtils::COMPRESS_NONE;
1351 }
1352 MOZ_ASSERT_UNREACHABLE("Unknown white-space-collapse value")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(false)>::isValid, "invalid assertion condition");
if ((__builtin_expect(!!(!(!!(false))), 0))) { do { } while (
false); MOZ_ReportAssertionFailure("false" " (" "MOZ_ASSERT_UNREACHABLE: "
"Unknown white-space-collapse value" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 1352); AnnotateMozCrashReason("MOZ_ASSERT" "(" "false" ") ("
"MOZ_ASSERT_UNREACHABLE: " "Unknown white-space-collapse value"
")"); do { MOZ_CrashSequence(__null, 1352); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
1353 return nsTextFrameUtils::COMPRESS_WHITESPACE_NEWLINE;
1354}
1355
1356struct FrameTextTraversal {
1357 FrameTextTraversal()
1358 : mFrameToScan(nullptr),
1359 mOverflowFrameToScan(nullptr),
1360 mScanSiblings(false),
1361 mLineBreakerCanCrossFrameBoundary(false),
1362 mTextRunCanCrossFrameBoundary(false) {}
1363
1364 // These fields identify which frames should be recursively scanned
1365 // The first normal frame to scan (or null, if no such frame should be
1366 // scanned)
1367 nsIFrame* mFrameToScan;
1368 // The first overflow frame to scan (or null, if no such frame should be
1369 // scanned)
1370 nsIFrame* mOverflowFrameToScan;
1371 // Whether to scan the siblings of
1372 // mFrameToDescendInto/mOverflowFrameToDescendInto
1373 bool mScanSiblings;
1374
1375 // These identify the boundaries of the context required for
1376 // line breaking or textrun construction
1377 bool mLineBreakerCanCrossFrameBoundary;
1378 bool mTextRunCanCrossFrameBoundary;
1379
1380 nsIFrame* NextFrameToScan() {
1381 nsIFrame* f;
1382 if (mFrameToScan) {
1383 f = mFrameToScan;
1384 mFrameToScan = mScanSiblings ? f->GetNextSibling() : nullptr;
1385 } else if (mOverflowFrameToScan) {
1386 f = mOverflowFrameToScan;
1387 mOverflowFrameToScan = mScanSiblings ? f->GetNextSibling() : nullptr;
1388 } else {
1389 f = nullptr;
1390 }
1391 return f;
1392 }
1393};
1394
1395static FrameTextTraversal CanTextCrossFrameBoundary(nsIFrame* aFrame) {
1396 FrameTextTraversal result;
1397
1398 bool continuesTextRun = aFrame->CanContinueTextRun();
1399 if (aFrame->IsPlaceholderFrame()) {
1400 // placeholders are "invisible", so a text run should be able to span
1401 // across one. But don't descend into the out-of-flow.
1402 result.mLineBreakerCanCrossFrameBoundary = true;
1403 if (continuesTextRun) {
1404 // ... Except for first-letter floats, which are really in-flow
1405 // from the point of view of capitalization etc, so we'd better
1406 // descend into them. But we actually need to break the textrun for
1407 // first-letter floats since things look bad if, say, we try to make a
1408 // ligature across the float boundary.
1409 result.mFrameToScan =
1410 (static_cast<nsPlaceholderFrame*>(aFrame))->GetOutOfFlowFrame();
1411 } else {
1412 result.mTextRunCanCrossFrameBoundary = true;
1413 }
1414 } else {
1415 if (continuesTextRun) {
1416 result.mFrameToScan = aFrame->PrincipalChildList().FirstChild();
1417 result.mOverflowFrameToScan =
1418 aFrame->GetChildList(FrameChildListID::Overflow).FirstChild();
1419 NS_WARNING_ASSERTION(do { if (!(!result.mOverflowFrameToScan)) { NS_DebugBreak(NS_DEBUG_WARNING
, "Scanning overflow inline frames is something we should avoid"
, "!result.mOverflowFrameToScan", "./../../../layout/generic/nsTextFrame.cpp"
, 1421); } } while (false)
1420 !result.mOverflowFrameToScan,do { if (!(!result.mOverflowFrameToScan)) { NS_DebugBreak(NS_DEBUG_WARNING
, "Scanning overflow inline frames is something we should avoid"
, "!result.mOverflowFrameToScan", "./../../../layout/generic/nsTextFrame.cpp"
, 1421); } } while (false)
1421 "Scanning overflow inline frames is something we should avoid")do { if (!(!result.mOverflowFrameToScan)) { NS_DebugBreak(NS_DEBUG_WARNING
, "Scanning overflow inline frames is something we should avoid"
, "!result.mOverflowFrameToScan", "./../../../layout/generic/nsTextFrame.cpp"
, 1421); } } while (false)
;
1422 result.mScanSiblings = true;
1423 result.mTextRunCanCrossFrameBoundary = true;
1424 result.mLineBreakerCanCrossFrameBoundary = true;
1425 } else {
1426 MOZ_ASSERT(!aFrame->IsRubyTextContainerFrame(),do { static_assert( mozilla::detail::AssertionConditionType<
decltype(!aFrame->IsRubyTextContainerFrame())>::isValid
, "invalid assertion condition"); if ((__builtin_expect(!!(!(
!!(!aFrame->IsRubyTextContainerFrame()))), 0))) { do { } while
(false); MOZ_ReportAssertionFailure("!aFrame->IsRubyTextContainerFrame()"
" (" "Shouldn't call this method for ruby text container" ")"
, "./../../../layout/generic/nsTextFrame.cpp", 1427); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "!aFrame->IsRubyTextContainerFrame()" ") ("
"Shouldn't call this method for ruby text container" ")"); do
{ MOZ_CrashSequence(__null, 1427); __attribute__((nomerge)) ::
abort(); } while (false); } } while (false)
1427 "Shouldn't call this method for ruby text container")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(!aFrame->IsRubyTextContainerFrame())>::isValid
, "invalid assertion condition"); if ((__builtin_expect(!!(!(
!!(!aFrame->IsRubyTextContainerFrame()))), 0))) { do { } while
(false); MOZ_ReportAssertionFailure("!aFrame->IsRubyTextContainerFrame()"
" (" "Shouldn't call this method for ruby text container" ")"
, "./../../../layout/generic/nsTextFrame.cpp", 1427); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "!aFrame->IsRubyTextContainerFrame()" ") ("
"Shouldn't call this method for ruby text container" ")"); do
{ MOZ_CrashSequence(__null, 1427); __attribute__((nomerge)) ::
abort(); } while (false); } } while (false)
;
1428 }
1429 }
1430 return result;
1431}
1432
1433BuildTextRunsScanner::FindBoundaryResult BuildTextRunsScanner::FindBoundaries(
1434 nsIFrame* aFrame, FindBoundaryState* aState) {
1435 LayoutFrameType frameType = aFrame->Type();
1436 if (frameType == LayoutFrameType::RubyTextContainer) {
1437 // Don't stop a text run for ruby text container. We want ruby text
1438 // containers to be skipped, but continue the text run across them.
1439 return FB_CONTINUE;
1440 }
1441
1442 nsTextFrame* textFrame = frameType == LayoutFrameType::Text
1443 ? static_cast<nsTextFrame*>(aFrame)
1444 : nullptr;
1445 if (textFrame) {
1446 if (aState->mLastTextFrame &&
1447 textFrame != aState->mLastTextFrame->GetNextInFlow() &&
1448 !ContinueTextRunAcrossFrames(aState->mLastTextFrame, textFrame)) {
1449 aState->mSeenTextRunBoundaryOnThisLine = true;
1450 if (aState->mSeenSpaceForLineBreakingOnThisLine) {
1451 return FB_FOUND_VALID_TEXTRUN_BOUNDARY;
1452 }
1453 }
1454 if (!aState->mFirstTextFrame) {
1455 aState->mFirstTextFrame = textFrame;
1456 }
1457 aState->mLastTextFrame = textFrame;
1458 }
1459
1460 if (aFrame == aState->mStopAtFrame) {
1461 return FB_STOPPED_AT_STOP_FRAME;
1462 }
1463
1464 if (textFrame) {
1465 if (aState->mSeenSpaceForLineBreakingOnThisLine) {
1466 return FB_CONTINUE;
1467 }
1468 const CharacterDataBuffer& characterDataBuffer =
1469 textFrame->CharacterDataBuffer();
1470 const int32_t start = textFrame->GetContentOffset();
1471 const int32_t end = textFrame->GetContentEnd();
1472 const int32_t bufferLength = characterDataBuffer.GetLength();
1473
1474 if (MOZ_UNLIKELY(start < 0 || end < start || end > bufferLength)(__builtin_expect(!!(start < 0 || end < start || end >
bufferLength), 0))
) {
1475 MOZ_ASSERT_UNREACHABLE("Invalid text-frame content range")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(false)>::isValid, "invalid assertion condition");
if ((__builtin_expect(!!(!(!!(false))), 0))) { do { } while (
false); MOZ_ReportAssertionFailure("false" " (" "MOZ_ASSERT_UNREACHABLE: "
"Invalid text-frame content range" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 1475); AnnotateMozCrashReason("MOZ_ASSERT" "(" "false" ") ("
"MOZ_ASSERT_UNREACHABLE: " "Invalid text-frame content range"
")"); do { MOZ_CrashSequence(__null, 1475); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
1476 return FB_CONTINUE;
1477 }
1478
1479 int32_t length = end - start;
1480 const void* text;
1481 const nsAtom* language = textFrame->StyleFont()->GetLangAtom();
1482 if (characterDataBuffer.Is2b()) {
1483 // It is possible that we may end up removing all whitespace in
1484 // a piece of text because of The White Space Processing Rules,
1485 // so we need to transform it before we can check existence of
1486 // such whitespaces.
1487 aState->mBuffer.EnsureLengthAtLeast(length);
1488 nsTextFrameUtils::CompressionMode compression =
1489 GetCSSWhitespaceToCompressionMode(textFrame, textFrame->StyleText());
1490 uint8_t incomingFlags = 0;
1491 gfxSkipChars skipChars;
1492 nsTextFrameUtils::Flags analysisFlags;
1493 char16_t* bufStart = aState->mBuffer.Elements();
1494 char16_t* bufEnd = nsTextFrameUtils::TransformText(
1495 characterDataBuffer.Get2b() + start, length, bufStart, compression,
1496 &incomingFlags, &skipChars, &analysisFlags, language);
1497 text = bufStart;
1498 length = bufEnd - bufStart;
1499 } else {
1500 // If the text only contains ASCII characters, it is currently
1501 // impossible that TransformText would remove all whitespaces,
1502 // and thus the check below should return the same result for
1503 // transformed text and original text. So we don't need to try
1504 // transforming it here.
1505 text = static_cast<const void*>(characterDataBuffer.Get1b() + start);
1506 }
1507 if (TextContainsLineBreakerWhiteSpace(text, length,
1508 characterDataBuffer.Is2b())) {
1509 aState->mSeenSpaceForLineBreakingOnThisLine = true;
1510 if (aState->mSeenTextRunBoundaryOnLaterLine) {
1511 return FB_FOUND_VALID_TEXTRUN_BOUNDARY;
1512 }
1513 }
1514 return FB_CONTINUE;
1515 }
1516
1517 FrameTextTraversal traversal = CanTextCrossFrameBoundary(aFrame);
1518 if (!traversal.mTextRunCanCrossFrameBoundary) {
1519 aState->mSeenTextRunBoundaryOnThisLine = true;
1520 if (aState->mSeenSpaceForLineBreakingOnThisLine) {
1521 return FB_FOUND_VALID_TEXTRUN_BOUNDARY;
1522 }
1523 }
1524
1525 for (nsIFrame* f = traversal.NextFrameToScan(); f;
1526 f = traversal.NextFrameToScan()) {
1527 FindBoundaryResult result = FindBoundaries(f, aState);
1528 if (result != FB_CONTINUE) {
1529 return result;
1530 }
1531 }
1532
1533 if (!traversal.mTextRunCanCrossFrameBoundary) {
1534 aState->mSeenTextRunBoundaryOnThisLine = true;
1535 if (aState->mSeenSpaceForLineBreakingOnThisLine) {
1536 return FB_FOUND_VALID_TEXTRUN_BOUNDARY;
1537 }
1538 }
1539
1540 return FB_CONTINUE;
1541}
1542
1543// build text runs for the 200 lines following aForFrame, and stop after that
1544// when we get a chance.
1545#define NUM_LINES_TO_BUILD_TEXT_RUNS200 200
1546
1547/**
1548 * General routine for building text runs. This is hairy because of the need
1549 * to build text runs that span content nodes.
1550 *
1551 * @param aContext The gfxContext we're using to construct this text run.
1552 * @param aForFrame The nsTextFrame for which we're building this text run.
1553 * @param aLineContainer the line container containing aForFrame; if null,
1554 * we'll walk the ancestors to find it. It's required to be non-null
1555 * when aForFrameLine is non-null.
1556 * @param aForFrameLine the line containing aForFrame; if null, we'll figure
1557 * out the line (slowly)
1558 * @param aWhichTextRun The type of text run we want to build. If font inflation
1559 * is enabled, this will be eInflated, otherwise it's eNotInflated.
1560 */
1561static void BuildTextRuns(DrawTarget* aDrawTarget, nsTextFrame* aForFrame,
1562 nsIFrame* aLineContainer,
1563 const nsLineList::iterator* aForFrameLine,
1564 nsTextFrame::TextRunType aWhichTextRun) {
1565 MOZ_ASSERT(aForFrame, "for no frame?")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aForFrame)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(aForFrame))), 0))) { do { } while
(false); MOZ_ReportAssertionFailure("aForFrame" " (" "for no frame?"
")", "./../../../layout/generic/nsTextFrame.cpp", 1565); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "aForFrame" ") (" "for no frame?" ")"); do {
MOZ_CrashSequence(__null, 1565); __attribute__((nomerge)) ::
abort(); } while (false); } } while (false)
;
1566 NS_ASSERTION(!aForFrameLine || aLineContainer, "line but no line container")do { if (!(!aForFrameLine || aLineContainer)) { NS_DebugBreak
(NS_DEBUG_ASSERTION, "line but no line container", "!aForFrameLine || aLineContainer"
, "./../../../layout/generic/nsTextFrame.cpp", 1566); MOZ_PretendNoReturn
(); } } while (0)
;
1567
1568 nsIFrame* lineContainerChild = aForFrame;
1569 if (!aLineContainer) {
1570 if (aForFrame->IsFloatingFirstLetterChild()) {
1571 lineContainerChild = aForFrame->GetParent()->GetPlaceholderFrame();
1572 }
1573 aLineContainer = FindLineContainer(lineContainerChild);
1574 } else {
1575 NS_ASSERTION(do { if (!((aLineContainer == FindLineContainer(aForFrame) ||
(aLineContainer->IsLetterFrame() && aLineContainer
->IsFloating())))) { NS_DebugBreak(NS_DEBUG_ASSERTION, "Wrong line container hint"
, "(aLineContainer == FindLineContainer(aForFrame) || (aLineContainer->IsLetterFrame() && aLineContainer->IsFloating()))"
, "./../../../layout/generic/nsTextFrame.cpp", 1578); MOZ_PretendNoReturn
(); } } while (0)
1576 (aLineContainer == FindLineContainer(aForFrame) ||do { if (!((aLineContainer == FindLineContainer(aForFrame) ||
(aLineContainer->IsLetterFrame() && aLineContainer
->IsFloating())))) { NS_DebugBreak(NS_DEBUG_ASSERTION, "Wrong line container hint"
, "(aLineContainer == FindLineContainer(aForFrame) || (aLineContainer->IsLetterFrame() && aLineContainer->IsFloating()))"
, "./../../../layout/generic/nsTextFrame.cpp", 1578); MOZ_PretendNoReturn
(); } } while (0)
1577 (aLineContainer->IsLetterFrame() && aLineContainer->IsFloating())),do { if (!((aLineContainer == FindLineContainer(aForFrame) ||
(aLineContainer->IsLetterFrame() && aLineContainer
->IsFloating())))) { NS_DebugBreak(NS_DEBUG_ASSERTION, "Wrong line container hint"
, "(aLineContainer == FindLineContainer(aForFrame) || (aLineContainer->IsLetterFrame() && aLineContainer->IsFloating()))"
, "./../../../layout/generic/nsTextFrame.cpp", 1578); MOZ_PretendNoReturn
(); } } while (0)
1578 "Wrong line container hint")do { if (!((aLineContainer == FindLineContainer(aForFrame) ||
(aLineContainer->IsLetterFrame() && aLineContainer
->IsFloating())))) { NS_DebugBreak(NS_DEBUG_ASSERTION, "Wrong line container hint"
, "(aLineContainer == FindLineContainer(aForFrame) || (aLineContainer->IsLetterFrame() && aLineContainer->IsFloating()))"
, "./../../../layout/generic/nsTextFrame.cpp", 1578); MOZ_PretendNoReturn
(); } } while (0)
;
1579 }
1580
1581 if (aForFrame->HasAnyStateBits(TEXT_IS_IN_TOKEN_MATHML)) {
1582 aLineContainer->AddStateBits(TEXT_IS_IN_TOKEN_MATHML);
1583 if (aForFrame->HasAnyStateBits(NS_FRAME_IS_IN_SINGLE_CHAR_MI)) {
1584 aLineContainer->AddStateBits(NS_FRAME_IS_IN_SINGLE_CHAR_MI);
1585 }
1586 }
1587 if (aForFrame->HasAnyStateBits(NS_FRAME_MATHML_SCRIPT_DESCENDANT)) {
1588 aLineContainer->AddStateBits(NS_FRAME_MATHML_SCRIPT_DESCENDANT);
1589 }
1590
1591 nsPresContext* presContext = aLineContainer->PresContext();
1592 bool doLineBreaking = !aForFrame->IsInSVGTextSubtree();
1593 BuildTextRunsScanner scanner(presContext, aDrawTarget, aLineContainer,
1594 aWhichTextRun, doLineBreaking);
1595
1596 nsBlockFrame* block = do_QueryFrame(aLineContainer);
1597
1598 if (!block) {
1599 nsIFrame* textRunContainer = aLineContainer;
1600 if (aLineContainer->IsRubyTextContainerFrame()) {
1601 textRunContainer = aForFrame;
1602 while (textRunContainer && !textRunContainer->IsRubyTextFrame()) {
1603 textRunContainer = textRunContainer->GetParent();
1604 }
1605 MOZ_ASSERT(textRunContainer &&do { static_assert( mozilla::detail::AssertionConditionType<
decltype(textRunContainer && textRunContainer->GetParent
() == aLineContainer)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(textRunContainer && textRunContainer
->GetParent() == aLineContainer))), 0))) { do { } while (false
); MOZ_ReportAssertionFailure("textRunContainer && textRunContainer->GetParent() == aLineContainer"
, "./../../../layout/generic/nsTextFrame.cpp", 1606); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "textRunContainer && textRunContainer->GetParent() == aLineContainer"
")"); do { MOZ_CrashSequence(__null, 1606); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
1606 textRunContainer->GetParent() == aLineContainer)do { static_assert( mozilla::detail::AssertionConditionType<
decltype(textRunContainer && textRunContainer->GetParent
() == aLineContainer)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(textRunContainer && textRunContainer
->GetParent() == aLineContainer))), 0))) { do { } while (false
); MOZ_ReportAssertionFailure("textRunContainer && textRunContainer->GetParent() == aLineContainer"
, "./../../../layout/generic/nsTextFrame.cpp", 1606); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "textRunContainer && textRunContainer->GetParent() == aLineContainer"
")"); do { MOZ_CrashSequence(__null, 1606); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
1607 } else {
1608 NS_ASSERTION(do { if (!(!aLineContainer->GetPrevInFlow() && !aLineContainer
->GetNextInFlow())) { NS_DebugBreak(NS_DEBUG_ASSERTION, "Breakable non-block line containers other than "
"ruby text container is not supported", "!aLineContainer->GetPrevInFlow() && !aLineContainer->GetNextInFlow()"
, "./../../../layout/generic/nsTextFrame.cpp", 1611); MOZ_PretendNoReturn
(); } } while (0)
1609 !aLineContainer->GetPrevInFlow() && !aLineContainer->GetNextInFlow(),do { if (!(!aLineContainer->GetPrevInFlow() && !aLineContainer
->GetNextInFlow())) { NS_DebugBreak(NS_DEBUG_ASSERTION, "Breakable non-block line containers other than "
"ruby text container is not supported", "!aLineContainer->GetPrevInFlow() && !aLineContainer->GetNextInFlow()"
, "./../../../layout/generic/nsTextFrame.cpp", 1611); MOZ_PretendNoReturn
(); } } while (0)
1610 "Breakable non-block line containers other than "do { if (!(!aLineContainer->GetPrevInFlow() && !aLineContainer
->GetNextInFlow())) { NS_DebugBreak(NS_DEBUG_ASSERTION, "Breakable non-block line containers other than "
"ruby text container is not supported", "!aLineContainer->GetPrevInFlow() && !aLineContainer->GetNextInFlow()"
, "./../../../layout/generic/nsTextFrame.cpp", 1611); MOZ_PretendNoReturn
(); } } while (0)
1611 "ruby text container is not supported")do { if (!(!aLineContainer->GetPrevInFlow() && !aLineContainer
->GetNextInFlow())) { NS_DebugBreak(NS_DEBUG_ASSERTION, "Breakable non-block line containers other than "
"ruby text container is not supported", "!aLineContainer->GetPrevInFlow() && !aLineContainer->GetNextInFlow()"
, "./../../../layout/generic/nsTextFrame.cpp", 1611); MOZ_PretendNoReturn
(); } } while (0)
;
1612 }
1613 // Just loop through all the children of the linecontainer ... it's really
1614 // just one line
1615 scanner.SetAtStartOfLine();
1616 scanner.SetCommonAncestorWithLastFrame(nullptr);
1617 for (nsIFrame* child : textRunContainer->PrincipalChildList()) {
1618 scanner.ScanFrame(child);
1619 }
1620 // Set mStartOfLine so FlushFrames knows its textrun ends a line
1621 scanner.SetAtStartOfLine();
1622 scanner.FlushFrames(true, false);
1623 return;
1624 }
1625
1626 // Find the line containing 'lineContainerChild'.
1627
1628 bool isValid = true;
1629 nsBlockInFlowLineIterator backIterator(block, &isValid);
1630 if (aForFrameLine) {
1631 backIterator = nsBlockInFlowLineIterator(block, *aForFrameLine);
1632 } else {
1633 backIterator =
1634 nsBlockInFlowLineIterator(block, lineContainerChild, &isValid);
1635 NS_ASSERTION(isValid, "aForFrame not found in block, someone lied to us")do { if (!(isValid)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "aForFrame not found in block, someone lied to us"
, "isValid", "./../../../layout/generic/nsTextFrame.cpp", 1635
); MOZ_PretendNoReturn(); } } while (0)
;
1636 NS_ASSERTION(backIterator.GetContainer() == block,do { if (!(backIterator.GetContainer() == block)) { NS_DebugBreak
(NS_DEBUG_ASSERTION, "Someone lied to us about the block", "backIterator.GetContainer() == block"
, "./../../../layout/generic/nsTextFrame.cpp", 1637); MOZ_PretendNoReturn
(); } } while (0)
1637 "Someone lied to us about the block")do { if (!(backIterator.GetContainer() == block)) { NS_DebugBreak
(NS_DEBUG_ASSERTION, "Someone lied to us about the block", "backIterator.GetContainer() == block"
, "./../../../layout/generic/nsTextFrame.cpp", 1637); MOZ_PretendNoReturn
(); } } while (0)
;
1638 }
1639 nsBlockFrame::LineIterator startLine = backIterator.GetLine();
1640
1641 // Find a line where we can start building text runs. We choose the last line
1642 // where:
1643 // -- there is a textrun boundary between the start of the line and the
1644 // start of aForFrame
1645 // -- there is a space between the start of the line and the textrun boundary
1646 // (this is so we can be sure the line breaks will be set properly
1647 // on the textruns we construct).
1648 // The possibly-partial text runs up to and including the first space
1649 // are not reconstructed. We construct partial text runs for that text ---
1650 // for the sake of simplifying the code and feeding the linebreaker ---
1651 // but we discard them instead of assigning them to frames.
1652 // This is a little awkward because we traverse lines in the reverse direction
1653 // but we traverse the frames in each line in the forward direction.
1654 nsBlockInFlowLineIterator forwardIterator = backIterator;
1655 nsIFrame* stopAtFrame = lineContainerChild;
1656 nsTextFrame* nextLineFirstTextFrame = nullptr;
1657 AutoTArray<char16_t, BIG_TEXT_NODE_SIZE4096> buffer;
1658 bool seenTextRunBoundaryOnLaterLine = false;
1659 bool mayBeginInTextRun = true;
1660 while (true) {
1661 forwardIterator = backIterator;
1662 nsBlockFrame::LineIterator line = backIterator.GetLine();
1663 if (!backIterator.Prev() || backIterator.GetLine()->IsBlock()) {
1664 mayBeginInTextRun = false;
1665 break;
1666 }
1667
1668 BuildTextRunsScanner::FindBoundaryState state = {
1669 stopAtFrame, nullptr, nullptr, bool(seenTextRunBoundaryOnLaterLine),
1670 false, false, buffer};
1671 bool foundBoundary = false;
1672 for (nsIFrame* child : line->ChildFrames()) {
1673 BuildTextRunsScanner::FindBoundaryResult result =
1674 scanner.FindBoundaries(child, &state);
1675 if (result == BuildTextRunsScanner::FB_FOUND_VALID_TEXTRUN_BOUNDARY) {
1676 foundBoundary = true;
1677 break;
1678 } else if (result == BuildTextRunsScanner::FB_STOPPED_AT_STOP_FRAME) {
1679 break;
1680 }
1681 }
1682 if (foundBoundary) {
1683 break;
1684 }
1685 if (!stopAtFrame && state.mLastTextFrame && nextLineFirstTextFrame &&
1686 !scanner.ContinueTextRunAcrossFrames(state.mLastTextFrame,
1687 nextLineFirstTextFrame)) {
1688 // Found a usable textrun boundary at the end of the line
1689 if (state.mSeenSpaceForLineBreakingOnThisLine) {
1690 break;
1691 }
1692 seenTextRunBoundaryOnLaterLine = true;
1693 } else if (state.mSeenTextRunBoundaryOnThisLine) {
1694 seenTextRunBoundaryOnLaterLine = true;
1695 }
1696 stopAtFrame = nullptr;
1697 if (state.mFirstTextFrame) {
1698 nextLineFirstTextFrame = state.mFirstTextFrame;
1699 }
1700 }
1701 scanner.SetSkipIncompleteTextRuns(mayBeginInTextRun);
1702
1703 // Now iterate over all text frames starting from the current line.
1704 // First-in-flow text frames will be accumulated into textRunFrames as we go.
1705 // When a text run boundary is required we flush textRunFrames ((re)building
1706 // their gfxTextRuns as necessary).
1707 bool seenStartLine = false;
1708 uint32_t linesAfterStartLine = 0;
1709 do {
1710 nsBlockFrame::LineIterator line = forwardIterator.GetLine();
1711 if (line->IsBlock()) {
1712 break;
1713 }
1714 line->SetInvalidateTextRuns(false);
1715 scanner.SetAtStartOfLine();
1716 scanner.SetCommonAncestorWithLastFrame(nullptr);
1717 for (nsIFrame* child : line->ChildFrames()) {
1718 scanner.ScanFrame(child);
1719 }
1720 if (line.get() == startLine.get()) {
1721 seenStartLine = true;
1722 }
1723 if (seenStartLine) {
1724 ++linesAfterStartLine;
1725 if (linesAfterStartLine >= NUM_LINES_TO_BUILD_TEXT_RUNS200 &&
1726 scanner.CanStopOnThisLine()) {
1727 // Don't flush frames; we may be in the middle of a textrun
1728 // that we can't end here. That's OK, we just won't build it.
1729 // Note that we must already have finished the textrun for aForFrame,
1730 // because we've seen the end of a textrun in a line after the line
1731 // containing aForFrame.
1732 scanner.FlushLineBreaks(nullptr);
1733 // This flushes out mMappedFlows and mLineBreakBeforeFrames, which
1734 // silences assertions in the scanner destructor.
1735 scanner.ResetRunInfo();
1736 return;
1737 }
1738 }
1739 } while (forwardIterator.Next());
1740
1741 // Set mStartOfLine so FlushFrames knows its textrun ends a line
1742 scanner.SetAtStartOfLine();
1743 scanner.FlushFrames(true, false);
1744}
1745
1746static char16_t* ExpandBuffer(char16_t* aDest, uint8_t* aSrc, uint32_t aCount) {
1747 while (aCount) {
1748 *aDest = *aSrc;
1749 ++aDest;
1750 ++aSrc;
1751 --aCount;
1752 }
1753 return aDest;
1754}
1755
1756bool BuildTextRunsScanner::IsTextRunValidForMappedFlows(
1757 const gfxTextRun* aTextRun) {
1758 if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow) {
1759 return mMappedFlows.Length() == 1 &&
1760 mMappedFlows[0].mStartFrame == GetFrameForSimpleFlow(aTextRun) &&
1761 mMappedFlows[0].mEndFrame == nullptr;
1762 }
1763
1764 auto userData = static_cast<TextRunUserData*>(aTextRun->GetUserData());
1765 TextRunMappedFlow* userMappedFlows = GetMappedFlows(aTextRun);
1766 if (userData->mMappedFlowCount != mMappedFlows.Length()) {
1767 return false;
1768 }
1769 for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) {
1770 if (userMappedFlows[i].mStartFrame != mMappedFlows[i].mStartFrame ||
1771 int32_t(userMappedFlows[i].mContentLength) !=
1772 mMappedFlows[i].GetContentEnd() -
1773 mMappedFlows[i].mStartFrame->GetContentOffset()) {
1774 return false;
1775 }
1776 }
1777 return true;
1778}
1779
1780/**
1781 * This gets called when we need to make a text run for the current list of
1782 * frames.
1783 */
1784void BuildTextRunsScanner::FlushFrames(bool aFlushLineBreaks,
1785 bool aSuppressTrailingBreak) {
1786 RefPtr<gfxTextRun> textRun;
1787 if (!mMappedFlows.IsEmpty()) {
1788 if (!mSkipIncompleteTextRuns && mCurrentFramesAllSameTextRun &&
1789 !!(mCurrentFramesAllSameTextRun->GetFlags2() &
1790 nsTextFrameUtils::Flags::IncomingWhitespace) ==
1791 !!(mCurrentRunContextInfo &
1792 nsTextFrameUtils::INCOMING_WHITESPACE) &&
1793 !!(mCurrentFramesAllSameTextRun->GetFlags() &
1794 gfx::ShapedTextFlags::TEXT_INCOMING_ARABICCHAR) ==
1795 !!(mCurrentRunContextInfo &
1796 nsTextFrameUtils::INCOMING_ARABICCHAR) &&
1797 IsTextRunValidForMappedFlows(mCurrentFramesAllSameTextRun)) {
1798 // Optimization: We do not need to (re)build the textrun.
1799 textRun = mCurrentFramesAllSameTextRun;
1800
1801 if (mDoLineBreaking) {
1802 // Feed this run's text into the linebreaker to provide context.
1803 if (!SetupLineBreakerContext(textRun)) {
1804 return;
1805 }
1806 }
1807
1808 // Update mNextRunContextInfo appropriately
1809 mNextRunContextInfo = nsTextFrameUtils::INCOMING_NONE;
1810 if (textRun->GetFlags2() & nsTextFrameUtils::Flags::TrailingWhitespace) {
1811 mNextRunContextInfo |= nsTextFrameUtils::INCOMING_WHITESPACE;
1812 }
1813 if (textRun->GetFlags() &
1814 gfx::ShapedTextFlags::TEXT_TRAILING_ARABICCHAR) {
1815 mNextRunContextInfo |= nsTextFrameUtils::INCOMING_ARABICCHAR;
1816 }
1817 } else {
1818 AutoTArray<uint8_t, BIG_TEXT_NODE_SIZE4096> buffer;
1819 uint32_t bufferSize = mMaxTextLength * (mDoubleByteText ? 2 : 1);
1820 if (bufferSize < mMaxTextLength || bufferSize == UINT32_MAX(4294967295U) ||
1821 !buffer.AppendElements(bufferSize, fallible)) {
1822 return;
1823 }
1824 textRun = BuildTextRunForFrames(buffer.Elements());
1825 if (textRun) {
1826 if (gfxPlatform::GetPlatform()->OpenTypeSVGEnabled()) {
1827 // Record the textrun for call to CreateObserversForAnimatedGlyphs,
1828 // done after line-breaking etc is complete.
1829 mCreatedTextRuns.AppendElement(textRun);
1830 }
1831 }
1832 }
1833 }
1834
1835 if (aFlushLineBreaks) {
1836 FlushLineBreaks(aSuppressTrailingBreak ? nullptr : textRun.get());
1837 }
1838
1839 mCanStopOnThisLine = true;
1840 ResetRunInfo();
1841}
1842
1843void BuildTextRunsScanner::FlushLineBreaks(gfxTextRun* aTrailingTextRun) {
1844 // If the line-breaker is buffering a potentially-unfinished word,
1845 // preserve the state of being in-word so that we don't spuriously
1846 // capitalize the next letter.
1847 bool inWord = mLineBreaker.InWord();
1848 bool trailingLineBreak;
1849 nsresult rv = mLineBreaker.Reset(&trailingLineBreak);
1850 mLineBreaker.SetWordContinuation(inWord);
1851 // textRun may be null for various reasons, including because we constructed
1852 // a partial textrun just to get the linebreaker and other state set up
1853 // to build the next textrun.
1854 if (NS_SUCCEEDED(rv)((bool)(__builtin_expect(!!(!NS_FAILED_impl(rv)), 1))) && trailingLineBreak && aTrailingTextRun) {
1855 aTrailingTextRun->SetFlagBits(nsTextFrameUtils::Flags::HasTrailingBreak);
1856 }
1857
1858 for (uint32_t i = 0; i < mBreakSinks.Length(); ++i) {
1859 // TODO cause frames associated with the textrun to be reflowed, if they
1860 // aren't being reflowed already!
1861 mBreakSinks[i]->Finish(mMissingFonts);
1862 }
1863 mBreakSinks.Clear();
1864}
1865
1866void BuildTextRunsScanner::AccumulateRunInfo(nsTextFrame* aFrame) {
1867 if (mMaxTextLength != UINT32_MAX(4294967295U)) {
1868 NS_ASSERTION(mMaxTextLength < UINT32_MAX - aFrame->GetContentLength(),do { if (!(mMaxTextLength < (4294967295U) - aFrame->GetContentLength
())) { NS_DebugBreak(NS_DEBUG_ASSERTION, "integer overflow", "mMaxTextLength < UINT32_MAX - aFrame->GetContentLength()"
, "./../../../layout/generic/nsTextFrame.cpp", 1869); MOZ_PretendNoReturn
(); } } while (0)
1869 "integer overflow")do { if (!(mMaxTextLength < (4294967295U) - aFrame->GetContentLength
())) { NS_DebugBreak(NS_DEBUG_ASSERTION, "integer overflow", "mMaxTextLength < UINT32_MAX - aFrame->GetContentLength()"
, "./../../../layout/generic/nsTextFrame.cpp", 1869); MOZ_PretendNoReturn
(); } } while (0)
;
1870 if (mMaxTextLength >= UINT32_MAX(4294967295U) - aFrame->GetContentLength()) {
1871 mMaxTextLength = UINT32_MAX(4294967295U);
1872 } else {
1873 mMaxTextLength += aFrame->GetContentLength();
1874 }
1875 }
1876 mDoubleByteText |= aFrame->CharacterDataBuffer().Is2b();
1877 mLastFrame = aFrame;
1878 mCommonAncestorWithLastFrame = aFrame->GetParent();
1879
1880 MappedFlow* mappedFlow = &mMappedFlows[mMappedFlows.Length() - 1];
1881 NS_ASSERTION(mappedFlow->mStartFrame == aFrame ||do { if (!(mappedFlow->mStartFrame == aFrame || mappedFlow
->GetContentEnd() == aFrame->GetContentOffset())) { NS_DebugBreak
(NS_DEBUG_ASSERTION, "Overlapping or discontiguous frames => BAD"
, "mappedFlow->mStartFrame == aFrame || mappedFlow->GetContentEnd() == aFrame->GetContentOffset()"
, "./../../../layout/generic/nsTextFrame.cpp", 1883); MOZ_PretendNoReturn
(); } } while (0)
1882 mappedFlow->GetContentEnd() == aFrame->GetContentOffset(),do { if (!(mappedFlow->mStartFrame == aFrame || mappedFlow
->GetContentEnd() == aFrame->GetContentOffset())) { NS_DebugBreak
(NS_DEBUG_ASSERTION, "Overlapping or discontiguous frames => BAD"
, "mappedFlow->mStartFrame == aFrame || mappedFlow->GetContentEnd() == aFrame->GetContentOffset()"
, "./../../../layout/generic/nsTextFrame.cpp", 1883); MOZ_PretendNoReturn
(); } } while (0)
1883 "Overlapping or discontiguous frames => BAD")do { if (!(mappedFlow->mStartFrame == aFrame || mappedFlow
->GetContentEnd() == aFrame->GetContentOffset())) { NS_DebugBreak
(NS_DEBUG_ASSERTION, "Overlapping or discontiguous frames => BAD"
, "mappedFlow->mStartFrame == aFrame || mappedFlow->GetContentEnd() == aFrame->GetContentOffset()"
, "./../../../layout/generic/nsTextFrame.cpp", 1883); MOZ_PretendNoReturn
(); } } while (0)
;
1884 mappedFlow->mEndFrame = aFrame->GetNextContinuation();
1885 if (mCurrentFramesAllSameTextRun != aFrame->GetTextRun(mWhichTextRun)) {
1886 mCurrentFramesAllSameTextRun = nullptr;
1887 }
1888
1889 if (mStartOfLine) {
1890 mLineBreakBeforeFrames.AppendElement(aFrame);
1891 mStartOfLine = false;
1892 }
1893
1894 // Default limits used by `hyphenate-limit-chars` for `auto` components, as
1895 // suggested by the CSS Text spec.
1896 // TODO: consider making these sensitive to the context, e.g. increasing the
1897 // values for long line lengths to reduce the tendency to hyphenate too much.
1898 const uint32_t kDefaultHyphenateTotalWordLength = 5;
1899 const uint32_t kDefaultHyphenatePreBreakLength = 2;
1900 const uint32_t kDefaultHyphenatePostBreakLength = 2;
1901
1902 const auto& hyphenateLimitChars = aFrame->StyleText()->mHyphenateLimitChars;
1903 uint32_t pre =
1904 hyphenateLimitChars.pre_hyphen_length.IsAuto()
1905 ? kDefaultHyphenatePreBreakLength
1906 : std::max(0, hyphenateLimitChars.pre_hyphen_length.AsNumber());
1907 uint32_t post =
1908 hyphenateLimitChars.post_hyphen_length.IsAuto()
1909 ? kDefaultHyphenatePostBreakLength
1910 : std::max(0, hyphenateLimitChars.post_hyphen_length.AsNumber());
1911 uint32_t total =
1912 hyphenateLimitChars.total_word_length.IsAuto()
1913 ? kDefaultHyphenateTotalWordLength
1914 : std::max(0, hyphenateLimitChars.total_word_length.AsNumber());
1915 total = std::max(total, pre + post);
1916 mLineBreaker.SetHyphenateLimitChars(total, pre, post);
1917}
1918
1919static bool HasTerminalNewline(const nsTextFrame* aFrame) {
1920 if (aFrame->GetContentLength() == 0) {
1921 return false;
1922 }
1923 const CharacterDataBuffer& characterDataBuffer =
1924 aFrame->CharacterDataBuffer();
1925 return characterDataBuffer.CharAt(
1926 AssertedCast<uint32_t>(aFrame->GetContentEnd()) - 1) == '\n';
1927}
1928
1929static gfxFont::Metrics GetFirstFontMetrics(gfxFontGroup* aFontGroup,
1930 bool aVerticalMetrics) {
1931 if (!aFontGroup) {
1932 return gfxFont::Metrics();
1933 }
1934 RefPtr<gfxFont> font = aFontGroup->GetFirstValidFont();
1935 return font->GetMetrics(aVerticalMetrics ? nsFontMetrics::eVertical
1936 : nsFontMetrics::eHorizontal);
1937}
1938
1939static gfxFloat GetMinTabAdvanceAppUnits(const gfxTextRun* aTextRun) {
1940 gfxFloat chWidthAppUnits = NS_round(
1941 GetFirstFontMetrics(aTextRun->GetFontGroup(), aTextRun->IsVertical())
1942 .ZeroOrAveCharWidth() *
1943 aTextRun->GetAppUnitsPerDevUnit());
1944 return 0.5 * chWidthAppUnits;
1945}
1946
1947static float GetSVGFontSizeScaleFactor(nsIFrame* aFrame) {
1948 if (!aFrame->IsInSVGTextSubtree()) {
1949 return 1.0f;
1950 }
1951 auto* container =
1952 nsLayoutUtils::GetClosestFrameOfType(aFrame, LayoutFrameType::SVGText);
1953 MOZ_ASSERT(container)do { static_assert( mozilla::detail::AssertionConditionType<
decltype(container)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(container))), 0))) { do { } while
(false); MOZ_ReportAssertionFailure("container", "./../../../layout/generic/nsTextFrame.cpp"
, 1953); AnnotateMozCrashReason("MOZ_ASSERT" "(" "container" ")"
); do { MOZ_CrashSequence(__null, 1953); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
1954 return static_cast<SVGTextFrame*>(container)->GetFontSizeScaleFactor();
1955}
1956
1957static nscoord ResolveLetterSpacing(nsIFrame* aFrame,
1958 const nsStyleText& aStyleText) {
1959 if (aFrame->IsInSVGTextSubtree()) {
1960 // SVG text can have a scaling factor applied so that very small or very
1961 // large font-sizes don't suffer from poor glyph placement due to app unit
1962 // rounding. The used letter-spacing value must be scaled by the same
1963 // factor.
1964 return GetSVGFontSizeScaleFactor(aFrame) *
1965 aStyleText.mLetterSpacing.Resolve(
1966 [&] { return aFrame->StyleFont()->mSize.ToAppUnits(); });
1967 }
1968
1969 return aStyleText.mLetterSpacing.Resolve(
1970 [&] { return aFrame->StyleFont()->mSize.ToAppUnits(); });
1971}
1972
1973// This function converts non-coord values (e.g. percentages) to nscoord.
1974static nscoord ResolveWordSpacing(nsIFrame* aFrame,
1975 const nsStyleText& aStyleText) {
1976 if (aFrame->IsInSVGTextSubtree()) {
1977 // SVG text can have a scaling factor applied so that very small or very
1978 // large font-sizes don't suffer from poor glyph placement due to app unit
1979 // rounding. The used word-spacing value must be scaled by the same
1980 // factor.
1981 return GetSVGFontSizeScaleFactor(aFrame) *
1982 aStyleText.mWordSpacing.Resolve(
1983 [&] { return aFrame->StyleFont()->mSize.ToAppUnits(); });
1984 }
1985
1986 return aStyleText.mWordSpacing.Resolve(
1987 [&] { return aFrame->StyleFont()->mSize.ToAppUnits(); });
1988}
1989
1990gfx::ShapedTextFlags nsTextFrame::GetSpacingFlags() const {
1991 const nsStyleText* styleText = StyleText();
1992 const auto& ls = styleText->mLetterSpacing;
1993 const auto& ws = styleText->mWordSpacing;
1994
1995 // It's possible to have a calc() value that computes to zero but for which
1996 // IsDefinitelyZero() is false, in which case we'll return
1997 // TEXT_ENABLE_SPACING unnecessarily. That's ok because such cases are likely
1998 // to be rare, and avoiding TEXT_ENABLE_SPACING is just an optimization.
1999 bool nonStandardSpacing =
2000 !ls.IsDefinitelyZero() || !ws.IsDefinitelyZero() ||
2001 TextAutospace::Enabled(styleText->EffectiveTextAutospace(), this);
2002 return nonStandardSpacing ? gfx::ShapedTextFlags::TEXT_ENABLE_SPACING
2003 : gfx::ShapedTextFlags();
2004}
2005
2006// Returns true if the frame has alignment-baseline and baseline-shift
2007// set to their initial values.
2008static bool HasDefaultVerticalAlignment(const nsIFrame* aFrame) {
2009 const auto& alignmentBaseline = aFrame->StyleDisplay()->mAlignmentBaseline;
2010 if (alignmentBaseline != StyleAlignmentBaseline::Baseline) {
2011 return false;
2012 }
2013
2014 const auto& baselineShift = aFrame->StyleDisplay()->mBaselineShift;
2015 if (baselineShift.IsKeyword() ||
2016 !baselineShift.AsLength().IsDefinitelyZero()) {
2017 return false;
2018 }
2019
2020 return true;
2021}
2022
2023bool BuildTextRunsScanner::ContinueTextRunAcrossFrames(nsTextFrame* aFrame1,
2024 nsTextFrame* aFrame2) {
2025 // We don't need to check font size inflation, since
2026 // |FindLineContainer| above (via |nsIFrame::CanContinueTextRun|)
2027 // ensures that text runs never cross block boundaries. This means
2028 // that the font size inflation on all text frames in the text run is
2029 // already guaranteed to be the same as each other (and for the line
2030 // container).
2031 if (mBidiEnabled) {
2032 FrameBidiData data1 = aFrame1->GetBidiData();
2033 FrameBidiData data2 = aFrame2->GetBidiData();
2034 if (data1.embeddingLevel != data2.embeddingLevel ||
2035 data2.precedingControl != kBidiLevelNone) {
2036 return false;
2037 }
2038 }
2039
2040 ComputedStyle* sc1 = aFrame1->Style();
2041 ComputedStyle* sc2 = aFrame2->Style();
2042
2043 // Any difference in writing-mode/directionality inhibits shaping across
2044 // the boundary.
2045 WritingMode wm(sc1);
2046 if (wm != WritingMode(sc2)) {
2047 return false;
2048 }
2049
2050 const nsStyleText* textStyle1 = sc1->StyleText();
2051 // If the first frame ends in a preformatted newline, then we end the textrun
2052 // here. This avoids creating giant textruns for an entire plain text file.
2053 // Note that we create a single text frame for a preformatted text node,
2054 // even if it has newlines in it, so typically we won't see trailing newlines
2055 // until after reflow has broken up the frame into one (or more) frames per
2056 // line. That's OK though.
2057 if (textStyle1->NewlineIsSignificant(aFrame1) &&
2058 HasTerminalNewline(aFrame1)) {
2059 return false;
2060 }
2061
2062 if (aFrame1->GetParent()->GetContent() !=
2063 aFrame2->GetParent()->GetContent()) {
2064 // Does aFrame, or any ancestor between it and aAncestor, have a property
2065 // that should inhibit cross-element-boundary shaping on aSide?
2066 auto PreventCrossBoundaryShaping =
2067 [](const nsIFrame* aFrame, const nsIFrame* aAncestor, Side aSide) {
2068 while (aFrame != aAncestor) {
2069 ComputedStyle* ctx = aFrame->Style();
2070 const auto anchorResolutionParams =
2071 AnchorPosResolutionParams::From(aFrame);
2072 // According to https://drafts.csswg.org/css-text/#boundary-shaping:
2073 //
2074 // Text shaping must be broken at inline box boundaries when any of
2075 // the following are true for any box whose boundary separates the
2076 // two typographic character units:
2077 //
2078 // 1. Any of margin/border/padding separating the two typographic
2079 // character units in the inline axis is non-zero.
2080 const auto margin =
2081 ctx->StyleMargin()->GetMargin(aSide, anchorResolutionParams);
2082 if (!margin->ConvertsToLength() ||
2083 margin->AsLengthPercentage().ToLength() != 0) {
2084 return true;
2085 }
2086 const auto& padding = ctx->StylePadding()->mPadding.Get(aSide);
2087 if (!padding.ConvertsToLength() || padding.ToLength() != 0) {
2088 return true;
2089 }
2090 if (ctx->StyleBorder()->GetComputedBorderWidth(aSide) != 0) {
2091 return true;
2092 }
2093
2094 // 2. vertical-align is not baseline.
2095 if (!HasDefaultVerticalAlignment(aFrame)) {
2096 return true;
2097 }
2098
2099 // 3. The boundary is a bidi isolation boundary.
2100 const auto unicodeBidi = ctx->StyleTextReset()->mUnicodeBidi;
2101 if (unicodeBidi == StyleUnicodeBidi::Isolate ||
2102 unicodeBidi == StyleUnicodeBidi::IsolateOverride) {
2103 return true;
2104 }
2105
2106 aFrame = aFrame->GetParent();
2107 }
2108 return false;
2109 };
2110
2111 const nsIFrame* ancestor =
2112 nsLayoutUtils::FindNearestCommonAncestorFrameWithinBlock(aFrame1,
2113 aFrame2);
2114
2115 if (!ancestor) {
2116 // The two frames are within different blocks, e.g. due to block
2117 // fragmentation. In theory we shouldn't prevent cross-frame shaping
2118 // here, but it's an edge case where we should rarely decide to allow
2119 // cross-frame shaping, so we don't try harder here.
2120 return false;
2121 }
2122
2123 // We inhibit cross-element-boundary shaping if we're in SVG content,
2124 // as there are too many things SVG might be doing (like applying per-
2125 // element positioning) that wouldn't make sense with shaping across
2126 // the boundary.
2127 if (ancestor->IsInSVGTextSubtree()) {
2128 return false;
2129 }
2130
2131 // Map inline-end and inline-start to physical sides for checking presence
2132 // of non-zero margin/border/padding.
2133 Side side1 = wm.PhysicalSide(LogicalSide::IEnd);
2134 Side side2 = wm.PhysicalSide(LogicalSide::IStart);
2135 // If the frames have an embedding level that is opposite to the writing
2136 // mode, we need to swap which sides we're checking.
2137 if (aFrame1->GetEmbeddingLevel().IsRTL() == wm.IsBidiLTR()) {
2138 std::swap(side1, side2);
2139 }
2140
2141 if (PreventCrossBoundaryShaping(aFrame1, ancestor, side1) ||
2142 PreventCrossBoundaryShaping(aFrame2, ancestor, side2)) {
2143 return false;
2144 }
2145 }
2146
2147 if (aFrame1->GetContent() == aFrame2->GetContent() &&
2148 aFrame1->GetNextInFlow() != aFrame2) {
2149 // aFrame2 must be a non-fluid continuation of aFrame1. This can happen
2150 // sometimes when the unicode-bidi property is used; the bidi resolver
2151 // breaks text into different frames even though the text has the same
2152 // direction. We can't allow these two frames to share the same textrun
2153 // because that would violate our invariant that two flows in the same
2154 // textrun have different content elements.
2155 return false;
2156 }
2157
2158 if (sc1 == sc2) {
2159 return true;
2160 }
2161
2162 const nsStyleText* textStyle2 = sc2->StyleText();
2163 if (textStyle1->mTextTransform != textStyle2->mTextTransform ||
2164 textStyle1->EffectiveWordBreak() != textStyle2->EffectiveWordBreak() ||
2165 textStyle1->mLineBreak != textStyle2->mLineBreak) {
2166 return false;
2167 }
2168
2169 nsPresContext* pc = aFrame1->PresContext();
2170 MOZ_ASSERT(pc == aFrame2->PresContext())do { static_assert( mozilla::detail::AssertionConditionType<
decltype(pc == aFrame2->PresContext())>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(pc == aFrame2->PresContext
()))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("pc == aFrame2->PresContext()", "./../../../layout/generic/nsTextFrame.cpp"
, 2170); AnnotateMozCrashReason("MOZ_ASSERT" "(" "pc == aFrame2->PresContext()"
")"); do { MOZ_CrashSequence(__null, 2170); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
2171
2172 const nsStyleFont* fontStyle1 = sc1->StyleFont();
2173 const nsStyleFont* fontStyle2 = sc2->StyleFont();
2174 nscoord letterSpacing1 = ResolveLetterSpacing(aFrame1, *textStyle1);
2175 nscoord letterSpacing2 = ResolveLetterSpacing(aFrame2, *textStyle2);
2176 return fontStyle1->mFont == fontStyle2->mFont &&
2177 fontStyle1->mLanguage == fontStyle2->mLanguage &&
2178 nsLayoutUtils::GetTextRunFlagsForStyle(sc1, pc, fontStyle1, textStyle1,
2179 letterSpacing1) ==
2180 nsLayoutUtils::GetTextRunFlagsForStyle(sc2, pc, fontStyle2,
2181 textStyle2, letterSpacing2);
2182}
2183
2184void BuildTextRunsScanner::ScanFrame(nsIFrame* aFrame) {
2185 LayoutFrameType frameType = aFrame->Type();
2186 if (frameType == LayoutFrameType::RubyTextContainer) {
2187 // Don't include any ruby text container into the text run.
2188 return;
2189 }
2190
2191 // First check if we can extend the current mapped frame block. This is
2192 // common.
2193 if (mMappedFlows.Length() > 0) {
2194 MappedFlow* mappedFlow = &mMappedFlows[mMappedFlows.Length() - 1];
2195 if (mappedFlow->mEndFrame == aFrame &&
2196 aFrame->HasAnyStateBits(NS_FRAME_IS_FLUID_CONTINUATION)) {
2197 NS_ASSERTION(frameType == LayoutFrameType::Text,do { if (!(frameType == LayoutFrameType::Text)) { NS_DebugBreak
(NS_DEBUG_ASSERTION, "Flow-sibling of a text frame is not a text frame?"
, "frameType == LayoutFrameType::Text", "./../../../layout/generic/nsTextFrame.cpp"
, 2198); MOZ_PretendNoReturn(); } } while (0)
2198 "Flow-sibling of a text frame is not a text frame?")do { if (!(frameType == LayoutFrameType::Text)) { NS_DebugBreak
(NS_DEBUG_ASSERTION, "Flow-sibling of a text frame is not a text frame?"
, "frameType == LayoutFrameType::Text", "./../../../layout/generic/nsTextFrame.cpp"
, 2198); MOZ_PretendNoReturn(); } } while (0)
;
2199
2200 // Don't do this optimization if mLastFrame has a terminal newline...
2201 // it's quite likely preformatted and we might want to end the textrun
2202 // here. This is almost always true:
2203 if (mLastFrame->Style() == aFrame->Style() &&
2204 !HasTerminalNewline(mLastFrame)) {
2205 AccumulateRunInfo(static_cast<nsTextFrame*>(aFrame));
2206 return;
2207 }
2208 }
2209 }
2210
2211 // Now see if we can add a new set of frames to the current textrun
2212 if (frameType == LayoutFrameType::Text) {
2213 nsTextFrame* frame = static_cast<nsTextFrame*>(aFrame);
2214
2215 if (mLastFrame) {
2216 if (!ContinueTextRunAcrossFrames(mLastFrame, frame)) {
2217 FlushFrames(false, false);
2218 } else {
2219 if (mLastFrame->GetContent() == frame->GetContent()) {
2220 AccumulateRunInfo(frame);
2221 return;
2222 }
2223 }
2224 }
2225
2226 MappedFlow* mappedFlow = mMappedFlows.AppendElement();
2227 mappedFlow->mStartFrame = frame;
2228 mappedFlow->mAncestorControllingInitialBreak = mCommonAncestorWithLastFrame;
2229
2230 AccumulateRunInfo(frame);
2231 if (mMappedFlows.Length() == 1) {
2232 mCurrentFramesAllSameTextRun = frame->GetTextRun(mWhichTextRun);
2233 mCurrentRunContextInfo = mNextRunContextInfo;
2234 }
2235 return;
2236 }
2237
2238 if (frameType == LayoutFrameType::Placeholder &&
2239 aFrame->HasAnyStateBits(PLACEHOLDER_FOR_ABSPOS |
2240 PLACEHOLDER_FOR_FIXEDPOS)) {
2241 // Somewhat hacky fix for bug 1418472:
2242 // If this is a placeholder for an absolute-positioned frame, we need to
2243 // flush the line-breaker to prevent the placeholder becoming separated
2244 // from the immediately-following content.
2245 // XXX This will interrupt text shaping (ligatures, etc) if an abs-pos
2246 // element occurs within a word where shaping should be in effect, but
2247 // that's an edge case, unlikely to occur in real content. A more precise
2248 // fix might require better separation of line-breaking from textrun setup,
2249 // but that's a big invasive change (and potentially expensive for perf, as
2250 // it might introduce an additional pass over all the frames).
2251 FlushFrames(true, false);
2252 }
2253
2254 FrameTextTraversal traversal = CanTextCrossFrameBoundary(aFrame);
2255 bool isBR = frameType == LayoutFrameType::Br;
2256 if (!traversal.mLineBreakerCanCrossFrameBoundary) {
2257 // BR frames are special. We do not need or want to record a break
2258 // opportunity before a BR frame.
2259 FlushFrames(true, isBR);
2260 mCommonAncestorWithLastFrame = aFrame;
2261 mNextRunContextInfo &= ~nsTextFrameUtils::INCOMING_WHITESPACE;
2262 mStartOfLine = false;
2263 } else if (!traversal.mTextRunCanCrossFrameBoundary) {
2264 FlushFrames(false, false);
2265 }
2266
2267 for (nsIFrame* f = traversal.NextFrameToScan(); f;
2268 f = traversal.NextFrameToScan()) {
2269 ScanFrame(f);
2270 }
2271
2272 if (!traversal.mLineBreakerCanCrossFrameBoundary) {
2273 // Really if we're a BR frame this is unnecessary since descendInto will be
2274 // false. In fact this whole "if" statement should move into the
2275 // descendInto.
2276 FlushFrames(true, isBR);
2277 mCommonAncestorWithLastFrame = aFrame;
2278 mNextRunContextInfo &= ~nsTextFrameUtils::INCOMING_WHITESPACE;
2279 } else if (!traversal.mTextRunCanCrossFrameBoundary) {
2280 FlushFrames(false, false);
2281 }
2282
2283 LiftCommonAncestorWithLastFrameToParent(aFrame->GetParent());
2284}
2285
2286nsTextFrame* BuildTextRunsScanner::GetNextBreakBeforeFrame(uint32_t* aIndex) {
2287 uint32_t index = *aIndex;
2288 if (index >= mLineBreakBeforeFrames.Length()) {
2289 return nullptr;
2290 }
2291 *aIndex = index + 1;
2292 return static_cast<nsTextFrame*>(mLineBreakBeforeFrames.ElementAt(index));
2293}
2294
2295static gfxFontGroup* GetFontGroupForFrame(
2296 const nsIFrame* aFrame, float aFontSizeInflation,
2297 nsFontMetrics** aOutFontMetrics = nullptr) {
2298 RefPtr<nsFontMetrics> metrics =
2299 nsLayoutUtils::GetFontMetricsForFrame(aFrame, aFontSizeInflation);
2300 gfxFontGroup* fontGroup = metrics->GetThebesFontGroup();
2301
2302 // Populate outparam before we return:
2303 if (aOutFontMetrics) {
2304 metrics.forget(aOutFontMetrics);
2305 }
2306 // XXX this is a bit bogus, we're releasing 'metrics' so the
2307 // returned font-group might actually be torn down, although because
2308 // of the way the device context caches font metrics, this seems to
2309 // not actually happen. But we should fix this.
2310 return fontGroup;
2311}
2312
2313nsFontMetrics* nsTextFrame::InflatedFontMetrics() const {
2314 if (!mFontMetrics) {
2315 float inflation = nsLayoutUtils::FontSizeInflationFor(this);
2316 mFontMetrics = nsLayoutUtils::GetFontMetricsForFrame(this, inflation);
2317 }
2318 return mFontMetrics;
2319}
2320
2321static gfxFontGroup* GetInflatedFontGroupForFrame(nsTextFrame* aFrame) {
2322 gfxTextRun* textRun = aFrame->GetTextRun(nsTextFrame::eInflated);
2323 if (textRun) {
2324 return textRun->GetFontGroup();
2325 }
2326 return aFrame->InflatedFontMetrics()->GetThebesFontGroup();
2327}
2328
2329static already_AddRefed<DrawTarget> CreateReferenceDrawTarget(
2330 const nsTextFrame* aTextFrame) {
2331 UniquePtr<gfxContext> ctx =
2332 aTextFrame->PresShell()->CreateReferenceRenderingContext();
2333 RefPtr<DrawTarget> dt = ctx->GetDrawTarget();
2334 return dt.forget();
2335}
2336
2337static already_AddRefed<gfxTextRun> GetHyphenTextRun(nsTextFrame* aTextFrame,
2338 DrawTarget* aDrawTarget) {
2339 RefPtr<DrawTarget> dt = aDrawTarget;
2340 if (!dt) {
2341 dt = CreateReferenceDrawTarget(aTextFrame);
2342 if (!dt) {
2343 return nullptr;
2344 }
2345 }
2346
2347 RefPtr<nsFontMetrics> fm =
2348 nsLayoutUtils::GetInflatedFontMetricsForFrame(aTextFrame);
2349 auto* fontGroup = fm->GetThebesFontGroup();
2350 auto appPerDev = aTextFrame->PresContext()->AppUnitsPerDevPixel();
2351 const auto& hyphenateChar = aTextFrame->StyleText()->mHyphenateCharacter;
2352 gfx::ShapedTextFlags flags =
2353 nsLayoutUtils::GetTextRunOrientFlagsForStyle(aTextFrame->Style());
2354 // Make the directionality of the hyphen run (in case it is multi-char) match
2355 // the text frame.
2356 if (aTextFrame->GetWritingMode().IsBidiRTL()) {
2357 flags |= gfx::ShapedTextFlags::TEXT_IS_RTL;
2358 }
2359 if (hyphenateChar.IsAuto()) {
2360 return fontGroup->MakeHyphenTextRun(dt, flags, appPerDev);
2361 }
2362 auto* missingFonts = aTextFrame->PresContext()->MissingFontRecorder();
2363 const NS_ConvertUTF8toUTF16 hyphenStr(hyphenateChar.AsString().AsString());
2364 return fontGroup->MakeTextRun(hyphenStr.BeginReading(), hyphenStr.Length(),
2365 dt, appPerDev, flags, nsTextFrameUtils::Flags(),
2366 missingFonts);
2367}
2368
2369already_AddRefed<gfxTextRun> BuildTextRunsScanner::BuildTextRunForFrames(
2370 void* aTextBuffer) {
2371 gfxSkipChars skipChars;
2372
2373 const void* textPtr = aTextBuffer;
2374 bool anyTextTransformStyle = false;
2375 bool anyMathMLStyling = false;
2376 bool anyTextEmphasis = false;
2377 uint8_t sstyScriptLevel = 0;
2378 uint32_t mathFlags = 0;
2379 gfx::ShapedTextFlags flags = gfx::ShapedTextFlags();
2380 nsTextFrameUtils::Flags flags2 = nsTextFrameUtils::Flags::NoBreaks;
2381
2382 if (mCurrentRunContextInfo & nsTextFrameUtils::INCOMING_WHITESPACE) {
2383 flags2 |= nsTextFrameUtils::Flags::IncomingWhitespace;
2384 }
2385 if (mCurrentRunContextInfo & nsTextFrameUtils::INCOMING_ARABICCHAR) {
2386 flags |= gfx::ShapedTextFlags::TEXT_INCOMING_ARABICCHAR;
2387 }
2388
2389 AutoTArray<int32_t, 50> textBreakPoints;
2390 TextRunUserData dummyData;
2391 TextRunMappedFlow dummyMappedFlow;
2392 TextRunMappedFlow* userMappedFlows;
2393 TextRunUserData* userData;
2394 TextRunUserData* userDataToDestroy;
2395 // If the situation is particularly simple (and common) we don't need to
2396 // allocate userData.
2397 if (mMappedFlows.Length() == 1 && !mMappedFlows[0].mEndFrame &&
2398 mMappedFlows[0].mStartFrame->GetContentOffset() == 0) {
2399 userData = &dummyData;
2400 userMappedFlows = &dummyMappedFlow;
2401 userDataToDestroy = nullptr;
2402 dummyData.mMappedFlowCount = mMappedFlows.Length();
2403 dummyData.mLastFlowIndex = 0;
2404 } else {
2405 userData = CreateUserData(mMappedFlows.Length());
2406 userMappedFlows = reinterpret_cast<TextRunMappedFlow*>(userData + 1);
2407 userDataToDestroy = userData;
2408 }
2409
2410 uint32_t currentTransformedTextOffset = 0;
2411
2412 uint32_t nextBreakIndex = 0;
2413 nsTextFrame* nextBreakBeforeFrame = GetNextBreakBeforeFrame(&nextBreakIndex);
2414 bool isSVG = mLineContainer->IsInSVGTextSubtree();
2415 bool enabledJustification =
2416 (mLineContainer->StyleText()->mTextAlign == StyleTextAlign::Justify ||
2417 mLineContainer->StyleText()->mTextAlignLast ==
2418 StyleTextAlignLast::Justify);
2419
2420 const nsStyleText* textStyle = nullptr;
2421 const nsStyleFont* fontStyle = nullptr;
2422 ComputedStyle* lastComputedStyle = nullptr;
2423 for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) {
2424 MappedFlow* mappedFlow = &mMappedFlows[i];
2425 nsTextFrame* f = mappedFlow->mStartFrame;
2426
2427 lastComputedStyle = f->Style();
2428 // Detect use of text-transform or font-variant anywhere in the run
2429 textStyle = f->StyleText();
2430 if (!textStyle->mTextTransform.IsNone() ||
2431 textStyle->mWebkitTextSecurity != StyleTextSecurity::None ||
2432 // text-combine-upright requires converting from full-width
2433 // characters to non-full-width correspendent in some cases.
2434 lastComputedStyle->IsTextCombined()) {
2435 anyTextTransformStyle = true;
2436 }
2437 if (textStyle->HasEffectiveTextEmphasis()) {
2438 anyTextEmphasis = true;
2439 }
2440 flags |= f->GetSpacingFlags();
2441 nsTextFrameUtils::CompressionMode compression =
2442 GetCSSWhitespaceToCompressionMode(f, textStyle);
2443 if ((enabledJustification || f->ShouldSuppressLineBreak()) && !isSVG) {
2444 flags |= gfx::ShapedTextFlags::TEXT_ENABLE_SPACING;
2445 }
2446 fontStyle = f->StyleFont();
2447 nsIFrame* parent = mLineContainer->GetParent();
2448 if (StyleMathVariant::None != fontStyle->mMathVariant) {
2449 if (StyleMathVariant::Normal != fontStyle->mMathVariant) {
2450 anyMathMLStyling = true;
2451 }
2452 } else if (mLineContainer->HasAnyStateBits(NS_FRAME_IS_IN_SINGLE_CHAR_MI)) {
2453 flags2 |= nsTextFrameUtils::Flags::IsSingleCharMi;
2454 anyMathMLStyling = true;
2455 }
2456 if (mLineContainer->HasAnyStateBits(TEXT_IS_IN_TOKEN_MATHML)) {
2457 // All MathML tokens except <mtext> use 'math' script.
2458 if (!(parent && parent->GetContent() &&
2459 parent->GetContent()->IsMathMLElement(nsGkAtoms::mtext))) {
2460 flags |= gfx::ShapedTextFlags::TEXT_USE_MATH_SCRIPT;
2461 }
2462 nsIMathMLFrame* mathFrame = do_QueryFrame(parent);
2463 if (mathFrame) {
2464 nsPresentationData presData;
2465 mathFrame->GetPresentationData(presData);
2466 if (presData.flags.contains(MathMLPresentationFlag::Dtls)) {
2467 mathFlags |= MathMLTextRunFactory::MATH_FONT_FEATURE_DTLS;
2468 anyMathMLStyling = true;
2469 }
2470 }
2471 }
2472 nsIFrame* child = mLineContainer;
2473 uint8_t oldScriptLevel = 0;
2474 while (parent &&
2475 child->HasAnyStateBits(NS_FRAME_MATHML_SCRIPT_DESCENDANT)) {
2476 // Reconstruct the script level ignoring any user overrides. It is
2477 // calculated this way instead of using scriptlevel to ensure the
2478 // correct ssty font feature setting is used even if the user sets a
2479 // different (especially negative) scriptlevel.
2480 nsIMathMLFrame* mathFrame = do_QueryFrame(parent);
2481 if (mathFrame) {
2482 sstyScriptLevel += mathFrame->ScriptIncrement(child);
2483 }
2484 if (sstyScriptLevel < oldScriptLevel) {
2485 // overflow
2486 sstyScriptLevel = UINT8_MAX(255);
2487 break;
2488 }
2489 child = parent;
2490 parent = parent->GetParent();
2491 oldScriptLevel = sstyScriptLevel;
2492 }
2493 if (sstyScriptLevel) {
2494 anyMathMLStyling = true;
2495 }
2496
2497 // Figure out what content is included in this flow.
2498 nsIContent* content = f->GetContent();
2499 const CharacterDataBuffer& characterDataBuffer = f->CharacterDataBuffer();
2500 int32_t contentStart = mappedFlow->mStartFrame->GetContentOffset();
2501 int32_t contentEnd = mappedFlow->GetContentEnd();
2502 int32_t contentLength = contentEnd - contentStart;
2503 const nsAtom* language = f->StyleFont()->GetLangAtom();
2504
2505 TextRunMappedFlow* newFlow = &userMappedFlows[i];
2506 newFlow->mStartFrame = mappedFlow->mStartFrame;
2507 newFlow->mDOMOffsetToBeforeTransformOffset =
2508 skipChars.GetOriginalCharCount() -
2509 mappedFlow->mStartFrame->GetContentOffset();
2510 newFlow->mContentLength = contentLength;
2511
2512 while (nextBreakBeforeFrame &&
2513 nextBreakBeforeFrame->GetContent() == content) {
2514 textBreakPoints.AppendElement(nextBreakBeforeFrame->GetContentOffset() +
2515 newFlow->mDOMOffsetToBeforeTransformOffset);
2516 nextBreakBeforeFrame = GetNextBreakBeforeFrame(&nextBreakIndex);
2517 }
2518
2519 nsTextFrameUtils::Flags analysisFlags;
2520 if (characterDataBuffer.Is2b()) {
2521 NS_ASSERTION(mDoubleByteText, "Wrong buffer char size!")do { if (!(mDoubleByteText)) { NS_DebugBreak(NS_DEBUG_ASSERTION
, "Wrong buffer char size!", "mDoubleByteText", "./../../../layout/generic/nsTextFrame.cpp"
, 2521); MOZ_PretendNoReturn(); } } while (0)
;
2522 char16_t* bufStart = static_cast<char16_t*>(aTextBuffer);
2523 char16_t* bufEnd = nsTextFrameUtils::TransformText(
2524 characterDataBuffer.Get2b() + contentStart, contentLength, bufStart,
2525 compression, &mNextRunContextInfo, &skipChars, &analysisFlags,
2526 language);
2527 aTextBuffer = bufEnd;
2528 currentTransformedTextOffset =
2529 bufEnd - static_cast<const char16_t*>(textPtr);
2530 } else {
2531 if (mDoubleByteText) {
2532 // Need to expand the text. First transform it into a temporary buffer,
2533 // then expand.
2534 AutoTArray<uint8_t, BIG_TEXT_NODE_SIZE4096> tempBuf;
2535 uint8_t* bufStart = tempBuf.AppendElements(contentLength, fallible);
2536 if (!bufStart) {
2537 DestroyUserData(userDataToDestroy);
2538 return nullptr;
2539 }
2540 uint8_t* end = nsTextFrameUtils::TransformText(
2541 reinterpret_cast<const uint8_t*>(characterDataBuffer.Get1b()) +
2542 contentStart,
2543 contentLength, bufStart, compression, &mNextRunContextInfo,
2544 &skipChars, &analysisFlags, language);
2545 aTextBuffer =
2546 ExpandBuffer(static_cast<char16_t*>(aTextBuffer),
2547 tempBuf.Elements(), end - tempBuf.Elements());
2548 currentTransformedTextOffset = static_cast<char16_t*>(aTextBuffer) -
2549 static_cast<const char16_t*>(textPtr);
2550 } else {
2551 uint8_t* bufStart = static_cast<uint8_t*>(aTextBuffer);
2552 uint8_t* end = nsTextFrameUtils::TransformText(
2553 reinterpret_cast<const uint8_t*>(characterDataBuffer.Get1b()) +
2554 contentStart,
2555 contentLength, bufStart, compression, &mNextRunContextInfo,
2556 &skipChars, &analysisFlags, language);
2557 aTextBuffer = end;
2558 currentTransformedTextOffset =
2559 end - static_cast<const uint8_t*>(textPtr);
2560 }
2561 }
2562 flags2 |= analysisFlags;
2563 }
2564
2565 void* finalUserData;
2566 if (userData == &dummyData) {
2567 flags2 |= nsTextFrameUtils::Flags::IsSimpleFlow;
2568 userData = nullptr;
2569 finalUserData = mMappedFlows[0].mStartFrame;
2570 } else {
2571 finalUserData = userData;
2572 }
2573
2574 uint32_t transformedLength = currentTransformedTextOffset;
2575
2576 // Now build the textrun
2577 nsTextFrame* firstFrame = mMappedFlows[0].mStartFrame;
2578 float fontInflation;
2579 gfxFontGroup* fontGroup;
2580 if (mWhichTextRun == nsTextFrame::eNotInflated) {
2581 fontInflation = 1.0f;
2582 fontGroup = GetFontGroupForFrame(firstFrame, fontInflation);
2583 } else {
2584 fontInflation = nsLayoutUtils::FontSizeInflationFor(firstFrame);
2585 fontGroup = GetInflatedFontGroupForFrame(firstFrame);
2586 }
2587 MOZ_ASSERT(fontGroup)do { static_assert( mozilla::detail::AssertionConditionType<
decltype(fontGroup)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(fontGroup))), 0))) { do { } while
(false); MOZ_ReportAssertionFailure("fontGroup", "./../../../layout/generic/nsTextFrame.cpp"
, 2587); AnnotateMozCrashReason("MOZ_ASSERT" "(" "fontGroup" ")"
); do { MOZ_CrashSequence(__null, 2587); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
2588
2589 if (flags2 & nsTextFrameUtils::Flags::HasTab) {
2590 flags |= gfx::ShapedTextFlags::TEXT_ENABLE_SPACING;
2591 }
2592 if (flags2 & nsTextFrameUtils::Flags::HasShy) {
2593 flags |= gfx::ShapedTextFlags::TEXT_ENABLE_HYPHEN_BREAKS;
2594 }
2595 if (mBidiEnabled && (firstFrame->GetEmbeddingLevel().IsRTL())) {
2596 flags |= gfx::ShapedTextFlags::TEXT_IS_RTL;
2597 }
2598 if (mNextRunContextInfo & nsTextFrameUtils::INCOMING_WHITESPACE) {
2599 flags2 |= nsTextFrameUtils::Flags::TrailingWhitespace;
2600 }
2601 if (mNextRunContextInfo & nsTextFrameUtils::INCOMING_ARABICCHAR) {
2602 flags |= gfx::ShapedTextFlags::TEXT_TRAILING_ARABICCHAR;
2603 }
2604 // ContinueTextRunAcrossFrames guarantees that it doesn't matter which
2605 // frame's style is used, so we use a mixture of the first frame and
2606 // last frame's style
2607 flags |= nsLayoutUtils::GetTextRunFlagsForStyle(
2608 lastComputedStyle, firstFrame->PresContext(), fontStyle, textStyle,
2609 ResolveLetterSpacing(firstFrame, *textStyle));
2610 // XXX this is a bit of a hack. For performance reasons, if we're favouring
2611 // performance over quality, don't try to get accurate glyph extents.
2612 if (!(flags & gfx::ShapedTextFlags::TEXT_OPTIMIZE_SPEED)) {
2613 flags |= gfx::ShapedTextFlags::TEXT_NEED_BOUNDING_BOX;
2614 }
2615
2616 // Convert linebreak coordinates to transformed string offsets
2617 NS_ASSERTION(nextBreakIndex == mLineBreakBeforeFrames.Length(),do { if (!(nextBreakIndex == mLineBreakBeforeFrames.Length())
) { NS_DebugBreak(NS_DEBUG_ASSERTION, "Didn't find all the frames to break-before..."
, "nextBreakIndex == mLineBreakBeforeFrames.Length()", "./../../../layout/generic/nsTextFrame.cpp"
, 2618); MOZ_PretendNoReturn(); } } while (0)
2618 "Didn't find all the frames to break-before...")do { if (!(nextBreakIndex == mLineBreakBeforeFrames.Length())
) { NS_DebugBreak(NS_DEBUG_ASSERTION, "Didn't find all the frames to break-before..."
, "nextBreakIndex == mLineBreakBeforeFrames.Length()", "./../../../layout/generic/nsTextFrame.cpp"
, 2618); MOZ_PretendNoReturn(); } } while (0)
;
2619 gfxSkipCharsIterator iter(skipChars);
2620 AutoTArray<uint32_t, 50> textBreakPointsAfterTransform;
2621 for (uint32_t i = 0; i < textBreakPoints.Length(); ++i) {
2622 nsTextFrameUtils::AppendLineBreakOffset(
2623 &textBreakPointsAfterTransform,
2624 iter.ConvertOriginalToSkipped(textBreakPoints[i]));
2625 }
2626 if (mStartOfLine) {
2627 nsTextFrameUtils::AppendLineBreakOffset(&textBreakPointsAfterTransform,
2628 transformedLength);
2629 }
2630
2631 // Setup factory chain
2632 bool needsToMaskPassword = NeedsToMaskPassword(firstFrame);
2633 UniquePtr<nsTransformingTextRunFactory> transformingFactory;
2634 if (anyTextTransformStyle || needsToMaskPassword) {
2635 bool useCapitalEsZett = false;
2636 if (StaticPrefs::layout_css_text_transform_uppercase_eszett_enabled()) {
2637 RefPtr firstFont = fontGroup->GetFirstValidFont(0xdf);
2638 useCapitalEsZett = firstFont && firstFont->HasCharacter(0x1e9e);
2639 }
2640 char16_t maskChar =
2641 needsToMaskPassword ? 0 : textStyle->TextSecurityMaskChar();
2642 transformingFactory = MakeUnique<nsCaseTransformTextRunFactory>(
2643 std::move(transformingFactory), false, useCapitalEsZett, maskChar);
2644 }
2645 if (anyMathMLStyling) {
2646 transformingFactory = MakeUnique<MathMLTextRunFactory>(
2647 std::move(transformingFactory), mathFlags, sstyScriptLevel,
2648 fontInflation);
2649 }
2650 nsTArray<RefPtr<nsTransformedCharStyle>> styles;
2651 if (transformingFactory) {
2652 uint32_t unmaskStart = 0, unmaskEnd = UINT32_MAX(4294967295U);
2653 if (needsToMaskPassword) {
2654 unmaskStart = unmaskEnd = UINT32_MAX(4294967295U);
2655 const TextEditor* const passwordEditor =
2656 nsContentUtils::GetExtantTextEditorFromAnonymousNode(
2657 firstFrame->GetContent());
2658 if (passwordEditor && !passwordEditor->IsAllMasked()) {
2659 unmaskStart = passwordEditor->UnmaskedStart();
2660 unmaskEnd = passwordEditor->UnmaskedEnd();
2661 }
2662 }
2663
2664 iter.SetOriginalOffset(0);
2665 for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) {
2666 MappedFlow* mappedFlow = &mMappedFlows[i];
2667 nsTextFrame* f;
2668 ComputedStyle* sc = nullptr;
2669 RefPtr<nsTransformedCharStyle> defaultStyle;
2670 RefPtr<nsTransformedCharStyle> unmaskStyle;
2671 for (f = mappedFlow->mStartFrame; f != mappedFlow->mEndFrame;
2672 f = f->GetNextContinuation()) {
2673 uint32_t skippedOffset = iter.GetSkippedOffset();
2674 // Text-combined frames have content-dependent transform, so we
2675 // want to create new nsTransformedCharStyle for them anyway.
2676 if (sc != f->Style() || sc->IsTextCombined()) {
2677 sc = f->Style();
2678 auto* pc = f->PresContext();
2679 defaultStyle = MakeRefPtr<nsTransformedCharStyle>(sc, pc);
2680 if (sc->IsTextCombined() && f->CountGraphemeClusters() > 1) {
2681 defaultStyle->mForceNonFullWidth = true;
2682 }
2683 if (needsToMaskPassword) {
2684 defaultStyle->mMaskPassword = true;
2685 if (unmaskStart != unmaskEnd) {
2686 unmaskStyle = MakeRefPtr<nsTransformedCharStyle>(sc, pc);
2687 unmaskStyle->mForceNonFullWidth =
2688 defaultStyle->mForceNonFullWidth;
2689 }
2690 }
2691 }
2692 iter.AdvanceOriginal(f->GetContentLength());
2693 uint32_t skippedEnd = iter.GetSkippedOffset();
2694 if (unmaskStyle) {
2695 uint32_t skippedUnmaskStart =
2696 iter.ConvertOriginalToSkipped(unmaskStart);
2697 uint32_t skippedUnmaskEnd = iter.ConvertOriginalToSkipped(unmaskEnd);
2698 iter.SetSkippedOffset(skippedEnd);
2699 for (; skippedOffset < std::min(skippedEnd, skippedUnmaskStart);
2700 ++skippedOffset) {
2701 styles.AppendElement(defaultStyle);
2702 }
2703 for (; skippedOffset < std::min(skippedEnd, skippedUnmaskEnd);
2704 ++skippedOffset) {
2705 styles.AppendElement(unmaskStyle);
2706 }
2707 for (; skippedOffset < skippedEnd; ++skippedOffset) {
2708 styles.AppendElement(defaultStyle);
2709 }
2710 } else {
2711 for (; skippedOffset < skippedEnd; ++skippedOffset) {
2712 styles.AppendElement(defaultStyle);
2713 }
2714 }
2715 }
2716 }
2717 flags2 |= nsTextFrameUtils::Flags::IsTransformed;
2718 NS_ASSERTION(iter.GetSkippedOffset() == transformedLength,do { if (!(iter.GetSkippedOffset() == transformedLength)) { NS_DebugBreak
(NS_DEBUG_ASSERTION, "We didn't cover all the characters in the text run!"
, "iter.GetSkippedOffset() == transformedLength", "./../../../layout/generic/nsTextFrame.cpp"
, 2719); MOZ_PretendNoReturn(); } } while (0)
2719 "We didn't cover all the characters in the text run!")do { if (!(iter.GetSkippedOffset() == transformedLength)) { NS_DebugBreak
(NS_DEBUG_ASSERTION, "We didn't cover all the characters in the text run!"
, "iter.GetSkippedOffset() == transformedLength", "./../../../layout/generic/nsTextFrame.cpp"
, 2719); MOZ_PretendNoReturn(); } } while (0)
;
2720 }
2721
2722 RefPtr<gfxTextRun> textRun;
2723 gfxTextRunFactory::Parameters params = {
2724 mDrawTarget,
2725 finalUserData,
2726 &skipChars,
2727 textBreakPointsAfterTransform.Elements(),
2728 uint32_t(textBreakPointsAfterTransform.Length()),
2729 int32_t(firstFrame->PresContext()->AppUnitsPerDevPixel())};
2730
2731 if (mDoubleByteText) {
2732 const char16_t* text = static_cast<const char16_t*>(textPtr);
2733 if (transformingFactory) {
2734 textRun = transformingFactory->MakeTextRun(
2735 text, transformedLength, &params, fontGroup, flags, flags2,
2736 std::move(styles), true);
2737 } else {
2738 textRun = fontGroup->MakeTextRun(text, transformedLength, &params, flags,
2739 flags2, mMissingFonts);
2740 }
2741 } else {
2742 const uint8_t* text = static_cast<const uint8_t*>(textPtr);
2743 flags |= gfx::ShapedTextFlags::TEXT_IS_8BIT;
2744 if (transformingFactory) {
2745 textRun = transformingFactory->MakeTextRun(
2746 text, transformedLength, &params, fontGroup, flags, flags2,
2747 std::move(styles), true);
2748 } else {
2749 textRun = fontGroup->MakeTextRun(text, transformedLength, &params, flags,
2750 flags2, mMissingFonts);
2751 }
2752 }
2753 if (!textRun) {
2754 DestroyUserData(userDataToDestroy);
2755 return nullptr;
2756 }
2757
2758 // We have to set these up after we've created the textrun, because
2759 // the breaks may be stored in the textrun during this very call.
2760 // This is a bit annoying because it requires another loop over the frames
2761 // making up the textrun, but I don't see a way to avoid this.
2762 // We have to do this if line-breaking is required OR if a text-transform
2763 // is in effect, because we depend on the line-breaker's scanner (via
2764 // BreakSink::Finish) to finish building transformed textruns.
2765 if (mDoLineBreaking || transformingFactory) {
2766 SetupBreakSinksForTextRun(textRun.get(), textPtr);
2767 }
2768
2769 // Ownership of the factory has passed to the textrun
2770 // TODO: bug 1285316: clean up ownership transfer from the factory to
2771 // the textrun
2772 (void)transformingFactory.release();
2773
2774 if (anyTextEmphasis) {
2775 SetupTextEmphasisForTextRun(textRun.get(), textPtr);
2776 }
2777
2778 if (mSkipIncompleteTextRuns) {
2779 mSkipIncompleteTextRuns = !TextContainsLineBreakerWhiteSpace(
2780 textPtr, transformedLength, mDoubleByteText);
2781 // Since we're doing to destroy the user data now, avoid a dangling
2782 // pointer. Strictly speaking we don't need to do this since it should
2783 // not be used (since this textrun will not be used and will be
2784 // itself deleted soon), but it's always better to not have dangling
2785 // pointers around.
2786 textRun->SetUserData(nullptr);
2787 DestroyUserData(userDataToDestroy);
2788 return nullptr;
2789 }
2790
2791 // Actually wipe out the textruns associated with the mapped frames and
2792 // associate those frames with this text run.
2793 AssignTextRun(textRun.get(), fontInflation);
2794 return textRun.forget();
2795}
2796
2797// This is a cut-down version of BuildTextRunForFrames used to set up
2798// context for the line-breaker, when the textrun has already been created.
2799// So it does the same walk over the mMappedFlows, but doesn't actually
2800// build a new textrun.
2801bool BuildTextRunsScanner::SetupLineBreakerContext(gfxTextRun* aTextRun) {
2802 AutoTArray<uint8_t, BIG_TEXT_NODE_SIZE4096> buffer;
2803 uint32_t bufferSize = mMaxTextLength * (mDoubleByteText ? 2 : 1);
2804 if (bufferSize < mMaxTextLength || bufferSize == UINT32_MAX(4294967295U)) {
2805 return false;
2806 }
2807 void* textPtr = buffer.AppendElements(bufferSize, fallible);
2808 if (!textPtr) {
2809 return false;
2810 }
2811
2812 gfxSkipChars skipChars;
2813 const nsAtom* language =
2814 mMappedFlows[0].mStartFrame->StyleFont()->GetLangAtom();
2815
2816 for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) {
2817 MappedFlow* mappedFlow = &mMappedFlows[i];
2818 nsTextFrame* f = mappedFlow->mStartFrame;
2819
2820 const nsStyleText* textStyle = f->StyleText();
2821 nsTextFrameUtils::CompressionMode compression =
2822 GetCSSWhitespaceToCompressionMode(f, textStyle);
2823
2824 // Figure out what content is included in this flow.
2825 const CharacterDataBuffer& characterDataBuffer = f->CharacterDataBuffer();
2826 int32_t contentStart = mappedFlow->mStartFrame->GetContentOffset();
2827 int32_t contentEnd = mappedFlow->GetContentEnd();
2828 int32_t contentLength = contentEnd - contentStart;
2829
2830 nsTextFrameUtils::Flags analysisFlags;
2831 if (characterDataBuffer.Is2b()) {
2832 NS_ASSERTION(mDoubleByteText, "Wrong buffer char size!")do { if (!(mDoubleByteText)) { NS_DebugBreak(NS_DEBUG_ASSERTION
, "Wrong buffer char size!", "mDoubleByteText", "./../../../layout/generic/nsTextFrame.cpp"
, 2832); MOZ_PretendNoReturn(); } } while (0)
;
2833 char16_t* bufStart = static_cast<char16_t*>(textPtr);
2834 char16_t* bufEnd = nsTextFrameUtils::TransformText(
2835 characterDataBuffer.Get2b() + contentStart, contentLength, bufStart,
2836 compression, &mNextRunContextInfo, &skipChars, &analysisFlags,
2837 language);
2838 textPtr = bufEnd;
2839 } else {
2840 if (mDoubleByteText) {
2841 // Need to expand the text. First transform it into a temporary buffer,
2842 // then expand.
2843 AutoTArray<uint8_t, BIG_TEXT_NODE_SIZE4096> tempBuf;
2844 uint8_t* bufStart = tempBuf.AppendElements(contentLength, fallible);
2845 if (!bufStart) {
2846 return false;
2847 }
2848 uint8_t* end = nsTextFrameUtils::TransformText(
2849 reinterpret_cast<const uint8_t*>(characterDataBuffer.Get1b()) +
2850 contentStart,
2851 contentLength, bufStart, compression, &mNextRunContextInfo,
2852 &skipChars, &analysisFlags, language);
2853 textPtr = ExpandBuffer(static_cast<char16_t*>(textPtr),
2854 tempBuf.Elements(), end - tempBuf.Elements());
2855 } else {
2856 uint8_t* bufStart = static_cast<uint8_t*>(textPtr);
2857 uint8_t* end = nsTextFrameUtils::TransformText(
2858 reinterpret_cast<const uint8_t*>(characterDataBuffer.Get1b()) +
2859 contentStart,
2860 contentLength, bufStart, compression, &mNextRunContextInfo,
2861 &skipChars, &analysisFlags, language);
2862 textPtr = end;
2863 }
2864 }
2865 }
2866
2867 // We have to set these up after we've created the textrun, because
2868 // the breaks may be stored in the textrun during this very call.
2869 // This is a bit annoying because it requires another loop over the frames
2870 // making up the textrun, but I don't see a way to avoid this.
2871 SetupBreakSinksForTextRun(aTextRun, buffer.Elements());
2872
2873 return true;
2874}
2875
2876static bool HasCompressedLeadingWhitespace(
2877 nsTextFrame* aFrame, const nsStyleText* aStyleText,
2878 int32_t aContentEndOffset, const gfxSkipCharsIterator& aIterator) {
2879 if (!aIterator.IsOriginalCharSkipped()) {
2880 return false;
2881 }
2882
2883 gfxSkipCharsIterator iter = aIterator;
2884 int32_t frameContentOffset = aFrame->GetContentOffset();
2885 const CharacterDataBuffer& characterDataBuffer =
2886 aFrame->CharacterDataBuffer();
2887 while (frameContentOffset < aContentEndOffset &&
2888 iter.IsOriginalCharSkipped()) {
2889 if (IsTrimmableSpace(characterDataBuffer, frameContentOffset, aStyleText)) {
2890 return true;
2891 }
2892 ++frameContentOffset;
2893 iter.AdvanceOriginal(1);
2894 }
2895 return false;
2896}
2897
2898void BuildTextRunsScanner::SetupBreakSinksForTextRun(gfxTextRun* aTextRun,
2899 const void* aTextPtr) {
2900 using mozilla::intl::LineBreakRule;
2901 using mozilla::intl::WordBreakRule;
2902
2903 // textruns have uniform language
2904 const nsStyleFont* styleFont = mMappedFlows[0].mStartFrame->StyleFont();
2905 // We should only use a language for hyphenation if it was specified
2906 // explicitly.
2907 nsAtom* hyphenationLanguage =
2908 styleFont->mExplicitLanguage ? styleFont->GetLangAtom() : nullptr;
2909 // We keep this pointed at the skip-chars data for the current mappedFlow.
2910 // This lets us cheaply check whether the flow has compressed initial
2911 // whitespace...
2912 gfxSkipCharsIterator iter(aTextRun->GetSkipChars());
2913
2914 for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) {
2915 MappedFlow* mappedFlow = &mMappedFlows[i];
2916 // The CSS word-break value may change within a word, so we reset it for
2917 // each MappedFlow. The line-breaker will flush its text if the property
2918 // actually changes.
2919 const auto* styleText = mappedFlow->mStartFrame->StyleText();
2920 auto wordBreak = styleText->EffectiveWordBreak();
2921 switch (wordBreak) {
2922 case StyleWordBreak::BreakAll:
2923 mLineBreaker.SetWordBreak(WordBreakRule::BreakAll);
2924 break;
2925 case StyleWordBreak::KeepAll:
2926 mLineBreaker.SetWordBreak(WordBreakRule::KeepAll);
2927 break;
2928 case StyleWordBreak::Normal:
2929 default:
2930 MOZ_ASSERT(wordBreak == StyleWordBreak::Normal)do { static_assert( mozilla::detail::AssertionConditionType<
decltype(wordBreak == StyleWordBreak::Normal)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(wordBreak == StyleWordBreak::
Normal))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("wordBreak == StyleWordBreak::Normal", "./../../../layout/generic/nsTextFrame.cpp"
, 2930); AnnotateMozCrashReason("MOZ_ASSERT" "(" "wordBreak == StyleWordBreak::Normal"
")"); do { MOZ_CrashSequence(__null, 2930); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
2931 mLineBreaker.SetWordBreak(WordBreakRule::Normal);
2932 break;
2933 }
2934 switch (styleText->mLineBreak) {
2935 case StyleLineBreak::Auto:
2936 mLineBreaker.SetStrictness(LineBreakRule::Auto);
2937 break;
2938 case StyleLineBreak::Normal:
2939 mLineBreaker.SetStrictness(LineBreakRule::Normal);
2940 break;
2941 case StyleLineBreak::Loose:
2942 mLineBreaker.SetStrictness(LineBreakRule::Loose);
2943 break;
2944 case StyleLineBreak::Strict:
2945 mLineBreaker.SetStrictness(LineBreakRule::Strict);
2946 break;
2947 case StyleLineBreak::Anywhere:
2948 mLineBreaker.SetStrictness(LineBreakRule::Anywhere);
2949 break;
2950 }
2951
2952 uint32_t offset = iter.GetSkippedOffset();
2953 gfxSkipCharsIterator iterNext = iter;
2954 iterNext.AdvanceOriginal(mappedFlow->GetContentEnd() -
2955 mappedFlow->mStartFrame->GetContentOffset());
2956
2957 UniquePtr<BreakSink>* breakSink = mBreakSinks.AppendElement(
2958 MakeUnique<BreakSink>(aTextRun, mDrawTarget, offset));
2959
2960 uint32_t length = iterNext.GetSkippedOffset() - offset;
2961 uint32_t flags = 0;
2962 nsIFrame* initialBreakController =
2963 mappedFlow->mAncestorControllingInitialBreak;
2964 if (!initialBreakController) {
2965 initialBreakController = mLineContainer;
2966 }
2967 if (!initialBreakController->StyleText()->WhiteSpaceCanWrap(
2968 initialBreakController)) {
2969 flags |= nsLineBreaker::BREAK_SUPPRESS_INITIAL;
2970 }
2971 nsTextFrame* startFrame = mappedFlow->mStartFrame;
2972 const nsStyleText* textStyle = startFrame->StyleText();
2973 if (!textStyle->WhiteSpaceCanWrap(startFrame)) {
2974 flags |= nsLineBreaker::BREAK_SUPPRESS_INSIDE;
2975 }
2976 if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::NoBreaks) {
2977 flags |= nsLineBreaker::BREAK_SKIP_SETTING_NO_BREAKS;
2978 }
2979 if (textStyle->mTextTransform & StyleTextTransform::CAPITALIZE) {
2980 flags |= nsLineBreaker::BREAK_NEED_CAPITALIZATION;
2981 }
2982 if (textStyle->mHyphens == StyleHyphens::Auto &&
2983 textStyle->mLineBreak != StyleLineBreak::Anywhere) {
2984 flags |= nsLineBreaker::BREAK_USE_AUTO_HYPHENATION;
2985 }
2986
2987 if (HasCompressedLeadingWhitespace(startFrame, textStyle,
2988 mappedFlow->GetContentEnd(), iter)) {
2989 mLineBreaker.AppendInvisibleWhitespace(flags);
2990 }
2991
2992 if (length > 0) {
2993 BreakSink* sink = mSkipIncompleteTextRuns ? nullptr : (*breakSink).get();
2994 if (mDoubleByteText) {
2995 const char16_t* text = reinterpret_cast<const char16_t*>(aTextPtr);
2996 mLineBreaker.AppendText(hyphenationLanguage, text + offset, length,
2997 flags, sink);
2998 } else {
2999 const uint8_t* text = reinterpret_cast<const uint8_t*>(aTextPtr);
3000 mLineBreaker.AppendText(hyphenationLanguage, text + offset, length,
3001 flags, sink);
3002 }
3003 }
3004
3005 iter = iterNext;
3006 }
3007}
3008
3009static bool MayCharacterHaveEmphasisMark(uint32_t aCh) {
3010 // Punctuation characters that *can* take emphasis marks (exceptions to the
3011 // rule that characters with GeneralCategory=P* do not take emphasis), as per
3012 // https://drafts.csswg.org/css-text-decor/#text-emphasis-style-property
3013 // There are no non-BMP codepoints in the punctuation exceptions, so we can
3014 // just use a 16-bit string to list & check them.
3015 constexpr nsLiteralString kPunctuationAcceptsEmphasis =
3016 u"\x0023" // # NUMBER SIGN
3017 u"\x0025" // % PERCENT SIGN
3018 u"\x0026" // & AMPERSAND
3019 u"\x0040" // @ COMMERCIAL AT
3020 u"\x00A7" // § SECTION SIGN
3021 u"\x00B6" // ¶ PILCROW SIGN
3022 u"\x0609" // ؉ ARABIC-INDIC PER MILLE SIGN
3023 u"\x060A" // ؊ ARABIC-INDIC PER TEN THOUSAND SIGN
3024 u"\x066A" // ٪ ARABIC PERCENT SIGN
3025 u"\x2030" // ‰ PER MILLE SIGN
3026 u"\x2031" // ‱ PER TEN THOUSAND SIGN
3027 u"\x204A" // ⁊ TIRONIAN SIGN ET
3028 u"\x204B" // ⁋ REVERSED PILCROW SIGN
3029 u"\x2053" // ⁓ SWUNG DASH
3030 u"\x303D" // 〽️ PART ALTERNATION MARK
3031 // Characters that are NFKD-equivalent to the above, extracted from
3032 // UnicodeData.txt.
3033 u"\xFE5F" // SMALL NUMBER SIGN;Po;0;ET;<small> 0023;;;;N;;;;;
3034 u"\xFE60" // SMALL AMPERSAND;Po;0;ON;<small> 0026;;;;N;;;;;
3035 u"\xFE6A" // SMALL PERCENT SIGN;Po;0;ET;<small> 0025;;;;N;;;;;
3036 u"\xFE6B" // SMALL COMMERCIAL AT;Po;0;ON;<small> 0040;;;;N;;;;;
3037 u"\xFF03" // FULLWIDTH NUMBER SIGN;Po;0;ET;<wide> 0023;;;;N;;;;;
3038 u"\xFF05" // FULLWIDTH PERCENT SIGN;Po;0;ET;<wide> 0025;;;;N;;;;;
3039 u"\xFF06" // FULLWIDTH AMPERSAND;Po;0;ON;<wide> 0026;;;;N;;;;;
3040 u"\xFF20"_ns; // FULLWIDTH COMMERCIAL AT;Po;0;ON;<wide> 0040;;;;N;;;;;
3041
3042 switch (unicode::GetGenCategory(aCh)) {
3043 case nsUGenCategory::kSeparator: // whitespace, line- & para-separators
3044 return false;
3045 case nsUGenCategory::kOther: // control categories
3046 return false;
3047 case nsUGenCategory::kPunctuation:
3048 return aCh <= 0xffff &&
3049 kPunctuationAcceptsEmphasis.Contains(char16_t(aCh));
3050 default:
3051 return true;
3052 }
3053}
3054
3055void BuildTextRunsScanner::SetupTextEmphasisForTextRun(gfxTextRun* aTextRun,
3056 const void* aTextPtr) {
3057 if (!mDoubleByteText) {
3058 auto text = reinterpret_cast<const uint8_t*>(aTextPtr);
3059 for (auto i : IntegerRange(aTextRun->GetLength())) {
3060 if (!MayCharacterHaveEmphasisMark(text[i])) {
3061 aTextRun->SetNoEmphasisMark(i);
3062 }
3063 }
3064 } else {
3065 auto text = reinterpret_cast<const char16_t*>(aTextPtr);
3066 auto length = aTextRun->GetLength();
3067 for (size_t i = 0; i < length; ++i) {
3068 if (i + 1 < length && IsSurrogatePair(text[i], text[i + 1])) {
3069 uint32_t ch = SurrogateToUCS4(text[i], text[i + 1]);
3070 if (!MayCharacterHaveEmphasisMark(ch)) {
3071 aTextRun->SetNoEmphasisMark(i);
3072 aTextRun->SetNoEmphasisMark(i + 1);
3073 }
3074 ++i;
3075 } else {
3076 if (!MayCharacterHaveEmphasisMark(uint32_t(text[i]))) {
3077 aTextRun->SetNoEmphasisMark(i);
3078 }
3079 }
3080 }
3081 }
3082}
3083
3084// Find the flow corresponding to aContent in aUserData
3085static inline TextRunMappedFlow* FindFlowForContent(
3086 TextRunUserData* aUserData, nsIContent* aContent,
3087 TextRunMappedFlow* userMappedFlows) {
3088 // Find the flow that contains us
3089 int32_t i = aUserData->mLastFlowIndex;
3090 int32_t delta = 1;
3091 int32_t sign = 1;
3092 // Search starting at the current position and examine close-by
3093 // positions first, moving further and further away as we go.
3094 while (i >= 0 && uint32_t(i) < aUserData->mMappedFlowCount) {
3095 TextRunMappedFlow* flow = &userMappedFlows[i];
3096 if (flow->mStartFrame->GetContent() == aContent) {
3097 return flow;
3098 }
3099
3100 i += delta;
3101 sign = -sign;
3102 delta = -delta + sign;
3103 }
3104
3105 // We ran into an array edge. Add |delta| to |i| once more to get
3106 // back to the side where we still need to search, then step in
3107 // the |sign| direction.
3108 i += delta;
3109 if (sign > 0) {
3110 for (; i < int32_t(aUserData->mMappedFlowCount); ++i) {
3111 TextRunMappedFlow* flow = &userMappedFlows[i];
3112 if (flow->mStartFrame->GetContent() == aContent) {
3113 return flow;
3114 }
3115 }
3116 } else {
3117 for (; i >= 0; --i) {
3118 TextRunMappedFlow* flow = &userMappedFlows[i];
3119 if (flow->mStartFrame->GetContent() == aContent) {
3120 return flow;
3121 }
3122 }
3123 }
3124
3125 return nullptr;
3126}
3127
3128void BuildTextRunsScanner::AssignTextRun(gfxTextRun* aTextRun,
3129 float aInflation) {
3130 for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) {
3131 MappedFlow* mappedFlow = &mMappedFlows[i];
3132 nsTextFrame* startFrame = mappedFlow->mStartFrame;
3133 nsTextFrame* endFrame = mappedFlow->mEndFrame;
3134 nsTextFrame* f;
3135 for (f = startFrame; f != endFrame; f = f->GetNextContinuation()) {
3136#ifdef DEBUG_roc
3137 if (f->GetTextRun(mWhichTextRun)) {
3138 gfxTextRun* textRun = f->GetTextRun(mWhichTextRun);
3139 if (textRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow) {
3140 if (mMappedFlows[0].mStartFrame != GetFrameForSimpleFlow(textRun)) {
3141 NS_WARNING("REASSIGNING SIMPLE FLOW TEXT RUN!")NS_DebugBreak(NS_DEBUG_WARNING, "REASSIGNING SIMPLE FLOW TEXT RUN!"
, nullptr, "./../../../layout/generic/nsTextFrame.cpp", 3141)
;
3142 }
3143 } else {
3144 auto userData =
3145 static_cast<TextRunUserData*>(aTextRun->GetUserData());
3146 TextRunMappedFlow* userMappedFlows = GetMappedFlows(aTextRun);
3147 if (userData->mMappedFlowCount >= mMappedFlows.Length() ||
3148 userMappedFlows[userData->mMappedFlowCount - 1].mStartFrame !=
3149 mMappedFlows[userdata->mMappedFlowCount - 1].mStartFrame) {
3150 NS_WARNING("REASSIGNING MULTIFLOW TEXT RUN (not append)!")NS_DebugBreak(NS_DEBUG_WARNING, "REASSIGNING MULTIFLOW TEXT RUN (not append)!"
, nullptr, "./../../../layout/generic/nsTextFrame.cpp", 3150)
;
3151 }
3152 }
3153 }
3154#endif
3155
3156 gfxTextRun* oldTextRun = f->GetTextRun(mWhichTextRun);
3157 if (oldTextRun) {
3158 nsTextFrame* firstFrame = nullptr;
3159 uint32_t startOffset = 0;
3160 if (oldTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow) {
3161 firstFrame = GetFrameForSimpleFlow(oldTextRun);
3162 } else {
3163 auto userData =
3164 static_cast<TextRunUserData*>(oldTextRun->GetUserData());
3165 TextRunMappedFlow* userMappedFlows = GetMappedFlows(oldTextRun);
3166 firstFrame = userMappedFlows[0].mStartFrame;
3167 if (MOZ_UNLIKELY(f != firstFrame)(__builtin_expect(!!(f != firstFrame), 0))) {
3168 TextRunMappedFlow* flow =
3169 FindFlowForContent(userData, f->GetContent(), userMappedFlows);
3170 if (flow) {
3171 startOffset = flow->mDOMOffsetToBeforeTransformOffset;
3172 } else {
3173 NS_ERROR("Can't find flow containing frame 'f'")do { NS_DebugBreak(NS_DEBUG_ASSERTION, "Can't find flow containing frame 'f'"
, "Error", "./../../../layout/generic/nsTextFrame.cpp", 3173)
; MOZ_PretendNoReturn(); } while (0)
;
3174 }
3175 }
3176 }
3177
3178 // Optimization: if |f| is the first frame in the flow then there are no
3179 // prev-continuations that use |oldTextRun|.
3180 nsTextFrame* clearFrom = nullptr;
3181 if (MOZ_UNLIKELY(f != firstFrame)(__builtin_expect(!!(f != firstFrame), 0))) {
3182 // If all the frames in the mapped flow starting at |f| (inclusive)
3183 // are empty then we let the prev-continuations keep the old text run.
3184 gfxSkipCharsIterator iter(oldTextRun->GetSkipChars(), startOffset,
3185 f->GetContentOffset());
3186 uint32_t textRunOffset =
3187 iter.ConvertOriginalToSkipped(f->GetContentOffset());
3188 clearFrom = textRunOffset == oldTextRun->GetLength() ? f : nullptr;
3189 }
3190 f->ClearTextRun(clearFrom, mWhichTextRun);
3191
3192#ifdef DEBUG1
3193 if (firstFrame && !firstFrame->GetTextRun(mWhichTextRun)) {
3194 // oldTextRun was destroyed - assert that we don't reference it.
3195 for (uint32_t j = 0; j < mBreakSinks.Length(); ++j) {
3196 NS_ASSERTION(oldTextRun != mBreakSinks[j]->mTextRun,do { if (!(oldTextRun != mBreakSinks[j]->mTextRun)) { NS_DebugBreak
(NS_DEBUG_ASSERTION, "destroyed text run is still in use", "oldTextRun != mBreakSinks[j]->mTextRun"
, "./../../../layout/generic/nsTextFrame.cpp", 3197); MOZ_PretendNoReturn
(); } } while (0)
3197 "destroyed text run is still in use")do { if (!(oldTextRun != mBreakSinks[j]->mTextRun)) { NS_DebugBreak
(NS_DEBUG_ASSERTION, "destroyed text run is still in use", "oldTextRun != mBreakSinks[j]->mTextRun"
, "./../../../layout/generic/nsTextFrame.cpp", 3197); MOZ_PretendNoReturn
(); } } while (0)
;
3198 }
3199 }
3200#endif
3201 }
3202 f->SetTextRun(aTextRun, mWhichTextRun, aInflation);
3203 }
3204 // Set this bit now; we can't set it any earlier because
3205 // f->ClearTextRun() might clear it out.
3206 nsFrameState whichTextRunState =
3207 startFrame->GetTextRun(nsTextFrame::eInflated) == aTextRun
3208 ? TEXT_IN_TEXTRUN_USER_DATA
3209 : TEXT_IN_UNINFLATED_TEXTRUN_USER_DATA;
3210 startFrame->AddStateBits(whichTextRunState);
3211 }
3212}
3213
3214NS_QUERYFRAME_HEAD(nsTextFrame)void* nsTextFrame ::QueryFrame(FrameIID id) const { switch (id
) {
3215 NS_QUERYFRAME_ENTRY(nsTextFrame)case nsTextFrame ::kFrameIID: { static_assert( std::is_same_v
<nsTextFrame, nsTextFrame ::Has_NS_DECL_QUERYFRAME_TARGET>
, "nsTextFrame" " must declare itself as a queryframe target"
); return const_cast<nsTextFrame*>(static_cast<const
nsTextFrame*>(this)); }
3216NS_QUERYFRAME_TAIL_INHERITING(nsIFrame)default: break; } return nsIFrame ::QueryFrame(id); }
3217
3218gfxSkipCharsIterator nsTextFrame::EnsureTextRun(
3219 TextRunType aWhichTextRun, DrawTarget* aRefDrawTarget,
3220 nsIFrame* aLineContainer, const nsLineList::iterator* aLine,
3221 uint32_t* aFlowEndInTextRun) {
3222 gfxTextRun* textRun = GetTextRun(aWhichTextRun);
3223 if (!textRun || (aLine && (*aLine)->GetInvalidateTextRuns())) {
3224 RefPtr<DrawTarget> refDT = aRefDrawTarget;
3225 if (!refDT) {
3226 refDT = CreateReferenceDrawTarget(this);
3227 }
3228 if (refDT) {
3229 BuildTextRuns(refDT, this, aLineContainer, aLine, aWhichTextRun);
3230 }
3231 textRun = GetTextRun(aWhichTextRun);
3232 if (!textRun) {
3233 // A text run was not constructed for this frame. This is bad. The caller
3234 // will check mTextRun.
3235 return gfxSkipCharsIterator(gfxPlatform::GetPlatform()->EmptySkipChars(),
3236 0);
3237 }
3238 TabWidthStore* tabWidths = GetProperty(TabWidthProperty());
3239 if (tabWidths && tabWidths->mValidForContentOffset != GetContentOffset()) {
3240 RemoveProperty(TabWidthProperty());
3241 }
3242 }
3243
3244 if (textRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow) {
3245 if (aFlowEndInTextRun) {
3246 *aFlowEndInTextRun = textRun->GetLength();
3247 }
3248 return gfxSkipCharsIterator(textRun->GetSkipChars(), 0, mContentOffset);
3249 }
3250
3251 auto userData = static_cast<TextRunUserData*>(textRun->GetUserData());
3252 TextRunMappedFlow* userMappedFlows = GetMappedFlows(textRun);
3253 TextRunMappedFlow* flow =
3254 FindFlowForContent(userData, mContent, userMappedFlows);
3255 if (flow) {
3256 // Since textruns can only contain one flow for a given content element,
3257 // this must be our flow.
3258 uint32_t flowIndex = flow - userMappedFlows;
3259 userData->mLastFlowIndex = flowIndex;
3260 gfxSkipCharsIterator iter(textRun->GetSkipChars(),
3261 flow->mDOMOffsetToBeforeTransformOffset,
3262 mContentOffset);
3263 if (aFlowEndInTextRun) {
3264 if (flowIndex + 1 < userData->mMappedFlowCount) {
3265 gfxSkipCharsIterator end(textRun->GetSkipChars());
3266 *aFlowEndInTextRun = end.ConvertOriginalToSkipped(
3267 flow[1].mStartFrame->GetContentOffset() +
3268 flow[1].mDOMOffsetToBeforeTransformOffset);
3269 } else {
3270 *aFlowEndInTextRun = textRun->GetLength();
3271 }
3272 }
3273 return iter;
3274 }
3275
3276 NS_ERROR("Can't find flow containing this frame???")do { NS_DebugBreak(NS_DEBUG_ASSERTION, "Can't find flow containing this frame???"
, "Error", "./../../../layout/generic/nsTextFrame.cpp", 3276)
; MOZ_PretendNoReturn(); } while (0)
;
3277 return gfxSkipCharsIterator(gfxPlatform::GetPlatform()->EmptySkipChars(), 0);
3278}
3279
3280static uint32_t GetEndOfTrimmedText(const CharacterDataBuffer& aBuffer,
3281 const nsStyleText* aStyleText,
3282 uint32_t aStart, uint32_t aEnd,
3283 gfxSkipCharsIterator* aIterator,
3284 bool aAllowHangingWS = false) {
3285 aIterator->SetSkippedOffset(aEnd);
3286 while (aIterator->GetSkippedOffset() > aStart) {
3287 aIterator->AdvanceSkipped(-1);
3288 if (!IsTrimmableSpace(aBuffer, aIterator->GetOriginalOffset(), aStyleText,
3289 aAllowHangingWS)) {
3290 return aIterator->GetSkippedOffset() + 1;
3291 }
3292 }
3293 return aStart;
3294}
3295
3296nsTextFrame::TrimmedOffsets nsTextFrame::GetTrimmedOffsets(
3297 const class CharacterDataBuffer& aBuffer, TrimmedOffsetFlags aFlags) const {
3298 NS_ASSERTION(mTextRun, "Need textrun here")do { if (!(mTextRun)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "Need textrun here"
, "mTextRun", "./../../../layout/generic/nsTextFrame.cpp", 3298
); MOZ_PretendNoReturn(); } } while (0)
;
3299 if (!(aFlags & TrimmedOffsetFlags::NotPostReflow)) {
3300 // This should not be used during reflow. We need our TEXT_REFLOW_FLAGS
3301 // to be set correctly. If our parent wasn't reflowed due to the frame
3302 // tree being too deep then the return value doesn't matter.
3303 NS_ASSERTION(do { if (!(!HasAnyStateBits(NS_FRAME_FIRST_REFLOW) || GetParent
()->HasAnyStateBits(NS_FRAME_TOO_DEEP_IN_FRAME_TREE))) { NS_DebugBreak
(NS_DEBUG_ASSERTION, "Can only call this on frames that have been reflowed"
, "!HasAnyStateBits(NS_FRAME_FIRST_REFLOW) || GetParent()->HasAnyStateBits(NS_FRAME_TOO_DEEP_IN_FRAME_TREE)"
, "./../../../layout/generic/nsTextFrame.cpp", 3306); MOZ_PretendNoReturn
(); } } while (0)
3304 !HasAnyStateBits(NS_FRAME_FIRST_REFLOW) ||do { if (!(!HasAnyStateBits(NS_FRAME_FIRST_REFLOW) || GetParent
()->HasAnyStateBits(NS_FRAME_TOO_DEEP_IN_FRAME_TREE))) { NS_DebugBreak
(NS_DEBUG_ASSERTION, "Can only call this on frames that have been reflowed"
, "!HasAnyStateBits(NS_FRAME_FIRST_REFLOW) || GetParent()->HasAnyStateBits(NS_FRAME_TOO_DEEP_IN_FRAME_TREE)"
, "./../../../layout/generic/nsTextFrame.cpp", 3306); MOZ_PretendNoReturn
(); } } while (0)
3305 GetParent()->HasAnyStateBits(NS_FRAME_TOO_DEEP_IN_FRAME_TREE),do { if (!(!HasAnyStateBits(NS_FRAME_FIRST_REFLOW) || GetParent
()->HasAnyStateBits(NS_FRAME_TOO_DEEP_IN_FRAME_TREE))) { NS_DebugBreak
(NS_DEBUG_ASSERTION, "Can only call this on frames that have been reflowed"
, "!HasAnyStateBits(NS_FRAME_FIRST_REFLOW) || GetParent()->HasAnyStateBits(NS_FRAME_TOO_DEEP_IN_FRAME_TREE)"
, "./../../../layout/generic/nsTextFrame.cpp", 3306); MOZ_PretendNoReturn
(); } } while (0)
3306 "Can only call this on frames that have been reflowed")do { if (!(!HasAnyStateBits(NS_FRAME_FIRST_REFLOW) || GetParent
()->HasAnyStateBits(NS_FRAME_TOO_DEEP_IN_FRAME_TREE))) { NS_DebugBreak
(NS_DEBUG_ASSERTION, "Can only call this on frames that have been reflowed"
, "!HasAnyStateBits(NS_FRAME_FIRST_REFLOW) || GetParent()->HasAnyStateBits(NS_FRAME_TOO_DEEP_IN_FRAME_TREE)"
, "./../../../layout/generic/nsTextFrame.cpp", 3306); MOZ_PretendNoReturn
(); } } while (0)
;
3307 NS_ASSERTION(!HasAnyStateBits(NS_FRAME_IN_REFLOW),do { if (!(!HasAnyStateBits(NS_FRAME_IN_REFLOW))) { NS_DebugBreak
(NS_DEBUG_ASSERTION, "Can only call this on frames that are not being reflowed"
, "!HasAnyStateBits(NS_FRAME_IN_REFLOW)", "./../../../layout/generic/nsTextFrame.cpp"
, 3308); MOZ_PretendNoReturn(); } } while (0)
3308 "Can only call this on frames that are not being reflowed")do { if (!(!HasAnyStateBits(NS_FRAME_IN_REFLOW))) { NS_DebugBreak
(NS_DEBUG_ASSERTION, "Can only call this on frames that are not being reflowed"
, "!HasAnyStateBits(NS_FRAME_IN_REFLOW)", "./../../../layout/generic/nsTextFrame.cpp"
, 3308); MOZ_PretendNoReturn(); } } while (0)
;
3309 }
3310
3311 TrimmedOffsets offsets = {GetContentOffset(), GetContentLength()};
3312 const nsStyleText* textStyle = StyleText();
3313 // Note that pre-line newlines should still allow us to trim spaces
3314 // for display
3315 if (textStyle->WhiteSpaceIsSignificant()) {
3316 return offsets;
3317 }
3318
3319 if (!(aFlags & TrimmedOffsetFlags::NoTrimBefore) &&
3320 ((aFlags & TrimmedOffsetFlags::NotPostReflow) ||
3321 HasAnyStateBits(TEXT_START_OF_LINE))) {
3322 int32_t whitespaceCount = GetTrimmableWhitespaceCount(
3323 aBuffer, offsets.mStart, offsets.mLength, 1);
3324 offsets.mStart += whitespaceCount;
3325 offsets.mLength -= whitespaceCount;
3326 }
3327
3328 if (!(aFlags & TrimmedOffsetFlags::NoTrimAfter) &&
3329 ((aFlags & TrimmedOffsetFlags::NotPostReflow) ||
3330 HasAnyStateBits(TEXT_END_OF_LINE))) {
3331 // This treats a trailing 'pre-line' newline as trimmable. That's fine,
3332 // it's actually what we want since we want whitespace before it to
3333 // be trimmed.
3334 int32_t whitespaceCount = GetTrimmableWhitespaceCount(
3335 aBuffer, offsets.GetEnd() - 1, offsets.mLength, -1);
3336 offsets.mLength -= whitespaceCount;
3337 }
3338 return offsets;
3339}
3340
3341static bool IsJustifiableCharacter(const nsStyleText* aTextStyle,
3342 const CharacterDataBuffer& aBuffer,
3343 int32_t aPos, bool aLangIsCJ) {
3344 NS_ASSERTION(aPos >= 0, "negative position?!")do { if (!(aPos >= 0)) { NS_DebugBreak(NS_DEBUG_ASSERTION,
"negative position?!", "aPos >= 0", "./../../../layout/generic/nsTextFrame.cpp"
, 3344); MOZ_PretendNoReturn(); } } while (0)
;
3345
3346 StyleTextJustify justifyStyle = aTextStyle->mTextJustify;
3347 if (justifyStyle == StyleTextJustify::None) {
3348 return false;
3349 }
3350
3351 const char16_t ch = aBuffer.CharAt(AssertedCast<uint32_t>(aPos));
3352 if (ch == '\n' || ch == '\t' || ch == '\r') {
3353 return !aTextStyle->WhiteSpaceIsSignificant();
3354 }
3355 if (ch == ' ' || ch == CH_NBSP160) {
3356 // Don't justify spaces that are combined with diacriticals
3357 if (!aBuffer.Is2b()) {
3358 return true;
3359 }
3360 return !nsTextFrameUtils::IsSpaceCombiningSequenceTail(
3361 aBuffer.Get2b() + aPos + 1, aBuffer.GetLength() - (aPos + 1));
3362 }
3363
3364 if (justifyStyle == StyleTextJustify::InterCharacter) {
3365 char32_t u = aBuffer.ScalarValueAt(AssertedCast<uint32_t>(aPos));
3366 if (intl::UnicodeProperties::IsCursiveScript(u)) {
3367 return false;
3368 }
3369 return true;
3370 } else if (justifyStyle == StyleTextJustify::InterWord) {
3371 return false;
3372 }
3373
3374 // text-justify: auto
3375 if (ch < 0x2150u) {
3376 return false;
3377 }
3378 if (aLangIsCJ) {
3379 if ( // Number Forms, Arrows, Mathematical Operators
3380 (0x2150u <= ch && ch <= 0x22ffu) ||
3381 // Enclosed Alphanumerics
3382 (0x2460u <= ch && ch <= 0x24ffu) ||
3383 // Block Elements, Geometric Shapes, Miscellaneous Symbols, Dingbats
3384 (0x2580u <= ch && ch <= 0x27bfu) ||
3385 // Supplemental Arrows-A, Braille Patterns, Supplemental Arrows-B,
3386 // Miscellaneous Mathematical Symbols-B,
3387 // Supplemental Mathematical Operators, Miscellaneous Symbols and Arrows
3388 (0x27f0u <= ch && ch <= 0x2bffu) ||
3389 // CJK Radicals Supplement, CJK Radicals Supplement, Ideographic
3390 // Description Characters, CJK Symbols and Punctuation, Hiragana,
3391 // Katakana, Bopomofo
3392 (0x2e80u <= ch && ch <= 0x312fu) ||
3393 // Kanbun, Bopomofo Extended, Katakana Phonetic Extensions,
3394 // Enclosed CJK Letters and Months, CJK Compatibility,
3395 // CJK Unified Ideographs Extension A, Yijing Hexagram Symbols,
3396 // CJK Unified Ideographs, Yi Syllables, Yi Radicals
3397 (0x3190u <= ch && ch <= 0xabffu) ||
3398 // CJK Compatibility Ideographs
3399 (0xf900u <= ch && ch <= 0xfaffu) ||
3400 // Halfwidth and Fullwidth Forms (a part)
3401 (0xff5eu <= ch && ch <= 0xff9fu)) {
3402 return true;
3403 }
3404 if (IsHighSurrogate(ch)) {
3405 if (char32_t u = aBuffer.ScalarValueAt(AssertedCast<uint32_t>(aPos))) {
3406 // CJK Unified Ideographs Extension B,
3407 // CJK Unified Ideographs Extension C,
3408 // CJK Unified Ideographs Extension D,
3409 // CJK Compatibility Ideographs Supplement
3410 if (0x20000u <= u && u <= 0x2ffffu) {
3411 return true;
3412 }
3413 }
3414 }
3415 }
3416 return false;
3417}
3418
3419void nsTextFrame::ClearMetrics(ReflowOutput& aMetrics) {
3420 aMetrics.ClearSize();
3421 aMetrics.SetBlockStartAscent(0);
3422 mAscent = 0;
3423
3424 AddStateBits(TEXT_NO_RENDERED_GLYPHS);
3425}
3426
3427static int32_t FindChar(const CharacterDataBuffer& characterDataBuffer,
3428 int32_t aOffset, int32_t aLength, char16_t ch) {
3429 int32_t i = 0;
3430 if (characterDataBuffer.Is2b()) {
3431 const char16_t* str = characterDataBuffer.Get2b() + aOffset;
3432 for (; i < aLength; ++i) {
3433 if (*str == ch) {
3434 return i + aOffset;
3435 }
3436 ++str;
3437 }
3438 } else {
3439 if (uint16_t(ch) <= 0xFF) {
3440 const char* str = characterDataBuffer.Get1b() + aOffset;
3441 const void* p = memchr(str, ch, aLength);
3442 if (p) {
3443 return (static_cast<const char*>(p) - str) + aOffset;
3444 }
3445 }
3446 }
3447 return -1;
3448}
3449
3450static bool IsChineseOrJapanese(const nsTextFrame* aFrame) {
3451 if (aFrame->ShouldSuppressLineBreak()) {
3452 // Always treat ruby as CJ language so that those characters can
3453 // be expanded properly even when surrounded by other language.
3454 return true;
3455 }
3456
3457 nsAtom* language = aFrame->StyleFont()->GetLangAtom();
3458 if (!language) {
3459 return false;
3460 }
3461 return nsStyleUtil::MatchesLanguagePrefix(language, u"ja") ||
3462 nsStyleUtil::MatchesLanguagePrefix(language, u"zh");
3463}
3464
3465#ifdef DEBUG1
3466static bool IsInBounds(const gfxSkipCharsIterator& aStart,
3467 int32_t aContentLength, gfxTextRun::Range aRange) {
3468 if (aStart.GetSkippedOffset() > aRange.start) {
3469 return false;
3470 }
3471 if (aContentLength == INT32_MAX(2147483647)) {
3472 return true;
3473 }
3474 gfxSkipCharsIterator iter(aStart);
3475 iter.AdvanceOriginal(aContentLength);
3476 return iter.GetSkippedOffset() >= aRange.end;
3477}
3478#endif
3479
3480nsTextFrame::PropertyProvider::PropertyProvider(
3481 gfxTextRun* aTextRun, const nsStyleText* aTextStyle,
3482 const class CharacterDataBuffer& aBuffer, nsTextFrame* aFrame,
3483 const gfxSkipCharsIterator& aStart, int32_t aLength,
3484 nsIFrame* aLineContainer, nscoord aOffsetFromBlockOriginForTabs,
3485 nsTextFrame::TextRunType aWhichTextRun, bool aAtStartOfLine)
3486 : mTextRun(aTextRun),
3487 mFontGroup(nullptr),
3488 mTextStyle(aTextStyle),
3489 mCharacterDataBuffer(aBuffer),
3490 mLineContainer(aLineContainer),
3491 mFrame(aFrame),
3492 mStart(aStart),
3493 mTempIterator(aStart),
3494 mTabWidths(nullptr),
3495 mTabWidthsAnalyzedLimit(0),
3496 mLength(aLength),
3497 mWordSpacing(ResolveWordSpacing(aFrame, *aTextStyle)),
3498 mLetterSpacing(ResolveLetterSpacing(aFrame, *aTextStyle)),
3499 mMinTabAdvance(-1.0),
3500 mHyphenWidth(-1),
3501 mOffsetFromBlockOriginForTabs(aOffsetFromBlockOriginForTabs),
3502 mJustificationArrayStart(0),
3503 mReflowing(true),
3504 mWhichTextRun(aWhichTextRun) {
3505 NS_ASSERTION(mStart.IsInitialized(), "Start not initialized?")do { if (!(mStart.IsInitialized())) { NS_DebugBreak(NS_DEBUG_ASSERTION
, "Start not initialized?", "mStart.IsInitialized()", "./../../../layout/generic/nsTextFrame.cpp"
, 3505); MOZ_PretendNoReturn(); } } while (0)
;
3506 if (aAtStartOfLine) {
3507 mStartOfLineOffset = mStart.GetSkippedOffset();
3508 }
3509 InitTextAutospace();
3510}
3511
3512nsTextFrame::PropertyProvider::PropertyProvider(
3513 nsTextFrame* aFrame, const gfxSkipCharsIterator& aStart,
3514 nsTextFrame::TextRunType aWhichTextRun, nsFontMetrics* aFontMetrics)
3515 : mTextRun(aFrame->GetTextRun(aWhichTextRun)),
3516 mFontGroup(nullptr),
3517 mFontMetrics(aFontMetrics),
3518 mTextStyle(aFrame->StyleText()),
3519 mCharacterDataBuffer(aFrame->CharacterDataBuffer()),
3520 mLineContainer(nullptr),
3521 mFrame(aFrame),
3522 mStart(aStart),
3523 mTempIterator(aStart),
3524 mTabWidths(nullptr),
3525 mTabWidthsAnalyzedLimit(0),
3526 mLength(aFrame->GetContentLength()),
3527 mWordSpacing(ResolveWordSpacing(aFrame, *mTextStyle)),
3528 mLetterSpacing(ResolveLetterSpacing(aFrame, *mTextStyle)),
3529 mMinTabAdvance(-1.0),
3530 mHyphenWidth(-1),
3531 mOffsetFromBlockOriginForTabs(0),
3532 mJustificationArrayStart(0),
3533 mReflowing(false),
3534 mWhichTextRun(aWhichTextRun) {
3535 NS_ASSERTION(mTextRun, "Textrun not initialized!")do { if (!(mTextRun)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "Textrun not initialized!"
, "mTextRun", "./../../../layout/generic/nsTextFrame.cpp", 3535
); MOZ_PretendNoReturn(); } } while (0)
;
3536 InitTextAutospace();
3537}
3538
3539gfx::ShapedTextFlags nsTextFrame::PropertyProvider::GetShapedTextFlags() const {
3540 return nsLayoutUtils::GetTextRunOrientFlagsForStyle(mFrame->Style());
3541}
3542
3543already_AddRefed<DrawTarget> nsTextFrame::PropertyProvider::GetDrawTarget()
3544 const {
3545 return CreateReferenceDrawTarget(GetFrame());
3546}
3547
3548gfxFloat nsTextFrame::PropertyProvider::MinTabAdvance() const {
3549 if (mMinTabAdvance < 0.0) {
3550 mMinTabAdvance = GetMinTabAdvanceAppUnits(mTextRun);
3551 }
3552 return mMinTabAdvance;
3553}
3554
3555/**
3556 * Finds the offset of the first character of the cluster containing aPos
3557 */
3558static void FindClusterStart(const gfxTextRun* aTextRun, int32_t aOriginalStart,
3559 gfxSkipCharsIterator* aPos) {
3560 while (aPos->GetOriginalOffset() > aOriginalStart) {
3561 if (aPos->IsOriginalCharSkipped() ||
3562 aTextRun->IsClusterStart(aPos->GetSkippedOffset())) {
3563 break;
3564 }
3565 aPos->AdvanceOriginal(-1);
3566 }
3567}
3568
3569/**
3570 * Finds the offset of the last character of the cluster containing aPos.
3571 * If aAllowSplitLigature is false, we also check for a ligature-group
3572 * start.
3573 */
3574static void FindClusterEnd(const gfxTextRun* aTextRun, int32_t aOriginalEnd,
3575 gfxSkipCharsIterator* aPos,
3576 bool aAllowSplitLigature = true) {
3577 MOZ_ASSERT(aPos->GetOriginalOffset() < aOriginalEnd,do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aPos->GetOriginalOffset() < aOriginalEnd)>::
isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(aPos->GetOriginalOffset() < aOriginalEnd))), 0
))) { do { } while (false); MOZ_ReportAssertionFailure("aPos->GetOriginalOffset() < aOriginalEnd"
" (" "character outside string" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 3578); AnnotateMozCrashReason("MOZ_ASSERT" "(" "aPos->GetOriginalOffset() < aOriginalEnd"
") (" "character outside string" ")"); do { MOZ_CrashSequence
(__null, 3578); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
3578 "character outside string")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aPos->GetOriginalOffset() < aOriginalEnd)>::
isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(aPos->GetOriginalOffset() < aOriginalEnd))), 0
))) { do { } while (false); MOZ_ReportAssertionFailure("aPos->GetOriginalOffset() < aOriginalEnd"
" (" "character outside string" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 3578); AnnotateMozCrashReason("MOZ_ASSERT" "(" "aPos->GetOriginalOffset() < aOriginalEnd"
") (" "character outside string" ")"); do { MOZ_CrashSequence
(__null, 3578); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
3579
3580 aPos->AdvanceOriginal(1);
3581 while (aPos->GetOriginalOffset() < aOriginalEnd) {
3582 if (aPos->IsOriginalCharSkipped() ||
3583 (aTextRun->IsClusterStart(aPos->GetSkippedOffset()) &&
3584 (aAllowSplitLigature ||
3585 aTextRun->IsLigatureGroupStart(aPos->GetSkippedOffset())))) {
3586 break;
3587 }
3588 aPos->AdvanceOriginal(1);
3589 }
3590 aPos->AdvanceOriginal(-1);
3591}
3592
3593// Get the line number of aFrame in the lines referenced by aLineIter, if
3594// known (returning -1 if we don't find it).
3595static int32_t GetFrameLineNum(nsIFrame* aFrame, nsILineIterator* aLineIter) {
3596 if (!aLineIter) {
3597 return -1;
3598 }
3599 // If we don't find the frame directly, but its parent is an inline or other
3600 // "line participant" (e.g. nsFirstLineFrame), we want the line that the
3601 // inline ancestor is on.
3602 do {
3603 int32_t n = aLineIter->FindLineContaining(aFrame);
3604 if (n >= 0) {
3605 return n;
3606 }
3607 aFrame = aFrame->GetParent();
3608 } while (aFrame && aFrame->IsLineParticipant());
3609 return -1;
3610}
3611
3612// Get the position of the first preserved newline in aFrame, if any,
3613// returning -1 if none.
3614static int32_t FindFirstNewlinePosition(const nsTextFrame* aFrame) {
3615 MOZ_ASSERT(aFrame->StyleText()->NewlineIsSignificantStyle(),do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aFrame->StyleText()->NewlineIsSignificantStyle
())>::isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(aFrame->StyleText()->NewlineIsSignificantStyle
()))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("aFrame->StyleText()->NewlineIsSignificantStyle()" " ("
"how did the HasNewline flag get set?" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 3616); AnnotateMozCrashReason("MOZ_ASSERT" "(" "aFrame->StyleText()->NewlineIsSignificantStyle()"
") (" "how did the HasNewline flag get set?" ")"); do { MOZ_CrashSequence
(__null, 3616); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
3616 "how did the HasNewline flag get set?")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aFrame->StyleText()->NewlineIsSignificantStyle
())>::isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(aFrame->StyleText()->NewlineIsSignificantStyle
()))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("aFrame->StyleText()->NewlineIsSignificantStyle()" " ("
"how did the HasNewline flag get set?" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 3616); AnnotateMozCrashReason("MOZ_ASSERT" "(" "aFrame->StyleText()->NewlineIsSignificantStyle()"
") (" "how did the HasNewline flag get set?" ")"); do { MOZ_CrashSequence
(__null, 3616); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
3617 const auto& characterDataBuffer = aFrame->CharacterDataBuffer();
3618 for (auto i = aFrame->GetContentOffset(); i < aFrame->GetContentEnd(); ++i) {
3619 if (characterDataBuffer.CharAt(i) == '\n') {
3620 return i;
3621 }
3622 }
3623 return -1;
3624}
3625
3626// Get the position of the last preserved tab in aFrame that is before the
3627// preserved newline at aNewlinePos.
3628// Passing -1 for aNewlinePos means there is no preserved newline, so we look
3629// for the last preserved tab in the whole content.
3630// Returns -1 if no such preserved tab is present.
3631static int32_t FindLastTabPositionBeforeNewline(const nsTextFrame* aFrame,
3632 int32_t aNewlinePos) {
3633 // We only call this if white-space is not being collapsed.
3634 MOZ_ASSERT(aFrame->StyleText()->WhiteSpaceIsSignificant(),do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aFrame->StyleText()->WhiteSpaceIsSignificant()
)>::isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(aFrame->StyleText()->WhiteSpaceIsSignificant()
))), 0))) { do { } while (false); MOZ_ReportAssertionFailure(
"aFrame->StyleText()->WhiteSpaceIsSignificant()" " (" "how did the HasTab flag get set?"
")", "./../../../layout/generic/nsTextFrame.cpp", 3635); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "aFrame->StyleText()->WhiteSpaceIsSignificant()"
") (" "how did the HasTab flag get set?" ")"); do { MOZ_CrashSequence
(__null, 3635); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
3635 "how did the HasTab flag get set?")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aFrame->StyleText()->WhiteSpaceIsSignificant()
)>::isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(aFrame->StyleText()->WhiteSpaceIsSignificant()
))), 0))) { do { } while (false); MOZ_ReportAssertionFailure(
"aFrame->StyleText()->WhiteSpaceIsSignificant()" " (" "how did the HasTab flag get set?"
")", "./../../../layout/generic/nsTextFrame.cpp", 3635); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "aFrame->StyleText()->WhiteSpaceIsSignificant()"
") (" "how did the HasTab flag get set?" ")"); do { MOZ_CrashSequence
(__null, 3635); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
3636 const auto& characterDataBuffer = aFrame->CharacterDataBuffer();
3637 // If a non-negative newline position was given, we only need to search the
3638 // text before that offset.
3639 for (auto i = aNewlinePos < 0 ? aFrame->GetContentEnd() : aNewlinePos;
3640 i > aFrame->GetContentOffset(); --i) {
3641 if (characterDataBuffer.CharAt(i - 1) == '\t') {
3642 return i;
3643 }
3644 }
3645 return -1;
3646}
3647
3648// Look for preserved tab or newline in the given frame or its following
3649// siblings on the same line, to determine whether justification should be
3650// suppressed in order to avoid disrupting tab-stop positions.
3651// Returns the first such preserved whitespace char, or 0 if none found.
3652static char NextPreservedWhiteSpaceOnLine(nsIFrame* aSibling,
3653 nsILineIterator* aLineIter,
3654 int32_t aLineNum) {
3655 while (aSibling) {
3656 // If we find a <br>, treat it like a newline.
3657 if (aSibling->IsBrFrame()) {
3658 return '\n';
3659 }
3660 // If we've moved on to a later line, stop searching.
3661 if (GetFrameLineNum(aSibling, aLineIter) > aLineNum) {
3662 return 0;
3663 }
3664 // If we encounter an inline frame, recurse into it.
3665 if (aSibling->IsInlineFrame()) {
3666 auto* child = aSibling->PrincipalChildList().FirstChild();
3667 char result = NextPreservedWhiteSpaceOnLine(child, aLineIter, aLineNum);
3668 if (result) {
3669 return result;
3670 }
3671 }
3672 // If we have a text frame, and whitespace is not collapsed, we need to
3673 // check its contents.
3674 if (aSibling->IsTextFrame()) {
3675 const auto* textStyle = aSibling->StyleText();
3676 if (textStyle->WhiteSpaceOrNewlineIsSignificant()) {
3677 const auto* textFrame = static_cast<nsTextFrame*>(aSibling);
3678 const auto& characterDataBuffer = textFrame->CharacterDataBuffer();
3679 for (auto i = textFrame->GetContentOffset();
3680 i < textFrame->GetContentEnd(); ++i) {
3681 const char16_t ch = characterDataBuffer.CharAt(i);
3682 if (ch == '\n' && textStyle->NewlineIsSignificantStyle()) {
3683 return '\n';
3684 }
3685 if (ch == '\t' && textStyle->WhiteSpaceIsSignificant()) {
3686 return '\t';
3687 }
3688 }
3689 }
3690 }
3691 aSibling = aSibling->GetNextSibling();
3692 }
3693 return 0;
3694}
3695
3696static bool HasPreservedTabInFollowingSiblingOnLine(nsTextFrame* aFrame) {
3697 bool foundTab = false;
3698
3699 nsIFrame* lineContainer = FindLineContainer(aFrame);
3700 nsILineIterator* iter = lineContainer->GetLineIterator();
3701 int32_t line = GetFrameLineNum(aFrame, iter);
3702 char ws = NextPreservedWhiteSpaceOnLine(aFrame->GetNextSibling(), iter, line);
3703 if (ws == '\t') {
3704 foundTab = true;
3705 } else if (!ws) {
3706 // Didn't find a preserved tab or newline in our siblings; if our parent
3707 // (and its parent, etc) is an inline, we need to look at their following
3708 // siblings, too, as long as they're on the same line.
3709 const nsIFrame* maybeInline = aFrame->GetParent();
3710 while (maybeInline && maybeInline->IsInlineFrame()) {
3711 ws = NextPreservedWhiteSpaceOnLine(maybeInline->GetNextSibling(), iter,
3712 line);
3713 if (ws == '\t') {
3714 foundTab = true;
3715 break;
3716 }
3717 if (ws == '\n') {
3718 break;
3719 }
3720 maybeInline = maybeInline->GetParent();
3721 }
3722 }
3723
3724 // We called lineContainer->GetLineIterator() above, but we mustn't
3725 // allow a block frame to retain this iterator if we're currently in
3726 // reflow, as it will become invalid as the line list is reflowed.
3727 if (lineContainer->HasAnyStateBits(NS_FRAME_IN_REFLOW) &&
3728 lineContainer->IsBlockFrameOrSubclass()) {
3729 static_cast<nsBlockFrame*>(lineContainer)->ClearLineIterator();
3730 }
3731
3732 return foundTab;
3733}
3734
3735JustificationInfo nsTextFrame::PropertyProvider::ComputeJustification(
3736 Range aRange, nsTArray<JustificationAssignment>* aAssignments) {
3737 JustificationInfo info;
3738
3739 // Horizontal-in-vertical frame is orthogonal to the line, so it
3740 // doesn't actually include any justification opportunity inside.
3741 // The spec says such frame should be treated as a U+FFFC. Since we
3742 // do not insert justification opportunities on the sides of that
3743 // character, the sides of this frame are not justifiable either.
3744 if (mFrame->Style()->IsTextCombined()) {
3745 return info;
3746 }
3747
3748 int32_t lastTab = -1;
3749 if (StaticPrefs::layout_css_text_align_justify_only_after_last_tab()) {
3750 // If there is a preserved tab on the line, we don't apply justification
3751 // until we're past its position.
3752 if (mTextStyle->WhiteSpaceIsSignificant()) {
3753 // If there is a preserved newline within the text, we don't need to look
3754 // beyond this frame, as following frames will not be on the same line.
3755 int32_t newlinePos =
3756 (mTextRun->GetFlags2() & nsTextFrameUtils::Flags::HasNewline)
3757 ? FindFirstNewlinePosition(mFrame)
3758 : -1;
3759 if (newlinePos < 0) {
3760 // There's no preserved newline within this frame; if there's a tab
3761 // in a later sibling frame on the same line, we won't apply any
3762 // justification to this one.
3763 if (HasPreservedTabInFollowingSiblingOnLine(mFrame)) {
3764 return info;
3765 }
3766 }
3767
3768 if (mTextRun->GetFlags2() & nsTextFrameUtils::Flags::HasTab) {
3769 // Find last tab character in the content; we won't justify anything
3770 // before that position, so that tab alignment remains correct.
3771 lastTab = FindLastTabPositionBeforeNewline(mFrame, newlinePos);
3772 }
3773 }
3774 }
3775
3776 bool isCJ = IsChineseOrJapanese(mFrame);
3777 nsSkipCharsRunIterator run(
3778 mStart, nsSkipCharsRunIterator::LENGTH_INCLUDES_SKIPPED, aRange.Length());
3779 run.SetOriginalOffset(aRange.start);
3780 mJustificationArrayStart = run.GetSkippedOffset();
3781
3782 nsTArray<JustificationAssignment> assignments;
3783 assignments.SetCapacity(aRange.Length());
3784 while (run.NextRun()) {
3785 uint32_t originalOffset = run.GetOriginalOffset();
3786 uint32_t skippedOffset = run.GetSkippedOffset();
3787 uint32_t length = run.GetRunLength();
3788 assignments.SetLength(skippedOffset + length - mJustificationArrayStart);
3789
3790 gfxSkipCharsIterator iter = run.GetPos();
3791 for (uint32_t i = 0; i < length; ++i) {
3792 uint32_t offset = originalOffset + i;
3793 if (!IsJustifiableCharacter(mTextStyle, mCharacterDataBuffer, offset,
3794 isCJ) ||
3795 (lastTab >= 0 && offset <= uint32_t(lastTab))) {
3796 continue;
3797 }
3798
3799 iter.SetOriginalOffset(offset);
3800
3801 FindClusterStart(mTextRun, originalOffset, &iter);
3802 uint32_t firstCharOffset = iter.GetSkippedOffset();
3803 uint32_t firstChar = firstCharOffset > mJustificationArrayStart
3804 ? firstCharOffset - mJustificationArrayStart
3805 : 0;
3806 if (!firstChar) {
3807 info.mIsStartJustifiable = true;
3808 } else {
3809 auto& assign = assignments[firstChar];
3810 auto& prevAssign = assignments[firstChar - 1];
3811 if (prevAssign.mGapsAtEnd) {
3812 prevAssign.mGapsAtEnd = 1;
3813 assign.mGapsAtStart = 1;
3814 } else {
3815 assign.mGapsAtStart = 2;
3816 info.mInnerOpportunities++;
3817 }
3818 }
3819
3820 FindClusterEnd(mTextRun, originalOffset + length, &iter);
3821 uint32_t lastChar = iter.GetSkippedOffset() - mJustificationArrayStart;
3822 // Assign the two gaps temporary to the last char. If the next cluster is
3823 // justifiable as well, one of the gaps will be removed by code above.
3824 assignments[lastChar].mGapsAtEnd = 2;
3825 info.mInnerOpportunities++;
3826
3827 // Skip the whole cluster
3828 i = iter.GetOriginalOffset() - originalOffset;
3829 }
3830 }
3831
3832 if (!assignments.IsEmpty() && assignments.LastElement().mGapsAtEnd) {
3833 // We counted the expansion opportunity after the last character,
3834 // but it is not an inner opportunity.
3835 MOZ_ASSERT(info.mInnerOpportunities > 0)do { static_assert( mozilla::detail::AssertionConditionType<
decltype(info.mInnerOpportunities > 0)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(info.mInnerOpportunities >
0))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("info.mInnerOpportunities > 0", "./../../../layout/generic/nsTextFrame.cpp"
, 3835); AnnotateMozCrashReason("MOZ_ASSERT" "(" "info.mInnerOpportunities > 0"
")"); do { MOZ_CrashSequence(__null, 3835); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
3836 info.mInnerOpportunities--;
3837 info.mIsEndJustifiable = true;
3838 }
3839
3840 if (aAssignments) {
3841 *aAssignments = std::move(assignments);
3842 }
3843 return info;
3844}
3845
3846// aStart, aLength in transformed string offsets.
3847// Returns false if no non-standard spacing was required.
3848bool nsTextFrame::PropertyProvider::GetSpacing(Range aRange,
3849 Spacing* aSpacing) const {
3850 return GetSpacingInternal(
3851 aRange, aSpacing,
3852 !(mTextRun->GetFlags2() & nsTextFrameUtils::Flags::HasTab));
3853}
3854
3855static bool CanAddSpacingBefore(const gfxTextRun* aTextRun, uint32_t aOffset,
3856 bool aNewlineIsSignificant) {
3857 const auto* g = aTextRun->GetCharacterGlyphs();
3858 MOZ_ASSERT(aOffset < aTextRun->GetLength())do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aOffset < aTextRun->GetLength())>::isValid,
"invalid assertion condition"); if ((__builtin_expect(!!(!(!
!(aOffset < aTextRun->GetLength()))), 0))) { do { } while
(false); MOZ_ReportAssertionFailure("aOffset < aTextRun->GetLength()"
, "./../../../layout/generic/nsTextFrame.cpp", 3858); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "aOffset < aTextRun->GetLength()" ")"
); do { MOZ_CrashSequence(__null, 3858); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
3859 if (aNewlineIsSignificant && g[aOffset].CharIsNewline()) {
3860 return false;
3861 }
3862 if (!aOffset) {
3863 return true;
3864 }
3865 return g[aOffset].IsClusterStart() && g[aOffset].IsLigatureGroupStart() &&
3866 !g[aOffset - 1].CharIsFormattingControl() && !g[aOffset].CharIsTab();
3867}
3868
3869static bool CanAddSpacingAfter(const gfxTextRun* aTextRun, uint32_t aOffset,
3870 bool aNewlineIsSignificant) {
3871 const auto* g = aTextRun->GetCharacterGlyphs();
3872 MOZ_ASSERT(aOffset < aTextRun->GetLength())do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aOffset < aTextRun->GetLength())>::isValid,
"invalid assertion condition"); if ((__builtin_expect(!!(!(!
!(aOffset < aTextRun->GetLength()))), 0))) { do { } while
(false); MOZ_ReportAssertionFailure("aOffset < aTextRun->GetLength()"
, "./../../../layout/generic/nsTextFrame.cpp", 3872); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "aOffset < aTextRun->GetLength()" ")"
); do { MOZ_CrashSequence(__null, 3872); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
3873 if (aNewlineIsSignificant && g[aOffset].CharIsNewline()) {
3874 return false;
3875 }
3876 if (aOffset + 1 >= aTextRun->GetLength()) {
3877 return true;
3878 }
3879 return g[aOffset + 1].IsClusterStart() &&
3880 g[aOffset + 1].IsLigatureGroupStart() &&
3881 !g[aOffset].CharIsFormattingControl() && !g[aOffset].CharIsTab();
3882}
3883
3884static gfxFloat ComputeTabWidthAppUnits(const nsIFrame* aFrame) {
3885 const auto& tabSize = aFrame->StyleText()->mTabSize;
3886 if (tabSize.IsLength()) {
3887 nscoord w = tabSize.length._0.ToAppUnits();
3888 MOZ_ASSERT(w >= 0)do { static_assert( mozilla::detail::AssertionConditionType<
decltype(w >= 0)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(w >= 0))), 0))) { do { } while
(false); MOZ_ReportAssertionFailure("w >= 0", "./../../../layout/generic/nsTextFrame.cpp"
, 3888); AnnotateMozCrashReason("MOZ_ASSERT" "(" "w >= 0" ")"
); do { MOZ_CrashSequence(__null, 3888); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
3889 return w;
3890 }
3891
3892 MOZ_ASSERT(tabSize.IsNumber())do { static_assert( mozilla::detail::AssertionConditionType<
decltype(tabSize.IsNumber())>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(tabSize.IsNumber()))), 0))) {
do { } while (false); MOZ_ReportAssertionFailure("tabSize.IsNumber()"
, "./../../../layout/generic/nsTextFrame.cpp", 3892); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "tabSize.IsNumber()" ")"); do { MOZ_CrashSequence
(__null, 3892); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
3893 gfxFloat spaces = tabSize.number._0;
3894 MOZ_ASSERT(spaces >= 0)do { static_assert( mozilla::detail::AssertionConditionType<
decltype(spaces >= 0)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(spaces >= 0))), 0))) { do
{ } while (false); MOZ_ReportAssertionFailure("spaces >= 0"
, "./../../../layout/generic/nsTextFrame.cpp", 3894); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "spaces >= 0" ")"); do { MOZ_CrashSequence
(__null, 3894); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
3895
3896 const nsIFrame* cb = aFrame->GetContainingBlock(0, aFrame->StyleDisplay());
3897 const auto* styleText = cb->StyleText();
3898
3899 // Round the space width when converting to appunits the same way textruns do.
3900 // We don't use GetFirstFontMetrics here because that may return a font that
3901 // does not actually have the <space> character, yet is considered the "first
3902 // available font" per CSS Fonts. Here, we want the font that would be used
3903 // to render <space>, even if that means looking further down the font-family
3904 // list.
3905 RefPtr fm = nsLayoutUtils::GetFontMetricsForFrame(cb, 1.0f);
3906 bool vertical = cb->GetWritingMode().IsCentralBaseline();
3907 RefPtr font = fm->GetThebesFontGroup()->GetFirstValidFont(' ');
3908 auto metrics = font->GetMetrics(vertical ? nsFontMetrics::eVertical
3909 : nsFontMetrics::eHorizontal);
3910 nscoord spaceWidth = NSToCoordRound(metrics.spaceWidth *
3911 cb->PresContext()->AppUnitsPerDevPixel());
3912 return spaces *
3913 (spaceWidth + styleText->mLetterSpacing.Resolve(fm->EmHeight()) +
3914 styleText->mWordSpacing.Resolve(spaceWidth));
3915}
3916
3917// Walk backward from aIter to prior cluster starts (within the same textframe's
3918// content) and return the first non-mark autospace class.
3919//
3920// @param aContentOffsetAtFrameStart the original content offset at the start of
3921// the textframe.
3922static Maybe<TextAutospace::CharClass> LastNonMarkCharClass(
3923 gfxSkipCharsIterator& aIter, int32_t aContentOffsetAtFrameStart,
3924 const gfxTextRun* aTextRun, const CharacterDataBuffer& aBuffer) {
3925 while (aIter.GetOriginalOffset() > aContentOffsetAtFrameStart) {
3926 aIter.AdvanceOriginal(-1);
3927 FindClusterStart(aTextRun, aContentOffsetAtFrameStart, &aIter);
3928 const char32_t ch = aBuffer.ScalarValueAt(aIter.GetOriginalOffset());
3929 auto cls = TextAutospace::GetCharClass(ch);
3930 if (cls != TextAutospace::CharClass::CombiningMark) {
3931 return Some(cls);
3932 }
3933 }
3934 return Nothing();
3935}
3936
3937// Walk backward through the frame's content and return the first non-mark
3938// autospace class. (Unlike the function above, this is usable when the frame
3939// does not currently have a textrun.)
3940static Maybe<TextAutospace::CharClass> LastNonMarkCharClass(
3941 const nsTextFrame* aFrame) {
3942 using CharClass = TextAutospace::CharClass;
3943 bool trimSpace = aFrame->HasAnyStateBits(TEXT_TRIMMED_TRAILING_WHITESPACE);
3944 const auto& buffer = aFrame->CharacterDataBuffer();
3945 const uint32_t startOffset = aFrame->GetContentOffset();
3946 uint32_t i = aFrame->GetContentEnd();
3947 while (i > startOffset) {
3948 // Get trailing character, decoding surrogate pair if necessary.
3949 char32_t ch = buffer.CharAt(--i);
3950 if (IsLowSurrogate(ch) && i > startOffset) {
3951 // Get potential high surrogate, and decode.
3952 char32 hi = buffer.CharAt(i - 1);
3953 if (IsHighSurrogate(hi)) {
3954 ch = SurrogateToUCS4(hi, ch);
3955 --i;
3956 }
3957 }
3958 // Skip over trailing whitespace if the frame was trimmed.
3959 if (trimSpace) {
3960 if (IsTrimmableSpace(ch)) {
3961 continue;
3962 }
3963 trimSpace = false;
3964 }
3965 // If it has a non-CombiningMark class, return it.
3966 auto cls = TextAutospace::GetCharClass(ch);
3967 if (cls != CharClass::CombiningMark) {
3968 return Some(cls);
3969 }
3970 }
3971 // No (non-mark, non-trimmed) characters were found.
3972 return Nothing();
3973}
3974
3975// Return the first non-mark autospace class from the end of content in aFrame.
3976static Maybe<TextAutospace::CharClass> LastNonMarkCharClassInFrame(
3977 nsTextFrame* aFrame) {
3978 using CharClass = TextAutospace::CharClass;
3979 if (!aFrame->GetContentLength()) {
3980 return Nothing();
3981 }
3982 Maybe<CharClass> prevClass;
3983 if (aFrame->GetTextRun(nsTextFrame::eInflated)) {
3984 // If the frame has a textrun, we can use that to find the last cluster
3985 // start character and return its class.
3986 gfxSkipCharsIterator iter = aFrame->EnsureTextRun(nsTextFrame::eInflated);
3987 iter.SetOriginalOffset(aFrame->GetContentEnd());
3988 prevClass = LastNonMarkCharClass(iter, aFrame->GetContentOffset(),
3989 aFrame->GetTextRun(nsTextFrame::eInflated),
3990 aFrame->CharacterDataBuffer());
3991 } else {
3992 // We can't call EnsureTextRun if it would build new textruns, because that
3993 // could destroy the glyph runs that we're currently iterating over. So
3994 // instead we fall back to inspecting the content directly. This means we
3995 // ignore CSS whitespace-collapsing, which could mean some of the content
3996 // should be skipped, but in practice none of the characters that are
3997 // relevant for autospace classes would be affected.
3998 prevClass = LastNonMarkCharClass(aFrame);
3999 }
4000 if (prevClass) {
4001 return prevClass;
4002 }
4003 if (aFrame->GetPrevInFlow()) {
4004 // If aFrame has a prev-in-flow, it is after a line-break, so autospace does
4005 // not apply here; just return Other.
4006 return Some(CharClass::Other);
4007 }
4008 return Nothing();
4009}
4010
4011// Look for the autospace class of the content preceding the given aFrame
4012// in the mapped flows of the current textrun.
4013static Maybe<TextAutospace::CharClass> GetPrecedingCharClassFromMappedFlows(
4014 const nsTextFrame* aFrame, const gfxTextRun* aTextRun) {
4015 using CharClass = TextAutospace::CharClass;
4016
4017 if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow) {
4018 return Nothing();
4019 }
4020
4021 auto data = static_cast<TextRunUserData*>(aTextRun->GetUserData());
4022 if (!data) {
4023 return Nothing();
4024 }
4025 TextRunMappedFlow* mappedFlows = GetMappedFlows(aTextRun);
4026
4027 // Search for aFrame in the mapped flows.
4028 uint32_t i = 0;
4029 for (; i < data->mMappedFlowCount; ++i) {
4030 if (mappedFlows[i].mStartFrame == aFrame) {
4031 break;
4032 }
4033 }
4034 MOZ_ASSERT(mappedFlows[i].mStartFrame == aFrame,do { static_assert( mozilla::detail::AssertionConditionType<
decltype(mappedFlows[i].mStartFrame == aFrame)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(mappedFlows[i].mStartFrame ==
aFrame))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("mappedFlows[i].mStartFrame == aFrame" " (" "aFrame not found in mapped flows!"
")", "./../../../layout/generic/nsTextFrame.cpp", 4035); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "mappedFlows[i].mStartFrame == aFrame" ") ("
"aFrame not found in mapped flows!" ")"); do { MOZ_CrashSequence
(__null, 4035); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
4035 "aFrame not found in mapped flows!")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(mappedFlows[i].mStartFrame == aFrame)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(mappedFlows[i].mStartFrame ==
aFrame))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("mappedFlows[i].mStartFrame == aFrame" " (" "aFrame not found in mapped flows!"
")", "./../../../layout/generic/nsTextFrame.cpp", 4035); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "mappedFlows[i].mStartFrame == aFrame" ") ("
"aFrame not found in mapped flows!" ")"); do { MOZ_CrashSequence
(__null, 4035); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
4036
4037 while (i > 0) {
4038 nsTextFrame* f = mappedFlows[--i].mStartFrame->LastInFlow();
4039 if (Maybe<CharClass> prevClass = LastNonMarkCharClassInFrame(f)) {
4040 return prevClass;
4041 }
4042 }
4043 return Nothing();
4044}
4045
4046// Look for the autospace class of content preceding the given frame.
4047static Maybe<TextAutospace::CharClass> GetPrecedingCharClassFromFrameTree(
4048 nsIFrame* aFrame) {
4049 using CharClass = TextAutospace::CharClass;
4050 while (!aFrame->GetPrevSibling() && aFrame->GetParent()->IsInlineFrame()) {
4051 // If this is the first child of an inline container, we want to ascend to
4052 // the parent and look at what precedes it.
4053 aFrame = aFrame->GetParent();
4054 }
4055 aFrame = aFrame->GetPrevSibling();
4056 while (aFrame) {
4057 if (aFrame->IsPlaceholderFrame()) {
4058 // Skip over out-of-flow placeholders.
4059 aFrame = aFrame->GetPrevSibling();
4060 continue;
4061 }
4062 if (aFrame->IsInlineFrame()) {
4063 // Descend into inline containers and go backwards through their content.
4064 aFrame = aFrame->PrincipalChildList().LastChild();
4065 continue;
4066 }
4067 if (nsTextFrame* f = do_QueryFrame(aFrame)) {
4068 // Look for the class of the last character in the textframe.
4069 Maybe<CharClass> prevClass = LastNonMarkCharClassInFrame(f);
4070 if (prevClass) {
4071 if ((*prevClass == CharClass::NonIdeographicLetter ||
4072 *prevClass == CharClass::NonIdeographicNumeral) &&
4073 TextAutospace::ShouldSuppressLetterNumeralSpacing(f)) {
4074 return Some(CharClass::Other);
4075 }
4076 return prevClass;
4077 }
4078 aFrame = aFrame->GetPrevSibling();
4079 continue;
4080 }
4081 return Nothing();
4082 }
4083 return Nothing();
4084}
4085
4086static bool HasCJKGlyphRun(const gfxTextRun* aTextRun) {
4087 uint32_t numGlyphRuns;
4088 const gfxTextRun::GlyphRun* run = aTextRun->GetGlyphRuns(&numGlyphRuns);
4089 while (numGlyphRuns-- > 0) {
4090 if (run->mIsCJK) {
4091 return true;
4092 }
4093 run++;
4094 }
4095 return false;
4096}
4097
4098bool nsTextFrame::PropertyProvider::GetSpacingInternal(Range aRange,
4099 Spacing* aSpacing,
4100 bool aIgnoreTabs) const {
4101 MOZ_ASSERT(IsInBounds(mStart, mLength, aRange), "Range out of bounds")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(IsInBounds(mStart, mLength, aRange))>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(IsInBounds(mStart, mLength, aRange
)))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("IsInBounds(mStart, mLength, aRange)" " (" "Range out of bounds"
")", "./../../../layout/generic/nsTextFrame.cpp", 4101); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "IsInBounds(mStart, mLength, aRange)" ") ("
"Range out of bounds" ")"); do { MOZ_CrashSequence(__null, 4101
); __attribute__((nomerge)) ::abort(); } while (false); } } while
(false)
;
4102
4103 std::memset(aSpacing, 0, aRange.Length() * sizeof(*aSpacing));
4104
4105 if (mFrame->Style()->IsTextCombined()) {
4106 return false;
4107 }
4108
4109 // Track whether any non-standard spacing is actually present in this range.
4110 // If letter-spacing is non-zero this will always be true, but for the
4111 /// word-spacing and text-autospace cases it will depend on the actual text.
4112 bool spacingPresent = mLetterSpacing;
4113
4114 // First, compute the word spacing, letter spacing, and text-autospace
4115 // spacing.
4116 if (mWordSpacing || mLetterSpacing || mTextAutospace) {
4117 bool newlineIsSignificant = mTextStyle->NewlineIsSignificant(mFrame);
4118 // Which letter-spacing model are we using?
4119 // 0 - Gecko legacy model, spacing added to trailing side of letter
4120 // 1 - WebKit/Blink-compatible, spacing added to right-hand side
4121 // 2 - Symmetrical spacing, half added to each side
4122 nscoord before, after;
4123 switch (StaticPrefs::layout_css_letter_spacing_model()) {
4124 default: // use Gecko legacy behavior if pref value is unknown
4125 case 0:
4126 before = 0;
4127 after = mLetterSpacing;
4128 break;
4129 case 1:
4130 if (mTextRun->IsRightToLeft()) {
4131 before = mLetterSpacing;
4132 after = 0;
4133 } else {
4134 before = 0;
4135 after = mLetterSpacing;
4136 }
4137 break;
4138 case 2:
4139 before = NSToCoordRound(mLetterSpacing * 0.5);
4140 after = mLetterSpacing - before;
4141 break;
4142 }
4143
4144 // Find our offset into the original+transformed string
4145 gfxSkipCharsIterator start(mStart);
4146 start.SetSkippedOffset(aRange.start);
4147 bool atStart = mStartOfLineOffset == start.GetSkippedOffset() &&
4148 !mFrame->IsInSVGTextSubtree();
4149
4150 using CharClass = TextAutospace::CharClass;
4151 // The non-mark class of a previous character at a cluster start (if any).
4152 Maybe<CharClass> prevClass;
4153
4154 // Initialization of prevClass at start-of-frame may be a bit expensive,
4155 // and we don't always need that initial value, so we encapsulate it in a
4156 // helper to be called on-demand.
4157 auto findPrecedingClass = [&]() -> CharClass {
4158 // Get the class of the character immediately before the current aRange.
4159 Maybe<CharClass> prevClass;
4160 if (aRange.start > 0) {
4161 gfxSkipCharsIterator iter = start;
4162 prevClass = LastNonMarkCharClass(iter, mFrame->GetContentOffset(),
4163 mTextRun, mCharacterDataBuffer);
4164 }
4165 // If no class was found, we need to look at the preceding content (if
4166 // any) to see what it ended with.
4167 if (!prevClass) {
4168 // If we have a prev-in-flow, we're after a line-break, so autospace
4169 // does not apply here; just set prevClass to Other.
4170 if (mFrame->GetPrevInFlow()) {
4171 prevClass = Some(CharClass::Other);
4172 } else {
4173 // If the textrun is mapping multiple content flows, we may be able
4174 // to find preceding content from there (without having to walk the
4175 // potentially more complex frame tree).
4176 prevClass = GetPrecedingCharClassFromMappedFlows(mFrame, mTextRun);
4177 // If we couldn't get it from an earlier flow covered by the textrun,
4178 // we'll have to delve into the frame tree to see what preceded this.
4179 if (!prevClass) {
4180 prevClass = GetPrecedingCharClassFromFrameTree(mFrame);
4181 }
4182 }
4183 }
4184 // If no valid class was found, return `Other`, which never participates
4185 // in autospacing rules.
4186 return prevClass.valueOr(CharClass::Other);
4187 };
4188
4189 // If text-autospace is enabled, we may be able to skip some processing if
4190 // there are no CJK glyphs in the textrun, so check for their presence.
4191 bool textIncludesCJK = mTextAutospace && mCharacterDataBuffer.Is2b() &&
4192 HasCJKGlyphRun(mTextRun);
4193
4194 // Iterate over non-skipped characters
4195 nsSkipCharsRunIterator run(
4196 start, nsSkipCharsRunIterator::LENGTH_UNSKIPPED_ONLY, aRange.Length());
4197 while (run.NextRun()) {
4198 uint32_t runOffsetInSubstring = run.GetSkippedOffset() - aRange.start;
4199 gfxSkipCharsIterator iter = run.GetPos();
4200 for (int32_t i = 0; i < run.GetRunLength(); ++i) {
4201 auto currScalar = [&]() -> char32_t {
4202 iter.SetSkippedOffset(run.GetSkippedOffset() + i);
4203 return mCharacterDataBuffer.ScalarValueAt(iter.GetOriginalOffset());
4204 };
4205 if (!atStart && before != 0 &&
4206 CanAddSpacingBefore(mTextRun, run.GetSkippedOffset() + i,
4207 newlineIsSignificant) &&
4208 !intl::UnicodeProperties::IsCursiveScript(currScalar())) {
4209 aSpacing[runOffsetInSubstring + i].mBefore += before;
4210 }
4211 if (after != 0 &&
4212 CanAddSpacingAfter(mTextRun, run.GetSkippedOffset() + i,
4213 newlineIsSignificant)) {
4214 // End of a cluster, not in a ligature: put letter-spacing after it,
4215 // unless the base char of the cluster belonged to a cursive script.
4216 iter.SetSkippedOffset(run.GetSkippedOffset() + i);
4217 FindClusterStart(mTextRun, run.GetOriginalOffset(), &iter);
4218 char32_t baseChar =
4219 mCharacterDataBuffer.ScalarValueAt(iter.GetOriginalOffset());
4220 if (!intl::UnicodeProperties::IsCursiveScript(baseChar)) {
4221 aSpacing[runOffsetInSubstring + i].mAfter += after;
4222 }
4223 }
4224 if (mWordSpacing && IsCSSWordSpacingSpace(mCharacterDataBuffer,
4225 i + run.GetOriginalOffset(),
4226 mFrame, mTextStyle)) {
4227 // It kinda sucks, but space characters can be part of clusters,
4228 // and even still be whitespace (I think!)
4229 iter.SetSkippedOffset(run.GetSkippedOffset() + i);
4230 FindClusterEnd(mTextRun, run.GetOriginalOffset() + run.GetRunLength(),
4231 &iter);
4232 uint32_t runOffset = iter.GetSkippedOffset() - aRange.start;
4233 aSpacing[runOffset].mAfter += mWordSpacing;
4234 spacingPresent = true;
4235 }
4236 // Add text-autospace spacing only at cluster starts. Always check the
4237 // character classes if the textrun includes CJK; otherwise, check only
4238 // at the frame start (as preceding content might be an ideograph
4239 // requiring autospacing).
4240 if (mTextAutospace &&
4241 (textIncludesCJK ||
4242 run.GetOriginalOffset() + i == mFrame->GetContentOffset()) &&
4243 mTextRun->IsClusterStart(run.GetSkippedOffset() + i)) {
4244 const auto currClass = TextAutospace::GetCharClass(currScalar());
4245
4246 // It is rare for the current class to be a combining mark, as
4247 // combining marks are not cluster starts. We still check in case a
4248 // stray mark appears at the start of a frame.
4249 if (currClass != CharClass::CombiningMark) {
4250 // We don't need to do anything if at start of line, or if the
4251 // current class is `Other`, which never participates in spacing.
4252 if (!atStart && currClass != CharClass::Other &&
4253 mTextAutospace->ShouldApplySpacing(
4254 prevClass.valueOrFrom(findPrecedingClass), currClass)) {
4255 aSpacing[runOffsetInSubstring + i].mBefore +=
4256 mTextAutospace->InterScriptSpacing();
4257 spacingPresent = true;
4258 }
4259 // Even if we didn't actually need to check spacing rules here, we
4260 // record the new prevClass. (Incidentally, this ensure that we'll
4261 // only call the findPrecedingClass() helper once.)
4262 prevClass = Some(currClass);
4263 }
4264 }
4265 atStart = false;
4266 }
4267 }
4268 }
4269
4270 // Now add tab spacing, if there is any
4271 if (!aIgnoreTabs) {
4272 gfxFloat tabWidth = ComputeTabWidthAppUnits(mFrame);
4273 if (tabWidth > 0) {
4274 CalcTabWidths(aRange, tabWidth);
4275 if (mTabWidths) {
4276 mTabWidths->ApplySpacing(aSpacing,
4277 aRange.start - mStart.GetSkippedOffset(),
4278 aRange.Length());
4279 spacingPresent = true;
4280 }
4281 }
4282 }
4283
4284 // Now add in justification spacing
4285 if (mJustificationSpacings.Length() > 0) {
4286 // If there is any spaces trimmed at the end, aStart + aLength may
4287 // be larger than the flags array. When that happens, we can simply
4288 // ignore those spaces.
4289 auto arrayEnd = mJustificationArrayStart +
4290 static_cast<uint32_t>(mJustificationSpacings.Length());
4291 auto end = std::min(aRange.end, arrayEnd);
4292 MOZ_ASSERT(aRange.start >= mJustificationArrayStart)do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aRange.start >= mJustificationArrayStart)>::isValid
, "invalid assertion condition"); if ((__builtin_expect(!!(!(
!!(aRange.start >= mJustificationArrayStart))), 0))) { do {
} while (false); MOZ_ReportAssertionFailure("aRange.start >= mJustificationArrayStart"
, "./../../../layout/generic/nsTextFrame.cpp", 4292); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "aRange.start >= mJustificationArrayStart"
")"); do { MOZ_CrashSequence(__null, 4292); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
4293 for (auto i = aRange.start; i < end; i++) {
4294 const auto& spacing =
4295 mJustificationSpacings[i - mJustificationArrayStart];
4296 uint32_t offset = i - aRange.start;
4297 aSpacing[offset].mBefore += spacing.mBefore;
4298 aSpacing[offset].mAfter += spacing.mAfter;
4299 }
4300 spacingPresent = true;
4301 }
4302
4303 return spacingPresent;
4304}
4305
4306// aX and the result are in whole appunits.
4307static gfxFloat AdvanceToNextTab(gfxFloat aX, gfxFloat aTabWidth,
4308 gfxFloat aMinAdvance) {
4309 // Advance aX to the next multiple of aTabWidth. We must advance
4310 // by at least aMinAdvance.
4311 gfxFloat nextPos = aX + aMinAdvance;
4312 return aTabWidth > 0.0 ? ceil(nextPos / aTabWidth) * aTabWidth : nextPos;
4313}
4314
4315void nsTextFrame::PropertyProvider::CalcTabWidths(Range aRange,
4316 gfxFloat aTabWidth) const {
4317 MOZ_ASSERT(aTabWidth > 0)do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aTabWidth > 0)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(aTabWidth > 0))), 0))) { do
{ } while (false); MOZ_ReportAssertionFailure("aTabWidth > 0"
, "./../../../layout/generic/nsTextFrame.cpp", 4317); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "aTabWidth > 0" ")"); do { MOZ_CrashSequence
(__null, 4317); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
4318
4319 if (!mTabWidths) {
4320 if (mReflowing && !mLineContainer) {
4321 // Intrinsic width computation does its own tab processing. We
4322 // just don't do anything here.
4323 return;
4324 }
4325 if (!mReflowing) {
4326 mTabWidths = mFrame->GetProperty(TabWidthProperty());
4327#ifdef DEBUG1
4328 // If we're not reflowing, we should have already computed the
4329 // tab widths; check that they're available as far as the last
4330 // tab character present (if any)
4331 for (uint32_t i = aRange.end; i > aRange.start; --i) {
4332 if (mTextRun->CharIsTab(i - 1)) {
4333 uint32_t startOffset = mStart.GetSkippedOffset();
4334 NS_ASSERTION(mTabWidths && mTabWidths->mLimit + startOffset >= i,do { if (!(mTabWidths && mTabWidths->mLimit + startOffset
>= i)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "Precomputed tab widths are missing!"
, "mTabWidths && mTabWidths->mLimit + startOffset >= i"
, "./../../../layout/generic/nsTextFrame.cpp", 4335); MOZ_PretendNoReturn
(); } } while (0)
4335 "Precomputed tab widths are missing!")do { if (!(mTabWidths && mTabWidths->mLimit + startOffset
>= i)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "Precomputed tab widths are missing!"
, "mTabWidths && mTabWidths->mLimit + startOffset >= i"
, "./../../../layout/generic/nsTextFrame.cpp", 4335); MOZ_PretendNoReturn
(); } } while (0)
;
4336 break;
4337 }
4338 }
4339#endif
4340 return;
4341 }
4342 }
4343
4344 uint32_t startOffset = mStart.GetSkippedOffset();
4345 MOZ_ASSERT(aRange.start >= startOffset, "wrong start offset")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aRange.start >= startOffset)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(aRange.start >= startOffset
))), 0))) { do { } while (false); MOZ_ReportAssertionFailure(
"aRange.start >= startOffset" " (" "wrong start offset" ")"
, "./../../../layout/generic/nsTextFrame.cpp", 4345); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "aRange.start >= startOffset" ") (" "wrong start offset"
")"); do { MOZ_CrashSequence(__null, 4345); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
4346 MOZ_ASSERT(aRange.end <= startOffset + mLength, "beyond the end")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aRange.end <= startOffset + mLength)>::isValid
, "invalid assertion condition"); if ((__builtin_expect(!!(!(
!!(aRange.end <= startOffset + mLength))), 0))) { do { } while
(false); MOZ_ReportAssertionFailure("aRange.end <= startOffset + mLength"
" (" "beyond the end" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 4346); AnnotateMozCrashReason("MOZ_ASSERT" "(" "aRange.end <= startOffset + mLength"
") (" "beyond the end" ")"); do { MOZ_CrashSequence(__null, 4346
); __attribute__((nomerge)) ::abort(); } while (false); } } while
(false)
;
4347 uint32_t tabsEnd =
4348 (mTabWidths ? mTabWidths->mLimit : mTabWidthsAnalyzedLimit) + startOffset;
4349 if (tabsEnd < aRange.end) {
4350 NS_ASSERTION(mReflowing,do { if (!(mReflowing)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "We need precomputed tab widths, but don't have enough."
, "mReflowing", "./../../../layout/generic/nsTextFrame.cpp", 4351
); MOZ_PretendNoReturn(); } } while (0)
4351 "We need precomputed tab widths, but don't have enough.")do { if (!(mReflowing)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "We need precomputed tab widths, but don't have enough."
, "mReflowing", "./../../../layout/generic/nsTextFrame.cpp", 4351
); MOZ_PretendNoReturn(); } } while (0)
;
4352
4353 for (uint32_t i = tabsEnd; i < aRange.end; ++i) {
4354 Spacing spacing;
4355 GetSpacingInternal(Range(i, i + 1), &spacing, true);
4356 mOffsetFromBlockOriginForTabs += spacing.mBefore;
4357
4358 if (!mTextRun->CharIsTab(i)) {
4359 if (mTextRun->IsClusterStart(i)) {
4360 uint32_t clusterEnd = i + 1;
4361 while (clusterEnd < mTextRun->GetLength() &&
4362 !mTextRun->IsClusterStart(clusterEnd)) {
4363 ++clusterEnd;
4364 }
4365 mOffsetFromBlockOriginForTabs +=
4366 mTextRun->GetAdvanceWidth(Range(i, clusterEnd), nullptr);
4367 }
4368 } else {
4369 if (!mTabWidths) {
4370 mTabWidths = new TabWidthStore(mFrame->GetContentOffset());
4371 mFrame->SetProperty(TabWidthProperty(), mTabWidths);
4372 }
4373 double nextTab = AdvanceToNextTab(mOffsetFromBlockOriginForTabs,
4374 aTabWidth, MinTabAdvance());
4375 mTabWidths->mWidths.AppendElement(
4376 TabWidth(i - startOffset,
4377 NSToIntRound(nextTab - mOffsetFromBlockOriginForTabs)));
4378 mOffsetFromBlockOriginForTabs = nextTab;
4379 }
4380
4381 mOffsetFromBlockOriginForTabs += spacing.mAfter;
4382 }
4383
4384 if (mTabWidths) {
4385 mTabWidths->mLimit = aRange.end - startOffset;
4386 }
4387 }
4388
4389 if (!mTabWidths) {
4390 // Delete any stale property that may be left on the frame
4391 mFrame->RemoveProperty(TabWidthProperty());
4392 mTabWidthsAnalyzedLimit =
4393 std::max(mTabWidthsAnalyzedLimit, aRange.end - startOffset);
4394 }
4395}
4396
4397gfxFloat nsTextFrame::PropertyProvider::GetHyphenWidth() const {
4398 if (mHyphenWidth < 0) {
4399 const auto& hyphenateChar = mTextStyle->mHyphenateCharacter;
4400 if (hyphenateChar.IsAuto()) {
4401 mHyphenWidth = GetFontGroup()->GetHyphenWidth(this);
4402 } else {
4403 RefPtr<gfxTextRun> hyphRun = GetHyphenTextRun(mFrame, nullptr);
4404 mHyphenWidth = hyphRun ? hyphRun->GetAdvanceWidth() : 0;
4405 }
4406 }
4407 return mHyphenWidth + mLetterSpacing;
4408}
4409
4410static inline bool IS_HYPHEN(char16_t u) {
4411 return u == char16_t('-') || // HYPHEN-MINUS
4412 u == 0x058A || // ARMENIAN HYPHEN
4413 u == 0x2010 || // HYPHEN
4414 u == 0x2012 || // FIGURE DASH
4415 u == 0x2013; // EN DASH
4416}
4417
4418void nsTextFrame::PropertyProvider::GetHyphenationBreaks(
4419 Range aRange, HyphenType* aBreakBefore) const {
4420 MOZ_ASSERT(IsInBounds(mStart, mLength, aRange), "Range out of bounds")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(IsInBounds(mStart, mLength, aRange))>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(IsInBounds(mStart, mLength, aRange
)))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("IsInBounds(mStart, mLength, aRange)" " (" "Range out of bounds"
")", "./../../../layout/generic/nsTextFrame.cpp", 4420); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "IsInBounds(mStart, mLength, aRange)" ") ("
"Range out of bounds" ")"); do { MOZ_CrashSequence(__null, 4420
); __attribute__((nomerge)) ::abort(); } while (false); } } while
(false)
;
4421 MOZ_ASSERT(mLength != INT32_MAX, "Can't call this with undefined length")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(mLength != (2147483647))>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(mLength != (2147483647)))), 0
))) { do { } while (false); MOZ_ReportAssertionFailure("mLength != (2147483647)"
" (" "Can't call this with undefined length" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 4421); AnnotateMozCrashReason("MOZ_ASSERT" "(" "mLength != (2147483647)"
") (" "Can't call this with undefined length" ")"); do { MOZ_CrashSequence
(__null, 4421); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
4422
4423 if (!mTextStyle->WhiteSpaceCanWrap(mFrame) ||
4424 mTextStyle->mHyphens == StyleHyphens::None) {
4425 memset(aBreakBefore, static_cast<uint8_t>(HyphenType::None),
4426 aRange.Length() * sizeof(HyphenType));
4427 return;
4428 }
4429
4430 // Iterate through the original-string character runs
4431 nsSkipCharsRunIterator run(
4432 mStart, nsSkipCharsRunIterator::LENGTH_UNSKIPPED_ONLY, aRange.Length());
4433 run.SetSkippedOffset(aRange.start);
4434 // We need to visit skipped characters so that we can detect SHY
4435 run.SetVisitSkipped();
4436
4437 int32_t prevTrailingCharOffset = run.GetPos().GetOriginalOffset() - 1;
4438 bool allowHyphenBreakBeforeNextChar =
4439 prevTrailingCharOffset >= mStart.GetOriginalOffset() &&
4440 prevTrailingCharOffset < mStart.GetOriginalOffset() + mLength &&
4441 mCharacterDataBuffer.CharAt(
4442 AssertedCast<uint32_t>(prevTrailingCharOffset)) == CH_SHY173;
4443
4444 while (run.NextRun()) {
4445 NS_ASSERTION(run.GetRunLength() > 0, "Shouldn't return zero-length runs")do { if (!(run.GetRunLength() > 0)) { NS_DebugBreak(NS_DEBUG_ASSERTION
, "Shouldn't return zero-length runs", "run.GetRunLength() > 0"
, "./../../../layout/generic/nsTextFrame.cpp", 4445); MOZ_PretendNoReturn
(); } } while (0)
;
4446 if (run.IsSkipped()) {
4447 // Check if there's a soft hyphen which would let us hyphenate before
4448 // the next non-skipped character. Don't look at soft hyphens followed
4449 // by other skipped characters, we won't use them.
4450 allowHyphenBreakBeforeNextChar =
4451 mCharacterDataBuffer.CharAt(AssertedCast<uint32_t>(
4452 run.GetOriginalOffset() + run.GetRunLength() - 1)) == CH_SHY173;
4453 } else {
4454 int32_t runOffsetInSubstring = run.GetSkippedOffset() - aRange.start;
4455 memset(aBreakBefore + runOffsetInSubstring,
4456 static_cast<uint8_t>(HyphenType::None),
4457 run.GetRunLength() * sizeof(HyphenType));
4458 // Don't allow hyphen breaks at the start of the line
4459 aBreakBefore[runOffsetInSubstring] =
4460 allowHyphenBreakBeforeNextChar &&
4461 (!mFrame->HasAnyStateBits(TEXT_START_OF_LINE) ||
4462 run.GetSkippedOffset() > mStart.GetSkippedOffset())
4463 ? HyphenType::Soft
4464 : HyphenType::None;
4465 allowHyphenBreakBeforeNextChar = false;
4466 }
4467 }
4468
4469 if (mTextStyle->mHyphens == StyleHyphens::Auto) {
4470 gfxSkipCharsIterator skipIter(mStart);
4471 for (uint32_t i = 0; i < aRange.Length(); ++i) {
4472 if (IS_HYPHEN(mCharacterDataBuffer.CharAt(AssertedCast<uint32_t>(
4473 skipIter.ConvertSkippedToOriginal(aRange.start + i))))) {
4474 if (i < aRange.Length() - 1) {
4475 aBreakBefore[i + 1] = HyphenType::Explicit;
4476 }
4477 continue;
4478 }
4479
4480 if (mTextRun->CanHyphenateBefore(aRange.start + i) &&
4481 aBreakBefore[i] == HyphenType::None) {
4482 aBreakBefore[i] = HyphenType::AutoWithoutManualInSameWord;
4483 }
4484 }
4485 }
4486}
4487
4488bool nsTextFrame::PropertyProvider::GetSourceText(Range aRange,
4489 nsAString& aText) const {
4490 MOZ_ASSERT(IsInBounds(mStart, mLength, aRange), "Range out of bounds")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(IsInBounds(mStart, mLength, aRange))>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(IsInBounds(mStart, mLength, aRange
)))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("IsInBounds(mStart, mLength, aRange)" " (" "Range out of bounds"
")", "./../../../layout/generic/nsTextFrame.cpp", 4490); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "IsInBounds(mStart, mLength, aRange)" ") ("
"Range out of bounds" ")"); do { MOZ_CrashSequence(__null, 4490
); __attribute__((nomerge)) ::abort(); } while (false); } } while
(false)
;
4491 MOZ_ASSERT(aText.IsEmpty())do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aText.IsEmpty())>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(aText.IsEmpty()))), 0))) { do
{ } while (false); MOZ_ReportAssertionFailure("aText.IsEmpty()"
, "./../../../layout/generic/nsTextFrame.cpp", 4491); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "aText.IsEmpty()" ")"); do { MOZ_CrashSequence
(__null, 4491); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
4492
4493 const bool isTransformed =
4494 !!(mTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsTransformed);
4495 const nsTransformedTextRun* transformedTextRun =
4496 isTransformed ? static_cast<const nsTransformedTextRun*>(mTextRun.get())
4497 : nullptr;
4498 gfxSkipCharsIterator skipIter(mStart);
4499
4500 // text-transform must not affect a plain text copy, so we always map back to
4501 // the DOM text, even for a transformed textrun. The DOM text is never masked,
4502 // so we must explicitly return false for masked password characters.
4503 aText.SetCapacity(aRange.Length());
4504 for (uint32_t i = aRange.start; i < aRange.end; ++i) {
4505 if (transformedTextRun && transformedTextRun->mStyles[i]->mMaskPassword) {
4506 aText.Truncate();
4507 return false;
4508 }
4509 aText.Append(mCharacterDataBuffer.CharAt(
4510 AssertedCast<uint32_t>(skipIter.ConvertSkippedToOriginal(i))));
4511 }
4512 return true;
4513}
4514
4515void nsTextFrame::PropertyProvider::InitializeForDisplay(bool aTrimAfter) {
4516 nsTextFrame::TrimmedOffsets trimmed = mFrame->GetTrimmedOffsets(
4517 mCharacterDataBuffer,
4518 (aTrimAfter ? nsTextFrame::TrimmedOffsetFlags::Default
4519 : nsTextFrame::TrimmedOffsetFlags::NoTrimAfter));
4520 mStart.SetOriginalOffset(trimmed.mStart);
4521 mLength = trimmed.mLength;
4522 if (mFrame->HasAnyStateBits(TEXT_START_OF_LINE)) {
4523 mStartOfLineOffset = mStart.GetSkippedOffset();
4524 }
4525 SetupJustificationSpacing(true);
4526}
4527
4528void nsTextFrame::PropertyProvider::InitializeForMeasure() {
4529 nsTextFrame::TrimmedOffsets trimmed = mFrame->GetTrimmedOffsets(
4530 mCharacterDataBuffer, nsTextFrame::TrimmedOffsetFlags::NotPostReflow);
4531 mStart.SetOriginalOffset(trimmed.mStart);
4532 mLength = trimmed.mLength;
4533 if (mFrame->HasAnyStateBits(TEXT_START_OF_LINE)) {
4534 mStartOfLineOffset = mStart.GetSkippedOffset();
4535 }
4536 SetupJustificationSpacing(false);
4537}
4538
4539void nsTextFrame::PropertyProvider::SetupJustificationSpacing(
4540 bool aPostReflow) {
4541 MOZ_ASSERT(mLength != INT32_MAX, "Can't call this with undefined length")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(mLength != (2147483647))>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(mLength != (2147483647)))), 0
))) { do { } while (false); MOZ_ReportAssertionFailure("mLength != (2147483647)"
" (" "Can't call this with undefined length" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 4541); AnnotateMozCrashReason("MOZ_ASSERT" "(" "mLength != (2147483647)"
") (" "Can't call this with undefined length" ")"); do { MOZ_CrashSequence
(__null, 4541); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
4542
4543 if (!mFrame->HasAnyStateBits(TEXT_JUSTIFICATION_ENABLED)) {
4544 return;
4545 }
4546
4547 gfxSkipCharsIterator start(mStart), end(mStart);
4548 // We can't just use our mLength here; when InitializeForDisplay is
4549 // called with false for aTrimAfter, we still shouldn't be assigning
4550 // justification space to any trailing whitespace.
4551 nsTextFrame::TrimmedOffsets trimmed = mFrame->GetTrimmedOffsets(
4552 mCharacterDataBuffer,
4553 (aPostReflow ? nsTextFrame::TrimmedOffsetFlags::Default
4554 : nsTextFrame::TrimmedOffsetFlags::NotPostReflow));
4555 end.AdvanceOriginal(trimmed.mLength);
4556 gfxSkipCharsIterator realEnd(end);
4557
4558 Range range(uint32_t(start.GetOriginalOffset()),
4559 uint32_t(end.GetOriginalOffset()));
4560 nsTArray<JustificationAssignment> assignments;
4561 JustificationInfo info = ComputeJustification(range, &assignments);
4562
4563 auto assign = mFrame->GetJustificationAssignment();
4564 auto totalGaps = JustificationUtils::CountGaps(info, assign);
4565 if (!totalGaps || assignments.IsEmpty()) {
4566 // Nothing to do, nothing is justifiable and we shouldn't have any
4567 // justification space assigned
4568 return;
4569 }
4570
4571 // Remember that textrun measurements are in the run's orientation,
4572 // so its advance "width" is actually a height in vertical writing modes,
4573 // corresponding to the inline-direction of the frame.
4574 gfxFloat naturalWidth = mTextRun->GetAdvanceWidth(
4575 Range(mStart.GetSkippedOffset(), realEnd.GetSkippedOffset()), this);
4576 if (mFrame->HasAnyStateBits(TEXT_HYPHEN_BREAK)) {
4577 naturalWidth += GetHyphenWidth();
4578 }
4579 nscoord totalSpacing = mFrame->ISize() - naturalWidth;
4580 if (totalSpacing <= 0) {
4581 // No space available
4582 return;
4583 }
4584
4585 assignments[0].mGapsAtStart = assign.mGapsAtStart;
4586 assignments.LastElement().mGapsAtEnd = assign.mGapsAtEnd;
4587
4588 MOZ_ASSERT(mJustificationSpacings.IsEmpty())do { static_assert( mozilla::detail::AssertionConditionType<
decltype(mJustificationSpacings.IsEmpty())>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(mJustificationSpacings.IsEmpty
()))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("mJustificationSpacings.IsEmpty()", "./../../../layout/generic/nsTextFrame.cpp"
, 4588); AnnotateMozCrashReason("MOZ_ASSERT" "(" "mJustificationSpacings.IsEmpty()"
")"); do { MOZ_CrashSequence(__null, 4588); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
4589 JustificationApplicationState state(totalGaps, totalSpacing);
4590 mJustificationSpacings.SetCapacity(assignments.Length());
4591 for (const JustificationAssignment& assign : assignments) {
4592 Spacing* spacing = mJustificationSpacings.AppendElement();
4593 spacing->mBefore = state.Consume(assign.mGapsAtStart);
4594 spacing->mAfter = state.Consume(assign.mGapsAtEnd);
4595 }
4596}
4597
4598void nsTextFrame::PropertyProvider::InitFontGroupAndFontMetrics() const {
4599 if (!mFontMetrics) {
4600 if (mWhichTextRun == nsTextFrame::eInflated) {
4601 mFontMetrics = mFrame->InflatedFontMetrics();
4602 } else {
4603 mFontMetrics = nsLayoutUtils::GetFontMetricsForFrame(mFrame, 1.0f);
4604 }
4605 }
4606 mFontGroup = mFontMetrics->GetThebesFontGroup();
4607}
4608
4609void nsTextFrame::PropertyProvider::InitTextAutospace() {
4610 const auto styleTextAutospace = mTextStyle->EffectiveTextAutospace();
4611 if (TextAutospace::Enabled(styleTextAutospace, mFrame)) {
4612 mTextAutospace.emplace(styleTextAutospace,
4613 GetFontMetrics()->InterScriptSpacingWidth());
4614 }
4615}
4616
4617#ifdef ACCESSIBILITY1
4618a11y::AccType nsTextFrame::AccessibleType() {
4619 if (IsEmpty()) {
4620 RenderedText text =
4621 GetRenderedText(0, UINT32_MAX(4294967295U), TextOffsetType::OffsetsInContentText,
4622 TrailingWhitespace::DontTrim);
4623 if (text.mString.IsEmpty()) {
4624 return a11y::eNoType;
4625 }
4626 }
4627
4628 return a11y::eTextLeafType;
4629}
4630#endif
4631
4632//-----------------------------------------------------------------------------
4633void nsTextFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
4634 nsIFrame* aPrevInFlow) {
4635 NS_ASSERTION(!aPrevInFlow, "Can't be a continuation!")do { if (!(!aPrevInFlow)) { NS_DebugBreak(NS_DEBUG_ASSERTION,
"Can't be a continuation!", "!aPrevInFlow", "./../../../layout/generic/nsTextFrame.cpp"
, 4635); MOZ_PretendNoReturn(); } } while (0)
;
4636 MOZ_ASSERT(aContent->IsText(), "Bogus content!")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aContent->IsText())>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(aContent->IsText()))), 0)
)) { do { } while (false); MOZ_ReportAssertionFailure("aContent->IsText()"
" (" "Bogus content!" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 4636); AnnotateMozCrashReason("MOZ_ASSERT" "(" "aContent->IsText()"
") (" "Bogus content!" ")"); do { MOZ_CrashSequence(__null, 4636
); __attribute__((nomerge)) ::abort(); } while (false); } } while
(false)
;
4637
4638 // Remove any NewlineOffsetProperty or InFlowContentLengthProperty since they
4639 // might be invalid if the content was modified while there was no frame
4640 if (aContent->HasFlag(NS_HAS_NEWLINE_PROPERTY)) {
4641 aContent->RemoveProperty(nsGkAtoms::newline);
4642 aContent->UnsetFlags(NS_HAS_NEWLINE_PROPERTY);
4643 }
4644 if (aContent->HasFlag(NS_HAS_FLOWLENGTH_PROPERTY)) {
4645 aContent->RemoveProperty(nsGkAtoms::flowlength);
4646 aContent->UnsetFlags(NS_HAS_FLOWLENGTH_PROPERTY);
4647 }
4648
4649 // Since our content has a frame now, this flag is no longer needed.
4650 aContent->UnsetFlags(NS_CREATE_FRAME_IF_NON_WHITESPACE);
4651
4652 // We're not a continuing frame.
4653 // mContentOffset = 0; not necessary since we get zeroed out at init
4654 nsIFrame::Init(aContent, aParent, aPrevInFlow);
4655}
4656
4657void nsTextFrame::ClearFrameOffsetCache() {
4658 // See if we need to remove ourselves from the offset cache
4659 if (HasAnyStateBits(TEXT_IN_OFFSET_CACHE)) {
4660 nsIFrame* primaryFrame = mContent->GetPrimaryFrame();
4661 if (primaryFrame) {
4662 // The primary frame might be null here. For example,
4663 // nsLineBox::DeleteLineList just destroys the frames in order, which
4664 // means that the primary frame is already dead if we're a continuing text
4665 // frame, in which case, all of its properties are gone, and we don't need
4666 // to worry about deleting this property here.
4667 primaryFrame->RemoveProperty(OffsetToFrameProperty());
4668 }
4669 RemoveStateBits(TEXT_IN_OFFSET_CACHE);
4670 }
4671}
4672
4673void nsTextFrame::Destroy(DestroyContext& aContext) {
4674 ClearFrameOffsetCache();
4675
4676 // We might want to clear NS_CREATE_FRAME_IF_NON_WHITESPACE or
4677 // NS_REFRAME_IF_WHITESPACE on mContent here, since our parent frame
4678 // type might be changing. Not clear whether it's worth it.
4679 ClearTextRuns();
4680 if (mNextContinuation) {
4681 mNextContinuation->SetPrevInFlow(nullptr);
4682 }
4683 // Let the base class destroy the frame
4684 nsIFrame::Destroy(aContext);
4685}
4686
4687nsTArray<nsTextFrame*>* nsTextFrame::GetContinuations() {
4688 MOZ_ASSERT(NS_IsMainThread())do { static_assert( mozilla::detail::AssertionConditionType<
decltype(NS_IsMainThread())>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(NS_IsMainThread()))), 0))) {
do { } while (false); MOZ_ReportAssertionFailure("NS_IsMainThread()"
, "./../../../layout/generic/nsTextFrame.cpp", 4688); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "NS_IsMainThread()" ")"); do { MOZ_CrashSequence
(__null, 4688); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
4689 // Only for use on the primary frame, which has no prev-continuation.
4690 MOZ_ASSERT(!GetPrevContinuation())do { static_assert( mozilla::detail::AssertionConditionType<
decltype(!GetPrevContinuation())>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(!GetPrevContinuation()))), 0
))) { do { } while (false); MOZ_ReportAssertionFailure("!GetPrevContinuation()"
, "./../../../layout/generic/nsTextFrame.cpp", 4690); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "!GetPrevContinuation()" ")"); do { MOZ_CrashSequence
(__null, 4690); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
4691 if (!mNextContinuation) {
4692 return nullptr;
4693 }
4694 if (mPropertyFlags & PropertyFlags::Continuations) {
4695 return GetProperty(ContinuationsProperty());
4696 }
4697 size_t count = 0;
4698 for (nsIFrame* f = this; f; f = f->GetNextContinuation()) {
4699 ++count;
4700 }
4701 auto* continuations = new nsTArray<nsTextFrame*>;
4702 if (continuations->SetCapacity(count, fallible)) {
4703 for (nsTextFrame* f = this; f;
4704 f = static_cast<nsTextFrame*>(f->GetNextContinuation())) {
4705 continuations->AppendElement(f);
4706 }
4707 } else {
4708 delete continuations;
4709 continuations = nullptr;
4710 }
4711 AddProperty(ContinuationsProperty(), continuations);
4712 mPropertyFlags |= PropertyFlags::Continuations;
4713 return continuations;
4714}
4715
4716class nsContinuingTextFrame final : public nsTextFrame {
4717 public:
4718 NS_DECL_FRAMEARENA_HELPERS(nsContinuingTextFrame)static const nsQueryFrame::FrameIID kFrameIID = nsQueryFrame::
nsContinuingTextFrame_id; typedef nsContinuingTextFrame Has_NS_DECL_QUERYFRAME_TARGET
; static constexpr nsIFrame::ClassID kClassID = nsIFrame::ClassID
::nsContinuingTextFrame_id; void* operator new(size_t, mozilla
::PresShell*) ; nsQueryFrame::FrameIID GetFrameId() const override
{ return nsQueryFrame::nsContinuingTextFrame_id; }
4719
4720 friend nsIFrame* NS_NewContinuingTextFrame(mozilla::PresShell* aPresShell,
4721 ComputedStyle* aStyle);
4722
4723 void Init(nsIContent* aContent, nsContainerFrame* aParent,
4724 nsIFrame* aPrevInFlow) final;
4725
4726 void Destroy(DestroyContext&) override;
4727
4728 nsTextFrame* GetPrevContinuation() const final { return mPrevContinuation; }
4729
4730 void SetPrevContinuation(nsIFrame* aPrevContinuation) final {
4731 NS_ASSERTION(!aPrevContinuation || Type() == aPrevContinuation->Type(),do { if (!(!aPrevContinuation || Type() == aPrevContinuation->
Type())) { NS_DebugBreak(NS_DEBUG_ASSERTION, "setting a prev continuation with incorrect type!"
, "!aPrevContinuation || Type() == aPrevContinuation->Type()"
, "./../../../layout/generic/nsTextFrame.cpp", 4732); MOZ_PretendNoReturn
(); } } while (0)
4732 "setting a prev continuation with incorrect type!")do { if (!(!aPrevContinuation || Type() == aPrevContinuation->
Type())) { NS_DebugBreak(NS_DEBUG_ASSERTION, "setting a prev continuation with incorrect type!"
, "!aPrevContinuation || Type() == aPrevContinuation->Type()"
, "./../../../layout/generic/nsTextFrame.cpp", 4732); MOZ_PretendNoReturn
(); } } while (0)
;
4733 NS_ASSERTION(do { if (!(!nsSplittableFrame::IsInPrevContinuationChain(aPrevContinuation
, this))) { NS_DebugBreak(NS_DEBUG_ASSERTION, "creating a loop in continuation chain!"
, "!nsSplittableFrame::IsInPrevContinuationChain(aPrevContinuation, this)"
, "./../../../layout/generic/nsTextFrame.cpp", 4735); MOZ_PretendNoReturn
(); } } while (0)
4734 !nsSplittableFrame::IsInPrevContinuationChain(aPrevContinuation, this),do { if (!(!nsSplittableFrame::IsInPrevContinuationChain(aPrevContinuation
, this))) { NS_DebugBreak(NS_DEBUG_ASSERTION, "creating a loop in continuation chain!"
, "!nsSplittableFrame::IsInPrevContinuationChain(aPrevContinuation, this)"
, "./../../../layout/generic/nsTextFrame.cpp", 4735); MOZ_PretendNoReturn
(); } } while (0)
4735 "creating a loop in continuation chain!")do { if (!(!nsSplittableFrame::IsInPrevContinuationChain(aPrevContinuation
, this))) { NS_DebugBreak(NS_DEBUG_ASSERTION, "creating a loop in continuation chain!"
, "!nsSplittableFrame::IsInPrevContinuationChain(aPrevContinuation, this)"
, "./../../../layout/generic/nsTextFrame.cpp", 4735); MOZ_PretendNoReturn
(); } } while (0)
;
4736 mPrevContinuation = static_cast<nsTextFrame*>(aPrevContinuation);
4737 RemoveStateBits(NS_FRAME_IS_FLUID_CONTINUATION);
4738 UpdateCachedContinuations();
4739 }
4740
4741 nsTextFrame* GetPrevInFlow() const final {
4742 return HasAnyStateBits(NS_FRAME_IS_FLUID_CONTINUATION) ? mPrevContinuation
4743 : nullptr;
4744 }
4745
4746 void SetPrevInFlow(nsIFrame* aPrevInFlow) final {
4747 NS_ASSERTION(!aPrevInFlow || Type() == aPrevInFlow->Type(),do { if (!(!aPrevInFlow || Type() == aPrevInFlow->Type()))
{ NS_DebugBreak(NS_DEBUG_ASSERTION, "setting a prev in flow with incorrect type!"
, "!aPrevInFlow || Type() == aPrevInFlow->Type()", "./../../../layout/generic/nsTextFrame.cpp"
, 4748); MOZ_PretendNoReturn(); } } while (0)
4748 "setting a prev in flow with incorrect type!")do { if (!(!aPrevInFlow || Type() == aPrevInFlow->Type()))
{ NS_DebugBreak(NS_DEBUG_ASSERTION, "setting a prev in flow with incorrect type!"
, "!aPrevInFlow || Type() == aPrevInFlow->Type()", "./../../../layout/generic/nsTextFrame.cpp"
, 4748); MOZ_PretendNoReturn(); } } while (0)
;
4749 NS_ASSERTION(do { if (!(!nsSplittableFrame::IsInPrevContinuationChain(aPrevInFlow
, this))) { NS_DebugBreak(NS_DEBUG_ASSERTION, "creating a loop in continuation chain!"
, "!nsSplittableFrame::IsInPrevContinuationChain(aPrevInFlow, this)"
, "./../../../layout/generic/nsTextFrame.cpp", 4751); MOZ_PretendNoReturn
(); } } while (0)
4750 !nsSplittableFrame::IsInPrevContinuationChain(aPrevInFlow, this),do { if (!(!nsSplittableFrame::IsInPrevContinuationChain(aPrevInFlow
, this))) { NS_DebugBreak(NS_DEBUG_ASSERTION, "creating a loop in continuation chain!"
, "!nsSplittableFrame::IsInPrevContinuationChain(aPrevInFlow, this)"
, "./../../../layout/generic/nsTextFrame.cpp", 4751); MOZ_PretendNoReturn
(); } } while (0)
4751 "creating a loop in continuation chain!")do { if (!(!nsSplittableFrame::IsInPrevContinuationChain(aPrevInFlow
, this))) { NS_DebugBreak(NS_DEBUG_ASSERTION, "creating a loop in continuation chain!"
, "!nsSplittableFrame::IsInPrevContinuationChain(aPrevInFlow, this)"
, "./../../../layout/generic/nsTextFrame.cpp", 4751); MOZ_PretendNoReturn
(); } } while (0)
;
4752 mPrevContinuation = static_cast<nsTextFrame*>(aPrevInFlow);
4753 AddStateBits(NS_FRAME_IS_FLUID_CONTINUATION);
4754 UpdateCachedContinuations();
4755 }
4756
4757 // Call this helper to update cache after mPrevContinuation is changed.
4758 void UpdateCachedContinuations() {
4759 nsTextFrame* prevFirst = mFirstContinuation;
4760 if (mPrevContinuation) {
4761 mFirstContinuation = mPrevContinuation->FirstContinuation();
4762 if (mFirstContinuation) {
4763 mFirstContinuation->ClearCachedContinuations();
4764 }
4765 } else {
4766 mFirstContinuation = nullptr;
4767 }
4768 if (mFirstContinuation != prevFirst) {
4769 if (prevFirst) {
4770 prevFirst->ClearCachedContinuations();
4771 }
4772 auto* f = static_cast<nsContinuingTextFrame*>(mNextContinuation);
4773 while (f) {
4774 f->mFirstContinuation = mFirstContinuation;
4775 f = static_cast<nsContinuingTextFrame*>(f->mNextContinuation);
4776 }
4777 }
4778 }
4779
4780 nsIFrame* FirstInFlow() const final;
4781 nsTextFrame* FirstContinuation() const final {
4782#if DEBUG1
4783 // If we have a prev-continuation pointer, then our first-continuation
4784 // must be the same as that frame's.
4785 if (mPrevContinuation) {
4786 // If there's a prev-prev, then we can safely cast mPrevContinuation to
4787 // an nsContinuingTextFrame and access its mFirstContinuation pointer
4788 // directly, to avoid recursively calling FirstContinuation(), leading
4789 // to exponentially-slow behavior in the assertion.
4790 if (mPrevContinuation->GetPrevContinuation()) {
4791 auto* prev = static_cast<nsContinuingTextFrame*>(mPrevContinuation);
4792 MOZ_ASSERT(mFirstContinuation == prev->mFirstContinuation)do { static_assert( mozilla::detail::AssertionConditionType<
decltype(mFirstContinuation == prev->mFirstContinuation)>
::isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(mFirstContinuation == prev->mFirstContinuation)))
, 0))) { do { } while (false); MOZ_ReportAssertionFailure("mFirstContinuation == prev->mFirstContinuation"
, "./../../../layout/generic/nsTextFrame.cpp", 4792); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "mFirstContinuation == prev->mFirstContinuation"
")"); do { MOZ_CrashSequence(__null, 4792); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
4793 } else {
4794 MOZ_ASSERT(mFirstContinuation ==do { static_assert( mozilla::detail::AssertionConditionType<
decltype(mFirstContinuation == mPrevContinuation->FirstContinuation
())>::isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(mFirstContinuation == mPrevContinuation->FirstContinuation
()))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("mFirstContinuation == mPrevContinuation->FirstContinuation()"
, "./../../../layout/generic/nsTextFrame.cpp", 4795); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "mFirstContinuation == mPrevContinuation->FirstContinuation()"
")"); do { MOZ_CrashSequence(__null, 4795); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
4795 mPrevContinuation->FirstContinuation())do { static_assert( mozilla::detail::AssertionConditionType<
decltype(mFirstContinuation == mPrevContinuation->FirstContinuation
())>::isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(mFirstContinuation == mPrevContinuation->FirstContinuation
()))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("mFirstContinuation == mPrevContinuation->FirstContinuation()"
, "./../../../layout/generic/nsTextFrame.cpp", 4795); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "mFirstContinuation == mPrevContinuation->FirstContinuation()"
")"); do { MOZ_CrashSequence(__null, 4795); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
4796 }
4797 } else {
4798 MOZ_ASSERT(!mFirstContinuation)do { static_assert( mozilla::detail::AssertionConditionType<
decltype(!mFirstContinuation)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(!mFirstContinuation))), 0)))
{ do { } while (false); MOZ_ReportAssertionFailure("!mFirstContinuation"
, "./../../../layout/generic/nsTextFrame.cpp", 4798); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "!mFirstContinuation" ")"); do { MOZ_CrashSequence
(__null, 4798); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
4799 }
4800#endif
4801 return mFirstContinuation;
4802 };
4803
4804 void AddInlineMinISize(const IntrinsicSizeInput& aInput,
4805 InlineMinISizeData* aData) final {
4806 // Do nothing, since the first-in-flow accounts for everything.
4807 }
4808 void AddInlinePrefISize(const IntrinsicSizeInput& aInput,
4809 InlinePrefISizeData* aData) final {
4810 // Do nothing, since the first-in-flow accounts for everything.
4811 }
4812
4813 protected:
4814 explicit nsContinuingTextFrame(ComputedStyle* aStyle,
4815 nsPresContext* aPresContext)
4816 : nsTextFrame(aStyle, aPresContext, kClassID) {}
4817
4818 nsTextFrame* mPrevContinuation = nullptr;
4819 nsTextFrame* mFirstContinuation = nullptr;
4820};
4821
4822void nsContinuingTextFrame::Init(nsIContent* aContent,
4823 nsContainerFrame* aParent,
4824 nsIFrame* aPrevInFlow) {
4825 NS_ASSERTION(aPrevInFlow, "Must be a continuation!")do { if (!(aPrevInFlow)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "Must be a continuation!"
, "aPrevInFlow", "./../../../layout/generic/nsTextFrame.cpp",
4825); MOZ_PretendNoReturn(); } } while (0)
;
4826
4827 // Hook the frame into the flow
4828 nsTextFrame* prev = static_cast<nsTextFrame*>(aPrevInFlow);
4829 nsTextFrame* nextContinuation = prev->GetNextContinuation();
4830 SetPrevInFlow(aPrevInFlow);
4831 aPrevInFlow->SetNextInFlow(this);
4832
4833 // NOTE: bypassing nsTextFrame::Init!!!
4834 nsIFrame::Init(aContent, aParent, aPrevInFlow);
4835
4836 mContentOffset = prev->GetContentOffset() + prev->GetContentLengthHint();
4837 NS_ASSERTION(do { if (!(mContentOffset < int32_t(aContent->GetCharacterDataBuffer
()->GetLength()))) { NS_DebugBreak(NS_DEBUG_ASSERTION, "Creating ContinuingTextFrame, but there is no more content"
, "mContentOffset < int32_t(aContent->GetCharacterDataBuffer()->GetLength())"
, "./../../../layout/generic/nsTextFrame.cpp", 4839); MOZ_PretendNoReturn
(); } } while (0)
4838 mContentOffset < int32_t(aContent->GetCharacterDataBuffer()->GetLength()),do { if (!(mContentOffset < int32_t(aContent->GetCharacterDataBuffer
()->GetLength()))) { NS_DebugBreak(NS_DEBUG_ASSERTION, "Creating ContinuingTextFrame, but there is no more content"
, "mContentOffset < int32_t(aContent->GetCharacterDataBuffer()->GetLength())"
, "./../../../layout/generic/nsTextFrame.cpp", 4839); MOZ_PretendNoReturn
(); } } while (0)
4839 "Creating ContinuingTextFrame, but there is no more content")do { if (!(mContentOffset < int32_t(aContent->GetCharacterDataBuffer
()->GetLength()))) { NS_DebugBreak(NS_DEBUG_ASSERTION, "Creating ContinuingTextFrame, but there is no more content"
, "mContentOffset < int32_t(aContent->GetCharacterDataBuffer()->GetLength())"
, "./../../../layout/generic/nsTextFrame.cpp", 4839); MOZ_PretendNoReturn
(); } } while (0)
;
4840 if (prev->Style() != Style()) {
4841 // We're taking part of prev's text, and its style may be different
4842 // so clear its textrun which may no longer be valid (and don't set ours)
4843 prev->ClearTextRuns();
4844 } else {
4845 float inflation = prev->GetFontSizeInflation();
4846 SetFontSizeInflation(inflation);
4847 mTextRun = prev->GetTextRun(nsTextFrame::eInflated);
4848 if (inflation != 1.0f) {
4849 gfxTextRun* uninflatedTextRun =
4850 prev->GetTextRun(nsTextFrame::eNotInflated);
4851 if (uninflatedTextRun) {
4852 SetTextRun(uninflatedTextRun, nsTextFrame::eNotInflated, 1.0f);
4853 }
4854 }
4855 }
4856 if (aPrevInFlow->HasAnyStateBits(NS_FRAME_IS_BIDI)) {
4857 FrameBidiData bidiData = aPrevInFlow->GetBidiData();
4858 bidiData.precedingControl = kBidiLevelNone;
4859 SetProperty(BidiDataProperty(), bidiData);
4860
4861 if (nextContinuation) {
4862 SetNextContinuation(nextContinuation);
4863 nextContinuation->SetPrevContinuation(this);
4864 // Adjust next-continuations' content offset as needed.
4865 while (nextContinuation &&
4866 nextContinuation->GetContentOffset() < mContentOffset) {
4867#ifdef DEBUG1
4868 FrameBidiData nextBidiData = nextContinuation->GetBidiData();
4869 NS_ASSERTION(bidiData.embeddingLevel == nextBidiData.embeddingLevel &&do { if (!(bidiData.embeddingLevel == nextBidiData.embeddingLevel
&& bidiData.baseLevel == nextBidiData.baseLevel)) { NS_DebugBreak
(NS_DEBUG_ASSERTION, "stealing text from different type of BIDI continuation"
, "bidiData.embeddingLevel == nextBidiData.embeddingLevel && bidiData.baseLevel == nextBidiData.baseLevel"
, "./../../../layout/generic/nsTextFrame.cpp", 4871); MOZ_PretendNoReturn
(); } } while (0)
4870 bidiData.baseLevel == nextBidiData.baseLevel,do { if (!(bidiData.embeddingLevel == nextBidiData.embeddingLevel
&& bidiData.baseLevel == nextBidiData.baseLevel)) { NS_DebugBreak
(NS_DEBUG_ASSERTION, "stealing text from different type of BIDI continuation"
, "bidiData.embeddingLevel == nextBidiData.embeddingLevel && bidiData.baseLevel == nextBidiData.baseLevel"
, "./../../../layout/generic/nsTextFrame.cpp", 4871); MOZ_PretendNoReturn
(); } } while (0)
4871 "stealing text from different type of BIDI continuation")do { if (!(bidiData.embeddingLevel == nextBidiData.embeddingLevel
&& bidiData.baseLevel == nextBidiData.baseLevel)) { NS_DebugBreak
(NS_DEBUG_ASSERTION, "stealing text from different type of BIDI continuation"
, "bidiData.embeddingLevel == nextBidiData.embeddingLevel && bidiData.baseLevel == nextBidiData.baseLevel"
, "./../../../layout/generic/nsTextFrame.cpp", 4871); MOZ_PretendNoReturn
(); } } while (0)
;
4872 MOZ_ASSERT(nextBidiData.precedingControl == kBidiLevelNone,do { static_assert( mozilla::detail::AssertionConditionType<
decltype(nextBidiData.precedingControl == kBidiLevelNone)>
::isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(nextBidiData.precedingControl == kBidiLevelNone))), 0
))) { do { } while (false); MOZ_ReportAssertionFailure("nextBidiData.precedingControl == kBidiLevelNone"
" (" "There shouldn't be any virtual bidi formatting character "
"between continuations" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 4874); AnnotateMozCrashReason("MOZ_ASSERT" "(" "nextBidiData.precedingControl == kBidiLevelNone"
") (" "There shouldn't be any virtual bidi formatting character "
"between continuations" ")"); do { MOZ_CrashSequence(__null,
4874); __attribute__((nomerge)) ::abort(); } while (false); }
} while (false)
4873 "There shouldn't be any virtual bidi formatting character "do { static_assert( mozilla::detail::AssertionConditionType<
decltype(nextBidiData.precedingControl == kBidiLevelNone)>
::isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(nextBidiData.precedingControl == kBidiLevelNone))), 0
))) { do { } while (false); MOZ_ReportAssertionFailure("nextBidiData.precedingControl == kBidiLevelNone"
" (" "There shouldn't be any virtual bidi formatting character "
"between continuations" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 4874); AnnotateMozCrashReason("MOZ_ASSERT" "(" "nextBidiData.precedingControl == kBidiLevelNone"
") (" "There shouldn't be any virtual bidi formatting character "
"between continuations" ")"); do { MOZ_CrashSequence(__null,
4874); __attribute__((nomerge)) ::abort(); } while (false); }
} while (false)
4874 "between continuations")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(nextBidiData.precedingControl == kBidiLevelNone)>
::isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(nextBidiData.precedingControl == kBidiLevelNone))), 0
))) { do { } while (false); MOZ_ReportAssertionFailure("nextBidiData.precedingControl == kBidiLevelNone"
" (" "There shouldn't be any virtual bidi formatting character "
"between continuations" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 4874); AnnotateMozCrashReason("MOZ_ASSERT" "(" "nextBidiData.precedingControl == kBidiLevelNone"
") (" "There shouldn't be any virtual bidi formatting character "
"between continuations" ")"); do { MOZ_CrashSequence(__null,
4874); __attribute__((nomerge)) ::abort(); } while (false); }
} while (false)
;
4875#endif
4876 nextContinuation->mContentOffset = mContentOffset;
4877 nextContinuation = nextContinuation->GetNextContinuation();
4878 }
4879 }
4880 AddStateBits(NS_FRAME_IS_BIDI);
4881 } // prev frame is bidi
4882}
4883
4884void nsContinuingTextFrame::Destroy(DestroyContext& aContext) {
4885 ClearFrameOffsetCache();
4886
4887 // The text associated with this frame will become associated with our
4888 // prev-continuation. If that means the text has changed style, then
4889 // we need to wipe out the text run for the text.
4890 // Note that mPrevContinuation can be null if we're destroying the whole
4891 // frame chain from the start to the end.
4892 // If this frame is mentioned in the userData for a textrun (say
4893 // because there's a direction change at the start of this frame), then
4894 // we have to clear the textrun because we're going away and the
4895 // textrun had better not keep a dangling reference to us.
4896 if (IsInTextRunUserData() ||
4897 (mPrevContinuation && mPrevContinuation->Style() != Style())) {
4898 ClearTextRuns();
4899 // Clear the previous continuation's text run also, so that it can rebuild
4900 // the text run to include our text.
4901 if (mPrevContinuation) {
4902 mPrevContinuation->ClearTextRuns();
4903 }
4904 }
4905 nsSplittableFrame::RemoveFromFlow(this);
4906 // Let the base class destroy the frame
4907 nsIFrame::Destroy(aContext);
4908}
4909
4910nsIFrame* nsContinuingTextFrame::FirstInFlow() const {
4911 // Can't cast to |nsContinuingTextFrame*| because the first one isn't.
4912 nsIFrame *firstInFlow,
4913 *previous = const_cast<nsIFrame*>(static_cast<const nsIFrame*>(this));
4914 do {
4915 firstInFlow = previous;
4916 previous = firstInFlow->GetPrevInFlow();
4917 } while (previous);
4918 MOZ_ASSERT(firstInFlow, "post-condition failed")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(firstInFlow)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(firstInFlow))), 0))) { do { }
while (false); MOZ_ReportAssertionFailure("firstInFlow" " ("
"post-condition failed" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 4918); AnnotateMozCrashReason("MOZ_ASSERT" "(" "firstInFlow"
") (" "post-condition failed" ")"); do { MOZ_CrashSequence(__null
, 4918); __attribute__((nomerge)) ::abort(); } while (false);
} } while (false)
;
4919 return firstInFlow;
4920}
4921
4922// XXX Do we want to do all the work for the first-in-flow or do the
4923// work for each part? (Be careful of first-letter / first-line, though,
4924// especially first-line!) Doing all the work on the first-in-flow has
4925// the advantage of avoiding the potential for incremental reflow bugs,
4926// but depends on our maintining the frame tree in reasonable ways even
4927// for edge cases (block-within-inline splits, nextBidi, etc.)
4928
4929// XXX We really need to make :first-letter happen during frame
4930// construction.
4931
4932nscoord nsTextFrame::IntrinsicISize(const IntrinsicSizeInput& aInput,
4933 IntrinsicISizeType aType) {
4934 return IntrinsicISizeFromInline(aInput, aType);
4935}
4936
4937//----------------------------------------------------------------------
4938
4939#if defined(DEBUG_rbs) || defined(DEBUG_bzbarsky)
4940static void VerifyNotDirty(nsFrameState state) {
4941 bool isZero = state & NS_FRAME_FIRST_REFLOW;
4942 bool isDirty = state & NS_FRAME_IS_DIRTY;
4943 if (!isZero && isDirty) {
4944 NS_WARNING("internal offsets may be out-of-sync")NS_DebugBreak(NS_DEBUG_WARNING, "internal offsets may be out-of-sync"
, nullptr, "./../../../layout/generic/nsTextFrame.cpp", 4944)
;
4945 }
4946}
4947# define DEBUG_VERIFY_NOT_DIRTY(state) VerifyNotDirty(state)
4948#else
4949# define DEBUG_VERIFY_NOT_DIRTY(state)
4950#endif
4951
4952nsIFrame* NS_NewTextFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
4953 return new (aPresShell) nsTextFrame(aStyle, aPresShell->GetPresContext());
4954}
4955
4956NS_IMPL_FRAMEARENA_HELPERS(nsTextFrame)void* nsTextFrame ::operator new(size_t sz, mozilla::PresShell
* aShell) { return aShell->AllocateFrame(nsQueryFrame::nsTextFrame_id
, sz); }
4957
4958nsIFrame* NS_NewContinuingTextFrame(PresShell* aPresShell,
4959 ComputedStyle* aStyle) {
4960 return new (aPresShell)
4961 nsContinuingTextFrame(aStyle, aPresShell->GetPresContext());
4962}
4963
4964NS_IMPL_FRAMEARENA_HELPERS(nsContinuingTextFrame)void* nsContinuingTextFrame ::operator new(size_t sz, mozilla
::PresShell * aShell) { return aShell->AllocateFrame(nsQueryFrame
::nsContinuingTextFrame_id, sz); }
4965
4966nsTextFrame::~nsTextFrame() = default;
4967
4968nsIFrame::Cursor nsTextFrame::GetCursor(const nsPoint& aPoint) {
4969 StyleCursorKind kind = StyleUI()->Cursor().keyword;
4970 if (kind == StyleCursorKind::Auto) {
4971 if (!IsSelectable()) {
4972 kind = StyleCursorKind::Default;
4973 } else {
4974 kind = GetWritingMode().IsVertical() ? StyleCursorKind::VerticalText
4975 : StyleCursorKind::Text;
4976 }
4977 }
4978 return Cursor{kind, AllowCustomCursorImage::Yes};
4979}
4980
4981nsTextFrame* nsTextFrame::LastInFlow() const {
4982 nsTextFrame* lastInFlow = const_cast<nsTextFrame*>(this);
4983 while (lastInFlow->GetNextInFlow()) {
4984 lastInFlow = lastInFlow->GetNextInFlow();
4985 }
4986 MOZ_ASSERT(lastInFlow, "post-condition failed")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(lastInFlow)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(lastInFlow))), 0))) { do { }
while (false); MOZ_ReportAssertionFailure("lastInFlow" " (" "post-condition failed"
")", "./../../../layout/generic/nsTextFrame.cpp", 4986); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "lastInFlow" ") (" "post-condition failed" ")"
); do { MOZ_CrashSequence(__null, 4986); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
4987 return lastInFlow;
4988}
4989
4990nsTextFrame* nsTextFrame::LastContinuation() const {
4991 nsTextFrame* lastContinuation = const_cast<nsTextFrame*>(this);
4992 while (lastContinuation->mNextContinuation) {
4993 lastContinuation = lastContinuation->mNextContinuation;
4994 }
4995 MOZ_ASSERT(lastContinuation, "post-condition failed")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(lastContinuation)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(lastContinuation))), 0))) { do
{ } while (false); MOZ_ReportAssertionFailure("lastContinuation"
" (" "post-condition failed" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 4995); AnnotateMozCrashReason("MOZ_ASSERT" "(" "lastContinuation"
") (" "post-condition failed" ")"); do { MOZ_CrashSequence(__null
, 4995); __attribute__((nomerge)) ::abort(); } while (false);
} } while (false)
;
4996 return lastContinuation;
4997}
4998
4999bool nsTextFrame::ShouldSuppressLineBreak() const {
5000 // If the parent frame of the text frame is ruby content box, it must
5001 // suppress line break inside. This check is necessary, because when
5002 // a whitespace is only contained by pseudo ruby frames, its style
5003 // context won't have SuppressLineBreak bit set.
5004 if (mozilla::RubyUtils::IsRubyContentBox(GetParent()->Type())) {
5005 return true;
5006 }
5007 return Style()->ShouldSuppressLineBreak();
5008}
5009
5010void nsTextFrame::InvalidateFrame(uint32_t aDisplayItemKey,
5011 bool aRebuildDisplayItems) {
5012 InvalidateSelectionState();
5013
5014 if (IsInSVGTextSubtree()) {
5015 nsIFrame* svgTextFrame = nsLayoutUtils::GetClosestFrameOfType(
5016 GetParent(), LayoutFrameType::SVGText);
5017 svgTextFrame->InvalidateFrame();
5018 return;
5019 }
5020 nsIFrame::InvalidateFrame(aDisplayItemKey, aRebuildDisplayItems);
5021}
5022
5023void nsTextFrame::InvalidateFrameWithRect(const nsRect& aRect,
5024 uint32_t aDisplayItemKey,
5025 bool aRebuildDisplayItems) {
5026 InvalidateSelectionState();
5027
5028 if (IsInSVGTextSubtree()) {
5029 nsIFrame* svgTextFrame = nsLayoutUtils::GetClosestFrameOfType(
5030 GetParent(), LayoutFrameType::SVGText);
5031 svgTextFrame->InvalidateFrame();
5032 return;
5033 }
5034 nsIFrame::InvalidateFrameWithRect(aRect, aDisplayItemKey,
5035 aRebuildDisplayItems);
5036}
5037
5038gfxTextRun* nsTextFrame::GetUninflatedTextRun() const {
5039 return GetProperty(UninflatedTextRunProperty());
5040}
5041
5042void nsTextFrame::SetTextRun(gfxTextRun* aTextRun, TextRunType aWhichTextRun,
5043 float aInflation) {
5044 NS_ASSERTION(aTextRun, "must have text run")do { if (!(aTextRun)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "must have text run"
, "aTextRun", "./../../../layout/generic/nsTextFrame.cpp", 5044
); MOZ_PretendNoReturn(); } } while (0)
;
5045
5046 // Our inflated text run is always stored in mTextRun. In the cases
5047 // where our current inflation is not 1.0, however, we store two text
5048 // runs, and the uninflated one goes in a frame property. We never
5049 // store a single text run in both.
5050 if (aWhichTextRun == eInflated) {
5051 if (HasFontSizeInflation() && aInflation == 1.0f) {
5052 // FIXME: Probably shouldn't do this within each SetTextRun
5053 // method, but it doesn't hurt.
5054 ClearTextRun(nullptr, nsTextFrame::eNotInflated);
5055 }
5056 SetFontSizeInflation(aInflation);
5057 } else {
5058 MOZ_ASSERT(aInflation == 1.0f, "unexpected inflation")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aInflation == 1.0f)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(aInflation == 1.0f))), 0))) {
do { } while (false); MOZ_ReportAssertionFailure("aInflation == 1.0f"
" (" "unexpected inflation" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 5058); AnnotateMozCrashReason("MOZ_ASSERT" "(" "aInflation == 1.0f"
") (" "unexpected inflation" ")"); do { MOZ_CrashSequence(__null
, 5058); __attribute__((nomerge)) ::abort(); } while (false);
} } while (false)
;
5059 if (HasFontSizeInflation()) {
5060 // Setting the property will not automatically increment the textrun's
5061 // reference count, so we need to do it here.
5062 aTextRun->AddRef();
5063 SetProperty(UninflatedTextRunProperty(), aTextRun);
5064 return;
5065 }
5066 // fall through to setting mTextRun
5067 }
5068
5069 mTextRun = aTextRun;
5070
5071 // FIXME: Add assertions testing the relationship between
5072 // GetFontSizeInflation() and whether we have an uninflated text run
5073 // (but be aware that text runs can go away).
5074}
5075
5076bool nsTextFrame::RemoveTextRun(gfxTextRun* aTextRun) {
5077 if (aTextRun == mTextRun) {
5078 mTextRun = nullptr;
5079 mFontMetrics = nullptr;
5080 return true;
5081 }
5082 if (HasAnyStateBits(TEXT_HAS_FONT_INFLATION) &&
5083 GetProperty(UninflatedTextRunProperty()) == aTextRun) {
5084 RemoveProperty(UninflatedTextRunProperty());
5085 return true;
5086 }
5087 return false;
5088}
5089
5090void nsTextFrame::ClearTextRun(nsTextFrame* aStartContinuation,
5091 TextRunType aWhichTextRun) {
5092 RefPtr<gfxTextRun> textRun = GetTextRun(aWhichTextRun);
5093 if (!textRun) {
5094 return;
5095 }
5096
5097 if (aWhichTextRun == nsTextFrame::eInflated) {
5098 mFontMetrics = nullptr;
5099 }
5100
5101 DebugOnly<bool> checkmTextrun = textRun == mTextRun;
5102 UnhookTextRunFromFrames(textRun, aStartContinuation);
5103 MOZ_ASSERT(checkmTextrun ? !mTextRundo { static_assert( mozilla::detail::AssertionConditionType<
decltype(checkmTextrun ? !mTextRun : !GetProperty(UninflatedTextRunProperty
()))>::isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(checkmTextrun ? !mTextRun : !GetProperty(UninflatedTextRunProperty
())))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("checkmTextrun ? !mTextRun : !GetProperty(UninflatedTextRunProperty())"
, "./../../../layout/generic/nsTextFrame.cpp", 5104); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "checkmTextrun ? !mTextRun : !GetProperty(UninflatedTextRunProperty())"
")"); do { MOZ_CrashSequence(__null, 5104); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
5104 : !GetProperty(UninflatedTextRunProperty()))do { static_assert( mozilla::detail::AssertionConditionType<
decltype(checkmTextrun ? !mTextRun : !GetProperty(UninflatedTextRunProperty
()))>::isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(checkmTextrun ? !mTextRun : !GetProperty(UninflatedTextRunProperty
())))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("checkmTextrun ? !mTextRun : !GetProperty(UninflatedTextRunProperty())"
, "./../../../layout/generic/nsTextFrame.cpp", 5104); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "checkmTextrun ? !mTextRun : !GetProperty(UninflatedTextRunProperty())"
")"); do { MOZ_CrashSequence(__null, 5104); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
5105}
5106
5107void nsTextFrame::DisconnectTextRuns() {
5108 MOZ_ASSERT(!IsInTextRunUserData(),do { static_assert( mozilla::detail::AssertionConditionType<
decltype(!IsInTextRunUserData())>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(!IsInTextRunUserData()))), 0
))) { do { } while (false); MOZ_ReportAssertionFailure("!IsInTextRunUserData()"
" (" "Textrun mentions this frame in its user data so we can't just "
"disconnect" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 5110); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!IsInTextRunUserData()"
") (" "Textrun mentions this frame in its user data so we can't just "
"disconnect" ")"); do { MOZ_CrashSequence(__null, 5110); __attribute__
((nomerge)) ::abort(); } while (false); } } while (false)
5109 "Textrun mentions this frame in its user data so we can't just "do { static_assert( mozilla::detail::AssertionConditionType<
decltype(!IsInTextRunUserData())>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(!IsInTextRunUserData()))), 0
))) { do { } while (false); MOZ_ReportAssertionFailure("!IsInTextRunUserData()"
" (" "Textrun mentions this frame in its user data so we can't just "
"disconnect" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 5110); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!IsInTextRunUserData()"
") (" "Textrun mentions this frame in its user data so we can't just "
"disconnect" ")"); do { MOZ_CrashSequence(__null, 5110); __attribute__
((nomerge)) ::abort(); } while (false); } } while (false)
5110 "disconnect")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(!IsInTextRunUserData())>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(!IsInTextRunUserData()))), 0
))) { do { } while (false); MOZ_ReportAssertionFailure("!IsInTextRunUserData()"
" (" "Textrun mentions this frame in its user data so we can't just "
"disconnect" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 5110); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!IsInTextRunUserData()"
") (" "Textrun mentions this frame in its user data so we can't just "
"disconnect" ")"); do { MOZ_CrashSequence(__null, 5110); __attribute__
((nomerge)) ::abort(); } while (false); } } while (false)
;
5111 mTextRun = nullptr;
5112 if (HasAnyStateBits(TEXT_HAS_FONT_INFLATION)) {
5113 RemoveProperty(UninflatedTextRunProperty());
5114 }
5115}
5116
5117void nsTextFrame::NotifyNativeAnonymousTextnodeChange(uint32_t aOldLength) {
5118 MOZ_ASSERT(mContent->IsInNativeAnonymousSubtree())do { static_assert( mozilla::detail::AssertionConditionType<
decltype(mContent->IsInNativeAnonymousSubtree())>::isValid
, "invalid assertion condition"); if ((__builtin_expect(!!(!(
!!(mContent->IsInNativeAnonymousSubtree()))), 0))) { do { }
while (false); MOZ_ReportAssertionFailure("mContent->IsInNativeAnonymousSubtree()"
, "./../../../layout/generic/nsTextFrame.cpp", 5118); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "mContent->IsInNativeAnonymousSubtree()"
")"); do { MOZ_CrashSequence(__null, 5118); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
5119
5120 MarkIntrinsicISizesDirty();
5121
5122 // This is to avoid making a new Reflow request in CharacterDataChanged:
5123 for (nsTextFrame* f = this; f; f = f->GetNextContinuation()) {
5124 f->MarkSubtreeDirty();
5125 f->mReflowRequestedForCharDataChange = true;
5126 }
5127
5128 // Pretend that all the text changed.
5129 CharacterDataChangeInfo info;
5130 info.mAppend = false;
5131 info.mChangeStart = 0;
5132 info.mChangeEnd = aOldLength;
5133 info.mReplaceLength = GetContent()->TextLength();
5134 CharacterDataChanged(info);
5135}
5136
5137nsresult nsTextFrame::CharacterDataChanged(
5138 const CharacterDataChangeInfo& aInfo) {
5139 if (mContent->HasFlag(NS_HAS_NEWLINE_PROPERTY)) {
5140 mContent->RemoveProperty(nsGkAtoms::newline);
5141 mContent->UnsetFlags(NS_HAS_NEWLINE_PROPERTY);
5142 }
5143 if (mContent->HasFlag(NS_HAS_FLOWLENGTH_PROPERTY)) {
5144 mContent->RemoveProperty(nsGkAtoms::flowlength);
5145 mContent->UnsetFlags(NS_HAS_FLOWLENGTH_PROPERTY);
5146 }
5147
5148 // Find the first frame whose text has changed. Frames that are entirely
5149 // before the text change are completely unaffected.
5150 nsTextFrame* next;
5151 nsTextFrame* textFrame = this;
5152 while (true) {
5153 next = textFrame->GetNextContinuation();
5154 if (!next || next->GetContentOffset() > int32_t(aInfo.mChangeStart)) {
5155 break;
5156 }
5157 textFrame = next;
5158 }
5159
5160 int32_t endOfChangedText = aInfo.mChangeStart + aInfo.mReplaceLength;
5161
5162 // Parent of the last frame that we passed to FrameNeedsReflow (or noticed
5163 // had already received an earlier FrameNeedsReflow call).
5164 // (For subsequent frames with this same parent, we can just set their
5165 // dirty bit without bothering to call FrameNeedsReflow again.)
5166 nsIFrame* lastDirtiedFrameParent = nullptr;
5167
5168 mozilla::PresShell* presShell = PresShell();
5169 do {
5170 // textFrame contained deleted text (or the insertion point,
5171 // if this was a pure insertion).
5172 textFrame->RemoveStateBits(TEXT_WHITESPACE_FLAGS);
5173 textFrame->ClearTextRuns();
5174
5175 nsIFrame* parentOfTextFrame = textFrame->GetParent();
5176 bool areAncestorsAwareOfReflowRequest = false;
5177 if (lastDirtiedFrameParent == parentOfTextFrame) {
5178 // An earlier iteration of this loop already called
5179 // FrameNeedsReflow for a sibling of |textFrame|.
5180 areAncestorsAwareOfReflowRequest = true;
5181 } else {
5182 lastDirtiedFrameParent = parentOfTextFrame;
5183 }
5184
5185 if (textFrame->mReflowRequestedForCharDataChange) {
5186 // We already requested a reflow for this frame; nothing to do.
5187 MOZ_ASSERT(textFrame->HasAnyStateBits(NS_FRAME_IS_DIRTY),do { static_assert( mozilla::detail::AssertionConditionType<
decltype(textFrame->HasAnyStateBits(NS_FRAME_IS_DIRTY))>
::isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(textFrame->HasAnyStateBits(NS_FRAME_IS_DIRTY)))),
0))) { do { } while (false); MOZ_ReportAssertionFailure("textFrame->HasAnyStateBits(NS_FRAME_IS_DIRTY)"
" (" "mReflowRequestedForCharDataChange should only be set "
"on dirty frames" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 5189); AnnotateMozCrashReason("MOZ_ASSERT" "(" "textFrame->HasAnyStateBits(NS_FRAME_IS_DIRTY)"
") (" "mReflowRequestedForCharDataChange should only be set "
"on dirty frames" ")"); do { MOZ_CrashSequence(__null, 5189)
; __attribute__((nomerge)) ::abort(); } while (false); } } while
(false)
5188 "mReflowRequestedForCharDataChange should only be set "do { static_assert( mozilla::detail::AssertionConditionType<
decltype(textFrame->HasAnyStateBits(NS_FRAME_IS_DIRTY))>
::isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(textFrame->HasAnyStateBits(NS_FRAME_IS_DIRTY)))),
0))) { do { } while (false); MOZ_ReportAssertionFailure("textFrame->HasAnyStateBits(NS_FRAME_IS_DIRTY)"
" (" "mReflowRequestedForCharDataChange should only be set "
"on dirty frames" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 5189); AnnotateMozCrashReason("MOZ_ASSERT" "(" "textFrame->HasAnyStateBits(NS_FRAME_IS_DIRTY)"
") (" "mReflowRequestedForCharDataChange should only be set "
"on dirty frames" ")"); do { MOZ_CrashSequence(__null, 5189)
; __attribute__((nomerge)) ::abort(); } while (false); } } while
(false)
5189 "on dirty frames")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(textFrame->HasAnyStateBits(NS_FRAME_IS_DIRTY))>
::isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(textFrame->HasAnyStateBits(NS_FRAME_IS_DIRTY)))),
0))) { do { } while (false); MOZ_ReportAssertionFailure("textFrame->HasAnyStateBits(NS_FRAME_IS_DIRTY)"
" (" "mReflowRequestedForCharDataChange should only be set "
"on dirty frames" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 5189); AnnotateMozCrashReason("MOZ_ASSERT" "(" "textFrame->HasAnyStateBits(NS_FRAME_IS_DIRTY)"
") (" "mReflowRequestedForCharDataChange should only be set "
"on dirty frames" ")"); do { MOZ_CrashSequence(__null, 5189)
; __attribute__((nomerge)) ::abort(); } while (false); } } while
(false)
;
5190 } else {
5191 // Make sure textFrame is queued up for a reflow. Also set a flag so we
5192 // don't waste time doing this again in repeated calls to this method.
5193 textFrame->mReflowRequestedForCharDataChange = true;
5194 if (!areAncestorsAwareOfReflowRequest) {
5195 // Ask the parent frame to reflow me.
5196 presShell->FrameNeedsReflow(
5197 textFrame, IntrinsicDirty::FrameAndAncestors, NS_FRAME_IS_DIRTY);
5198 } else {
5199 // We already called FrameNeedsReflow on behalf of an earlier sibling,
5200 // so we can just mark this frame as dirty and don't need to bother
5201 // telling its ancestors.
5202 // Note: if the parent is a block, we're cheating here because we should
5203 // be marking our line dirty, but we're not. nsTextFrame::SetLength will
5204 // do that when it gets called during reflow.
5205 textFrame->MarkSubtreeDirty();
5206 }
5207 }
5208 textFrame->InvalidateFrame();
5209
5210 // Below, frames that start after the deleted text will be adjusted so that
5211 // their offsets move with the trailing unchanged text. If this change
5212 // deletes more text than it inserts, those frame offsets will decrease.
5213 // We need to maintain the invariant that mContentOffset is non-decreasing
5214 // along the continuation chain. So we need to ensure that frames that
5215 // started in the deleted text are all still starting before the
5216 // unchanged text.
5217 if (textFrame->mContentOffset > endOfChangedText) {
5218 textFrame->mContentOffset = endOfChangedText;
5219 }
5220
5221 textFrame = textFrame->GetNextContinuation();
5222 } while (textFrame &&
5223 textFrame->GetContentOffset() < int32_t(aInfo.mChangeEnd));
5224
5225 // This is how much the length of the string changed by --- i.e.,
5226 // how much the trailing unchanged text moved.
5227 int32_t sizeChange =
5228 aInfo.mChangeStart + aInfo.mReplaceLength - aInfo.mChangeEnd;
5229
5230 if (sizeChange) {
5231 // Fix the offsets of the text frames that start in the trailing
5232 // unchanged text.
5233 while (textFrame) {
5234 textFrame->mContentOffset += sizeChange;
5235 // XXX we could rescue some text runs by adjusting their user data
5236 // to reflect the change in DOM offsets
5237 textFrame->ClearTextRuns();
5238 textFrame = textFrame->GetNextContinuation();
5239 }
5240 }
5241
5242 return NS_OK;
5243}
5244
5245struct TextCombineData {
5246 // Measured advance of the text before any text-combine scaling is applied.
5247 nscoord mNaturalWidth = 0;
5248 // Inline offset to place this text within the 1-em block of the upright-
5249 // combined cell.
5250 nscoord mOffset = 0;
5251 // Inline scaling factor to apply (always <= 1.0, as the text may be
5252 // compressed but is never expanded to fit the 1-em cell).
5253 float mScale = 1.0f;
5254};
5255
5256NS_DECLARE_FRAME_PROPERTY_DELETABLE(TextCombineDataProperty, TextCombineData)static const mozilla::FramePropertyDescriptor<TextCombineData
>* TextCombineDataProperty() { static const auto descriptor
= mozilla::FramePropertyDescriptor<TextCombineData>::NewWithDestructor
<DeleteValue>(); return &descriptor; }
5257
5258float nsTextFrame::GetTextCombineScale() const {
5259 const auto* data = GetProperty(TextCombineDataProperty());
5260 return data ? data->mScale : 1.0f;
5261}
5262
5263std::pair<nscoord, float> nsTextFrame::GetTextCombineOffsetAndScale() const {
5264 const auto* data = GetProperty(TextCombineDataProperty());
5265 return data ? std::pair(data->mOffset, data->mScale) : std::pair(0, 1.0f);
5266}
5267
5268void nsTextFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
5269 const nsDisplayListSet& aLists) {
5270 if (!IsVisibleForPainting()) {
5271 return;
5272 }
5273
5274 DO_GLOBAL_REFLOW_COUNT_DSP("nsTextFrame")do { if (!aBuilder->IsBackgroundOnly() && !aBuilder
->IsForEventDelivery() && PresShell()->IsPaintingFrameCounts
()) { aLists.Outlines()->AppendNewToTop<mozilla::nsDisplayReflowCount
>( aBuilder, this, "nsTextFrame"); } } while (0)
;
5275
5276 const nsStyleText* st = StyleText();
5277 bool isTextTransparent =
5278 NS_GET_A(st->mWebkitTextFillColor.CalcColor(this))((uint8_t)(((st->mWebkitTextFillColor.CalcColor(this)) >>
24) & 0xff))
== 0 &&
5279 NS_GET_A(st->mWebkitTextStrokeColor.CalcColor(this))((uint8_t)(((st->mWebkitTextStrokeColor.CalcColor(this)) >>
24) & 0xff))
== 0;
5280 if ((HasAnyStateBits(TEXT_NO_RENDERED_GLYPHS) ||
5281 (isTextTransparent && !StyleText()->HasTextShadow())) &&
5282 aBuilder->IsForPainting() && !IsInSVGTextSubtree()) {
5283 if (!IsSelected()) {
5284 TextDecorations textDecs;
5285 GetTextDecorations(PresContext(), eResolvedColors, textDecs);
5286 if (!textDecs.HasDecorationLines()) {
5287 if (auto* currentPresContext = aBuilder->CurrentPresContext()) {
5288 currentPresContext->SetBuiltInvisibleText();
5289 }
5290 return;
5291 }
5292 }
5293 }
5294
5295 aLists.Content()->AppendNewToTop<nsDisplayText>(aBuilder, this);
5296}
5297
5298UniquePtr<SelectionDetails> nsTextFrame::GetSelectionDetails() {
5299 const nsFrameSelection* frameSelection = GetConstFrameSelection();
5300 if (frameSelection->IsInTableSelectionMode()) {
5301 return nullptr;
5302 }
5303 UniquePtr<SelectionDetails> details = frameSelection->LookUpSelection(
5304 mContent, GetContentOffset(), GetContentLength(),
5305 // We don't want to paint text as selected if this is not selectable.
5306 // Note if this is editable, this is always treated as selectable, i.e.,
5307 // if `user-select` is specified to `none` so that we never stop painting
5308 // selections when there is IME composition which may need normal
5309 // selection as a part of it.
5310 ShouldPaintNormalSelection()
5311 ? nsFrameSelection::IgnoreNormalSelection::No
5312 : nsFrameSelection::IgnoreNormalSelection::Yes);
5313 for (SelectionDetails* sd = details.get(); sd; sd = sd->mNext.get()) {
5314 sd->mStart += mContentOffset;
5315 sd->mEnd += mContentOffset;
5316 }
5317 return details;
5318}
5319
5320static void PaintSelectionBackground(
5321 DrawTarget& aDrawTarget, nscolor aColor, const LayoutDeviceRect& aDirtyRect,
5322 const LayoutDeviceRect& aRect, nsTextFrame::DrawPathCallbacks* aCallbacks) {
5323 Rect rect = aRect.Intersect(aDirtyRect).ToUnknownRect();
5324 MaybeSnapToDevicePixels(rect, aDrawTarget);
5325
5326 if (aCallbacks) {
5327 aCallbacks->NotifySelectionBackgroundNeedsFill(rect, aColor, aDrawTarget);
5328 } else {
5329 ColorPattern color(ToDeviceColor(aColor));
5330 aDrawTarget.FillRect(rect, color);
5331 }
5332}
5333
5334// Attempt to get the LineBaselineOffset property of aChildFrame
5335// If not set, calculate this value for all child frames of aBlockFrame
5336static nscoord LazyGetLineBaselineOffset(nsIFrame* aChildFrame,
5337 nsBlockFrame* aBlockFrame) {
5338 bool offsetFound;
5339 nscoord offset =
5340 aChildFrame->GetProperty(nsIFrame::LineBaselineOffset(), &offsetFound);
5341
5342 if (!offsetFound) {
5343 for (const auto& line : aBlockFrame->Lines()) {
5344 if (line.IsInline()) {
5345 nscoord lineBaseline = line.BStart() + line.GetLogicalAscent();
5346 for (nsIFrame* lineFrame : line.ChildFrames()) {
5347 offset = lineBaseline - lineFrame->GetNormalPosition().y;
5348 lineFrame->SetProperty(nsIFrame::LineBaselineOffset(), offset);
5349 }
5350 }
5351 }
5352 return aChildFrame->GetProperty(nsIFrame::LineBaselineOffset(),
5353 &offsetFound);
5354 } else {
5355 return offset;
5356 }
5357}
5358
5359static bool IsUnderlineRight(const ComputedStyle& aStyle) {
5360 // Check for 'left' or 'right' explicitly specified in the property;
5361 // if neither is there, we use auto positioning based on lang.
5362 const auto position = aStyle.StyleText()->mTextUnderlinePosition;
5363 if (position.IsLeft()) {
5364 return false;
5365 }
5366 if (position.IsRight()) {
5367 return true;
5368 }
5369 // If neither 'left' nor 'right' was specified, check the language.
5370 nsAtom* langAtom = aStyle.StyleFont()->GetLangAtom();
5371 if (!langAtom) {
5372 return false;
5373 }
5374 return nsStyleUtil::MatchesLanguagePrefix(langAtom, u"ja") ||
5375 nsStyleUtil::MatchesLanguagePrefix(langAtom, u"ko") ||
5376 nsStyleUtil::MatchesLanguagePrefix(langAtom, u"mn");
5377}
5378
5379static bool FrameStopsLineDecorationPropagation(nsIFrame* aFrame,
5380 nsCompatibility aCompatMode) {
5381 // In all modes, if we're on an inline-block/table/grid/flex, we're done.
5382 // If we're on a ruby frame other than ruby text container, we
5383 // should continue.
5384 mozilla::StyleDisplay display = aFrame->GetDisplay();
5385 if (!display.IsInlineFlow() &&
5386 (!display.IsRuby() ||
5387 display == mozilla::StyleDisplay::RubyTextContainer) &&
5388 display.IsInlineOutside()) {
5389 return true;
5390 }
5391 // In quirks mode, if we're on an HTML table element, we're done.
5392 if (aCompatMode == eCompatibility_NavQuirks &&
5393 aFrame->GetContent()->IsHTMLElement(nsGkAtoms::table)) {
5394 return true;
5395 }
5396 // If we're on an absolutely-positioned element or a floating
5397 // element, we're done.
5398 if (aFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) {
5399 return true;
5400 }
5401 // If we're an outer <svg> element, which is classified as an atomic
5402 // inline-level element, we're done.
5403 if (aFrame->IsSVGOuterSVGFrame()) {
5404 return true;
5405 }
5406 return false;
5407}
5408
5409void nsTextFrame::GetTextDecorations(
5410 nsPresContext* aPresContext,
5411 nsTextFrame::TextDecorationColorResolution aColorResolution,
5412 nsTextFrame::TextDecorations& aDecorations) {
5413 const nsCompatibility compatMode = aPresContext->CompatibilityMode();
5414
5415 bool useOverride = false;
5416 nscolor overrideColor = NS_RGBA(0, 0, 0, 0)((nscolor)(((0) << 24) | ((0) << 16) | ((0) <<
8) | (0)))
;
5417
5418 bool nearestBlockFound = false;
5419 // Use writing mode of parent frame for orthogonal text frame to work.
5420 // See comment in nsTextFrame::DrawTextRunAndDecorations.
5421 WritingMode wm = GetParent()->GetWritingMode();
5422 bool vertical = wm.IsVertical();
5423
5424 nscoord ascent = GetLogicalBaseline(wm);
5425 // physicalBlockStartOffset represents the offset from our baseline
5426 // to f's physical block start, which is top in horizontal writing
5427 // mode, and left in vertical writing modes, in our coordinate space.
5428 // This physical block start is logical block start in most cases,
5429 // but for vertical-rl, it is logical block end, and consequently in
5430 // that case, it starts from the descent instead of ascent.
5431 nscoord physicalBlockStartOffset =
5432 wm.IsVerticalRL() ? GetSize().width - ascent : ascent;
5433 // baselineOffset represents the offset from our baseline to f's baseline or
5434 // the nearest block's baseline, in our coordinate space, whichever is closest
5435 // during the particular iteration
5436 nscoord baselineOffset = 0;
5437
5438 for (nsIFrame *f = this, *fChild = nullptr; f;
5439 fChild = f, f = nsLayoutUtils::GetParentOrPlaceholderFor(f)) {
5440 ComputedStyle* const context = f->Style();
5441 if (!context->HasTextDecorationLines()) {
5442 break;
5443 }
5444
5445 if (context->GetPseudoType() == PseudoStyleType::Marker &&
5446 (context->StyleList()->mListStylePosition ==
5447 StyleListStylePosition::Outside ||
5448 !context->StyleDisplay()->IsInlineOutsideStyle())) {
5449 // Outside ::marker pseudos, and inside markers that aren't inlines, don't
5450 // have text decorations.
5451 break;
5452 }
5453
5454 const nsStyleTextReset* const styleTextReset = context->StyleTextReset();
5455 StyleTextDecorationLine textDecorations =
5456 styleTextReset->mTextDecorationLine;
5457 bool ignoreSubproperties = false;
5458
5459 auto lineStyle = styleTextReset->mTextDecorationStyle;
5460 if (textDecorations == StyleTextDecorationLine::SPELLING_ERROR ||
5461 textDecorations == StyleTextDecorationLine::GRAMMAR_ERROR) {
5462 nscolor lineColor;
5463 float relativeSize;
5464 useOverride = nsTextPaintStyle::GetSelectionUnderline(
5465 this, nsTextPaintStyle::SelectionStyleIndex::SpellChecker, &lineColor,
5466 &relativeSize, &lineStyle);
5467 if (useOverride) {
5468 // We don't currently have a SelectionStyleIndex::GrammarChecker; for
5469 // now just use SpellChecker and change its color to green.
5470 overrideColor =
5471 textDecorations == StyleTextDecorationLine::SPELLING_ERROR
5472 ? lineColor
5473 : NS_RGBA(0, 128, 0, 255)((nscolor)(((255) << 24) | ((0) << 16) | ((128) <<
8) | (0)))
;
5474 textDecorations = StyleTextDecorationLine::UNDERLINE;
5475 ignoreSubproperties = true;
5476 }
5477 }
5478
5479 if (!useOverride &&
5480 (StyleTextDecorationLine::COLOR_OVERRIDE & textDecorations)) {
5481 // This handles the <a href="blah.html"><font color="green">La
5482 // la la</font></a> case. The link underline should be green.
5483 useOverride = true;
5484 overrideColor = nsLayoutUtils::GetTextColor(
5485 f, &nsStyleTextReset::mTextDecorationColor);
5486 }
5487
5488 nsBlockFrame* fBlock = do_QueryFrame(f);
5489 const bool firstBlock = !nearestBlockFound && fBlock;
5490
5491 // Not updating positions once we hit a parent block is equivalent to
5492 // the CSS 2.1 spec that blocks should propagate decorations down to their
5493 // children (albeit the style should be preserved)
5494 // However, if we're vertically aligned within a block, then we need to
5495 // recover the correct baseline from the line by querying the FrameProperty
5496 // that should be set (see nsLineLayout::VerticalAlignLine).
5497 if (firstBlock) {
5498 // At this point, fChild can't be null since TextFrames can't be blocks
5499 if (!HasDefaultVerticalAlignment(fChild)) {
5500 // Since offset is the offset in the child's coordinate space, we have
5501 // to undo the accumulation to bring the transform out of the block's
5502 // coordinate space
5503 const nscoord lineBaselineOffset =
5504 LazyGetLineBaselineOffset(fChild, fBlock);
5505
5506 baselineOffset = physicalBlockStartOffset - lineBaselineOffset -
5507 (vertical ? fChild->GetNormalPosition().x
5508 : fChild->GetNormalPosition().y);
5509 }
5510 } else if (!nearestBlockFound) {
5511 // offset here is the offset from f's baseline to f's top/left
5512 // boundary. It's descent for vertical-rl, and ascent otherwise.
5513 nscoord offset = wm.IsVerticalRL()
5514 ? f->GetSize().width - f->GetLogicalBaseline(wm)
5515 : f->GetLogicalBaseline(wm);
5516 baselineOffset = physicalBlockStartOffset - offset;
5517 }
5518
5519 nearestBlockFound = nearestBlockFound || firstBlock;
5520 physicalBlockStartOffset +=
5521 vertical ? f->GetNormalPosition().x : f->GetNormalPosition().y;
5522
5523 if (textDecorations) {
5524 nscolor color;
5525 if (useOverride) {
5526 color = overrideColor;
5527 } else if (IsInSVGTextSubtree()) {
5528 // XXX We might want to do something with text-decoration-color when
5529 // painting SVG text, but it's not clear what we should do. We
5530 // at least need SVG text decorations to paint with 'fill' if
5531 // text-decoration-color has its initial value currentColor.
5532 // We could choose to interpret currentColor as "currentFill"
5533 // for SVG text, and have e.g. text-decoration-color:red to
5534 // override the fill paint of the decoration.
5535 color = aColorResolution == eResolvedColors
5536 ? nsLayoutUtils::GetTextColor(f, &nsStyleSVG::mFill)
5537 : NS_SAME_AS_FOREGROUND_COLOR;
5538 } else {
5539 color = nsLayoutUtils::GetTextColor(
5540 f, &nsStyleTextReset::mTextDecorationColor);
5541 }
5542
5543 bool swapUnderlineAndOverline =
5544 wm.IsCentralBaseline() && IsUnderlineRight(*context);
5545 const auto kUnderline = swapUnderlineAndOverline
5546 ? StyleTextDecorationLine::OVERLINE
5547 : StyleTextDecorationLine::UNDERLINE;
5548 const auto kOverline = swapUnderlineAndOverline
5549 ? StyleTextDecorationLine::UNDERLINE
5550 : StyleTextDecorationLine::OVERLINE;
5551
5552 const nsStyleText* const styleText = context->StyleText();
5553 const auto position = ignoreSubproperties
5554 ? StyleTextUnderlinePosition::AUTO
5555 : styleText->mTextUnderlinePosition;
5556 const auto offset = ignoreSubproperties ? LengthPercentageOrAuto::Auto()
5557 : styleText->mTextUnderlineOffset;
5558 const auto thickness = ignoreSubproperties
5559 ? StyleTextDecorationLength::Auto()
5560 : styleTextReset->mTextDecorationThickness;
5561
5562 if (textDecorations & kUnderline) {
5563 aDecorations.mUnderlines.AppendElement(nsTextFrame::LineDecoration(
5564 f, baselineOffset, position, offset, thickness, color, lineStyle,
5565 !ignoreSubproperties));
5566 }
5567 if (textDecorations & kOverline) {
5568 aDecorations.mOverlines.AppendElement(nsTextFrame::LineDecoration(
5569 f, baselineOffset, position, offset, thickness, color, lineStyle,
5570 !ignoreSubproperties));
5571 }
5572 if (textDecorations & StyleTextDecorationLine::LINE_THROUGH) {
5573 aDecorations.mStrikes.AppendElement(nsTextFrame::LineDecoration(
5574 f, baselineOffset, position, offset, thickness, color, lineStyle,
5575 !ignoreSubproperties));
5576 }
5577 }
5578 if (FrameStopsLineDecorationPropagation(f, compatMode)) {
5579 break;
5580 }
5581 }
5582}
5583
5584static float GetInflationForTextDecorations(nsIFrame* aFrame,
5585 nscoord aInflationMinFontSize) {
5586 if (aFrame->IsInSVGTextSubtree()) {
5587 auto* container =
5588 nsLayoutUtils::GetClosestFrameOfType(aFrame, LayoutFrameType::SVGText);
5589 MOZ_ASSERT(container)do { static_assert( mozilla::detail::AssertionConditionType<
decltype(container)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(container))), 0))) { do { } while
(false); MOZ_ReportAssertionFailure("container", "./../../../layout/generic/nsTextFrame.cpp"
, 5589); AnnotateMozCrashReason("MOZ_ASSERT" "(" "container" ")"
); do { MOZ_CrashSequence(__null, 5589); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
5590 return static_cast<SVGTextFrame*>(container)->GetFontSizeScaleFactor();
5591 }
5592 return nsLayoutUtils::FontSizeInflationInner(aFrame, aInflationMinFontSize);
5593}
5594
5595struct EmphasisMarkInfo {
5596 RefPtr<gfxTextRun> textRun;
5597 gfxFloat advance;
5598 gfxFloat baselineOffset;
5599};
5600
5601NS_DECLARE_FRAME_PROPERTY_DELETABLE(EmphasisMarkProperty, EmphasisMarkInfo)static const mozilla::FramePropertyDescriptor<EmphasisMarkInfo
>* EmphasisMarkProperty() { static const auto descriptor =
mozilla::FramePropertyDescriptor<EmphasisMarkInfo>::NewWithDestructor
<DeleteValue>(); return &descriptor; }
5602
5603static void ComputeTextEmphasisStyleString(const StyleTextEmphasisStyle& aStyle,
5604 nsAString& aOut) {
5605 MOZ_ASSERT(!aStyle.IsNone())do { static_assert( mozilla::detail::AssertionConditionType<
decltype(!aStyle.IsNone())>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(!aStyle.IsNone()))), 0))) { do
{ } while (false); MOZ_ReportAssertionFailure("!aStyle.IsNone()"
, "./../../../layout/generic/nsTextFrame.cpp", 5605); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "!aStyle.IsNone()" ")"); do { MOZ_CrashSequence
(__null, 5605); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
5606 if (aStyle.IsString()) {
5607 nsDependentCSubstring string = aStyle.AsString().AsString();
5608 AppendUTF8toUTF16(string, aOut);
5609 return;
5610 }
5611 const auto& keyword = aStyle.AsKeyword();
5612 const bool fill = keyword.fill == StyleTextEmphasisFillMode::Filled;
5613 switch (keyword.shape) {
5614 case StyleTextEmphasisShapeKeyword::Dot:
5615 return aOut.AppendLiteral(fill ? u"\u2022" : u"\u25e6");
5616 case StyleTextEmphasisShapeKeyword::Circle:
5617 return aOut.AppendLiteral(fill ? u"\u25cf" : u"\u25cb");
5618 case StyleTextEmphasisShapeKeyword::DoubleCircle:
5619 return aOut.AppendLiteral(fill ? u"\u25c9" : u"\u25ce");
5620 case StyleTextEmphasisShapeKeyword::Triangle:
5621 return aOut.AppendLiteral(fill ? u"\u25b2" : u"\u25b3");
5622 case StyleTextEmphasisShapeKeyword::Sesame:
5623 return aOut.AppendLiteral(fill ? u"\ufe45" : u"\ufe46");
5624 default:
5625 MOZ_ASSERT_UNREACHABLE("Unknown emphasis style shape")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(false)>::isValid, "invalid assertion condition");
if ((__builtin_expect(!!(!(!!(false))), 0))) { do { } while (
false); MOZ_ReportAssertionFailure("false" " (" "MOZ_ASSERT_UNREACHABLE: "
"Unknown emphasis style shape" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 5625); AnnotateMozCrashReason("MOZ_ASSERT" "(" "false" ") ("
"MOZ_ASSERT_UNREACHABLE: " "Unknown emphasis style shape" ")"
); do { MOZ_CrashSequence(__null, 5625); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
5626 }
5627}
5628
5629static already_AddRefed<gfxTextRun> GenerateTextRunForEmphasisMarks(
5630 nsTextFrame* aFrame, gfxFontGroup* aFontGroup,
5631 ComputedStyle* aComputedStyle, const nsStyleText* aStyleText) {
5632 nsAutoString string;
5633 ComputeTextEmphasisStyleString(aStyleText->mTextEmphasisStyle, string);
5634
5635 RefPtr<DrawTarget> dt = CreateReferenceDrawTarget(aFrame);
5636 auto appUnitsPerDevUnit = aFrame->PresContext()->AppUnitsPerDevPixel();
5637 gfx::ShapedTextFlags flags =
5638 nsLayoutUtils::GetTextRunOrientFlagsForStyle(aComputedStyle);
5639 if (flags == gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED) {
5640 // The emphasis marks should always be rendered upright per spec.
5641 flags = gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT;
5642 }
5643 return aFontGroup->MakeTextRun<char16_t>(string.get(), string.Length(), dt,
5644 appUnitsPerDevUnit, flags,
5645 nsTextFrameUtils::Flags(), nullptr);
5646}
5647
5648static nsRubyFrame* FindFurthestInlineRubyAncestor(nsTextFrame* aFrame) {
5649 nsRubyFrame* rubyFrame = nullptr;
5650 for (nsIFrame* frame = aFrame->GetParent();
5651 frame && frame->IsLineParticipant(); frame = frame->GetParent()) {
5652 if (frame->IsRubyFrame()) {
5653 rubyFrame = static_cast<nsRubyFrame*>(frame);
5654 }
5655 }
5656 return rubyFrame;
5657}
5658
5659nsRect nsTextFrame::UpdateTextEmphasis(WritingMode aWM,
5660 PropertyProvider& aProvider) {
5661 const nsStyleText* styleText = StyleText();
5662 if (!styleText->HasEffectiveTextEmphasis()) {
5663 RemoveProperty(EmphasisMarkProperty());
5664 return nsRect();
5665 }
5666
5667 ComputedStyle* computedStyle = Style();
5668 bool isTextCombined = computedStyle->IsTextCombined();
5669 if (isTextCombined) {
5670 computedStyle = GetParent()->Style();
5671 }
5672 RefPtr<nsFontMetrics> fm = nsLayoutUtils::GetFontMetricsOfEmphasisMarks(
5673 computedStyle, PresContext(), GetFontSizeInflation());
5674 EmphasisMarkInfo* info = new EmphasisMarkInfo;
5675 info->textRun = GenerateTextRunForEmphasisMarks(
5676 this, fm->GetThebesFontGroup(), computedStyle, styleText);
5677 info->advance = info->textRun->GetAdvanceWidth();
5678
5679 bool normalizeRubyMetrics = PresContext()->NormalizeRubyMetrics();
5680 float rubyMetricsFactor =
5681 normalizeRubyMetrics ? PresContext()->RubyPositioningFactor() : 0.0f;
5682
5683 // Calculate the baseline offset
5684 LogicalSide side =
5685 styleText->TextEmphasisSide(aWM, StyleFont()->GetLangAtom());
5686 LogicalSize frameSize = GetLogicalSize(aWM);
5687 // The overflow rect is inflated in the inline direction by half
5688 // advance of the emphasis mark on each side, so that even if a mark
5689 // is drawn for a zero-width character, it won't be clipped.
5690 LogicalRect overflowRect(
5691 aWM, -info->advance / 2, /* BStart to be computed below */ 0,
5692 frameSize.ISize(aWM) + info->advance,
5693 normalizeRubyMetrics
5694 ? rubyMetricsFactor * (fm->TrimmedAscent() + fm->TrimmedDescent())
5695 : fm->MaxAscent() + fm->MaxDescent());
5696 RefPtr<nsFontMetrics> baseFontMetrics =
5697 isTextCombined
5698 ? nsLayoutUtils::GetInflatedFontMetricsForFrame(GetParent())
5699 : do_AddRef(aProvider.GetFontMetrics());
5700 // When the writing mode is vertical-lr the line is inverted, and thus
5701 // the ascent and descent are swapped.
5702 bool startSideOrInvertedLine =
5703 (side == LogicalSide::BStart) != aWM.IsLineInverted();
5704 nscoord absOffset;
5705 if (normalizeRubyMetrics) {
5706 absOffset = startSideOrInvertedLine
5707 ? baseFontMetrics->TrimmedAscent() + fm->TrimmedDescent()
5708 : baseFontMetrics->TrimmedDescent() + fm->TrimmedAscent();
5709 absOffset *= rubyMetricsFactor;
5710 } else {
5711 absOffset = startSideOrInvertedLine
5712 ? baseFontMetrics->MaxAscent() + fm->MaxDescent()
5713 : baseFontMetrics->MaxDescent() + fm->MaxAscent();
5714 }
5715 RubyBlockLeadings leadings;
5716 if (nsRubyFrame* ruby = FindFurthestInlineRubyAncestor(this)) {
5717 leadings = ruby->GetBlockLeadings();
5718 if (normalizeRubyMetrics) {
5719 // Adjust absOffset to account for any ruby annotations that effectively
5720 // added to the trimmed height of the base text.
5721 auto [ascent, descent] = ruby->RubyMetrics(rubyMetricsFactor);
5722 absOffset = std::max(absOffset, side == LogicalSide::BStart
5723 ? ascent + fm->TrimmedDescent()
5724 : descent + fm->TrimmedAscent());
5725 }
5726 }
5727 if (side == LogicalSide::BStart) {
5728 info->baselineOffset =
5729 normalizeRubyMetrics ? -absOffset : -absOffset - leadings.mStart;
5730 overflowRect.BStart(aWM) = -overflowRect.BSize(aWM) - leadings.mStart;
5731 } else {
5732 MOZ_ASSERT(side == LogicalSide::BEnd)do { static_assert( mozilla::detail::AssertionConditionType<
decltype(side == LogicalSide::BEnd)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(side == LogicalSide::BEnd)))
, 0))) { do { } while (false); MOZ_ReportAssertionFailure("side == LogicalSide::BEnd"
, "./../../../layout/generic/nsTextFrame.cpp", 5732); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "side == LogicalSide::BEnd" ")"); do { MOZ_CrashSequence
(__null, 5732); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
5733 info->baselineOffset =
5734 normalizeRubyMetrics ? absOffset : absOffset + leadings.mEnd;
5735 overflowRect.BStart(aWM) = frameSize.BSize(aWM) + leadings.mEnd;
5736 }
5737 // If text combined, fix the gap between the text frame and its parent.
5738 if (isTextCombined) {
5739 nscoord height =
5740 normalizeRubyMetrics
5741 ? rubyMetricsFactor * (baseFontMetrics->TrimmedAscent() +
5742 baseFontMetrics->TrimmedDescent())
5743 : baseFontMetrics->MaxHeight();
5744 nscoord gap = (height - frameSize.BSize(aWM)) / 2;
5745 overflowRect.BStart(aWM) += gap * (side == LogicalSide::BStart ? -1 : 1);
5746 }
5747
5748 SetProperty(EmphasisMarkProperty(), info);
5749 return overflowRect.GetPhysicalRect(aWM, frameSize.GetPhysicalSize(aWM));
5750}
5751
5752// helper function for implementing text-decoration-thickness
5753// https://drafts.csswg.org/css-text-decor-4/#text-decoration-width-property
5754// Returns the thickness in device pixels.
5755static gfxFloat ComputeDecorationLineThickness(
5756 const StyleTextDecorationLength& aThickness, const gfxFloat aAutoValue,
5757 const gfxFont::Metrics& aFontMetrics, const gfxFloat aAppUnitsPerDevPixel,
5758 const nsIFrame* aFrame) {
5759 if (aThickness.IsAuto()) {
5760 return widget::ThemeDrawing::SnapBorderWidth(aAutoValue);
5761 }
5762 if (aThickness.IsFromFont()) {
5763 return widget::ThemeDrawing::SnapBorderWidth(aFontMetrics.underlineSize);
5764 }
5765 auto em = [&] { return aFrame->StyleFont()->mSize.ToAppUnits(); };
5766 return widget::ThemeDrawing::SnapBorderWidth(
5767 aThickness.AsLengthPercentage().Resolve(em) / aAppUnitsPerDevPixel);
5768}
5769
5770// Helper function for implementing text-underline-offset and -position
5771// https://drafts.csswg.org/css-text-decor-4/#underline-offset
5772// Returns the offset in device pixels.
5773static gfxFloat ComputeDecorationLineOffset(
5774 StyleTextDecorationLine aLineType,
5775 const StyleTextUnderlinePosition& aPosition,
5776 const LengthPercentageOrAuto& aOffset, const gfxFont::Metrics& aFontMetrics,
5777 const gfxFloat aAppUnitsPerDevPixel, const nsIFrame* aFrame,
5778 bool aIsCentralBaseline, bool aSwappedUnderline) {
5779 // Em value to use if we need to resolve a percentage length.
5780 auto em = [&] { return aFrame->StyleFont()->mSize.ToAppUnits(); };
5781 // If we're in vertical-upright typographic mode, we need to compute the
5782 // offset of the decoration line from the default central baseline.
5783 if (aIsCentralBaseline) {
5784 // Line-through simply goes at the (central) baseline.
5785 if (aLineType == StyleTextDecorationLine::LINE_THROUGH) {
5786 return 0;
5787 }
5788
5789 // Compute "zero position" for the under- or overline.
5790 gfxFloat zeroPos = 0.5 * aFontMetrics.emHeight;
5791
5792 // aOffset applies to underline only; for overline (or offset:auto) we use
5793 // a somewhat arbitrary offset of half the font's (horziontal-mode) value
5794 // for underline-offset, to get a little bit of separation between glyph
5795 // edges and the line in typical cases.
5796 // If we have swapped under-/overlines for text-underline-position:right,
5797 // we need to take account of this to determine which decoration lines are
5798 // "real" underlines which should respect the text-underline-* values.
5799 bool isUnderline =
5800 (aLineType == StyleTextDecorationLine::UNDERLINE) != aSwappedUnderline;
5801 gfxFloat offset =
5802 isUnderline && !aOffset.IsAuto()
5803 ? aOffset.AsLengthPercentage().Resolve(em) / aAppUnitsPerDevPixel
5804 : aFontMetrics.underlineOffset * -0.5;
5805
5806 // Direction of the decoration line's offset from the central baseline.
5807 gfxFloat dir = aLineType == StyleTextDecorationLine::OVERLINE ? 1.0 : -1.0;
5808 return dir * (zeroPos + offset);
5809 }
5810
5811 // Compute line offset for horizontal typographic mode.
5812 if (aLineType == StyleTextDecorationLine::UNDERLINE) {
5813 if (aPosition.IsFromFont()) {
5814 gfxFloat zeroPos = aFontMetrics.underlineOffset;
5815 gfxFloat offset =
5816 aOffset.IsAuto()
5817 ? 0
5818 : aOffset.AsLengthPercentage().Resolve(em) / aAppUnitsPerDevPixel;
5819 return zeroPos - offset;
5820 }
5821
5822 if (aPosition.IsUnder()) {
5823 gfxFloat zeroPos = -aFontMetrics.maxDescent;
5824 gfxFloat offset =
5825 aOffset.IsAuto()
5826 ? -0.5 * aFontMetrics.underlineOffset
5827 : aOffset.AsLengthPercentage().Resolve(em) / aAppUnitsPerDevPixel;
5828 return zeroPos - offset;
5829 }
5830
5831 // text-underline-position must be 'auto', so zero position is the
5832 // baseline and 'auto' offset will apply the font's underline-offset.
5833 //
5834 // If offset is `auto`, we clamp the offset (in horizontal typographic mode)
5835 // to a minimum of 1/16 em (equivalent to 1px at font-size 16px) to mitigate
5836 // skip-ink issues with fonts that leave the underlineOffset field as zero.
5837 MOZ_ASSERT(aPosition.IsAuto())do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aPosition.IsAuto())>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(aPosition.IsAuto()))), 0))) {
do { } while (false); MOZ_ReportAssertionFailure("aPosition.IsAuto()"
, "./../../../layout/generic/nsTextFrame.cpp", 5837); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "aPosition.IsAuto()" ")"); do { MOZ_CrashSequence
(__null, 5837); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
5838 return aOffset.IsAuto() ? std::min(aFontMetrics.underlineOffset,
5839 -aFontMetrics.emHeight / 16.0)
5840 : -aOffset.AsLengthPercentage().Resolve(em) /
5841 aAppUnitsPerDevPixel;
5842 }
5843
5844 if (aLineType == StyleTextDecorationLine::OVERLINE) {
5845 return aFontMetrics.maxAscent;
5846 }
5847
5848 if (aLineType == StyleTextDecorationLine::LINE_THROUGH) {
5849 return aFontMetrics.strikeoutOffset;
5850 }
5851
5852 MOZ_ASSERT_UNREACHABLE("unknown decoration line type")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(false)>::isValid, "invalid assertion condition");
if ((__builtin_expect(!!(!(!!(false))), 0))) { do { } while (
false); MOZ_ReportAssertionFailure("false" " (" "MOZ_ASSERT_UNREACHABLE: "
"unknown decoration line type" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 5852); AnnotateMozCrashReason("MOZ_ASSERT" "(" "false" ") ("
"MOZ_ASSERT_UNREACHABLE: " "unknown decoration line type" ")"
); do { MOZ_CrashSequence(__null, 5852); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
5853 return 0;
5854}
5855
5856// Per the spec resolution, if it's split (i.e. box-decoration-break:slice) we
5857// count the sum width (or height) of the fragments, but if it is with clone,
5858// each fragment is considered individually.
5859// https://github.com/w3c/csswg-drafts/issues/8403
5860static nscoord TextDecorationInsetPercentageBasis(const nsTextFrame* aFrame,
5861 const nsIFrame* aDecFrame) {
5862 // Note: We use the same way to get the writing mode in
5863 // ComputeDecorationInset().
5864 const WritingMode wm = aDecFrame->IsInlineFrame()
5865 ? aDecFrame->GetWritingMode()
5866 : FindLineContainer(aFrame)->GetWritingMode();
5867 auto getLength = [wm](const nsIFrame* aFrame) {
5868 return wm.IsVertical() ? aFrame->GetRectRelativeToSelf().height
5869 : aFrame->GetRectRelativeToSelf().width;
5870 };
5871 if (aDecFrame->StyleBorder()->mBoxDecorationBreak ==
5872 StyleBoxDecorationBreak::Clone) {
5873 return getLength(aFrame);
5874 }
5875
5876 // FIXME: This is not great because we may calculate this multiple times if
5877 // there are a lot of fragments. The better way is to move this calculation
5878 // out of ComputeDecorationInset().
5879 //
5880 // FIXME: Bug 2050962. We have to add the extra handling if we break the text
5881 // by `<br>`. It breaks the continuations and it may look like multiple
5882 // independent text frames, so we cannot use continuation to find all
5883 // fragments.
5884 nscoord sum = getLength(aFrame);
5885 const nsIFrame* prev = aFrame;
5886 while ((prev = nsLayoutUtils::GetPrevContinuationOrIBSplitSibling(prev))) {
5887 sum += getLength(prev);
5888 }
5889 const nsIFrame* next = aFrame;
5890 while ((next = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(next))) {
5891 sum += getLength(next);
5892 }
5893 return sum;
5894}
5895
5896// Helper to determine decoration inset.
5897// Returns false if the inset would cut off the decoration entirely.
5898// If aOnlyExtend is true, this will only consider cases with negative inset
5899// (i.e. the line will actually be extended beyond the normal length).
5900static bool ComputeDecorationInset(
5901 nsTextFrame* aFrame, const nsPresContext* aPresCtx,
5902 const nsIFrame* aDecFrame, const gfxFont::Metrics& aMetrics,
5903 nsCSSRendering::DecorationRectParams& aParams, bool aOnlyExtend = false) {
5904 aParams.insetLeft = 0.0;
5905 aParams.insetRight = 0.0;
5906
5907 // Find the decoration-line inset values for this frame.
5908 const StyleTextDecorationInset& cssInset =
5909 aDecFrame->StyleTextReset()->mTextDecorationInset;
5910 nscoord insetLeft, insetRight;
5911 if (cssInset.IsAuto()) {
5912 const float emSize =
5913 aMetrics.emHeight / aPresCtx->CSSToDevPixelScale().scale;
5914 const nscoord autoDecorationInset = nsPresContext::CSSPixelsToAppUnits(
5915 Gecko_CalcAutoDecorationInset(emSize));
5916 insetLeft = autoDecorationInset;
5917 insetRight = autoDecorationInset;
5918 } else {
5919 MOZ_ASSERT(cssInset.IsLengthPercentage(),do { static_assert( mozilla::detail::AssertionConditionType<
decltype(cssInset.IsLengthPercentage())>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(cssInset.IsLengthPercentage(
)))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("cssInset.IsLengthPercentage()" " (" "Impossible text-decoration-inset"
")", "./../../../layout/generic/nsTextFrame.cpp", 5920); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "cssInset.IsLengthPercentage()" ") (" "Impossible text-decoration-inset"
")"); do { MOZ_CrashSequence(__null, 5920); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
5920 "Impossible text-decoration-inset")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(cssInset.IsLengthPercentage())>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(cssInset.IsLengthPercentage(
)))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("cssInset.IsLengthPercentage()" " (" "Impossible text-decoration-inset"
")", "./../../../layout/generic/nsTextFrame.cpp", 5920); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "cssInset.IsLengthPercentage()" ") (" "Impossible text-decoration-inset"
")"); do { MOZ_CrashSequence(__null, 5920); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
5921 const auto& inset = cssInset.AsLengthPercentage();
5922 if (inset.start.IsDefinitelyZero() && inset.end.IsDefinitelyZero()) {
5923 // We can avoid doing the geometric calculations below, potentially
5924 // walking up and back down the frame tree, and walking continuations.
5925 return true;
5926 }
5927
5928 if (inset.start.IsLength() && inset.end.IsLength()) {
5929 insetLeft = inset.start.AsLength().ToAppUnits();
5930 insetRight = inset.end.AsLength().ToAppUnits();
5931 } else {
5932 const nscoord basis =
5933 TextDecorationInsetPercentageBasis(aFrame, aDecFrame);
5934 insetLeft = inset.start.Resolve(basis);
5935 insetRight = inset.end.Resolve(basis);
5936 if (!insetLeft && !insetRight) {
5937 // Same as our previous check. If both are zero, we can bail out.
5938 return true;
5939 }
5940 }
5941 }
5942
5943 // If we only care about extended lines (for UnionAdditionalOverflow),
5944 // we can bail out if neither inset value is negative.
5945 if (aOnlyExtend && insetLeft >= 0 && insetRight >= 0) {
5946 return true;
5947 }
5948
5949 // The rect of the decorating box (if an inline) or of the current line (if
5950 // the decoration is propagated from a block ancestor). We will need to
5951 // compare this with the rect of the current frame, which may be only a
5952 // sub-range of the entire decorated range.
5953 nsRect decRect;
5954
5955 // The container of the decoration, or the fragment of it on this line.
5956 const nsIFrame* decContainer;
5957
5958 // If the decorating frame is an inline frame, we can use it as the
5959 // reference frame for measurements.
5960 // If the decorating frame is not inline, then we will need to consider
5961 // text indentation and calculate geometry using line boxes.
5962 WritingMode wm;
5963 if (aDecFrame->IsInlineFrame()) {
5964 decRect = aDecFrame->GetContentRectRelativeToSelf();
5965 decContainer = aDecFrame;
5966 wm = aDecFrame->GetWritingMode();
5967 } else {
5968 nsIFrame* const lineContainer = FindLineContainer(aFrame);
5969 wm = lineContainer->GetWritingMode();
5970 if (nsILineIterator* const iter = lineContainer->GetLineIterator()) {
5971 const int32_t lineNum = GetFrameLineNum(aFrame, iter);
5972 const nsILineIterator::LineInfo lineInfo =
5973 iter->GetLine(lineNum).unwrap();
5974 decRect = lineInfo.mLineBounds;
5975
5976 // Account for text-indent, which will push text frames into the line box.
5977 const StyleTextIndent& textIndent = aFrame->StyleText()->mTextIndent;
5978 if (!textIndent.length.IsDefinitelyZero()) {
5979 bool isFirstLineOrAfterHardBreak = true;
5980 if (lineNum > 0 && !textIndent.each_line) {
5981 isFirstLineOrAfterHardBreak = false;
5982 } else if (nsBlockFrame* prevBlock =
5983 do_QueryFrame(lineContainer->GetPrevInFlow())) {
5984 if (!(textIndent.each_line &&
5985 (prevBlock->Lines().empty() ||
5986 !prevBlock->LinesEnd().prev()->IsLineWrapped()))) {
5987 isFirstLineOrAfterHardBreak = false;
5988 }
5989 }
5990 if (isFirstLineOrAfterHardBreak != textIndent.hanging) {
5991 // Determine which side to shrink.
5992 const Side side = wm.PhysicalSide(LogicalSide::IStart);
5993 // Calculate the text indent, and shrink the line box by this amount
5994 // to account for the indent size at the start of the line.
5995 const nscoord basis = lineContainer->GetLogicalSize(wm).ISize(wm);
5996 nsMargin indentMargin;
5997 indentMargin.Side(side) = textIndent.length.Resolve(basis);
5998 decRect.Deflate(indentMargin);
5999 }
6000 }
6001
6002 // We can't allow a block frame to retain a line iterator if we're
6003 // currently in reflow, as it will become invalid as the line list is
6004 // reflowed.
6005 if (lineContainer->HasAnyStateBits(NS_FRAME_IN_REFLOW) &&
6006 lineContainer->IsBlockFrameOrSubclass()) {
6007 static_cast<nsBlockFrame*>(lineContainer)->ClearLineIterator();
6008 }
6009 } else {
6010 // Not a block or similar container with multiple lines; just use the
6011 // content rect directly.
6012 decRect = lineContainer->GetContentRectRelativeToSelf();
6013 }
6014
6015 decContainer = lineContainer;
6016 }
6017
6018 // The rect of the current frame, mapped to the same coordinate space as
6019 // decRect so that we can compare their edges.
6020 const nsRect frameRect =
6021 aFrame->GetRectRelativeToSelf() + aFrame->GetOffsetTo(decContainer);
6022
6023 // The nominal size of the decoration (prior to insets being applied) is
6024 // reduced by any margin, border, and padding present on frames intervening
6025 // between aFrame and decContainer.
6026 for (const nsIFrame* parent = aFrame->GetParent(); parent != decContainer;
6027 parent = parent->GetParent()) {
6028 decRect.Deflate(parent->GetUsedMargin());
6029 decRect.Deflate(parent->GetUsedBorderAndPadding());
6030 }
6031
6032 // Find the margin of the of this frame inside its container.
6033 nscoord marginLeft, marginRight, frameSize;
6034 const nsMargin difference = decRect - frameRect;
6035 if (wm.IsVertical()) {
6036 marginLeft = difference.top;
6037 marginRight = difference.bottom;
6038 frameSize = frameRect.height;
6039 } else {
6040 marginLeft = difference.left;
6041 marginRight = difference.right;
6042 frameSize = frameRect.width;
6043 }
6044
6045 const bool cloneDecBreak = aDecFrame->StyleBorder()->mBoxDecorationBreak ==
6046 StyleBoxDecorationBreak::Clone;
6047 // TODO alaskanemily: This will not correctly account for the case that the
6048 // continuations are bidi continuations.
6049 bool applyLeft = cloneDecBreak || (!aFrame->GetPrevContinuation() &&
6050 !aDecFrame->GetPrevContinuation());
6051 bool applyRight = cloneDecBreak || (!aFrame->GetNextContinuation() &&
6052 !aDecFrame->GetNextContinuation());
6053 if (wm.IsInlineReversed()) {
6054 std::swap(insetLeft, insetRight);
6055 std::swap(applyLeft, applyRight);
6056 }
6057 if (applyLeft) {
6058 insetLeft -= marginLeft;
6059 } else {
6060 insetLeft = 0;
6061 }
6062 if (applyRight) {
6063 insetRight -= marginRight;
6064 } else {
6065 insetRight = 0;
6066 }
6067
6068 if (insetLeft + insetRight >= frameSize) {
6069 // This frame does not contain the decoration at all.
6070 return false;
6071 }
6072 // TODO alaskanemily: We currently determine if we should have a negative
6073 // inset value by checking if we are at the edge of frame from which the
6074 // decloration comes from.
6075 //
6076 // This is not absolutely correct, there could in theory be a zero-width
6077 // frame before/after this frame, and we will draw the decoration extension
6078 // twice (causing a visible inaccuracy for semi-transparent decorations).
6079 //
6080 // I am unsure if it's possible that the first/last frame might be inset
6081 // for some reason, as well, in which case we will not draw the outset
6082 // decorations.
6083 if (insetLeft > 0 || marginLeft == 0) {
6084 aParams.insetLeft = aPresCtx->AppUnitsToFloatDevPixels(insetLeft);
6085 }
6086 if (insetRight > 0 || marginRight == 0) {
6087 aParams.insetRight = aPresCtx->AppUnitsToFloatDevPixels(insetRight);
6088 }
6089 return true;
6090}
6091
6092void nsTextFrame::UnionAdditionalOverflow(nsPresContext* aPresContext,
6093 nsIFrame* aBlock,
6094 PropertyProvider& aProvider,
6095 nsRect* aInkOverflowRect,
6096 bool aIncludeTextDecorations,
6097 bool aIncludeShadows) {
6098 const WritingMode wm = GetWritingMode();
6099 bool verticalRun = mTextRun->IsVertical();
6100 const gfxFloat appUnitsPerDevUnit = aPresContext->AppUnitsPerDevPixel();
6101
6102 if (IsFloatingFirstLetterChild()) {
6103 bool inverted = wm.IsLineInverted();
6104 // The underline/overline drawable area must be contained in the overflow
6105 // rect when this is in floating first letter frame at *both* modes.
6106 // In this case, aBlock is the ::first-letter frame.
6107 auto decorationStyle =
6108 aBlock->Style()->StyleTextReset()->mTextDecorationStyle;
6109 // If the style is none, let's include decoration line rect as solid style
6110 // since changing the style from none to solid/dotted/dashed doesn't cause
6111 // reflow.
6112 if (decorationStyle == StyleTextDecorationStyle::None) {
6113 decorationStyle = StyleTextDecorationStyle::Solid;
6114 }
6115 nsCSSRendering::DecorationRectParams params;
6116 bool useVerticalMetrics = verticalRun && mTextRun->UseCenterBaseline();
6117 nsFontMetrics* fontMetrics = aProvider.GetFontMetrics();
6118 RefPtr<gfxFont> font =
6119 fontMetrics->GetThebesFontGroup()->GetFirstValidFont();
6120 const gfxFont::Metrics& metrics =
6121 font->GetMetrics(useVerticalMetrics ? nsFontMetrics::eVertical
6122 : nsFontMetrics::eHorizontal);
6123
6124 params.defaultLineThickness = metrics.underlineSize;
6125 params.lineSize.height = ComputeDecorationLineThickness(
6126 aBlock->Style()->StyleTextReset()->mTextDecorationThickness,
6127 params.defaultLineThickness, metrics, appUnitsPerDevUnit, this);
6128
6129 const auto* styleText = aBlock->StyleText();
6130 bool swapUnderline =
6131 wm.IsCentralBaseline() && IsUnderlineRight(*aBlock->Style());
6132 params.offset = ComputeDecorationLineOffset(
6133 StyleTextDecorationLine::UNDERLINE, styleText->mTextUnderlinePosition,
6134 styleText->mTextUnderlineOffset, metrics, appUnitsPerDevUnit, this,
6135 wm.IsCentralBaseline(), swapUnderline);
6136
6137 nscoord maxAscent =
6138 inverted ? fontMetrics->MaxDescent() : fontMetrics->MaxAscent();
6139
6140 Float gfxWidth =
6141 (verticalRun ? aInkOverflowRect->height : aInkOverflowRect->width) /
6142 appUnitsPerDevUnit;
6143 params.lineSize.width = gfxWidth;
6144 params.ascent = gfxFloat(mAscent) / appUnitsPerDevUnit;
6145 params.style = decorationStyle;
6146 params.vertical = verticalRun;
6147 params.sidewaysLeft = mTextRun->IsSidewaysLeft();
6148 params.decoration = StyleTextDecorationLine::UNDERLINE;
6149 nsRect underlineRect =
6150 nsCSSRendering::GetTextDecorationRect(aPresContext, params);
6151
6152 // TODO(jfkthame):
6153 // Should we actually be calling ComputeDecorationLineOffset again here?
6154 params.offset = maxAscent / appUnitsPerDevUnit;
6155 params.decoration = StyleTextDecorationLine::OVERLINE;
6156 nsRect overlineRect =
6157 nsCSSRendering::GetTextDecorationRect(aPresContext, params);
6158
6159 aInkOverflowRect->UnionRect(*aInkOverflowRect, underlineRect);
6160 aInkOverflowRect->UnionRect(*aInkOverflowRect, overlineRect);
6161
6162 // XXX If strikeoutSize is much thicker than the underlineSize, it may
6163 // cause overflowing from the overflow rect. However, such case
6164 // isn't realistic, we don't need to compute it now.
6165 }
6166 if (aIncludeTextDecorations) {
6167 // Use writing mode of parent frame for orthogonal text frame to
6168 // work. See comment in nsTextFrame::DrawTextRunAndDecorations.
6169 WritingMode parentWM = GetParent()->GetWritingMode();
6170 bool verticalDec = parentWM.IsVertical();
6171 bool useVerticalMetrics =
6172 verticalDec != verticalRun
6173 ? verticalDec
6174 : verticalRun && mTextRun->UseCenterBaseline();
6175
6176 // Since CSS 2.1 requires that text-decoration defined on ancestors maintain
6177 // style and position, they can be drawn at virtually any y-offset, so
6178 // maxima and minima are required to reliably generate the rectangle for
6179 // them
6180 TextDecorations textDecs;
6181 GetTextDecorations(aPresContext, eResolvedColors, textDecs);
6182 if (textDecs.HasDecorationLines()) {
6183 nscoord inflationMinFontSize =
6184 nsLayoutUtils::InflationMinFontSizeFor(aBlock);
6185
6186 const nscoord measure = verticalDec ? GetSize().height : GetSize().width;
6187 gfxFloat gfxWidth = measure / appUnitsPerDevUnit;
6188 gfxFloat ascent =
6189 gfxFloat(GetLogicalBaseline(parentWM)) / appUnitsPerDevUnit;
6190 nscoord frameBStart = 0;
6191 if (parentWM.IsVerticalRL()) {
6192 frameBStart = GetSize().width;
6193 ascent = -ascent;
6194 }
6195
6196 nsCSSRendering::DecorationRectParams params;
6197 params.lineSize = Size(gfxWidth, 0);
6198 params.ascent = ascent;
6199 params.vertical = verticalDec;
6200 params.sidewaysLeft = mTextRun->IsSidewaysLeft();
6201
6202 typedef gfxFont::Metrics Metrics;
6203 auto accumulateDecorationRect =
6204 [&](const LineDecoration& dec, gfxFloat Metrics::* lineSize,
6205 mozilla::StyleTextDecorationLine lineType) {
6206 params.style = dec.mStyle;
6207 // If the style is solid, let's include decoration line rect of
6208 // solid style since changing the style from none to
6209 // solid/dotted/dashed doesn't cause reflow.
6210 if (params.style == StyleTextDecorationStyle::None) {
6211 params.style = StyleTextDecorationStyle::Solid;
6212 }
6213
6214 float inflation = GetInflationForTextDecorations(
6215 dec.mFrame, inflationMinFontSize);
6216 const Metrics metrics =
6217 GetFirstFontMetrics(GetFontGroupForFrame(dec.mFrame, inflation),
6218 useVerticalMetrics);
6219
6220 params.defaultLineThickness = metrics.*lineSize;
6221 params.lineSize.height = ComputeDecorationLineThickness(
6222 dec.mTextDecorationThickness, params.defaultLineThickness,
6223 metrics, appUnitsPerDevUnit, this);
6224
6225 bool swapUnderline =
6226 parentWM.IsCentralBaseline() && IsUnderlineRight(*Style());
6227 params.offset = ComputeDecorationLineOffset(
6228 lineType, dec.mTextUnderlinePosition, dec.mTextUnderlineOffset,
6229 metrics, appUnitsPerDevUnit, this, parentWM.IsCentralBaseline(),
6230 swapUnderline);
6231
6232 if (!ComputeDecorationInset(this, aPresContext, dec.mFrame, metrics,
6233 params, /* aOnlyExtend = */ true)) {
6234 return;
6235 }
6236
6237 nsRect decorationRect =
6238 nsCSSRendering::GetTextDecorationRect(aPresContext, params) +
6239 (verticalDec ? nsPoint(frameBStart - dec.mBaselineOffset, 0)
6240 : nsPoint(0, -dec.mBaselineOffset));
6241
6242 aInkOverflowRect->UnionRect(*aInkOverflowRect, decorationRect);
6243 };
6244
6245 // Below we loop through all text decorations and compute the rectangle
6246 // containing all of them, in this frame's coordinate space
6247 params.decoration = StyleTextDecorationLine::UNDERLINE;
6248 for (const LineDecoration& dec : textDecs.mUnderlines) {
6249 accumulateDecorationRect(dec, &Metrics::underlineSize,
6250 params.decoration);
6251 }
6252 params.decoration = StyleTextDecorationLine::OVERLINE;
6253 for (const LineDecoration& dec : textDecs.mOverlines) {
6254 accumulateDecorationRect(dec, &Metrics::underlineSize,
6255 params.decoration);
6256 }
6257 params.decoration = StyleTextDecorationLine::LINE_THROUGH;
6258 for (const LineDecoration& dec : textDecs.mStrikes) {
6259 accumulateDecorationRect(dec, &Metrics::strikeoutSize,
6260 params.decoration);
6261 }
6262 }
6263
6264 aInkOverflowRect->UnionRect(*aInkOverflowRect,
6265 UpdateTextEmphasis(parentWM, aProvider));
6266 }
6267
6268 // text-stroke overflows: add half of text-stroke-width on all sides
6269 nscoord textStrokeWidth = StyleText()->mWebkitTextStrokeWidth;
6270 if (textStrokeWidth > 0) {
6271 // Inflate rect by stroke-width/2; we add an extra pixel to allow for
6272 // antialiasing, rounding errors, etc.
6273 nsRect strokeRect = *aInkOverflowRect;
6274 strokeRect.Inflate(textStrokeWidth / 2 + appUnitsPerDevUnit);
6275 aInkOverflowRect->UnionRect(*aInkOverflowRect, strokeRect);
6276 }
6277
6278 // Text-shadow overflows
6279 if (aIncludeShadows) {
6280 *aInkOverflowRect =
6281 nsLayoutUtils::GetTextShadowRectsUnion(*aInkOverflowRect, this);
6282 }
6283
6284 // When this frame is not selected, the text-decoration area must be in
6285 // frame bounds.
6286 if (!IsSelected() ||
6287 !CombineSelectionUnderlineRect(aPresContext, *aInkOverflowRect)) {
6288 return;
6289 }
6290 AddStateBits(TEXT_SELECTION_UNDERLINE_OVERFLOWED);
6291}
6292
6293nscoord nsTextFrame::ComputeLineHeight() const {
6294 return ReflowInput::CalcLineHeight(*Style(), PresContext(), GetContent(),
6295 GetFontSizeInflation());
6296}
6297
6298gfxFloat nsTextFrame::ComputeDescentLimitForSelectionUnderline(
6299 nsPresContext* aPresContext, const gfxFont::Metrics& aFontMetrics) {
6300 const gfxFloat lineHeight =
6301 gfxFloat(ComputeLineHeight()) / aPresContext->AppUnitsPerDevPixel();
6302 if (lineHeight <= aFontMetrics.maxHeight) {
6303 return aFontMetrics.maxDescent;
6304 }
6305 return aFontMetrics.maxDescent + (lineHeight - aFontMetrics.maxHeight) / 2;
6306}
6307
6308// Make sure this stays in sync with DrawSelectionDecorations below
6309static constexpr SelectionTypeMask kSelectionTypesWithDecorations =
6310 ToSelectionTypeMask(SelectionType::eNormal) |
6311 ToSelectionTypeMask(SelectionType::eTargetText) |
6312 ToSelectionTypeMask(SelectionType::eHighlight) |
6313 ToSelectionTypeMask(SelectionType::eSpellCheck) |
6314 ToSelectionTypeMask(SelectionType::eURLStrikeout) |
6315 ToSelectionTypeMask(SelectionType::eIMERawClause) |
6316 ToSelectionTypeMask(SelectionType::eIMESelectedRawClause) |
6317 ToSelectionTypeMask(SelectionType::eIMEConvertedClause) |
6318 ToSelectionTypeMask(SelectionType::eIMESelectedClause);
6319
6320/* static */
6321gfxFloat nsTextFrame::ComputeSelectionUnderlineHeight(
6322 nsPresContext* aPresContext, const gfxFont::Metrics& aFontMetrics,
6323 SelectionType aSelectionType) {
6324 switch (aSelectionType) {
6325 case SelectionType::eNormal:
6326 case SelectionType::eTargetText:
6327 case SelectionType::eHighlight:
6328 case SelectionType::eIMERawClause:
6329 case SelectionType::eIMESelectedRawClause:
6330 case SelectionType::eIMEConvertedClause:
6331 case SelectionType::eIMESelectedClause:
6332 return aFontMetrics.underlineSize;
6333 case SelectionType::eSpellCheck: {
6334 // The thickness of the spellchecker underline shouldn't honor the font
6335 // metrics. It should be constant pixels value which is decided from the
6336 // default font size. Note that if the actual font size is smaller than
6337 // the default font size, we should use the actual font size because the
6338 // computed value from the default font size can be too thick for the
6339 // current font size.
6340 Length defaultFontSize =
6341 aPresContext->Document()
6342 ->GetFontPrefsForLang(nullptr)
6343 ->GetDefaultFont(StyleGenericFontFamily::None)
6344 ->size;
6345 int32_t zoomedFontSize = aPresContext->CSSPixelsToDevPixels(
6346 nsStyleFont::ZoomText(*aPresContext->Document(), defaultFontSize)
6347 .ToCSSPixels());
6348 gfxFloat fontSize =
6349 std::min(gfxFloat(zoomedFontSize), aFontMetrics.emHeight);
6350 fontSize = std::max(fontSize, 1.0);
6351 return ceil(fontSize / 20);
6352 }
6353 default:
6354 NS_WARNING("Requested underline style is not valid")NS_DebugBreak(NS_DEBUG_WARNING, "Requested underline style is not valid"
, nullptr, "./../../../layout/generic/nsTextFrame.cpp", 6354)
;
6355 return aFontMetrics.underlineSize;
6356 }
6357}
6358
6359enum class DecorationType { Normal, Selection };
6360struct nsTextFrame::PaintDecorationLineParams
6361 : nsCSSRendering::DecorationRectParams {
6362 gfxContext* context = nullptr;
6363 LayoutDeviceRect dirtyRect;
6364 Point pt;
6365 const nscolor* overrideColor = nullptr;
6366 nscolor color = NS_RGBA(0, 0, 0, 0)((nscolor)(((0) << 24) | ((0) << 16) | ((0) <<
8) | (0)))
;
6367 gfxFloat icoordInFrame = 0.0f;
6368 gfxFloat baselineOffset = 0.0f;
6369 DecorationType decorationType = DecorationType::Normal;
6370 DrawPathCallbacks* callbacks = nullptr;
6371 bool paintingShadows = false;
6372 bool allowInkSkipping = true;
6373 StyleTextDecorationSkipInk skipInk = StyleTextDecorationSkipInk::None;
6374};
6375
6376void nsTextFrame::PaintDecorationLine(
6377 const PaintDecorationLineParams& aParams) {
6378 nsCSSRendering::PaintDecorationLineParams params;
6379 static_cast<nsCSSRendering::DecorationRectParams&>(params) = aParams;
6380 params.dirtyRect = aParams.dirtyRect.ToUnknownRect();
6381 params.pt = aParams.pt;
6382 params.color = aParams.overrideColor ? *aParams.overrideColor : aParams.color;
6383 params.icoordInFrame = Float(aParams.icoordInFrame);
6384 params.baselineOffset = Float(aParams.baselineOffset);
6385 // Disable ink-skipping for frames with text-combine-upright.
6386 params.allowInkSkipping =
6387 aParams.allowInkSkipping && !Style()->IsTextCombined();
6388 params.skipInk = aParams.skipInk;
6389 if (aParams.callbacks) {
6390 Rect path = nsCSSRendering::DecorationLineToPath(params);
6391 if (aParams.decorationType == DecorationType::Normal) {
6392 aParams.callbacks->PaintDecorationLine(path, aParams.paintingShadows,
6393 params.color);
6394 } else {
6395 aParams.callbacks->PaintSelectionDecorationLine(
6396 path, aParams.paintingShadows, params.color);
6397 }
6398 } else {
6399 nsCSSRendering::PaintDecorationLine(this, *aParams.context->GetDrawTarget(),
6400 params);
6401 }
6402}
6403
6404static StyleTextDecorationStyle ToStyleLineStyle(const TextRangeStyle& aStyle) {
6405 switch (aStyle.mLineStyle) {
6406 case TextRangeStyle::LineStyle::None:
6407 return StyleTextDecorationStyle::None;
6408 case TextRangeStyle::LineStyle::Solid:
6409 return StyleTextDecorationStyle::Solid;
6410 case TextRangeStyle::LineStyle::Dotted:
6411 return StyleTextDecorationStyle::Dotted;
6412 case TextRangeStyle::LineStyle::Dashed:
6413 return StyleTextDecorationStyle::Dashed;
6414 case TextRangeStyle::LineStyle::Double:
6415 return StyleTextDecorationStyle::Double;
6416 case TextRangeStyle::LineStyle::Wavy:
6417 return StyleTextDecorationStyle::Wavy;
6418 }
6419 MOZ_ASSERT_UNREACHABLE("Invalid line style")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(false)>::isValid, "invalid assertion condition");
if ((__builtin_expect(!!(!(!!(false))), 0))) { do { } while (
false); MOZ_ReportAssertionFailure("false" " (" "MOZ_ASSERT_UNREACHABLE: "
"Invalid line style" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 6419); AnnotateMozCrashReason("MOZ_ASSERT" "(" "false" ") ("
"MOZ_ASSERT_UNREACHABLE: " "Invalid line style" ")"); do { MOZ_CrashSequence
(__null, 6419); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
6420 return StyleTextDecorationStyle::None;
6421}
6422
6423/**
6424 * This, plus kSelectionTypesWithDecorations, encapsulates all knowledge
6425 * about drawing text decoration for selections.
6426 */
6427void nsTextFrame::DrawSelectionDecorations(
6428 gfxContext* aContext, const LayoutDeviceRect& aDirtyRect,
6429 SelectionType aSelectionType, nsAtom* aHighlightName,
6430 nsTextPaintStyle& aTextPaintStyle, const TextRangeStyle& aRangeStyle,
6431 const Point& aPt, gfxFloat aICoordInFrame, gfxFloat aWidth,
6432 gfxFloat aAscent, const gfxFont::Metrics& aFontMetrics,
6433 DrawPathCallbacks* aCallbacks, bool aVertical,
6434 StyleTextDecorationLine aDecoration, const Range& aGlyphRange,
6435 PropertyProvider* aProvider) {
6436 PaintDecorationLineParams params;
6437 params.context = aContext;
6438 params.dirtyRect = aDirtyRect;
6439 params.pt = aPt;
6440 params.lineSize.width = aWidth;
6441 params.ascent = aAscent;
6442 params.decoration = aDecoration;
6443 params.decorationType = DecorationType::Selection;
6444 params.callbacks = aCallbacks;
6445 params.vertical = aVertical;
6446 params.sidewaysLeft = mTextRun->IsSidewaysLeft();
6447 params.descentLimit = ComputeDescentLimitForSelectionUnderline(
6448 aTextPaintStyle.PresContext(), aFontMetrics);
6449 params.glyphRange = aGlyphRange;
6450 params.provider = aProvider;
6451
6452 float relativeSize = 1.f;
6453 const auto& decThickness = StyleTextReset()->mTextDecorationThickness;
6454 const gfxFloat appUnitsPerDevPixel =
6455 aTextPaintStyle.PresContext()->AppUnitsPerDevPixel();
6456
6457 const WritingMode wm = GetWritingMode();
6458 switch (aSelectionType) {
6459 case SelectionType::eNormal:
6460 case SelectionType::eHighlight:
6461 case SelectionType::eTargetText: {
6462 RefPtr computedStyleFromPseudo =
6463 aTextPaintStyle.GetComputedStyleForSelectionPseudo(aSelectionType,
6464 aHighlightName);
6465 const bool hasTextDecorations =
6466 computedStyleFromPseudo
6467 ? computedStyleFromPseudo->HasTextDecorationLines()
6468 : false;
6469 if (!hasTextDecorations) {
6470 return;
6471 }
6472 params.style =
6473 computedStyleFromPseudo->StyleTextReset()->mTextDecorationStyle;
6474 params.color =
6475 computedStyleFromPseudo->StyleTextReset()
6476 ->mTextDecorationColor.CalcColor(*computedStyleFromPseudo);
6477 params.decoration =
6478 computedStyleFromPseudo->StyleTextReset()->mTextDecorationLine;
6479 params.descentLimit = -1.f;
6480
6481 const bool swapUnderline =
6482 wm.IsCentralBaseline() && IsUnderlineRight(*Style());
6483 params.icoordInFrame = aICoordInFrame;
6484 auto paintForLine = [&](StyleTextDecorationLine decoration) {
6485 if (!(computedStyleFromPseudo->StyleTextReset()->mTextDecorationLine &
6486 decoration)) {
6487 return;
6488 }
6489
6490 params.allowInkSkipping = true;
6491 params.skipInk =
6492 computedStyleFromPseudo->StyleText()->mTextDecorationSkipInk;
6493 params.decoration = decoration;
6494 params.offset = ComputeDecorationLineOffset(
6495 params.decoration,
6496 computedStyleFromPseudo->StyleText()->mTextUnderlinePosition,
6497 computedStyleFromPseudo->StyleText()->mTextUnderlineOffset,
6498 aFontMetrics, appUnitsPerDevPixel, this, wm.IsCentralBaseline(),
6499 swapUnderline);
6500
6501 if (decoration == StyleTextDecorationLine::LINE_THROUGH) {
6502 params.defaultLineThickness = aFontMetrics.strikeoutSize;
6503 } else {
6504 params.defaultLineThickness = ComputeSelectionUnderlineHeight(
6505 aTextPaintStyle.PresContext(), aFontMetrics, aSelectionType);
6506 }
6507 params.lineSize.height = ComputeDecorationLineThickness(
6508 computedStyleFromPseudo->StyleTextReset()->mTextDecorationThickness,
6509 params.defaultLineThickness, aFontMetrics, appUnitsPerDevPixel,
6510 this);
6511
6512 PaintDecorationLine(params);
6513 };
6514 paintForLine(StyleTextDecorationLine::UNDERLINE);
6515 paintForLine(StyleTextDecorationLine::OVERLINE);
6516 paintForLine(StyleTextDecorationLine::LINE_THROUGH);
6517 return;
6518 }
6519 case SelectionType::eIMERawClause:
6520 case SelectionType::eIMESelectedRawClause:
6521 case SelectionType::eIMEConvertedClause:
6522 case SelectionType::eIMESelectedClause:
6523 case SelectionType::eSpellCheck: {
6524 auto index = nsTextPaintStyle::GetUnderlineStyleIndexForSelectionType(
6525 aSelectionType);
6526 bool weDefineSelectionUnderline =
6527 aTextPaintStyle.GetSelectionUnderlineForPaint(
6528 index, &params.color, &relativeSize, &params.style);
6529 params.defaultLineThickness = ComputeSelectionUnderlineHeight(
6530 aTextPaintStyle.PresContext(), aFontMetrics, aSelectionType);
6531 params.lineSize.height = ComputeDecorationLineThickness(
6532 decThickness, params.defaultLineThickness, aFontMetrics,
6533 appUnitsPerDevPixel, this);
6534
6535 bool swapUnderline = wm.IsCentralBaseline() && IsUnderlineRight(*Style());
6536 const auto* styleText = StyleText();
6537 params.offset = ComputeDecorationLineOffset(
6538 aDecoration, styleText->mTextUnderlinePosition,
6539 styleText->mTextUnderlineOffset, aFontMetrics, appUnitsPerDevPixel,
6540 this, wm.IsCentralBaseline(), swapUnderline);
6541
6542 bool isIMEType = aSelectionType != SelectionType::eSpellCheck;
6543
6544 if (isIMEType) {
6545 // IME decoration lines should not be drawn on the both ends, i.e., we
6546 // need to cut both edges of the decoration lines. Because same style
6547 // IME selections can adjoin, but the users need to be able to know
6548 // where are the boundaries of the selections.
6549 //
6550 // X: underline
6551 //
6552 // IME selection #1 IME selection #2 IME selection #3
6553 // | | |
6554 // | XXXXXXXXXXXXXXXXXXX | XXXXXXXXXXXXXXXXXXXX | XXXXXXXXXXXXXXXXXXX
6555 // +---------------------+----------------------+--------------------
6556 // ^ ^ ^ ^ ^
6557 // gap gap gap
6558 params.pt.x += 1.0;
6559 params.lineSize.width -= 2.0;
6560 }
6561 if (isIMEType && aRangeStyle.IsDefined()) {
6562 // If IME defines the style, that should override our definition.
6563 if (aRangeStyle.IsLineStyleDefined()) {
6564 if (aRangeStyle.mLineStyle == TextRangeStyle::LineStyle::None) {
6565 return;
6566 }
6567 params.style = ToStyleLineStyle(aRangeStyle);
6568 relativeSize = aRangeStyle.mIsBoldLine ? 2.0f : 1.0f;
6569 } else if (!weDefineSelectionUnderline) {
6570 // There is no underline style definition.
6571 return;
6572 }
6573 // If underline color is defined and that doesn't depend on the
6574 // foreground color, we should use the color directly.
6575 if (aRangeStyle.IsUnderlineColorDefined() &&
6576 (!aRangeStyle.IsForegroundColorDefined() ||
6577 aRangeStyle.mUnderlineColor != aRangeStyle.mForegroundColor)) {
6578 params.color = aRangeStyle.mUnderlineColor;
6579 }
6580 // If foreground color or background color is defined, the both colors
6581 // are computed by GetSelectionTextColors(). Then, we should use its
6582 // foreground color always. The color should have sufficient contrast
6583 // with the background color.
6584 else if (aRangeStyle.IsForegroundColorDefined() ||
6585 aRangeStyle.IsBackgroundColorDefined()) {
6586 params.color = GetSelectionTextColors(aSelectionType, nullptr,
6587 aTextPaintStyle, aRangeStyle)
6588 .mForeground;
6589 }
6590 // Otherwise, use the foreground color of the frame.
6591 else {
6592 params.color = aTextPaintStyle.GetTextColor();
6593 }
6594 } else if (!weDefineSelectionUnderline) {
6595 // IME doesn't specify the selection style and we don't define selection
6596 // underline.
6597 return;
6598 }
6599 break;
6600 }
6601 case SelectionType::eURLStrikeout: {
6602 nscoord inflationMinFontSize =
6603 nsLayoutUtils::InflationMinFontSizeFor(this);
6604 float inflation =
6605 GetInflationForTextDecorations(this, inflationMinFontSize);
6606 const gfxFont::Metrics metrics =
6607 GetFirstFontMetrics(GetFontGroupForFrame(this, inflation), aVertical);
6608
6609 relativeSize = 2.0f;
6610 aTextPaintStyle.GetURLSecondaryColor(&params.color);
6611 params.style = StyleTextDecorationStyle::Solid;
6612 params.defaultLineThickness = metrics.strikeoutSize;
6613 params.lineSize.height = ComputeDecorationLineThickness(
6614 decThickness, params.defaultLineThickness, metrics,
6615 appUnitsPerDevPixel, this);
6616 // TODO(jfkthame): ComputeDecorationLineOffset? check vertical mode!
6617 params.offset = metrics.strikeoutOffset + 0.5;
6618 params.decoration = StyleTextDecorationLine::LINE_THROUGH;
6619 break;
6620 }
6621 default:
6622 NS_WARNING("Requested selection decorations when there aren't any")NS_DebugBreak(NS_DEBUG_WARNING, "Requested selection decorations when there aren't any"
, nullptr, "./../../../layout/generic/nsTextFrame.cpp", 6622)
;
6623 return;
6624 }
6625 params.lineSize.height *= relativeSize;
6626 params.defaultLineThickness *= relativeSize;
6627 params.icoordInFrame =
6628 (aVertical ? params.pt.y - aPt.y : params.pt.x - aPt.x) + aICoordInFrame;
6629 PaintDecorationLine(params);
6630}
6631
6632/* static */
6633nsTextFrame::SelectionColors nsTextFrame::GetSelectionTextColors(
6634 SelectionType aSelectionType, nsAtom* aHighlightName,
6635 nsTextPaintStyle& aTextPaintStyle, const TextRangeStyle& aRangeStyle) {
6636 SelectionColors selectionColors;
6637 switch (aSelectionType) {
6638 case SelectionType::eNormal: {
6639 const bool displayed = aTextPaintStyle.GetSelectionColors(
6640 &selectionColors.mForeground, &selectionColors.mBackground);
6641 selectionColors.mHasBackground =
6642 displayed && NS_GET_A(selectionColors.mBackground)((uint8_t)(((selectionColors.mBackground) >> 24) & 0xff
))
> 0;
6643 selectionColors.mOverridesForeground = displayed;
6644 break;
6645 }
6646 case SelectionType::eFind:
6647 aTextPaintStyle.GetHighlightColors(&selectionColors.mForeground,
6648 &selectionColors.mBackground);
6649 selectionColors.mHasBackground =
6650 NS_GET_A(selectionColors.mBackground)((uint8_t)(((selectionColors.mBackground) >> 24) & 0xff
))
> 0;
6651 selectionColors.mOverridesForeground = true;
6652 break;
6653 case SelectionType::eHighlight:
6654 // Intentionally not short-cutting here: both methods must always run.
6655 selectionColors.mOverridesForeground =
6656 aTextPaintStyle.GetCustomHighlightTextColor(
6657 aHighlightName, &selectionColors.mForeground);
6658 selectionColors.mHasBackground =
6659 aTextPaintStyle.GetCustomHighlightBackgroundColor(
6660 aHighlightName, &selectionColors.mBackground);
6661 selectionColors.mHasPaintImpact =
6662 !!aTextPaintStyle.GetComputedStyleForSelectionPseudo(aSelectionType,
6663 aHighlightName);
6664 break;
6665 case SelectionType::eTargetText:
6666 // Intentionally not short-cutting here: both methods must always run.
6667 selectionColors.mOverridesForeground =
6668 aTextPaintStyle.GetTargetTextColor(&selectionColors.mForeground);
6669 selectionColors.mHasBackground =
6670 aTextPaintStyle.GetTargetTextBackgroundColor(
6671 &selectionColors.mBackground);
6672 selectionColors.mHasPaintImpact =
6673 !!aTextPaintStyle.GetComputedStyleForSelectionPseudo(aSelectionType,
6674 aHighlightName);
6675 break;
6676 case SelectionType::eURLSecondary:
6677 aTextPaintStyle.GetURLSecondaryColor(&selectionColors.mForeground);
6678 selectionColors.mOverridesForeground = true;
6679 break;
6680 case SelectionType::eIMERawClause:
6681 case SelectionType::eIMESelectedRawClause:
6682 case SelectionType::eIMEConvertedClause:
6683 case SelectionType::eIMESelectedClause:
6684 if (aRangeStyle.IsDefined()) {
6685 if (!aRangeStyle.IsForegroundColorDefined() &&
6686 !aRangeStyle.IsBackgroundColorDefined()) {
6687 selectionColors.mForeground = aTextPaintStyle.GetTextColor();
6688 break;
6689 }
6690 if (aRangeStyle.IsForegroundColorDefined()) {
6691 selectionColors.mForeground = aRangeStyle.mForegroundColor;
6692 if (aRangeStyle.IsBackgroundColorDefined()) {
6693 selectionColors.mBackground = aRangeStyle.mBackgroundColor;
6694 } else {
6695 // If foreground color is defined but background color isn't
6696 // defined, we can guess that IME must expect that the background
6697 // color is system's default field background color.
6698 selectionColors.mBackground =
6699 aTextPaintStyle.GetSystemFieldBackgroundColor();
6700 }
6701 } else { // aRangeStyle.IsBackgroundColorDefined() is true
6702 selectionColors.mBackground = aRangeStyle.mBackgroundColor;
6703 // If background color is defined but foreground color isn't defined,
6704 // we can assume that IME must expect that the foreground color is
6705 // same as system's field text color.
6706 selectionColors.mForeground =
6707 aTextPaintStyle.GetSystemFieldForegroundColor();
6708 }
6709 selectionColors.mHasBackground =
6710 NS_GET_A(selectionColors.mBackground)((uint8_t)(((selectionColors.mBackground) >> 24) & 0xff
))
> 0;
6711 selectionColors.mOverridesForeground = true;
6712 break;
6713 }
6714 aTextPaintStyle.GetIMESelectionColors(
6715 nsTextPaintStyle::GetUnderlineStyleIndexForSelectionType(
6716 aSelectionType),
6717 &selectionColors.mForeground, &selectionColors.mBackground);
6718 selectionColors.mHasBackground =
6719 NS_GET_A(selectionColors.mBackground)((uint8_t)(((selectionColors.mBackground) >> 24) & 0xff
))
> 0;
6720 selectionColors.mOverridesForeground = true;
6721 break;
6722 default:
6723 selectionColors.mForeground = aTextPaintStyle.GetTextColor();
6724 break;
6725 }
6726 selectionColors.mHasPaintImpact |= selectionColors.HasAnyColorImpact();
6727 return selectionColors;
6728}
6729
6730/**
6731 * This sets *aShadows to the appropriate shadows, if any, for the given
6732 * type of selection.
6733 * If text-shadow was not specified, *aShadows is left untouched.
6734 */
6735mozilla::Span<const StyleSimpleShadow> nsTextFrame::GetSelectionTextShadow(
6736 SelectionType aSelectionType, nsTextPaintStyle& aTextPaintStyle,
6737 nsAtom* aHighlightName) {
6738 if (aSelectionType == SelectionType::eNormal) {
6739 return aTextPaintStyle.GetSelectionShadow();
6740 }
6741 if (aSelectionType == SelectionType::eTargetText) {
6742 return aTextPaintStyle.GetTargetTextShadow();
6743 }
6744 if (aSelectionType == SelectionType::eHighlight && aHighlightName) {
6745 return aTextPaintStyle.GetCustomHighlightTextShadow(aHighlightName);
6746 }
6747 return {};
6748}
6749
6750/**
6751 * This class lets us iterate over chunks of text recorded in an array of
6752 * resolved selection ranges, observing cluster boundaries, in content order,
6753 * maintaining the current x-offset as we go, and telling whether the text
6754 * chunk has a hyphen after it or not.
6755 * In addition to returning the selected chunks, the iterator is responsible
6756 * to interpolate unselected chunks in any gaps between them.
6757 * The caller is responsible for actually computing the advance width of each
6758 * chunk.
6759 */
6760class MOZ_STACK_CLASS SelectionRangeIterator {
6761 using PropertyProvider = nsTextFrame::PropertyProvider;
6762 using CombinedSelectionRange = nsTextFrame::PriorityOrderedSelectionsForRange;
6763
6764 public:
6765 // aSelectionRanges and aRange are according to the original string.
6766 SelectionRangeIterator(
6767 const nsTArray<CombinedSelectionRange>& aSelectionRanges,
6768 gfxTextRun::Range aRange, PropertyProvider& aProvider,
6769 gfxTextRun* aTextRun, gfxFloat aXOffset);
6770
6771 bool GetNextSegment(gfxFloat* aXOffset, gfxTextRun::Range* aRange,
6772 gfxFloat* aHyphenWidth,
6773 nsTArray<SelectionType>& aSelectionType,
6774 nsTArray<RefPtr<nsAtom>>& aHighlightName,
6775 nsTArray<TextRangeStyle>& aStyle);
6776
6777 void UpdateWithAdvance(gfxFloat aAdvance) {
6778 mXOffset += aAdvance * mTextRun->GetDirection();
6779 }
6780
6781 private:
6782 const nsTArray<CombinedSelectionRange>& mSelectionRanges;
6783 PropertyProvider& mProvider;
6784 gfxTextRun* mTextRun;
6785 gfxSkipCharsIterator mIterator;
6786 gfxTextRun::Range mOriginalRange;
6787 gfxFloat mXOffset;
6788 uint32_t mIndex;
6789};
6790
6791SelectionRangeIterator::SelectionRangeIterator(
6792 const nsTArray<nsTextFrame::PriorityOrderedSelectionsForRange>&
6793 aSelectionRanges,
6794 gfxTextRun::Range aRange, PropertyProvider& aProvider, gfxTextRun* aTextRun,
6795 gfxFloat aXOffset)
6796 : mSelectionRanges(aSelectionRanges),
6797 mProvider(aProvider),
6798 mTextRun(aTextRun),
6799 mIterator(aProvider.GetStart()),
6800 mOriginalRange(aRange),
6801 mXOffset(aXOffset),
6802 mIndex(0) {
6803 mIterator.SetOriginalOffset(int32_t(aRange.start));
6804}
6805
6806bool SelectionRangeIterator::GetNextSegment(
6807 gfxFloat* aXOffset, gfxTextRun::Range* aRange, gfxFloat* aHyphenWidth,
6808 nsTArray<SelectionType>& aSelectionType,
6809 nsTArray<RefPtr<nsAtom>>& aHighlightName,
6810 nsTArray<TextRangeStyle>& aStyle) {
6811 if (mIterator.GetOriginalOffset() >= int32_t(mOriginalRange.end)) {
6812 return false;
6813 }
6814
6815 uint32_t runOffset = mIterator.GetSkippedOffset();
6816 uint32_t segmentEnd = mOriginalRange.end;
6817
6818 aSelectionType.Clear();
6819 aHighlightName.Clear();
6820 aStyle.Clear();
6821
6822 if (mIndex == mSelectionRanges.Length() ||
6823 mIterator.GetOriginalOffset() <
6824 int32_t(mSelectionRanges[mIndex].mRange.start)) {
6825 // There's an unselected segment before the next range (or at the end).
6826 aSelectionType.AppendElement(SelectionType::eNone);
6827 aHighlightName.AppendElement();
6828 aStyle.AppendElement(TextRangeStyle());
6829 if (mIndex < mSelectionRanges.Length()) {
6830 segmentEnd = mSelectionRanges[mIndex].mRange.start;
6831 }
6832 } else {
6833 // Get the selection details for the next segment, and increment index.
6834 for (const SelectionDetails* sdptr :
6835 mSelectionRanges[mIndex].mSelectionRanges) {
6836 aSelectionType.AppendElement(sdptr->mSelectionType);
6837 aHighlightName.AppendElement(sdptr->mHighlightData.mHighlightName);
6838 aStyle.AppendElement(sdptr->mTextRangeStyle);
6839 }
6840 segmentEnd = mSelectionRanges[mIndex].mRange.end;
6841 ++mIndex;
6842 }
6843
6844 // Advance iterator to the end of the segment.
6845 mIterator.SetOriginalOffset(int32_t(segmentEnd));
6846
6847 // Further advance if necessary to a cluster boundary.
6848 while (mIterator.GetOriginalOffset() < int32_t(mOriginalRange.end) &&
6849 !mIterator.IsOriginalCharSkipped() &&
6850 !mTextRun->IsClusterStart(mIterator.GetSkippedOffset())) {
6851 mIterator.AdvanceOriginal(1);
6852 }
6853
6854 aRange->start = runOffset;
6855 aRange->end = mIterator.GetSkippedOffset();
6856 *aXOffset = mXOffset;
6857 *aHyphenWidth = 0;
6858 if (mIterator.GetOriginalOffset() == int32_t(mOriginalRange.end) &&
6859 mProvider.GetFrame()->HasAnyStateBits(TEXT_HYPHEN_BREAK)) {
6860 *aHyphenWidth = mProvider.GetHyphenWidth();
6861 }
6862
6863 return true;
6864}
6865
6866static void AddHyphenToMetrics(nsTextFrame* aTextFrame, bool aIsRightToLeft,
6867 gfxTextRun::Metrics* aMetrics,
6868 gfxFont::BoundingBoxType aBoundingBoxType,
6869 DrawTarget* aDrawTarget) {
6870 // Fix up metrics to include hyphen
6871 RefPtr<gfxTextRun> hyphenTextRun = GetHyphenTextRun(aTextFrame, aDrawTarget);
6872 if (!hyphenTextRun) {
6873 return;
6874 }
6875
6876 gfxTextRun::Metrics hyphenMetrics =
6877 hyphenTextRun->MeasureText(aBoundingBoxType, aDrawTarget);
6878 if (aTextFrame->GetWritingMode().IsLineInverted()) {
6879 hyphenMetrics.mBoundingBox.y = -hyphenMetrics.mBoundingBox.YMost();
6880 }
6881 aMetrics->CombineWith(hyphenMetrics, aIsRightToLeft);
6882}
6883
6884void nsTextFrame::PaintOneShadow(const PaintShadowParams& aParams,
6885 const StyleSimpleShadow& aShadowDetails,
6886 gfxRect& aBoundingBox, uint32_t aBlurFlags,
6887 imgDrawingParams& aImgParams) {
6888 AUTO_PROFILER_LABEL("nsTextFrame::PaintOneShadow", GRAPHICS)mozilla::AutoProfilerLabel raiiObject6888( "nsTextFrame::PaintOneShadow"
, nullptr, JS::ProfilingCategoryPair::GRAPHICS)
;
6889
6890 nsPoint shadowOffset(aShadowDetails.horizontal.ToAppUnits(),
6891 aShadowDetails.vertical.ToAppUnits());
6892 nscoord blurRadius = std::max(aShadowDetails.blur.ToAppUnits(), 0);
6893
6894 nscolor shadowColor = aShadowDetails.color.CalcColor(aParams.foregroundColor);
6895
6896 if (auto* textDrawer = aParams.context->GetTextDrawer()) {
6897 wr::Shadow wrShadow;
6898
6899 wrShadow.offset = {PresContext()->AppUnitsToFloatDevPixels(shadowOffset.x),
6900 PresContext()->AppUnitsToFloatDevPixels(shadowOffset.y)};
6901
6902 wrShadow.blur_radius = PresContext()->AppUnitsToFloatDevPixels(blurRadius);
6903 wrShadow.color = wr::ToColorF(ToDeviceColor(shadowColor));
6904
6905 bool inflateMOZ_Z_inflate = true;
6906 textDrawer->AppendShadow(wrShadow, inflateMOZ_Z_inflate);
6907 return;
6908 }
6909
6910 // This rect is the box which is equivalent to where the shadow will be
6911 // painted. The origin of aBoundingBox is the text baseline left, so we must
6912 // translate it by that much in order to make the origin the top-left corner
6913 // of the text bounding box. Note that aLeftSideOffset is line-left, so
6914 // actually means top offset in vertical writing modes.
6915 gfxRect shadowGfxRect;
6916 WritingMode wm = GetWritingMode();
6917 if (wm.IsVertical()) {
6918 shadowGfxRect = aBoundingBox;
6919 if (wm.IsVerticalRL()) {
6920 // for vertical-RL, reverse direction of x-coords of bounding box
6921 shadowGfxRect.x = -shadowGfxRect.XMost();
6922 }
6923 shadowGfxRect += gfxPoint(aParams.textBaselinePt.x,
6924 aParams.framePt.y + aParams.leftSideOffset);
6925 } else {
6926 shadowGfxRect =
6927 aBoundingBox + gfxPoint(aParams.framePt.x + aParams.leftSideOffset,
6928 aParams.textBaselinePt.y);
6929 }
6930 Point shadowGfxOffset(shadowOffset.x, shadowOffset.y);
6931 shadowGfxRect += gfxPoint(shadowGfxOffset.x, shadowOffset.y);
6932
6933 nsRect shadowRect(NSToCoordRound(shadowGfxRect.X()),
6934 NSToCoordRound(shadowGfxRect.Y()),
6935 NSToCoordRound(shadowGfxRect.Width()),
6936 NSToCoordRound(shadowGfxRect.Height()));
6937
6938 nsContextBoxBlur contextBoxBlur;
6939 const auto A2D = PresContext()->AppUnitsPerDevPixel();
6940 gfxContext* shadowContext =
6941 contextBoxBlur.Init(shadowRect, 0, blurRadius, A2D, aParams.context,
6942 LayoutDevicePixel::ToAppUnits(aParams.dirtyRect, A2D),
6943 nullptr, aBlurFlags);
6944 if (!shadowContext) {
6945 return;
6946 }
6947
6948 aParams.context->Save();
6949 aParams.context->SetColor(sRGBColor::FromABGR(shadowColor));
6950
6951 // Draw the text onto our alpha-only surface to capture the alpha values.
6952 // Remember that the box blur context has a device offset on it, so we don't
6953 // need to translate any coordinates to fit on the surface.
6954 gfxFloat advanceWidth;
6955 nsTextPaintStyle textPaintStyle(this);
6956 DrawTextParams params(shadowContext, PresContext()->FontPaletteCache());
6957 params.paintingShadows = true;
6958 params.advanceWidth = &advanceWidth;
6959 params.dirtyRect = aParams.dirtyRect;
6960 params.framePt = aParams.framePt + shadowGfxOffset;
6961 params.provider = aParams.provider;
6962 params.textStyle = &textPaintStyle;
6963 params.textColor =
6964 aParams.context == shadowContext ? shadowColor : NS_RGB(0, 0, 0)((nscolor)((255 << 24) | ((0) << 16) | ((0) <<
8) | (0)))
;
6965 params.callbacks = aParams.callbacks;
6966 params.clipEdges = aParams.clipEdges;
6967 params.drawSoftHyphen = HasAnyStateBits(TEXT_HYPHEN_BREAK);
6968 // Multi-color shadow is not allowed, so we use the same color as the text
6969 // color.
6970 params.decorationOverrideColor = &params.textColor;
6971 params.fontPalette = StyleFont()->GetFontPaletteAtom();
6972
6973 DrawText(aParams.range, aParams.textBaselinePt + shadowGfxOffset, params,
6974 aImgParams);
6975
6976 contextBoxBlur.DoPaint();
6977 aParams.context->Restore();
6978}
6979
6980/* static */
6981SelectionTypeMask nsTextFrame::CreateSelectionRangeList(
6982 const SelectionDetails& aDetails, SelectionType aSelectionType,
6983 const PaintTextSelectionParams& aParams,
6984 nsTArray<SelectionRange>& aSelectionRanges, bool* aAnyBackgrounds) {
6985 SelectionTypeMask allTypes = 0;
6986 bool anyBackgrounds = false;
6987
6988 uint32_t priorityOfInsertionOrder = 0;
6989 for (const SelectionDetails* sd = &aDetails; sd; sd = sd->mNext.get()) {
6990 MOZ_ASSERT(sd->mStart >= 0 && sd->mEnd >= 0)do { static_assert( mozilla::detail::AssertionConditionType<
decltype(sd->mStart >= 0 && sd->mEnd >= 0
)>::isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(sd->mStart >= 0 && sd->mEnd >= 0
))), 0))) { do { } while (false); MOZ_ReportAssertionFailure(
"sd->mStart >= 0 && sd->mEnd >= 0", "./../../../layout/generic/nsTextFrame.cpp"
, 6990); AnnotateMozCrashReason("MOZ_ASSERT" "(" "sd->mStart >= 0 && sd->mEnd >= 0"
")"); do { MOZ_CrashSequence(__null, 6990); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
; // XXX make unsigned?
6991 uint32_t start = std::max(aParams.contentRange.start, uint32_t(sd->mStart));
6992 uint32_t end = std::min(aParams.contentRange.end, uint32_t(sd->mEnd));
6993 if (start < end) {
6994 // The PaintTextWithSelectionColors caller passes SelectionType::eNone,
6995 // so we collect all selections that affect text/background paint, and
6996 // prioritize them according to selection type (lower types take
6997 // precedence).
6998 if (aSelectionType == SelectionType::eNone) {
6999 allTypes |= ToSelectionTypeMask(sd->mSelectionType);
7000 // Ignore selections that don't affect text/background paint.
7001 const auto colors = GetSelectionTextColors(
7002 sd->mSelectionType, sd->mHighlightData.mHighlightName,
7003 *aParams.textPaintStyle, sd->mTextRangeStyle);
7004 if (colors.HasAnyPaintImpact()) {
7005 if (colors.mHasBackground) {
7006 anyBackgrounds = true;
7007 }
7008 aSelectionRanges.AppendElement(
7009 SelectionRange{sd, {start, end}, priorityOfInsertionOrder++});
7010 }
7011 } else if (sd->mSelectionType == aSelectionType) {
7012 // The PaintSelectionTextDecorations caller passes a specific type,
7013 // so we include only ranges of that type, and keep them in order
7014 // so that later ones take precedence over earlier.
7015 aSelectionRanges.AppendElement(
7016 SelectionRange{sd, {start, end}, priorityOfInsertionOrder++});
7017 }
7018 }
7019 }
7020 if (aAnyBackgrounds) {
7021 *aAnyBackgrounds = anyBackgrounds;
7022 }
7023 return allTypes;
7024}
7025
7026/* static */
7027void nsTextFrame::CombineSelectionRanges(
7028 const nsTArray<SelectionRange>& aSelectionRanges,
7029 nsTArray<PriorityOrderedSelectionsForRange>& aCombinedSelectionRanges) {
7030 struct SelectionRangeEndCmp {
7031 bool Equals(const SelectionRange* a, const SelectionRange* b) const {
7032 return a->mRange.end == b->mRange.end;
7033 }
7034 bool LessThan(const SelectionRange* a, const SelectionRange* b) const {
7035 return a->mRange.end < b->mRange.end;
7036 }
7037 };
7038
7039 struct SelectionRangePriorityCmp {
7040 bool Equals(const SelectionRange* a, const SelectionRange* b) const {
7041 const SelectionDetails* aDetails = a->mDetails;
7042 const SelectionDetails* bDetails = b->mDetails;
7043 if (aDetails->mSelectionType != bDetails->mSelectionType) {
7044 return false;
7045 }
7046 if (aDetails->mSelectionType != SelectionType::eHighlight) {
7047 return a->mPriority == b->mPriority;
7048 }
7049 if (aDetails->mHighlightData.mHighlight->Priority() !=
7050 bDetails->mHighlightData.mHighlight->Priority()) {
7051 return false;
7052 }
7053 return a->mPriority == b->mPriority;
7054 }
7055
7056 bool LessThan(const SelectionRange* a, const SelectionRange* b) const {
7057 if (a->mDetails->mSelectionType != b->mDetails->mSelectionType) {
7058 // Even though this looks counter-intuitive,
7059 // this is intended, as values in `SelectionType` are inverted:
7060 // a lower value indicates a higher priority.
7061 return a->mDetails->mSelectionType > b->mDetails->mSelectionType;
7062 }
7063
7064 if (a->mDetails->mSelectionType != SelectionType::eHighlight) {
7065 // for non-highlights, the selection which was added later
7066 // has a higher priority.
7067 return a->mPriority < b->mPriority;
7068 }
7069
7070 if (a->mDetails->mHighlightData.mHighlight->Priority() !=
7071 b->mDetails->mHighlightData.mHighlight->Priority()) {
7072 // For highlights, first compare the priorities set by the user.
7073 return a->mDetails->mHighlightData.mHighlight->Priority() <
7074 b->mDetails->mHighlightData.mHighlight->Priority();
7075 }
7076 // only if the user priorities are equal, let the highlight that was added
7077 // later take precedence.
7078 return a->mPriority < b->mPriority;
7079 }
7080 };
7081
7082 uint32_t currentOffset = 0;
7083 AutoTArray<const SelectionRange*, 1> activeSelectionsForCurrentSegment;
7084 size_t rangeIndex = 0;
7085
7086 // Divide the given selection ranges into segments which share the same
7087 // set of selections.
7088 // The following algorithm iterates `aSelectionRanges`, assuming
7089 // that its elements are sorted by their start offset.
7090 // Each time a new selection starts, it is pushed into an array of
7091 // "currently present" selections, sorted by their *end* offset.
7092 // For each iteration the next segment end offset is determined,
7093 // which is either the start offset of the next selection or
7094 // the next end offset of all "currently present" selections
7095 // (which is always the first element of the array because of its order).
7096 // Then, a `CombinedSelectionRange` can be constructed, which describes
7097 // the text segment until its end offset (as determined above), and contains
7098 // all elements of the "currently present" selection list, now sorted
7099 // by their priority.
7100 // If a range ends at the given offset, it is removed from the array.
7101 while (rangeIndex < aSelectionRanges.Length() ||
7102 !activeSelectionsForCurrentSegment.IsEmpty()) {
7103 uint32_t currentSegmentEndOffset =
7104 activeSelectionsForCurrentSegment.IsEmpty()
7105 ? -1
7106 : activeSelectionsForCurrentSegment[0]->mRange.end;
7107 uint32_t nextRangeStartOffset =
7108 rangeIndex < aSelectionRanges.Length()
7109 ? aSelectionRanges[rangeIndex].mRange.start
7110 : -1;
7111 uint32_t nextOffset =
7112 std::min(currentSegmentEndOffset, nextRangeStartOffset);
7113 if (!activeSelectionsForCurrentSegment.IsEmpty() &&
7114 currentOffset != nextOffset) {
7115 auto activeSelectionRangesSortedByPriority =
7116 activeSelectionsForCurrentSegment.Clone();
7117 activeSelectionRangesSortedByPriority.Sort(SelectionRangePriorityCmp());
7118
7119 AutoTArray<const SelectionDetails*, 1> selectionDetails;
7120 selectionDetails.SetCapacity(
7121 activeSelectionRangesSortedByPriority.Length());
7122 // ensure that overlapping highlights which have the same name
7123 // are only added once. If added each time, they would be painted
7124 // several times (see wpt
7125 // /css/css-highlight-api/painting/custom-highlight-painting-003.html)
7126 // Comparing the highlight name with the previous one is
7127 // sufficient here because selections are already sorted
7128 // in a way that ensures that highlights of the same name are
7129 // grouped together.
7130 nsAtom* currentHighlightName = nullptr;
7131 for (const auto* selectionRange : activeSelectionRangesSortedByPriority) {
7132 if (selectionRange->mDetails->mSelectionType ==
7133 SelectionType::eHighlight) {
7134 if (selectionRange->mDetails->mHighlightData.mHighlightName ==
7135 currentHighlightName) {
7136 continue;
7137 }
7138 currentHighlightName =
7139 selectionRange->mDetails->mHighlightData.mHighlightName;
7140 }
7141 selectionDetails.AppendElement(selectionRange->mDetails);
7142 }
7143 aCombinedSelectionRanges.AppendElement(PriorityOrderedSelectionsForRange{
7144 std::move(selectionDetails), {currentOffset, nextOffset}});
7145 }
7146 currentOffset = nextOffset;
7147
7148 if (nextRangeStartOffset < currentSegmentEndOffset) {
7149 activeSelectionsForCurrentSegment.InsertElementSorted(
7150 &aSelectionRanges[rangeIndex++], SelectionRangeEndCmp());
7151 } else {
7152 activeSelectionsForCurrentSegment.RemoveElementAt(0);
7153 }
7154 }
7155}
7156
7157SelectionTypeMask nsTextFrame::ResolveSelections(
7158 const PaintTextSelectionParams& aParams, const SelectionDetails& aDetails,
7159 nsTArray<PriorityOrderedSelectionsForRange>& aResult,
7160 SelectionType aSelectionType, bool* aAnyBackgrounds) const {
7161 AutoTArray<SelectionRange, 4> selectionRanges;
7162
7163 SelectionTypeMask allTypes = CreateSelectionRangeList(
7164 aDetails, aSelectionType, aParams, selectionRanges, aAnyBackgrounds);
7165
7166 if (selectionRanges.IsEmpty()) {
7167 return allTypes;
7168 }
7169
7170 struct SelectionRangeStartCmp {
7171 bool Equals(const SelectionRange& a, const SelectionRange& b) const {
7172 return a.mRange.start == b.mRange.start;
7173 }
7174 bool LessThan(const SelectionRange& a, const SelectionRange& b) const {
7175 return a.mRange.start < b.mRange.start;
7176 }
7177 };
7178 selectionRanges.Sort(SelectionRangeStartCmp());
7179
7180 CombineSelectionRanges(selectionRanges, aResult);
7181
7182 return allTypes;
7183}
7184
7185// Paints selection backgrounds and text in the correct colors. Also computes
7186// aAllSelectionTypeMask, the union of all selection types that are applying to
7187// this text.
7188bool nsTextFrame::PaintTextWithSelectionColors(
7189 const PaintTextSelectionParams& aParams, const SelectionDetails& aDetails,
7190 SelectionTypeMask* aAllSelectionTypeMask, const ClipEdges& aClipEdges,
7191 imgDrawingParams& aImgParams) {
7192 bool anyBackgrounds = false;
7193 AutoTArray<PriorityOrderedSelectionsForRange, 8> selectionRanges;
7194
7195 *aAllSelectionTypeMask =
7196 ResolveSelections(aParams, aDetails, selectionRanges,
7197 SelectionType::eNone, &anyBackgrounds);
7198 bool vertical = mTextRun->IsVertical();
7199 const gfxFloat startIOffset =
7200 vertical ? aParams.textBaselinePt.y - aParams.framePt.y
7201 : aParams.textBaselinePt.x - aParams.framePt.x;
7202 gfxFloat iOffset, hyphenWidth;
7203 Range range; // in transformed string
7204
7205 const gfxTextRun::Range& contentRange = aParams.contentRange;
7206 auto* textDrawer = aParams.context->GetTextDrawer();
7207
7208 if (anyBackgrounds && !aParams.IsGenerateTextMask()) {
7209 int32_t appUnitsPerDevPixel =
7210 aParams.textPaintStyle->PresContext()->AppUnitsPerDevPixel();
7211 SelectionRangeIterator iterator(selectionRanges, contentRange,
7212 *aParams.provider, mTextRun, startIOffset);
7213 AutoTArray<SelectionType, 1> selectionTypes;
7214 AutoTArray<RefPtr<nsAtom>, 1> highlightNames;
7215 AutoTArray<TextRangeStyle, 1> rangeStyles;
7216 while (iterator.GetNextSegment(&iOffset, &range, &hyphenWidth,
7217 selectionTypes, highlightNames,
7218 rangeStyles)) {
7219 gfxFloat advance =
7220 hyphenWidth + mTextRun->GetAdvanceWidth(range, aParams.provider);
7221 nsRect bgRect;
7222 gfxFloat offs = iOffset - (mTextRun->IsInlineReversed() ? advance : 0);
7223 if (vertical) {
7224 bgRect = nsRect(nscoord(aParams.framePt.x),
7225 nscoord(aParams.framePt.y + offs), GetSize().width,
7226 nscoord(advance));
7227 } else {
7228 // For text-combine-upright, we may have collapsed the frame height,
7229 // so we instead use 1em to ensure the selection is visible.
7230 nscoord height = Style()->IsTextCombined()
7231 ? aParams.provider->GetFontMetrics()->EmHeight()
7232 : GetSize().height;
7233 bgRect = nsRect(nscoord(aParams.framePt.x + offs),
7234 nscoord(aParams.framePt.y), nscoord(advance), height);
7235 }
7236
7237 LayoutDeviceRect selectionRect =
7238 LayoutDeviceRect::FromAppUnits(bgRect, appUnitsPerDevPixel);
7239 // The elements in `selectionTypes` are ordered ascending by their
7240 // priority. To account for non-opaque overlapping selections, all
7241 // selection backgrounds are painted.
7242 for (size_t index = 0; index < selectionTypes.Length(); ++index) {
7243 const auto colors =
7244 GetSelectionTextColors(selectionTypes[index], highlightNames[index],
7245 *aParams.textPaintStyle, rangeStyles[index]);
7246 if (colors.mHasBackground) {
7247 if (textDrawer) {
7248 textDrawer->AppendSelectionRect(selectionRect,
7249 ToDeviceColor(colors.mBackground));
7250 } else {
7251 PaintSelectionBackground(*aParams.context->GetDrawTarget(),
7252 colors.mBackground, aParams.dirtyRect,
7253 selectionRect, aParams.callbacks);
7254 }
7255 }
7256 }
7257 iterator.UpdateWithAdvance(advance);
7258 }
7259 }
7260
7261 gfxFloat advance;
7262 DrawTextParams params(aParams.context, PresContext()->FontPaletteCache());
7263 params.dirtyRect = aParams.dirtyRect;
7264 params.framePt = aParams.framePt;
7265 params.provider = aParams.provider;
7266 params.textStyle = aParams.textPaintStyle;
7267 params.clipEdges = &aClipEdges;
7268 params.advanceWidth = &advance;
7269 params.callbacks = aParams.callbacks;
7270 params.glyphRange = aParams.glyphRange;
7271 params.fontPalette = StyleFont()->GetFontPaletteAtom();
7272 params.hasTextShadow = !StyleText()->mTextShadow.IsEmpty();
7273
7274 PaintShadowParams shadowParams(aParams);
7275 shadowParams.provider = aParams.provider;
7276 shadowParams.callbacks = aParams.callbacks;
7277 shadowParams.clipEdges = &aClipEdges;
7278
7279 // Draw text
7280 const nsStyleText* textStyle = StyleText();
7281 SelectionRangeIterator iterator(selectionRanges, contentRange,
7282 *aParams.provider, mTextRun, startIOffset);
7283 AutoTArray<SelectionType, 1> selectionTypes;
7284 AutoTArray<RefPtr<nsAtom>, 1> highlightNames;
7285 AutoTArray<TextRangeStyle, 1> rangeStyles;
7286 while (iterator.GetNextSegment(&iOffset, &range, &hyphenWidth, selectionTypes,
7287 highlightNames, rangeStyles)) {
7288 nscolor foreground(0);
7289 if (aParams.IsGenerateTextMask()) {
7290 foreground = NS_RGBA(0, 0, 0, 255)((nscolor)(((255) << 24) | ((0) << 16) | ((0) <<
8) | (0)))
;
7291 } else {
7292 nscolor fallbackForeground(0);
7293 bool colorHasBeenSet = false;
7294 for (size_t index = 0; index < selectionTypes.Length(); ++index) {
7295 const auto colors =
7296 GetSelectionTextColors(selectionTypes[index], highlightNames[index],
7297 *aParams.textPaintStyle, rangeStyles[index]);
7298 fallbackForeground = colors.mForeground;
7299 if (colors.mOverridesForeground) {
7300 foreground = colors.mForeground;
7301 colorHasBeenSet = true;
7302 }
7303 }
7304 if (!colorHasBeenSet) {
7305 foreground = fallbackForeground;
7306 }
7307 }
7308
7309 gfx::Point textBaselinePt =
7310 vertical
7311 ? gfx::Point(aParams.textBaselinePt.x, aParams.framePt.y + iOffset)
7312 : gfx::Point(aParams.framePt.x + iOffset, aParams.textBaselinePt.y);
7313
7314 // Determine what shadow, if any, to draw - either from textStyle
7315 // or from the ::-moz-selection pseudo-class if specified there
7316 AutoTArray<Span<const StyleSimpleShadow>, 1> shadows;
7317 bool hasSelectionShadow = false;
7318
7319 // Collect all selection/highlight shadows first
7320 for (size_t index = 0; index < selectionTypes.Length(); ++index) {
7321 nsAtom* highlightName = index < highlightNames.Length()
7322 ? highlightNames[index].get()
7323 : nullptr;
7324 RefPtr<ComputedStyle> selectionStyle =
7325 aParams.textPaintStyle->GetComputedStyleForSelectionPseudo(
7326 selectionTypes[index], highlightName);
7327 if (selectionStyle &&
7328 selectionStyle->HasAuthorOrUserSpecifiedTextShadow()) {
7329 // text-shadow was explicitly specified (including "none")
7330 hasSelectionShadow = true;
7331 Span<const StyleSimpleShadow> shadowSpan =
7332 selectionStyle->StyleText()->mTextShadow.AsSpan();
7333 if (!shadowSpan.IsEmpty()) {
7334 shadows.AppendElement(shadowSpan);
7335 }
7336 }
7337 }
7338
7339 // Only use element shadow if no selection shadow was explicitly specified
7340 // See https://github.com/w3c/csswg-drafts/issues/13376 for context.
7341 if (!hasSelectionShadow) {
7342 if (auto sh = textStyle->mTextShadow.AsSpan(); !sh.IsEmpty()) {
7343 shadows.AppendElement(sh);
7344 }
7345 }
7346 if (!shadows.IsEmpty()) {
7347 nscoord startEdge = iOffset;
7348 if (mTextRun->IsInlineReversed()) {
7349 startEdge -=
7350 hyphenWidth + mTextRun->GetAdvanceWidth(range, aParams.provider);
7351 }
7352 shadowParams.foregroundColor = foreground;
7353 shadowParams.textBaselinePt = textBaselinePt;
7354 shadowParams.framePt = aParams.framePt;
7355 shadowParams.leftSideOffset = startEdge;
7356 shadowParams.range = range;
7357 for (const Span<const StyleSimpleShadow>& shadowSpan : shadows) {
7358 PaintShadows(shadowSpan, shadowParams, aImgParams);
7359 }
7360 }
7361
7362 // Draw text segment. -webkit-text-stroke-* are not applicable to highlight
7363 // pseudo-elements per
7364 // https://drafts.csswg.org/css-pseudo-4/#highlight-styling. Suppress stroke
7365 // for highlighted segments; non-highlighted segments (eNone) still use the
7366 // originating element's stroke.
7367 const bool isUnselected = selectionTypes.Length() == 1 &&
7368 selectionTypes[0] == SelectionType::eNone;
7369 params.textColor = foreground;
7370 params.textStrokeColor =
7371 isUnselected ? aParams.textPaintStyle->GetWebkitTextStrokeColor() : 0;
7372 params.textStrokeWidth =
7373 isUnselected ? aParams.textPaintStyle->GetWebkitTextStrokeWidth()
7374 : 0.0f;
7375 params.drawSoftHyphen = hyphenWidth > 0;
7376 DrawText(range, textBaselinePt, params, aImgParams);
7377 advance += hyphenWidth;
7378 iterator.UpdateWithAdvance(advance);
7379 }
7380 return true;
7381}
7382
7383void nsTextFrame::PaintTextSelectionDecorations(
7384 const PaintTextSelectionParams& aParams, const SelectionDetails& aDetails,
7385 SelectionType aSelectionType) {
7386 // Hide text decorations if we're currently hiding @font-face fallback text
7387 if (aParams.provider->GetFontGroup()->ShouldSkipDrawing()) {
7388 return;
7389 }
7390
7391 AutoTArray<PriorityOrderedSelectionsForRange, 8> selectionRanges;
7392 ResolveSelections(aParams, aDetails, selectionRanges, aSelectionType);
7393
7394 RefPtr<gfxFont> firstFont =
7395 aParams.provider->GetFontGroup()->GetFirstValidFont();
7396 bool verticalRun = mTextRun->IsVertical();
7397 bool useVerticalMetrics = verticalRun && mTextRun->UseCenterBaseline();
7398 bool rightUnderline = useVerticalMetrics && IsUnderlineRight(*Style());
7399 const auto kDecoration = rightUnderline ? StyleTextDecorationLine::OVERLINE
7400 : StyleTextDecorationLine::UNDERLINE;
7401 gfxFont::Metrics decorationMetrics(
7402 firstFont->GetMetrics(useVerticalMetrics ? nsFontMetrics::eVertical
7403 : nsFontMetrics::eHorizontal));
7404 decorationMetrics.underlineOffset =
7405 aParams.provider->GetFontGroup()->GetUnderlineOffset();
7406
7407 const gfxTextRun::Range& contentRange = aParams.contentRange;
7408 gfxFloat startIOffset = verticalRun
7409 ? aParams.textBaselinePt.y - aParams.framePt.y
7410 : aParams.textBaselinePt.x - aParams.framePt.x;
7411 SelectionRangeIterator iterator(selectionRanges, contentRange,
7412 *aParams.provider, mTextRun, startIOffset);
7413 gfxFloat iOffset, hyphenWidth;
7414 Range range;
7415 gfxFloat app = aParams.textPaintStyle->PresContext()->AppUnitsPerDevPixel();
7416 // Use the parent frame's writing mode for decoration positioning, matching
7417 // DrawTextRunAndDecorations. This matters when IsCentralBaseline() is true
7418 // (e.g. vertical-lr), where GetLogicalBaseline(parentWM) differs from
7419 // mAscent.
7420 const WritingMode parentWM = GetParent()->GetWritingMode();
7421 const bool verticalDec = parentWM.IsVertical();
7422 gfxFloat decorationAscent = gfxFloat(GetLogicalBaseline(parentWM)) / app;
7423 gfxFloat frameBStart = verticalDec ? aParams.framePt.x : aParams.framePt.y;
7424 if (parentWM.IsVerticalRL()) {
7425 frameBStart += GetSize().width;
7426 decorationAscent = -decorationAscent;
7427 }
7428 Point pt;
7429 if (verticalRun) {
7430 pt.x = frameBStart / app;
7431 } else {
7432 pt.y = frameBStart / app;
7433 }
7434 AutoTArray<SelectionType, 1> nextSelectionTypes;
7435 AutoTArray<RefPtr<nsAtom>, 1> highlightNames;
7436 AutoTArray<TextRangeStyle, 1> selectedStyles;
7437
7438 while (iterator.GetNextSegment(&iOffset, &range, &hyphenWidth,
7439 nextSelectionTypes, highlightNames,
7440 selectedStyles)) {
7441 gfxFloat advance =
7442 hyphenWidth + mTextRun->GetAdvanceWidth(range, aParams.provider);
7443 for (size_t index = 0; index < nextSelectionTypes.Length(); ++index) {
7444 if (nextSelectionTypes[index] == aSelectionType) {
7445 if (verticalRun) {
7446 pt.y = (aParams.framePt.y + iOffset -
7447 (mTextRun->IsInlineReversed() ? advance : 0)) /
7448 app;
7449 } else {
7450 pt.x = (aParams.framePt.x + iOffset -
7451 (mTextRun->IsInlineReversed() ? advance : 0)) /
7452 app;
7453 }
7454 gfxFloat width = Abs(advance) / app;
7455 gfxFloat xInFrame = pt.x - (aParams.framePt.x / app);
7456 DrawSelectionDecorations(
7457 aParams.context, aParams.dirtyRect, aSelectionType,
7458 highlightNames[index], *aParams.textPaintStyle,
7459 selectedStyles[index], pt, xInFrame, width, decorationAscent,
7460 decorationMetrics, aParams.callbacks, verticalRun, kDecoration,
7461 aParams.glyphRange, aParams.provider);
7462 }
7463 }
7464 iterator.UpdateWithAdvance(advance);
7465 }
7466}
7467
7468bool nsTextFrame::PaintTextWithSelection(
7469 const PaintTextSelectionParams& aParams, const ClipEdges& aClipEdges,
7470 const SelectionDetails& aDetails, imgDrawingParams& aImgParams) {
7471 NS_ASSERTION(GetContent()->IsMaybeSelected(), "wrong paint path")do { if (!(GetContent()->IsMaybeSelected())) { NS_DebugBreak
(NS_DEBUG_ASSERTION, "wrong paint path", "GetContent()->IsMaybeSelected()"
, "./../../../layout/generic/nsTextFrame.cpp", 7471); MOZ_PretendNoReturn
(); } } while (0)
;
7472
7473 SelectionTypeMask allSelectionTypeMask;
7474 if (!PaintTextWithSelectionColors(aParams, aDetails, &allSelectionTypeMask,
7475 aClipEdges, aImgParams)) {
7476 return false;
7477 }
7478 // Iterate through just the selection rawSelectionTypes that paint decorations
7479 // and paint decorations for any that actually occur in this frame. Paint
7480 // higher-numbered selection rawSelectionTypes below lower-numered ones on the
7481 // general principal that lower-numbered selections are higher priority.
7482 allSelectionTypeMask &= kSelectionTypesWithDecorations;
7483 MOZ_ASSERT(kPresentSelectionTypes[0] == SelectionType::eNormal,do { static_assert( mozilla::detail::AssertionConditionType<
decltype(kPresentSelectionTypes[0] == SelectionType::eNormal)
>::isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(kPresentSelectionTypes[0] == SelectionType::eNormal)
)), 0))) { do { } while (false); MOZ_ReportAssertionFailure("kPresentSelectionTypes[0] == SelectionType::eNormal"
" (" "The following for loop assumes that the first item of "
"kPresentSelectionTypes is SelectionType::eNormal" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 7485); AnnotateMozCrashReason("MOZ_ASSERT" "(" "kPresentSelectionTypes[0] == SelectionType::eNormal"
") (" "The following for loop assumes that the first item of "
"kPresentSelectionTypes is SelectionType::eNormal" ")"); do {
MOZ_CrashSequence(__null, 7485); __attribute__((nomerge)) ::
abort(); } while (false); } } while (false)
7484 "The following for loop assumes that the first item of "do { static_assert( mozilla::detail::AssertionConditionType<
decltype(kPresentSelectionTypes[0] == SelectionType::eNormal)
>::isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(kPresentSelectionTypes[0] == SelectionType::eNormal)
)), 0))) { do { } while (false); MOZ_ReportAssertionFailure("kPresentSelectionTypes[0] == SelectionType::eNormal"
" (" "The following for loop assumes that the first item of "
"kPresentSelectionTypes is SelectionType::eNormal" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 7485); AnnotateMozCrashReason("MOZ_ASSERT" "(" "kPresentSelectionTypes[0] == SelectionType::eNormal"
") (" "The following for loop assumes that the first item of "
"kPresentSelectionTypes is SelectionType::eNormal" ")"); do {
MOZ_CrashSequence(__null, 7485); __attribute__((nomerge)) ::
abort(); } while (false); } } while (false)
7485 "kPresentSelectionTypes is SelectionType::eNormal")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(kPresentSelectionTypes[0] == SelectionType::eNormal)
>::isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(kPresentSelectionTypes[0] == SelectionType::eNormal)
)), 0))) { do { } while (false); MOZ_ReportAssertionFailure("kPresentSelectionTypes[0] == SelectionType::eNormal"
" (" "The following for loop assumes that the first item of "
"kPresentSelectionTypes is SelectionType::eNormal" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 7485); AnnotateMozCrashReason("MOZ_ASSERT" "(" "kPresentSelectionTypes[0] == SelectionType::eNormal"
") (" "The following for loop assumes that the first item of "
"kPresentSelectionTypes is SelectionType::eNormal" ")"); do {
MOZ_CrashSequence(__null, 7485); __attribute__((nomerge)) ::
abort(); } while (false); } } while (false)
;
7486
7487 Span presentTypes(kPresentSelectionTypes);
7488 for (SelectionType selectionType : Reversed(presentTypes)) {
7489 if (ToSelectionTypeMask(selectionType) & allSelectionTypeMask) {
7490 // There is some selection of this selectionType. Try to paint its
7491 // decorations (there might not be any for this type but that's OK,
7492 // PaintTextSelectionDecorations will exit early).
7493 PaintTextSelectionDecorations(aParams, aDetails, selectionType);
7494 }
7495 }
7496
7497 return true;
7498}
7499
7500void nsTextFrame::DrawEmphasisMarks(gfxContext* aContext, WritingMode aWM,
7501 const gfx::Point& aTextBaselinePt,
7502 const gfx::Point& aFramePt, Range aRange,
7503 const nscolor* aDecorationOverrideColor,
7504 PropertyProvider* aProvider,
7505 image::imgDrawingParams& aImgParams) {
7506 const EmphasisMarkInfo* info = GetProperty(EmphasisMarkProperty());
7507 if (!info) {
7508 return;
7509 }
7510
7511 bool isTextCombined = Style()->IsTextCombined();
7512 if (isTextCombined && !aWM.IsVertical()) {
7513 // XXX This only happens when the parent is display:contents with an
7514 // orthogonal writing mode. This should be rare, and don't have use
7515 // cases, so we don't care. It is non-trivial to implement a sane
7516 // behavior for that case: if you treat the text as not combined,
7517 // the marks would spread wider than the text (which is rendered as
7518 // combined); if you try to draw a single mark, selecting part of
7519 // the text could dynamically create multiple new marks.
7520 NS_WARNING("Give up on combined text with horizontal wm")NS_DebugBreak(NS_DEBUG_WARNING, "Give up on combined text with horizontal wm"
, nullptr, "./../../../layout/generic/nsTextFrame.cpp", 7520)
;
7521 return;
7522 }
7523 nscolor color =
7524 aDecorationOverrideColor
7525 ? *aDecorationOverrideColor
7526 : nsLayoutUtils::GetTextColor(this, &nsStyleText::mTextEmphasisColor);
7527 aContext->SetColor(sRGBColor::FromABGR(color));
7528 gfx::Point pt;
7529 if (!isTextCombined) {
7530 pt = aTextBaselinePt;
7531 } else {
7532 MOZ_ASSERT(aWM.IsVertical())do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aWM.IsVertical())>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(aWM.IsVertical()))), 0))) { do
{ } while (false); MOZ_ReportAssertionFailure("aWM.IsVertical()"
, "./../../../layout/generic/nsTextFrame.cpp", 7532); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "aWM.IsVertical()" ")"); do { MOZ_CrashSequence
(__null, 7532); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
7533 pt = aFramePt;
7534 if (aWM.IsVerticalRL()) {
7535 pt.x += GetSize().width - GetLogicalBaseline(aWM);
7536 } else {
7537 pt.x += GetLogicalBaseline(aWM);
7538 }
7539 }
7540 if (!aWM.IsVertical()) {
7541 pt.y += info->baselineOffset;
7542 } else {
7543 if (aWM.IsVerticalRL()) {
7544 pt.x -= info->baselineOffset;
7545 } else {
7546 pt.x += info->baselineOffset;
7547 }
7548 }
7549 if (!isTextCombined) {
7550 mTextRun->DrawEmphasisMarks(aContext, info->textRun.get(), info->advance,
7551 pt, aRange, aProvider,
7552 PresContext()->FontPaletteCache(), aImgParams);
7553 } else {
7554 pt.y += (GetSize().height - info->advance) / 2;
7555 gfxTextRun::DrawParams params(aContext, PresContext()->FontPaletteCache());
7556 info->textRun->Draw(Range(info->textRun.get()), pt, params, aImgParams);
7557 }
7558}
7559
7560nscolor nsTextFrame::GetCaretColorAt(int32_t aOffset) {
7561 MOZ_ASSERT(aOffset >= 0, "aOffset must be positive")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aOffset >= 0)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(aOffset >= 0))), 0))) { do
{ } while (false); MOZ_ReportAssertionFailure("aOffset >= 0"
" (" "aOffset must be positive" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 7561); AnnotateMozCrashReason("MOZ_ASSERT" "(" "aOffset >= 0"
") (" "aOffset must be positive" ")"); do { MOZ_CrashSequence
(__null, 7561); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
7562
7563 nscolor result = nsIFrame::GetCaretColorAt(aOffset);
7564 gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
7565 PropertyProvider provider(this, iter, nsTextFrame::eInflated, mFontMetrics);
7566 int32_t contentOffset = provider.GetStart().GetOriginalOffset();
7567 int32_t contentLength = provider.GetOriginalLength();
7568 MOZ_ASSERT(do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aOffset >= contentOffset && aOffset <=
contentOffset + contentLength)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(aOffset >= contentOffset &&
aOffset <= contentOffset + contentLength))), 0))) { do { }
while (false); MOZ_ReportAssertionFailure("aOffset >= contentOffset && aOffset <= contentOffset + contentLength"
" (" "aOffset must be in the frame's range" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 7570); AnnotateMozCrashReason("MOZ_ASSERT" "(" "aOffset >= contentOffset && aOffset <= contentOffset + contentLength"
") (" "aOffset must be in the frame's range" ")"); do { MOZ_CrashSequence
(__null, 7570); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
7569 aOffset >= contentOffset && aOffset <= contentOffset + contentLength,do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aOffset >= contentOffset && aOffset <=
contentOffset + contentLength)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(aOffset >= contentOffset &&
aOffset <= contentOffset + contentLength))), 0))) { do { }
while (false); MOZ_ReportAssertionFailure("aOffset >= contentOffset && aOffset <= contentOffset + contentLength"
" (" "aOffset must be in the frame's range" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 7570); AnnotateMozCrashReason("MOZ_ASSERT" "(" "aOffset >= contentOffset && aOffset <= contentOffset + contentLength"
") (" "aOffset must be in the frame's range" ")"); do { MOZ_CrashSequence
(__null, 7570); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
7570 "aOffset must be in the frame's range")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aOffset >= contentOffset && aOffset <=
contentOffset + contentLength)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(aOffset >= contentOffset &&
aOffset <= contentOffset + contentLength))), 0))) { do { }
while (false); MOZ_ReportAssertionFailure("aOffset >= contentOffset && aOffset <= contentOffset + contentLength"
" (" "aOffset must be in the frame's range" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 7570); AnnotateMozCrashReason("MOZ_ASSERT" "(" "aOffset >= contentOffset && aOffset <= contentOffset + contentLength"
") (" "aOffset must be in the frame's range" ")"); do { MOZ_CrashSequence
(__null, 7570); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
7571
7572 int32_t offsetInFrame = aOffset - contentOffset;
7573 if (offsetInFrame < 0 || offsetInFrame >= contentLength) {
7574 return result;
7575 }
7576
7577 bool isSolidTextColor = true;
7578 if (IsInSVGTextSubtree()) {
7579 const nsStyleSVG* style = StyleSVG();
7580 if (!style->mFill.kind.IsNone() && !style->mFill.kind.IsColor()) {
7581 isSolidTextColor = false;
7582 }
7583 }
7584
7585 nsTextPaintStyle textPaintStyle(this);
7586 textPaintStyle.SetResolveColors(isSolidTextColor);
7587 UniquePtr<SelectionDetails> details = GetSelectionDetails();
7588 SelectionType selectionType = SelectionType::eNone;
7589 for (SelectionDetails* sdptr = details.get(); sdptr;
7590 sdptr = sdptr->mNext.get()) {
7591 int32_t start = std::max(0, sdptr->mStart - contentOffset);
7592 int32_t end = std::min(contentLength, sdptr->mEnd - contentOffset);
7593 if (start <= offsetInFrame && offsetInFrame < end &&
7594 (selectionType == SelectionType::eNone ||
7595 sdptr->mSelectionType < selectionType)) {
7596 const auto colors = GetSelectionTextColors(
7597 sdptr->mSelectionType, sdptr->mHighlightData.mHighlightName,
7598 textPaintStyle, sdptr->mTextRangeStyle);
7599 if (colors.HasAnyColorImpact()) {
7600 if (!isSolidTextColor &&
7601 NS_IS_SELECTION_SPECIAL_COLOR(colors.mForeground)((colors.mForeground) == NS_TRANSPARENT || (colors.mForeground
) == NS_SAME_AS_FOREGROUND_COLOR || (colors.mForeground) == NS_40PERCENT_FOREGROUND_COLOR
)
) {
7602 result = NS_RGBA(0, 0, 0, 255)((nscolor)(((255) << 24) | ((0) << 16) | ((0) <<
8) | (0)))
;
7603 } else {
7604 result = colors.mForeground;
7605 }
7606 selectionType = sdptr->mSelectionType;
7607 }
7608 }
7609 }
7610
7611 return result;
7612}
7613
7614static gfxTextRun::Range ComputeTransformedRange(
7615 nsTextFrame::PropertyProvider& aProvider) {
7616 gfxSkipCharsIterator iter(aProvider.GetStart());
7617 uint32_t start = iter.GetSkippedOffset();
7618 iter.AdvanceOriginal(aProvider.GetOriginalLength());
7619 return gfxTextRun::Range(start, iter.GetSkippedOffset());
7620}
7621
7622bool nsTextFrame::MeasureCharClippedText(nscoord aVisIStartEdge,
7623 nscoord aVisIEndEdge,
7624 nscoord* aSnappedStartEdge,
7625 nscoord* aSnappedEndEdge) {
7626 // We need a *reference* rendering context (not one that might have a
7627 // transform), so we don't have a rendering context argument.
7628 // XXX get the block and line passed to us somehow! This is slow!
7629 gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
7630 if (!mTextRun) {
7631 return false;
7632 }
7633
7634 PropertyProvider provider(this, iter, nsTextFrame::eInflated, mFontMetrics);
7635 // Trim trailing whitespace
7636 provider.InitializeForDisplay(true);
7637
7638 Range range = ComputeTransformedRange(provider);
7639 uint32_t startOffset = range.start;
7640 uint32_t maxLength = range.Length();
7641 return MeasureCharClippedText(provider, aVisIStartEdge, aVisIEndEdge,
7642 &startOffset, &maxLength, aSnappedStartEdge,
7643 aSnappedEndEdge);
7644}
7645
7646static uint32_t GetClusterLength(const gfxTextRun* aTextRun,
7647 uint32_t aStartOffset, uint32_t aMaxLength) {
7648 uint32_t clusterLength = 0;
7649 while (++clusterLength < aMaxLength) {
7650 if (aTextRun->IsClusterStart(aStartOffset + clusterLength)) {
7651 return clusterLength;
7652 }
7653 }
7654 return aMaxLength;
7655}
7656
7657bool nsTextFrame::MeasureCharClippedText(
7658 PropertyProvider& aProvider, nscoord aVisIStartEdge, nscoord aVisIEndEdge,
7659 uint32_t* aStartOffset, uint32_t* aMaxLength, nscoord* aSnappedStartEdge,
7660 nscoord* aSnappedEndEdge) {
7661 *aSnappedStartEdge = 0;
7662 *aSnappedEndEdge = 0;
7663 if (aVisIStartEdge <= 0 && aVisIEndEdge <= 0) {
7664 return true;
7665 }
7666
7667 uint32_t offset = *aStartOffset;
7668 uint32_t maxLength = *aMaxLength;
7669 const nscoord frameISize = ISize();
7670 const bool rtl = mTextRun->IsRightToLeft();
7671 gfxFloat advanceWidth = 0;
7672 const nscoord startEdge = rtl ? aVisIEndEdge : aVisIStartEdge;
7673 if (startEdge > 0) {
7674 const gfxFloat maxAdvance = gfxFloat(startEdge);
7675 while (maxLength > 0) {
7676 uint32_t clusterLength = GetClusterLength(mTextRun, offset, maxLength);
7677 advanceWidth += mTextRun->GetAdvanceWidth(
7678 Range(offset, offset + clusterLength), &aProvider);
7679 maxLength -= clusterLength;
7680 offset += clusterLength;
7681 if (advanceWidth >= maxAdvance) {
7682 break;
7683 }
7684 }
7685 nscoord* snappedStartEdge = rtl ? aSnappedEndEdge : aSnappedStartEdge;
7686 *snappedStartEdge = NSToCoordFloor(advanceWidth);
7687 *aStartOffset = offset;
7688 }
7689
7690 const nscoord endEdge = rtl ? aVisIStartEdge : aVisIEndEdge;
7691 if (endEdge > 0) {
7692 const gfxFloat maxAdvance = gfxFloat(frameISize - endEdge);
7693 while (maxLength > 0) {
7694 uint32_t clusterLength = GetClusterLength(mTextRun, offset, maxLength);
7695 gfxFloat nextAdvance =
7696 advanceWidth + mTextRun->GetAdvanceWidth(
7697 Range(offset, offset + clusterLength), &aProvider);
7698 if (nextAdvance > maxAdvance) {
7699 break;
7700 }
7701 // This cluster fits, include it.
7702 advanceWidth = nextAdvance;
7703 maxLength -= clusterLength;
7704 offset += clusterLength;
7705 }
7706 maxLength = offset - *aStartOffset;
7707 nscoord* snappedEndEdge = rtl ? aSnappedStartEdge : aSnappedEndEdge;
7708 *snappedEndEdge = NSToCoordFloor(gfxFloat(frameISize) - advanceWidth);
7709 }
7710 *aMaxLength = maxLength;
7711 return maxLength != 0;
7712}
7713
7714void nsTextFrame::PaintShadows(Span<const StyleSimpleShadow> aShadows,
7715 const PaintShadowParams& aParams,
7716 imgDrawingParams& aImgParams) {
7717 if (aShadows.IsEmpty()) {
7718 return;
7719 }
7720
7721 gfxTextRun::Metrics shadowMetrics = mTextRun->MeasureText(
7722 aParams.range, gfxFont::LOOSE_INK_EXTENTS, nullptr, aParams.provider);
7723 if (GetWritingMode().IsLineInverted()) {
7724 std::swap(shadowMetrics.mAscent, shadowMetrics.mDescent);
7725 shadowMetrics.mBoundingBox.y = -shadowMetrics.mBoundingBox.YMost();
7726 }
7727 if (HasAnyStateBits(TEXT_HYPHEN_BREAK)) {
7728 AddHyphenToMetrics(this, mTextRun->IsRightToLeft(), &shadowMetrics,
7729 gfxFont::LOOSE_INK_EXTENTS,
7730 aParams.context->GetDrawTarget());
7731 }
7732 // Add bounds of text decorations
7733 gfxRect decorationRect(0, -shadowMetrics.mAscent, shadowMetrics.mAdvanceWidth,
7734 shadowMetrics.mAscent + shadowMetrics.mDescent);
7735 shadowMetrics.mBoundingBox.UnionRect(shadowMetrics.mBoundingBox,
7736 decorationRect);
7737
7738 // If the textrun uses any color or SVG fonts, we need to force use of a mask
7739 // for shadow rendering even if blur radius is zero.
7740 uint32_t blurFlags = 0;
7741 uint32_t numGlyphRuns;
7742 const gfxTextRun::GlyphRun* run = mTextRun->GetGlyphRuns(&numGlyphRuns);
7743 while (numGlyphRuns-- > 0) {
7744 if (run->mFont->AlwaysNeedsMaskForShadow()) {
7745 blurFlags |= nsContextBoxBlur::FORCE_MASK;
7746 break;
7747 }
7748 run++;
7749 }
7750
7751 if (mTextRun->IsVertical()) {
7752 std::swap(shadowMetrics.mBoundingBox.x, shadowMetrics.mBoundingBox.y);
7753 std::swap(shadowMetrics.mBoundingBox.width,
7754 shadowMetrics.mBoundingBox.height);
7755 }
7756
7757 for (const auto& shadow : Reversed(aShadows)) {
7758 PaintOneShadow(aParams, shadow, shadowMetrics.mBoundingBox, blurFlags,
7759 aImgParams);
7760 }
7761}
7762
7763void nsTextFrame::PaintText(const PaintTextParams& aParams,
7764 const nscoord aVisIStartEdge,
7765 const nscoord aVisIEndEdge,
7766 const nsPoint& aToReferenceFrame,
7767 const bool aIsSelected,
7768 imgDrawingParams& aImgParams,
7769 float aOpacity /* = 1.0f */) {
7770#ifdef DEBUG1
7771 if (IsInSVGTextSubtree()) {
7772 auto* container =
7773 nsLayoutUtils::GetClosestFrameOfType(this, LayoutFrameType::SVGText);
7774 MOZ_ASSERT(container)do { static_assert( mozilla::detail::AssertionConditionType<
decltype(container)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(container))), 0))) { do { } while
(false); MOZ_ReportAssertionFailure("container", "./../../../layout/generic/nsTextFrame.cpp"
, 7774); AnnotateMozCrashReason("MOZ_ASSERT" "(" "container" ")"
); do { MOZ_CrashSequence(__null, 7774); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
7775 MOZ_ASSERT(!container->HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD) ||do { static_assert( mozilla::detail::AssertionConditionType<
decltype(!container->HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD
) || !aParams.IsPaintText())>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(!container->HasAnyStateBits
(NS_STATE_SVG_CLIPPATH_CHILD) || !aParams.IsPaintText()))), 0
))) { do { } while (false); MOZ_ReportAssertionFailure("!container->HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD) || !aParams.IsPaintText()"
" (" "Expecting IsPaintText to be false for a clipPath" ")",
"./../../../layout/generic/nsTextFrame.cpp", 7777); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "!container->HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD) || !aParams.IsPaintText()"
") (" "Expecting IsPaintText to be false for a clipPath" ")"
); do { MOZ_CrashSequence(__null, 7777); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
7776 !aParams.IsPaintText(),do { static_assert( mozilla::detail::AssertionConditionType<
decltype(!container->HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD
) || !aParams.IsPaintText())>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(!container->HasAnyStateBits
(NS_STATE_SVG_CLIPPATH_CHILD) || !aParams.IsPaintText()))), 0
))) { do { } while (false); MOZ_ReportAssertionFailure("!container->HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD) || !aParams.IsPaintText()"
" (" "Expecting IsPaintText to be false for a clipPath" ")",
"./../../../layout/generic/nsTextFrame.cpp", 7777); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "!container->HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD) || !aParams.IsPaintText()"
") (" "Expecting IsPaintText to be false for a clipPath" ")"
); do { MOZ_CrashSequence(__null, 7777); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
7777 "Expecting IsPaintText to be false for a clipPath")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(!container->HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD
) || !aParams.IsPaintText())>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(!container->HasAnyStateBits
(NS_STATE_SVG_CLIPPATH_CHILD) || !aParams.IsPaintText()))), 0
))) { do { } while (false); MOZ_ReportAssertionFailure("!container->HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD) || !aParams.IsPaintText()"
" (" "Expecting IsPaintText to be false for a clipPath" ")",
"./../../../layout/generic/nsTextFrame.cpp", 7777); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "!container->HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD) || !aParams.IsPaintText()"
") (" "Expecting IsPaintText to be false for a clipPath" ")"
); do { MOZ_CrashSequence(__null, 7777); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
7778 }
7779#endif
7780
7781 // Don't pass in the rendering context here, because we need a
7782 // *reference* context and rendering context might have some transform
7783 // in it
7784 // XXX get the block and line passed to us somehow! This is slow!
7785 gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
7786 if (!mTextRun) {
7787 return;
7788 }
7789
7790 PropertyProvider provider(this, iter, nsTextFrame::eInflated, mFontMetrics);
7791
7792 // Trim trailing whitespace, unless we have a normal selection (bug 1146754)
7793 // This matches behavior in other browsers.
7794 UniquePtr<SelectionDetails> selectionDetails;
7795 bool hasNormalSelection = false;
7796 if (aIsSelected) {
7797 selectionDetails = GetSelectionDetails();
7798 for (const SelectionDetails* sd = selectionDetails.get(); sd;
7799 sd = sd->mNext.get()) {
7800 if (sd->mSelectionType == SelectionType::eNormal) {
7801 hasNormalSelection = true;
7802 break;
7803 }
7804 }
7805 }
7806 provider.InitializeForDisplay(!hasNormalSelection);
7807
7808 const bool reversed = mTextRun->IsInlineReversed();
7809 const bool verticalRun = mTextRun->IsVertical();
7810 WritingMode wm = GetWritingMode();
7811 const float frameWidth = GetSize().width;
7812 const float frameHeight = GetSize().height;
7813 gfx::Point textBaselinePt;
7814 if (verticalRun) {
7815 if (wm.IsVerticalLR()) {
7816 textBaselinePt.x = nsLayoutUtils::GetMaybeSnappedBaselineX(
7817 this, aParams.context, nscoord(aParams.framePt.x), mAscent);
7818 } else {
7819 textBaselinePt.x = nsLayoutUtils::GetMaybeSnappedBaselineX(
7820 this, aParams.context, nscoord(aParams.framePt.x) + frameWidth,
7821 -mAscent);
7822 }
7823 textBaselinePt.y = reversed ? aParams.framePt.y.value + frameHeight
7824 : aParams.framePt.y.value;
7825 } else {
7826 textBaselinePt =
7827 gfx::Point(reversed ? aParams.framePt.x.value + frameWidth
7828 : aParams.framePt.x.value,
7829 nsLayoutUtils::GetMaybeSnappedBaselineY(
7830 this, aParams.context, aParams.framePt.y, mAscent));
7831 }
7832 Range range = ComputeTransformedRange(provider);
7833 uint32_t startOffset = range.start;
7834 uint32_t maxLength = range.Length();
7835 nscoord snappedStartEdge, snappedEndEdge;
7836 if (!MeasureCharClippedText(provider, aVisIStartEdge, aVisIEndEdge,
7837 &startOffset, &maxLength, &snappedStartEdge,
7838 &snappedEndEdge)) {
7839 return;
7840 }
7841 if (verticalRun) {
7842 textBaselinePt.y += reversed ? -snappedEndEdge : snappedStartEdge;
7843 } else {
7844 textBaselinePt.x += reversed ? -snappedEndEdge : snappedStartEdge;
7845 }
7846 const ClipEdges clipEdges(this, aToReferenceFrame, snappedStartEdge,
7847 snappedEndEdge);
7848 nsTextPaintStyle textPaintStyle(this);
7849 textPaintStyle.SetResolveColors(!aParams.callbacks);
7850
7851 // Fork off to the (slower) paint-with-selection path if necessary.
7852 if (aIsSelected && selectionDetails) {
7853 MOZ_ASSERT(aOpacity == 1.0f, "We don't support opacity with selections!")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aOpacity == 1.0f)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(aOpacity == 1.0f))), 0))) { do
{ } while (false); MOZ_ReportAssertionFailure("aOpacity == 1.0f"
" (" "We don't support opacity with selections!" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 7853); AnnotateMozCrashReason("MOZ_ASSERT" "(" "aOpacity == 1.0f"
") (" "We don't support opacity with selections!" ")"); do {
MOZ_CrashSequence(__null, 7853); __attribute__((nomerge)) ::
abort(); } while (false); } } while (false)
;
7854 gfxSkipCharsIterator tmp(provider.GetStart());
7855 Range contentRange(
7856 uint32_t(tmp.ConvertSkippedToOriginal(startOffset)),
7857 uint32_t(tmp.ConvertSkippedToOriginal(startOffset + maxLength)));
7858 PaintTextSelectionParams params(aParams);
7859 params.textBaselinePt = textBaselinePt;
7860 params.provider = &provider;
7861 params.contentRange = contentRange;
7862 params.textPaintStyle = &textPaintStyle;
7863 params.glyphRange = range;
7864 if (PaintTextWithSelection(params, clipEdges, *selectionDetails,
7865 aImgParams)) {
7866 return;
7867 }
7868 }
7869
7870 nscolor foregroundColor = aParams.IsGenerateTextMask()
7871 ? NS_RGBA(0, 0, 0, 255)((nscolor)(((255) << 24) | ((0) << 16) | ((0) <<
8) | (0)))
7872 : textPaintStyle.GetTextColor();
7873 if (aOpacity != 1.0f) {
7874 gfx::sRGBColor gfxColor = gfx::sRGBColor::FromABGR(foregroundColor);
7875 gfxColor.a *= aOpacity;
7876 foregroundColor = gfxColor.ToABGR();
7877 }
7878
7879 nscolor textStrokeColor = aParams.IsGenerateTextMask()
7880 ? NS_RGBA(0, 0, 0, 255)((nscolor)(((255) << 24) | ((0) << 16) | ((0) <<
8) | (0)))
7881 : textPaintStyle.GetWebkitTextStrokeColor();
7882 if (aOpacity != 1.0f) {
7883 gfx::sRGBColor gfxColor = gfx::sRGBColor::FromABGR(textStrokeColor);
7884 gfxColor.a *= aOpacity;
7885 textStrokeColor = gfxColor.ToABGR();
7886 }
7887
7888 range = Range(startOffset, startOffset + maxLength);
7889 if (aParams.IsPaintText()) {
7890 const nsStyleText* textStyle = StyleText();
7891 PaintShadowParams shadowParams(aParams);
7892 shadowParams.range = range;
7893 shadowParams.textBaselinePt = textBaselinePt;
7894 shadowParams.leftSideOffset = snappedStartEdge;
7895 shadowParams.provider = &provider;
7896 shadowParams.callbacks = aParams.callbacks;
7897 shadowParams.foregroundColor = foregroundColor;
7898 shadowParams.clipEdges = &clipEdges;
7899 PaintShadows(textStyle->mTextShadow.AsSpan(), shadowParams, aImgParams);
7900 }
7901
7902 gfxFloat advanceWidth;
7903 DrawTextParams params(aParams.context, PresContext()->FontPaletteCache());
7904 params.dirtyRect = aParams.dirtyRect;
7905 params.framePt = aParams.framePt;
7906 params.provider = &provider;
7907 params.advanceWidth = &advanceWidth;
7908 params.textStyle = &textPaintStyle;
7909 params.textColor = foregroundColor;
7910 params.textStrokeColor = textStrokeColor;
7911 params.textStrokeWidth = textPaintStyle.GetWebkitTextStrokeWidth();
7912 params.clipEdges = &clipEdges;
7913 params.drawSoftHyphen = HasAnyStateBits(TEXT_HYPHEN_BREAK);
7914 params.contextPaint = aParams.contextPaint;
7915 params.callbacks = aParams.callbacks;
7916 params.glyphRange = range;
7917 params.fontPalette = StyleFont()->GetFontPaletteAtom();
7918 params.hasTextShadow = !StyleText()->mTextShadow.IsEmpty();
7919
7920 DrawText(range, textBaselinePt, params, aImgParams);
7921}
7922
7923static void DrawTextRun(const gfxTextRun* aTextRun,
7924 const gfx::Point& aTextBaselinePt,
7925 gfxTextRun::Range aRange,
7926 const nsTextFrame::DrawTextRunParams& aParams,
7927 mozilla::image::imgDrawingParams& aImgParams,
7928 nsTextFrame* aFrame) {
7929 gfxTextRun::DrawParams params(aParams.context, aParams.paletteCache);
7930 params.provider = aParams.provider;
7931 params.advanceWidth = aParams.advanceWidth;
7932 params.contextPaint = aParams.contextPaint;
7933 params.fontPalette = aParams.fontPalette;
7934 params.callbacks = aParams.callbacks;
7935 params.hasTextShadow = aParams.hasTextShadow;
7936 if (aParams.callbacks) {
7937 aParams.callbacks->NotifyBeforeText(aParams.paintingShadows,
7938 aParams.textColor);
7939 params.drawMode = DrawMode::GLYPH_PATH;
7940 aTextRun->Draw(aRange, aTextBaselinePt, params, aImgParams);
7941 aParams.callbacks->NotifyAfterText();
7942 } else {
7943 auto* textDrawer = aParams.context->GetTextDrawer();
7944 if (NS_GET_A(aParams.textColor)((uint8_t)(((aParams.textColor) >> 24) & 0xff)) != 0 || textDrawer ||
7945 aParams.textStrokeWidth == 0.0f) {
7946 aParams.context->SetColor(sRGBColor::FromABGR(aParams.textColor));
7947 } else {
7948 params.drawMode = DrawMode::GLYPH_STROKE;
7949 }
7950
7951 if ((NS_GET_A(aParams.textStrokeColor)((uint8_t)(((aParams.textStrokeColor) >> 24) & 0xff
))
!= 0 || textDrawer) &&
7952 aParams.textStrokeWidth != 0.0f) {
7953 if (textDrawer) {
7954 textDrawer->FoundUnsupportedFeature();
7955 return;
7956 }
7957 params.drawMode |= DrawMode::GLYPH_STROKE;
7958
7959 // Check the paint-order property; if we find stroke before fill,
7960 // then change mode to GLYPH_STROKE_UNDERNEATH.
7961 uint32_t paintOrder = aFrame->StyleSVG()->mPaintOrder;
7962 while (paintOrder) {
7963 auto component = StylePaintOrder(paintOrder & kPaintOrderMask);
7964 switch (component) {
7965 case StylePaintOrder::Fill:
7966 // Just break the loop, no need to check further
7967 paintOrder = 0;
7968 break;
7969 case StylePaintOrder::Stroke:
7970 params.drawMode |= DrawMode::GLYPH_STROKE_UNDERNEATH;
7971 paintOrder = 0;
7972 break;
7973 default:
7974 MOZ_FALLTHROUGH_ASSERT("Unknown paint-order variant, how?")do { do { } while (false); MOZ_ReportCrash("" "MOZ_FALLTHROUGH_ASSERT: "
"Unknown paint-order variant, how?", "./../../../layout/generic/nsTextFrame.cpp"
, 7974); AnnotateMozCrashReason("MOZ_CRASH(" "MOZ_FALLTHROUGH_ASSERT: "
"Unknown paint-order variant, how?" ")"); do { MOZ_CrashSequence
(__null, 7974); __attribute__((nomerge)) ::abort(); } while (
false); } while (false)
;
7975 case StylePaintOrder::Markers:
7976 case StylePaintOrder::Normal:
7977 break;
7978 }
7979 paintOrder >>= kPaintOrderShift;
7980 }
7981
7982 // Use ROUND joins as they are less likely to produce ugly artifacts
7983 // when stroking glyphs with sharp angles (see bug 1546985).
7984 StrokeOptions strokeOpts(aParams.textStrokeWidth, JoinStyle::ROUND);
7985 params.textStrokeColor = aParams.textStrokeColor;
7986 params.strokeOpts = &strokeOpts;
7987 aTextRun->Draw(aRange, aTextBaselinePt, params, aImgParams);
7988 } else {
7989 aTextRun->Draw(aRange, aTextBaselinePt, params, aImgParams);
7990 }
7991 }
7992}
7993
7994void nsTextFrame::DrawTextRun(Range aRange, const gfx::Point& aTextBaselinePt,
7995 const DrawTextRunParams& aParams,
7996 imgDrawingParams& aImgParams) {
7997 MOZ_ASSERT(aParams.advanceWidth, "Must provide advanceWidth")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aParams.advanceWidth)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(aParams.advanceWidth))), 0))
) { do { } while (false); MOZ_ReportAssertionFailure("aParams.advanceWidth"
" (" "Must provide advanceWidth" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 7997); AnnotateMozCrashReason("MOZ_ASSERT" "(" "aParams.advanceWidth"
") (" "Must provide advanceWidth" ")"); do { MOZ_CrashSequence
(__null, 7997); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
7998
7999 ::DrawTextRun(mTextRun, aTextBaselinePt, aRange, aParams, aImgParams, this);
8000
8001 if (aParams.drawSoftHyphen) {
8002 // Don't use ctx as the context, because we need a reference context here,
8003 // ctx may be transformed.
8004 DrawTextRunParams params = aParams;
8005 params.provider = nullptr;
8006 params.advanceWidth = nullptr;
8007 RefPtr<gfxTextRun> hyphenTextRun = GetHyphenTextRun(this, nullptr);
8008 if (hyphenTextRun) {
8009 gfx::Point p(aTextBaselinePt);
8010 bool vertical = GetWritingMode().IsVertical();
8011 // For right-to-left text runs, the soft-hyphen is positioned at the left
8012 // of the text.
8013 float shift = mTextRun->GetDirection() * (*aParams.advanceWidth);
8014 if (vertical) {
8015 p.y += shift;
8016 } else {
8017 p.x += shift;
8018 }
8019 ::DrawTextRun(hyphenTextRun.get(), p, Range(hyphenTextRun.get()), params,
8020 aImgParams, this);
8021 }
8022 }
8023}
8024
8025void nsTextFrame::DrawTextRunAndDecorations(Range aRange,
8026 const gfx::Point& aTextBaselinePt,
8027 const DrawTextParams& aParams,
8028 const TextDecorations& aDecorations,
8029 imgDrawingParams& aImgParams) {
8030 const gfxFloat app = aParams.textStyle->PresContext()->AppUnitsPerDevPixel();
8031 // Writing mode of parent frame is used because the text frame may
8032 // be orthogonal to its parent when text-combine-upright is used or
8033 // its parent has "display: contents", and in those cases, we want
8034 // to draw the decoration lines according to parents' direction
8035 // rather than ours.
8036 const WritingMode wm = GetParent()->GetWritingMode();
8037 bool verticalDec = wm.IsVertical();
8038 bool verticalRun = mTextRun->IsVertical();
8039 // If the text run and the decoration is orthogonal, we choose the
8040 // metrics for decoration so that decoration line won't be broken.
8041 bool useVerticalMetrics = verticalDec != verticalRun
8042 ? verticalDec
8043 : verticalRun && mTextRun->UseCenterBaseline();
8044
8045 // XXX aFramePt is in AppUnits, shouldn't it be nsFloatPoint?
8046 nscoord x = NSToCoordRound(aParams.framePt.x);
8047 nscoord y = NSToCoordRound(aParams.framePt.y);
8048
8049 // 'measure' here is textrun-relative, so for a horizontal run it's the
8050 // width, while for a vertical run it's the height of the decoration
8051 const nsSize frameSize = GetSize();
8052 nscoord measure = verticalDec ? frameSize.height : frameSize.width;
8053
8054 if (verticalDec) {
8055 aParams.clipEdges->Intersect(&y, &measure);
8056 } else {
8057 aParams.clipEdges->Intersect(&x, &measure);
8058 }
8059
8060 // decSize is a textrun-relative size, so its 'width' field is actually
8061 // the run-relative measure, and 'height' will be the line thickness
8062 gfxFloat ascent = gfxFloat(GetLogicalBaseline(wm)) / app;
8063 // The starting edge of the frame in block direction
8064 gfxFloat frameBStart = verticalDec ? aParams.framePt.x : aParams.framePt.y;
8065
8066 // In vertical-rl mode, block coordinates are measured from the
8067 // right, so we need to adjust here.
8068 if (wm.IsVerticalRL()) {
8069 frameBStart += frameSize.width;
8070 ascent = -ascent;
8071 }
8072
8073 nscoord inflationMinFontSize = nsLayoutUtils::InflationMinFontSizeFor(this);
8074
8075 PaintDecorationLineParams params;
8076 params.context = aParams.context;
8077 params.dirtyRect = aParams.dirtyRect;
8078 params.overrideColor = aParams.decorationOverrideColor;
8079 params.callbacks = aParams.callbacks;
8080 params.glyphRange = aParams.glyphRange;
8081 params.provider = aParams.provider;
8082 params.paintingShadows = aParams.paintingShadows;
8083 // pt is the physical point where the decoration is to be drawn,
8084 // relative to the frame; one of its coordinates will be updated below.
8085 params.pt = Point(x / app, y / app);
8086 Float& bCoord = verticalDec ? params.pt.x.value : params.pt.y.value;
8087 params.lineSize = Size(measure / app, 0);
8088 params.ascent = ascent;
8089 params.vertical = verticalDec;
8090 params.sidewaysLeft = mTextRun->IsSidewaysLeft();
8091
8092 // The matrix of the context may have been altered for text-combine-
8093 // upright. However, we want to draw decoration lines unscaled, thus
8094 // we need to revert the scaling here.
8095 gfxContextMatrixAutoSaveRestore scaledRestorer;
8096 if (Style()->IsTextCombined()) {
8097 float scaleFactor = GetTextCombineScale();
8098 if (scaleFactor != 1.0f) {
8099 scaledRestorer.SetContext(aParams.context);
8100 gfxMatrix unscaled = aParams.context->CurrentMatrixDouble();
8101 gfxPoint pt(x / app, y / app);
8102 if (GetTextRun(nsTextFrame::eInflated)->IsRightToLeft()) {
8103 pt.x += gfxFloat(frameSize.width) / app;
8104 }
8105 unscaled.PreTranslate(pt)
8106 .PreScale(1.0f / scaleFactor, 1.0f)
8107 .PreTranslate(-pt);
8108 aParams.context->SetMatrixDouble(unscaled);
8109 }
8110 }
8111
8112 // We create a clip region in order to draw the decoration lines only in the
8113 // range of the text. Restricting the draw area prevents the decoration lines
8114 // to be drawn multiple times when a part of the text is selected.
8115 Maybe<gfxRect> clipRect;
8116
8117 // We skip clipping for the following cases:
8118 // - drawing the whole text
8119 // - having different orientation of the text and the writing-mode, such as
8120 // "text-combine-upright" (Bug 1408825)
8121 if (aRange.Length() != mTextRun->GetLength() && verticalDec == verticalRun) {
8122 // Get the inline-size according to the specified range.
8123 gfxFloat clipLength = mTextRun->GetAdvanceWidth(aRange, aParams.provider);
8124 nsRect visualRect = InkOverflowRect();
8125
8126 const bool isInlineReversed = mTextRun->IsInlineReversed();
8127 gfxFloat x, y, w, h;
8128 if (verticalDec) {
8129 x = aParams.framePt.x + visualRect.x;
8130 y = isInlineReversed ? aTextBaselinePt.y.value - clipLength
8131 : aTextBaselinePt.y.value;
8132 w = visualRect.width;
8133 h = clipLength;
8134 } else {
8135 x = isInlineReversed ? aTextBaselinePt.x.value - clipLength
8136 : aTextBaselinePt.x.value;
8137 y = aParams.framePt.y + visualRect.y;
8138 w = clipLength;
8139 h = visualRect.height;
8140 }
8141 clipRect.emplace(x, y, w, h);
8142 clipRect->Scale(1 / app);
8143 // Round the inline axis to nearest: that's the axis the clip exists for,
8144 // and it keeps the clips of adjacent ranges tiling exactly, so a decoration
8145 // is neither dropped nor drawn twice where two ranges meet. The block axis
8146 // only bounds the rect - it spans the ink overflow rect, which contains the
8147 // decoration - so round it outward instead. An edge rounded inward there
8148 // can land on the device pixel row the line was snapped to, and then the
8149 // line is culled entirely rather than trimmed (bug 2059455).
8150 gfxRect inlineRounded = *clipRect;
8151 inlineRounded.Round();
8152 clipRect->RoundOut();
8153 if (verticalDec) {
8154 clipRect->y = inlineRounded.y;
8155 clipRect->height = inlineRounded.height;
8156 } else {
8157 clipRect->x = inlineRounded.x;
8158 clipRect->width = inlineRounded.width;
8159 }
8160 }
8161
8162 typedef gfxFont::Metrics Metrics;
8163 auto paintDecorationLine = [&](const LineDecoration& dec,
8164 gfxFloat Metrics::* lineSize,
8165 StyleTextDecorationLine lineType) {
8166 if (dec.mStyle == StyleTextDecorationStyle::None) {
8167 return;
8168 }
8169
8170 float inflation =
8171 GetInflationForTextDecorations(dec.mFrame, inflationMinFontSize);
8172 const Metrics metrics = GetFirstFontMetrics(
8173 GetFontGroupForFrame(dec.mFrame, inflation), useVerticalMetrics);
8174 if (!ComputeDecorationInset(this, aParams.textStyle->PresContext(),
8175 dec.mFrame, metrics, params)) {
8176 return;
8177 }
8178 bCoord = (frameBStart - dec.mBaselineOffset) / app;
8179
8180 params.color = dec.mColor;
8181 params.baselineOffset = dec.mBaselineOffset / app;
8182 params.defaultLineThickness = metrics.*lineSize;
8183 params.lineSize.height = ComputeDecorationLineThickness(
8184 dec.mTextDecorationThickness, params.defaultLineThickness, metrics, app,
8185 dec.mFrame);
8186
8187 bool swapUnderline = wm.IsCentralBaseline() && IsUnderlineRight(*Style());
8188 params.offset = ComputeDecorationLineOffset(
8189 lineType, dec.mTextUnderlinePosition, dec.mTextUnderlineOffset, metrics,
8190 app, dec.mFrame, wm.IsCentralBaseline(), swapUnderline);
8191
8192 params.style = dec.mStyle;
8193 params.allowInkSkipping = dec.mAllowInkSkipping;
8194 params.skipInk = StyleText()->mTextDecorationSkipInk;
8195 gfxClipAutoSaveRestore clipRestore(params.context);
8196 // If we have a negative inset value, then the decoration will extend
8197 // outside the edges of the text.
8198 // TODO alaskanemily: Ideally we would adjust the clipping rect, but as
8199 // an initial pass we just disable clipping in this case.
8200 if (clipRect && !params.HasNegativeInset()) {
8201 clipRestore.Clip(*clipRect);
8202 }
8203 PaintDecorationLine(params);
8204 };
8205
8206 // Underlines
8207 params.decoration = StyleTextDecorationLine::UNDERLINE;
8208 for (const LineDecoration& dec : Reversed(aDecorations.mUnderlines)) {
8209 paintDecorationLine(dec, &Metrics::underlineSize, params.decoration);
8210 }
8211
8212 // Overlines
8213 params.decoration = StyleTextDecorationLine::OVERLINE;
8214 for (const LineDecoration& dec : Reversed(aDecorations.mOverlines)) {
8215 paintDecorationLine(dec, &Metrics::underlineSize, params.decoration);
8216 }
8217
8218 // Some glyphs and emphasis marks may extend outside the region, so we do
8219 // not set the clip region here to the clip rect. For an example, italic
8220 // glyphs.
8221
8222 {
8223 gfxContextMatrixAutoSaveRestore unscaledRestorer;
8224 if (scaledRestorer.HasMatrix()) {
8225 unscaledRestorer.SetContext(aParams.context);
8226 aParams.context->SetMatrix(scaledRestorer.Matrix());
8227 }
8228
8229 // CSS 2.1 mandates that text be painted after over/underlines,
8230 // and *then* line-throughs
8231 DrawTextRun(aRange, aTextBaselinePt, aParams, aImgParams);
8232 }
8233
8234 // Emphasis marks
8235 DrawEmphasisMarks(aParams.context, wm, aTextBaselinePt, aParams.framePt,
8236 aRange, aParams.decorationOverrideColor, aParams.provider,
8237 aImgParams);
8238
8239 // Line-throughs
8240 params.decoration = StyleTextDecorationLine::LINE_THROUGH;
8241 for (const LineDecoration& dec : Reversed(aDecorations.mStrikes)) {
8242 paintDecorationLine(dec, &Metrics::strikeoutSize, params.decoration);
8243 }
8244}
8245
8246void nsTextFrame::DrawText(Range aRange, const gfx::Point& aTextBaselinePt,
8247 const DrawTextParams& aParams,
8248 imgDrawingParams& aImgParams) {
8249 TextDecorations decorations;
8250 GetTextDecorations(aParams.textStyle->PresContext(),
8251 aParams.callbacks ? eUnresolvedColors : eResolvedColors,
8252 decorations);
8253
8254 // Hide text decorations if we're currently hiding @font-face fallback text
8255 const bool drawDecorations =
8256 !aParams.provider->GetFontGroup()->ShouldSkipDrawing() &&
8257 (decorations.HasDecorationLines() ||
8258 StyleText()->HasEffectiveTextEmphasis());
8259 if (drawDecorations) {
8260 DrawTextRunAndDecorations(aRange, aTextBaselinePt, aParams, decorations,
8261 aImgParams);
8262 } else {
8263 DrawTextRun(aRange, aTextBaselinePt, aParams, aImgParams);
8264 }
8265
8266 if (auto* textDrawer = aParams.context->GetTextDrawer()) {
8267 textDrawer->TerminateShadows();
8268 }
8269}
8270
8271NS_DECLARE_FRAME_PROPERTY_DELETABLE(WebRenderTextBounds, nsRect)static const mozilla::FramePropertyDescriptor<nsRect>* WebRenderTextBounds
() { static const auto descriptor = mozilla::FramePropertyDescriptor
<nsRect>::NewWithDestructor<DeleteValue>(); return
&descriptor; }
8272
8273nsRect nsTextFrame::WebRenderBounds() {
8274 // WR text bounds is just our ink overflow rect but without shadows. So if we
8275 // have no shadows, just use the layout bounds.
8276 if (!StyleText()->HasTextShadow()) {
8277 return InkOverflowRect();
8278 }
8279 nsRect* cachedBounds = GetProperty(WebRenderTextBounds());
8280 if (!cachedBounds) {
8281 OverflowAreas overflowAreas;
8282 ComputeCustomOverflowInternal(overflowAreas, false);
8283 cachedBounds = new nsRect(overflowAreas.InkOverflow());
8284 SetProperty(WebRenderTextBounds(), cachedBounds);
8285 }
8286 return *cachedBounds;
8287}
8288
8289int16_t nsTextFrame::GetSelectionStatus(int16_t* aSelectionFlags) {
8290 nsISelectionController* const selCon = GetSelectionController();
8291 if (MOZ_UNLIKELY(!selCon)(__builtin_expect(!!(!selCon), 0))) {
8292 return nsISelectionController::SELECTION_OFF;
8293 }
8294
8295 selCon->GetSelectionFlags(aSelectionFlags);
8296
8297 int16_t selectionValue = nsISelectionController::SELECTION_OFF;
8298 selCon->GetDisplaySelection(&selectionValue);
8299 return selectionValue;
8300}
8301
8302bool nsTextFrame::IsEntirelyWhitespace() const {
8303 const auto& characterDataBuffer = mContent->AsText()->DataBuffer();
8304 for (uint32_t index = 0; index < characterDataBuffer.GetLength(); ++index) {
8305 const char16_t ch = characterDataBuffer.CharAt(index);
8306 if (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' || ch == 0xa0) {
8307 continue;
8308 }
8309 return false;
8310 }
8311 return true;
8312}
8313
8314/**
8315 * Compute the longest prefix of text whose width is <= aWidth. Return
8316 * the length of the prefix. Also returns the width of the prefix in aFitWidth.
8317 */
8318static uint32_t CountCharsFit(const gfxTextRun* aTextRun,
8319 gfxTextRun::Range aRange, gfxFloat aWidth,
8320 nsTextFrame::PropertyProvider* aProvider,
8321 gfxFloat* aFitWidth) {
8322 uint32_t last = 0;
8323 gfxFloat width = 0;
8324 for (uint32_t i = 1; i <= aRange.Length(); ++i) {
8325 if (i == aRange.Length() || aTextRun->IsClusterStart(aRange.start + i)) {
8326 gfxTextRun::Range range(aRange.start + last, aRange.start + i);
8327 gfxFloat nextWidth = width + aTextRun->GetAdvanceWidth(range, aProvider);
8328 if (nextWidth > aWidth) {
8329 break;
8330 }
8331 last = i;
8332 width = nextWidth;
8333 }
8334 }
8335 *aFitWidth = width;
8336 return last;
8337}
8338
8339nsIFrame::ContentOffsets nsTextFrame::CalcContentOffsetsFromFramePoint(
8340 const nsPoint& aPoint) {
8341 return GetCharacterOffsetAtFramePointInternal(aPoint, true);
8342}
8343
8344nsIFrame::ContentOffsets nsTextFrame::GetCharacterOffsetAtFramePoint(
8345 const nsPoint& aPoint) {
8346 return GetCharacterOffsetAtFramePointInternal(aPoint, false);
8347}
8348
8349nsIFrame::ContentOffsets nsTextFrame::GetCharacterOffsetAtFramePointInternal(
8350 const nsPoint& aPoint, bool aForInsertionPoint) {
8351 ContentOffsets offsets;
8352
8353 gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
8354 if (!mTextRun) {
8355 return offsets;
8356 }
8357
8358 PropertyProvider provider(this, iter, nsTextFrame::eInflated, mFontMetrics);
8359 // Trim leading but not trailing whitespace if possible
8360 provider.InitializeForDisplay(false);
8361 gfxFloat width =
8362 mTextRun->IsVertical()
8363 ? (mTextRun->IsInlineReversed() ? mRect.height - aPoint.y : aPoint.y)
8364 : (mTextRun->IsInlineReversed() ? mRect.width - aPoint.x : aPoint.x);
8365 if (Style()->IsTextCombined()) {
8366 width /= GetTextCombineScale();
8367 }
8368 gfxFloat fitWidth;
8369 Range skippedRange = ComputeTransformedRange(provider);
8370
8371 uint32_t charsFit =
8372 CountCharsFit(mTextRun, skippedRange, width, &provider, &fitWidth);
8373
8374 int32_t selectedOffset;
8375 if (charsFit < skippedRange.Length()) {
8376 // charsFit characters fitted, but no more could fit. See if we're
8377 // more than halfway through the cluster.. If we are, choose the next
8378 // cluster.
8379 gfxSkipCharsIterator extraCluster(provider.GetStart());
8380 extraCluster.AdvanceSkipped(charsFit);
8381
8382 bool allowSplitLigature = true; // Allow selection of partial ligature...
8383
8384 // ...but don't let selection/insertion-point split two Regional Indicator
8385 // chars that are ligated in the textrun to form a single flag symbol.
8386 uint32_t offs = extraCluster.GetOriginalOffset();
8387 const auto& characterDataBuffer = CharacterDataBuffer();
8388 if (characterDataBuffer.IsHighSurrogateFollowedByLowSurrogateAt(offs) &&
8389 gfxFontUtils::IsRegionalIndicator(
8390 characterDataBuffer.ScalarValueAt(offs))) {
8391 allowSplitLigature = false;
8392 if (extraCluster.GetSkippedOffset() >= skippedRange.start + 2 &&
8393 !mTextRun->IsLigatureGroupStart(extraCluster.GetSkippedOffset())) {
8394 // CountCharsFit() left us in the middle of the flag; back up over the
8395 // first character of the ligature, and adjust fitWidth accordingly.
8396 extraCluster.AdvanceSkipped(-2); // it's a surrogate pair: 2 code units
8397 fitWidth -= mTextRun->GetAdvanceWidth(
8398 Range(extraCluster.GetSkippedOffset(),
8399 extraCluster.GetSkippedOffset() + 2),
8400 &provider);
8401 }
8402 }
8403
8404 gfxSkipCharsIterator extraClusterLastChar(extraCluster);
8405 FindClusterEnd(
8406 mTextRun,
8407 provider.GetStart().GetOriginalOffset() + provider.GetOriginalLength(),
8408 &extraClusterLastChar, allowSplitLigature);
8409 PropertyProvider::Spacing spacing;
8410 Range extraClusterRange(extraCluster.GetSkippedOffset(),
8411 extraClusterLastChar.GetSkippedOffset() + 1);
8412 gfxFloat charWidth =
8413 mTextRun->GetAdvanceWidth(extraClusterRange, &provider, &spacing);
8414 charWidth -= spacing.mBefore + spacing.mAfter;
8415 selectedOffset = !aForInsertionPoint ||
8416 width <= fitWidth + spacing.mBefore + charWidth / 2
8417 ? extraCluster.GetOriginalOffset()
8418 : extraClusterLastChar.GetOriginalOffset() + 1;
8419 } else {
8420 // All characters fitted, we're at (or beyond) the end of the text.
8421 // XXX This could be some pathological situation where negative spacing
8422 // caused characters to move backwards. We can't really handle that
8423 // in the current frame system because frames can't have negative
8424 // intrinsic widths.
8425 selectedOffset =
8426 provider.GetStart().GetOriginalOffset() + provider.GetOriginalLength();
8427 // If we're at the end of a preformatted line which has a terminating
8428 // linefeed, we want to reduce the offset by one to make sure that the
8429 // selection is placed before the linefeed character.
8430 if (HasSignificantTerminalNewline()) {
8431 --selectedOffset;
8432 }
8433 }
8434
8435 offsets.content = GetContent();
8436 offsets.offset = offsets.secondaryOffset = selectedOffset;
8437 offsets.associate = mContentOffset == offsets.offset
8438 ? CaretAssociationHint::After
8439 : CaretAssociationHint::Before;
8440 return offsets;
8441}
8442
8443bool nsTextFrame::CombineSelectionUnderlineRect(nsPresContext* aPresContext,
8444 nsRect& aRect) {
8445 if (aRect.IsEmpty()) {
8446 return false;
8447 }
8448
8449 nsRect givenRect = aRect;
8450
8451 gfxFontGroup* fontGroup = GetInflatedFontGroupForFrame(this);
8452 RefPtr<gfxFont> firstFont = fontGroup->GetFirstValidFont();
8453 WritingMode wm = GetWritingMode();
8454 bool verticalRun = wm.IsVertical();
8455 bool useVerticalMetrics = verticalRun && !wm.IsSideways();
8456 const gfxFont::Metrics& metrics =
8457 firstFont->GetMetrics(useVerticalMetrics ? nsFontMetrics::eVertical
8458 : nsFontMetrics::eHorizontal);
8459
8460 nsCSSRendering::DecorationRectParams params;
8461 params.ascent = aPresContext->AppUnitsToGfxUnits(mAscent);
8462
8463 params.offset = fontGroup->GetUnderlineOffset();
8464
8465 TextDecorations textDecs;
8466 GetTextDecorations(aPresContext, eResolvedColors, textDecs);
8467
8468 params.descentLimit =
8469 ComputeDescentLimitForSelectionUnderline(aPresContext, metrics);
8470 params.vertical = verticalRun;
8471
8472 if (verticalRun) {
8473 EnsureTextRun(nsTextFrame::eInflated);
8474 params.sidewaysLeft = mTextRun ? mTextRun->IsSidewaysLeft() : false;
8475 } else {
8476 params.sidewaysLeft = false;
8477 }
8478
8479 UniquePtr<SelectionDetails> details = GetSelectionDetails();
8480 for (SelectionDetails* sd = details.get(); sd; sd = sd->mNext.get()) {
8481 if (sd->mStart == sd->mEnd ||
8482 sd->mSelectionType == SelectionType::eInvalid ||
8483 !(ToSelectionTypeMask(sd->mSelectionType) &
8484 kSelectionTypesWithDecorations) ||
8485 // URL strikeout does not use underline.
8486 sd->mSelectionType == SelectionType::eURLStrikeout) {
8487 continue;
8488 }
8489 float relativeSize = 1.f;
8490 RefPtr<ComputedStyle> style = Style();
8491
8492 if (sd->mSelectionType == SelectionType::eNormal ||
8493 sd->mSelectionType == SelectionType::eTargetText ||
8494 sd->mSelectionType == SelectionType::eHighlight) {
8495 style = [&]() {
8496 if (sd->mSelectionType == SelectionType::eHighlight) {
8497 return ComputeHighlightSelectionStyle(
8498 sd->mHighlightData.mHighlightName);
8499 }
8500 if (sd->mSelectionType == SelectionType::eTargetText) {
8501 return ComputeTargetTextStyle();
8502 }
8503 int16_t unusedFlags = 0;
8504 const int16_t selectionStatus = GetSelectionStatus(&unusedFlags);
8505 return ComputeSelectionStyle(selectionStatus);
8506 }();
8507 if (!style || !style->HasTextDecorationLines()) {
8508 continue;
8509 }
8510 // For pseudo-based selection styles (::selection, ::highlight,
8511 // ::target-text), compute a decoration rect for each line type declared
8512 // by the pseudo's style, mirroring the logic in DrawSelectionDecorations.
8513 // This must use the pseudo's own text-underline-position and
8514 // text-underline-offset (not the originating element's), and pass the
8515 // correct decoration line type so that ComputeDecorationLineOffset
8516 // applies the offset to underlines rather than treating them as
8517 // overlines.
8518 // descentLimit must be negative to disable lifting, matching
8519 // DrawSelectionDecorations which also disables it for pseudo-based
8520 // selection styles (the offset is explicitly specified by the author).
8521 const auto* styleTextReset = style->StyleTextReset();
8522 const auto& decThickness = styleTextReset->mTextDecorationThickness;
8523 params.lineSize.width = aPresContext->AppUnitsToGfxUnits(aRect.width);
8524 params.style = styleTextReset->mTextDecorationStyle;
8525 params.descentLimit = -1.f;
8526 const bool swapUnderline =
8527 wm.IsCentralBaseline() && IsUnderlineRight(*style);
8528 const auto* styleText = style->StyleText();
8529 auto accumForLine = [&](StyleTextDecorationLine decoration) {
8530 if (!(styleTextReset->mTextDecorationLine & decoration)) {
8531 return;
8532 }
8533 params.decoration = decoration;
8534 params.offset = ComputeDecorationLineOffset(
8535 decoration, styleText->mTextUnderlinePosition,
8536 styleText->mTextUnderlineOffset, metrics,
8537 aPresContext->AppUnitsPerDevPixel(), this, wm.IsCentralBaseline(),
8538 swapUnderline);
8539
8540 if (decoration == StyleTextDecorationLine::LINE_THROUGH) {
8541 params.defaultLineThickness = metrics.strikeoutSize;
8542 } else {
8543 params.defaultLineThickness = ComputeSelectionUnderlineHeight(
8544 aPresContext, metrics, sd->mSelectionType);
8545 }
8546 params.lineSize.height = ComputeDecorationLineThickness(
8547 decThickness, params.defaultLineThickness, metrics,
8548 aPresContext->AppUnitsPerDevPixel(), this);
8549
8550 nsRect decorationArea =
8551 nsCSSRendering::GetTextDecorationRect(aPresContext, params);
8552 aRect.UnionRect(aRect, decorationArea);
8553 };
8554 accumForLine(StyleTextDecorationLine::UNDERLINE);
8555 accumForLine(StyleTextDecorationLine::OVERLINE);
8556 accumForLine(StyleTextDecorationLine::LINE_THROUGH);
8557 } else {
8558 auto index = nsTextPaintStyle::GetUnderlineStyleIndexForSelectionType(
8559 sd->mSelectionType);
8560 if (sd->mSelectionType == SelectionType::eSpellCheck) {
8561 if (!nsTextPaintStyle::GetSelectionUnderline(
8562 this, index, nullptr, &relativeSize, &params.style)) {
8563 continue;
8564 }
8565 } else {
8566 // IME selections
8567 TextRangeStyle& rangeStyle = sd->mTextRangeStyle;
8568 if (rangeStyle.IsDefined()) {
8569 if (!rangeStyle.IsLineStyleDefined() ||
8570 rangeStyle.mLineStyle == TextRangeStyle::LineStyle::None) {
8571 continue;
8572 }
8573 params.style = ToStyleLineStyle(rangeStyle);
8574 relativeSize = rangeStyle.mIsBoldLine ? 2.0f : 1.0f;
8575 } else if (!nsTextPaintStyle::GetSelectionUnderline(
8576 this, index, nullptr, &relativeSize, &params.style)) {
8577 continue;
8578 }
8579 }
8580 }
8581 nsRect decorationArea;
8582
8583 const auto& decThickness =
8584 style->StyleTextReset()->mTextDecorationThickness;
8585 params.lineSize.width = aPresContext->AppUnitsToGfxUnits(aRect.width);
8586 params.defaultLineThickness = ComputeSelectionUnderlineHeight(
8587 aPresContext, metrics, sd->mSelectionType);
8588
8589 params.lineSize.height = ComputeDecorationLineThickness(
8590 decThickness, params.defaultLineThickness, metrics,
8591 aPresContext->AppUnitsPerDevPixel(), this);
8592
8593 bool swapUnderline = wm.IsCentralBaseline() && IsUnderlineRight(*style);
8594 const auto* styleText = style->StyleText();
8595 params.offset = ComputeDecorationLineOffset(
8596 textDecs.HasUnderline() ? StyleTextDecorationLine::UNDERLINE
8597 : StyleTextDecorationLine::OVERLINE,
8598 styleText->mTextUnderlinePosition, styleText->mTextUnderlineOffset,
8599 metrics, aPresContext->AppUnitsPerDevPixel(), this,
8600 wm.IsCentralBaseline(), swapUnderline);
8601
8602 relativeSize = std::max(relativeSize, 1.0f);
8603 params.lineSize.height *= relativeSize;
8604 params.defaultLineThickness *= relativeSize;
8605 decorationArea =
8606 nsCSSRendering::GetTextDecorationRect(aPresContext, params);
8607 aRect.UnionRect(aRect, decorationArea);
8608 }
8609
8610 return !aRect.IsEmpty() && !givenRect.Contains(aRect);
8611}
8612
8613bool nsTextFrame::IsFrameSelected() const {
8614 NS_ASSERTION(!GetContent() || GetContent()->IsMaybeSelected(),do { if (!(!GetContent() || GetContent()->IsMaybeSelected(
))) { NS_DebugBreak(NS_DEBUG_ASSERTION, "use the public IsSelected() instead"
, "!GetContent() || GetContent()->IsMaybeSelected()", "./../../../layout/generic/nsTextFrame.cpp"
, 8615); MOZ_PretendNoReturn(); } } while (0)
8615 "use the public IsSelected() instead")do { if (!(!GetContent() || GetContent()->IsMaybeSelected(
))) { NS_DebugBreak(NS_DEBUG_ASSERTION, "use the public IsSelected() instead"
, "!GetContent() || GetContent()->IsMaybeSelected()", "./../../../layout/generic/nsTextFrame.cpp"
, 8615); MOZ_PretendNoReturn(); } } while (0)
;
8616 if (mIsSelected == nsTextFrame::SelectionState::Unknown) {
8617 const bool isSelected =
8618 GetContent()->IsSelected(GetContentOffset(), GetContentEnd(),
8619 PresShell()->GetSelectionNodeCache());
8620 mIsSelected = isSelected ? nsTextFrame::SelectionState::Selected
8621 : nsTextFrame::SelectionState::NotSelected;
8622 } else {
8623#ifdef DEBUG1
8624 // Assert that the selection caching works.
8625 const bool isReallySelected =
8626 GetContent()->IsSelected(GetContentOffset(), GetContentEnd());
8627 MOZ_ASSERT((mIsSelected == nsTextFrame::SelectionState::Selected) ==do { static_assert( mozilla::detail::AssertionConditionType<
decltype((mIsSelected == nsTextFrame::SelectionState::Selected
) == isReallySelected)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!((mIsSelected == nsTextFrame::
SelectionState::Selected) == isReallySelected))), 0))) { do {
} while (false); MOZ_ReportAssertionFailure("(mIsSelected == nsTextFrame::SelectionState::Selected) == isReallySelected"
" (" "Should have called InvalidateSelectionState()" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 8629); AnnotateMozCrashReason("MOZ_ASSERT" "(" "(mIsSelected == nsTextFrame::SelectionState::Selected) == isReallySelected"
") (" "Should have called InvalidateSelectionState()" ")"); do
{ MOZ_CrashSequence(__null, 8629); __attribute__((nomerge)) ::
abort(); } while (false); } } while (false)
8628 isReallySelected,do { static_assert( mozilla::detail::AssertionConditionType<
decltype((mIsSelected == nsTextFrame::SelectionState::Selected
) == isReallySelected)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!((mIsSelected == nsTextFrame::
SelectionState::Selected) == isReallySelected))), 0))) { do {
} while (false); MOZ_ReportAssertionFailure("(mIsSelected == nsTextFrame::SelectionState::Selected) == isReallySelected"
" (" "Should have called InvalidateSelectionState()" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 8629); AnnotateMozCrashReason("MOZ_ASSERT" "(" "(mIsSelected == nsTextFrame::SelectionState::Selected) == isReallySelected"
") (" "Should have called InvalidateSelectionState()" ")"); do
{ MOZ_CrashSequence(__null, 8629); __attribute__((nomerge)) ::
abort(); } while (false); } } while (false)
8629 "Should have called InvalidateSelectionState()")do { static_assert( mozilla::detail::AssertionConditionType<
decltype((mIsSelected == nsTextFrame::SelectionState::Selected
) == isReallySelected)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!((mIsSelected == nsTextFrame::
SelectionState::Selected) == isReallySelected))), 0))) { do {
} while (false); MOZ_ReportAssertionFailure("(mIsSelected == nsTextFrame::SelectionState::Selected) == isReallySelected"
" (" "Should have called InvalidateSelectionState()" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 8629); AnnotateMozCrashReason("MOZ_ASSERT" "(" "(mIsSelected == nsTextFrame::SelectionState::Selected) == isReallySelected"
") (" "Should have called InvalidateSelectionState()" ")"); do
{ MOZ_CrashSequence(__null, 8629); __attribute__((nomerge)) ::
abort(); } while (false); } } while (false)
;
8630#endif
8631 }
8632
8633 return mIsSelected == nsTextFrame::SelectionState::Selected;
8634}
8635
8636nsTextFrame* nsTextFrame::FindContinuationForOffset(int32_t aOffset) {
8637 // Use a continuations array to accelerate finding the first continuation
8638 // of interest, if possible.
8639 MOZ_ASSERT(!GetPrevContinuation(), "should be called on the primary frame")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(!GetPrevContinuation())>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(!GetPrevContinuation()))), 0
))) { do { } while (false); MOZ_ReportAssertionFailure("!GetPrevContinuation()"
" (" "should be called on the primary frame" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 8639); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!GetPrevContinuation()"
") (" "should be called on the primary frame" ")"); do { MOZ_CrashSequence
(__null, 8639); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
8640 auto* continuations = GetContinuations();
8641 nsTextFrame* f = this;
8642 if (continuations) {
8643 size_t index;
8644 if (BinarySearchIf(
8645 *continuations, 0, continuations->Length(),
8646 [=](nsTextFrame* aFrame) -> int {
8647 return aOffset - aFrame->GetContentOffset();
8648 },
8649 &index)) {
8650 f = (*continuations)[index];
8651 } else {
8652 f = (*continuations)[index ? index - 1 : 0];
8653 }
8654 }
8655
8656 while (f && f->GetContentEnd() <= aOffset) {
8657 f = f->GetNextContinuation();
8658 }
8659
8660 return f;
8661}
8662
8663void nsTextFrame::SelectionStateChanged(uint32_t aStart, uint32_t aEnd,
8664 bool aSelected,
8665 SelectionType aSelectionType) {
8666 NS_ASSERTION(!GetPrevContinuation(),do { if (!(!GetPrevContinuation())) { NS_DebugBreak(NS_DEBUG_ASSERTION
, "Should only be called for primary frame", "!GetPrevContinuation()"
, "./../../../layout/generic/nsTextFrame.cpp", 8667); MOZ_PretendNoReturn
(); } } while (0)
8667 "Should only be called for primary frame")do { if (!(!GetPrevContinuation())) { NS_DebugBreak(NS_DEBUG_ASSERTION
, "Should only be called for primary frame", "!GetPrevContinuation()"
, "./../../../layout/generic/nsTextFrame.cpp", 8667); MOZ_PretendNoReturn
(); } } while (0)
;
8668 DEBUG_VERIFY_NOT_DIRTY(GetStateBits());
8669
8670 InvalidateSelectionState();
8671
8672 // Selection is collapsed, which can't affect text frame rendering
8673 if (aStart == aEnd) {
8674 return;
8675 }
8676
8677 nsTextFrame* f = FindContinuationForOffset(aStart);
8678
8679 nsPresContext* presContext = PresContext();
8680 while (f && f->GetContentOffset() < int32_t(aEnd)) {
8681 // We may need to reflow to recompute the overflow area for spellchecking or
8682 // IME underline if their underline is thicker than the normal decoration
8683 // line.
8684 // FIXME(emilio): Text shadows would need similar treatment, wouldn't they?
8685 if (ToSelectionTypeMask(aSelectionType) & kSelectionTypesWithDecorations &&
8686 !f->HasAnyStateBits(NS_FRAME_IS_DIRTY)) {
8687 const bool didHaveOverflowingSelection =
8688 f->HasAnyStateBits(TEXT_SELECTION_UNDERLINE_OVERFLOWED);
8689 nsRect r(nsPoint(), GetSize());
8690 if (didHaveOverflowingSelection ||
8691 (aSelected && f->CombineSelectionUnderlineRect(presContext, r))) {
8692 // TODO(emilio): If we allow text to store changes or so, this could use
8693 // PostRestyleEvent(..., UpdateOverflow);
8694 presContext->PresShell()->FrameNeedsReflow(f, IntrinsicDirty::None,
8695 NS_FRAME_IS_DIRTY);
8696 }
8697 }
8698 // Selection might change anything. Invalidate the overflow area.
8699 f->InvalidateFrame();
8700
8701 f = f->GetNextContinuation();
8702 }
8703}
8704
8705void nsTextFrame::UpdateIteratorFromOffset(const PropertyProvider& aProperties,
8706 int32_t& aInOffset,
8707 gfxSkipCharsIterator& aIter) {
8708 if (aInOffset < GetContentOffset()) {
8709 NS_WARNING("offset before this frame's content")NS_DebugBreak(NS_DEBUG_WARNING, "offset before this frame's content"
, nullptr, "./../../../layout/generic/nsTextFrame.cpp", 8709)
;
8710 aInOffset = GetContentOffset();
8711 } else if (aInOffset > GetContentEnd()) {
8712 NS_WARNING("offset after this frame's content")NS_DebugBreak(NS_DEBUG_WARNING, "offset after this frame's content"
, nullptr, "./../../../layout/generic/nsTextFrame.cpp", 8712)
;
8713 aInOffset = GetContentEnd();
8714 }
8715
8716 int32_t trimmedOffset = aProperties.GetStart().GetOriginalOffset();
8717 int32_t trimmedEnd = trimmedOffset + aProperties.GetOriginalLength();
8718 aInOffset = std::max(aInOffset, trimmedOffset);
8719 aInOffset = std::min(aInOffset, trimmedEnd);
8720
8721 aIter.SetOriginalOffset(aInOffset);
8722
8723 if (aInOffset < trimmedEnd && !aIter.IsOriginalCharSkipped() &&
8724 !mTextRun->IsClusterStart(aIter.GetSkippedOffset())) {
8725 // Called for non-cluster boundary
8726 FindClusterStart(mTextRun, trimmedOffset, &aIter);
8727 }
8728}
8729
8730nsPoint nsTextFrame::GetPointFromIterator(const gfxSkipCharsIterator& aIter,
8731 PropertyProvider& aProperties) {
8732 Range range(aProperties.GetStart().GetSkippedOffset(),
8733 aIter.GetSkippedOffset());
8734 gfxFloat advance = mTextRun->GetAdvanceWidth(range, &aProperties);
8735 nscoord iSize = NSToCoordCeilClamped(advance);
8736 nsPoint point;
8737
8738 if (mTextRun->IsVertical()) {
8739 point.x = 0;
8740 if (mTextRun->IsInlineReversed()) {
8741 point.y = mRect.height - iSize;
8742 } else {
8743 point.y = iSize;
8744 }
8745 } else {
8746 point.y = 0;
8747 if (Style()->IsTextCombined()) {
8748 iSize *= GetTextCombineScale();
8749 }
8750 if (mTextRun->IsInlineReversed()) {
8751 point.x = mRect.width - iSize;
8752 } else {
8753 point.x = iSize;
8754 }
8755 }
8756 return point;
8757}
8758
8759nsresult nsTextFrame::GetPointFromOffset(int32_t inOffset, nsPoint* outPoint) {
8760 if (!outPoint) {
8761 return NS_ERROR_NULL_POINTER;
8762 }
8763
8764 DEBUG_VERIFY_NOT_DIRTY(GetStateBits());
8765 if (HasAnyStateBits(NS_FRAME_IS_DIRTY)) {
8766 return NS_ERROR_UNEXPECTED;
8767 }
8768
8769 if (GetContentLength() <= 0) {
8770 outPoint->x = 0;
8771 outPoint->y = 0;
8772 return NS_OK;
8773 }
8774
8775 gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
8776 if (!mTextRun) {
8777 return NS_ERROR_FAILURE;
8778 }
8779
8780 PropertyProvider properties(this, iter, nsTextFrame::eInflated, mFontMetrics);
8781 // Don't trim trailing whitespace, we want the caret to appear in the right
8782 // place if it's positioned there
8783 properties.InitializeForDisplay(false);
8784
8785 UpdateIteratorFromOffset(properties, inOffset, iter);
8786
8787 *outPoint = GetPointFromIterator(iter, properties);
8788
8789 return NS_OK;
8790}
8791
8792nsresult nsTextFrame::GetCharacterRectsInRange(int32_t aInOffset,
8793 int32_t aLength,
8794 nsTArray<nsRect>& aRects) {
8795 DEBUG_VERIFY_NOT_DIRTY(GetStateBits());
8796 if (HasAnyStateBits(NS_FRAME_IS_DIRTY)) {
8797 return NS_ERROR_UNEXPECTED;
8798 }
8799
8800 if (GetContentLength() <= 0) {
8801 return NS_OK;
8802 }
8803
8804 if (!mTextRun) {
8805 return NS_ERROR_FAILURE;
8806 }
8807
8808 gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
8809 PropertyProvider properties(this, iter, nsTextFrame::eInflated, mFontMetrics);
8810 // Don't trim trailing whitespace, we want the caret to appear in the right
8811 // place if it's positioned there
8812 properties.InitializeForDisplay(false);
8813
8814 // Initialize iter; this will call FindClusterStart if necessary to align
8815 // iter to a cluster boundary.
8816 UpdateIteratorFromOffset(properties, aInOffset, iter);
8817 nsPoint point = GetPointFromIterator(iter, properties);
8818
8819 const int32_t kContentEnd = GetContentEnd();
8820 const int32_t kEndOffset = std::min(aInOffset + aLength, kContentEnd);
8821
8822 if (aInOffset >= kEndOffset) {
8823 return NS_OK;
8824 }
8825
8826 if (!aRects.SetCapacity(aRects.Length() + kEndOffset - aInOffset,
8827 mozilla::fallible)) {
8828 return NS_ERROR_OUT_OF_MEMORY;
8829 }
8830
8831 do {
8832 // We'd like to assert here that |point| matches
8833 // |GetPointFromIterator(iter, properties)|, which in principle should be
8834 // true; however, testcases with vast dimensions can lead to coordinate
8835 // overflow and disrupt the calculations. So we've dropped the assertion
8836 // to avoid tripping the fuzzer unnecessarily.
8837
8838 // Measure to the end of the cluster.
8839 nscoord iSize = 0;
8840 gfxSkipCharsIterator nextIter(iter);
8841 if (aInOffset < kContentEnd) {
8842 nextIter.AdvanceOriginal(1);
8843 if (!nextIter.IsOriginalCharSkipped() &&
8844 !mTextRun->IsClusterStart(nextIter.GetSkippedOffset()) &&
8845 nextIter.GetOriginalOffset() < kContentEnd) {
8846 FindClusterEnd(mTextRun, kContentEnd, &nextIter);
8847 }
8848
8849 gfxFloat advance = mTextRun->GetAdvanceWidth(
8850 Range(iter.GetSkippedOffset(), nextIter.GetSkippedOffset()),
8851 &properties);
8852 iSize = NSToCoordCeilClamped(advance);
8853 }
8854
8855 // Compute the cluster rect, depending on directionality, and update
8856 // point to the origin we'll need for the next cluster.
8857 nsRect rect;
8858 rect.x = point.x;
8859 rect.y = point.y;
8860
8861 if (mTextRun->IsVertical()) {
8862 rect.width = mRect.width;
8863 rect.height = iSize;
8864 if (mTextRun->IsInlineReversed()) {
8865 // The iterator above returns a point with the origin at the
8866 // bottom left instead of the top left. Move the origin to the top left
8867 // by subtracting the character's height.
8868 rect.y -= rect.height;
8869 point.y -= iSize;
8870 } else {
8871 point.y += iSize;
8872 }
8873 } else {
8874 if (Style()->IsTextCombined()) {
8875 // The scale factor applies to the inline advance of the glyphs, so it
8876 // affects both the rect width and the origin point for the next glyph.
8877 iSize *= GetTextCombineScale();
8878 }
8879 rect.width = iSize;
8880 rect.height = mRect.height;
8881 if (mTextRun->IsInlineReversed()) {
8882 // The iterator above returns a point with the origin at the
8883 // top right instead of the top left. Move the origin to the top left by
8884 // subtracting the character's width.
8885 rect.x -= iSize;
8886 point.x -= iSize;
8887 } else {
8888 point.x += iSize;
8889 }
8890 }
8891
8892 // Set the rect for all characters in the cluster.
8893 int32_t end = std::min(kEndOffset, nextIter.GetOriginalOffset());
8894 while (aInOffset < end) {
8895 aRects.AppendElement(rect);
8896 aInOffset++;
8897 }
8898
8899 // Advance iter for the next cluster.
8900 iter = nextIter;
8901 } while (aInOffset < kEndOffset);
8902
8903 return NS_OK;
8904}
8905
8906nsresult nsTextFrame::GetChildFrameContainingOffset(int32_t aContentOffset,
8907 bool aHint,
8908 int32_t* aOutOffset,
8909 nsIFrame** aOutFrame) {
8910 DEBUG_VERIFY_NOT_DIRTY(GetStateBits());
8911#if 0 // XXXrbs disable due to bug 310227
8912 if (HasAnyStateBits(NS_FRAME_IS_DIRTY))
8913 return NS_ERROR_UNEXPECTED;
8914#endif
8915
8916 NS_ASSERTION(aOutOffset && aOutFrame, "Bad out parameters")do { if (!(aOutOffset && aOutFrame)) { NS_DebugBreak(
NS_DEBUG_ASSERTION, "Bad out parameters", "aOutOffset && aOutFrame"
, "./../../../layout/generic/nsTextFrame.cpp", 8916); MOZ_PretendNoReturn
(); } } while (0)
;
8917 NS_ASSERTION(aContentOffset >= 0,do { if (!(aContentOffset >= 0)) { NS_DebugBreak(NS_DEBUG_ASSERTION
, "Negative content offset, existing code was very broken!", "aContentOffset >= 0"
, "./../../../layout/generic/nsTextFrame.cpp", 8918); MOZ_PretendNoReturn
(); } } while (0)
8918 "Negative content offset, existing code was very broken!")do { if (!(aContentOffset >= 0)) { NS_DebugBreak(NS_DEBUG_ASSERTION
, "Negative content offset, existing code was very broken!", "aContentOffset >= 0"
, "./../../../layout/generic/nsTextFrame.cpp", 8918); MOZ_PretendNoReturn
(); } } while (0)
;
8919 nsIFrame* primaryFrame = mContent->GetPrimaryFrame();
8920 if (this != primaryFrame) {
8921 // This call needs to happen on the primary frame
8922 return primaryFrame->GetChildFrameContainingOffset(aContentOffset, aHint,
8923 aOutOffset, aOutFrame);
8924 }
8925
8926 nsTextFrame* f = this;
8927 int32_t offset = mContentOffset;
8928
8929 // Try to look up the offset to frame property
8930 nsTextFrame* cachedFrame = GetProperty(OffsetToFrameProperty());
8931
8932 if (cachedFrame) {
8933 f = cachedFrame;
8934 offset = f->GetContentOffset();
8935
8936 f->RemoveStateBits(TEXT_IN_OFFSET_CACHE);
8937 }
8938
8939 if ((aContentOffset >= offset) && (aHint || aContentOffset != offset)) {
8940 while (true) {
8941 nsTextFrame* next = f->GetNextContinuation();
8942 if (!next || aContentOffset < next->GetContentOffset()) {
8943 break;
8944 }
8945 if (aContentOffset == next->GetContentOffset()) {
8946 if (aHint) {
8947 f = next;
8948 if (f->GetContentLength() == 0) {
8949 continue; // use the last of the empty frames with this offset
8950 }
8951 }
8952 break;
8953 }
8954 f = next;
8955 }
8956 } else {
8957 while (true) {
8958 nsTextFrame* prev = f->GetPrevContinuation();
8959 if (!prev || aContentOffset > f->GetContentOffset()) {
8960 break;
8961 }
8962 if (aContentOffset == f->GetContentOffset()) {
8963 if (!aHint) {
8964 f = prev;
8965 if (f->GetContentLength() == 0) {
8966 continue; // use the first of the empty frames with this offset
8967 }
8968 }
8969 break;
8970 }
8971 f = prev;
8972 }
8973 }
8974
8975 *aOutOffset = aContentOffset - f->GetContentOffset();
8976 *aOutFrame = f;
8977
8978 // cache the frame we found
8979 SetProperty(OffsetToFrameProperty(), f);
8980 f->AddStateBits(TEXT_IN_OFFSET_CACHE);
8981
8982 return NS_OK;
8983}
8984
8985nsIFrame::FrameSearchResult nsTextFrame::PeekOffsetNoAmount(bool aForward,
8986 int32_t* aOffset) {
8987 NS_ASSERTION(aOffset && *aOffset <= GetContentLength(),do { if (!(aOffset && *aOffset <= GetContentLength
())) { NS_DebugBreak(NS_DEBUG_ASSERTION, "aOffset out of range"
, "aOffset && *aOffset <= GetContentLength()", "./../../../layout/generic/nsTextFrame.cpp"
, 8988); MOZ_PretendNoReturn(); } } while (0)
8988 "aOffset out of range")do { if (!(aOffset && *aOffset <= GetContentLength
())) { NS_DebugBreak(NS_DEBUG_ASSERTION, "aOffset out of range"
, "aOffset && *aOffset <= GetContentLength()", "./../../../layout/generic/nsTextFrame.cpp"
, 8988); MOZ_PretendNoReturn(); } } while (0)
;
8989
8990 gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
8991 if (!mTextRun) {
8992 return CONTINUE_EMPTY;
8993 }
8994
8995 TrimmedOffsets trimmed = GetTrimmedOffsets(CharacterDataBuffer());
8996 // Check whether there are nonskipped characters in the trimmmed range
8997 return (iter.ConvertOriginalToSkipped(trimmed.GetEnd()) >
8998 iter.ConvertOriginalToSkipped(trimmed.mStart))
8999 ? FOUND
9000 : CONTINUE;
9001}
9002
9003/**
9004 * This class iterates through the clusters before or after the given
9005 * aPosition (which is a content offset). You can test each cluster
9006 * to see if it's whitespace (as far as selection/caret movement is concerned),
9007 * or punctuation, or if there is a word break before the cluster. ("Before"
9008 * is interpreted according to aDirection, so if aDirection is -1, "before"
9009 * means actually *after* the cluster content.)
9010 */
9011class MOZ_STACK_CLASS ClusterIterator {
9012 public:
9013 ClusterIterator(nsTextFrame* aTextFrame, int32_t aPosition,
9014 int32_t aDirection, nsString& aContext,
9015 bool aTrimSpaces = true);
9016
9017 bool NextCluster();
9018 bool IsInlineWhitespace() const;
9019 bool IsNewline() const;
9020 bool IsPunctuation() const;
9021 intl::Script ScriptCode() const;
9022 bool HaveWordBreakBefore() const { return mHaveWordBreak; }
9023
9024 // Get the charIndex that corresponds to the "before" side of the current
9025 // character, according to the direction of iteration: so for a forward
9026 // iterator, this is simply mCharIndex, while for a reverse iterator it will
9027 // be mCharIndex + <number of code units in the character>.
9028 int32_t GetBeforeOffset() const {
9029 MOZ_ASSERT(mCharIndex >= 0)do { static_assert( mozilla::detail::AssertionConditionType<
decltype(mCharIndex >= 0)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(mCharIndex >= 0))), 0))) {
do { } while (false); MOZ_ReportAssertionFailure("mCharIndex >= 0"
, "./../../../layout/generic/nsTextFrame.cpp", 9029); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "mCharIndex >= 0" ")"); do { MOZ_CrashSequence
(__null, 9029); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
9030 return mDirection < 0 ? GetAfterInternal() : mCharIndex;
9031 }
9032 // Get the charIndex that corresponds to the "before" side of the current
9033 // character, according to the direction of iteration: the opposite side
9034 // to what GetBeforeOffset returns.
9035 int32_t GetAfterOffset() const {
9036 MOZ_ASSERT(mCharIndex >= 0)do { static_assert( mozilla::detail::AssertionConditionType<
decltype(mCharIndex >= 0)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(mCharIndex >= 0))), 0))) {
do { } while (false); MOZ_ReportAssertionFailure("mCharIndex >= 0"
, "./../../../layout/generic/nsTextFrame.cpp", 9036); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "mCharIndex >= 0" ")"); do { MOZ_CrashSequence
(__null, 9036); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
9037 return mDirection > 0 ? GetAfterInternal() : mCharIndex;
9038 }
9039
9040 private:
9041 // Helper for Get{After,Before}Offset; returns the charIndex after the
9042 // current position in the text, accounting for surrogate pairs.
9043 int32_t GetAfterInternal() const;
9044
9045 gfxSkipCharsIterator mIterator;
9046 // Usually, mCharacterDataBuffer is pointer to `dom::CharacterData::mText`.
9047 // However, if we're in a password field, this points to `mMaskedBuffer`.
9048 const CharacterDataBuffer* mCharacterDataBuffer;
9049 // If we're in a password field, this is initialized with mask characters.
9050 CharacterDataBuffer mMaskedBuffer;
9051 nsTextFrame* mTextFrame;
9052 int32_t mDirection; // +1 or -1, or 0 to indicate failure
9053 int32_t mCharIndex;
9054 nsTextFrame::TrimmedOffsets mTrimmed;
9055 nsTArray<bool> mWordBreaks;
9056 bool mHaveWordBreak;
9057};
9058
9059static bool IsAcceptableCaretPosition(const gfxSkipCharsIterator& aIter,
9060 bool aRespectClusters,
9061 const gfxTextRun* aTextRun,
9062 nsTextFrame* aFrame) {
9063 if (aIter.IsOriginalCharSkipped()) {
9064 return false;
9065 }
9066 uint32_t index = aIter.GetSkippedOffset();
9067 if (aRespectClusters && !aTextRun->IsClusterStart(index)) {
9068 return false;
9069 }
9070 if (index > 0) {
9071 // Check whether the proposed position is in between the two halves of a
9072 // surrogate pair, before a Variation Selector character, or within a
9073 // ligated emoji sequence; if so, this is not a valid character boundary.
9074 // (In the case where we are respecting clusters, we won't actually get
9075 // this far because the low surrogate is also marked as non-clusterStart
9076 // so we'll return FALSE above.)
9077 const uint32_t offs = AssertedCast<uint32_t>(aIter.GetOriginalOffset());
9078 const CharacterDataBuffer& characterDataBuffer =
9079 aFrame->CharacterDataBuffer();
9080 const char16_t ch = characterDataBuffer.CharAt(offs);
9081
9082 if (gfxFontUtils::IsVarSelector(ch) ||
9083 characterDataBuffer.IsLowSurrogateFollowingHighSurrogateAt(offs) ||
9084 (!aTextRun->IsLigatureGroupStart(index) &&
9085 (unicode::GetEmojiPresentation(ch) == unicode::EmojiDefault ||
9086 (unicode::GetEmojiPresentation(ch) == unicode::TextDefault &&
9087 offs + 1 < characterDataBuffer.GetLength() &&
9088 characterDataBuffer.CharAt(offs + 1) ==
9089 gfxFontUtils::kUnicodeVS16)))) {
9090 return false;
9091 }
9092
9093 // If the proposed position is before a high surrogate, we need to decode
9094 // the surrogate pair (if valid) and check the resulting character.
9095 if (IsHighSurrogate(ch)) {
9096 if (const char32_t ucs4 = characterDataBuffer.ScalarValueAt(offs)) {
9097 // If the character is a (Plane-14) variation selector,
9098 // or an emoji character that is ligated with the previous
9099 // character (i.e. part of a Regional-Indicator flag pair,
9100 // or an emoji-ZWJ sequence), this is not a valid boundary.
9101 if (gfxFontUtils::IsVarSelector(ucs4) ||
9102 (!aTextRun->IsLigatureGroupStart(index) &&
9103 unicode::GetEmojiPresentation(ucs4) == unicode::EmojiDefault)) {
9104 return false;
9105 }
9106 }
9107 }
9108 }
9109 return true;
9110}
9111
9112nsIFrame::FrameSearchResult nsTextFrame::PeekOffsetCharacter(
9113 bool aForward, int32_t* aOffset, PeekOffsetCharacterOptions aOptions) {
9114 const int32_t contentLengthInFrame = GetContentLength();
9115 NS_ASSERTION(aOffset && *aOffset <= contentLengthInFrame,do { if (!(aOffset && *aOffset <= contentLengthInFrame
)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "aOffset out of range"
, "aOffset && *aOffset <= contentLengthInFrame", "./../../../layout/generic/nsTextFrame.cpp"
, 9116); MOZ_PretendNoReturn(); } } while (0)
9116 "aOffset out of range")do { if (!(aOffset && *aOffset <= contentLengthInFrame
)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "aOffset out of range"
, "aOffset && *aOffset <= contentLengthInFrame", "./../../../layout/generic/nsTextFrame.cpp"
, 9116); MOZ_PretendNoReturn(); } } while (0)
;
9117
9118 if (!aOptions.mIgnoreUserStyleAll) {
9119 StyleUserSelect selectStyle;
9120 (void)IsSelectable(&selectStyle);
9121 if (selectStyle == StyleUserSelect::All) {
9122 return CONTINUE_UNSELECTABLE;
9123 }
9124 }
9125
9126 gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
9127 if (!mTextRun) {
9128 return CONTINUE_EMPTY;
9129 }
9130 const TrimmedOffsets trimmed =
9131 GetTrimmedOffsets(CharacterDataBuffer(), TrimmedOffsetFlags::NoTrimAfter);
9132
9133 // A negative offset means "end of frame".
9134 const int32_t offset =
9135 GetContentOffset() + (*aOffset < 0 ? contentLengthInFrame : *aOffset);
9136
9137 if (!aForward) {
9138 const int32_t endOffset = [&]() -> int32_t {
9139 const int32_t minEndOffset = std::min(trimmed.GetEnd(), offset);
9140 if (minEndOffset <= trimmed.mStart ||
9141 minEndOffset + 1 >= trimmed.GetEnd()) {
9142 return minEndOffset;
9143 }
9144 // If the end offset points a character in this frame and it's skipped
9145 // character, we want to scan previous character of the prceding first
9146 // non-skipped character.
9147 for (const int32_t i :
9148 Reversed(IntegerRange(trimmed.mStart, minEndOffset + 1))) {
9149 iter.SetOriginalOffset(i);
9150 if (!iter.IsOriginalCharSkipped()) {
9151 return i;
9152 }
9153 }
9154 return trimmed.mStart;
9155 }();
9156 // If we're at the start of a line, look at the next continuation
9157 if (endOffset <= trimmed.mStart) {
9158 *aOffset = 0;
9159 return CONTINUE;
9160 }
9161 // If at the beginning of the line, look at the previous continuation
9162 for (const int32_t i : Reversed(IntegerRange(trimmed.mStart, endOffset))) {
9163 iter.SetOriginalOffset(i);
9164 // If we entered into a skipped char range again, we should skip all
9165 // of them. However, we cannot know the number of preceding skipped
9166 // chars. Therefore, we cannot skip all of them once.
9167 if (iter.IsOriginalCharSkipped()) {
9168 continue;
9169 }
9170 if (IsAcceptableCaretPosition(iter, aOptions.mRespectClusters, mTextRun,
9171 this)) {
9172 *aOffset = i - mContentOffset;
9173 return FOUND;
9174 }
9175 }
9176 *aOffset = 0;
9177 return CONTINUE;
9178 }
9179
9180 // If we're at the end of a line, look at the next continuation
9181 if (offset + 1 > trimmed.GetEnd()) {
9182 *aOffset = contentLengthInFrame;
9183 return CONTINUE;
9184 }
9185
9186 iter.SetOriginalOffset(offset);
9187
9188 // If we're at a preformatted linefeed, look at the next continutation
9189 if (offset < trimmed.GetEnd() && StyleText()->NewlineIsSignificant(this) &&
9190 iter.GetSkippedOffset() < mTextRun->GetLength() &&
9191 mTextRun->CharIsNewline(iter.GetSkippedOffset())) {
9192 *aOffset = contentLengthInFrame;
9193 return CONTINUE;
9194 }
9195
9196 const int32_t scanStartOffset = [&]() -> int32_t {
9197 // If current char is skipped, scan starting from the following
9198 // non-skipped char.
9199 int32_t skippedLength = 0;
9200 if (iter.IsOriginalCharSkipped(&skippedLength)) {
9201 const int32_t skippedLengthInFrame =
9202 std::min(skippedLength, trimmed.GetEnd() - iter.GetOriginalOffset());
9203 return iter.GetOriginalOffset() + skippedLengthInFrame + 1;
9204 }
9205 return iter.GetOriginalOffset() + 1;
9206 }();
9207
9208 for (int32_t i = scanStartOffset; i < trimmed.GetEnd(); i++) {
9209 iter.SetOriginalOffset(i);
9210 // If we entered into a skipped char range again, we should skip all
9211 // of them.
9212 int32_t skippedLength = 0;
9213 if (iter.IsOriginalCharSkipped(&skippedLength)) {
9214 const int32_t skippedLengthInFrame =
9215 std::min(skippedLength, trimmed.GetEnd() - iter.GetOriginalOffset());
9216 if (skippedLengthInFrame) {
9217 i += skippedLengthInFrame - 1;
9218 }
9219 continue;
9220 }
9221 if (IsAcceptableCaretPosition(iter, aOptions.mRespectClusters, mTextRun,
9222 this)) {
9223 *aOffset = i - mContentOffset;
9224 return FOUND;
9225 }
9226 }
9227
9228 *aOffset = trimmed.GetEnd() - mContentOffset;
9229 return FOUND;
9230}
9231
9232bool ClusterIterator::IsInlineWhitespace() const {
9233 NS_ASSERTION(mCharIndex >= 0, "No cluster selected")do { if (!(mCharIndex >= 0)) { NS_DebugBreak(NS_DEBUG_ASSERTION
, "No cluster selected", "mCharIndex >= 0", "./../../../layout/generic/nsTextFrame.cpp"
, 9233); MOZ_PretendNoReturn(); } } while (0)
;
9234 return IsSelectionInlineWhitespace(*mCharacterDataBuffer, mCharIndex);
9235}
9236
9237bool ClusterIterator::IsNewline() const {
9238 NS_ASSERTION(mCharIndex >= 0, "No cluster selected")do { if (!(mCharIndex >= 0)) { NS_DebugBreak(NS_DEBUG_ASSERTION
, "No cluster selected", "mCharIndex >= 0", "./../../../layout/generic/nsTextFrame.cpp"
, 9238); MOZ_PretendNoReturn(); } } while (0)
;
9239 return IsSelectionNewline(*mCharacterDataBuffer, mCharIndex);
9240}
9241
9242bool ClusterIterator::IsPunctuation() const {
9243 NS_ASSERTION(mCharIndex >= 0, "No cluster selected")do { if (!(mCharIndex >= 0)) { NS_DebugBreak(NS_DEBUG_ASSERTION
, "No cluster selected", "mCharIndex >= 0", "./../../../layout/generic/nsTextFrame.cpp"
, 9243); MOZ_PretendNoReturn(); } } while (0)
;
9244 const char16_t ch =
9245 mCharacterDataBuffer->CharAt(AssertedCast<uint32_t>(mCharIndex));
9246 return IsPunctuationForWordSelect(ch);
9247}
9248
9249intl::Script ClusterIterator::ScriptCode() const {
9250 NS_ASSERTION(mCharIndex >= 0, "No cluster selected")do { if (!(mCharIndex >= 0)) { NS_DebugBreak(NS_DEBUG_ASSERTION
, "No cluster selected", "mCharIndex >= 0", "./../../../layout/generic/nsTextFrame.cpp"
, 9250); MOZ_PretendNoReturn(); } } while (0)
;
9251 const char16_t ch =
9252 mCharacterDataBuffer->CharAt(AssertedCast<uint32_t>(mCharIndex));
9253 return intl::UnicodeProperties::GetScriptCode(ch);
9254}
9255
9256static inline bool IsKorean(intl::Script aScript) {
9257 // We only need to check for HANGUL script code; there is a script code
9258 // KOREAN but this is not assigned to any codepoints. (If that ever changes,
9259 // we could check for both codes here.)
9260 MOZ_ASSERT(aScript != intl::Script::KOREAN, "unexpected script code")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aScript != intl::Script::KOREAN)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(aScript != intl::Script::KOREAN
))), 0))) { do { } while (false); MOZ_ReportAssertionFailure(
"aScript != intl::Script::KOREAN" " (" "unexpected script code"
")", "./../../../layout/generic/nsTextFrame.cpp", 9260); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "aScript != intl::Script::KOREAN" ") (" "unexpected script code"
")"); do { MOZ_CrashSequence(__null, 9260); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
9261 return aScript == intl::Script::HANGUL;
9262}
9263
9264int32_t ClusterIterator::GetAfterInternal() const {
9265 if (mCharacterDataBuffer->IsHighSurrogateFollowedByLowSurrogateAt(
9266 AssertedCast<uint32_t>(mCharIndex))) {
9267 return mCharIndex + 2;
9268 }
9269 return mCharIndex + 1;
9270}
9271
9272bool ClusterIterator::NextCluster() {
9273 if (!mDirection) {
9274 return false;
9275 }
9276 const gfxTextRun* textRun = mTextFrame->GetTextRun(nsTextFrame::eInflated);
9277
9278 mHaveWordBreak = false;
9279 while (true) {
9280 bool keepGoing = false;
9281 if (mDirection > 0) {
9282 if (mIterator.GetOriginalOffset() >= mTrimmed.GetEnd()) {
9283 return false;
9284 }
9285 keepGoing = mIterator.IsOriginalCharSkipped() ||
9286 mIterator.GetOriginalOffset() < mTrimmed.mStart ||
9287 !textRun->IsClusterStart(mIterator.GetSkippedOffset());
9288 mCharIndex = mIterator.GetOriginalOffset();
9289 mIterator.AdvanceOriginal(1);
9290 } else {
9291 if (mIterator.GetOriginalOffset() <= mTrimmed.mStart) {
9292 // Trimming can skip backward word breakers, see bug 1667138
9293 return mHaveWordBreak;
9294 }
9295 mIterator.AdvanceOriginal(-1);
9296 keepGoing = mIterator.IsOriginalCharSkipped() ||
9297 mIterator.GetOriginalOffset() >= mTrimmed.GetEnd() ||
9298 !textRun->IsClusterStart(mIterator.GetSkippedOffset());
9299 mCharIndex = mIterator.GetOriginalOffset();
9300 }
9301
9302 if (mWordBreaks[GetBeforeOffset() - mTextFrame->GetContentOffset()]) {
9303 mHaveWordBreak = true;
9304 }
9305 if (!keepGoing) {
9306 return true;
9307 }
9308 }
9309}
9310
9311ClusterIterator::ClusterIterator(nsTextFrame* aTextFrame, int32_t aPosition,
9312 int32_t aDirection, nsString& aContext,
9313 bool aTrimSpaces)
9314 : mIterator(aTextFrame->EnsureTextRun(nsTextFrame::eInflated)),
9315 mTextFrame(aTextFrame),
9316 mDirection(aDirection),
9317 mCharIndex(-1),
9318 mHaveWordBreak(false) {
9319 gfxTextRun* textRun = aTextFrame->GetTextRun(nsTextFrame::eInflated);
9320 if (!textRun) {
9321 mDirection = 0; // signal failure
9322 return;
9323 }
9324
9325 mCharacterDataBuffer = &aTextFrame->CharacterDataBuffer();
9326
9327 const uint32_t textOffset =
9328 AssertedCast<uint32_t>(aTextFrame->GetContentOffset());
9329 const uint32_t textLen =
9330 AssertedCast<uint32_t>(aTextFrame->GetContentLength());
9331
9332 // If we're in a password field, some characters may be masked. In such
9333 // case, we need to treat each masked character as a mask character since
9334 // we shouldn't expose word boundary which is hidden by the masking.
9335 if (aTextFrame->GetContent() && mCharacterDataBuffer->GetLength() > 0 &&
9336 aTextFrame->GetContent()->HasFlag(NS_MAYBE_MASKED) &&
9337 (textRun->GetFlags2() & nsTextFrameUtils::Flags::IsTransformed)) {
9338 const char16_t kPasswordMask = TextEditor::PasswordMask();
9339 const nsTransformedTextRun* transformedTextRun =
9340 static_cast<const nsTransformedTextRun*>(textRun);
9341 // Use nsString and not nsAutoString so that we get a nsStringBuffer which
9342 // can be just AddRefed in `mMaskedFrag`.
9343 nsString maskedText;
9344 maskedText.SetCapacity(mCharacterDataBuffer->GetLength());
9345 // Note that aTextFrame may not cover the whole of mFrag (in cases with
9346 // bidi continuations), so we cannot rely on its textrun (and associated
9347 // styles) being available for the entire fragment.
9348 uint32_t i = 0;
9349 // Just copy any text that precedes what aTextFrame covers.
9350 while (i < textOffset) {
9351 maskedText.Append(mCharacterDataBuffer->CharAt(i++));
9352 }
9353 // For the range covered by aTextFrame, mask chars if appropriate.
9354 while (i < textOffset + textLen) {
9355 uint32_t skippedOffset = mIterator.ConvertOriginalToSkipped(i);
9356 bool mask =
9357 skippedOffset < transformedTextRun->GetLength()
9358 ? transformedTextRun->mStyles[skippedOffset]->mMaskPassword
9359 : false;
9360 if (mCharacterDataBuffer->IsHighSurrogateFollowedByLowSurrogateAt(i)) {
9361 if (mask) {
9362 maskedText.Append(kPasswordMask);
9363 maskedText.Append(kPasswordMask);
9364 } else {
9365 maskedText.Append(mCharacterDataBuffer->CharAt(i));
9366 maskedText.Append(mCharacterDataBuffer->CharAt(i + 1));
9367 }
9368 i += 2;
9369 } else {
9370 maskedText.Append(mask ? kPasswordMask
9371 : mCharacterDataBuffer->CharAt(i));
9372 ++i;
9373 }
9374 }
9375 // Copy any trailing text from the fragment.
9376 while (i < mCharacterDataBuffer->GetLength()) {
9377 maskedText.Append(mCharacterDataBuffer->CharAt(i++));
9378 }
9379 mMaskedBuffer.SetTo(maskedText, mCharacterDataBuffer->IsBidi(), true);
9380 mCharacterDataBuffer = &mMaskedBuffer;
9381 }
9382
9383 mIterator.SetOriginalOffset(aPosition);
9384 mTrimmed = aTextFrame->GetTrimmedOffsets(
9385 *mCharacterDataBuffer,
9386 aTrimSpaces ? nsTextFrame::TrimmedOffsetFlags::Default
9387 : nsTextFrame::TrimmedOffsetFlags::NoTrimAfter |
9388 nsTextFrame::TrimmedOffsetFlags::NoTrimBefore);
9389
9390 // Allocate an extra element to record the word break at the end of the line
9391 // or text run in mWordBreak[textLen].
9392 mWordBreaks.AppendElements(textLen + 1);
9393 PodZero(mWordBreaks.Elements(), textLen + 1);
9394 uint32_t textStart;
9395 if (aDirection > 0) {
9396 if (aContext.IsEmpty()) {
9397 // No previous context, so it must be the start of a line or text run
9398 mWordBreaks[0] = true;
9399 }
9400 textStart = aContext.Length();
9401 mCharacterDataBuffer->AppendTo(aContext, textOffset, textLen);
9402 } else {
9403 if (aContext.IsEmpty()) {
9404 // No following context, so it must be the end of a line or text run
9405 mWordBreaks[textLen] = true;
9406 }
9407 textStart = 0;
9408 nsAutoString str;
9409 mCharacterDataBuffer->AppendTo(str, textOffset, textLen);
9410 aContext.Insert(str, 0);
9411 }
9412
9413 const uint32_t textEnd = textStart + textLen;
9414 intl::WordBreakIteratorUtf16 wordBreakIter(aContext);
9415 Maybe<uint32_t> nextBreak =
9416 wordBreakIter.Seek(textStart > 0 ? textStart - 1 : textStart);
9417 while (nextBreak && *nextBreak <= textEnd) {
9418 mWordBreaks[*nextBreak - textStart] = true;
9419 nextBreak = wordBreakIter.Next();
9420 }
9421
9422 MOZ_ASSERT(textEnd != aContext.Length() || mWordBreaks[textLen],do { static_assert( mozilla::detail::AssertionConditionType<
decltype(textEnd != aContext.Length() || mWordBreaks[textLen]
)>::isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(textEnd != aContext.Length() || mWordBreaks[textLen]
))), 0))) { do { } while (false); MOZ_ReportAssertionFailure(
"textEnd != aContext.Length() || mWordBreaks[textLen]" " (" "There should be a word break at the end of a line or text run!"
")", "./../../../layout/generic/nsTextFrame.cpp", 9423); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "textEnd != aContext.Length() || mWordBreaks[textLen]"
") (" "There should be a word break at the end of a line or text run!"
")"); do { MOZ_CrashSequence(__null, 9423); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
9423 "There should be a word break at the end of a line or text run!")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(textEnd != aContext.Length() || mWordBreaks[textLen]
)>::isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(textEnd != aContext.Length() || mWordBreaks[textLen]
))), 0))) { do { } while (false); MOZ_ReportAssertionFailure(
"textEnd != aContext.Length() || mWordBreaks[textLen]" " (" "There should be a word break at the end of a line or text run!"
")", "./../../../layout/generic/nsTextFrame.cpp", 9423); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "textEnd != aContext.Length() || mWordBreaks[textLen]"
") (" "There should be a word break at the end of a line or text run!"
")"); do { MOZ_CrashSequence(__null, 9423); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
9424}
9425
9426nsIFrame::FrameSearchResult nsTextFrame::PeekOffsetWord(
9427 bool aForward, bool aWordSelectEatSpace, bool aIsKeyboardSelect,
9428 int32_t* aOffset, PeekWordState* aState, bool aTrimSpaces) {
9429 int32_t contentLength = GetContentLength();
9430 NS_ASSERTION(aOffset && *aOffset <= contentLength, "aOffset out of range")do { if (!(aOffset && *aOffset <= contentLength)) {
NS_DebugBreak(NS_DEBUG_ASSERTION, "aOffset out of range", "aOffset && *aOffset <= contentLength"
, "./../../../layout/generic/nsTextFrame.cpp", 9430); MOZ_PretendNoReturn
(); } } while (0)
;
9431
9432 StyleUserSelect selectStyle;
9433 (void)IsSelectable(&selectStyle);
9434 if (selectStyle == StyleUserSelect::All) {
9435 return CONTINUE_UNSELECTABLE;
9436 }
9437
9438 int32_t offset =
9439 GetContentOffset() + (*aOffset < 0 ? contentLength : *aOffset);
9440 ClusterIterator cIter(this, offset, aForward ? 1 : -1, aState->mContext,
9441 aTrimSpaces);
9442
9443 if (!cIter.NextCluster()) {
9444 return CONTINUE_EMPTY;
9445 }
9446
9447 // Do we need to check for Korean characters?
9448 bool is2b = CharacterDataBuffer().Is2b();
9449 do {
9450 bool isPunctuation = cIter.IsPunctuation();
9451 bool isInlineWhitespace = cIter.IsInlineWhitespace();
9452 bool isWhitespace = isInlineWhitespace || cIter.IsNewline();
9453 bool isWordBreakBefore = cIter.HaveWordBreakBefore();
9454 // If the text is one-byte, we don't actually care about script code as
9455 // there cannot be any Korean in the frame.
9456 intl::Script scriptCode = is2b ? cIter.ScriptCode() : intl::Script::COMMON;
9457 if (!isWhitespace || isInlineWhitespace) {
9458 aState->SetSawInlineCharacter();
9459 }
9460 if (aWordSelectEatSpace == isWhitespace && !aState->mSawBeforeType) {
9461 aState->SetSawBeforeType();
9462 aState->Update(isPunctuation, isWhitespace, scriptCode);
9463 continue;
9464 }
9465 // See if we can break before the current cluster
9466 if (!aState->mAtStart) {
9467 bool canBreak;
9468 if (isPunctuation != aState->mLastCharWasPunctuation) {
9469 canBreak = BreakWordBetweenPunctuation(aState, aForward, isPunctuation,
9470 isWhitespace, aIsKeyboardSelect);
9471 } else if (!aState->mLastCharWasWhitespace && !isWhitespace &&
9472 !isPunctuation && isWordBreakBefore) {
9473 // if both the previous and the current character are not white
9474 // space but this can be word break before, we don't need to eat
9475 // a white space in this case. This case happens in some languages
9476 // that their words are not separated by white spaces. E.g.,
9477 // Japanese and Chinese.
9478 canBreak = true;
9479 } else {
9480 canBreak = isWordBreakBefore && aState->mSawBeforeType &&
9481 (aWordSelectEatSpace != isWhitespace);
9482 }
9483 // Special-case for Korean: treat a boundary between Hangul & non-Hangul
9484 // characters as a word boundary (see bug 1973393 and UAX#29).
9485 if (!canBreak && is2b && aState->mLastScript != intl::Script::INVALID &&
9486 IsKorean(aState->mLastScript) != IsKorean(scriptCode)) {
9487 canBreak = true;
9488 }
9489 if (canBreak) {
9490 *aOffset = cIter.GetBeforeOffset() - mContentOffset;
9491 return FOUND;
9492 }
9493 }
9494 aState->Update(isPunctuation, isWhitespace, scriptCode);
9495 } while (cIter.NextCluster());
9496
9497 *aOffset = cIter.GetAfterOffset() - mContentOffset;
9498 return CONTINUE;
9499}
9500
9501bool nsTextFrame::HasVisibleText() {
9502 // Text in the range is visible if there is at least one character in the
9503 // range that is not skipped and is mapped by this frame (which is the primary
9504 // frame) or one of its continuations.
9505 for (nsTextFrame* f = this; f; f = f->GetNextContinuation()) {
9506 int32_t dummyOffset = 0;
9507 if (f->PeekOffsetNoAmount(true, &dummyOffset) == FOUND) {
9508 return true;
9509 }
9510 }
9511 return false;
9512}
9513
9514std::pair<int32_t, int32_t> nsTextFrame::GetOffsets() const {
9515 return std::make_pair(GetContentOffset(), GetContentEnd());
9516}
9517
9518static bool IsFirstLetterPrefixPunctuation(uint32_t aChar) {
9519 switch (mozilla::unicode::GetGeneralCategory(aChar)) {
9520 case HB_UNICODE_GENERAL_CATEGORY_CONNECT_PUNCTUATION: /* Pc */
9521 case HB_UNICODE_GENERAL_CATEGORY_DASH_PUNCTUATION: /* Pd */
9522 case HB_UNICODE_GENERAL_CATEGORY_CLOSE_PUNCTUATION: /* Pe */
9523 case HB_UNICODE_GENERAL_CATEGORY_FINAL_PUNCTUATION: /* Pf */
9524 case HB_UNICODE_GENERAL_CATEGORY_INITIAL_PUNCTUATION: /* Pi */
9525 case HB_UNICODE_GENERAL_CATEGORY_OTHER_PUNCTUATION: /* Po */
9526 case HB_UNICODE_GENERAL_CATEGORY_OPEN_PUNCTUATION: /* Ps */
9527 return true;
9528 default:
9529 return false;
9530 }
9531}
9532
9533static bool IsFirstLetterSuffixPunctuation(uint32_t aChar) {
9534 switch (mozilla::unicode::GetGeneralCategory(aChar)) {
9535 case HB_UNICODE_GENERAL_CATEGORY_CONNECT_PUNCTUATION: /* Pc */
9536 case HB_UNICODE_GENERAL_CATEGORY_CLOSE_PUNCTUATION: /* Pe */
9537 case HB_UNICODE_GENERAL_CATEGORY_FINAL_PUNCTUATION: /* Pf */
9538 case HB_UNICODE_GENERAL_CATEGORY_INITIAL_PUNCTUATION: /* Pi */
9539 case HB_UNICODE_GENERAL_CATEGORY_OTHER_PUNCTUATION: /* Po */
9540 return true;
9541 default:
9542 return false;
9543 }
9544}
9545
9546static int32_t FindEndOfPrefixPunctuationRun(const CharacterDataBuffer& aBuffer,
9547 const gfxTextRun* aTextRun,
9548 gfxSkipCharsIterator* aIter,
9549 int32_t aOffset, int32_t aStart,
9550 int32_t aEnd) {
9551 int32_t i;
9552 for (i = aStart; i < aEnd - aOffset; ++i) {
9553 if (IsFirstLetterPrefixPunctuation(
9554 aBuffer.ScalarValueAt(AssertedCast<uint32_t>(aOffset + i)))) {
9555 aIter->SetOriginalOffset(aOffset + i);
9556 FindClusterEnd(aTextRun, aEnd, aIter);
9557 i = aIter->GetOriginalOffset() - aOffset;
9558 } else {
9559 break;
9560 }
9561 }
9562 return i;
9563}
9564
9565static int32_t FindEndOfSuffixPunctuationRun(const CharacterDataBuffer& aBuffer,
9566 const gfxTextRun* aTextRun,
9567 gfxSkipCharsIterator* aIter,
9568 int32_t aOffset, int32_t aStart,
9569 int32_t aEnd) {
9570 int32_t i;
9571 for (i = aStart; i < aEnd - aOffset; ++i) {
9572 if (IsFirstLetterSuffixPunctuation(
9573 aBuffer.ScalarValueAt(AssertedCast<uint32_t>(aOffset + i)))) {
9574 aIter->SetOriginalOffset(aOffset + i);
9575 FindClusterEnd(aTextRun, aEnd, aIter);
9576 i = aIter->GetOriginalOffset() - aOffset;
9577 } else {
9578 break;
9579 }
9580 }
9581 return i;
9582}
9583
9584/**
9585 * Returns true if this text frame completes the first-letter, false
9586 * if it does not contain a true "letter".
9587 * If returns true, then it also updates aLength to cover just the first-letter
9588 * text.
9589 *
9590 * XXX :first-letter should be handled during frame construction
9591 * (and it has a good bit in common with nextBidi)
9592 *
9593 * @param aLength an in/out parameter: on entry contains the maximum length to
9594 * return, on exit returns length of the first-letter fragment (which may
9595 * include leading and trailing punctuation, for example)
9596 */
9597static bool FindFirstLetterRange(const CharacterDataBuffer& aBuffer,
9598 const nsAtom* aLang,
9599 const gfxTextRun* aTextRun, int32_t aOffset,
9600 const gfxSkipCharsIterator& aIter,
9601 int32_t* aLength) {
9602 int32_t length = *aLength;
9603 int32_t endOffset = aOffset + length;
9604 gfxSkipCharsIterator iter(aIter);
9605
9606 // Currently the only language-specific special case we handle here is the
9607 // Dutch "IJ" digraph.
9608 auto LangTagIsDutch = [](const nsAtom* aLang) -> bool {
9609 if (!aLang) {
9610 return false;
9611 }
9612 if (aLang == nsGkAtoms::nl) {
9613 return true;
9614 }
9615 // We don't need to fully parse as a Locale; just check the initial subtag.
9616 nsDependentAtomString langStr(aLang);
9617 int32_t index = langStr.FindChar('-');
9618 if (index > 0) {
9619 langStr.Truncate(index);
9620 return langStr.EqualsLiteral("nl");
9621 }
9622 return false;
9623 };
9624
9625 // Skip any trimmable leading whitespace.
9626 int32_t i = GetTrimmableWhitespaceCount(aBuffer, aOffset, length, 1);
9627 while (true) {
9628 // Scan past any leading punctuation. This leaves `j` at the first
9629 // non-punctuation character.
9630 int32_t j = FindEndOfPrefixPunctuationRun(aBuffer, aTextRun, &iter, aOffset,
9631 i, endOffset);
9632 if (j == length) {
9633 return false;
9634 }
9635
9636 // Scan past any Unicode whitespace characters after punctuation.
9637 while (j < length) {
9638 char16_t ch = aBuffer.CharAt(AssertedCast<uint32_t>(aOffset + j));
9639 // The spec says to allow "characters that belong to the `Zs` Unicode
9640 // general category _other than_ U+3000" here.
9641 if (unicode::GetGeneralCategory(ch) ==
9642 HB_UNICODE_GENERAL_CATEGORY_SPACE_SEPARATOR &&
9643 ch != 0x3000) {
9644 ++j;
9645 } else {
9646 break;
9647 }
9648 }
9649 if (j == length) {
9650 return false;
9651 }
9652 if (j == i) {
9653 // If no whitespace was found, we've finished the first-letter prefix;
9654 // if there was some, then go back to check for more punctuation.
9655 break;
9656 }
9657 i = j;
9658 }
9659
9660 // If the next character is not a letter, number or symbol, there is no
9661 // first-letter.
9662 // Return true so that we don't go on looking, but set aLength to 0.
9663 const char32_t usv =
9664 aBuffer.ScalarValueAt(AssertedCast<uint32_t>(aOffset + i));
9665 if (!nsContentUtils::IsAlphanumericOrSymbol(usv)) {
9666 *aLength = 0;
9667 return true;
9668 }
9669
9670 // Consume another cluster (the actual first letter):
9671
9672 // For complex scripts such as Indic and SEAsian, where first-letter
9673 // should extend to entire orthographic "syllable" clusters, we don't
9674 // want to allow this to split a ligature.
9675 bool allowSplitLigature;
9676 bool usesIndicHalfForms = false;
9677
9678 typedef intl::Script Script;
9679 Script script = intl::UnicodeProperties::GetScriptCode(usv);
9680 switch (script) {
9681 default:
9682 allowSplitLigature = true;
9683 break;
9684
9685 // Don't break regional-indicator ligatures.
9686 case Script::COMMON:
9687 allowSplitLigature = !gfxFontUtils::IsRegionalIndicator(usv);
9688 break;
9689
9690 // For now, lacking any definitive specification of when to apply this
9691 // behavior, we'll base the decision on the HarfBuzz shaping engine
9692 // used for each script: those that are handled by the Indic, Tibetan,
9693 // Myanmar and SEAsian shapers will apply the "don't split ligatures"
9694 // rule.
9695
9696 // Indic
9697 case Script::BENGALI:
9698 case Script::DEVANAGARI:
9699 case Script::GUJARATI:
9700 usesIndicHalfForms = true;
9701 [[fallthrough]];
9702
9703 case Script::GURMUKHI:
9704 case Script::KANNADA:
9705 case Script::MALAYALAM:
9706 case Script::ORIYA:
9707 case Script::TAMIL:
9708 case Script::TELUGU:
9709 case Script::SINHALA:
9710 case Script::BALINESE:
9711 case Script::LEPCHA:
9712 case Script::REJANG:
9713 case Script::SUNDANESE:
9714 case Script::JAVANESE:
9715 case Script::KAITHI:
9716 case Script::MEETEI_MAYEK:
9717 case Script::CHAKMA:
9718 case Script::SHARADA:
9719 case Script::TAKRI:
9720 case Script::KHMER:
9721
9722 // Tibetan
9723 case Script::TIBETAN:
9724
9725 // Myanmar
9726 case Script::MYANMAR:
9727
9728 // Other SEAsian
9729 case Script::BUGINESE:
9730 case Script::NEW_TAI_LUE:
9731 case Script::CHAM:
9732 case Script::TAI_THAM:
9733
9734 // What about Thai/Lao - any special handling needed?
9735 // Should we special-case Arabic lam-alef?
9736
9737 allowSplitLigature = false;
9738 break;
9739 }
9740
9741 // NOTE that FindClusterEnd sets the iterator to the last character that is
9742 // part of the cluster, NOT to the first character beyond it.
9743 iter.SetOriginalOffset(aOffset + i);
9744 FindClusterEnd(aTextRun, endOffset, &iter, allowSplitLigature);
9745
9746 // Index of the last character included in the first-letter cluster.
9747 i = iter.GetOriginalOffset() - aOffset;
9748
9749 // Heuristic for Indic scripts that like to form conjuncts:
9750 // If we ended at a virama that is ligated with the preceding character
9751 // (e.g. creating a half-form), then don't stop here; include the next
9752 // cluster as well so that we don't break a conjunct.
9753 //
9754 // Unfortunately this cannot distinguish between a letter+virama that ligate
9755 // to create a half-form (in which case we have a conjunct that should not
9756 // be broken) and a letter+virama that ligate purely for presentational
9757 // reasons to position the (visible) virama component (in which case breaking
9758 // after the virama would be acceptable). So results may be imperfect,
9759 // depending how the font has chosen to implement visible viramas.
9760 if (usesIndicHalfForms) {
9761 while (i + 1 < length && iter.GetSkippedOffset() < aTextRun->GetLength() &&
9762 !aTextRun->IsLigatureGroupStart(iter.GetSkippedOffset())) {
9763 char32_t c = aBuffer.ScalarValueAt(AssertedCast<uint32_t>(aOffset + i));
9764 if (intl::UnicodeProperties::GetCombiningClass(c) ==
9765 HB_UNICODE_COMBINING_CLASS_VIRAMA) {
9766 iter.AdvanceOriginal(1);
9767 FindClusterEnd(aTextRun, endOffset, &iter, allowSplitLigature);
9768 i = iter.GetOriginalOffset() - aOffset;
9769 } else {
9770 break;
9771 }
9772 }
9773 }
9774
9775 if (i + 1 == length) {
9776 return true;
9777 }
9778
9779 // Check for Dutch "ij" digraph special case, but only if both letters have
9780 // the same case.
9781 if (script == Script::LATIN && LangTagIsDutch(aLang)) {
9782 char16_t ch1 = aBuffer.CharAt(AssertedCast<uint32_t>(aOffset + i));
9783 char16_t ch2 = aBuffer.CharAt(AssertedCast<uint32_t>(aOffset + i + 1));
9784 if ((ch1 == 'i' && ch2 == 'j') || (ch1 == 'I' && ch2 == 'J')) {
9785 iter.SetOriginalOffset(aOffset + i + 1);
9786 FindClusterEnd(aTextRun, endOffset, &iter, allowSplitLigature);
9787 i = iter.GetOriginalOffset() - aOffset;
9788 if (i + 1 == length) {
9789 return true;
9790 }
9791 }
9792 }
9793
9794 // When we reach here, `i` points to the last character of the first-letter
9795 // cluster, NOT to the first character beyond it. Advance to the next char,
9796 // ready to check for following whitespace/punctuation:
9797 ++i;
9798
9799 while (i < length) {
9800 // Skip over whitespace, except for word separator characters, before the
9801 // check for following punctuation. But remember the position before the
9802 // whitespace, in case we need to reset.
9803 const int32_t preWS = i;
9804 while (i < length) {
9805 char16_t ch = aBuffer.CharAt(AssertedCast<uint32_t>(aOffset + i));
9806 // The spec says the first-letter suffix includes "any intervening
9807 // typographic space -- characters belonging to the Zs Unicode general
9808 // category other than U+3000 IDEOGRAPHIC SPACE or a word separator",
9809 // where "word separator" includes U+0020 and U+00A0.
9810 if (ch == 0x0020 || ch == 0x00A0 || ch == 0x3000 ||
9811 unicode::GetGeneralCategory(ch) !=
9812 HB_UNICODE_GENERAL_CATEGORY_SPACE_SEPARATOR) {
9813 break;
9814 } else {
9815 ++i;
9816 }
9817 }
9818
9819 // Consume clusters that start with punctuation.
9820 const int32_t prePunct = i;
9821 i = FindEndOfSuffixPunctuationRun(aBuffer, aTextRun, &iter, aOffset, i,
9822 endOffset);
9823
9824 // If we didn't find punctuation here, then we also don't want to include
9825 // any preceding whitespace, so reset our index.
9826 if (i == prePunct) {
9827 i = preWS;
9828 break;
9829 }
9830 }
9831
9832 if (i < length) {
9833 *aLength = i;
9834 }
9835 return true;
9836}
9837
9838static uint32_t FindStartAfterSkippingWhitespace(
9839 nsTextFrame::PropertyProvider* aProvider,
9840 nsIFrame::InlineIntrinsicISizeData* aData, const nsStyleText* aTextStyle,
9841 gfxSkipCharsIterator* aIterator, uint32_t aFlowEndInTextRun) {
9842 if (aData->mSkipWhitespace) {
9843 while (aIterator->GetSkippedOffset() < aFlowEndInTextRun &&
9844 IsTrimmableSpace(aProvider->GetCharacterDataBuffer(),
9845 aIterator->GetOriginalOffset(), aTextStyle)) {
9846 aIterator->AdvanceOriginal(1);
9847 }
9848 }
9849 return aIterator->GetSkippedOffset();
9850}
9851
9852float nsTextFrame::GetFontSizeInflation() const {
9853 if (!HasFontSizeInflation()) {
9854 return 1.0f;
9855 }
9856 return GetProperty(FontSizeInflationProperty());
9857}
9858
9859void nsTextFrame::SetFontSizeInflation(float aInflation) {
9860 if (aInflation == 1.0f) {
9861 if (HasFontSizeInflation()) {
9862 RemoveStateBits(TEXT_HAS_FONT_INFLATION);
9863 RemoveProperty(FontSizeInflationProperty());
9864 }
9865 return;
9866 }
9867
9868 AddStateBits(TEXT_HAS_FONT_INFLATION);
9869 SetProperty(FontSizeInflationProperty(), aInflation);
9870}
9871
9872void nsTextFrame::SetHangableISize(nscoord aISize) {
9873 MOZ_ASSERT(aISize >= 0, "unexpected negative hangable advance")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aISize >= 0)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(aISize >= 0))), 0))) { do
{ } while (false); MOZ_ReportAssertionFailure("aISize >= 0"
" (" "unexpected negative hangable advance" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 9873); AnnotateMozCrashReason("MOZ_ASSERT" "(" "aISize >= 0"
") (" "unexpected negative hangable advance" ")"); do { MOZ_CrashSequence
(__null, 9873); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
9874 if (aISize <= 0) {
9875 ClearHangableISize();
9876 return;
9877 }
9878 SetProperty(HangableWhitespaceProperty(), aISize);
9879 mPropertyFlags |= PropertyFlags::HangableWS;
9880}
9881
9882nscoord nsTextFrame::GetHangableISize() const {
9883 MOZ_ASSERT(!!(mPropertyFlags & PropertyFlags::HangableWS) ==do { static_assert( mozilla::detail::AssertionConditionType<
decltype(!!(mPropertyFlags & PropertyFlags::HangableWS) ==
HasProperty(HangableWhitespaceProperty()))>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(!!(mPropertyFlags & PropertyFlags
::HangableWS) == HasProperty(HangableWhitespaceProperty()))))
, 0))) { do { } while (false); MOZ_ReportAssertionFailure("!!(mPropertyFlags & PropertyFlags::HangableWS) == HasProperty(HangableWhitespaceProperty())"
" (" "flag/property mismatch!" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 9885); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!!(mPropertyFlags & PropertyFlags::HangableWS) == HasProperty(HangableWhitespaceProperty())"
") (" "flag/property mismatch!" ")"); do { MOZ_CrashSequence
(__null, 9885); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
9884 HasProperty(HangableWhitespaceProperty()),do { static_assert( mozilla::detail::AssertionConditionType<
decltype(!!(mPropertyFlags & PropertyFlags::HangableWS) ==
HasProperty(HangableWhitespaceProperty()))>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(!!(mPropertyFlags & PropertyFlags
::HangableWS) == HasProperty(HangableWhitespaceProperty()))))
, 0))) { do { } while (false); MOZ_ReportAssertionFailure("!!(mPropertyFlags & PropertyFlags::HangableWS) == HasProperty(HangableWhitespaceProperty())"
" (" "flag/property mismatch!" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 9885); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!!(mPropertyFlags & PropertyFlags::HangableWS) == HasProperty(HangableWhitespaceProperty())"
") (" "flag/property mismatch!" ")"); do { MOZ_CrashSequence
(__null, 9885); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
9885 "flag/property mismatch!")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(!!(mPropertyFlags & PropertyFlags::HangableWS) ==
HasProperty(HangableWhitespaceProperty()))>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(!!(mPropertyFlags & PropertyFlags
::HangableWS) == HasProperty(HangableWhitespaceProperty()))))
, 0))) { do { } while (false); MOZ_ReportAssertionFailure("!!(mPropertyFlags & PropertyFlags::HangableWS) == HasProperty(HangableWhitespaceProperty())"
" (" "flag/property mismatch!" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 9885); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!!(mPropertyFlags & PropertyFlags::HangableWS) == HasProperty(HangableWhitespaceProperty())"
") (" "flag/property mismatch!" ")"); do { MOZ_CrashSequence
(__null, 9885); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
9886 return (mPropertyFlags & PropertyFlags::HangableWS)
9887 ? GetProperty(HangableWhitespaceProperty())
9888 : 0;
9889}
9890
9891void nsTextFrame::ClearHangableISize() {
9892 if (mPropertyFlags & PropertyFlags::HangableWS) {
9893 RemoveProperty(HangableWhitespaceProperty());
9894 mPropertyFlags &= ~PropertyFlags::HangableWS;
9895 }
9896}
9897
9898void nsTextFrame::SetTrimmableWS(gfxTextRun::TrimmableWS aTrimmableWS) {
9899 MOZ_ASSERT(aTrimmableWS.mAdvance >= 0, "negative trimmable size")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aTrimmableWS.mAdvance >= 0)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(aTrimmableWS.mAdvance >= 0
))), 0))) { do { } while (false); MOZ_ReportAssertionFailure(
"aTrimmableWS.mAdvance >= 0" " (" "negative trimmable size"
")", "./../../../layout/generic/nsTextFrame.cpp", 9899); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "aTrimmableWS.mAdvance >= 0" ") (" "negative trimmable size"
")"); do { MOZ_CrashSequence(__null, 9899); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
9900 if (aTrimmableWS.mAdvance <= 0) {
9901 ClearTrimmableWS();
9902 return;
9903 }
9904 SetProperty(TrimmableWhitespaceProperty(), aTrimmableWS);
9905 mPropertyFlags |= PropertyFlags::TrimmableWS;
9906}
9907
9908gfxTextRun::TrimmableWS nsTextFrame::GetTrimmableWS() const {
9909 MOZ_ASSERT(!!(mPropertyFlags & PropertyFlags::TrimmableWS) ==do { static_assert( mozilla::detail::AssertionConditionType<
decltype(!!(mPropertyFlags & PropertyFlags::TrimmableWS) ==
HasProperty(TrimmableWhitespaceProperty()))>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(!!(mPropertyFlags & PropertyFlags
::TrimmableWS) == HasProperty(TrimmableWhitespaceProperty()))
)), 0))) { do { } while (false); MOZ_ReportAssertionFailure("!!(mPropertyFlags & PropertyFlags::TrimmableWS) == HasProperty(TrimmableWhitespaceProperty())"
" (" "flag/property mismatch!" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 9911); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!!(mPropertyFlags & PropertyFlags::TrimmableWS) == HasProperty(TrimmableWhitespaceProperty())"
") (" "flag/property mismatch!" ")"); do { MOZ_CrashSequence
(__null, 9911); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
9910 HasProperty(TrimmableWhitespaceProperty()),do { static_assert( mozilla::detail::AssertionConditionType<
decltype(!!(mPropertyFlags & PropertyFlags::TrimmableWS) ==
HasProperty(TrimmableWhitespaceProperty()))>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(!!(mPropertyFlags & PropertyFlags
::TrimmableWS) == HasProperty(TrimmableWhitespaceProperty()))
)), 0))) { do { } while (false); MOZ_ReportAssertionFailure("!!(mPropertyFlags & PropertyFlags::TrimmableWS) == HasProperty(TrimmableWhitespaceProperty())"
" (" "flag/property mismatch!" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 9911); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!!(mPropertyFlags & PropertyFlags::TrimmableWS) == HasProperty(TrimmableWhitespaceProperty())"
") (" "flag/property mismatch!" ")"); do { MOZ_CrashSequence
(__null, 9911); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
9911 "flag/property mismatch!")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(!!(mPropertyFlags & PropertyFlags::TrimmableWS) ==
HasProperty(TrimmableWhitespaceProperty()))>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(!!(mPropertyFlags & PropertyFlags
::TrimmableWS) == HasProperty(TrimmableWhitespaceProperty()))
)), 0))) { do { } while (false); MOZ_ReportAssertionFailure("!!(mPropertyFlags & PropertyFlags::TrimmableWS) == HasProperty(TrimmableWhitespaceProperty())"
" (" "flag/property mismatch!" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 9911); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!!(mPropertyFlags & PropertyFlags::TrimmableWS) == HasProperty(TrimmableWhitespaceProperty())"
") (" "flag/property mismatch!" ")"); do { MOZ_CrashSequence
(__null, 9911); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
9912 return (mPropertyFlags & PropertyFlags::TrimmableWS)
9913 ? GetProperty(TrimmableWhitespaceProperty())
9914 : gfxTextRun::TrimmableWS{};
9915}
9916
9917void nsTextFrame::ClearTrimmableWS() {
9918 if (mPropertyFlags & PropertyFlags::TrimmableWS) {
9919 RemoveProperty(TrimmableWhitespaceProperty());
9920 mPropertyFlags &= ~PropertyFlags::TrimmableWS;
9921 }
9922}
9923
9924/* virtual */
9925void nsTextFrame::MarkIntrinsicISizesDirty() {
9926 ClearTextRuns();
9927 nsIFrame::MarkIntrinsicISizesDirty();
9928}
9929
9930// XXX this doesn't handle characters shaped by line endings. We need to
9931// temporarily override the "current line ending" settings.
9932void nsTextFrame::AddInlineMinISizeForFlow(gfxContext* aRenderingContext,
9933 InlineMinISizeData* aData,
9934 TextRunType aTextRunType) {
9935 uint32_t flowEndInTextRun;
9936 gfxSkipCharsIterator iter =
9937 EnsureTextRun(aTextRunType, aRenderingContext->GetDrawTarget(),
9938 aData->LineContainer(), aData->mLine, &flowEndInTextRun);
9939 gfxTextRun* textRun = GetTextRun(aTextRunType);
9940 if (!textRun) {
9941 return;
9942 }
9943
9944 // Pass null for the line container. This will disable tab spacing, but that's
9945 // OK since we can't really handle tabs for intrinsic sizing anyway.
9946 const nsStyleText* textStyle = StyleText();
9947 const auto& characterDataBuffer = CharacterDataBuffer();
9948
9949 // If we're hyphenating, the PropertyProvider needs the actual length;
9950 // otherwise we can just pass INT32_MAX to mean "all the text"
9951 int32_t len = INT32_MAX(2147483647);
9952 bool hyphenating = characterDataBuffer.GetLength() > 0 &&
9953 (textStyle->mHyphens == StyleHyphens::Auto ||
9954 (textStyle->mHyphens == StyleHyphens::Manual &&
9955 !!(textRun->GetFlags() &
9956 gfx::ShapedTextFlags::TEXT_ENABLE_HYPHEN_BREAKS)));
9957 if (hyphenating) {
9958 gfxSkipCharsIterator tmp(iter);
9959 len = std::min<int32_t>(GetContentOffset() + GetInFlowContentLength(),
9960 tmp.ConvertSkippedToOriginal(flowEndInTextRun)) -
9961 iter.GetOriginalOffset();
9962 }
9963 PropertyProvider provider(textRun, textStyle, characterDataBuffer, this, iter,
9964 len, nullptr, 0, aTextRunType,
9965 aData->mAtStartOfLine);
9966
9967 const bool collapseWhitespace = !textStyle->WhiteSpaceIsSignificant();
9968 const bool preformatNewlines = textStyle->NewlineIsSignificant(this);
9969 const bool preformatTabs = textStyle->WhiteSpaceIsSignificant();
9970 const bool whitespaceCanHang =
9971 textStyle->WhiteSpaceCanHangOrVisuallyCollapse();
9972 gfxFloat tabWidth = -1;
9973 const uint32_t start = FindStartAfterSkippingWhitespace(
9974 &provider, aData, textStyle, &iter, flowEndInTextRun);
9975
9976 // text-combine-upright frame is constantly 1em on inline-axis; but if it has
9977 // a following sibling with the same style, it contributes nothing.
9978 if (Style()->IsTextCombined()) {
9979 if (start < flowEndInTextRun && textRun->CanBreakLineBefore(start)) {
9980 aData->OptionallyBreak();
9981 }
9982 if (!GetNextSibling() || GetNextSibling()->Style() != Style()) {
9983 aData->mCurrentLine += provider.GetFontMetrics()->EmHeight();
9984 }
9985 aData->mTrailingWhitespace = 0;
9986 return;
9987 }
9988
9989 if (textStyle->EffectiveOverflowWrap() == StyleOverflowWrap::Anywhere &&
9990 textStyle->WordCanWrap(this)) {
9991 aData->OptionallyBreak();
9992 aData->mCurrentLine += textRun->GetMinAdvanceWidth(
9993 Range(start, flowEndInTextRun), provider.LetterSpacing());
9994 aData->mTrailingWhitespace = 0;
9995 aData->mAtStartOfLine = false;
9996 aData->OptionallyBreak();
9997 return;
9998 }
9999
10000 AutoTArray<gfxTextRun::HyphenType, BIG_TEXT_NODE_SIZE4096> hyphBuffer;
10001 if (hyphenating) {
10002 if (hyphBuffer.AppendElements(flowEndInTextRun - start, fallible)) {
10003 provider.GetHyphenationBreaks(Range(start, flowEndInTextRun),
10004 hyphBuffer.Elements());
10005 } else {
10006 hyphenating = false;
10007 }
10008 }
10009
10010 const auto* glyphs = textRun->GetCharacterGlyphs();
10011 const auto* limit = glyphs + flowEndInTextRun;
10012 const bool hasSpacing = provider.HasSpacing();
10013 nscoord wordAdvance = 0;
10014 uint32_t wordStart = start;
10015 for (const auto* g = glyphs + start; g <= limit; ++g) {
10016 if (!hyphenating) {
10017 while (g < limit && g->IsSimpleGlyphNoBreakBefore()) {
10018 wordAdvance += g->GetSimpleAdvance();
10019 ++g;
10020 }
10021 }
10022
10023 bool preformattedNewline = false;
10024 bool preformattedTab = false;
10025 if (g < limit) {
10026 // XXXldb Shouldn't we be including the newline as part of the
10027 // segment that it ends rather than part of the segment that it
10028 // starts?
10029 using CompressedGlyph = gfxTextRun::CompressedGlyph;
10030 preformattedNewline = preformatNewlines && g->CharIsNewline();
10031 preformattedTab = preformatTabs && g->CharIsTab();
10032 if (g->CanBreakBefore() != CompressedGlyph::FLAG_BREAK_TYPE_NORMAL &&
10033 !preformattedNewline && !preformattedTab &&
10034 (!hyphenating || !gfxTextRun::IsOptionalHyphenBreak(
10035 hyphBuffer[g - glyphs - start]))) {
10036 // we can't break here (and it's not the end of the flow)
10037 if (g->IsSimpleGlyph()) {
10038 wordAdvance += g->GetSimpleAdvance();
10039 } else if (!hasSpacing) {
10040 if (uint32_t count = g->GetGlyphCount()) {
10041 const auto* details = textRun->GetDetailedGlyphs(g - glyphs, count);
10042 while (count--) {
10043 wordAdvance += details->mAdvance;
10044 ++details;
10045 }
10046 }
10047 }
10048 continue;
10049 }
10050 }
10051
10052 const uint32_t wordEnd = g - glyphs;
10053 if (wordEnd > wordStart) {
10054 nscoord width = !hasSpacing &&
10055 (glyphs + wordStart)->IsLigatureGroupStart() &&
10056 (wordEnd == flowEndInTextRun ||
10057 (glyphs + wordEnd)->IsLigatureGroupStart())
10058 ? wordAdvance
10059 : NSToCoordCeilClamped(textRun->GetAdvanceWidth(
10060 Range(wordStart, wordEnd), &provider));
10061 width = std::max(0, width);
10062 aData->mCurrentLine = NSCoordSaturatingAdd(aData->mCurrentLine, width);
10063 aData->mAtStartOfLine = false;
10064
10065 if (collapseWhitespace || whitespaceCanHang) {
10066 uint32_t trimStart =
10067 GetEndOfTrimmedText(characterDataBuffer, textStyle, wordStart,
10068 wordEnd, &iter, whitespaceCanHang);
10069 if (trimStart == start) {
10070 // This is *all* trimmable whitespace, so whatever trailingWhitespace
10071 // we saw previously is still trailing...
10072 aData->mTrailingWhitespace += width;
10073 } else {
10074 // Some non-whitespace so the old trailingWhitespace is no longer
10075 // trailing
10076 nscoord wsWidth = NSToCoordCeilClamped(
10077 textRun->GetAdvanceWidth(Range(trimStart, wordEnd), &provider));
10078 aData->mTrailingWhitespace = std::max(0, wsWidth);
10079 }
10080 } else {
10081 aData->mTrailingWhitespace = 0;
10082 }
10083 }
10084
10085 if (g < limit) {
10086 if (g->IsSimpleGlyph()) {
10087 wordAdvance = g->GetSimpleAdvance();
10088 } else {
10089 wordAdvance = 0;
10090 if (!preformattedNewline && !preformattedTab && !hasSpacing) {
10091 if (uint32_t count = g->GetGlyphCount()) {
10092 const auto* details = textRun->GetDetailedGlyphs(g - glyphs, count);
10093 while (count--) {
10094 wordAdvance += details->mAdvance;
10095 ++details;
10096 }
10097 }
10098 }
10099 }
10100 }
10101
10102 if (preformattedTab) {
10103 PropertyProvider::Spacing spacing;
10104 provider.GetSpacing(Range(wordEnd, wordEnd + 1), &spacing);
10105 aData->mCurrentLine += nscoord(spacing.mBefore);
10106 if (tabWidth < 0) {
10107 tabWidth = ComputeTabWidthAppUnits(this);
10108 }
10109 gfxFloat afterTab = AdvanceToNextTab(aData->mCurrentLine, tabWidth,
10110 provider.MinTabAdvance());
10111 aData->mCurrentLine = nscoord(afterTab + spacing.mAfter);
10112 wordStart = wordEnd + 1;
10113 } else if (wordEnd < flowEndInTextRun ||
10114 (wordEnd == textRun->GetLength() &&
10115 (textRun->GetFlags2() &
10116 nsTextFrameUtils::Flags::HasTrailingBreak))) {
10117 if (preformattedNewline) {
10118 aData->ForceBreak();
10119 } else if (wordEnd < flowEndInTextRun && hyphenating &&
10120 gfxTextRun::IsOptionalHyphenBreak(
10121 hyphBuffer[wordEnd - start])) {
10122 aData->OptionallyBreak(NSToCoordRound(provider.GetHyphenWidth()));
10123 } else {
10124 aData->OptionallyBreak();
10125 }
10126 if (aData->mSkipWhitespace) {
10127 iter.SetSkippedOffset(wordEnd);
10128 wordStart = FindStartAfterSkippingWhitespace(
10129 &provider, aData, textStyle, &iter, flowEndInTextRun);
10130 } else {
10131 wordStart = wordEnd;
10132 }
10133 provider.SetStartOfLine(iter);
10134 }
10135 }
10136
10137 if (start < flowEndInTextRun) {
10138 // Check if we have collapsible whitespace at the end
10139 aData->mSkipWhitespace = IsTrimmableSpace(
10140 provider.GetCharacterDataBuffer(),
10141 iter.ConvertSkippedToOriginal(flowEndInTextRun - 1), textStyle);
10142 }
10143}
10144
10145bool nsTextFrame::IsCurrentFontInflation(float aInflation) const {
10146 return fabsf(aInflation - GetFontSizeInflation()) < 1e-6;
10147}
10148
10149void nsTextFrame::MaybeSplitFramesForFirstLetter() {
10150 if (!StaticPrefs::layout_css_intrinsic_size_first_letter_enabled()) {
10151 return;
10152 }
10153
10154 if (GetParent()->IsFloating() && GetContentLength() > 0) {
10155 // We've already claimed our first-letter content, don't try again.
10156 return;
10157 }
10158 if (GetPrevContinuation()) {
10159 // This isn't the first part of the first-letter.
10160 return;
10161 }
10162
10163 // Find the length of the first-letter. We need a textrun for this; just bail
10164 // out if we fail to create it.
10165 // But in the floating first-letter case, the text is initially all in our
10166 // next-in-flow, and the float itself is empty. So we need to look at that
10167 // textrun instead of our own during FindFirstLetterRange.
10168 nsTextFrame* f = GetParent()->IsFloating() ? GetNextInFlow() : this;
10169 gfxSkipCharsIterator iter = f->EnsureTextRun(nsTextFrame::eInflated);
10170 const gfxTextRun* textRun = f->GetTextRun(nsTextFrame::eInflated);
10171
10172 const auto& characterDataBuffer = CharacterDataBuffer();
10173 const int32_t length = GetInFlowContentLength();
10174 const int32_t offset = GetContentOffset();
10175 int32_t firstLetterLength = length;
10176 NewlineProperty* cachedNewlineOffset = nullptr;
10177 int32_t newLineOffset = -1; // this will be -1 or a content offset
10178 // This will just return -1 if newlines are not significant.
10179 int32_t contentNewLineOffset =
10180 GetContentNewLineOffset(offset, cachedNewlineOffset);
10181 if (contentNewLineOffset < offset + length) {
10182 // The new line offset could be outside this frame if the frame has been
10183 // split by bidi resolution. In that case we won't use it in this reflow
10184 // (newLineOffset will remain -1), but we will still cache it in mContent
10185 newLineOffset = contentNewLineOffset;
10186 if (newLineOffset >= 0) {
10187 firstLetterLength = newLineOffset - offset;
10188 }
10189 }
10190
10191 if (contentNewLineOffset >= 0 && contentNewLineOffset < offset) {
10192 // We're in a first-letter frame's first in flow, so if there
10193 // was a first-letter, we'd be it. However, for one reason
10194 // or another (e.g., preformatted line break before this text),
10195 // we're not actually supposed to have first-letter style. So
10196 // just make a zero-length first-letter.
10197 firstLetterLength = 0;
10198 } else {
10199 // We only pass a language code to FindFirstLetterRange if it was
10200 // explicit in the content.
10201 const nsStyleFont* styleFont = StyleFont();
10202 const nsAtom* lang =
10203 styleFont->mExplicitLanguage ? styleFont->GetLangAtom() : nullptr;
10204 FindFirstLetterRange(characterDataBuffer, lang, textRun, offset, iter,
10205 &firstLetterLength);
10206 if (newLineOffset >= 0) {
10207 // Don't allow a preformatted newline to be part of a first-letter.
10208 firstLetterLength = std::min(firstLetterLength, length - 1);
10209 }
10210 }
10211 if (firstLetterLength) {
10212 AddStateBits(TEXT_FIRST_LETTER);
10213 }
10214
10215 // Change this frame's length to the first-letter length right now
10216 // so that when we rebuild the textrun it will be built with the
10217 // right first-letter boundary.
10218 SetFirstLetterLength(firstLetterLength);
10219}
10220
10221static bool IsUnreflowedLetterFrame(nsIFrame* aFrame) {
10222 return aFrame->IsLetterFrame() &&
10223 aFrame->HasAnyStateBits(NS_FRAME_FIRST_REFLOW);
10224}
10225
10226// XXX Need to do something here to avoid incremental reflow bugs due to
10227// first-line changing min-width
10228/* virtual */
10229void nsTextFrame::AddInlineMinISize(const IntrinsicSizeInput& aInput,
10230 InlineMinISizeData* aData) {
10231 // Check if this textframe belongs to a first-letter frame that has not yet
10232 // been reflowed; if so, we need to deal with splitting off a continuation
10233 // before we can measure the advance correctly.
10234 if (IsUnreflowedLetterFrame(GetParent())) {
10235 MaybeSplitFramesForFirstLetter();
10236 }
10237
10238 float inflation = nsLayoutUtils::FontSizeInflationFor(this);
10239 TextRunType trtype = (inflation == 1.0f) ? eNotInflated : eInflated;
10240
10241 if (trtype == eInflated && !IsCurrentFontInflation(inflation)) {
10242 // FIXME: Ideally, if we already have a text run, we'd move it to be
10243 // the uninflated text run.
10244 ClearTextRun(nullptr, nsTextFrame::eInflated);
10245 mFontMetrics = nullptr;
10246 }
10247
10248 nsTextFrame* f;
10249 const gfxTextRun* lastTextRun = nullptr;
10250 // nsContinuingTextFrame does nothing for AddInlineMinISize; all text frames
10251 // in the flow are handled right here.
10252 for (f = this; f; f = f->GetNextContinuation()) {
10253 // f->GetTextRun(nsTextFrame::eNotInflated) could be null if we
10254 // haven't set up textruns yet for f. Except in OOM situations,
10255 // lastTextRun will only be null for the first text frame.
10256 if (f == this || f->GetTextRun(trtype) != lastTextRun) {
10257 nsIFrame* lc;
10258 if (aData->LineContainer() &&
10259 aData->LineContainer() != (lc = f->FindLineContainer())) {
10260 NS_ASSERTION(f != this,do { if (!(f != this)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "wrong InlineMinISizeData container"
" for first continuation", "f != this", "./../../../layout/generic/nsTextFrame.cpp"
, 10262); MOZ_PretendNoReturn(); } } while (0)
10261 "wrong InlineMinISizeData container"do { if (!(f != this)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "wrong InlineMinISizeData container"
" for first continuation", "f != this", "./../../../layout/generic/nsTextFrame.cpp"
, 10262); MOZ_PretendNoReturn(); } } while (0)
10262 " for first continuation")do { if (!(f != this)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "wrong InlineMinISizeData container"
" for first continuation", "f != this", "./../../../layout/generic/nsTextFrame.cpp"
, 10262); MOZ_PretendNoReturn(); } } while (0)
;
10263 aData->mLine = nullptr;
10264 aData->SetLineContainer(lc);
10265 }
10266
10267 // This will process all the text frames that share the same textrun as f.
10268 f->AddInlineMinISizeForFlow(aInput.mContext, aData, trtype);
10269 lastTextRun = f->GetTextRun(trtype);
10270 }
10271 }
10272}
10273
10274// XXX this doesn't handle characters shaped by line endings. We need to
10275// temporarily override the "current line ending" settings.
10276void nsTextFrame::AddInlinePrefISizeForFlow(gfxContext* aRenderingContext,
10277 InlinePrefISizeData* aData,
10278 TextRunType aTextRunType) {
10279 if (IsUnreflowedLetterFrame(GetParent())) {
10280 MaybeSplitFramesForFirstLetter();
10281 }
10282
10283 uint32_t flowEndInTextRun;
10284 gfxSkipCharsIterator iter =
10285 EnsureTextRun(aTextRunType, aRenderingContext->GetDrawTarget(),
10286 aData->LineContainer(), aData->mLine, &flowEndInTextRun);
10287 gfxTextRun* textRun = GetTextRun(aTextRunType);
10288 if (!textRun) {
10289 return;
10290 }
10291
10292 // Pass null for the line container. This will disable tab spacing, but that's
10293 // OK since we can't really handle tabs for intrinsic sizing anyway.
10294
10295 const nsStyleText* textStyle = StyleText();
10296 const auto& characterDataBuffer = CharacterDataBuffer();
10297 PropertyProvider provider(textRun, textStyle, characterDataBuffer, this, iter,
10298 INT32_MAX(2147483647), nullptr, 0, aTextRunType,
10299 aData->mLineIsEmpty);
10300
10301 // text-combine-upright frame is constantly 1em on inline-axis, or zero if
10302 // there is a following sibling with the same style.
10303 if (Style()->IsTextCombined()) {
10304 if (!GetNextSibling() || GetNextSibling()->Style() != Style()) {
10305 aData->mCurrentLine += provider.GetFontMetrics()->EmHeight();
10306 }
10307 aData->mTrailingWhitespace = 0;
10308 aData->mLineIsEmpty = false;
10309 return;
10310 }
10311
10312 bool collapseWhitespace = !textStyle->WhiteSpaceIsSignificant();
10313 bool preformatNewlines = textStyle->NewlineIsSignificant(this);
10314 bool preformatTabs = textStyle->TabIsSignificant();
10315 gfxFloat tabWidth = -1;
10316 uint32_t start = FindStartAfterSkippingWhitespace(&provider, aData, textStyle,
10317 &iter, flowEndInTextRun);
10318 if (aData->mLineIsEmpty) {
10319 provider.SetStartOfLine(iter);
10320 }
10321
10322 // XXX Should we consider hyphenation here?
10323 // If newlines and tabs aren't preformatted, nothing to do inside
10324 // the loop so skip directly to the end
10325 const uint32_t loopStart =
10326 (preformatNewlines || preformatTabs) ? start : flowEndInTextRun;
10327 const auto* glyphs = textRun->GetCharacterGlyphs();
10328 const auto* limit = glyphs + flowEndInTextRun;
10329 const bool canUseSimpleAdvance = loopStart == start && !provider.HasSpacing();
10330 uint32_t lineStart = start;
10331 nscoord runAdvance = 0;
10332 for (const auto* g = glyphs + loopStart; g <= limit; ++g) {
10333 // Simple glyphs are never tabs or newlines, so batch-skip them to avoid
10334 // per-character CharIsTab/CharIsNewline calls.
10335 while (g < limit && g->IsSimpleGlyph()) {
10336 runAdvance += g->GetSimpleAdvance();
10337 ++g;
10338 }
10339
10340 // Now /g/ points to a non-simple glyph, or has reached /limit/.
10341 bool preformattedNewline = false;
10342 bool preformattedTab = false;
10343 if (g < limit) {
10344 // XXXldb Shouldn't we be including the newline as part of the
10345 // segment that it ends rather than part of the segment that it
10346 // starts?
10347 MOZ_ASSERT(preformatNewlines || preformatTabs,do { static_assert( mozilla::detail::AssertionConditionType<
decltype(preformatNewlines || preformatTabs)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(preformatNewlines || preformatTabs
))), 0))) { do { } while (false); MOZ_ReportAssertionFailure(
"preformatNewlines || preformatTabs" " (" "We can't be here unless newlines are "
"hard breaks or there are tabs" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 10349); AnnotateMozCrashReason("MOZ_ASSERT" "(" "preformatNewlines || preformatTabs"
") (" "We can't be here unless newlines are " "hard breaks or there are tabs"
")"); do { MOZ_CrashSequence(__null, 10349); __attribute__((
nomerge)) ::abort(); } while (false); } } while (false)
10348 "We can't be here unless newlines are "do { static_assert( mozilla::detail::AssertionConditionType<
decltype(preformatNewlines || preformatTabs)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(preformatNewlines || preformatTabs
))), 0))) { do { } while (false); MOZ_ReportAssertionFailure(
"preformatNewlines || preformatTabs" " (" "We can't be here unless newlines are "
"hard breaks or there are tabs" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 10349); AnnotateMozCrashReason("MOZ_ASSERT" "(" "preformatNewlines || preformatTabs"
") (" "We can't be here unless newlines are " "hard breaks or there are tabs"
")"); do { MOZ_CrashSequence(__null, 10349); __attribute__((
nomerge)) ::abort(); } while (false); } } while (false)
10349 "hard breaks or there are tabs")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(preformatNewlines || preformatTabs)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(preformatNewlines || preformatTabs
))), 0))) { do { } while (false); MOZ_ReportAssertionFailure(
"preformatNewlines || preformatTabs" " (" "We can't be here unless newlines are "
"hard breaks or there are tabs" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 10349); AnnotateMozCrashReason("MOZ_ASSERT" "(" "preformatNewlines || preformatTabs"
") (" "We can't be here unless newlines are " "hard breaks or there are tabs"
")"); do { MOZ_CrashSequence(__null, 10349); __attribute__((
nomerge)) ::abort(); } while (false); } } while (false)
;
10350 MOZ_ASSERT(!g->IsSimpleGlyph(), "should have been skipped")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(!g->IsSimpleGlyph())>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(!g->IsSimpleGlyph()))), 0
))) { do { } while (false); MOZ_ReportAssertionFailure("!g->IsSimpleGlyph()"
" (" "should have been skipped" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 10350); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!g->IsSimpleGlyph()"
") (" "should have been skipped" ")"); do { MOZ_CrashSequence
(__null, 10350); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
10351 preformattedNewline = preformatNewlines && g->CharIsNewline();
10352 preformattedTab = preformatTabs && g->CharIsTab();
10353 // Skip accumulating advance if there's custom spacing in the
10354 // PropertyProvider; we'll call textRun->GetAdvanceWidth instead.
10355 if (canUseSimpleAdvance) {
10356 if (uint32_t count = g->GetGlyphCount()) {
10357 // We don't check g->ApplyLetterSpacingBetweenDetailedGlyphs() here
10358 // because canUseSimpleAdvance depends on there being no spacing to
10359 // consider.
10360 const auto* details = textRun->GetDetailedGlyphs(g - glyphs, count);
10361 while (count--) {
10362 runAdvance += details->mAdvance;
10363 ++details;
10364 }
10365 }
10366 }
10367 if (!preformattedNewline && !preformattedTab) {
10368 // we needn't break here (and it's not the end of the flow)
10369 continue;
10370 }
10371 }
10372
10373 uint32_t lineEnd = g - glyphs;
10374 if (lineEnd > lineStart) {
10375 // If there's no custom spacing, and we didn't immediately skip to
10376 // the end of the loop, and neither end of the range falls within a
10377 // ligature, we can use the advance that was accumulated above.
10378 nscoord width = canUseSimpleAdvance &&
10379 (glyphs + lineStart)->IsLigatureGroupStart() &&
10380 (lineEnd == flowEndInTextRun ||
10381 (glyphs + lineEnd)->IsLigatureGroupStart())
10382 ? runAdvance
10383 : NSToCoordCeilClamped(textRun->GetAdvanceWidth(
10384 Range(lineStart, lineEnd), &provider));
10385 width = std::max(0, width);
10386 aData->mCurrentLine = NSCoordSaturatingAdd(aData->mCurrentLine, width);
10387 aData->mLineIsEmpty = false;
10388
10389 if (collapseWhitespace) {
10390 uint32_t trimStart = GetEndOfTrimmedText(characterDataBuffer, textStyle,
10391 lineStart, lineEnd, &iter);
10392 if (trimStart == start) {
10393 // This is *all* trimmable whitespace, so whatever trailingWhitespace
10394 // we saw previously is still trailing...
10395 aData->mTrailingWhitespace += width;
10396 } else {
10397 // Some non-whitespace so the old trailingWhitespace is no longer
10398 // trailing
10399 nscoord wsWidth = NSToCoordCeilClamped(
10400 textRun->GetAdvanceWidth(Range(trimStart, lineEnd), &provider));
10401 aData->mTrailingWhitespace = std::max(0, wsWidth);
10402 }
10403 } else {
10404 aData->mTrailingWhitespace = 0;
10405 }
10406 }
10407 runAdvance = 0;
10408
10409 if (preformattedTab) {
10410 PropertyProvider::Spacing spacing;
10411 provider.GetSpacing(Range(lineEnd, lineEnd + 1), &spacing);
10412 aData->mCurrentLine += nscoord(spacing.mBefore);
10413 if (tabWidth < 0) {
10414 tabWidth = ComputeTabWidthAppUnits(this);
10415 }
10416 gfxFloat afterTab = AdvanceToNextTab(aData->mCurrentLine, tabWidth,
10417 provider.MinTabAdvance());
10418 aData->mCurrentLine = nscoord(afterTab + spacing.mAfter);
10419 aData->mLineIsEmpty = false;
10420 lineStart = lineEnd + 1;
10421 } else if (preformattedNewline) {
10422 aData->ForceBreak();
10423 lineStart = lineEnd;
10424 }
10425 }
10426
10427 // Check if we have collapsible whitespace at the end
10428 if (start < flowEndInTextRun) {
10429 aData->mSkipWhitespace = IsTrimmableSpace(
10430 provider.GetCharacterDataBuffer(),
10431 iter.ConvertSkippedToOriginal(flowEndInTextRun - 1), textStyle);
10432 }
10433}
10434
10435// XXX Need to do something here to avoid incremental reflow bugs due to
10436// first-line and first-letter changing pref-width
10437/* virtual */
10438void nsTextFrame::AddInlinePrefISize(const IntrinsicSizeInput& aInput,
10439 InlinePrefISizeData* aData) {
10440 float inflation = nsLayoutUtils::FontSizeInflationFor(this);
10441 TextRunType trtype = (inflation == 1.0f) ? eNotInflated : eInflated;
10442
10443 if (trtype == eInflated && !IsCurrentFontInflation(inflation)) {
10444 // FIXME: Ideally, if we already have a text run, we'd move it to be
10445 // the uninflated text run.
10446 ClearTextRun(nullptr, nsTextFrame::eInflated);
10447 mFontMetrics = nullptr;
10448 }
10449
10450 nsTextFrame* f;
10451 const gfxTextRun* lastTextRun = nullptr;
10452 // nsContinuingTextFrame does nothing for AddInlineMinISize; all text frames
10453 // in the flow are handled right here.
10454 for (f = this; f; f = f->GetNextContinuation()) {
10455 // f->GetTextRun(nsTextFrame::eNotInflated) could be null if we
10456 // haven't set up textruns yet for f. Except in OOM situations,
10457 // lastTextRun will only be null for the first text frame.
10458 if (f == this || f->GetTextRun(trtype) != lastTextRun) {
10459 nsIFrame* lc;
10460 if (aData->LineContainer() &&
10461 aData->LineContainer() != (lc = f->FindLineContainer())) {
10462 NS_ASSERTION(f != this,do { if (!(f != this)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "wrong InlinePrefISizeData container"
" for first continuation", "f != this", "./../../../layout/generic/nsTextFrame.cpp"
, 10464); MOZ_PretendNoReturn(); } } while (0)
10463 "wrong InlinePrefISizeData container"do { if (!(f != this)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "wrong InlinePrefISizeData container"
" for first continuation", "f != this", "./../../../layout/generic/nsTextFrame.cpp"
, 10464); MOZ_PretendNoReturn(); } } while (0)
10464 " for first continuation")do { if (!(f != this)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "wrong InlinePrefISizeData container"
" for first continuation", "f != this", "./../../../layout/generic/nsTextFrame.cpp"
, 10464); MOZ_PretendNoReturn(); } } while (0)
;
10465 aData->mLine = nullptr;
10466 aData->SetLineContainer(lc);
10467 }
10468
10469 // This will process all the text frames that share the same textrun as f.
10470 f->AddInlinePrefISizeForFlow(aInput.mContext, aData, trtype);
10471 lastTextRun = f->GetTextRun(trtype);
10472 }
10473 }
10474}
10475
10476/* virtual */
10477nsIFrame::SizeComputationResult nsTextFrame::ComputeSize(
10478 const SizeComputationInput& aSizingInput, WritingMode aWM,
10479 const LogicalSize& aCBSize, nscoord aAvailableISize,
10480 const LogicalSize& aMargin, const LogicalSize& aBorderPadding,
10481 const StyleSizeOverrides& aSizeOverrides, ComputeSizeFlags aFlags) {
10482 // Inlines and text don't compute size before reflow.
10483 return {LogicalSize(aWM, NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE),
10484 AspectRatioUsage::None};
10485}
10486
10487static nsRect RoundOut(const gfxRect& aRect) {
10488 nsRect r;
10489 r.x = NSToCoordFloor(aRect.X());
10490 r.y = NSToCoordFloor(aRect.Y());
10491 r.width = NSToCoordCeil(aRect.XMost()) - r.x;
10492 r.height = NSToCoordCeil(aRect.YMost()) - r.y;
10493 return r;
10494}
10495
10496nsRect nsTextFrame::ComputeTightBounds(DrawTarget* aDrawTarget) const {
10497 if (Style()->HasTextDecorationLines() || HasAnyStateBits(TEXT_HYPHEN_BREAK)) {
10498 // This is conservative, but OK.
10499 return InkOverflowRect();
10500 }
10501
10502 gfxSkipCharsIterator iter =
10503 const_cast<nsTextFrame*>(this)->EnsureTextRun(nsTextFrame::eInflated);
10504 if (!mTextRun) {
10505 return nsRect();
10506 }
10507
10508 PropertyProvider provider(const_cast<nsTextFrame*>(this), iter,
10509 nsTextFrame::eInflated, mFontMetrics);
10510 // Trim trailing whitespace
10511 provider.InitializeForDisplay(true);
10512
10513 gfxTextRun::Metrics metrics = mTextRun->MeasureText(
10514 ComputeTransformedRange(provider), gfxFont::TIGHT_HINTED_OUTLINE_EXTENTS,
10515 aDrawTarget, &provider);
10516 if (GetWritingMode().IsLineInverted()) {
10517 metrics.mBoundingBox.y = -metrics.mBoundingBox.YMost();
10518 }
10519 // mAscent should be the same as metrics.mAscent, but it's what we use to
10520 // paint so that's the one we'll use.
10521 nsRect boundingBox = RoundOut(metrics.mBoundingBox);
10522 boundingBox += nsPoint(0, mAscent);
10523 if (mTextRun->IsVertical()) {
10524 // Swap line-relative textMetrics dimensions to physical coordinates.
10525 std::swap(boundingBox.x, boundingBox.y);
10526 std::swap(boundingBox.width, boundingBox.height);
10527 }
10528 return boundingBox;
10529}
10530
10531/* virtual */
10532nsresult nsTextFrame::GetPrefWidthTightBounds(gfxContext* aContext, nscoord* aX,
10533 nscoord* aXMost) {
10534 gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
10535 if (!mTextRun) {
10536 return NS_ERROR_FAILURE;
10537 }
10538
10539 PropertyProvider provider(this, iter, nsTextFrame::eInflated, mFontMetrics);
10540 provider.InitializeForMeasure();
10541
10542 gfxTextRun::Metrics metrics = mTextRun->MeasureText(
10543 ComputeTransformedRange(provider), gfxFont::TIGHT_HINTED_OUTLINE_EXTENTS,
10544 aContext->GetDrawTarget(), &provider);
10545 // Round it like nsTextFrame::ComputeTightBounds() to ensure consistency.
10546 *aX = NSToCoordFloor(metrics.mBoundingBox.x);
10547 *aXMost = NSToCoordCeil(metrics.mBoundingBox.XMost());
10548
10549 return NS_OK;
10550}
10551
10552static bool HasSoftHyphenBefore(const CharacterDataBuffer& aBuffer,
10553 const gfxTextRun* aTextRun,
10554 int32_t aStartOffset,
10555 const gfxSkipCharsIterator& aIter) {
10556 if (aIter.GetSkippedOffset() < aTextRun->GetLength() &&
10557 aTextRun->CanHyphenateBefore(aIter.GetSkippedOffset())) {
10558 return true;
10559 }
10560 if (!(aTextRun->GetFlags2() & nsTextFrameUtils::Flags::HasShy)) {
10561 return false;
10562 }
10563 gfxSkipCharsIterator iter = aIter;
10564 while (iter.GetOriginalOffset() > aStartOffset) {
10565 iter.AdvanceOriginal(-1);
10566 if (!iter.IsOriginalCharSkipped()) {
10567 break;
10568 }
10569 if (aBuffer.CharAt(AssertedCast<uint32_t>(iter.GetOriginalOffset())) ==
10570 CH_SHY173) {
10571 return true;
10572 }
10573 }
10574 return false;
10575}
10576
10577/**
10578 * Removes all frames from aFrame up to (but not including) aFirstToNotRemove,
10579 * because their text has all been taken and reflowed by earlier frames.
10580 */
10581static void RemoveEmptyInFlows(nsTextFrame* aFrame,
10582 nsTextFrame* aFirstToNotRemove) {
10583 MOZ_ASSERT(aFrame != aFirstToNotRemove, "This will go very badly")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aFrame != aFirstToNotRemove)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(aFrame != aFirstToNotRemove)
)), 0))) { do { } while (false); MOZ_ReportAssertionFailure("aFrame != aFirstToNotRemove"
" (" "This will go very badly" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 10583); AnnotateMozCrashReason("MOZ_ASSERT" "(" "aFrame != aFirstToNotRemove"
") (" "This will go very badly" ")"); do { MOZ_CrashSequence
(__null, 10583); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
10584 // We have to be careful here, because some RemoveFrame implementations
10585 // remove and destroy not only the passed-in frame but also all its following
10586 // in-flows (and sometimes all its following continuations in general). So
10587 // we remove |f| and everything up to but not including firstToNotRemove from
10588 // the flow first, to make sure that only the things we want destroyed are
10589 // destroyed.
10590
10591 // This sadly duplicates some of the logic from
10592 // nsSplittableFrame::RemoveFromFlow. We can get away with not duplicating
10593 // all of it, because we know that the prev-continuation links of
10594 // firstToNotRemove and f are fluid, and non-null.
10595 NS_ASSERTION(aFirstToNotRemove->GetPrevContinuation() ==do { if (!(aFirstToNotRemove->GetPrevContinuation() == aFirstToNotRemove
->GetPrevInFlow() && aFirstToNotRemove->GetPrevInFlow
() != nullptr)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "aFirstToNotRemove should have a fluid prev continuation"
, "aFirstToNotRemove->GetPrevContinuation() == aFirstToNotRemove->GetPrevInFlow() && aFirstToNotRemove->GetPrevInFlow() != nullptr"
, "./../../../layout/generic/nsTextFrame.cpp", 10598); MOZ_PretendNoReturn
(); } } while (0)
10596 aFirstToNotRemove->GetPrevInFlow() &&do { if (!(aFirstToNotRemove->GetPrevContinuation() == aFirstToNotRemove
->GetPrevInFlow() && aFirstToNotRemove->GetPrevInFlow
() != nullptr)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "aFirstToNotRemove should have a fluid prev continuation"
, "aFirstToNotRemove->GetPrevContinuation() == aFirstToNotRemove->GetPrevInFlow() && aFirstToNotRemove->GetPrevInFlow() != nullptr"
, "./../../../layout/generic/nsTextFrame.cpp", 10598); MOZ_PretendNoReturn
(); } } while (0)
10597 aFirstToNotRemove->GetPrevInFlow() != nullptr,do { if (!(aFirstToNotRemove->GetPrevContinuation() == aFirstToNotRemove
->GetPrevInFlow() && aFirstToNotRemove->GetPrevInFlow
() != nullptr)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "aFirstToNotRemove should have a fluid prev continuation"
, "aFirstToNotRemove->GetPrevContinuation() == aFirstToNotRemove->GetPrevInFlow() && aFirstToNotRemove->GetPrevInFlow() != nullptr"
, "./../../../layout/generic/nsTextFrame.cpp", 10598); MOZ_PretendNoReturn
(); } } while (0)
10598 "aFirstToNotRemove should have a fluid prev continuation")do { if (!(aFirstToNotRemove->GetPrevContinuation() == aFirstToNotRemove
->GetPrevInFlow() && aFirstToNotRemove->GetPrevInFlow
() != nullptr)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "aFirstToNotRemove should have a fluid prev continuation"
, "aFirstToNotRemove->GetPrevContinuation() == aFirstToNotRemove->GetPrevInFlow() && aFirstToNotRemove->GetPrevInFlow() != nullptr"
, "./../../../layout/generic/nsTextFrame.cpp", 10598); MOZ_PretendNoReturn
(); } } while (0)
;
10599 NS_ASSERTION(aFrame->GetPrevContinuation() == aFrame->GetPrevInFlow() &&do { if (!(aFrame->GetPrevContinuation() == aFrame->GetPrevInFlow
() && aFrame->GetPrevInFlow() != nullptr)) { NS_DebugBreak
(NS_DEBUG_ASSERTION, "aFrame should have a fluid prev continuation"
, "aFrame->GetPrevContinuation() == aFrame->GetPrevInFlow() && aFrame->GetPrevInFlow() != nullptr"
, "./../../../layout/generic/nsTextFrame.cpp", 10601); MOZ_PretendNoReturn
(); } } while (0)
10600 aFrame->GetPrevInFlow() != nullptr,do { if (!(aFrame->GetPrevContinuation() == aFrame->GetPrevInFlow
() && aFrame->GetPrevInFlow() != nullptr)) { NS_DebugBreak
(NS_DEBUG_ASSERTION, "aFrame should have a fluid prev continuation"
, "aFrame->GetPrevContinuation() == aFrame->GetPrevInFlow() && aFrame->GetPrevInFlow() != nullptr"
, "./../../../layout/generic/nsTextFrame.cpp", 10601); MOZ_PretendNoReturn
(); } } while (0)
10601 "aFrame should have a fluid prev continuation")do { if (!(aFrame->GetPrevContinuation() == aFrame->GetPrevInFlow
() && aFrame->GetPrevInFlow() != nullptr)) { NS_DebugBreak
(NS_DEBUG_ASSERTION, "aFrame should have a fluid prev continuation"
, "aFrame->GetPrevContinuation() == aFrame->GetPrevInFlow() && aFrame->GetPrevInFlow() != nullptr"
, "./../../../layout/generic/nsTextFrame.cpp", 10601); MOZ_PretendNoReturn
(); } } while (0)
;
10602
10603 nsTextFrame* prevContinuation = aFrame->GetPrevContinuation();
10604 nsTextFrame* lastRemoved = aFirstToNotRemove->GetPrevContinuation();
10605
10606 for (nsTextFrame* f = aFrame; f != aFirstToNotRemove;
10607 f = f->GetNextContinuation()) {
10608 // f is going to be destroyed soon, after it is unlinked from the
10609 // continuation chain. If its textrun is going to be destroyed we need to
10610 // do it now, before we unlink the frames to remove from the flow,
10611 // because Destroy calls ClearTextRuns() and that will start at the
10612 // first frame with the text run and walk the continuations.
10613 if (f->IsInTextRunUserData()) {
10614 f->ClearTextRuns();
10615 } else {
10616 f->DisconnectTextRuns();
10617 }
10618 }
10619
10620 prevContinuation->SetNextInFlow(aFirstToNotRemove);
10621 aFirstToNotRemove->SetPrevInFlow(prevContinuation);
10622
10623 // **Note: it is important here that we clear the Next link from lastRemoved
10624 // BEFORE clearing the Prev link from aFrame, because SetPrevInFlow() will
10625 // follow the Next pointers, wiping out the cached mFirstContinuation field
10626 // from each following frame in the list. We need this to stop when it
10627 // reaches lastRemoved!
10628 lastRemoved->SetNextInFlow(nullptr);
10629 aFrame->SetPrevInFlow(nullptr);
10630
10631 nsContainerFrame* parent = aFrame->GetParent();
10632 nsIFrame::DestroyContext context(aFrame->PresShell());
10633 nsBlockFrame* parentBlock = do_QueryFrame(parent);
10634 if (parentBlock) {
10635 // Manually call DoRemoveFrame so we can tell it that we're
10636 // removing empty frames; this will keep it from blowing away
10637 // text runs.
10638 parentBlock->DoRemoveFrame(context, aFrame, nsBlockFrame::FRAMES_ARE_EMPTY);
10639 } else {
10640 // Just remove it normally; use FrameChildListID::NoReflowPrincipal to avoid
10641 // posting new reflows.
10642 parent->RemoveFrame(context, FrameChildListID::NoReflowPrincipal, aFrame);
10643 }
10644}
10645
10646void nsTextFrame::SetLength(int32_t aLength, nsLineLayout* aLineLayout,
10647 uint32_t aSetLengthFlags) {
10648 mContentLengthHint = aLength;
10649 int32_t end = GetContentOffset() + aLength;
10650 nsTextFrame* f = GetNextInFlow();
10651 if (!f) {
10652 return;
10653 }
10654
10655 // If our end offset is moving, then even if frames are not being pushed or
10656 // pulled, content is moving to or from the next line and the next line
10657 // must be reflowed.
10658 // If the next-continuation is dirty, then we should dirty the next line now
10659 // because we may have skipped doing it if we dirtied it in
10660 // CharacterDataChanged. This is ugly but teaching FrameNeedsReflow
10661 // and ChildIsDirty to handle a range of frames would be worse.
10662 if (aLineLayout &&
10663 (end != f->mContentOffset || f->HasAnyStateBits(NS_FRAME_IS_DIRTY))) {
10664 aLineLayout->SetDirtyNextLine();
10665 }
10666
10667 if (end < f->mContentOffset) {
10668 // Our frame is shrinking. Give the text to our next in flow.
10669 if (aLineLayout && HasSignificantTerminalNewline() &&
10670 !GetParent()->IsLetterFrame() &&
10671 (aSetLengthFlags & ALLOW_FRAME_CREATION_AND_DESTRUCTION)) {
10672 // Whatever text we hand to our next-in-flow will end up in a frame all of
10673 // its own, since it ends in a forced linebreak. Might as well just put
10674 // it in a separate frame now. This is important to prevent text run
10675 // churn; if we did not do that, then we'd likely end up rebuilding
10676 // textruns for all our following continuations.
10677 // We skip this optimization when the parent is a first-letter frame
10678 // because it doesn't deal well with more than one child frame.
10679 // We also skip this optimization if we were called during bidi
10680 // resolution, so as not to create a new frame which doesn't appear in
10681 // the bidi resolver's list of frames
10682 nsIFrame* newFrame =
10683 PresShell()->FrameConstructor()->CreateContinuingFrame(this,
10684 GetParent());
10685 nsTextFrame* next = static_cast<nsTextFrame*>(newFrame);
10686 GetParent()->InsertFrames(FrameChildListID::NoReflowPrincipal, this,
10687 aLineLayout->GetLine(),
10688 nsFrameList(next, next));
10689 f = next;
10690 }
10691
10692 f->mContentOffset = end;
10693 if (f->GetTextRun(nsTextFrame::eInflated) != mTextRun) {
10694 ClearTextRuns();
10695 f->ClearTextRuns();
10696 }
10697 return;
10698 }
10699 // Our frame is growing. Take text from our in-flow(s).
10700 // We can take text from frames in lines beyond just the next line.
10701 // We don't dirty those lines. That's OK, because when we reflow
10702 // our empty next-in-flow, it will take text from its next-in-flow and
10703 // dirty that line.
10704
10705 // Note that in the process we may end up removing some frames from
10706 // the flow if they end up empty.
10707 nsTextFrame* framesToRemove = nullptr;
10708 while (f && f->mContentOffset < end) {
10709 f->mContentOffset = end;
10710 if (f->GetTextRun(nsTextFrame::eInflated) != mTextRun) {
10711 ClearTextRuns();
10712 f->ClearTextRuns();
10713 }
10714 nsTextFrame* next = f->GetNextInFlow();
10715 // Note: the "f->GetNextSibling() == next" check below is to restrict
10716 // this optimization to the case where they are on the same child list.
10717 // Otherwise we might remove the only child of a nsFirstLetterFrame
10718 // for example and it can't handle that. See bug 597627 for details.
10719 if (next && next->mContentOffset <= end && f->GetNextSibling() == next &&
10720 (aSetLengthFlags & ALLOW_FRAME_CREATION_AND_DESTRUCTION)) {
10721 // |f| is now empty. We may as well remove it, instead of copying all
10722 // the text from |next| into it instead; the latter leads to use
10723 // rebuilding textruns for all following continuations.
10724 // We skip this optimization if we were called during bidi resolution,
10725 // since the bidi resolver may try to handle the destroyed frame later
10726 // and crash
10727 if (!framesToRemove) {
10728 // Remember that we have to remove this frame.
10729 framesToRemove = f;
10730 }
10731 } else if (framesToRemove) {
10732 RemoveEmptyInFlows(framesToRemove, f);
10733 framesToRemove = nullptr;
10734 }
10735 f = next;
10736 }
10737
10738 MOZ_ASSERT(!framesToRemove || (f && f->mContentOffset == end),do { static_assert( mozilla::detail::AssertionConditionType<
decltype(!framesToRemove || (f && f->mContentOffset
== end))>::isValid, "invalid assertion condition"); if ((
__builtin_expect(!!(!(!!(!framesToRemove || (f && f->
mContentOffset == end)))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("!framesToRemove || (f && f->mContentOffset == end)"
" (" "How did we exit the loop if we null out framesToRemove if "
"!next || next->mContentOffset > end ?" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 10740); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!framesToRemove || (f && f->mContentOffset == end)"
") (" "How did we exit the loop if we null out framesToRemove if "
"!next || next->mContentOffset > end ?" ")"); do { MOZ_CrashSequence
(__null, 10740); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
10739 "How did we exit the loop if we null out framesToRemove if "do { static_assert( mozilla::detail::AssertionConditionType<
decltype(!framesToRemove || (f && f->mContentOffset
== end))>::isValid, "invalid assertion condition"); if ((
__builtin_expect(!!(!(!!(!framesToRemove || (f && f->
mContentOffset == end)))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("!framesToRemove || (f && f->mContentOffset == end)"
" (" "How did we exit the loop if we null out framesToRemove if "
"!next || next->mContentOffset > end ?" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 10740); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!framesToRemove || (f && f->mContentOffset == end)"
") (" "How did we exit the loop if we null out framesToRemove if "
"!next || next->mContentOffset > end ?" ")"); do { MOZ_CrashSequence
(__null, 10740); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
10740 "!next || next->mContentOffset > end ?")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(!framesToRemove || (f && f->mContentOffset
== end))>::isValid, "invalid assertion condition"); if ((
__builtin_expect(!!(!(!!(!framesToRemove || (f && f->
mContentOffset == end)))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("!framesToRemove || (f && f->mContentOffset == end)"
" (" "How did we exit the loop if we null out framesToRemove if "
"!next || next->mContentOffset > end ?" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 10740); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!framesToRemove || (f && f->mContentOffset == end)"
") (" "How did we exit the loop if we null out framesToRemove if "
"!next || next->mContentOffset > end ?" ")"); do { MOZ_CrashSequence
(__null, 10740); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
10741
10742 if (framesToRemove) {
10743 // We are guaranteed that we exited the loop with f not null, per the
10744 // postcondition above
10745 RemoveEmptyInFlows(framesToRemove, f);
10746 }
10747
10748#ifdef DEBUG1
10749 f = this;
10750 int32_t iterations = 0;
10751 while (f && iterations < 10) {
10752 f->GetContentLength(); // Assert if negative length
10753 f = f->GetNextContinuation();
10754 ++iterations;
10755 }
10756 f = this;
10757 iterations = 0;
10758 while (f && iterations < 10) {
10759 f->GetContentLength(); // Assert if negative length
10760 f = f->GetPrevContinuation();
10761 ++iterations;
10762 }
10763#endif
10764}
10765
10766void nsTextFrame::SetFirstLetterLength(int32_t aLength) {
10767 if (aLength == GetContentLength()) {
10768 return;
10769 }
10770
10771 mContentLengthHint = aLength;
10772 nsTextFrame* next = static_cast<nsTextFrame*>(GetNextInFlow());
10773 if (!aLength && !next) {
10774 return;
10775 }
10776
10777 if (aLength > GetContentLength()) {
10778 // Stealing some text from our next-in-flow; this happens with floating
10779 // first-letter, which is initially given a zero-length range, with all
10780 // the text being in its continuation.
10781 if (!next) {
10782 MOZ_ASSERT_UNREACHABLE("Expected a next-in-flow; first-letter broken?")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(false)>::isValid, "invalid assertion condition");
if ((__builtin_expect(!!(!(!!(false))), 0))) { do { } while (
false); MOZ_ReportAssertionFailure("false" " (" "MOZ_ASSERT_UNREACHABLE: "
"Expected a next-in-flow; first-letter broken?" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 10782); AnnotateMozCrashReason("MOZ_ASSERT" "(" "false" ") ("
"MOZ_ASSERT_UNREACHABLE: " "Expected a next-in-flow; first-letter broken?"
")"); do { MOZ_CrashSequence(__null, 10782); __attribute__((
nomerge)) ::abort(); } while (false); } } while (false)
;
10783 return;
10784 }
10785 } else if (!next) {
10786 // We need to create a continuation for the parent first-letter frame,
10787 // and move any kids after this frame to the new one; if there are none,
10788 // a new continuing text frame will be created there.
10789 MOZ_ASSERT(GetParent()->IsLetterFrame())do { static_assert( mozilla::detail::AssertionConditionType<
decltype(GetParent()->IsLetterFrame())>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(GetParent()->IsLetterFrame
()))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("GetParent()->IsLetterFrame()", "./../../../layout/generic/nsTextFrame.cpp"
, 10789); AnnotateMozCrashReason("MOZ_ASSERT" "(" "GetParent()->IsLetterFrame()"
")"); do { MOZ_CrashSequence(__null, 10789); __attribute__((
nomerge)) ::abort(); } while (false); } } while (false)
;
10790 auto* letterFrame = static_cast<nsFirstLetterFrame*>(GetParent());
10791 next = letterFrame->CreateContinuationForFramesAfter(this);
Value stored to 'next' is never read
10792 }
10793
10794 SetLength(aLength, nullptr, 0);
10795
10796 ClearTextRuns();
10797}
10798
10799bool nsTextFrame::IsFloatingFirstLetterChild() const {
10800 nsIFrame* frame = GetParent();
10801 return frame && frame->IsFloating() && frame->IsLetterFrame();
10802}
10803
10804bool nsTextFrame::IsInitialLetterChild() const {
10805 nsIFrame* frame = GetParent();
10806 return frame && frame->StyleTextReset()->mInitialLetter.size != 0.0f &&
10807 frame->IsLetterFrame();
10808}
10809
10810struct nsTextFrame::NewlineProperty {
10811 int32_t mStartOffset;
10812 // The offset of the first \n after mStartOffset, or -1 if there is none
10813 int32_t mNewlineOffset;
10814};
10815
10816int32_t nsTextFrame::GetContentNewLineOffset(
10817 int32_t aOffset, NewlineProperty*& aCachedNewlineOffset) {
10818 int32_t contentNewLineOffset = -1; // this will be -1 or a content offset
10819 if (StyleText()->NewlineIsSignificant(this)) {
10820 // Pointer to the nsGkAtoms::newline set on this frame's element
10821 aCachedNewlineOffset = mContent->HasFlag(NS_HAS_NEWLINE_PROPERTY)
10822 ? static_cast<NewlineProperty*>(
10823 mContent->GetProperty(nsGkAtoms::newline))
10824 : nullptr;
10825 if (aCachedNewlineOffset && aCachedNewlineOffset->mStartOffset <= aOffset &&
10826 (aCachedNewlineOffset->mNewlineOffset == -1 ||
10827 aCachedNewlineOffset->mNewlineOffset >= aOffset)) {
10828 contentNewLineOffset = aCachedNewlineOffset->mNewlineOffset;
10829 } else {
10830 contentNewLineOffset =
10831 FindChar(CharacterDataBuffer(), aOffset,
10832 GetContent()->TextLength() - aOffset, '\n');
10833 }
10834 }
10835
10836 return contentNewLineOffset;
10837}
10838
10839void nsTextFrame::Reflow(nsPresContext* aPresContext, ReflowOutput& aMetrics,
10840 const ReflowInput& aReflowInput,
10841 nsReflowStatus& aStatus) {
10842 MarkInReflow();
10843 DO_GLOBAL_REFLOW_COUNT("nsTextFrame")aPresContext->CountReflows(("nsTextFrame"), (nsIFrame*)this
);
;
10844 MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aStatus.IsEmpty())>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(aStatus.IsEmpty()))), 0))) {
do { } while (false); MOZ_ReportAssertionFailure("aStatus.IsEmpty()"
" (" "Caller should pass a fresh reflow status!" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 10844); AnnotateMozCrashReason("MOZ_ASSERT" "(" "aStatus.IsEmpty()"
") (" "Caller should pass a fresh reflow status!" ")"); do {
MOZ_CrashSequence(__null, 10844); __attribute__((nomerge)) ::
abort(); } while (false); } } while (false)
;
10845
10846 InvalidateSelectionState();
10847
10848 // XXX If there's no line layout, we shouldn't even have created this
10849 // frame. This may happen if, for example, this is text inside a table
10850 // but not inside a cell. For now, just don't reflow.
10851 if (!aReflowInput.mLineLayout) {
10852 ClearMetrics(aMetrics);
10853 return;
10854 }
10855
10856 ReflowText(*aReflowInput.mLineLayout, aReflowInput.AvailableWidth(),
10857 aReflowInput.mRenderingContext->GetDrawTarget(), aMetrics,
10858 aStatus);
10859}
10860
10861#ifdef ACCESSIBILITY1
10862/**
10863 * Notifies accessibility about text reflow. Used by nsTextFrame::ReflowText.
10864 */
10865class MOZ_STACK_CLASS ReflowTextA11yNotifier {
10866 public:
10867 ReflowTextA11yNotifier(nsPresContext* aPresContext, nsIContent* aContent)
10868 : mContent(aContent), mPresContext(aPresContext) {}
10869 ~ReflowTextA11yNotifier() {
10870 if (nsAccessibilityService* accService = GetAccService()) {
10871 accService->UpdateText(mPresContext->PresShell(), mContent);
10872 }
10873 }
10874
10875 ReflowTextA11yNotifier() = delete;
10876 ReflowTextA11yNotifier(const ReflowTextA11yNotifier&) = delete;
10877 ReflowTextA11yNotifier& operator=(const ReflowTextA11yNotifier&) = delete;
10878
10879 private:
10880 nsIContent* mContent;
10881 nsPresContext* mPresContext;
10882};
10883#endif
10884
10885void nsTextFrame::ReflowText(nsLineLayout& aLineLayout, nscoord aAvailableWidth,
10886 DrawTarget* aDrawTarget, ReflowOutput& aMetrics,
10887 nsReflowStatus& aStatus) {
10888 MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aStatus.IsEmpty())>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(aStatus.IsEmpty()))), 0))) {
do { } while (false); MOZ_ReportAssertionFailure("aStatus.IsEmpty()"
" (" "Caller should pass a fresh reflow status!" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 10888); AnnotateMozCrashReason("MOZ_ASSERT" "(" "aStatus.IsEmpty()"
") (" "Caller should pass a fresh reflow status!" ")"); do {
MOZ_CrashSequence(__null, 10888); __attribute__((nomerge)) ::
abort(); } while (false); } } while (false)
;
10889
10890#ifdef NOISY_REFLOW
10891 ListTag(stdoutstdout);
10892 printf(": BeginReflow: availableWidth=%d\n", aAvailableWidth);
10893#endif
10894
10895 nsPresContext* presContext = PresContext();
10896
10897#ifdef ACCESSIBILITY1
10898 // Schedule the update of accessible tree since rendered text might be
10899 // changed.
10900 if (StyleVisibility()->IsVisible()) {
10901 ReflowTextA11yNotifier(presContext, mContent);
10902 }
10903#endif
10904
10905 /////////////////////////////////////////////////////////////////////
10906 // Set up flags and clear out state
10907 /////////////////////////////////////////////////////////////////////
10908
10909 // Clear out the reflow input flags in mState. We also clear the whitespace
10910 // flags because this can change whether the frame maps whitespace-only text
10911 // or not. We also clear the flag that tracks whether we had a pending
10912 // reflow request from CharacterDataChanged (since we're reflowing now).
10913 RemoveStateBits(TEXT_REFLOW_FLAGS | TEXT_WHITESPACE_FLAGS);
10914 mReflowRequestedForCharDataChange = false;
10915 RemoveProperty(WebRenderTextBounds());
10916
10917 // Discard cached continuations array that will be invalidated by the reflow.
10918 if (nsTextFrame* first = FirstContinuation()) {
10919 first->ClearCachedContinuations();
10920 }
10921
10922 // Temporarily map all possible content while we construct our new textrun.
10923 // so that when doing reflow our styles prevail over any part of the
10924 // textrun we look at. Note that next-in-flows may be mapping the same
10925 // content; gfxTextRun construction logic will ensure that we take priority.
10926 int32_t maxContentLength = GetInFlowContentLength();
10927
10928 InvalidateSelectionState();
10929
10930 // We don't need to reflow if there is no content.
10931 if (!maxContentLength) {
10932 ClearMetrics(aMetrics);
10933 return;
10934 }
10935
10936#ifdef NOISY_BIDI
10937 printf("Reflowed textframe\n");
10938#endif
10939
10940 const nsStyleText* textStyle = StyleText();
10941
10942 bool atStartOfLine = aLineLayout.LineAtStart();
10943 if (atStartOfLine) {
10944 AddStateBits(TEXT_START_OF_LINE);
10945 }
10946
10947 uint32_t flowEndInTextRun;
10948 nsIFrame* lineContainer = aLineLayout.LineContainerFrame();
10949 const auto& characterDataBuffer = CharacterDataBuffer();
10950
10951 // DOM offsets of the text range we need to measure, after trimming
10952 // whitespace, restricting to first-letter, and restricting preformatted text
10953 // to nearest newline
10954 int32_t length = maxContentLength;
10955 int32_t offset = GetContentOffset();
10956
10957 // Restrict preformatted text to the nearest newline
10958 NewlineProperty* cachedNewlineOffset = nullptr;
10959 int32_t newLineOffset = -1; // this will be -1 or a content offset
10960 // This will just return -1 if newlines are not significant.
10961 int32_t contentNewLineOffset =
10962 GetContentNewLineOffset(offset, cachedNewlineOffset);
10963 if (contentNewLineOffset < offset + length) {
10964 // The new line offset could be outside this frame if the frame has been
10965 // split by bidi resolution. In that case we won't use it in this reflow
10966 // (newLineOffset will remain -1), but we will still cache it in mContent
10967 newLineOffset = contentNewLineOffset;
10968 }
10969 if (newLineOffset >= 0) {
10970 length = newLineOffset + 1 - offset;
10971 }
10972
10973 if ((atStartOfLine && !textStyle->WhiteSpaceIsSignificant()) ||
10974 HasAnyStateBits(TEXT_IS_IN_TOKEN_MATHML)) {
10975 // Skip leading whitespace. Make sure we don't skip a 'pre-line'
10976 // newline if there is one.
10977 int32_t skipLength = newLineOffset >= 0 ? length - 1 : length;
10978 int32_t whitespaceCount =
10979 GetTrimmableWhitespaceCount(characterDataBuffer, offset, skipLength, 1);
10980 if (whitespaceCount) {
10981 offset += whitespaceCount;
10982 length -= whitespaceCount;
10983 // Make sure this frame maps the trimmable whitespace.
10984 if (MOZ_UNLIKELY(offset > GetContentEnd())(__builtin_expect(!!(offset > GetContentEnd()), 0))) {
10985 SetLength(offset - GetContentOffset(), &aLineLayout,
10986 ALLOW_FRAME_CREATION_AND_DESTRUCTION);
10987 }
10988 }
10989 }
10990
10991 // If trimming whitespace left us with nothing to do, return early.
10992 if (length == 0) {
10993 ClearMetrics(aMetrics);
10994 return;
10995 }
10996
10997 bool completedFirstLetter = false;
10998 // Layout dependent styles are a problem because we need to reconstruct
10999 // the gfxTextRun based on our layout.
11000 if (aLineLayout.GetInFirstLetter() || aLineLayout.GetInFirstLine()) {
11001 SetLength(maxContentLength, &aLineLayout,
11002 ALLOW_FRAME_CREATION_AND_DESTRUCTION);
11003
11004 if (aLineLayout.GetInFirstLetter()) {
11005 // floating first-letter boundaries are significant in textrun
11006 // construction, so clear the textrun out every time we hit a first-letter
11007 // and have changed our length (which controls the first-letter boundary)
11008 ClearTextRuns();
11009 // Find the length of the first-letter. We need a textrun for this.
11010 // REVIEW: maybe-bogus inflation should be ok (fixed below)
11011 gfxSkipCharsIterator iter =
11012 EnsureTextRun(nsTextFrame::eInflated, aDrawTarget, lineContainer,
11013 aLineLayout.GetLine(), &flowEndInTextRun);
11014
11015 if (mTextRun) {
11016 int32_t firstLetterLength = length;
11017 if (aLineLayout.GetFirstLetterStyleOK()) {
11018 // We only pass a language code to FindFirstLetterRange if it was
11019 // explicit in the content.
11020 const nsStyleFont* styleFont = StyleFont();
11021 const nsAtom* lang =
11022 styleFont->mExplicitLanguage ? styleFont->GetLangAtom() : nullptr;
11023 completedFirstLetter =
11024 FindFirstLetterRange(characterDataBuffer, lang, mTextRun, offset,
11025 iter, &firstLetterLength);
11026 if (newLineOffset >= 0) {
11027 // Don't allow a preformatted newline to be part of a first-letter.
11028 firstLetterLength = std::min(firstLetterLength, length - 1);
11029 if (length == 1) {
11030 // There is no text to be consumed by the first-letter before the
11031 // preformatted newline. Note that the first letter is therefore
11032 // complete (FindFirstLetterRange will have returned false).
11033 completedFirstLetter = true;
11034 }
11035 }
11036 } else {
11037 // We're in a first-letter frame's first in flow, so if there
11038 // was a first-letter, we'd be it. However, for one reason
11039 // or another (e.g., preformatted line break before this text),
11040 // we're not actually supposed to have first-letter style. So
11041 // just make a zero-length first-letter.
11042 firstLetterLength = 0;
11043 completedFirstLetter = true;
11044 }
11045 length = firstLetterLength;
11046 if (length) {
11047 AddStateBits(TEXT_FIRST_LETTER);
11048 }
11049 // Change this frame's length to the first-letter length right now
11050 // so that when we rebuild the textrun it will be built with the
11051 // right first-letter boundary
11052 SetLength(offset + length - GetContentOffset(), &aLineLayout,
11053 ALLOW_FRAME_CREATION_AND_DESTRUCTION);
11054 // Ensure that the textrun will be rebuilt
11055 ClearTextRuns();
11056 }
11057 }
11058 }
11059
11060 float fontSizeInflation = nsLayoutUtils::FontSizeInflationFor(this);
11061
11062 if (!IsCurrentFontInflation(fontSizeInflation)) {
11063 // FIXME: Ideally, if we already have a text run, we'd move it to be
11064 // the uninflated text run.
11065 ClearTextRun(nullptr, nsTextFrame::eInflated);
11066 mFontMetrics = nullptr;
11067 }
11068
11069 gfxSkipCharsIterator iter =
11070 EnsureTextRun(nsTextFrame::eInflated, aDrawTarget, lineContainer,
11071 aLineLayout.GetLine(), &flowEndInTextRun);
11072
11073 NS_ASSERTION(IsCurrentFontInflation(fontSizeInflation),do { if (!(IsCurrentFontInflation(fontSizeInflation))) { NS_DebugBreak
(NS_DEBUG_ASSERTION, "EnsureTextRun should have set font size inflation"
, "IsCurrentFontInflation(fontSizeInflation)", "./../../../layout/generic/nsTextFrame.cpp"
, 11074); MOZ_PretendNoReturn(); } } while (0)
11074 "EnsureTextRun should have set font size inflation")do { if (!(IsCurrentFontInflation(fontSizeInflation))) { NS_DebugBreak
(NS_DEBUG_ASSERTION, "EnsureTextRun should have set font size inflation"
, "IsCurrentFontInflation(fontSizeInflation)", "./../../../layout/generic/nsTextFrame.cpp"
, 11074); MOZ_PretendNoReturn(); } } while (0)
;
11075
11076 if (mTextRun && iter.GetOriginalEnd() < offset + length) {
11077 // The textrun does not map enough text for this frame. This can happen
11078 // when the textrun was ended in the middle of a text node because a
11079 // preformatted newline was encountered, and prev-in-flow frames have
11080 // consumed all the text of the textrun. We need a new textrun.
11081 ClearTextRuns();
11082 iter = EnsureTextRun(nsTextFrame::eInflated, aDrawTarget, lineContainer,
11083 aLineLayout.GetLine(), &flowEndInTextRun);
11084 }
11085
11086 if (!mTextRun) {
11087 ClearMetrics(aMetrics);
11088 return;
11089 }
11090
11091 NS_ASSERTION(gfxSkipCharsIterator(iter).ConvertOriginalToSkipped(do { if (!(gfxSkipCharsIterator(iter).ConvertOriginalToSkipped
( offset + length) <= mTextRun->GetLength())) { NS_DebugBreak
(NS_DEBUG_ASSERTION, "Text run does not map enough text for our reflow"
, "gfxSkipCharsIterator(iter).ConvertOriginalToSkipped( offset + length) <= mTextRun->GetLength()"
, "./../../../layout/generic/nsTextFrame.cpp", 11093); MOZ_PretendNoReturn
(); } } while (0)
11092 offset + length) <= mTextRun->GetLength(),do { if (!(gfxSkipCharsIterator(iter).ConvertOriginalToSkipped
( offset + length) <= mTextRun->GetLength())) { NS_DebugBreak
(NS_DEBUG_ASSERTION, "Text run does not map enough text for our reflow"
, "gfxSkipCharsIterator(iter).ConvertOriginalToSkipped( offset + length) <= mTextRun->GetLength()"
, "./../../../layout/generic/nsTextFrame.cpp", 11093); MOZ_PretendNoReturn
(); } } while (0)
11093 "Text run does not map enough text for our reflow")do { if (!(gfxSkipCharsIterator(iter).ConvertOriginalToSkipped
( offset + length) <= mTextRun->GetLength())) { NS_DebugBreak
(NS_DEBUG_ASSERTION, "Text run does not map enough text for our reflow"
, "gfxSkipCharsIterator(iter).ConvertOriginalToSkipped( offset + length) <= mTextRun->GetLength()"
, "./../../../layout/generic/nsTextFrame.cpp", 11093); MOZ_PretendNoReturn
(); } } while (0)
;
11094
11095 /////////////////////////////////////////////////////////////////////
11096 // See how much text should belong to this text frame, and measure it
11097 /////////////////////////////////////////////////////////////////////
11098
11099 iter.SetOriginalOffset(offset);
11100 nscoord xOffsetForTabs =
11101 (mTextRun->GetFlags2() & nsTextFrameUtils::Flags::HasTab)
11102 ? (aLineLayout.GetCurrentFrameInlineDistanceFromBlock() -
11103 lineContainer->GetUsedBorderAndPadding().left)
11104 : -1;
11105 PropertyProvider provider(mTextRun, textStyle, characterDataBuffer, this,
11106 iter, length, lineContainer, xOffsetForTabs,
11107 nsTextFrame::eInflated,
11108 HasAnyStateBits(TEXT_START_OF_LINE));
11109
11110 uint32_t transformedOffset = provider.GetStart().GetSkippedOffset();
11111
11112 gfxFont::BoundingBoxType boundingBoxType = gfxFont::LOOSE_INK_EXTENTS;
11113 if (IsFloatingFirstLetterChild() || IsInitialLetterChild()) {
11114 if (nsFirstLetterFrame* firstLetter = do_QueryFrame(GetParent())) {
11115 if (firstLetter->UseTightBounds()) {
11116 boundingBoxType = gfxFont::TIGHT_HINTED_OUTLINE_EXTENTS;
11117 }
11118 }
11119 }
11120
11121 int32_t limitLength = length;
11122 int32_t forceBreak = aLineLayout.GetForcedBreakPosition(this);
11123 bool forceBreakAfter = false;
11124 if (forceBreak >= length) {
11125 forceBreakAfter = forceBreak == length;
11126 // The break is not within the text considered for this textframe.
11127 forceBreak = -1;
11128 }
11129 if (forceBreak >= 0) {
11130 limitLength = forceBreak;
11131 }
11132 // This is the heart of text reflow right here! We don't know where
11133 // to break, so we need to see how much text fits in the available width.
11134 uint32_t transformedLength;
11135 if (offset + limitLength >= int32_t(characterDataBuffer.GetLength())) {
11136 NS_ASSERTION(do { if (!(offset + limitLength == int32_t(characterDataBuffer
.GetLength()))) { NS_DebugBreak(NS_DEBUG_ASSERTION, "Content offset/length out of bounds"
, "offset + limitLength == int32_t(characterDataBuffer.GetLength())"
, "./../../../layout/generic/nsTextFrame.cpp", 11138); MOZ_PretendNoReturn
(); } } while (0)
11137 offset + limitLength == int32_t(characterDataBuffer.GetLength()),do { if (!(offset + limitLength == int32_t(characterDataBuffer
.GetLength()))) { NS_DebugBreak(NS_DEBUG_ASSERTION, "Content offset/length out of bounds"
, "offset + limitLength == int32_t(characterDataBuffer.GetLength())"
, "./../../../layout/generic/nsTextFrame.cpp", 11138); MOZ_PretendNoReturn
(); } } while (0)
11138 "Content offset/length out of bounds")do { if (!(offset + limitLength == int32_t(characterDataBuffer
.GetLength()))) { NS_DebugBreak(NS_DEBUG_ASSERTION, "Content offset/length out of bounds"
, "offset + limitLength == int32_t(characterDataBuffer.GetLength())"
, "./../../../layout/generic/nsTextFrame.cpp", 11138); MOZ_PretendNoReturn
(); } } while (0)
;
11139 NS_ASSERTION(flowEndInTextRun >= transformedOffset,do { if (!(flowEndInTextRun >= transformedOffset)) { NS_DebugBreak
(NS_DEBUG_ASSERTION, "Negative flow length?", "flowEndInTextRun >= transformedOffset"
, "./../../../layout/generic/nsTextFrame.cpp", 11140); MOZ_PretendNoReturn
(); } } while (0)
11140 "Negative flow length?")do { if (!(flowEndInTextRun >= transformedOffset)) { NS_DebugBreak
(NS_DEBUG_ASSERTION, "Negative flow length?", "flowEndInTextRun >= transformedOffset"
, "./../../../layout/generic/nsTextFrame.cpp", 11140); MOZ_PretendNoReturn
(); } } while (0)
;
11141 transformedLength = flowEndInTextRun - transformedOffset;
11142 } else {
11143 // we're not looking at all the content, so we need to compute the
11144 // length of the transformed substring we're looking at
11145 gfxSkipCharsIterator iter(provider.GetStart());
11146 iter.SetOriginalOffset(offset + limitLength);
11147 transformedLength = iter.GetSkippedOffset() - transformedOffset;
11148 }
11149 gfxTextRun::Metrics textMetrics;
11150 uint32_t transformedLastBreak = 0;
11151 bool usedHyphenation = false;
11152 gfxTextRun::TrimmableWS trimmableWS;
11153 gfxFloat availWidth = aAvailableWidth;
11154 if (Style()->IsTextCombined()) {
11155 // If text-combine-upright is 'all', we would compress whatever long
11156 // text into ~1em width, so there is no limited on the avail width.
11157 availWidth = std::numeric_limits<gfxFloat>::infinity();
11158 }
11159 bool canTrimTrailingWhitespace = !textStyle->WhiteSpaceIsSignificant() ||
11160 HasAnyStateBits(TEXT_IS_IN_TOKEN_MATHML);
11161 bool isBreakSpaces =
11162 textStyle->mWhiteSpaceCollapse == StyleWhiteSpaceCollapse::BreakSpaces;
11163 // allow whitespace to overflow the container
11164 bool whitespaceCanHang = textStyle->WhiteSpaceCanHangOrVisuallyCollapse();
11165 gfxBreakPriority breakPriority = aLineLayout.LastOptionalBreakPriority();
11166 gfxTextRun::SuppressBreak suppressBreak = gfxTextRun::eNoSuppressBreak;
11167 bool shouldSuppressLineBreak = ShouldSuppressLineBreak();
11168 if (shouldSuppressLineBreak) {
11169 suppressBreak = gfxTextRun::eSuppressAllBreaks;
11170 } else if (!aLineLayout.LineIsBreakable()) {
11171 suppressBreak = gfxTextRun::eSuppressInitialBreak;
11172 }
11173 uint32_t transformedCharsFit = mTextRun->BreakAndMeasureText(
11174 transformedOffset, transformedLength, HasAnyStateBits(TEXT_START_OF_LINE),
11175 availWidth, provider, suppressBreak, boundingBoxType, aDrawTarget,
11176 textStyle->WordCanWrap(this), textStyle->WhiteSpaceCanWrap(this),
11177 isBreakSpaces,
11178 // The following are output parameters:
11179 canTrimTrailingWhitespace || whitespaceCanHang ? &trimmableWS : nullptr,
11180 textMetrics, usedHyphenation, transformedLastBreak,
11181 // In/out
11182 breakPriority);
11183 if (!length && !textMetrics.mAscent && !textMetrics.mDescent) {
11184 // If we're measuring a zero-length piece of text, update
11185 // the height manually.
11186 nsFontMetrics* fm = provider.GetFontMetrics();
11187 if (fm) {
11188 textMetrics.mAscent = gfxFloat(fm->MaxAscent());
11189 textMetrics.mDescent = gfxFloat(fm->MaxDescent());
11190 }
11191 }
11192 if (GetWritingMode().IsLineInverted()) {
11193 std::swap(textMetrics.mAscent, textMetrics.mDescent);
11194 textMetrics.mBoundingBox.y = -textMetrics.mBoundingBox.YMost();
11195 }
11196 // The "end" iterator points to the first character after the string mapped
11197 // by this frame. Basically, its original-string offset is offset+charsFit
11198 // after we've computed charsFit.
11199 gfxSkipCharsIterator end(provider.GetEndHint());
11200 end.SetSkippedOffset(transformedOffset + transformedCharsFit);
11201 int32_t charsFit = end.GetOriginalOffset() - offset;
11202 if (offset + charsFit == newLineOffset) {
11203 // We broke before a trailing preformatted '\n'. The newline should
11204 // be assigned to this frame. Note that newLineOffset will be -1 if
11205 // there was no preformatted newline, so we wouldn't get here in that
11206 // case.
11207 ++charsFit;
11208 }
11209 // That might have taken us beyond our assigned content range (because
11210 // we might have advanced over some skipped chars that extend outside
11211 // this frame), so get back in.
11212 int32_t lastBreak = -1;
11213 if (charsFit >= limitLength) {
11214 charsFit = limitLength;
11215 if (transformedLastBreak != UINT32_MAX(4294967295U)) {
11216 // lastBreak is needed.
11217 // This may set lastBreak greater than 'length', but that's OK
11218 lastBreak = end.ConvertSkippedToOriginal(transformedOffset +
11219 transformedLastBreak);
11220 }
11221 end.SetOriginalOffset(offset + charsFit);
11222 // If we were forced to fit, and the break position is after a soft hyphen,
11223 // note that this is a hyphenation break.
11224 if ((forceBreak >= 0 || forceBreakAfter) &&
11225 HasSoftHyphenBefore(characterDataBuffer, mTextRun, offset, end)) {
11226 usedHyphenation = true;
11227 }
11228 }
11229 if (usedHyphenation) {
11230 // Fix up metrics to include hyphen
11231 AddHyphenToMetrics(this, mTextRun->IsRightToLeft(), &textMetrics,
11232 boundingBoxType, aDrawTarget);
11233 AddStateBits(TEXT_HYPHEN_BREAK | TEXT_HAS_NONCOLLAPSED_CHARACTERS);
11234 }
11235 if (textMetrics.mBoundingBox.IsEmpty()) {
11236 AddStateBits(TEXT_NO_RENDERED_GLYPHS);
11237 }
11238
11239 bool brokeText = forceBreak >= 0 || transformedCharsFit < transformedLength;
11240 if (trimmableWS.mAdvance > 0.0) {
11241 if (canTrimTrailingWhitespace) {
11242 // Optimization: if we we can be sure this frame will be at end of line,
11243 // then trim the whitespace now.
11244 if (brokeText || HasAnyStateBits(TEXT_IS_IN_TOKEN_MATHML)) {
11245 // We're definitely going to break so our trailing whitespace should
11246 // definitely be trimmed. Record that we've already done it.
11247 AddStateBits(TEXT_TRIMMED_TRAILING_WHITESPACE);
11248 textMetrics.mAdvanceWidth -= trimmableWS.mAdvance;
11249 trimmableWS.mAdvance = 0.0;
11250 }
11251 ClearHangableISize();
11252 ClearTrimmableWS();
11253 } else if (whitespaceCanHang) {
11254 // Figure out how much whitespace will hang if at end-of-line.
11255 gfxFloat hang =
11256 std::min(std::max(0.0, textMetrics.mAdvanceWidth - availWidth),
11257 gfxFloat(trimmableWS.mAdvance));
11258 SetHangableISize(NSToCoordRound(trimmableWS.mAdvance - hang));
11259 // nsLineLayout only needs the TrimmableWS property if justifying, so
11260 // check whether this is relevant.
11261 if (textStyle->mTextAlign == StyleTextAlign::Justify ||
11262 textStyle->mTextAlignLast == StyleTextAlignLast::Justify) {
11263 SetTrimmableWS(trimmableWS);
11264 }
11265 textMetrics.mAdvanceWidth -= hang;
11266 trimmableWS.mAdvance = 0.0;
11267 } else {
11268 MOZ_ASSERT_UNREACHABLE("How did trimmableWS get set?!")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(false)>::isValid, "invalid assertion condition");
if ((__builtin_expect(!!(!(!!(false))), 0))) { do { } while (
false); MOZ_ReportAssertionFailure("false" " (" "MOZ_ASSERT_UNREACHABLE: "
"How did trimmableWS get set?!" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 11268); AnnotateMozCrashReason("MOZ_ASSERT" "(" "false" ") ("
"MOZ_ASSERT_UNREACHABLE: " "How did trimmableWS get set?!" ")"
); do { MOZ_CrashSequence(__null, 11268); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
11269 ClearHangableISize();
11270 ClearTrimmableWS();
11271 trimmableWS.mAdvance = 0.0;
11272 }
11273 } else {
11274 // Remove any stale frame properties.
11275 ClearHangableISize();
11276 ClearTrimmableWS();
11277 }
11278
11279 if (!brokeText && lastBreak >= 0) {
11280 // Since everything fit and no break was forced,
11281 // record the last break opportunity
11282 NS_ASSERTION(textMetrics.mAdvanceWidth - trimmableWS.mAdvance <= availWidth,do { if (!(textMetrics.mAdvanceWidth - trimmableWS.mAdvance <=
availWidth)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "If the text doesn't fit, and we have a break opportunity, "
"why didn't MeasureText use it?", "textMetrics.mAdvanceWidth - trimmableWS.mAdvance <= availWidth"
, "./../../../layout/generic/nsTextFrame.cpp", 11284); MOZ_PretendNoReturn
(); } } while (0)
11283 "If the text doesn't fit, and we have a break opportunity, "do { if (!(textMetrics.mAdvanceWidth - trimmableWS.mAdvance <=
availWidth)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "If the text doesn't fit, and we have a break opportunity, "
"why didn't MeasureText use it?", "textMetrics.mAdvanceWidth - trimmableWS.mAdvance <= availWidth"
, "./../../../layout/generic/nsTextFrame.cpp", 11284); MOZ_PretendNoReturn
(); } } while (0)
11284 "why didn't MeasureText use it?")do { if (!(textMetrics.mAdvanceWidth - trimmableWS.mAdvance <=
availWidth)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "If the text doesn't fit, and we have a break opportunity, "
"why didn't MeasureText use it?", "textMetrics.mAdvanceWidth - trimmableWS.mAdvance <= availWidth"
, "./../../../layout/generic/nsTextFrame.cpp", 11284); MOZ_PretendNoReturn
(); } } while (0)
;
11285 MOZ_ASSERT(lastBreak >= offset, "Strange break position")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(lastBreak >= offset)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(lastBreak >= offset))), 0
))) { do { } while (false); MOZ_ReportAssertionFailure("lastBreak >= offset"
" (" "Strange break position" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 11285); AnnotateMozCrashReason("MOZ_ASSERT" "(" "lastBreak >= offset"
") (" "Strange break position" ")"); do { MOZ_CrashSequence(
__null, 11285); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
11286 aLineLayout.NotifyOptionalBreakPosition(this, lastBreak - offset, true,
11287 breakPriority);
11288 }
11289
11290 int32_t contentLength = offset + charsFit - GetContentOffset();
11291
11292 /////////////////////////////////////////////////////////////////////
11293 // Compute output metrics
11294 /////////////////////////////////////////////////////////////////////
11295
11296 // first-letter frames should use the tight bounding box metrics for
11297 // ascent/descent for good drop-cap effects
11298 if (HasAnyStateBits(TEXT_FIRST_LETTER)) {
11299 textMetrics.mAscent =
11300 std::max(gfxFloat(0.0), -textMetrics.mBoundingBox.Y());
11301 textMetrics.mDescent =
11302 std::max(gfxFloat(0.0), textMetrics.mBoundingBox.YMost());
11303 }
11304
11305 // Setup metrics for caller
11306 // Disallow negative widths
11307 WritingMode wm = GetWritingMode();
11308 LogicalSize finalSize(wm);
11309 finalSize.ISize(wm) =
11310 NSToCoordCeilClamped(std::max(gfxFloat(0.0), textMetrics.mAdvanceWidth));
11311
11312 nscoord fontBaseline;
11313 // Note(dshin): Baseline should tecnhically be halfway through the em box for
11314 // a central baseline. It is simply half of the text run block size so that it
11315 // can be easily calculated in `GetNaturalBaselineBOffset`.
11316 if (transformedCharsFit == 0 && !usedHyphenation) {
11317 aMetrics.SetBlockStartAscent(0);
11318 finalSize.BSize(wm) = 0;
11319 fontBaseline = 0;
11320 } else if (boundingBoxType != gfxFont::LOOSE_INK_EXTENTS) {
11321 fontBaseline = NSToCoordCeil(textMetrics.mAscent);
11322 const auto size = fontBaseline + NSToCoordCeil(textMetrics.mDescent);
11323 // Use actual text metrics for floating first letter frame.
11324 aMetrics.SetBlockStartAscent(wm.IsAlphabeticalBaseline() ? fontBaseline
11325 : size / 2);
11326 finalSize.BSize(wm) = size;
11327 } else {
11328 // Otherwise, ascent should contain the overline drawable area.
11329 // And also descent should contain the underline drawable area.
11330 // nsFontMetrics::GetMaxAscent/GetMaxDescent contains them.
11331 nsFontMetrics* fm = provider.GetFontMetrics();
11332 nscoord fontAscent =
11333 wm.IsLineInverted() ? fm->MaxDescent() : fm->MaxAscent();
11334 nscoord fontDescent =
11335 wm.IsLineInverted() ? fm->MaxAscent() : fm->MaxDescent();
11336 fontBaseline = std::max(NSToCoordCeil(textMetrics.mAscent), fontAscent);
11337 const auto size =
11338 fontBaseline +
11339 std::max(NSToCoordCeil(textMetrics.mDescent), fontDescent);
11340 aMetrics.SetBlockStartAscent(wm.IsAlphabeticalBaseline() ? fontBaseline
11341 : size / 2);
11342 finalSize.BSize(wm) = size;
11343 }
11344 if (Style()->IsTextCombined()) {
11345 nsFontMetrics* fm = provider.GetFontMetrics();
11346 nscoord width = finalSize.ISize(wm);
11347 nscoord em = fm->EmHeight();
11348 // Compress the characters in horizontal axis if necessary.
11349 auto* data = GetOrCreateDeletableProperty(TextCombineDataProperty());
11350 data->mNaturalWidth = width;
11351 finalSize.ISize(wm) = em;
11352 // If we're going to have to adjust the block-size to make it 1em, make an
11353 // appropriate adjustment to the font baseline (determined by the ascent
11354 // returned in aMetrics)
11355 if (finalSize.BSize(wm) != em) {
11356 fontBaseline =
11357 aMetrics.BlockStartAscent() + (em - finalSize.BSize(wm)) / 2;
11358 aMetrics.SetBlockStartAscent(fontBaseline);
11359 }
11360 // If we have a next-in-flow with the same style, remove our block-size
11361 // so that they end up beside each other; only the last in the series
11362 // gets to keep its block-axis size.
11363 if (GetNextSibling() && GetNextSibling()->Style() == Style()) {
11364 finalSize.BSize(wm) = 0;
11365 } else {
11366 // Make the characters be in an 1em square.
11367 finalSize.BSize(wm) = em;
11368 }
11369 // If there are earlier sibling frames with the same style, and we have
11370 // reached the end of the run of same-style siblings, recompute the scale
11371 // as necessary to make them all fit. (This is a bit inefficient, but is
11372 // such a rare case that we don't much care.)
11373 nsIFrame* f = GetPrevSibling();
11374 if (f && f->Style() == Style() &&
11375 (!GetNextSibling() || GetNextSibling()->Style() != Style())) {
11376 // Collect the total "natural" width of the text.
11377 while (f->Style() == Style()) {
11378 if (auto* fData = f->GetProperty(TextCombineDataProperty())) {
11379 width += fData->mNaturalWidth;
11380 }
11381 if (!f->GetPrevSibling()) {
11382 break;
11383 }
11384 f = f->GetPrevSibling();
11385 }
11386 } else {
11387 // Not at the end of a run of frames; we're just going to handle `this`.
11388 f = this;
11389 }
11390 // Figure out scaling to apply to this frame (or to the run of same-style
11391 // sibling frames), and the starting offset within the em square.
11392 float scale;
11393 nscoord offset;
11394 if (width > em) {
11395 scale = static_cast<float>(em) / width;
11396 offset = 0;
11397 } else {
11398 scale = 1.0f;
11399 offset = (em - width) / 2;
11400 }
11401 while (true) {
11402 if (auto* fData = f->GetProperty(TextCombineDataProperty())) {
11403 fData->mScale = scale;
11404 fData->mOffset = offset;
11405 offset += fData->mNaturalWidth * scale;
11406 }
11407 if (f == this) {
11408 break;
11409 }
11410 f = f->GetNextSibling();
11411 }
11412 }
11413 aMetrics.SetSize(wm, finalSize);
11414
11415 NS_ASSERTION(aMetrics.BlockStartAscent() >= 0, "Negative ascent???")do { if (!(aMetrics.BlockStartAscent() >= 0)) { NS_DebugBreak
(NS_DEBUG_ASSERTION, "Negative ascent???", "aMetrics.BlockStartAscent() >= 0"
, "./../../../layout/generic/nsTextFrame.cpp", 11415); MOZ_PretendNoReturn
(); } } while (0)
;
11416 // The effective "descent" here will be negative in the case of a frame that
11417 // is participating in text-combine-upright, but is not the last frame within
11418 // the combined upright cell, because we zero out its block-size (see above).
11419 // So this creates an exception to the non-negative assertion here.
11420 DebugOnly<nscoord> descent =
11421 (Style()->IsTextCombined() ? aMetrics.ISize(aMetrics.GetWritingMode())
11422 : aMetrics.BSize(aMetrics.GetWritingMode())) -
11423 aMetrics.BlockStartAscent();
11424 NS_ASSERTION(descent >= 0 || (Style()->IsTextCombined() && GetNextSibling() &&do { if (!(descent >= 0 || (Style()->IsTextCombined() &&
GetNextSibling() && GetNextSibling()->Style() == Style
()))) { NS_DebugBreak(NS_DEBUG_ASSERTION, "Unexpected negative descent???"
, "descent >= 0 || (Style()->IsTextCombined() && GetNextSibling() && GetNextSibling()->Style() == Style())"
, "./../../../layout/generic/nsTextFrame.cpp", 11426); MOZ_PretendNoReturn
(); } } while (0)
11425 GetNextSibling()->Style() == Style()),do { if (!(descent >= 0 || (Style()->IsTextCombined() &&
GetNextSibling() && GetNextSibling()->Style() == Style
()))) { NS_DebugBreak(NS_DEBUG_ASSERTION, "Unexpected negative descent???"
, "descent >= 0 || (Style()->IsTextCombined() && GetNextSibling() && GetNextSibling()->Style() == Style())"
, "./../../../layout/generic/nsTextFrame.cpp", 11426); MOZ_PretendNoReturn
(); } } while (0)
11426 "Unexpected negative descent???")do { if (!(descent >= 0 || (Style()->IsTextCombined() &&
GetNextSibling() && GetNextSibling()->Style() == Style
()))) { NS_DebugBreak(NS_DEBUG_ASSERTION, "Unexpected negative descent???"
, "descent >= 0 || (Style()->IsTextCombined() && GetNextSibling() && GetNextSibling()->Style() == Style())"
, "./../../../layout/generic/nsTextFrame.cpp", 11426); MOZ_PretendNoReturn
(); } } while (0)
;
11427
11428 mAscent = fontBaseline;
11429
11430 // Handle text that runs outside its normal bounds.
11431 nsRect boundingBox = RoundOut(textMetrics.mBoundingBox);
11432 if (mTextRun->IsVertical()) {
11433 // Swap line-relative textMetrics dimensions to physical coordinates.
11434 std::swap(boundingBox.x, boundingBox.y);
11435 std::swap(boundingBox.width, boundingBox.height);
11436 if (GetWritingMode().IsVerticalRL()) {
11437 boundingBox.x = -boundingBox.XMost();
11438 boundingBox.x += aMetrics.Width() - mAscent;
11439 } else {
11440 boundingBox.x += mAscent;
11441 }
11442 } else {
11443 boundingBox.y += mAscent;
11444 }
11445 aMetrics.SetOverflowAreasToDesiredBounds();
11446 aMetrics.InkOverflow().UnionRect(aMetrics.InkOverflow(), boundingBox);
11447
11448 // When we have text decorations, we don't need to compute their overflow now
11449 // because we're guaranteed to do it later
11450 // (see nsLineLayout::RelativePositionFrames)
11451 UnionAdditionalOverflow(presContext, aLineLayout.LineContainerFrame(),
11452 provider, &aMetrics.InkOverflow(), false, true);
11453
11454 /////////////////////////////////////////////////////////////////////
11455 // Clean up, update state
11456 /////////////////////////////////////////////////////////////////////
11457
11458 // If all our characters are discarded or collapsed, then trimmable width
11459 // from the last textframe should be preserved. Otherwise the trimmable width
11460 // from this textframe overrides. (Currently in CSS trimmable width can be
11461 // at most one space so there's no way for trimmable width from a previous
11462 // frame to accumulate with trimmable width from this frame.)
11463 if (transformedCharsFit > 0) {
11464 aLineLayout.SetTrimmableISize(NSToCoordFloor(trimmableWS.mAdvance));
11465 AddStateBits(TEXT_HAS_NONCOLLAPSED_CHARACTERS);
11466 }
11467 bool breakAfter = forceBreakAfter;
11468 if (!shouldSuppressLineBreak) {
11469 if (charsFit > 0 && charsFit == length &&
11470 textStyle->mHyphens != StyleHyphens::None &&
11471 HasSoftHyphenBefore(characterDataBuffer, mTextRun, offset, end)) {
11472 bool fits =
11473 textMetrics.mAdvanceWidth + provider.GetHyphenWidth() <= availWidth;
11474 // Record a potential break after final soft hyphen
11475 aLineLayout.NotifyOptionalBreakPosition(this, length, fits,
11476 gfxBreakPriority::eNormalBreak);
11477 }
11478 // length == 0 means either the text is empty or it's all collapsed away
11479 bool emptyTextAtStartOfLine = atStartOfLine && length == 0;
11480 if (!breakAfter && charsFit == length && !emptyTextAtStartOfLine &&
11481 transformedOffset + transformedLength == mTextRun->GetLength() &&
11482 (mTextRun->GetFlags2() & nsTextFrameUtils::Flags::HasTrailingBreak)) {
11483 // We placed all the text in the textrun and we have a break opportunity
11484 // at the end of the textrun. We need to record it because the following
11485 // content may not care about nsLineBreaker.
11486
11487 // Note that because we didn't break, we can be sure that (thanks to the
11488 // code up above) textMetrics.mAdvanceWidth includes the width of any
11489 // trailing whitespace. So we need to subtract trimmableWidth here
11490 // because if we did break at this point, that much width would be
11491 // trimmed.
11492 if (textMetrics.mAdvanceWidth - trimmableWS.mAdvance > availWidth) {
11493 breakAfter = true;
11494 } else {
11495 aLineLayout.NotifyOptionalBreakPosition(this, length, true,
11496 gfxBreakPriority::eNormalBreak);
11497 }
11498 }
11499 }
11500
11501 // Compute reflow status
11502 if (contentLength != maxContentLength) {
11503 aStatus.SetIncomplete();
11504 }
11505
11506 if (charsFit == 0 && length > 0 && !usedHyphenation) {
11507 // Couldn't place any text
11508 aStatus.SetInlineLineBreakBeforeAndReset();
11509 } else if (contentLength > 0 &&
11510 mContentOffset + contentLength - 1 == newLineOffset) {
11511 // Ends in \n
11512 aStatus.SetInlineLineBreakAfter();
11513 aLineLayout.SetLineEndsInBR(true);
11514 } else if (breakAfter) {
11515 aStatus.SetInlineLineBreakAfter();
11516 }
11517 if (completedFirstLetter) {
11518 aLineLayout.SetFirstLetterStyleOK(false);
11519 aStatus.SetFirstLetterComplete();
11520 }
11521 if (brokeText && breakPriority == gfxBreakPriority::eWordWrapBreak) {
11522 aLineLayout.SetUsedOverflowWrap();
11523 }
11524
11525 // Updated the cached NewlineProperty, or delete it.
11526 if (contentLength < maxContentLength &&
11527 textStyle->NewlineIsSignificant(this) &&
11528 (contentNewLineOffset < 0 ||
11529 mContentOffset + contentLength <= contentNewLineOffset)) {
11530 if (!cachedNewlineOffset) {
11531 cachedNewlineOffset = new NewlineProperty;
11532 if (NS_FAILED(mContent->SetProperty(((bool)(__builtin_expect(!!(NS_FAILED_impl(mContent->SetProperty
( nsGkAtoms::newline, cachedNewlineOffset, nsINode::DeleteProperty
<NewlineProperty>))), 0)))
11533 nsGkAtoms::newline, cachedNewlineOffset,((bool)(__builtin_expect(!!(NS_FAILED_impl(mContent->SetProperty
( nsGkAtoms::newline, cachedNewlineOffset, nsINode::DeleteProperty
<NewlineProperty>))), 0)))
11534 nsINode::DeleteProperty<NewlineProperty>))((bool)(__builtin_expect(!!(NS_FAILED_impl(mContent->SetProperty
( nsGkAtoms::newline, cachedNewlineOffset, nsINode::DeleteProperty
<NewlineProperty>))), 0)))
) {
11535 delete cachedNewlineOffset;
11536 cachedNewlineOffset = nullptr;
11537 }
11538 mContent->SetFlags(NS_HAS_NEWLINE_PROPERTY);
11539 }
11540 if (cachedNewlineOffset) {
11541 cachedNewlineOffset->mStartOffset = offset;
11542 cachedNewlineOffset->mNewlineOffset = contentNewLineOffset;
11543 }
11544 } else if (cachedNewlineOffset) {
11545 mContent->RemoveProperty(nsGkAtoms::newline);
11546 mContent->UnsetFlags(NS_HAS_NEWLINE_PROPERTY);
11547 }
11548
11549 // Compute space and letter counts for justification, if required
11550 if ((lineContainer->StyleText()->mTextAlign == StyleTextAlign::Justify ||
11551 lineContainer->StyleText()->mTextAlignLast ==
11552 StyleTextAlignLast::Justify ||
11553 shouldSuppressLineBreak) &&
11554 !lineContainer->IsInSVGTextSubtree()) {
11555 AddStateBits(TEXT_JUSTIFICATION_ENABLED);
11556 Range range(uint32_t(offset), uint32_t(offset + charsFit));
11557 aLineLayout.SetJustificationInfo(provider.ComputeJustification(range));
11558 }
11559
11560 SetLength(contentLength, &aLineLayout, ALLOW_FRAME_CREATION_AND_DESTRUCTION);
11561
11562 InvalidateFrame();
11563
11564#ifdef NOISY_REFLOW
11565 ListTag(stdoutstdout);
11566 printf(": desiredSize=%d,%d(b=%d) status=%x\n", aMetrics.Width(),
11567 aMetrics.Height(), aMetrics.BlockStartAscent(), aStatus);
11568#endif
11569}
11570
11571/* virtual */
11572bool nsTextFrame::CanContinueTextRun() const {
11573 // We can continue a text run through a text frame
11574 return true;
11575}
11576
11577nsTextFrame::TrimOutput nsTextFrame::TrimTrailingWhiteSpace(
11578 DrawTarget* aDrawTarget) {
11579 MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_IS_DIRTY | NS_FRAME_FIRST_REFLOW),do { static_assert( mozilla::detail::AssertionConditionType<
decltype(!HasAnyStateBits(NS_FRAME_IS_DIRTY | NS_FRAME_FIRST_REFLOW
))>::isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(!HasAnyStateBits(NS_FRAME_IS_DIRTY | NS_FRAME_FIRST_REFLOW
)))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("!HasAnyStateBits(NS_FRAME_IS_DIRTY | NS_FRAME_FIRST_REFLOW)"
" (" "frame should have been reflowed" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 11580); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!HasAnyStateBits(NS_FRAME_IS_DIRTY | NS_FRAME_FIRST_REFLOW)"
") (" "frame should have been reflowed" ")"); do { MOZ_CrashSequence
(__null, 11580); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
11580 "frame should have been reflowed")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(!HasAnyStateBits(NS_FRAME_IS_DIRTY | NS_FRAME_FIRST_REFLOW
))>::isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(!HasAnyStateBits(NS_FRAME_IS_DIRTY | NS_FRAME_FIRST_REFLOW
)))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("!HasAnyStateBits(NS_FRAME_IS_DIRTY | NS_FRAME_FIRST_REFLOW)"
" (" "frame should have been reflowed" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 11580); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!HasAnyStateBits(NS_FRAME_IS_DIRTY | NS_FRAME_FIRST_REFLOW)"
") (" "frame should have been reflowed" ")"); do { MOZ_CrashSequence
(__null, 11580); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
11581
11582 TrimOutput result;
11583 result.mChanged = false;
11584 result.mDeltaWidth = 0;
11585
11586 AddStateBits(TEXT_END_OF_LINE);
11587
11588 if (!GetTextRun(nsTextFrame::eInflated)) {
11589 // If reflow didn't create a textrun, there must have been no content once
11590 // leading whitespace was trimmed, so nothing more to do here.
11591 return result;
11592 }
11593
11594 int32_t contentLength = GetContentLength();
11595 if (!contentLength) {
11596 return result;
11597 }
11598
11599 gfxSkipCharsIterator start =
11600 EnsureTextRun(nsTextFrame::eInflated, aDrawTarget);
11601 NS_ENSURE_TRUE(mTextRun, result)do { if ((__builtin_expect(!!(!(mTextRun)), 0))) { NS_DebugBreak
(NS_DEBUG_WARNING, "NS_ENSURE_TRUE(" "mTextRun" ") failed", nullptr
, "./../../../layout/generic/nsTextFrame.cpp", 11601); return
result; } } while (false)
;
11602
11603 uint32_t trimmedStart = start.GetSkippedOffset();
11604
11605 const auto& characterDataBuffer = CharacterDataBuffer();
11606 TrimmedOffsets trimmed = GetTrimmedOffsets(characterDataBuffer);
11607 gfxSkipCharsIterator trimmedEndIter = start;
11608 const nsStyleText* textStyle = StyleText();
11609 gfxFloat delta = 0;
11610 uint32_t trimmedEnd =
11611 trimmedEndIter.ConvertOriginalToSkipped(trimmed.GetEnd());
11612
11613 if (!HasAnyStateBits(TEXT_TRIMMED_TRAILING_WHITESPACE) &&
11614 trimmed.GetEnd() < GetContentEnd()) {
11615 gfxSkipCharsIterator end = trimmedEndIter;
11616 uint32_t endOffset =
11617 end.ConvertOriginalToSkipped(GetContentOffset() + contentLength);
11618 if (trimmedEnd < endOffset) {
11619 // We can't be dealing with tabs here ... they wouldn't be trimmed. So
11620 // it's OK to pass null for the line container.
11621 PropertyProvider provider(mTextRun, textStyle, characterDataBuffer, this,
11622 start, contentLength, nullptr, 0,
11623 nsTextFrame::eInflated,
11624 HasAnyStateBits(TEXT_START_OF_LINE));
11625 delta =
11626 mTextRun->GetAdvanceWidth(Range(trimmedEnd, endOffset), &provider);
11627 result.mChanged = true;
11628 }
11629 }
11630
11631 gfxFloat advanceDelta;
11632 mTextRun->SetLineBreaks(Range(trimmedStart, trimmedEnd),
11633 HasAnyStateBits(TEXT_START_OF_LINE), true,
11634 &advanceDelta);
11635 if (advanceDelta != 0) {
11636 result.mChanged = true;
11637 }
11638
11639 // aDeltaWidth is *subtracted* from our width.
11640 // If advanceDelta is positive then setting the line break made us longer,
11641 // so aDeltaWidth could go negative.
11642 result.mDeltaWidth = NSToCoordFloor(delta - advanceDelta);
11643 // If aDeltaWidth goes negative, that means this frame might not actually fit
11644 // anymore!!! We need higher level line layout to recover somehow.
11645 // If it's because the frame has a soft hyphen that is now being displayed,
11646 // this should actually be OK, because our reflow recorded the break
11647 // opportunity that allowed the soft hyphen to be used, and we wouldn't
11648 // have recorded the opportunity unless the hyphen fit (or was the first
11649 // opportunity on the line).
11650 // Otherwise this can/ really only happen when we have glyphs with special
11651 // shapes at the end of lines, I think. Breaking inside a kerning pair won't
11652 // do it because that would mean we broke inside this textrun, and
11653 // BreakAndMeasureText should make sure the resulting shaped substring fits.
11654 // Maybe if we passed a maxTextLength? But that only happens at direction
11655 // changes (so we wouldn't kern across the boundary) or for first-letter
11656 // (which always fits because it starts the line!).
11657 NS_WARNING_ASSERTION(result.mDeltaWidth >= 0,do { if (!(result.mDeltaWidth >= 0)) { NS_DebugBreak(NS_DEBUG_WARNING
, "Negative deltawidth, something odd is happening", "result.mDeltaWidth >= 0"
, "./../../../layout/generic/nsTextFrame.cpp", 11658); } } while
(false)
11658 "Negative deltawidth, something odd is happening")do { if (!(result.mDeltaWidth >= 0)) { NS_DebugBreak(NS_DEBUG_WARNING
, "Negative deltawidth, something odd is happening", "result.mDeltaWidth >= 0"
, "./../../../layout/generic/nsTextFrame.cpp", 11658); } } while
(false)
;
11659
11660#ifdef NOISY_TRIM
11661 ListTag(stdoutstdout);
11662 printf(": trim => %d\n", result.mDeltaWidth);
11663#endif
11664 return result;
11665}
11666
11667OverflowAreas nsTextFrame::RecomputeOverflow(nsIFrame* aBlockFrame,
11668 bool aIncludeShadows) {
11669 RemoveProperty(WebRenderTextBounds());
11670
11671 nsRect bounds(nsPoint(0, 0), GetSize());
11672 OverflowAreas result(bounds, bounds);
11673
11674 gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
11675 if (!mTextRun) {
11676 return result;
11677 }
11678
11679 PropertyProvider provider(this, iter, nsTextFrame::eInflated, mFontMetrics);
11680 // Don't trim trailing space, in case we need to paint it as selected.
11681 provider.InitializeForDisplay(false);
11682
11683 gfxTextRun::Metrics textMetrics =
11684 mTextRun->MeasureText(ComputeTransformedRange(provider),
11685 gfxFont::LOOSE_INK_EXTENTS, nullptr, &provider);
11686 if (GetWritingMode().IsLineInverted()) {
11687 textMetrics.mBoundingBox.y = -textMetrics.mBoundingBox.YMost();
11688 }
11689 nsRect boundingBox = RoundOut(textMetrics.mBoundingBox);
11690 boundingBox += nsPoint(0, mAscent);
11691 if (mTextRun->IsVertical()) {
11692 // Swap line-relative textMetrics dimensions to physical coordinates.
11693 std::swap(boundingBox.x, boundingBox.y);
11694 std::swap(boundingBox.width, boundingBox.height);
11695 }
11696 nsRect& vis = result.InkOverflow();
11697 vis.UnionRect(vis, boundingBox);
11698 UnionAdditionalOverflow(PresContext(), aBlockFrame, provider, &vis, true,
11699 aIncludeShadows);
11700 return result;
11701}
11702
11703static void TransformChars(nsTextFrame* aFrame, const nsStyleText* aStyle,
11704 const gfxTextRun* aTextRun, uint32_t aSkippedOffset,
11705 const CharacterDataBuffer& aBuffer,
11706 int32_t aBufferOffset, int32_t aBufferLen,
11707 nsAString& aOut) {
11708 nsAutoString fragString;
11709 char16_t* out;
11710 bool needsToMaskPassword = NeedsToMaskPassword(aFrame);
11711 if (aStyle->mTextTransform.IsNone() && !needsToMaskPassword &&
11712 aStyle->mWebkitTextSecurity == StyleTextSecurity::None) {
11713 // No text-transform, so we can copy directly to the output string.
11714 aOut.SetLength(aOut.Length() + aBufferLen);
11715 out = aOut.EndWriting() - aBufferLen;
11716 } else {
11717 // Use a temporary string as source for the transform.
11718 fragString.SetLength(aBufferLen);
11719 out = fragString.BeginWriting();
11720 }
11721
11722 // Copy the text, with \n and \t replaced by <space> if appropriate.
11723 MOZ_ASSERT(aBufferOffset >= 0)do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aBufferOffset >= 0)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(aBufferOffset >= 0))), 0)
)) { do { } while (false); MOZ_ReportAssertionFailure("aBufferOffset >= 0"
, "./../../../layout/generic/nsTextFrame.cpp", 11723); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "aBufferOffset >= 0" ")"); do { MOZ_CrashSequence
(__null, 11723); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
11724 for (uint32_t i = 0; i < static_cast<uint32_t>(aBufferLen); ++i) {
11725 char16_t ch = aBuffer.CharAt(static_cast<uint32_t>(aBufferOffset) + i);
11726 if ((ch == '\n' && !aStyle->NewlineIsSignificant(aFrame)) ||
11727 (ch == '\t' && !aStyle->TabIsSignificant())) {
11728 ch = ' ';
11729 }
11730 out[i] = ch;
11731 }
11732
11733 if (!aStyle->mTextTransform.IsNone() || needsToMaskPassword ||
11734 aStyle->mWebkitTextSecurity != StyleTextSecurity::None) {
11735 MOZ_ASSERT(aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsTransformed)do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aTextRun->GetFlags2() & nsTextFrameUtils::Flags
::IsTransformed)>::isValid, "invalid assertion condition")
; if ((__builtin_expect(!!(!(!!(aTextRun->GetFlags2() &
nsTextFrameUtils::Flags::IsTransformed))), 0))) { do { } while
(false); MOZ_ReportAssertionFailure("aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsTransformed"
, "./../../../layout/generic/nsTextFrame.cpp", 11735); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsTransformed"
")"); do { MOZ_CrashSequence(__null, 11735); __attribute__((
nomerge)) ::abort(); } while (false); } } while (false)
;
11736 if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsTransformed) {
11737 // Apply text-transform according to style in the transformed run.
11738 char16_t maskChar =
11739 needsToMaskPassword ? 0 : aStyle->TextSecurityMaskChar();
11740 auto transformedTextRun =
11741 static_cast<const nsTransformedTextRun*>(aTextRun);
11742 nsAutoString convertedString;
11743 AutoTArray<bool, 50> charsToMergeArray;
11744 AutoTArray<bool, 50> deletedCharsArray;
11745 nsCaseTransformTextRunFactory::TransformString(
11746 fragString, convertedString, /* aGlobalTransform = */ Nothing(),
11747 maskChar, /* aCaseTransformsOnly = */ true,
11748 StaticPrefs::layout_css_text_transform_uppercase_eszett_enabled(),
11749 nullptr, charsToMergeArray, deletedCharsArray, transformedTextRun,
11750 aSkippedOffset);
11751 aOut.Append(convertedString);
11752 } else {
11753 // Should not happen (see assertion above), but as a fallback...
11754 aOut.Append(fragString);
11755 }
11756 }
11757}
11758
11759static void LineStartsOrEndsAtHardLineBreak(nsTextFrame* aFrame,
11760 nsBlockFrame* aLineContainer,
11761 bool* aStartsAtHardBreak,
11762 bool* aEndsAtHardBreak) {
11763 bool foundValidLine;
11764 nsBlockInFlowLineIterator iter(aLineContainer, aFrame, &foundValidLine);
11765 if (!foundValidLine) {
11766 NS_ERROR("Invalid line!")do { NS_DebugBreak(NS_DEBUG_ASSERTION, "Invalid line!", "Error"
, "./../../../layout/generic/nsTextFrame.cpp", 11766); MOZ_PretendNoReturn
(); } while (0)
;
11767 *aStartsAtHardBreak = *aEndsAtHardBreak = true;
11768 return;
11769 }
11770
11771 *aEndsAtHardBreak = !iter.GetLine()->IsLineWrapped();
11772 if (iter.Prev()) {
11773 *aStartsAtHardBreak = !iter.GetLine()->IsLineWrapped();
11774 } else {
11775 // Hit block boundary
11776 *aStartsAtHardBreak = true;
11777 }
11778}
11779
11780bool nsTextFrame::AppendRenderedText(AppendRenderedTextState& aState,
11781 RenderedText& aResult) {
11782 if (HasAnyStateBits(NS_FRAME_IS_DIRTY)) {
11783 // We don't trust dirty frames, especially when computing rendered text.
11784 return false;
11785 }
11786
11787 // Ensure the text run and grab the gfxSkipCharsIterator for it
11788 gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
11789 if (!mTextRun) {
11790 return false;
11791 }
11792 gfxSkipCharsIterator tmpIter = iter;
11793
11794 // Check if the frame starts/ends at a hard line break, to determine
11795 // whether whitespace should be trimmed.
11796 bool startsAtHardBreak, endsAtHardBreak;
11797 if (!HasAnyStateBits(TEXT_START_OF_LINE | TEXT_END_OF_LINE)) {
11798 startsAtHardBreak = endsAtHardBreak = false;
11799 } else if (nsBlockFrame* thisLc = do_QueryFrame(FindLineContainer())) {
11800 if (thisLc != aState.mLineContainer) {
11801 // Setup line cursor when needed.
11802 aState.mLineContainer = thisLc;
11803 aState.mLineContainer->SetupLineCursorForQuery();
11804 }
11805 LineStartsOrEndsAtHardLineBreak(this, aState.mLineContainer,
11806 &startsAtHardBreak, &endsAtHardBreak);
11807 } else {
11808 // Weird situation where we have a line layout without a block.
11809 // No soft breaks occur in this situation.
11810 startsAtHardBreak = endsAtHardBreak = true;
11811 }
11812
11813 // Whether we need to trim whitespaces after the text frame.
11814 // TrimmedOffsetFlags::Default will allow trimming; we set NoTrim* flags
11815 // in the cases where this should not occur.
11816 TrimmedOffsetFlags trimFlags = TrimmedOffsetFlags::Default;
11817 if (!IsAtEndOfLine() ||
11818 aState.mTrimTrailingWhitespace != TrailingWhitespace::Trim ||
11819 !endsAtHardBreak) {
11820 trimFlags |= TrimmedOffsetFlags::NoTrimAfter;
11821 }
11822
11823 // Whether to trim whitespaces before the text frame.
11824 if (!startsAtHardBreak) {
11825 trimFlags |= TrimmedOffsetFlags::NoTrimBefore;
11826 }
11827
11828 TrimmedOffsets trimmedOffsets =
11829 GetTrimmedOffsets(aState.mCharacterDataBuffer, trimFlags);
11830 bool trimmedSignificantNewline =
11831 (trimmedOffsets.GetEnd() < GetContentEnd() ||
11832 (aState.mTrimTrailingWhitespace == TrailingWhitespace::Trim &&
11833 StyleText()->mWhiteSpaceCollapse ==
11834 StyleWhiteSpaceCollapse::PreserveBreaks)) &&
11835 HasSignificantTerminalNewline();
11836 uint32_t skippedToRenderedStringOffset =
11837 aState.mOffsetInRenderedString -
11838 tmpIter.ConvertOriginalToSkipped(trimmedOffsets.mStart);
11839 uint32_t nextOffsetInRenderedString =
11840 tmpIter.ConvertOriginalToSkipped(trimmedOffsets.GetEnd()) +
11841 (trimmedSignificantNewline ? 1 : 0) + skippedToRenderedStringOffset;
11842
11843 if (aState.mOffsetType == TextOffsetType::OffsetsInRenderedText) {
11844 if (nextOffsetInRenderedString <= aState.mStartOffset) {
11845 aState.mOffsetInRenderedString = nextOffsetInRenderedString;
11846 return true;
11847 }
11848 if (!aState.mHaveOffsets) {
11849 aResult.mOffsetWithinNodeText = tmpIter.ConvertSkippedToOriginal(
11850 aState.mStartOffset - skippedToRenderedStringOffset);
11851 aResult.mOffsetWithinNodeRenderedText = aState.mStartOffset;
11852 aState.mHaveOffsets = true;
11853 }
11854 if (aState.mOffsetInRenderedString >= aState.mEndOffset) {
11855 return false;
11856 }
11857 } else {
11858 if (uint32_t(GetContentEnd()) <= aState.mStartOffset) {
11859 aState.mOffsetInRenderedString = nextOffsetInRenderedString;
11860 return true;
11861 }
11862 if (!aState.mHaveOffsets) {
11863 aResult.mOffsetWithinNodeText = aState.mStartOffset;
11864 // Skip trimmed space when computed the rendered text offset.
11865 int32_t clamped =
11866 std::max<int32_t>(aState.mStartOffset, trimmedOffsets.mStart);
11867 aResult.mOffsetWithinNodeRenderedText =
11868 tmpIter.ConvertOriginalToSkipped(clamped) +
11869 skippedToRenderedStringOffset;
11870 MOZ_ASSERT(aResult.mOffsetWithinNodeRenderedText >=do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aResult.mOffsetWithinNodeRenderedText >= aState.mOffsetInRenderedString
&& aResult.mOffsetWithinNodeRenderedText <= (2147483647
))>::isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(aResult.mOffsetWithinNodeRenderedText >= aState.mOffsetInRenderedString
&& aResult.mOffsetWithinNodeRenderedText <= (2147483647
)))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("aResult.mOffsetWithinNodeRenderedText >= aState.mOffsetInRenderedString && aResult.mOffsetWithinNodeRenderedText <= (2147483647)"
" (" "Bad offset within rendered text" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 11873); AnnotateMozCrashReason("MOZ_ASSERT" "(" "aResult.mOffsetWithinNodeRenderedText >= aState.mOffsetInRenderedString && aResult.mOffsetWithinNodeRenderedText <= (2147483647)"
") (" "Bad offset within rendered text" ")"); do { MOZ_CrashSequence
(__null, 11873); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
11871 aState.mOffsetInRenderedString &&do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aResult.mOffsetWithinNodeRenderedText >= aState.mOffsetInRenderedString
&& aResult.mOffsetWithinNodeRenderedText <= (2147483647
))>::isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(aResult.mOffsetWithinNodeRenderedText >= aState.mOffsetInRenderedString
&& aResult.mOffsetWithinNodeRenderedText <= (2147483647
)))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("aResult.mOffsetWithinNodeRenderedText >= aState.mOffsetInRenderedString && aResult.mOffsetWithinNodeRenderedText <= (2147483647)"
" (" "Bad offset within rendered text" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 11873); AnnotateMozCrashReason("MOZ_ASSERT" "(" "aResult.mOffsetWithinNodeRenderedText >= aState.mOffsetInRenderedString && aResult.mOffsetWithinNodeRenderedText <= (2147483647)"
") (" "Bad offset within rendered text" ")"); do { MOZ_CrashSequence
(__null, 11873); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
11872 aResult.mOffsetWithinNodeRenderedText <= INT32_MAX,do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aResult.mOffsetWithinNodeRenderedText >= aState.mOffsetInRenderedString
&& aResult.mOffsetWithinNodeRenderedText <= (2147483647
))>::isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(aResult.mOffsetWithinNodeRenderedText >= aState.mOffsetInRenderedString
&& aResult.mOffsetWithinNodeRenderedText <= (2147483647
)))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("aResult.mOffsetWithinNodeRenderedText >= aState.mOffsetInRenderedString && aResult.mOffsetWithinNodeRenderedText <= (2147483647)"
" (" "Bad offset within rendered text" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 11873); AnnotateMozCrashReason("MOZ_ASSERT" "(" "aResult.mOffsetWithinNodeRenderedText >= aState.mOffsetInRenderedString && aResult.mOffsetWithinNodeRenderedText <= (2147483647)"
") (" "Bad offset within rendered text" ")"); do { MOZ_CrashSequence
(__null, 11873); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
11873 "Bad offset within rendered text")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aResult.mOffsetWithinNodeRenderedText >= aState.mOffsetInRenderedString
&& aResult.mOffsetWithinNodeRenderedText <= (2147483647
))>::isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(aResult.mOffsetWithinNodeRenderedText >= aState.mOffsetInRenderedString
&& aResult.mOffsetWithinNodeRenderedText <= (2147483647
)))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("aResult.mOffsetWithinNodeRenderedText >= aState.mOffsetInRenderedString && aResult.mOffsetWithinNodeRenderedText <= (2147483647)"
" (" "Bad offset within rendered text" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 11873); AnnotateMozCrashReason("MOZ_ASSERT" "(" "aResult.mOffsetWithinNodeRenderedText >= aState.mOffsetInRenderedString && aResult.mOffsetWithinNodeRenderedText <= (2147483647)"
") (" "Bad offset within rendered text" ")"); do { MOZ_CrashSequence
(__null, 11873); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
11874 aState.mHaveOffsets = true;
11875 }
11876 if (uint32_t(mContentOffset) >= aState.mEndOffset) {
11877 return false;
11878 }
11879 }
11880
11881 int32_t startOffset;
11882 int32_t endOffset;
11883 if (aState.mOffsetType == TextOffsetType::OffsetsInRenderedText) {
11884 startOffset = tmpIter.ConvertSkippedToOriginal(
11885 aState.mStartOffset - skippedToRenderedStringOffset);
11886 endOffset = tmpIter.ConvertSkippedToOriginal(aState.mEndOffset -
11887 skippedToRenderedStringOffset);
11888 } else {
11889 startOffset = aState.mStartOffset;
11890 endOffset = std::min<uint32_t>(INT32_MAX(2147483647), aState.mEndOffset);
11891 }
11892
11893 // If startOffset and/or endOffset are inside of trimmedOffsets' range,
11894 // then clamp the edges of trimmedOffsets accordingly.
11895 int32_t origTrimmedOffsetsEnd = trimmedOffsets.GetEnd();
11896 trimmedOffsets.mStart =
11897 std::max<uint32_t>(trimmedOffsets.mStart, startOffset);
11898 trimmedOffsets.mLength =
11899 std::min<uint32_t>(origTrimmedOffsetsEnd, endOffset) -
11900 trimmedOffsets.mStart;
11901
11902 if (trimmedOffsets.mLength > 0) {
11903 const nsStyleText* textStyle = StyleText();
11904 iter.SetOriginalOffset(trimmedOffsets.mStart);
11905 while (iter.GetOriginalOffset() < trimmedOffsets.GetEnd()) {
11906 int32_t runLength;
11907 bool isSkipped = iter.IsOriginalCharSkipped(&runLength);
11908 runLength = std::min(runLength,
11909 trimmedOffsets.GetEnd() - iter.GetOriginalOffset());
11910 if (isSkipped) {
11911 MOZ_ASSERT(runLength >= 0)do { static_assert( mozilla::detail::AssertionConditionType<
decltype(runLength >= 0)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(runLength >= 0))), 0))) {
do { } while (false); MOZ_ReportAssertionFailure("runLength >= 0"
, "./../../../layout/generic/nsTextFrame.cpp", 11911); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "runLength >= 0" ")"); do { MOZ_CrashSequence
(__null, 11911); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
11912 for (uint32_t i = 0; i < static_cast<uint32_t>(runLength); ++i) {
11913 const char16_t ch = aState.mCharacterDataBuffer.CharAt(
11914 AssertedCast<uint32_t>(iter.GetOriginalOffset() + i));
11915 if (ch == CH_SHY173) {
11916 // We should preserve soft hyphens. They can't be transformed.
11917 aResult.mString.Append(ch);
11918 }
11919 }
11920 } else {
11921 TransformChars(this, textStyle, mTextRun, iter.GetSkippedOffset(),
11922 aState.mCharacterDataBuffer, iter.GetOriginalOffset(),
11923 runLength, aResult.mString);
11924 }
11925 iter.AdvanceOriginal(runLength);
11926 }
11927 }
11928
11929 if (trimmedSignificantNewline && GetContentEnd() <= endOffset) {
11930 // A significant newline was trimmed off (we must be
11931 // white-space:pre-line). Put it back.
11932 aResult.mString.Append('\n');
11933 }
11934 aState.mOffsetInRenderedString = nextOffsetInRenderedString;
11935
11936 return true;
11937}
11938
11939nsIFrame::RenderedText nsTextFrame::GetRenderedText(
11940 uint32_t aStartOffset, uint32_t aEndOffset, TextOffsetType aOffsetType,
11941 TrailingWhitespace aTrimTrailingWhitespace) {
11942 MOZ_ASSERT(aStartOffset <= aEndOffset, "bogus offsets")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aStartOffset <= aEndOffset)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(aStartOffset <= aEndOffset
))), 0))) { do { } while (false); MOZ_ReportAssertionFailure(
"aStartOffset <= aEndOffset" " (" "bogus offsets" ")", "./../../../layout/generic/nsTextFrame.cpp"
, 11942); AnnotateMozCrashReason("MOZ_ASSERT" "(" "aStartOffset <= aEndOffset"
") (" "bogus offsets" ")"); do { MOZ_CrashSequence(__null, 11942
); __attribute__((nomerge)) ::abort(); } while (false); } } while
(false)
;
11943 MOZ_ASSERT(!GetPrevContinuation() ||do { static_assert( mozilla::detail::AssertionConditionType<
decltype(!GetPrevContinuation() || (aOffsetType == TextOffsetType
::OffsetsInContentText && aStartOffset >= (uint32_t
)GetContentOffset() && aEndOffset <= (uint32_t)GetContentEnd
()))>::isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(!GetPrevContinuation() || (aOffsetType == TextOffsetType
::OffsetsInContentText && aStartOffset >= (uint32_t
)GetContentOffset() && aEndOffset <= (uint32_t)GetContentEnd
())))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("!GetPrevContinuation() || (aOffsetType == TextOffsetType::OffsetsInContentText && aStartOffset >= (uint32_t)GetContentOffset() && aEndOffset <= (uint32_t)GetContentEnd())"
" (" "Must be called on first-in-flow, or content offsets must be "
"given and be within this frame." ")", "./../../../layout/generic/nsTextFrame.cpp"
, 11948); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!GetPrevContinuation() || (aOffsetType == TextOffsetType::OffsetsInContentText && aStartOffset >= (uint32_t)GetContentOffset() && aEndOffset <= (uint32_t)GetContentEnd())"
") (" "Must be called on first-in-flow, or content offsets must be "
"given and be within this frame." ")"); do { MOZ_CrashSequence
(__null, 11948); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
11944 (aOffsetType == TextOffsetType::OffsetsInContentText &&do { static_assert( mozilla::detail::AssertionConditionType<
decltype(!GetPrevContinuation() || (aOffsetType == TextOffsetType
::OffsetsInContentText && aStartOffset >= (uint32_t
)GetContentOffset() && aEndOffset <= (uint32_t)GetContentEnd
()))>::isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(!GetPrevContinuation() || (aOffsetType == TextOffsetType
::OffsetsInContentText && aStartOffset >= (uint32_t
)GetContentOffset() && aEndOffset <= (uint32_t)GetContentEnd
())))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("!GetPrevContinuation() || (aOffsetType == TextOffsetType::OffsetsInContentText && aStartOffset >= (uint32_t)GetContentOffset() && aEndOffset <= (uint32_t)GetContentEnd())"
" (" "Must be called on first-in-flow, or content offsets must be "
"given and be within this frame." ")", "./../../../layout/generic/nsTextFrame.cpp"
, 11948); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!GetPrevContinuation() || (aOffsetType == TextOffsetType::OffsetsInContentText && aStartOffset >= (uint32_t)GetContentOffset() && aEndOffset <= (uint32_t)GetContentEnd())"
") (" "Must be called on first-in-flow, or content offsets must be "
"given and be within this frame." ")"); do { MOZ_CrashSequence
(__null, 11948); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
11945 aStartOffset >= (uint32_t)GetContentOffset() &&do { static_assert( mozilla::detail::AssertionConditionType<
decltype(!GetPrevContinuation() || (aOffsetType == TextOffsetType
::OffsetsInContentText && aStartOffset >= (uint32_t
)GetContentOffset() && aEndOffset <= (uint32_t)GetContentEnd
()))>::isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(!GetPrevContinuation() || (aOffsetType == TextOffsetType
::OffsetsInContentText && aStartOffset >= (uint32_t
)GetContentOffset() && aEndOffset <= (uint32_t)GetContentEnd
())))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("!GetPrevContinuation() || (aOffsetType == TextOffsetType::OffsetsInContentText && aStartOffset >= (uint32_t)GetContentOffset() && aEndOffset <= (uint32_t)GetContentEnd())"
" (" "Must be called on first-in-flow, or content offsets must be "
"given and be within this frame." ")", "./../../../layout/generic/nsTextFrame.cpp"
, 11948); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!GetPrevContinuation() || (aOffsetType == TextOffsetType::OffsetsInContentText && aStartOffset >= (uint32_t)GetContentOffset() && aEndOffset <= (uint32_t)GetContentEnd())"
") (" "Must be called on first-in-flow, or content offsets must be "
"given and be within this frame." ")"); do { MOZ_CrashSequence
(__null, 11948); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
11946 aEndOffset <= (uint32_t)GetContentEnd()),do { static_assert( mozilla::detail::AssertionConditionType<
decltype(!GetPrevContinuation() || (aOffsetType == TextOffsetType
::OffsetsInContentText && aStartOffset >= (uint32_t
)GetContentOffset() && aEndOffset <= (uint32_t)GetContentEnd
()))>::isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(!GetPrevContinuation() || (aOffsetType == TextOffsetType
::OffsetsInContentText && aStartOffset >= (uint32_t
)GetContentOffset() && aEndOffset <= (uint32_t)GetContentEnd
())))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("!GetPrevContinuation() || (aOffsetType == TextOffsetType::OffsetsInContentText && aStartOffset >= (uint32_t)GetContentOffset() && aEndOffset <= (uint32_t)GetContentEnd())"
" (" "Must be called on first-in-flow, or content offsets must be "
"given and be within this frame." ")", "./../../../layout/generic/nsTextFrame.cpp"
, 11948); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!GetPrevContinuation() || (aOffsetType == TextOffsetType::OffsetsInContentText && aStartOffset >= (uint32_t)GetContentOffset() && aEndOffset <= (uint32_t)GetContentEnd())"
") (" "Must be called on first-in-flow, or content offsets must be "
"given and be within this frame." ")"); do { MOZ_CrashSequence
(__null, 11948); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
11947 "Must be called on first-in-flow, or content offsets must be "do { static_assert( mozilla::detail::AssertionConditionType<
decltype(!GetPrevContinuation() || (aOffsetType == TextOffsetType
::OffsetsInContentText && aStartOffset >= (uint32_t
)GetContentOffset() && aEndOffset <= (uint32_t)GetContentEnd
()))>::isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(!GetPrevContinuation() || (aOffsetType == TextOffsetType
::OffsetsInContentText && aStartOffset >= (uint32_t
)GetContentOffset() && aEndOffset <= (uint32_t)GetContentEnd
())))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("!GetPrevContinuation() || (aOffsetType == TextOffsetType::OffsetsInContentText && aStartOffset >= (uint32_t)GetContentOffset() && aEndOffset <= (uint32_t)GetContentEnd())"
" (" "Must be called on first-in-flow, or content offsets must be "
"given and be within this frame." ")", "./../../../layout/generic/nsTextFrame.cpp"
, 11948); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!GetPrevContinuation() || (aOffsetType == TextOffsetType::OffsetsInContentText && aStartOffset >= (uint32_t)GetContentOffset() && aEndOffset <= (uint32_t)GetContentEnd())"
") (" "Must be called on first-in-flow, or content offsets must be "
"given and be within this frame." ")"); do { MOZ_CrashSequence
(__null, 11948); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
11948 "given and be within this frame.")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(!GetPrevContinuation() || (aOffsetType == TextOffsetType
::OffsetsInContentText && aStartOffset >= (uint32_t
)GetContentOffset() && aEndOffset <= (uint32_t)GetContentEnd
()))>::isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(!GetPrevContinuation() || (aOffsetType == TextOffsetType
::OffsetsInContentText && aStartOffset >= (uint32_t
)GetContentOffset() && aEndOffset <= (uint32_t)GetContentEnd
())))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("!GetPrevContinuation() || (aOffsetType == TextOffsetType::OffsetsInContentText && aStartOffset >= (uint32_t)GetContentOffset() && aEndOffset <= (uint32_t)GetContentEnd())"
" (" "Must be called on first-in-flow, or content offsets must be "
"given and be within this frame." ")", "./../../../layout/generic/nsTextFrame.cpp"
, 11948); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!GetPrevContinuation() || (aOffsetType == TextOffsetType::OffsetsInContentText && aStartOffset >= (uint32_t)GetContentOffset() && aEndOffset <= (uint32_t)GetContentEnd())"
") (" "Must be called on first-in-flow, or content offsets must be "
"given and be within this frame." ")"); do { MOZ_CrashSequence
(__null, 11948); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
11949
11950 // The handling of offsets could be more efficient...
11951 RenderedText result;
11952 AppendRenderedTextState state{aStartOffset, aEndOffset, aOffsetType,
11953 aTrimTrailingWhitespace, CharacterDataBuffer()};
11954
11955 for (nsTextFrame* textFrame = this; textFrame;
11956 textFrame = textFrame->GetNextContinuation()) {
11957 if (!textFrame->AppendRenderedText(state, result)) {
11958 break;
11959 }
11960 }
11961
11962 if (!state.mHaveOffsets) {
11963 result.mOffsetWithinNodeText = state.mCharacterDataBuffer.GetLength();
11964 result.mOffsetWithinNodeRenderedText = state.mOffsetInRenderedString;
11965 }
11966
11967 return result;
11968}
11969
11970/* virtual */
11971bool nsTextFrame::IsEmpty() {
11972 NS_ASSERTION(do { if (!(!HasAllStateBits(TEXT_IS_ONLY_WHITESPACE | TEXT_ISNOT_ONLY_WHITESPACE
))) { NS_DebugBreak(NS_DEBUG_ASSERTION, "Invalid state", "!HasAllStateBits(TEXT_IS_ONLY_WHITESPACE | TEXT_ISNOT_ONLY_WHITESPACE)"
, "./../../../layout/generic/nsTextFrame.cpp", 11974); MOZ_PretendNoReturn
(); } } while (0)
11973 !HasAllStateBits(TEXT_IS_ONLY_WHITESPACE | TEXT_ISNOT_ONLY_WHITESPACE),do { if (!(!HasAllStateBits(TEXT_IS_ONLY_WHITESPACE | TEXT_ISNOT_ONLY_WHITESPACE
))) { NS_DebugBreak(NS_DEBUG_ASSERTION, "Invalid state", "!HasAllStateBits(TEXT_IS_ONLY_WHITESPACE | TEXT_ISNOT_ONLY_WHITESPACE)"
, "./../../../layout/generic/nsTextFrame.cpp", 11974); MOZ_PretendNoReturn
(); } } while (0)
11974 "Invalid state")do { if (!(!HasAllStateBits(TEXT_IS_ONLY_WHITESPACE | TEXT_ISNOT_ONLY_WHITESPACE
))) { NS_DebugBreak(NS_DEBUG_ASSERTION, "Invalid state", "!HasAllStateBits(TEXT_IS_ONLY_WHITESPACE | TEXT_ISNOT_ONLY_WHITESPACE)"
, "./../../../layout/generic/nsTextFrame.cpp", 11974); MOZ_PretendNoReturn
(); } } while (0)
;
11975
11976 // XXXldb Should this check compatibility mode as well???
11977 const nsStyleText* textStyle = StyleText();
11978 if (textStyle->WhiteSpaceIsSignificant()) {
11979 // When WhiteSpaceIsSignificant styles are in effect, we only treat the
11980 // frame as empty if its content really is entirely *empty* (not just
11981 // whitespace).
11982 return !GetContentLength();
11983 }
11984
11985 if (HasAnyStateBits(TEXT_ISNOT_ONLY_WHITESPACE)) {
11986 return false;
11987 }
11988
11989 if (HasAnyStateBits(TEXT_IS_ONLY_WHITESPACE)) {
11990 return true;
11991 }
11992
11993 bool isEmpty = IsAllWhitespace(CharacterDataBuffer(),
11994 textStyle->mWhiteSpaceCollapse !=
11995 StyleWhiteSpaceCollapse::PreserveBreaks);
11996 AddStateBits(isEmpty ? TEXT_IS_ONLY_WHITESPACE : TEXT_ISNOT_ONLY_WHITESPACE);
11997 return isEmpty;
11998}
11999
12000#ifdef DEBUG_FRAME_DUMP1
12001// Translate the mapped content into a string that's printable
12002void nsTextFrame::ToCString(nsCString& aBuf) const {
12003 // Get the frame's text content
12004 const auto& characterDataBuffer = CharacterDataBuffer();
12005
12006 const int32_t length = GetContentEnd() - mContentOffset;
12007 if (length <= 0) {
12008 // Negative lengths are possible during invalidation.
12009 return;
12010 }
12011
12012 const uint32_t bufferLength = AssertedCast<uint32_t>(GetContentEnd());
12013 uint32_t bufferOffset = AssertedCast<uint32_t>(GetContentOffset());
12014
12015 while (bufferOffset < bufferLength) {
12016 char16_t ch = characterDataBuffer.CharAt(bufferOffset++);
12017 if (ch == '\r') {
12018 aBuf.AppendLiteral("\\r");
12019 } else if (ch == '\n') {
12020 aBuf.AppendLiteral("\\n");
12021 } else if (ch == '\t') {
12022 aBuf.AppendLiteral("\\t");
12023 } else if ((ch < ' ') || (ch >= 127)) {
12024 aBuf.Append(nsPrintfCString("\\u%04x", ch));
12025 } else {
12026 aBuf.Append(ch);
12027 }
12028 }
12029}
12030
12031nsresult nsTextFrame::GetFrameName(nsAString& aResult) const {
12032 MakeFrameName(u"Text"_ns, aResult);
12033 nsAutoCString tmp;
12034 ToCString(tmp);
12035 tmp.SetLength(std::min<size_t>(tmp.Length(), 50u));
12036 aResult += u"\""_ns + NS_ConvertASCIItoUTF16(tmp) + u"\""_ns;
12037 return NS_OK;
12038}
12039
12040void nsTextFrame::List(FILE* out, const char* aPrefix, ListFlags aFlags) const {
12041 nsCString str;
12042 ListGeneric(str, aPrefix, aFlags);
12043
12044 if (!aFlags.contains(ListFlag::OnlyListDeterministicInfo)) {
12045 str += nsPrintfCString(" [run=%p]", static_cast<void*>(mTextRun));
12046 }
12047
12048 // Output the first/last content offset and prev/next in flow info
12049 bool isComplete = uint32_t(GetContentEnd()) == GetContent()->TextLength();
12050 str += nsPrintfCString("[%d,%d,%c] ", GetContentOffset(), GetContentLength(),
12051 isComplete ? 'T' : 'F');
12052
12053 if (IsSelected()) {
12054 str += " SELECTED";
12055 }
12056 fprintf_stderr(out, "%s\n", str.get());
12057}
12058
12059void nsTextFrame::ListTextRuns(FILE* out,
12060 nsTHashSet<const void*>& aSeen) const {
12061 if (!mTextRun || aSeen.Contains(mTextRun)) {
12062 return;
12063 }
12064 aSeen.Insert(mTextRun);
12065 mTextRun->Dump(out);
12066}
12067#endif
12068
12069void nsTextFrame::AdjustOffsetsForBidi(int32_t aStart, int32_t aEnd) {
12070 AddStateBits(NS_FRAME_IS_BIDI);
12071 if (mContent->HasFlag(NS_HAS_FLOWLENGTH_PROPERTY)) {
12072 mContent->RemoveProperty(nsGkAtoms::flowlength);
12073 mContent->UnsetFlags(NS_HAS_FLOWLENGTH_PROPERTY);
12074 }
12075
12076 /*
12077 * After Bidi resolution we may need to reassign text runs.
12078 * This is called during bidi resolution from the block container, so we
12079 * shouldn't be holding a local reference to a textrun anywhere.
12080 */
12081 ClearTextRuns();
12082
12083 nsTextFrame* prev = GetPrevContinuation();
12084 if (prev) {
12085 // the bidi resolver can be very evil when columns/pages are involved. Don't
12086 // let it violate our invariants.
12087 int32_t prevOffset = prev->GetContentOffset();
12088 aStart = std::max(aStart, prevOffset);
12089 aEnd = std::max(aEnd, prevOffset);
12090 prev->ClearTextRuns();
12091 }
12092
12093 mContentOffset = aStart;
12094 SetLength(aEnd - aStart, nullptr, 0);
12095}
12096
12097/**
12098 * @return true if this text frame ends with a newline character. It should
12099 * return false if it is not a text frame.
12100 */
12101bool nsTextFrame::HasSignificantTerminalNewline() const {
12102 return ::HasTerminalNewline(this) && StyleText()->NewlineIsSignificant(this);
12103}
12104
12105bool nsTextFrame::IsAtEndOfLine() const {
12106 return HasAnyStateBits(TEXT_END_OF_LINE);
12107}
12108
12109Maybe<nscoord> nsTextFrame::GetNaturalBaselineBOffset(
12110 WritingMode aWM, BaselineSharingGroup aBaselineGroup,
12111 BaselineExportContext) const {
12112 if (aBaselineGroup == BaselineSharingGroup::Last) {
12113 return Nothing{};
12114 }
12115
12116 if (!aWM.IsOrthogonalTo(GetWritingMode())) {
12117 if (aWM.IsCentralBaseline()) {
12118 return Some(GetLogicalUsedBorderAndPadding(aWM).BStart(aWM) +
12119 ContentBSize(aWM) / 2);
12120 }
12121 return Some(mAscent);
12122 }
12123
12124 // When the text frame has a writing mode orthogonal to the desired
12125 // writing mode, return a baseline coincides its parent frame.
12126 nsIFrame* parent = GetParent();
12127 nsPoint position = GetNormalPosition();
12128 nscoord parentAscent = parent->GetLogicalBaseline(aWM);
12129 if (aWM.IsVerticalRL()) {
12130 nscoord parentDescent = parent->GetSize().width - parentAscent;
12131 nscoord descent = parentDescent - position.x;
12132 return Some(GetSize().width - descent);
12133 }
12134 return Some(parentAscent - (aWM.IsVertical() ? position.x : position.y));
12135}
12136
12137nscoord nsTextFrame::GetCaretBaseline() const {
12138 if (mAscent == 0 && HasAnyStateBits(TEXT_NO_RENDERED_GLYPHS)) {
12139 nsBlockFrame* container = do_QueryFrame(FindLineContainer());
12140 // TODO(emilio): Ideally we'd want to find out if only our line is empty,
12141 // but that's non-trivial to do, and realistically empty inlines and text
12142 // will get placed into a non-empty line unless all lines are empty, I
12143 // believe...
12144 if (container && container->LinesAreEmpty()) {
12145 return GetFontMetricsDerivedCaretBaseline();
12146 }
12147 }
12148 return nsIFrame::GetCaretBaseline();
12149}
12150
12151bool nsTextFrame::HasAnyNoncollapsedCharacters() {
12152 gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
12153 int32_t offset = GetContentOffset(), offsetEnd = GetContentEnd();
12154 int32_t skippedOffset = iter.ConvertOriginalToSkipped(offset);
12155 int32_t skippedOffsetEnd = iter.ConvertOriginalToSkipped(offsetEnd);
12156 return skippedOffset != skippedOffsetEnd;
12157}
12158
12159bool nsTextFrame::ComputeCustomOverflow(OverflowAreas& aOverflowAreas) {
12160 return ComputeCustomOverflowInternal(aOverflowAreas, true);
12161}
12162
12163bool nsTextFrame::ComputeCustomOverflowInternal(OverflowAreas& aOverflowAreas,
12164 bool aIncludeShadows) {
12165 if (HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
12166 return true;
12167 }
12168
12169 nsIFrame* decorationsBlock;
12170 if (IsFloatingFirstLetterChild()) {
12171 decorationsBlock = GetParent();
12172 } else {
12173 nsIFrame* f = this;
12174 for (;;) {
12175 nsBlockFrame* fBlock = do_QueryFrame(f);
12176 if (fBlock) {
12177 decorationsBlock = fBlock;
12178 break;
12179 }
12180
12181 f = f->GetParent();
12182 if (!f) {
12183 NS_ERROR("Couldn't find any block ancestor (for text decorations)")do { NS_DebugBreak(NS_DEBUG_ASSERTION, "Couldn't find any block ancestor (for text decorations)"
, "Error", "./../../../layout/generic/nsTextFrame.cpp", 12183
); MOZ_PretendNoReturn(); } while (0)
;
12184 return nsIFrame::ComputeCustomOverflow(aOverflowAreas);
12185 }
12186 }
12187 }
12188
12189 aOverflowAreas = RecomputeOverflow(decorationsBlock, aIncludeShadows);
12190 return nsIFrame::ComputeCustomOverflow(aOverflowAreas);
12191}
12192
12193NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(JustificationAssignmentProperty, int32_t)static const mozilla::FramePropertyDescriptor<mozilla::SmallValueHolder
<int32_t> >* JustificationAssignmentProperty() { static
const auto descriptor = mozilla::FramePropertyDescriptor<
mozilla::SmallValueHolder<int32_t> >::NewWithoutDestructor
(); return &descriptor; }
12194
12195void nsTextFrame::AssignJustificationGaps(
12196 const mozilla::JustificationAssignment& aAssign) {
12197 int32_t encoded = (aAssign.mGapsAtStart << 8) | aAssign.mGapsAtEnd;
12198 static_assert(sizeof(aAssign) == 1,
12199 "The encoding might be broken if JustificationAssignment "
12200 "is larger than 1 byte");
12201 SetProperty(JustificationAssignmentProperty(), encoded);
12202}
12203
12204mozilla::JustificationAssignment nsTextFrame::GetJustificationAssignment()
12205 const {
12206 int32_t encoded = GetProperty(JustificationAssignmentProperty());
12207 mozilla::JustificationAssignment result;
12208 result.mGapsAtStart = encoded >> 8;
12209 result.mGapsAtEnd = encoded & 0xFF;
12210 return result;
12211}
12212
12213uint32_t nsTextFrame::CountGraphemeClusters() const {
12214 const auto& characterDataBuffer = CharacterDataBuffer();
12215 nsAutoString content;
12216 characterDataBuffer.AppendTo(content,
12217 AssertedCast<uint32_t>(GetContentOffset()),
12218 AssertedCast<uint32_t>(GetContentLength()));
12219 return unicode::CountGraphemeClusters(content);
12220}
12221
12222bool nsTextFrame::HasNonSuppressedText() const {
12223 if (HasAnyStateBits(TEXT_ISNOT_ONLY_WHITESPACE |
12224 // If we haven't reflowed yet, or are currently doing so,
12225 // just return true because we can't be sure.
12226 NS_FRAME_FIRST_REFLOW | NS_FRAME_IN_REFLOW)) {
12227 return true;
12228 }
12229
12230 if (!GetTextRun(nsTextFrame::eInflated)) {
12231 return false;
12232 }
12233
12234 TrimmedOffsets offsets =
12235 GetTrimmedOffsets(CharacterDataBuffer(), TrimmedOffsetFlags::NoTrimAfter);
12236 return offsets.mLength != 0;
12237}