Bug Summary

File:root/firefox-clang/obj-x86_64-pc-linux-gnu/layout/svg/./../../../layout/svg/SVGTextFrame.cpp
Warning:line 4877, column 14
Value stored to 'end' during its initialization 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_svg1.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/svg -fcoverage-compilation-dir=/root/firefox-clang/obj-x86_64-pc-linux-gnu/layout/svg -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/svg -I /root/firefox-clang/obj-x86_64-pc-linux-gnu/layout/svg -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/toolkit/components/telemetry -I /root/firefox-clang/xpcom/base -I /root/firefox-clang/layout/base -I /root/firefox-clang/layout/generic -I /root/firefox-clang/layout/painting -I /root/firefox-clang/layout/style -I /root/firefox-clang/dom/base -I /root/firefox-clang/dom/svg -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 -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-09-01-224014-2642839-1 -x c++ Unified_cpp_layout_svg1.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// Main header first:
6#include "SVGTextFrame.h"
7
8// Keep others in (case-insensitive) order:
9#include <algorithm>
10#include <cmath>
11#include <limits>
12#include <numbers>
13#include <numeric>
14
15#include "DOMSVGPoint.h"
16#include "SVGAnimatedNumberList.h"
17#include "SVGContentUtils.h"
18#include "SVGContextPaint.h"
19#include "SVGLengthList.h"
20#include "SVGNumberList.h"
21#include "SVGPaintServerFrame.h"
22#include "gfx2DGlue.h"
23#include "gfxContext.h"
24#include "gfxFont.h"
25#include "gfxSkipChars.h"
26#include "gfxTypes.h"
27#include "gfxUtils.h"
28#include "mozilla/CaretAssociationHint.h"
29#include "mozilla/DisplaySVGItem.h"
30#include "mozilla/Likely.h"
31#include "mozilla/PresShell.h"
32#include "mozilla/ReflowInput.h"
33#include "mozilla/SVGObserverUtils.h"
34#include "mozilla/SVGOuterSVGFrame.h"
35#include "mozilla/SVGUtils.h"
36#include "mozilla/dom/SVGGeometryElement.h"
37#include "mozilla/dom/SVGRect.h"
38#include "mozilla/dom/SVGTextContentElementBinding.h"
39#include "mozilla/dom/SVGTextPathElement.h"
40#include "mozilla/dom/SVGTextPathElementBinding.h"
41#include "mozilla/dom/Selection.h"
42#include "mozilla/dom/Text.h"
43#include "mozilla/gfx/2D.h"
44#include "mozilla/gfx/PatternHelpers.h"
45#include "nsBidiPresUtils.h"
46#include "nsBlockFrame.h"
47#include "nsCaret.h"
48#include "nsContentUtils.h"
49#include "nsFrameSelection.h"
50#include "nsGkAtoms.h"
51#include "nsLayoutUtils.h"
52#include "nsStyleStructInlines.h"
53#include "nsTArray.h"
54#include "nsTHashSet.h"
55#include "nsTextFrame.h"
56
57using namespace mozilla::dom;
58using namespace mozilla::dom::SVGTextContentElement_Binding;
59using namespace mozilla::gfx;
60using namespace mozilla::image;
61
62namespace mozilla {
63
64// ============================================================================
65// Utility functions
66
67/**
68 * Using the specified gfxSkipCharsIterator, converts an offset and length
69 * in original char indexes to skipped char indexes.
70 *
71 * @param aIterator The gfxSkipCharsIterator to use for the conversion.
72 * @param aOriginalOffset The original offset.
73 * @param aOriginalLength The original length.
74 */
75static gfxTextRun::Range ConvertOriginalToSkipped(
76 gfxSkipCharsIterator& aIterator, uint32_t aOriginalOffset,
77 uint32_t aOriginalLength) {
78 uint32_t start = aIterator.ConvertOriginalToSkipped(aOriginalOffset);
79 aIterator.AdvanceOriginal(aOriginalLength);
80 return gfxTextRun::Range(start, aIterator.GetSkippedOffset());
81}
82
83/**
84 * Converts an nsPoint from app units to user space units using the specified
85 * nsPresContext and returns it as a gfxPoint.
86 */
87static gfxPoint AppUnitsToGfxUnits(const nsPoint& aPoint,
88 const nsPresContext* aContext) {
89 return gfxPoint(aContext->AppUnitsToGfxUnits(aPoint.x),
90 aContext->AppUnitsToGfxUnits(aPoint.y));
91}
92
93/**
94 * Converts a nsRect that is in app units to CSS pixels and returns it
95 * as a gfxRect.
96 */
97static gfxRect AppUnitsToFloatCSSPixels(const nsRect& aRect) {
98 return gfxRect(nsPresContext::AppUnitsToFloatCSSPixels(aRect.x),
99 nsPresContext::AppUnitsToFloatCSSPixels(aRect.y),
100 nsPresContext::AppUnitsToFloatCSSPixels(aRect.width),
101 nsPresContext::AppUnitsToFloatCSSPixels(aRect.height));
102}
103
104/**
105 * Gets the measured ascent and descent of the text in the given nsTextFrame
106 * in app units.
107 *
108 * @param aFrame The text frame.
109 * @param aAscent The ascent in app units (output).
110 * @param aDescent The descent in app units (output).
111 */
112static void GetAscentAndDescentInAppUnits(nsTextFrame* aFrame,
113 gfxFloat& aAscent,
114 gfxFloat& aDescent) {
115 gfxSkipCharsIterator it = aFrame->EnsureTextRun(nsTextFrame::eInflated);
116 gfxTextRun* textRun = aFrame->GetTextRun(nsTextFrame::eInflated);
117
118 gfxTextRun::Range range = ConvertOriginalToSkipped(
119 it, aFrame->GetContentOffset(), aFrame->GetContentLength());
120
121 textRun->GetLineHeightMetrics(range, aAscent, aDescent);
122}
123
124/**
125 * Updates an interval by intersecting it with another interval.
126 * The intervals are specified using a start index and a length.
127 */
128static void IntersectInterval(uint32_t& aStart, uint32_t& aLength,
129 uint32_t aStartOther, uint32_t aLengthOther) {
130 uint32_t aEnd = aStart + aLength;
131 uint32_t aEndOther = aStartOther + aLengthOther;
132
133 if (aStartOther >= aEnd || aStart >= aEndOther) {
134 aLength = 0;
135 } else {
136 if (aStartOther >= aStart) {
137 aStart = aStartOther;
138 }
139 aLength = std::min(aEnd, aEndOther) - aStart;
140 }
141}
142
143/**
144 * Intersects an interval as IntersectInterval does but by taking
145 * the offset and length of the other interval from a
146 * nsTextFrame::TrimmedOffsets object.
147 */
148static void TrimOffsets(uint32_t& aStart, uint32_t& aLength,
149 const nsTextFrame::TrimmedOffsets& aTrimmedOffsets) {
150 IntersectInterval(aStart, aLength, aTrimmedOffsets.mStart,
151 aTrimmedOffsets.mLength);
152}
153
154/**
155 * Returns the closest ancestor-or-self node that is not an SVG <a>
156 * element.
157 */
158static nsIContent* GetFirstNonAAncestor(nsIContent* aContent) {
159 while (aContent && aContent->IsSVGElement(nsGkAtoms::a)) {
160 aContent = aContent->GetParent();
161 }
162 return aContent;
163}
164
165/**
166 * Returns whether the given node is a text content element[1], taking into
167 * account whether it has a valid parent.
168 *
169 * For example, in:
170 *
171 * <svg xmlns="http://www.w3.org/2000/svg">
172 * <text><a/><text/></text>
173 * <tspan/>
174 * </svg>
175 *
176 * true would be returned for the outer <text> element and the <a> element,
177 * and false for the inner <text> element (since a <text> is not allowed
178 * to be a child of another <text>) and the <tspan> element (because it
179 * must be inside a <text> subtree).
180 *
181 * [1] https://svgwg.org/svg2-draft/intro.html#TermTextContentElement
182 */
183static bool IsTextContentElement(const nsIContent* aContent) {
184 if (aContent->IsSVGElement(nsGkAtoms::text)) {
185 const nsIContent* parent = GetFirstNonAAncestor(aContent->GetParent());
186 return !parent || !IsTextContentElement(parent);
187 }
188
189 if (aContent->IsSVGElement(nsGkAtoms::textPath)) {
190 const nsIContent* parent = GetFirstNonAAncestor(aContent->GetParent());
191 return parent && parent->IsSVGElement(nsGkAtoms::text);
192 }
193
194 return aContent->IsAnyOfSVGElements(nsGkAtoms::a, nsGkAtoms::tspan);
195}
196
197/**
198 * Returns whether the specified frame is an nsTextFrame that has some text
199 * content.
200 */
201static bool IsNonEmptyTextFrame(const nsIFrame* aFrame) {
202 const nsTextFrame* textFrame = do_QueryFrame(aFrame);
203 if (!textFrame) {
204 return false;
205 }
206
207 return textFrame->GetContentLength() != 0;
208}
209
210/**
211 * Takes an nsIFrame and if it is a text frame that has some text content,
212 * returns it as an nsTextFrame and its corresponding Text.
213 *
214 * @param aFrame The frame to look at.
215 * @param aTextFrame aFrame as an nsTextFrame (output).
216 * @param aTextNode The Text content of aFrame (output).
217 * @return true if aFrame is a non-empty text frame, false otherwise.
218 */
219static bool GetNonEmptyTextFrameAndNode(nsIFrame* aFrame,
220 nsTextFrame*& aTextFrame,
221 Text*& aTextNode) {
222 nsTextFrame* text = do_QueryFrame(aFrame);
223 bool isNonEmptyTextFrame = text && text->GetContentLength() != 0;
224
225 if (isNonEmptyTextFrame) {
226 nsIContent* content = text->GetContent();
227 NS_ASSERTION(content && content->IsText(),do { if (!(content && content->IsText())) { NS_DebugBreak
(NS_DEBUG_ASSERTION, "unexpected content type for nsTextFrame"
, "content && content->IsText()", "./../../../layout/svg/SVGTextFrame.cpp"
, 228); MOZ_PretendNoReturn(); } } while (0)
228 "unexpected content type for nsTextFrame")do { if (!(content && content->IsText())) { NS_DebugBreak
(NS_DEBUG_ASSERTION, "unexpected content type for nsTextFrame"
, "content && content->IsText()", "./../../../layout/svg/SVGTextFrame.cpp"
, 228); MOZ_PretendNoReturn(); } } while (0)
;
229
230 Text* node = content->AsText();
231 MOZ_ASSERT(node->TextLength() != 0,do { static_assert( mozilla::detail::AssertionConditionType<
decltype(node->TextLength() != 0)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(node->TextLength() != 0))
), 0))) { do { } while (false); MOZ_ReportAssertionFailure("node->TextLength() != 0"
" (" "frame's GetContentLength() should be 0 if the text node "
"has no content" ")", "./../../../layout/svg/SVGTextFrame.cpp"
, 233); AnnotateMozCrashReason("MOZ_ASSERT" "(" "node->TextLength() != 0"
") (" "frame's GetContentLength() should be 0 if the text node "
"has no content" ")"); do { MOZ_CrashSequence(__null, 233); __attribute__
((nomerge)) ::abort(); } while (false); } } while (false)
232 "frame's GetContentLength() should be 0 if the text node "do { static_assert( mozilla::detail::AssertionConditionType<
decltype(node->TextLength() != 0)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(node->TextLength() != 0))
), 0))) { do { } while (false); MOZ_ReportAssertionFailure("node->TextLength() != 0"
" (" "frame's GetContentLength() should be 0 if the text node "
"has no content" ")", "./../../../layout/svg/SVGTextFrame.cpp"
, 233); AnnotateMozCrashReason("MOZ_ASSERT" "(" "node->TextLength() != 0"
") (" "frame's GetContentLength() should be 0 if the text node "
"has no content" ")"); do { MOZ_CrashSequence(__null, 233); __attribute__
((nomerge)) ::abort(); } while (false); } } while (false)
233 "has no content")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(node->TextLength() != 0)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(node->TextLength() != 0))
), 0))) { do { } while (false); MOZ_ReportAssertionFailure("node->TextLength() != 0"
" (" "frame's GetContentLength() should be 0 if the text node "
"has no content" ")", "./../../../layout/svg/SVGTextFrame.cpp"
, 233); AnnotateMozCrashReason("MOZ_ASSERT" "(" "node->TextLength() != 0"
") (" "frame's GetContentLength() should be 0 if the text node "
"has no content" ")"); do { MOZ_CrashSequence(__null, 233); __attribute__
((nomerge)) ::abort(); } while (false); } } while (false)
;
234
235 aTextFrame = text;
236 aTextNode = node;
237 }
238
239 MOZ_ASSERT(IsNonEmptyTextFrame(aFrame) == isNonEmptyTextFrame,do { static_assert( mozilla::detail::AssertionConditionType<
decltype(IsNonEmptyTextFrame(aFrame) == isNonEmptyTextFrame)>
::isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(IsNonEmptyTextFrame(aFrame) == isNonEmptyTextFrame))
), 0))) { do { } while (false); MOZ_ReportAssertionFailure("IsNonEmptyTextFrame(aFrame) == isNonEmptyTextFrame"
" (" "our logic should agree with IsNonEmptyTextFrame" ")", "./../../../layout/svg/SVGTextFrame.cpp"
, 240); AnnotateMozCrashReason("MOZ_ASSERT" "(" "IsNonEmptyTextFrame(aFrame) == isNonEmptyTextFrame"
") (" "our logic should agree with IsNonEmptyTextFrame" ")")
; do { MOZ_CrashSequence(__null, 240); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
240 "our logic should agree with IsNonEmptyTextFrame")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(IsNonEmptyTextFrame(aFrame) == isNonEmptyTextFrame)>
::isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(IsNonEmptyTextFrame(aFrame) == isNonEmptyTextFrame))
), 0))) { do { } while (false); MOZ_ReportAssertionFailure("IsNonEmptyTextFrame(aFrame) == isNonEmptyTextFrame"
" (" "our logic should agree with IsNonEmptyTextFrame" ")", "./../../../layout/svg/SVGTextFrame.cpp"
, 240); AnnotateMozCrashReason("MOZ_ASSERT" "(" "IsNonEmptyTextFrame(aFrame) == isNonEmptyTextFrame"
") (" "our logic should agree with IsNonEmptyTextFrame" ")")
; do { MOZ_CrashSequence(__null, 240); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
241 return isNonEmptyTextFrame;
242}
243
244/**
245 * Returns whether the specified atom is for one of the five
246 * glyph positioning attributes that can appear on SVG text
247 * elements -- x, y, dx, dy or rotate.
248 */
249static bool IsGlyphPositioningAttribute(const nsAtom* aAttribute) {
250 return aAttribute == nsGkAtoms::x || aAttribute == nsGkAtoms::y ||
251 aAttribute == nsGkAtoms::dx || aAttribute == nsGkAtoms::dy ||
252 aAttribute == nsGkAtoms::rotate;
253}
254
255/**
256 * Returns the position in app units of a given baseline (using an
257 * SVG dominant-baseline property value) for a given nsTextFrame.
258 *
259 * @param aFrame The text frame to inspect.
260 * @param aTextRun The text run of aFrame.
261 * @param aDominantBaseline The dominant-baseline value to use.
262 */
263static nscoord GetBaselinePosition(nsTextFrame* aFrame,
264 const gfxTextRun* aTextRun,
265 StyleDominantBaseline aDominantBaseline,
266 float aFontSizeScaleFactor) {
267 WritingMode writingMode = aFrame->GetWritingMode();
268 gfxFloat ascent, descent;
269 aTextRun->GetLineHeightMetrics(ascent, descent);
270
271 auto convertIfVerticalRL = [&](gfxFloat dominantBaseline) {
272 return writingMode.IsVerticalRL() ? ascent + descent - dominantBaseline
273 : dominantBaseline;
274 };
275
276 switch (aDominantBaseline) {
277 case StyleDominantBaseline::Hanging:
278 return convertIfVerticalRL(ascent * 0.2);
279 case StyleDominantBaseline::TextTop:
280 return convertIfVerticalRL(0);
281
282 case StyleDominantBaseline::Alphabetic:
283 return writingMode.IsVerticalRL()
284 ? ascent * 0.5
285 : aFrame->GetLogicalBaseline(writingMode);
286
287 case StyleDominantBaseline::Auto:
288 return convertIfVerticalRL(aFrame->GetLogicalBaseline(writingMode));
289
290 case StyleDominantBaseline::Middle:
291 return convertIfVerticalRL(aFrame->GetLogicalBaseline(writingMode) -
292 SVGContentUtils::GetFontXHeight(aFrame) / 2.0 *
293 AppUnitsPerCSSPixel() *
294 aFontSizeScaleFactor);
295
296 case StyleDominantBaseline::TextBottom:
297 case StyleDominantBaseline::Ideographic:
298 return convertIfVerticalRL(ascent + descent);
299
300 case StyleDominantBaseline::Central:
301 return (ascent + descent) / 2.0;
302 case StyleDominantBaseline::Mathematical:
303 return convertIfVerticalRL(ascent / 2.0);
304 }
305
306 MOZ_ASSERT_UNREACHABLE("unexpected dominant-baseline 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: "
"unexpected dominant-baseline value" ")", "./../../../layout/svg/SVGTextFrame.cpp"
, 306); AnnotateMozCrashReason("MOZ_ASSERT" "(" "false" ") ("
"MOZ_ASSERT_UNREACHABLE: " "unexpected dominant-baseline value"
")"); do { MOZ_CrashSequence(__null, 306); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
307 return convertIfVerticalRL(aFrame->GetLogicalBaseline(writingMode));
308}
309
310/**
311 * Truncates an array to be at most the length of another array.
312 *
313 * @param aArrayToTruncate The array to truncate.
314 * @param aReferenceArray The array whose length will be used to truncate
315 * aArrayToTruncate to.
316 */
317template <typename T, typename U>
318static void TruncateTo(nsTArray<T>& aArrayToTruncate,
319 const nsTArray<U>& aReferenceArray) {
320 uint32_t length = aReferenceArray.Length();
321 if (aArrayToTruncate.Length() > length) {
322 aArrayToTruncate.TruncateLength(length);
323 }
324}
325
326/**
327 * Asserts that the anonymous block child of the SVGTextFrame has been
328 * reflowed (or does not exist). Returns null if the child has not been
329 * reflowed, and the frame otherwise.
330 *
331 * We check whether the kid has been reflowed and not the frame itself
332 * since we sometimes need to call this function during reflow, after the
333 * kid has been reflowed but before we have cleared the dirty bits on the
334 * frame itself.
335 */
336static SVGTextFrame* FrameIfAnonymousChildReflowed(SVGTextFrame* aFrame) {
337 MOZ_ASSERT(aFrame, "aFrame must not be null")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aFrame)>::isValid, "invalid assertion condition")
; if ((__builtin_expect(!!(!(!!(aFrame))), 0))) { do { } while
(false); MOZ_ReportAssertionFailure("aFrame" " (" "aFrame must not be null"
")", "./../../../layout/svg/SVGTextFrame.cpp", 337); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "aFrame" ") (" "aFrame must not be null" ")"
); do { MOZ_CrashSequence(__null, 337); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
338 nsIFrame* kid = aFrame->PrincipalChildList().FirstChild();
339 if (kid->IsSubtreeDirty()) {
340 MOZ_ASSERT(false, "should have already reflowed the anonymous block child")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(false)>::isValid, "invalid assertion condition");
if ((__builtin_expect(!!(!(!!(false))), 0))) { do { } while (
false); MOZ_ReportAssertionFailure("false" " (" "should have already reflowed the anonymous block child"
")", "./../../../layout/svg/SVGTextFrame.cpp", 340); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "false" ") (" "should have already reflowed the anonymous block child"
")"); do { MOZ_CrashSequence(__null, 340); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
341 return nullptr;
342 }
343 return aFrame;
344}
345
346// FIXME(emilio): SVG is a special-case where transforms affect layout. We don't
347// want that to go outside the SVG stuff (and really we should aim to remove
348// that).
349static float GetContextScale(SVGTextFrame* aFrame) {
350 if (aFrame->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) {
351 // When we are non-display, we could be painted in different coordinate
352 // spaces, and we don't want to have to reflow for each of these. We just
353 // assume that the context scale is 1.0 for them all, so we don't get stuck
354 // with a font size scale factor based on whichever referencing frame
355 // happens to reflow first.
356 return 1.0f;
357 }
358 auto matrix = nsLayoutUtils::GetTransformToAncestor(
359 RelativeTo{aFrame}, RelativeTo{SVGUtils::GetOuterSVGFrame(aFrame)});
360 Matrix transform2D;
361 if (!matrix.CanDraw2D(&transform2D)) {
362 return 1.0f;
363 }
364 auto scales = transform2D.ScaleFactors();
365 return std::max(0.0f, std::max(scales.xScale, scales.yScale));
366}
367
368// ============================================================================
369// Utility classes
370
371// ----------------------------------------------------------------------------
372// TextRenderedRun
373
374/**
375 * A run of text within a single nsTextFrame whose glyphs can all be painted
376 * with a single call to nsTextFrame::PaintText. A text rendered run can
377 * be created for a sequence of two or more consecutive glyphs as long as:
378 *
379 * - Only the first glyph has (or none of the glyphs have) been positioned
380 * with SVG text positioning attributes
381 * - All of the glyphs have zero rotation
382 * - The glyphs are not on a text path
383 * - The glyphs correspond to content within the one nsTextFrame
384 *
385 * A TextRenderedRunIterator produces TextRenderedRuns required for painting a
386 * whole SVGTextFrame.
387 */
388struct TextRenderedRun {
389 using Range = gfxTextRun::Range;
390
391 /**
392 * Constructs a TextRenderedRun that is uninitialized except for mFrame
393 * being null.
394 */
395 TextRenderedRun() : mFrame(nullptr) {}
396
397 /**
398 * Constructs a TextRenderedRun with all of the information required to
399 * paint it. See the comments documenting the member variables below
400 * for descriptions of the arguments.
401 */
402 TextRenderedRun(nsTextFrame* aFrame, SVGTextFrame* aSVGTextFrame,
403 const gfxPoint& aPosition, double aRotate,
404 float aFontSizeScaleFactor, nscoord aBaseline,
405 uint32_t aTextFrameContentOffset,
406 uint32_t aTextFrameContentLength,
407 uint32_t aTextElementCharIndex)
408 : mFrame(aFrame),
409 mRoot(aSVGTextFrame),
410 mPosition(aPosition),
411 mLengthAdjustScaleFactor(mRoot->mLengthAdjustScaleFactor),
412 mRotate(static_cast<float>(aRotate)),
413 mFontSizeScaleFactor(aFontSizeScaleFactor),
414 mBaseline(aBaseline),
415 mTextFrameContentOffset(aTextFrameContentOffset),
416 mTextFrameContentLength(aTextFrameContentLength),
417 mTextElementCharIndex(aTextElementCharIndex) {}
418
419 /**
420 * Returns the text run for the text frame that this rendered run is part of.
421 */
422 gfxTextRun* GetTextRun() const {
423 mFrame->EnsureTextRun(nsTextFrame::eInflated);
424 return mFrame->GetTextRun(nsTextFrame::eInflated);
425 }
426
427 /**
428 * Return true if the logical inline direction is reversed compared to
429 * normal physical coordinates (i.e. if it is leftwards or upwards).
430 */
431 bool IsInlineReversed() const { return GetTextRun()->IsInlineReversed(); }
432
433 /**
434 * Returns whether this rendered run is vertical.
435 */
436 bool IsVertical() const { return GetTextRun()->IsVertical(); }
437
438 /**
439 * Returns the transform that converts from a <text> element's user space into
440 * the coordinate space that rendered runs can be painted directly in.
441 *
442 * The difference between this method and
443 * GetTransformFromRunUserSpaceToUserSpace is that when calling in to
444 * nsTextFrame::PaintText, it will already take into account any left clip
445 * edge (that is, it doesn't just apply a visual clip to the rendered text, it
446 * shifts the glyphs over so that they are painted with their left edge at the
447 * x coordinate passed in to it). Thus we need to account for this in our
448 * transform.
449 *
450 *
451 * Assume that we have:
452 *
453 * <text x="100" y="100" rotate="0 0 1 0 0 * 1">abcdef</text>.
454 *
455 * This would result in four text rendered runs:
456 *
457 * - one for "ab"
458 * - one for "c"
459 * - one for "de"
460 * - one for "f"
461 *
462 * Assume now that we are painting the third TextRenderedRun. It will have
463 * a left clip edge that is the sum of the advances of "abc", and it will
464 * have a right clip edge that is the advance of "f". In
465 * SVGTextFrame::PaintSVG(), we pass in nsPoint() (i.e., the origin)
466 * as the point at which to paint the text frame, and we pass in the
467 * clip edge values. The nsTextFrame will paint the substring of its
468 * text such that the top-left corner of the "d"'s glyph cell will be at
469 * (0, 0) in the current coordinate system.
470 *
471 * Thus, GetTransformFromUserSpaceForPainting must return a transform from
472 * whatever user space the <text> element is in to a coordinate space in
473 * device pixels (as that's what nsTextFrame works in) where the origin is at
474 * the same position as our user space mPositions[i].mPosition value for
475 * the "d" glyph, which will be (100 + userSpaceAdvance("abc"), 100).
476 * The translation required to do this (ignoring the scale to get from
477 * user space to device pixels, and ignoring the
478 * (100 + userSpaceAdvance("abc"), 100) translation) is:
479 *
480 * (-leftEdge, -baseline)
481 *
482 * where baseline is the distance between the baseline of the text and the top
483 * edge of the nsTextFrame. We translate by -leftEdge horizontally because
484 * the nsTextFrame will already shift the glyphs over by that amount and start
485 * painting glyphs at x = 0. We translate by -baseline vertically so that
486 * painting the top edges of the glyphs at y = 0 will result in their
487 * baselines being at our desired y position.
488 *
489 *
490 * Now for an example with RTL text. Assume our content is now
491 * <text x="100" y="100" rotate="0 0 1 0 0 1">WERBEH</text>. We'd have
492 * the following text rendered runs:
493 *
494 * - one for "EH"
495 * - one for "B"
496 * - one for "ER"
497 * - one for "W"
498 *
499 * Again, we are painting the third TextRenderedRun. The left clip edge
500 * is the advance of the "W" and the right clip edge is the sum of the
501 * advances of "BEH". Our translation to get the rendered "ER" glyphs
502 * in the right place this time is:
503 *
504 * (-frameWidth + rightEdge, -baseline)
505 *
506 * which is equivalent to:
507 *
508 * (-(leftEdge + advance("ER")), -baseline)
509 *
510 * The reason we have to shift left additionally by the width of the run
511 * of glyphs we are painting is that although the nsTextFrame is RTL,
512 * we still supply the top-left corner to paint the frame at when calling
513 * nsTextFrame::PaintText, even though our user space positions for each
514 * glyph in mPositions specifies the origin of each glyph, which for RTL
515 * glyphs is at the right edge of the glyph cell.
516 *
517 *
518 * For any other use of an nsTextFrame in the context of a particular run
519 * (such as hit testing, or getting its rectangle),
520 * GetTransformFromRunUserSpaceToUserSpace should be used.
521 *
522 * @param aContext The context to use for unit conversions.
523 */
524 gfxMatrix GetTransformFromUserSpaceForPainting(
525 nsPresContext* aContext, const nscoord aVisIStartEdge,
526 const nscoord aVisIEndEdge) const;
527
528 /**
529 * Returns the transform that converts from "run user space" to a <text>
530 * element's user space. Run user space is a coordinate system that has the
531 * same size as the <text>'s user space but rotated and translated such that
532 * (0,0) is the top-left of the rectangle that bounds the text.
533 *
534 * @param aContext The context to use for unit conversions.
535 */
536 gfxMatrix GetTransformFromRunUserSpaceToUserSpace(
537 nsPresContext* aContext) const;
538
539 /**
540 * Returns the transform that converts from "run user space" to float pixels
541 * relative to the nsTextFrame that this rendered run is a part of.
542 *
543 * @param aContext The context to use for unit conversions.
544 */
545 gfxMatrix GetTransformFromRunUserSpaceToFrameUserSpace(
546 nsPresContext* aContext) const;
547
548 /**
549 * Flag values used for the aFlags arguments of GetRunUserSpaceRect,
550 * GetFrameUserSpaceRect and GetUserSpaceRect.
551 */
552 enum class GeometryFlag {
553 // Includes the fill geometry of the text in the returned rectangle.
554 IncludeFill,
555 // Includes the stroke geometry of the text in the returned rectangle.
556 IncludeStroke,
557 // Don't include any horizontal glyph overflow in the returned rectangle.
558 NoHorizontalOverflow
559 };
560 using GeometryFlags = EnumSet<GeometryFlag>;
561
562 /**
563 * Returns a rectangle that bounds the fill and/or stroke of the rendered run
564 * in run user space.
565 *
566 * @param aFlags Flags indicating what parts of the text to include in
567 * the rectangle.
568 */
569 SVGBBox GetRunUserSpaceRect(GeometryFlags aFlags) const;
570
571 /**
572 * Returns a rectangle that covers the fill and/or stroke of the rendered run
573 * in "frame user space".
574 *
575 * Frame user space is a coordinate space of the same scale as the <text>
576 * element's user space, but with its rotation set to the rotation of
577 * the glyphs within this rendered run and its origin set to the position
578 * such that placing the nsTextFrame there would result in the glyphs in
579 * this rendered run being at their correct positions.
580 *
581 * For example, say we have <text x="100 150" y="100">ab</text>. Assume
582 * the advance of both the "a" and the "b" is 12 user units, and the
583 * ascent of the text is 8 user units and its descent is 6 user units,
584 * and that we are not measuing the stroke of the text, so that we stay
585 * entirely within the glyph cells.
586 *
587 * There will be two text rendered runs, one for "a" and one for "b".
588 *
589 * The frame user space for the "a" run will have its origin at
590 * (100, 100 - 8) in the <text> element's user space and will have its
591 * axes aligned with the user space (since there is no rotate="" or
592 * text path involve) and with its scale the same as the user space.
593 * The rect returned by this method will be (0, 0, 12, 14), since the "a"
594 * glyph is right at the left of the nsTextFrame.
595 *
596 * The frame user space for the "b" run will have its origin at
597 * (150 - 12, 100 - 8), and scale/rotation the same as above. The rect
598 * returned by this method will be (12, 0, 12, 14), since we are
599 * advance("a") horizontally in to the text frame.
600 *
601 * @param aContext The context to use for unit conversions.
602 * @param aFlags Flags indicating what parts of the text to include in
603 * the rectangle.
604 */
605 SVGBBox GetFrameUserSpaceRect(nsPresContext* aContext,
606 GeometryFlags aFlags) const;
607
608 /**
609 * Returns a rectangle that covers the fill and/or stroke of the rendered run
610 * in the <text> element's user space.
611 *
612 * @param aContext The context to use for unit conversions.
613 * @param aFlags A combination of flags indicating what parts of
614 * the text to include in the rectangle.
615 * @param aAdditionalTransform An additional transform to apply to the
616 * frame user space rectangle before its bounds are transformed into
617 * user space.
618 */
619 SVGBBox GetUserSpaceRect(
620 nsPresContext* aContext, GeometryFlags aFlags,
621 const gfxMatrix* aAdditionalTransform = nullptr) const;
622
623 /**
624 * Gets the app unit amounts to clip from the left and right edges of
625 * the nsTextFrame in order to paint just this rendered run.
626 *
627 * Note that if clip edge amounts land in the middle of a glyph, the
628 * glyph won't be painted at all. The clip edges are thus more of
629 * a selection mechanism for which glyphs will be painted, rather
630 * than a geometric clip.
631 */
632 void GetClipEdges(nscoord& aVisIStartEdge, nscoord& aVisIEndEdge) const;
633
634 /**
635 * Returns the advance width of the whole rendered run.
636 */
637 nscoord GetAdvanceWidth() const;
638
639 /**
640 * Returns the index of the character into this rendered run whose
641 * glyph cell contains the given point, or -1 if there is no such
642 * character. This does not hit test against any overflow.
643 *
644 * @param aContext The context to use for unit conversions.
645 * @param aPoint The point in the user space of the <text> element.
646 */
647 int32_t GetCharNumAtPosition(nsPresContext* aContext,
648 const gfxPoint& aPoint) const;
649
650 /**
651 * The text frame that this rendered run lies within.
652 */
653 nsTextFrame* mFrame;
654
655 /**
656 * The SVGTextFrame to which our text frame belongs.
657 */
658 SVGTextFrame* mRoot;
659
660 /**
661 * The point in user space that the text is positioned at.
662 *
663 * For a horizontal run:
664 * The x coordinate is the left edge of a LTR run of text or the right edge of
665 * an RTL run. The y coordinate is the baseline of the text.
666 * For a vertical run:
667 * The x coordinate is the baseline of the text.
668 * The y coordinate is the top edge of a LTR run, or bottom of RTL.
669 */
670 gfxPoint mPosition;
671
672 /**
673 * The horizontal scale factor to apply when painting glyphs to take
674 * into account textLength="".
675 */
676 float mLengthAdjustScaleFactor;
677
678 /**
679 * The rotation in radians in the user coordinate system that the text has.
680 */
681 float mRotate;
682
683 /**
684 * The scale factor that was used to transform the text run's original font
685 * size into a sane range for painting and measurement.
686 */
687 double mFontSizeScaleFactor;
688
689 /**
690 * The baseline in app units of this text run. The measurement is from the
691 * top of the text frame. (From the left edge if vertical.)
692 */
693 nscoord mBaseline;
694
695 /**
696 * The offset and length in mFrame's content Text that corresponds to
697 * this text rendered run. These are original char indexes.
698 */
699 uint32_t mTextFrameContentOffset;
700 uint32_t mTextFrameContentLength;
701
702 /**
703 * The character index in the whole SVG <text> element that this text rendered
704 * run begins at.
705 */
706 uint32_t mTextElementCharIndex;
707};
708
709gfxMatrix TextRenderedRun::GetTransformFromUserSpaceForPainting(
710 nsPresContext* aContext, const nscoord aVisIStartEdge,
711 const nscoord aVisIEndEdge) const {
712 // We transform to device pixels positioned such that painting the text frame
713 // at (0,0) with aItem will result in the text being in the right place.
714
715 gfxMatrix m;
716 if (!mFrame) {
717 return m;
718 }
719
720 float cssPxPerDevPx =
721 nsPresContext::AppUnitsToFloatCSSPixels(aContext->AppUnitsPerDevPixel());
722
723 // Glyph position in user space.
724 m.PreTranslate(mPosition / cssPxPerDevPx);
725
726 // Take into account any font size scaling and scaling due to textLength="".
727 m.PreScale(1.0 / mFontSizeScaleFactor, 1.0 / mFontSizeScaleFactor);
728
729 // Rotation due to rotate="" or a <textPath>.
730 m.PreRotate(mRotate);
731
732 // Scale for textLength="" and translate to get the text frame
733 // to the right place.
734 nsPoint t;
735 if (IsVertical()) {
736 m.PreScale(1.0, mLengthAdjustScaleFactor);
737 t = nsPoint(-mBaseline, IsInlineReversed()
738 ? -mFrame->GetRect().height + aVisIEndEdge
739 : -aVisIStartEdge);
740 } else {
741 m.PreScale(mLengthAdjustScaleFactor, 1.0);
742 t = nsPoint(IsInlineReversed() ? -mFrame->GetRect().width + aVisIEndEdge
743 : -aVisIStartEdge,
744 -mBaseline);
745 }
746 m.PreTranslate(AppUnitsToGfxUnits(t, aContext));
747
748 return m;
749}
750
751gfxMatrix TextRenderedRun::GetTransformFromRunUserSpaceToUserSpace(
752 nsPresContext* aContext) const {
753 gfxMatrix m;
754 if (!mFrame) {
755 return m;
756 }
757
758 float cssPxPerDevPx =
759 nsPresContext::AppUnitsToFloatCSSPixels(aContext->AppUnitsPerDevPixel());
760
761 nscoord start, end;
762 GetClipEdges(start, end);
763
764 // Glyph position in user space.
765 m.PreTranslate(mPosition);
766
767 // Rotation due to rotate="" or a <textPath>.
768 m.PreRotate(mRotate);
769
770 // Scale for textLength="" and translate to get the text frame
771 // to the right place.
772
773 nsPoint t;
774 if (IsVertical()) {
775 m.PreScale(1.0, mLengthAdjustScaleFactor);
776 t = nsPoint(-mBaseline, IsInlineReversed()
777 ? -mFrame->GetRect().height + start + end
778 : 0);
779 } else {
780 m.PreScale(mLengthAdjustScaleFactor, 1.0);
781 t = nsPoint(IsInlineReversed() ? -mFrame->GetRect().width + start + end : 0,
782 -mBaseline);
783 }
784 m.PreTranslate(AppUnitsToGfxUnits(t, aContext) * cssPxPerDevPx /
785 mFontSizeScaleFactor);
786
787 return m;
788}
789
790gfxMatrix TextRenderedRun::GetTransformFromRunUserSpaceToFrameUserSpace(
791 nsPresContext* aContext) const {
792 gfxMatrix m;
793 if (!mFrame) {
794 return m;
795 }
796
797 nscoord start, end;
798 GetClipEdges(start, end);
799
800 // Translate by the horizontal distance into the text frame this
801 // rendered run is.
802 gfxFloat appPerCssPx = AppUnitsPerCSSPixel();
803 gfxPoint t = IsVertical() ? gfxPoint(0, start / appPerCssPx)
804 : gfxPoint(start / appPerCssPx, 0);
805 return m.PreTranslate(t);
806}
807
808SVGBBox TextRenderedRun::GetRunUserSpaceRect(GeometryFlags aFlags) const {
809 SVGBBox r;
810 if (!mFrame) {
811 return r;
812 }
813
814 // Determine the amount of overflow around frame's mRect.
815 //
816 // We need to call InkOverflowRectRelativeToSelf because this includes
817 // overflowing decorations, which the MeasureText call below does not.
818 nsRect self = mFrame->InkOverflowRectRelativeToSelf();
819 nsRect rect = mFrame->GetRect();
820 bool vertical = IsVertical();
821 nsMargin inkOverflow(
822 vertical ? -self.x : -self.y,
823 vertical ? self.YMost() - rect.height : self.XMost() - rect.width,
824 vertical ? self.XMost() - rect.width : self.YMost() - rect.height,
825 vertical ? -self.y : -self.x);
826
827 gfxSkipCharsIterator it = mFrame->EnsureTextRun(nsTextFrame::eInflated);
828 gfxTextRun* textRun = mFrame->GetTextRun(nsTextFrame::eInflated);
829
830 // Get the content range for this rendered run.
831 Range range = ConvertOriginalToSkipped(it, mTextFrameContentOffset,
832 mTextFrameContentLength);
833 if (range.Length() == 0) {
834 return r;
835 }
836
837 auto& provider = mRoot->PropertyProviderFor(mFrame);
838
839 // Measure that range.
840 gfxTextRun::Metrics metrics = textRun->MeasureText(
841 range, gfxFont::LOOSE_INK_EXTENTS, nullptr, &provider);
842 // Make sure it includes the font-box.
843 gfxRect fontBox(0, -metrics.mAscent, metrics.mAdvanceWidth,
844 metrics.mAscent + metrics.mDescent);
845 metrics.mBoundingBox.UnionRect(metrics.mBoundingBox, fontBox);
846
847 // Determine the rectangle that covers the rendered run's fill,
848 // taking into account the measured overflow due to decorations.
849 nscoord baseline =
850 NSToCoordRoundWithClamp(metrics.mBoundingBox.y + metrics.mAscent);
851 gfxFloat x, width;
852 if (aFlags.contains(GeometryFlag::NoHorizontalOverflow)) {
853 x = 0.0;
854 width = textRun->GetAdvanceWidth(range, &provider);
855 if (width < 0.0) {
856 x = width;
857 width = -width;
858 }
859 } else {
860 x = metrics.mBoundingBox.x;
861 width = metrics.mBoundingBox.width;
862 }
863 nsRect fillInAppUnits(NSToCoordRoundWithClamp(x), baseline,
864 NSToCoordRoundWithClamp(width),
865 NSToCoordRoundWithClamp(metrics.mBoundingBox.height));
866 fillInAppUnits.Inflate(inkOverflow);
867 if (textRun->IsVertical()) {
868 // Swap line-relative textMetrics dimensions to physical coordinates.
869 std::swap(fillInAppUnits.x, fillInAppUnits.y);
870 std::swap(fillInAppUnits.width, fillInAppUnits.height);
871 }
872
873 // Convert the app units rectangle to user units.
874 gfxRect fill = AppUnitsToFloatCSSPixels(fillInAppUnits);
875
876 // Scale the rectangle up due to any mFontSizeScaleFactor.
877 fill.Scale(1.0 / mFontSizeScaleFactor);
878
879 // Include the fill if requested.
880 if (aFlags.contains(GeometryFlag::IncludeFill)) {
881 r = fill;
882 }
883
884 // Include the stroke if requested.
885 if (aFlags.contains(GeometryFlag::IncludeStroke) && !fill.IsEmpty() &&
886 SVGUtils::GetStrokeWidth(mFrame) > 0) {
887 r.UnionEdges(
888 SVGUtils::PathExtentsToMaxStrokeExtents(fill, mFrame, gfxMatrix()));
889 }
890
891 return r;
892}
893
894SVGBBox TextRenderedRun::GetFrameUserSpaceRect(nsPresContext* aContext,
895 GeometryFlags aFlags) const {
896 SVGBBox r = GetRunUserSpaceRect(aFlags);
897 if (r.IsEmpty()) {
898 return r;
899 }
900 gfxMatrix m = GetTransformFromRunUserSpaceToFrameUserSpace(aContext);
901 return m.TransformBounds(r.ToThebesRect());
902}
903
904SVGBBox TextRenderedRun::GetUserSpaceRect(
905 nsPresContext* aContext, GeometryFlags aFlags,
906 const gfxMatrix* aAdditionalTransform) const {
907 SVGBBox r = GetRunUserSpaceRect(aFlags);
908 if (r.IsEmpty()) {
909 return r;
910 }
911 gfxMatrix m = GetTransformFromRunUserSpaceToUserSpace(aContext);
912 if (aAdditionalTransform) {
913 m *= *aAdditionalTransform;
914 }
915 return m.TransformBounds(r.ToThebesRect());
916}
917
918void TextRenderedRun::GetClipEdges(nscoord& aVisIStartEdge,
919 nscoord& aVisIEndEdge) const {
920 uint32_t contentLength = mFrame->GetContentLength();
921 if (mTextFrameContentOffset == 0 &&
922 mTextFrameContentLength == contentLength) {
923 // If the rendered run covers the entire content, we know we don't need
924 // to clip without having to measure anything.
925 aVisIStartEdge = 0;
926 aVisIEndEdge = 0;
927 return;
928 }
929
930 gfxSkipCharsIterator it = mFrame->EnsureTextRun(nsTextFrame::eInflated);
931 gfxTextRun* textRun = mFrame->GetTextRun(nsTextFrame::eInflated);
932 auto& provider = mRoot->PropertyProviderFor(mFrame);
933
934 // Get the covered content offset/length for this rendered run in skipped
935 // characters, since that is what GetAdvanceWidth expects.
936 Range runRange = ConvertOriginalToSkipped(it, mTextFrameContentOffset,
937 mTextFrameContentLength);
938
939 // Get the offset/length of the whole nsTextFrame.
940 uint32_t frameOffset = mFrame->GetContentOffset();
941 uint32_t frameLength = mFrame->GetContentLength();
942
943 // Trim the whole-nsTextFrame offset/length to remove any leading/trailing
944 // white space, as the nsTextFrame when painting does not include them when
945 // interpreting clip edges.
946 nsTextFrame::TrimmedOffsets trimmedOffsets =
947 mFrame->GetTrimmedOffsets(mFrame->CharacterDataBuffer());
948 TrimOffsets(frameOffset, frameLength, trimmedOffsets);
949
950 // Convert the trimmed whole-nsTextFrame offset/length into skipped
951 // characters.
952 Range frameRange = ConvertOriginalToSkipped(it, frameOffset, frameLength);
953
954 // Get the advance of aRange, using the aCachedRange if available to
955 // accelerate textrun measurement.
956 auto MeasureUsingCache = [&](SVGTextFrame::CachedMeasuredRange& aCachedRange,
957 const Range& aRange) -> nscoord {
958 if (aRange.Intersects(aCachedRange.mRange)) {
959 // Figure out the deltas between the cached range and the new one at the
960 // start and end edges.
961 Range startDelta, endDelta;
962 int startSign = 0, endSign = 0;
963 if (aRange.start < aCachedRange.mRange.start) {
964 // This range extends the cached range at the start.
965 startSign = 1;
966 startDelta = Range(aRange.start, aCachedRange.mRange.start);
967 } else if (aRange.start > aCachedRange.mRange.start) {
968 // This range trims the cached range at the start.
969 startSign = -1;
970 startDelta = Range(aCachedRange.mRange.start, aRange.start);
971 }
972 if (aRange.end > aCachedRange.mRange.end) {
973 // This range extends the cached range at the end.
974 endSign = 1;
975 endDelta = Range(aCachedRange.mRange.end, aRange.end);
976 } else if (aRange.end < aCachedRange.mRange.end) {
977 // This range trims the cached range at the end.
978 endSign = -1;
979 endDelta = Range(aRange.end, aCachedRange.mRange.end);
980 }
981 // If the total of the deltas is less than the length of aRange,
982 // it will be cheaper to measure them and adjust the cached advance
983 // instead of measuring the whole of aRange.
984 if (startDelta.Length() + endDelta.Length() < aRange.Length()) {
985 if (startSign) {
986 aCachedRange.mAdvance +=
987 startSign * textRun->GetAdvanceWidth(startDelta, &provider);
988 }
989 if (endSign) {
990 aCachedRange.mAdvance +=
991 endSign * textRun->GetAdvanceWidth(endDelta, &provider);
992 }
993 } else {
994 aCachedRange.mAdvance = textRun->GetAdvanceWidth(aRange, &provider);
995 }
996 } else {
997 // Just measure the range, and cache the result.
998 aCachedRange.mAdvance = textRun->GetAdvanceWidth(aRange, &provider);
999 }
1000 aCachedRange.mRange = aRange;
1001 return aCachedRange.mAdvance;
1002 };
1003
1004 mRoot->SetCurrentFrameForCaching(mFrame);
1005 nscoord startEdge =
1006 MeasureUsingCache(mRoot->CachedRange(SVGTextFrame::WhichRange::Before),
1007 Range(frameRange.start, runRange.start));
1008 nscoord endEdge =
1009 MeasureUsingCache(mRoot->CachedRange(SVGTextFrame::WhichRange::After),
1010 Range(runRange.end, frameRange.end));
1011
1012 if (textRun->IsInlineReversed()) {
1013 aVisIStartEdge = endEdge;
1014 aVisIEndEdge = startEdge;
1015 } else {
1016 aVisIStartEdge = startEdge;
1017 aVisIEndEdge = endEdge;
1018 }
1019}
1020
1021nscoord TextRenderedRun::GetAdvanceWidth() const {
1022 gfxSkipCharsIterator it = mFrame->EnsureTextRun(nsTextFrame::eInflated);
1023 gfxTextRun* textRun = mFrame->GetTextRun(nsTextFrame::eInflated);
1024 auto& provider = mRoot->PropertyProviderFor(mFrame);
1025
1026 Range range = ConvertOriginalToSkipped(it, mTextFrameContentOffset,
1027 mTextFrameContentLength);
1028
1029 return textRun->GetAdvanceWidth(range, &provider);
1030}
1031
1032int32_t TextRenderedRun::GetCharNumAtPosition(nsPresContext* aContext,
1033 const gfxPoint& aPoint) const {
1034 if (mTextFrameContentLength == 0) {
1035 return -1;
1036 }
1037
1038 float cssPxPerDevPx =
1039 nsPresContext::AppUnitsToFloatCSSPixels(aContext->AppUnitsPerDevPixel());
1040
1041 // Convert the point from user space into run user space, and take
1042 // into account any mFontSizeScaleFactor.
1043 gfxMatrix m = GetTransformFromRunUserSpaceToUserSpace(aContext);
1044 if (!m.Invert()) {
1045 return -1;
1046 }
1047 gfxPoint p = m.TransformPoint(aPoint) / cssPxPerDevPx * mFontSizeScaleFactor;
1048
1049 // First check that the point lies vertically between the top and bottom
1050 // edges of the text.
1051 gfxFloat ascent, descent;
1052 GetAscentAndDescentInAppUnits(mFrame, ascent, descent);
1053
1054 WritingMode writingMode = mFrame->GetWritingMode();
1055 if (writingMode.IsVertical()) {
1056 gfxFloat leftEdge = mFrame->GetLogicalBaseline(writingMode) -
1057 (writingMode.IsVerticalRL() ? ascent : descent);
1058 gfxFloat rightEdge = leftEdge + ascent + descent;
1059 if (p.x < aContext->AppUnitsToGfxUnits(leftEdge) ||
1060 p.x > aContext->AppUnitsToGfxUnits(rightEdge)) {
1061 return -1;
1062 }
1063 } else {
1064 gfxFloat topEdge = mFrame->GetLogicalBaseline(writingMode) - ascent;
1065 gfxFloat bottomEdge = topEdge + ascent + descent;
1066 if (p.y < aContext->AppUnitsToGfxUnits(topEdge) ||
1067 p.y > aContext->AppUnitsToGfxUnits(bottomEdge)) {
1068 return -1;
1069 }
1070 }
1071
1072 gfxSkipCharsIterator it = mFrame->EnsureTextRun(nsTextFrame::eInflated);
1073 gfxTextRun* textRun = mFrame->GetTextRun(nsTextFrame::eInflated);
1074 auto& provider = mRoot->PropertyProviderFor(mFrame);
1075
1076 // Next check that the point lies horizontally within the left and right
1077 // edges of the text.
1078 Range range = ConvertOriginalToSkipped(it, mTextFrameContentOffset,
1079 mTextFrameContentLength);
1080 gfxFloat runAdvance =
1081 aContext->AppUnitsToGfxUnits(textRun->GetAdvanceWidth(range, &provider));
1082
1083 gfxFloat pos = writingMode.IsVertical() ? p.y : p.x;
1084 if (pos < 0 || pos >= runAdvance) {
1085 return -1;
1086 }
1087
1088 // Finally, measure progressively smaller portions of the rendered run to
1089 // find which glyph it lies within. This will need to change once we
1090 // support letter-spacing and word-spacing.
1091 bool ir = textRun->IsInlineReversed();
1092 for (int32_t i = mTextFrameContentLength - 1; i >= 0; i--) {
1093 range = ConvertOriginalToSkipped(it, mTextFrameContentOffset, i);
1094 gfxFloat advance = aContext->AppUnitsToGfxUnits(
1095 textRun->GetAdvanceWidth(range, &provider));
1096 if ((ir && pos < runAdvance - advance) || (!ir && pos >= advance)) {
1097 return i;
1098 }
1099 }
1100 return -1;
1101}
1102
1103// ----------------------------------------------------------------------------
1104// TextNodeIterator
1105
1106enum class SubtreePosition { Before, Within, After };
1107
1108/**
1109 * An iterator class for Text that are descendants of a given node, the
1110 * root. Nodes are iterated in document order. An optional subtree can be
1111 * specified, in which case the iterator will track whether the current state of
1112 * the traversal over the tree is within that subtree or is past that subtree.
1113 */
1114class TextNodeIterator {
1115 public:
1116 /**
1117 * Constructs a TextNodeIterator with the specified root node and optional
1118 * subtree.
1119 */
1120 explicit TextNodeIterator(nsIContent* aRoot, nsIContent* aSubtree = nullptr)
1121 : mRoot(aRoot),
1122 mSubtree(aSubtree == aRoot ? nullptr : aSubtree),
1123 mCurrent(aRoot),
1124 mSubtreePosition(mSubtree ? SubtreePosition::Before
1125 : SubtreePosition::Within) {
1126 NS_ASSERTION(aRoot, "expected non-null root")do { if (!(aRoot)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "expected non-null root"
, "aRoot", "./../../../layout/svg/SVGTextFrame.cpp", 1126); MOZ_PretendNoReturn
(); } } while (0)
;
1127 if (!aRoot->IsText()) {
1128 GetNext();
1129 }
1130 }
1131
1132 /**
1133 * Returns the current Text, or null if the iterator has finished.
1134 */
1135 Text* GetCurrent() const { return mCurrent ? mCurrent->AsText() : nullptr; }
1136
1137 /**
1138 * Advances to the next Text and returns it, or null if the end of
1139 * iteration has been reached.
1140 */
1141 Text* GetNext();
1142
1143 /**
1144 * Returns whether the iterator is currently within the subtree rooted
1145 * at mSubtree. Returns true if we are not tracking a subtree (we consider
1146 * that we're always within the subtree).
1147 */
1148 bool IsWithinSubtree() const {
1149 return mSubtreePosition == SubtreePosition::Within;
1150 }
1151
1152 /**
1153 * Returns whether the iterator is past the subtree rooted at mSubtree.
1154 * Returns false if we are not tracking a subtree.
1155 */
1156 bool IsAfterSubtree() const {
1157 return mSubtreePosition == SubtreePosition::After;
1158 }
1159
1160 private:
1161 /**
1162 * The root under which all Text will be iterated over.
1163 */
1164 nsIContent* const mRoot;
1165
1166 /**
1167 * The node rooting the subtree to track.
1168 */
1169 nsIContent* const mSubtree;
1170
1171 /**
1172 * The current node during iteration.
1173 */
1174 nsIContent* mCurrent;
1175
1176 /**
1177 * The current iterator position relative to mSubtree.
1178 */
1179 SubtreePosition mSubtreePosition;
1180};
1181
1182Text* TextNodeIterator::GetNext() {
1183 // Starting from mCurrent, we do a non-recursive traversal to the next
1184 // Text beneath mRoot, updating mSubtreePosition appropriately if we
1185 // encounter mSubtree.
1186 if (mCurrent) {
1187 do {
1188 nsIContent* next =
1189 IsTextContentElement(mCurrent) ? mCurrent->GetFirstChild() : nullptr;
1190 if (next) {
1191 mCurrent = next;
1192 if (mCurrent == mSubtree) {
1193 mSubtreePosition = SubtreePosition::Within;
1194 }
1195 } else {
1196 for (;;) {
1197 if (mCurrent == mRoot) {
1198 mCurrent = nullptr;
1199 break;
1200 }
1201 if (mCurrent == mSubtree) {
1202 mSubtreePosition = SubtreePosition::After;
1203 }
1204 next = mCurrent->GetNextSibling();
1205 if (next) {
1206 mCurrent = next;
1207 if (mCurrent == mSubtree) {
1208 mSubtreePosition = SubtreePosition::Within;
1209 }
1210 break;
1211 }
1212 if (mCurrent == mSubtree) {
1213 mSubtreePosition = SubtreePosition::After;
1214 }
1215 mCurrent = mCurrent->GetParent();
1216 }
1217 }
1218 } while (mCurrent && !mCurrent->IsText());
1219 }
1220
1221 return mCurrent ? mCurrent->AsText() : nullptr;
1222}
1223
1224// ----------------------------------------------------------------------------
1225// TextNodeCorrespondenceRecorder
1226
1227/**
1228 * TextNodeCorrespondence is used as the value of a frame property that
1229 * is stored on all its descendant nsTextFrames. It stores the number of DOM
1230 * characters between it and the previous nsTextFrame that did not have an
1231 * nsTextFrame created for them, due to either not being in a correctly
1232 * parented text content element, or because they were display:none.
1233 * These are called "undisplayed characters".
1234 *
1235 * See also TextNodeCorrespondenceRecorder below, which is what sets the
1236 * frame property.
1237 */
1238struct TextNodeCorrespondence {
1239 explicit TextNodeCorrespondence(uint32_t aUndisplayedCharacters)
1240 : mUndisplayedCharacters(aUndisplayedCharacters) {}
1241
1242 uint32_t mUndisplayedCharacters;
1243};
1244
1245NS_DECLARE_FRAME_PROPERTY_DELETABLE(TextNodeCorrespondenceProperty,static const mozilla::FramePropertyDescriptor<TextNodeCorrespondence
>* TextNodeCorrespondenceProperty() { static const auto descriptor
= mozilla::FramePropertyDescriptor<TextNodeCorrespondence
>::NewWithDestructor<DeleteValue>(); return &descriptor
; }
1246 TextNodeCorrespondence)static const mozilla::FramePropertyDescriptor<TextNodeCorrespondence
>* TextNodeCorrespondenceProperty() { static const auto descriptor
= mozilla::FramePropertyDescriptor<TextNodeCorrespondence
>::NewWithDestructor<DeleteValue>(); return &descriptor
; }
1247
1248/**
1249 * Returns the number of undisplayed characters before the specified
1250 * nsTextFrame.
1251 */
1252static uint32_t GetUndisplayedCharactersBeforeFrame(nsTextFrame* aFrame) {
1253 void* value = aFrame->GetProperty(TextNodeCorrespondenceProperty());
1254 TextNodeCorrespondence* correspondence =
1255 static_cast<TextNodeCorrespondence*>(value);
1256 if (!correspondence) {
1257 // FIXME bug 903785
1258 NS_ERROR(do { NS_DebugBreak(NS_DEBUG_ASSERTION, "expected a TextNodeCorrespondenceProperty on nsTextFrame "
"used for SVG text", "Error", "./../../../layout/svg/SVGTextFrame.cpp"
, 1260); MOZ_PretendNoReturn(); } while (0)
1259 "expected a TextNodeCorrespondenceProperty on nsTextFrame "do { NS_DebugBreak(NS_DEBUG_ASSERTION, "expected a TextNodeCorrespondenceProperty on nsTextFrame "
"used for SVG text", "Error", "./../../../layout/svg/SVGTextFrame.cpp"
, 1260); MOZ_PretendNoReturn(); } while (0)
1260 "used for SVG text")do { NS_DebugBreak(NS_DEBUG_ASSERTION, "expected a TextNodeCorrespondenceProperty on nsTextFrame "
"used for SVG text", "Error", "./../../../layout/svg/SVGTextFrame.cpp"
, 1260); MOZ_PretendNoReturn(); } while (0)
;
1261 return 0;
1262 }
1263 return correspondence->mUndisplayedCharacters;
1264}
1265
1266/**
1267 * Traverses the nsTextFrames for an SVGTextFrame and records a
1268 * TextNodeCorrespondenceProperty on each for the number of undisplayed DOM
1269 * characters between each frame. This is done by iterating simultaneously
1270 * over the Text and nsTextFrames and noting when Text (or
1271 * parts of them) are skipped when finding the next nsTextFrame.
1272 */
1273class TextNodeCorrespondenceRecorder {
1274 public:
1275 /**
1276 * Entry point for the TextNodeCorrespondenceProperty recording.
1277 */
1278 static void RecordCorrespondence(SVGTextFrame* aRoot);
1279
1280 private:
1281 explicit TextNodeCorrespondenceRecorder(SVGTextFrame* aRoot)
1282 : mNodeIterator(aRoot->GetContent()),
1283 mPreviousNode(nullptr),
1284 mNodeCharIndex(0) {}
1285
1286 void Record(SVGTextFrame* aRoot);
1287 void TraverseAndRecord(nsIFrame* aFrame);
1288
1289 /**
1290 * Returns the next non-empty Text.
1291 */
1292 Text* NextNode();
1293
1294 /**
1295 * The iterator over the Text that we use as we simultaneously
1296 * iterate over the nsTextFrames.
1297 */
1298 TextNodeIterator mNodeIterator;
1299
1300 /**
1301 * The previous Text we iterated over.
1302 */
1303 Text* mPreviousNode;
1304
1305 /**
1306 * The index into the current Text's character content.
1307 */
1308 uint32_t mNodeCharIndex;
1309};
1310
1311/* static */
1312void TextNodeCorrespondenceRecorder::RecordCorrespondence(SVGTextFrame* aRoot) {
1313 if (aRoot->HasAnyStateBits(NS_STATE_SVG_TEXT_CORRESPONDENCE_DIRTY)) {
1314 // Resolve bidi so that continuation frames are created if necessary:
1315 aRoot->MaybeResolveBidiForAnonymousBlockChild();
1316 TextNodeCorrespondenceRecorder recorder(aRoot);
1317 recorder.Record(aRoot);
1318 aRoot->RemoveStateBits(NS_STATE_SVG_TEXT_CORRESPONDENCE_DIRTY);
1319 }
1320}
1321
1322void TextNodeCorrespondenceRecorder::Record(SVGTextFrame* aRoot) {
1323 if (!mNodeIterator.GetCurrent()) {
1324 // If there are no Text nodes then there is nothing to do.
1325 return;
1326 }
1327
1328 // Traverse over all the nsTextFrames and record the number of undisplayed
1329 // characters.
1330 TraverseAndRecord(aRoot);
1331
1332 // Find how many undisplayed characters there are after the final nsTextFrame.
1333 uint32_t undisplayed = 0;
1334 if (mNodeIterator.GetCurrent()) {
1335 if (mPreviousNode && mPreviousNode->TextLength() != mNodeCharIndex) {
1336 // The last nsTextFrame ended part way through a Text node. The
1337 // remaining characters count as undisplayed.
1338 NS_ASSERTION(mNodeCharIndex < mPreviousNode->TextLength(),do { if (!(mNodeCharIndex < mPreviousNode->TextLength()
)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "incorrect tracking of undisplayed characters in "
"text nodes", "mNodeCharIndex < mPreviousNode->TextLength()"
, "./../../../layout/svg/SVGTextFrame.cpp", 1340); MOZ_PretendNoReturn
(); } } while (0)
1339 "incorrect tracking of undisplayed characters in "do { if (!(mNodeCharIndex < mPreviousNode->TextLength()
)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "incorrect tracking of undisplayed characters in "
"text nodes", "mNodeCharIndex < mPreviousNode->TextLength()"
, "./../../../layout/svg/SVGTextFrame.cpp", 1340); MOZ_PretendNoReturn
(); } } while (0)
1340 "text nodes")do { if (!(mNodeCharIndex < mPreviousNode->TextLength()
)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "incorrect tracking of undisplayed characters in "
"text nodes", "mNodeCharIndex < mPreviousNode->TextLength()"
, "./../../../layout/svg/SVGTextFrame.cpp", 1340); MOZ_PretendNoReturn
(); } } while (0)
;
1341 undisplayed += mPreviousNode->TextLength() - mNodeCharIndex;
1342 }
1343 // All the remaining Text that we iterate must also be undisplayed.
1344 for (Text* textNode = mNodeIterator.GetCurrent(); textNode;
1345 textNode = NextNode()) {
1346 undisplayed += textNode->TextLength();
1347 }
1348 }
1349
1350 // Record the trailing number of undisplayed characters on the
1351 // SVGTextFrame.
1352 aRoot->mTrailingUndisplayedCharacters = undisplayed;
1353}
1354
1355Text* TextNodeCorrespondenceRecorder::NextNode() {
1356 mPreviousNode = mNodeIterator.GetCurrent();
1357 Text* next;
1358 do {
1359 next = mNodeIterator.GetNext();
1360 } while (next && next->TextLength() == 0);
1361 return next;
1362}
1363
1364void TextNodeCorrespondenceRecorder::TraverseAndRecord(nsIFrame* aFrame) {
1365 // Recursively iterate over the frame tree, for frames that correspond
1366 // to text content elements.
1367 if (IsTextContentElement(aFrame->GetContent())) {
1368 for (nsIFrame* f : aFrame->PrincipalChildList()) {
1369 TraverseAndRecord(f);
1370 }
1371 return;
1372 }
1373
1374 nsTextFrame* frame; // The current text frame.
1375 Text* node; // The text node for the current text frame.
1376 if (!GetNonEmptyTextFrameAndNode(aFrame, frame, node)) {
1377 // If this isn't an nsTextFrame, or is empty, nothing to do.
1378 return;
1379 }
1380
1381 NS_ASSERTION(frame->GetContentOffset() >= 0,do { if (!(frame->GetContentOffset() >= 0)) { NS_DebugBreak
(NS_DEBUG_ASSERTION, "don't know how to handle negative content indexes"
, "frame->GetContentOffset() >= 0", "./../../../layout/svg/SVGTextFrame.cpp"
, 1382); MOZ_PretendNoReturn(); } } while (0)
1382 "don't know how to handle negative content indexes")do { if (!(frame->GetContentOffset() >= 0)) { NS_DebugBreak
(NS_DEBUG_ASSERTION, "don't know how to handle negative content indexes"
, "frame->GetContentOffset() >= 0", "./../../../layout/svg/SVGTextFrame.cpp"
, 1382); MOZ_PretendNoReturn(); } } while (0)
;
1383
1384 uint32_t undisplayed = 0;
1385 if (!mPreviousNode) {
1386 // Must be the very first text frame.
1387 NS_ASSERTION(mNodeCharIndex == 0,do { if (!(mNodeCharIndex == 0)) { NS_DebugBreak(NS_DEBUG_ASSERTION
, "incorrect tracking of undisplayed " "characters in text nodes"
, "mNodeCharIndex == 0", "./../../../layout/svg/SVGTextFrame.cpp"
, 1389); MOZ_PretendNoReturn(); } } while (0)
1388 "incorrect tracking of undisplayed "do { if (!(mNodeCharIndex == 0)) { NS_DebugBreak(NS_DEBUG_ASSERTION
, "incorrect tracking of undisplayed " "characters in text nodes"
, "mNodeCharIndex == 0", "./../../../layout/svg/SVGTextFrame.cpp"
, 1389); MOZ_PretendNoReturn(); } } while (0)
1389 "characters in text nodes")do { if (!(mNodeCharIndex == 0)) { NS_DebugBreak(NS_DEBUG_ASSERTION
, "incorrect tracking of undisplayed " "characters in text nodes"
, "mNodeCharIndex == 0", "./../../../layout/svg/SVGTextFrame.cpp"
, 1389); MOZ_PretendNoReturn(); } } while (0)
;
1390 if (!mNodeIterator.GetCurrent()) {
1391 MOZ_ASSERT_UNREACHABLE(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: "
"incorrect tracking of correspondence between " "text frames and text nodes"
")", "./../../../layout/svg/SVGTextFrame.cpp", 1393); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "false" ") (" "MOZ_ASSERT_UNREACHABLE: " "incorrect tracking of correspondence between "
"text frames and text nodes" ")"); do { MOZ_CrashSequence(__null
, 1393); __attribute__((nomerge)) ::abort(); } while (false);
} } while (false)
1392 "incorrect tracking of correspondence between "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: "
"incorrect tracking of correspondence between " "text frames and text nodes"
")", "./../../../layout/svg/SVGTextFrame.cpp", 1393); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "false" ") (" "MOZ_ASSERT_UNREACHABLE: " "incorrect tracking of correspondence between "
"text frames and text nodes" ")"); do { MOZ_CrashSequence(__null
, 1393); __attribute__((nomerge)) ::abort(); } while (false);
} } while (false)
1393 "text frames and text nodes")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: "
"incorrect tracking of correspondence between " "text frames and text nodes"
")", "./../../../layout/svg/SVGTextFrame.cpp", 1393); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "false" ") (" "MOZ_ASSERT_UNREACHABLE: " "incorrect tracking of correspondence between "
"text frames and text nodes" ")"); do { MOZ_CrashSequence(__null
, 1393); __attribute__((nomerge)) ::abort(); } while (false);
} } while (false)
;
1394 } else {
1395 // Each whole Text we find before we get to the text node for the
1396 // first text frame must be undisplayed.
1397 while (mNodeIterator.GetCurrent() != node) {
1398 undisplayed += mNodeIterator.GetCurrent()->TextLength();
1399 NextNode();
1400 }
1401 // If the first text frame starts at a non-zero content offset, then those
1402 // earlier characters are also undisplayed.
1403 undisplayed += frame->GetContentOffset();
1404 NextNode();
1405 }
1406 } else if (mPreviousNode == node) {
1407 // Same text node as last time.
1408 if (static_cast<uint32_t>(frame->GetContentOffset()) != mNodeCharIndex) {
1409 // We have some characters in the middle of the text node
1410 // that are undisplayed.
1411 NS_ASSERTION(do { if (!(mNodeCharIndex < static_cast<uint32_t>(frame
->GetContentOffset()))) { NS_DebugBreak(NS_DEBUG_ASSERTION
, "incorrect tracking of undisplayed characters in " "text nodes"
, "mNodeCharIndex < static_cast<uint32_t>(frame->GetContentOffset())"
, "./../../../layout/svg/SVGTextFrame.cpp", 1414); MOZ_PretendNoReturn
(); } } while (0)
1412 mNodeCharIndex < static_cast<uint32_t>(frame->GetContentOffset()),do { if (!(mNodeCharIndex < static_cast<uint32_t>(frame
->GetContentOffset()))) { NS_DebugBreak(NS_DEBUG_ASSERTION
, "incorrect tracking of undisplayed characters in " "text nodes"
, "mNodeCharIndex < static_cast<uint32_t>(frame->GetContentOffset())"
, "./../../../layout/svg/SVGTextFrame.cpp", 1414); MOZ_PretendNoReturn
(); } } while (0)
1413 "incorrect tracking of undisplayed characters in "do { if (!(mNodeCharIndex < static_cast<uint32_t>(frame
->GetContentOffset()))) { NS_DebugBreak(NS_DEBUG_ASSERTION
, "incorrect tracking of undisplayed characters in " "text nodes"
, "mNodeCharIndex < static_cast<uint32_t>(frame->GetContentOffset())"
, "./../../../layout/svg/SVGTextFrame.cpp", 1414); MOZ_PretendNoReturn
(); } } while (0)
1414 "text nodes")do { if (!(mNodeCharIndex < static_cast<uint32_t>(frame
->GetContentOffset()))) { NS_DebugBreak(NS_DEBUG_ASSERTION
, "incorrect tracking of undisplayed characters in " "text nodes"
, "mNodeCharIndex < static_cast<uint32_t>(frame->GetContentOffset())"
, "./../../../layout/svg/SVGTextFrame.cpp", 1414); MOZ_PretendNoReturn
(); } } while (0)
;
1415 undisplayed = frame->GetContentOffset() - mNodeCharIndex;
1416 }
1417 } else {
1418 // Different text node from last time.
1419 if (mPreviousNode->TextLength() != mNodeCharIndex) {
1420 NS_ASSERTION(mNodeCharIndex < mPreviousNode->TextLength(),do { if (!(mNodeCharIndex < mPreviousNode->TextLength()
)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "incorrect tracking of undisplayed characters in "
"text nodes", "mNodeCharIndex < mPreviousNode->TextLength()"
, "./../../../layout/svg/SVGTextFrame.cpp", 1422); MOZ_PretendNoReturn
(); } } while (0)
1421 "incorrect tracking of undisplayed characters in "do { if (!(mNodeCharIndex < mPreviousNode->TextLength()
)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "incorrect tracking of undisplayed characters in "
"text nodes", "mNodeCharIndex < mPreviousNode->TextLength()"
, "./../../../layout/svg/SVGTextFrame.cpp", 1422); MOZ_PretendNoReturn
(); } } while (0)
1422 "text nodes")do { if (!(mNodeCharIndex < mPreviousNode->TextLength()
)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "incorrect tracking of undisplayed characters in "
"text nodes", "mNodeCharIndex < mPreviousNode->TextLength()"
, "./../../../layout/svg/SVGTextFrame.cpp", 1422); MOZ_PretendNoReturn
(); } } while (0)
;
1423 // Any trailing characters at the end of the previous Text are
1424 // undisplayed.
1425 undisplayed = mPreviousNode->TextLength() - mNodeCharIndex;
1426 }
1427 // Each whole Text we find before we get to the text node for
1428 // the current text frame must be undisplayed.
1429 while (mNodeIterator.GetCurrent() && mNodeIterator.GetCurrent() != node) {
1430 undisplayed += mNodeIterator.GetCurrent()->TextLength();
1431 NextNode();
1432 }
1433 // If the current text frame starts at a non-zero content offset, then those
1434 // earlier characters are also undisplayed.
1435 undisplayed += frame->GetContentOffset();
1436 NextNode();
1437 }
1438
1439 // Set the frame property.
1440 frame->SetProperty(TextNodeCorrespondenceProperty(),
1441 new TextNodeCorrespondence(undisplayed));
1442
1443 // Remember how far into the current Text we are.
1444 mNodeCharIndex = frame->GetContentEnd();
1445}
1446
1447// ----------------------------------------------------------------------------
1448// TextFrameIterator
1449
1450/**
1451 * An iterator class for nsTextFrames that are descendants of an
1452 * SVGTextFrame. The iterator can optionally track whether the
1453 * current nsTextFrame is for a descendant of, or past, a given subtree
1454 * content node or frame. (This functionality is used for example by the SVG
1455 * DOM text methods to get only the nsTextFrames for a particular <tspan>.)
1456 *
1457 * TextFrameIterator also tracks and exposes other information about the
1458 * current nsTextFrame:
1459 *
1460 * * how many undisplayed characters came just before it
1461 * * its position (in app units) relative to the SVGTextFrame's anonymous
1462 * block frame
1463 * * what nsInlineFrame corresponding to a <textPath> element it is a
1464 * descendant of
1465 * * what computed dominant-baseline value applies to it
1466 *
1467 * Note that any text frames that are empty -- whose ContentLength() is 0 --
1468 * will be skipped over.
1469 */
1470class MOZ_STACK_CLASS TextFrameIterator {
1471 public:
1472 /**
1473 * Constructs a TextFrameIterator for the specified SVGTextFrame
1474 * with an optional frame subtree to restrict iterated text frames to.
1475 */
1476 explicit TextFrameIterator(SVGTextFrame* aRoot,
1477 const nsIFrame* aSubtree = nullptr)
1478 : mRootFrame(aRoot), mCurrentFrame(aRoot) {
1479 Init(aSubtree);
1480 }
1481
1482 /**
1483 * Constructs a TextFrameIterator for the specified SVGTextFrame
1484 * with an optional frame content subtree to restrict iterated text frames to.
1485 */
1486 TextFrameIterator(SVGTextFrame* aRoot, const nsIContent* aSubtree)
1487 : mRootFrame(aRoot), mCurrentFrame(aRoot) {
1488 Init(aRoot && aSubtree && aSubtree != aRoot->GetContent()
1489 ? aSubtree->GetPrimaryFrame()
1490 : nullptr);
1491 }
1492
1493 /**
1494 * Returns the root SVGTextFrame this TextFrameIterator is iterating over.
1495 * (May be nullptr, if nullptr was passed to our constructor.)
1496 */
1497 SVGTextFrame* GetRoot() const { return mRootFrame; }
1498
1499 /**
1500 * Returns the current nsTextFrame, or null if the iterator has finished.
1501 */
1502 nsTextFrame* GetCurrent() const { return do_QueryFrame(mCurrentFrame); }
1503
1504 /**
1505 * Returns the number of undisplayed characters in the DOM just before the
1506 * current frame.
1507 */
1508 uint32_t UndisplayedCharacters() const;
1509
1510 /**
1511 * Returns the current frame's position, in app units, relative to the
1512 * root SVGTextFrame's anonymous block frame.
1513 */
1514 nsPoint Position() const { return mCurrentPosition; }
1515
1516 /**
1517 * Advances to the next nsTextFrame and returns it, or null if the end of
1518 * iteration has been reached.
1519 */
1520 nsTextFrame* GetNext();
1521
1522 /**
1523 * Returns whether the iterator is within the subtree.
1524 */
1525 bool IsWithinSubtree() const {
1526 return mSubtreePosition == SubtreePosition::Within;
1527 }
1528
1529 /**
1530 * Returns whether the iterator is past the subtree.
1531 */
1532 bool IsAfterSubtree() const {
1533 return mSubtreePosition == SubtreePosition::After;
1534 }
1535
1536 /**
1537 * Returns the frame corresponding to the <textPath> element, if we
1538 * are inside one.
1539 */
1540 nsIFrame* TextPathFrame() const {
1541 return mTextPathFrames.IsEmpty() ? nullptr : mTextPathFrames.LastElement();
1542 }
1543
1544 /**
1545 * Returns the current frame's computed dominant-baseline value.
1546 */
1547 StyleDominantBaseline DominantBaseline() const {
1548 return mBaselines.LastElement();
1549 }
1550
1551 /**
1552 * Finishes the iterator.
1553 */
1554 void Close() { mCurrentFrame = nullptr; }
1555
1556 private:
1557 /**
1558 * Initializes the iterator and advances to the first item.
1559 */
1560 void Init(const nsIFrame* aSubtree) {
1561 for (const nsIFrame* f = aSubtree; f; f = f->GetNextContinuation()) {
1562 mSubtreeRoot.Insert(f);
1563 }
1564 mSubtreePosition =
1565 aSubtree ? SubtreePosition::Before : SubtreePosition::Within;
1566 if (!mRootFrame) {
1567 return;
1568 }
1569
1570 mBaselines.AppendElement(mRootFrame->StyleVisibility()->mDominantBaseline);
1571 GetNext();
1572 }
1573
1574 /**
1575 * Pushes the specified frame's computed dominant-baseline value.
1576 * If the value of the property is "auto", then the parent frame's
1577 * computed value is used.
1578 */
1579 void PushBaseline(nsIFrame* aNextFrame);
1580
1581 /**
1582 * Pops the current dominant-baseline off the stack.
1583 */
1584 void PopBaseline();
1585
1586 /**
1587 * The root frame we are iterating through.
1588 */
1589 SVGTextFrame* const mRootFrame;
1590
1591 /**
1592 * The root frame of any subtree we are interested in tracking
1593 * and its following continuations, if any.
1594 */
1595 nsTHashSet<const nsIFrame*> mSubtreeRoot;
1596
1597 /**
1598 * The current value of the iterator.
1599 */
1600 nsIFrame* mCurrentFrame;
1601
1602 /**
1603 * The position, in app units, of the current frame relative to mRootFrame.
1604 */
1605 nsPoint mCurrentPosition;
1606
1607 /**
1608 * Stack of frames corresponding to <textPath> elements that are in scope
1609 * for the current frame.
1610 */
1611 AutoTArray<nsIFrame*, 1> mTextPathFrames;
1612
1613 /**
1614 * Stack of dominant-baseline values to record as we traverse through the
1615 * frame tree.
1616 */
1617 AutoTArray<StyleDominantBaseline, 8> mBaselines;
1618
1619 /**
1620 * The iterator's current position relative to the subtree.
1621 */
1622 SubtreePosition mSubtreePosition;
1623};
1624
1625uint32_t TextFrameIterator::UndisplayedCharacters() const {
1626 MOZ_ASSERT(do { static_assert( mozilla::detail::AssertionConditionType<
decltype(!mRootFrame->HasAnyStateBits(NS_STATE_SVG_TEXT_CORRESPONDENCE_DIRTY
))>::isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(!mRootFrame->HasAnyStateBits(NS_STATE_SVG_TEXT_CORRESPONDENCE_DIRTY
)))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("!mRootFrame->HasAnyStateBits(NS_STATE_SVG_TEXT_CORRESPONDENCE_DIRTY)"
" (" "Text correspondence must be up to date" ")", "./../../../layout/svg/SVGTextFrame.cpp"
, 1628); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!mRootFrame->HasAnyStateBits(NS_STATE_SVG_TEXT_CORRESPONDENCE_DIRTY)"
") (" "Text correspondence must be up to date" ")"); do { MOZ_CrashSequence
(__null, 1628); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
1627 !mRootFrame->HasAnyStateBits(NS_STATE_SVG_TEXT_CORRESPONDENCE_DIRTY),do { static_assert( mozilla::detail::AssertionConditionType<
decltype(!mRootFrame->HasAnyStateBits(NS_STATE_SVG_TEXT_CORRESPONDENCE_DIRTY
))>::isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(!mRootFrame->HasAnyStateBits(NS_STATE_SVG_TEXT_CORRESPONDENCE_DIRTY
)))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("!mRootFrame->HasAnyStateBits(NS_STATE_SVG_TEXT_CORRESPONDENCE_DIRTY)"
" (" "Text correspondence must be up to date" ")", "./../../../layout/svg/SVGTextFrame.cpp"
, 1628); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!mRootFrame->HasAnyStateBits(NS_STATE_SVG_TEXT_CORRESPONDENCE_DIRTY)"
") (" "Text correspondence must be up to date" ")"); do { MOZ_CrashSequence
(__null, 1628); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
1628 "Text correspondence must be up to date")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(!mRootFrame->HasAnyStateBits(NS_STATE_SVG_TEXT_CORRESPONDENCE_DIRTY
))>::isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(!mRootFrame->HasAnyStateBits(NS_STATE_SVG_TEXT_CORRESPONDENCE_DIRTY
)))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("!mRootFrame->HasAnyStateBits(NS_STATE_SVG_TEXT_CORRESPONDENCE_DIRTY)"
" (" "Text correspondence must be up to date" ")", "./../../../layout/svg/SVGTextFrame.cpp"
, 1628); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!mRootFrame->HasAnyStateBits(NS_STATE_SVG_TEXT_CORRESPONDENCE_DIRTY)"
") (" "Text correspondence must be up to date" ")"); do { MOZ_CrashSequence
(__null, 1628); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
1629
1630 if (!mCurrentFrame) {
1631 return mRootFrame->mTrailingUndisplayedCharacters;
1632 }
1633
1634 nsTextFrame* frame = do_QueryFrame(mCurrentFrame);
1635 return GetUndisplayedCharactersBeforeFrame(frame);
1636}
1637
1638nsTextFrame* TextFrameIterator::GetNext() {
1639 // Starting from mCurrentFrame, we do a non-recursive traversal to the next
1640 // nsTextFrame beneath mRoot, updating mSubtreePosition appropriately if we
1641 // encounter mSubtreeRoot.
1642 if (mCurrentFrame) {
1643 do {
1644 nsIFrame* next = IsTextContentElement(mCurrentFrame->GetContent())
1645 ? mCurrentFrame->PrincipalChildList().FirstChild()
1646 : nullptr;
1647 if (next) {
1648 // Descend into this frame, and accumulate its position.
1649 mCurrentPosition += next->GetPosition();
1650 if (next->GetContent()->IsSVGElement(nsGkAtoms::textPath)) {
1651 // Record this <textPath> frame.
1652 mTextPathFrames.AppendElement(next);
1653 }
1654 // Record the frame's baseline.
1655 PushBaseline(next);
1656 mCurrentFrame = next;
1657 if (mSubtreeRoot.Contains(mCurrentFrame)) {
1658 // If the current frame within mSubtreeRoot, we have now moved into
1659 // it.
1660 mSubtreePosition = SubtreePosition::Within;
1661 }
1662 } else {
1663 for (;;) {
1664 // We want to move past the current frame.
1665 if (mCurrentFrame == mRootFrame) {
1666 // If we've reached the root frame, we're finished.
1667 mCurrentFrame = nullptr;
1668 break;
1669 }
1670 // Remove the current frame's position.
1671 mCurrentPosition -= mCurrentFrame->GetPosition();
1672 if (mCurrentFrame->GetContent()->IsSVGElement(nsGkAtoms::textPath)) {
1673 // Pop off the <textPath> frame if this is a <textPath>.
1674 mTextPathFrames.RemoveLastElement();
1675 }
1676 // Pop off the current baseline.
1677 PopBaseline();
1678 if (mSubtreeRoot.Contains(mCurrentFrame)) {
1679 // If this was within mSubtreeRoot, we have now moved past it.
1680 mSubtreePosition = SubtreePosition::After;
1681 }
1682 next = mCurrentFrame->GetNextSibling();
1683 if (next) {
1684 // Moving to the next sibling.
1685 mCurrentPosition += next->GetPosition();
1686 if (next->GetContent()->IsSVGElement(nsGkAtoms::textPath)) {
1687 // Record this <textPath> frame.
1688 mTextPathFrames.AppendElement(next);
1689 }
1690 // Record the frame's baseline.
1691 PushBaseline(next);
1692 mCurrentFrame = next;
1693 if (mSubtreeRoot.Contains(mCurrentFrame)) {
1694 // If the current frame is within mSubtreeRoot, we have now moved
1695 // into it.
1696 mSubtreePosition = SubtreePosition::Within;
1697 }
1698 break;
1699 }
1700 if (mSubtreeRoot.Contains(mCurrentFrame)) {
1701 // If there is no next sibling frame, and the current frame is
1702 // within mSubtreeRoot, we have now moved past it.
1703 mSubtreePosition = SubtreePosition::After;
1704 }
1705 // Ascend out of this frame.
1706 mCurrentFrame = mCurrentFrame->GetParent();
1707 }
1708 }
1709 } while (mCurrentFrame && !IsNonEmptyTextFrame(mCurrentFrame));
1710 }
1711
1712 return GetCurrent();
1713}
1714
1715void TextFrameIterator::PushBaseline(nsIFrame* aNextFrame) {
1716 StyleDominantBaseline baseline =
1717 aNextFrame->StyleVisibility()->mDominantBaseline;
1718 mBaselines.AppendElement(baseline);
1719}
1720
1721void TextFrameIterator::PopBaseline() {
1722 NS_ASSERTION(!mBaselines.IsEmpty(), "popped too many baselines")do { if (!(!mBaselines.IsEmpty())) { NS_DebugBreak(NS_DEBUG_ASSERTION
, "popped too many baselines", "!mBaselines.IsEmpty()", "./../../../layout/svg/SVGTextFrame.cpp"
, 1722); MOZ_PretendNoReturn(); } } while (0)
;
1723 mBaselines.RemoveLastElement();
1724}
1725
1726// -----------------------------------------------------------------------------
1727// TextRenderedRunIterator
1728
1729/**
1730 * Iterator for TextRenderedRun objects for the SVGTextFrame.
1731 */
1732class TextRenderedRunIterator {
1733 public:
1734 /**
1735 * Values for the aFilter argument of the constructor, to indicate which
1736 * frames we should be limited to iterating TextRenderedRun objects for.
1737 */
1738 enum class RenderedRunFilter {
1739 // Iterate TextRenderedRuns for all nsTextFrames.
1740 AllFrames,
1741 // Iterate only TextRenderedRuns for nsTextFrames that are
1742 // visibility:visible.
1743 VisibleFrames
1744 };
1745
1746 /**
1747 * Constructs a TextRenderedRunIterator with an optional frame subtree to
1748 * restrict iterated rendered runs to.
1749 *
1750 * @param aSVGTextFrame The SVGTextFrame whose rendered runs to iterate
1751 * through.
1752 * @param aFilter Indicates whether to iterate rendered runs for non-visible
1753 * nsTextFrames.
1754 * @param aSubtree An optional frame subtree to restrict iterated rendered
1755 * runs to.
1756 */
1757 explicit TextRenderedRunIterator(
1758 SVGTextFrame* aSVGTextFrame,
1759 RenderedRunFilter aFilter = RenderedRunFilter::AllFrames,
1760 const nsIFrame* aSubtree = nullptr)
1761 : mFrameIterator(FrameIfAnonymousChildReflowed(aSVGTextFrame), aSubtree),
1762 mFilter(aFilter),
1763 mTextElementCharIndex(0),
1764 mFrameStartTextElementCharIndex(0),
1765 mFontSizeScaleFactor(aSVGTextFrame->mFontSizeScaleFactor),
1766 mCurrent(First()) {}
1767
1768 /**
1769 * Constructs a TextRenderedRunIterator with a content subtree to restrict
1770 * iterated rendered runs to.
1771 *
1772 * @param aSVGTextFrame The SVGTextFrame whose rendered runs to iterate
1773 * through.
1774 * @param aFilter Indicates whether to iterate rendered runs for non-visible
1775 * nsTextFrames.
1776 * @param aSubtree A content subtree to restrict iterated rendered runs to.
1777 */
1778 TextRenderedRunIterator(SVGTextFrame* aSVGTextFrame,
1779 RenderedRunFilter aFilter, nsIContent* aSubtree)
1780 : mFrameIterator(FrameIfAnonymousChildReflowed(aSVGTextFrame), aSubtree),
1781 mFilter(aFilter),
1782 mTextElementCharIndex(0),
1783 mFrameStartTextElementCharIndex(0),
1784 mFontSizeScaleFactor(aSVGTextFrame->mFontSizeScaleFactor),
1785 mCurrent(First()) {}
1786
1787 /**
1788 * Ensure any cached PropertyProvider is cleared at the end of the iteration.
1789 */
1790 ~TextRenderedRunIterator() {
1791 if (auto* root = mFrameIterator.GetRoot()) {
1792 root->ForgetCachedProvider();
1793 }
1794 }
1795
1796 /**
1797 * Returns the current TextRenderedRun.
1798 */
1799 TextRenderedRun Current() const { return mCurrent; }
1800
1801 /**
1802 * Advances to the next TextRenderedRun and returns it.
1803 */
1804 TextRenderedRun Next();
1805
1806 private:
1807 /**
1808 * Returns the root SVGTextFrame this iterator is for.
1809 */
1810 SVGTextFrame* GetRoot() const { return mFrameIterator.GetRoot(); }
1811
1812 /**
1813 * Advances to the first TextRenderedRun and returns it.
1814 */
1815 TextRenderedRun First();
1816
1817 /**
1818 * The frame iterator to use.
1819 */
1820 TextFrameIterator mFrameIterator;
1821
1822 /**
1823 * The filter indicating which TextRenderedRuns to return.
1824 */
1825 RenderedRunFilter mFilter;
1826
1827 /**
1828 * The character index across the entire <text> element we are currently
1829 * up to.
1830 */
1831 uint32_t mTextElementCharIndex;
1832
1833 /**
1834 * The character index across the entire <text> for the start of the current
1835 * frame.
1836 */
1837 uint32_t mFrameStartTextElementCharIndex;
1838
1839 /**
1840 * The font-size scale factor we used when constructing the nsTextFrames.
1841 */
1842 double mFontSizeScaleFactor;
1843
1844 /**
1845 * The current TextRenderedRun.
1846 */
1847 TextRenderedRun mCurrent;
1848};
1849
1850TextRenderedRun TextRenderedRunIterator::Next() {
1851 if (!mFrameIterator.GetCurrent()) {
1852 // If there are no more frames, then there are no more rendered runs to
1853 // return.
1854 mCurrent = TextRenderedRun();
1855 return mCurrent;
1856 }
1857
1858 // The values we will use to initialize the TextRenderedRun with.
1859 nsTextFrame* frame;
1860 gfxPoint pt;
1861 double rotate;
1862 nscoord baseline;
1863 uint32_t offset, length;
1864 uint32_t charIndex;
1865
1866 // We loop, because we want to skip over rendered runs that either aren't
1867 // within our subtree of interest, because they don't match the filter,
1868 // or because they are hidden due to having fallen off the end of a
1869 // <textPath>.
1870 for (;;) {
1871 if (mFrameIterator.IsAfterSubtree()) {
1872 mCurrent = TextRenderedRun();
1873 return mCurrent;
1874 }
1875
1876 frame = mFrameIterator.GetCurrent();
1877
1878 charIndex = mTextElementCharIndex;
1879
1880 // Find the end of the rendered run, by looking through the
1881 // SVGTextFrame's positions array until we find one that is recorded
1882 // as a run boundary.
1883 uint32_t runStart,
1884 runEnd; // XXX Replace runStart with mTextElementCharIndex.
1885 runStart = mTextElementCharIndex;
1886 runEnd = runStart + 1;
1887 while (runEnd < GetRoot()->mPositions.Length() &&
1888 !GetRoot()->mPositions[runEnd].mRunBoundary) {
1889 runEnd++;
1890 }
1891
1892 // Convert the global run start/end indexes into an offset/length into the
1893 // current frame's Text.
1894 offset =
1895 frame->GetContentOffset() + runStart - mFrameStartTextElementCharIndex;
1896 length = runEnd - runStart;
1897
1898 // If the end of the frame's content comes before the run boundary we found
1899 // in SVGTextFrame's position array, we need to shorten the rendered run.
1900 uint32_t contentEnd = frame->GetContentEnd();
1901 if (offset + length > contentEnd) {
1902 length = contentEnd - offset;
1903 }
1904
1905 NS_ASSERTION(offset >= uint32_t(frame->GetContentOffset()),do { if (!(offset >= uint32_t(frame->GetContentOffset()
))) { NS_DebugBreak(NS_DEBUG_ASSERTION, "invalid offset", "offset >= uint32_t(frame->GetContentOffset())"
, "./../../../layout/svg/SVGTextFrame.cpp", 1906); MOZ_PretendNoReturn
(); } } while (0)
1906 "invalid offset")do { if (!(offset >= uint32_t(frame->GetContentOffset()
))) { NS_DebugBreak(NS_DEBUG_ASSERTION, "invalid offset", "offset >= uint32_t(frame->GetContentOffset())"
, "./../../../layout/svg/SVGTextFrame.cpp", 1906); MOZ_PretendNoReturn
(); } } while (0)
;
1907 NS_ASSERTION(offset + length <= contentEnd, "invalid offset or length")do { if (!(offset + length <= contentEnd)) { NS_DebugBreak
(NS_DEBUG_ASSERTION, "invalid offset or length", "offset + length <= contentEnd"
, "./../../../layout/svg/SVGTextFrame.cpp", 1907); MOZ_PretendNoReturn
(); } } while (0)
;
1908
1909 // Get the frame's baseline position.
1910 frame->EnsureTextRun(nsTextFrame::eInflated);
1911 baseline = GetBaselinePosition(
1912 frame, frame->GetTextRun(nsTextFrame::eInflated),
1913 mFrameIterator.DominantBaseline(), mFontSizeScaleFactor);
1914
1915 // Trim the offset/length to remove any leading/trailing white space.
1916 uint32_t untrimmedOffset = offset;
1917 uint32_t untrimmedLength = length;
1918 nsTextFrame::TrimmedOffsets trimmedOffsets =
1919 frame->GetTrimmedOffsets(frame->CharacterDataBuffer());
1920 TrimOffsets(offset, length, trimmedOffsets);
1921 charIndex += offset - untrimmedOffset;
1922
1923 // Get the position and rotation of the character that begins this
1924 // rendered run.
1925 pt = GetRoot()->mPositions[charIndex].mPosition;
1926 rotate = GetRoot()->mPositions[charIndex].mAngle;
1927
1928 // Determine if we should skip this rendered run.
1929 bool skip = !mFrameIterator.IsWithinSubtree() ||
1930 GetRoot()->mPositions[mTextElementCharIndex].mHidden;
1931 if (mFilter == RenderedRunFilter::VisibleFrames) {
1932 skip = skip || !frame->StyleVisibility()->IsVisible();
1933 }
1934
1935 // Update our global character index to move past the characters
1936 // corresponding to this rendered run.
1937 mTextElementCharIndex += untrimmedLength;
1938
1939 // If we have moved past the end of the current frame's content, we need to
1940 // advance to the next frame.
1941 if (offset + untrimmedLength >= contentEnd) {
1942 mFrameIterator.GetNext();
1943 mTextElementCharIndex += mFrameIterator.UndisplayedCharacters();
1944 mFrameStartTextElementCharIndex = mTextElementCharIndex;
1945 }
1946
1947 if (!mFrameIterator.GetCurrent()) {
1948 if (skip) {
1949 // That was the last frame, and we skipped this rendered run. So we
1950 // have no rendered run to return.
1951 mCurrent = TextRenderedRun();
1952 return mCurrent;
1953 }
1954 break;
1955 }
1956
1957 if (length && !skip) {
1958 // Only return a rendered run if it didn't get collapsed away entirely
1959 // (due to it being all white space) and if we don't want to skip it.
1960 break;
1961 }
1962 }
1963
1964 mCurrent = TextRenderedRun(frame, GetRoot(), pt, rotate, mFontSizeScaleFactor,
1965 baseline, offset, length, charIndex);
1966 return mCurrent;
1967}
1968
1969TextRenderedRun TextRenderedRunIterator::First() {
1970 if (!mFrameIterator.GetCurrent()) {
1971 return TextRenderedRun();
1972 }
1973
1974 if (GetRoot()->mPositions.IsEmpty()) {
1975 mFrameIterator.Close();
1976 return TextRenderedRun();
1977 }
1978
1979 // Get the character index for the start of this rendered run, by skipping
1980 // any undisplayed characters.
1981 mTextElementCharIndex = mFrameIterator.UndisplayedCharacters();
1982 mFrameStartTextElementCharIndex = mTextElementCharIndex;
1983
1984 return Next();
1985}
1986
1987// -----------------------------------------------------------------------------
1988// CharIterator
1989
1990/**
1991 * Iterator for characters within an SVGTextFrame.
1992 */
1993class MOZ_STACK_CLASS CharIterator {
1994 using Range = gfxTextRun::Range;
1995
1996 public:
1997 /**
1998 * Values for the aFilter argument of the constructor, to indicate which
1999 * characters we should be iterating over.
2000 */
2001 enum class CharacterFilter {
2002 // Iterate over all original characters from the DOM that are within valid
2003 // text content elements.
2004 Original,
2005 // Iterate only over characters that are not skipped characters.
2006 Unskipped,
2007 // Iterate only over characters that are addressable by the positioning
2008 // attributes x="", y="", etc. This includes all characters after
2009 // collapsing white space as required by the value of 'white-space'.
2010 Addressable,
2011 };
2012
2013 /**
2014 * Constructs a CharIterator.
2015 *
2016 * @param aSVGTextFrame The SVGTextFrame whose characters to iterate
2017 * through.
2018 * @param aFilter Indicates which characters to iterate over.
2019 * @param aSubtree A content subtree to track whether the current character
2020 * is within.
2021 */
2022 CharIterator(SVGTextFrame* aSVGTextFrame, CharacterFilter aFilter,
2023 nsIContent* aSubtree, bool aPostReflow = true);
2024
2025 /**
2026 * Ensure any cached PropertyProvider is cleared at the end of the iteration.
2027 */
2028 ~CharIterator() {
2029 if (auto* root = mFrameIterator.GetRoot()) {
2030 root->ForgetCachedProvider();
2031 }
2032 }
2033
2034 /**
2035 * Returns whether the iterator is finished.
2036 */
2037 bool AtEnd() const { return !mFrameIterator.GetCurrent(); }
2038
2039 /**
2040 * Advances to the next matching character. Returns true if there was a
2041 * character to advance to, and false otherwise.
2042 */
2043 bool Next();
2044
2045 /**
2046 * Advances ahead aCount matching characters. Returns true if there were
2047 * enough characters to advance past, and false otherwise.
2048 */
2049 bool Next(uint32_t aCount);
2050
2051 /**
2052 * Advances ahead up to aCount matching characters.
2053 */
2054 void NextWithinSubtree(uint32_t aCount);
2055
2056 /**
2057 * Advances to the character with the specified index. The index is in the
2058 * space of original characters (i.e., all DOM characters under the <text>
2059 * that are within valid text content elements).
2060 */
2061 bool AdvanceToCharacter(uint32_t aTextElementCharIndex);
2062
2063 /**
2064 * Advances to the first matching character after the current nsTextFrame.
2065 */
2066 bool AdvancePastCurrentFrame();
2067
2068 /**
2069 * Advances to the first matching character after the frames within
2070 * the current <textPath>.
2071 */
2072 bool AdvancePastCurrentTextPathFrame();
2073
2074 /**
2075 * Advances to the first matching character of the subtree. Returns true
2076 * if we successfully advance to the subtree, or if we are already within
2077 * the subtree. Returns false if we are past the subtree.
2078 */
2079 bool AdvanceToSubtree();
2080
2081 /**
2082 * Returns the nsTextFrame for the current character, or null if the end of
2083 * iteration has been reached.
2084 */
2085 nsTextFrame* GetTextFrame() const { return mFrameIterator.GetCurrent(); }
2086
2087 /**
2088 * Returns whether the iterator is within the subtree.
2089 */
2090 bool IsWithinSubtree() const { return mFrameIterator.IsWithinSubtree(); }
2091
2092 /**
2093 * Returns whether the iterator is past the subtree.
2094 */
2095 bool IsAfterSubtree() const { return mFrameIterator.IsAfterSubtree(); }
2096
2097 /**
2098 * Returns the iterator's computed dominant-baseline value.
2099 */
2100 StyleDominantBaseline DominantBaseline() const {
2101 return mFrameIterator.DominantBaseline();
2102 }
2103
2104 /**
2105 * Returns whether the current character is a skipped character.
2106 */
2107 bool IsOriginalCharSkipped() const {
2108 return mSkipCharsIterator.IsOriginalCharSkipped();
2109 }
2110
2111 /**
2112 * Returns whether the current character is the start of a cluster and
2113 * ligature group.
2114 */
2115 bool IsClusterAndLigatureGroupStart() const {
2116 return mTextRun->IsLigatureGroupStart(
2117 mSkipCharsIterator.GetSkippedOffset()) &&
2118 mTextRun->IsClusterStart(mSkipCharsIterator.GetSkippedOffset());
2119 }
2120
2121 /**
2122 * Returns the glyph run for the current character.
2123 */
2124 const gfxTextRun::GlyphRun& GlyphRun() const {
2125 return *mTextRun->FindFirstGlyphRunContaining(
2126 mSkipCharsIterator.GetSkippedOffset());
2127 }
2128
2129 /**
2130 * Returns whether the current character is trimmed away when painting,
2131 * due to it being leading/trailing white space.
2132 */
2133 bool IsOriginalCharTrimmed() const;
2134
2135 /**
2136 * Returns whether the current character is unaddressable from the SVG glyph
2137 * positioning attributes.
2138 */
2139 bool IsOriginalCharUnaddressable() const {
2140 return IsOriginalCharSkipped() || IsOriginalCharTrimmed();
2141 }
2142
2143 /**
2144 * Returns the text run for the current character.
2145 */
2146 gfxTextRun* TextRun() const { return mTextRun; }
2147
2148 /**
2149 * Returns the current character index.
2150 */
2151 uint32_t TextElementCharIndex() const { return mTextElementCharIndex; }
2152
2153 /**
2154 * Returns the character index for the start of the cluster/ligature group it
2155 * is part of.
2156 */
2157 uint32_t GlyphStartTextElementCharIndex() const {
2158 return mGlyphStartTextElementCharIndex;
2159 }
2160
2161 /**
2162 * Gets the advance, in user units, of the current character. If the
2163 * character is a part of ligature, then the advance returned will be
2164 * a fraction of the ligature glyph's advance.
2165 *
2166 * @param aContext The context to use for unit conversions.
2167 */
2168 gfxFloat GetAdvance(nsPresContext* aContext) const;
2169
2170 /**
2171 * Returns the frame corresponding to the <textPath> that the current
2172 * character is within.
2173 */
2174 nsIFrame* TextPathFrame() const { return mFrameIterator.TextPathFrame(); }
2175
2176#ifdef DEBUG1
2177 /**
2178 * Returns the subtree we were constructed with.
2179 */
2180 nsIContent* GetSubtree() const { return mSubtree; }
2181
2182 /**
2183 * Returns the CharacterFilter mode in use.
2184 */
2185 CharacterFilter Filter() const { return mFilter; }
2186#endif
2187
2188 private:
2189 /**
2190 * Advances to the next character without checking it against the filter.
2191 * Returns true if there was a next character to advance to, or false
2192 * otherwise.
2193 */
2194 bool NextCharacter();
2195
2196 /**
2197 * Returns whether the current character matches the filter.
2198 */
2199 bool MatchesFilter() const;
2200
2201 /**
2202 * If this is the start of a glyph, record it.
2203 */
2204 void UpdateGlyphStartTextElementCharIndex() {
2205 if (!IsOriginalCharSkipped() && IsClusterAndLigatureGroupStart()) {
2206 mGlyphStartTextElementCharIndex = mTextElementCharIndex;
2207 }
2208 }
2209
2210 /**
2211 * The filter to use.
2212 */
2213 CharacterFilter mFilter;
2214
2215 /**
2216 * The iterator for text frames.
2217 */
2218 TextFrameIterator mFrameIterator;
2219
2220#ifdef DEBUG1
2221 /**
2222 * The subtree we were constructed with.
2223 */
2224 nsIContent* const mSubtree;
2225#endif
2226
2227 /**
2228 * A gfxSkipCharsIterator for the text frame the current character is
2229 * a part of.
2230 */
2231 gfxSkipCharsIterator mSkipCharsIterator;
2232
2233 // Cache for information computed by IsOriginalCharTrimmed.
2234 mutable nsTextFrame* mFrameForTrimCheck;
2235 mutable uint32_t mTrimmedOffset;
2236 mutable uint32_t mTrimmedLength;
2237
2238 /**
2239 * The text run the current character is a part of.
2240 */
2241 gfxTextRun* mTextRun;
2242
2243 /**
2244 * The current character's index.
2245 */
2246 uint32_t mTextElementCharIndex;
2247
2248 /**
2249 * The index of the character that starts the cluster/ligature group the
2250 * current character is a part of.
2251 */
2252 uint32_t mGlyphStartTextElementCharIndex;
2253
2254 /**
2255 * The scale factor to apply to glyph advances returned by
2256 * GetAdvance etc. to take into account textLength="".
2257 */
2258 float mLengthAdjustScaleFactor;
2259
2260 /**
2261 * Whether the instance of this class is being used after reflow has occurred
2262 * or not.
2263 */
2264 bool mPostReflow;
2265};
2266
2267CharIterator::CharIterator(SVGTextFrame* aSVGTextFrame,
2268 CharIterator::CharacterFilter aFilter,
2269 nsIContent* aSubtree, bool aPostReflow)
2270 : mFilter(aFilter),
2271 mFrameIterator(aSVGTextFrame, aSubtree),
2272#ifdef DEBUG1
2273 mSubtree(aSubtree),
2274#endif
2275 mFrameForTrimCheck(nullptr),
2276 mTrimmedOffset(0),
2277 mTrimmedLength(0),
2278 mTextRun(nullptr),
2279 mTextElementCharIndex(0),
2280 mGlyphStartTextElementCharIndex(0),
2281 mLengthAdjustScaleFactor(aSVGTextFrame->mLengthAdjustScaleFactor),
2282 mPostReflow(aPostReflow) {
2283 if (!AtEnd()) {
2284 mSkipCharsIterator = GetTextFrame()->EnsureTextRun(nsTextFrame::eInflated);
2285 mTextRun = GetTextFrame()->GetTextRun(nsTextFrame::eInflated);
2286 mTextElementCharIndex = mFrameIterator.UndisplayedCharacters();
2287 UpdateGlyphStartTextElementCharIndex();
2288 if (!MatchesFilter()) {
2289 Next();
2290 }
2291 }
2292}
2293
2294bool CharIterator::Next() {
2295 while (NextCharacter()) {
2296 if (MatchesFilter()) {
2297 return true;
2298 }
2299 }
2300 return false;
2301}
2302
2303bool CharIterator::Next(uint32_t aCount) {
2304 if (aCount == 0 && AtEnd()) {
2305 return false;
2306 }
2307 while (aCount) {
2308 if (!Next()) {
2309 return false;
2310 }
2311 aCount--;
2312 }
2313 return true;
2314}
2315
2316void CharIterator::NextWithinSubtree(uint32_t aCount) {
2317 while (IsWithinSubtree() && aCount) {
2318 --aCount;
2319 if (!Next()) {
2320 return;
2321 }
2322 }
2323}
2324
2325bool CharIterator::AdvanceToCharacter(uint32_t aTextElementCharIndex) {
2326 while (mTextElementCharIndex < aTextElementCharIndex) {
2327 if (!Next()) {
2328 return false;
2329 }
2330 }
2331 return true;
2332}
2333
2334bool CharIterator::AdvancePastCurrentFrame() {
2335 // XXX Can do this better than one character at a time if it matters.
2336 nsTextFrame* currentFrame = GetTextFrame();
2337 do {
2338 if (!Next()) {
2339 return false;
2340 }
2341 } while (GetTextFrame() == currentFrame);
2342 return true;
2343}
2344
2345bool CharIterator::AdvancePastCurrentTextPathFrame() {
2346 nsIFrame* currentTextPathFrame = TextPathFrame();
2347 NS_ASSERTION(currentTextPathFrame,do { if (!(currentTextPathFrame)) { NS_DebugBreak(NS_DEBUG_ASSERTION
, "expected AdvancePastCurrentTextPathFrame to be called only "
"within a text path frame", "currentTextPathFrame", "./../../../layout/svg/SVGTextFrame.cpp"
, 2349); MOZ_PretendNoReturn(); } } while (0)
2348 "expected AdvancePastCurrentTextPathFrame to be called only "do { if (!(currentTextPathFrame)) { NS_DebugBreak(NS_DEBUG_ASSERTION
, "expected AdvancePastCurrentTextPathFrame to be called only "
"within a text path frame", "currentTextPathFrame", "./../../../layout/svg/SVGTextFrame.cpp"
, 2349); MOZ_PretendNoReturn(); } } while (0)
2349 "within a text path frame")do { if (!(currentTextPathFrame)) { NS_DebugBreak(NS_DEBUG_ASSERTION
, "expected AdvancePastCurrentTextPathFrame to be called only "
"within a text path frame", "currentTextPathFrame", "./../../../layout/svg/SVGTextFrame.cpp"
, 2349); MOZ_PretendNoReturn(); } } while (0)
;
2350 do {
2351 if (!AdvancePastCurrentFrame()) {
2352 return false;
2353 }
2354 } while (TextPathFrame() == currentTextPathFrame);
2355 return true;
2356}
2357
2358bool CharIterator::AdvanceToSubtree() {
2359 while (!IsWithinSubtree()) {
2360 if (IsAfterSubtree()) {
2361 return false;
2362 }
2363 if (!AdvancePastCurrentFrame()) {
2364 return false;
2365 }
2366 }
2367 return true;
2368}
2369
2370bool CharIterator::IsOriginalCharTrimmed() const {
2371 if (mFrameForTrimCheck != GetTextFrame()) {
2372 // Since we do a lot of trim checking, we cache the trimmed offsets and
2373 // lengths while we are in the same frame.
2374 mFrameForTrimCheck = GetTextFrame();
2375 uint32_t offset = mFrameForTrimCheck->GetContentOffset();
2376 uint32_t length = mFrameForTrimCheck->GetContentLength();
2377 nsTextFrame::TrimmedOffsets trim = mFrameForTrimCheck->GetTrimmedOffsets(
2378 mFrameForTrimCheck->CharacterDataBuffer(),
2379 (mPostReflow ? nsTextFrame::TrimmedOffsetFlags::Default
2380 : nsTextFrame::TrimmedOffsetFlags::NotPostReflow));
2381 TrimOffsets(offset, length, trim);
2382 mTrimmedOffset = offset;
2383 mTrimmedLength = length;
2384 }
2385
2386 // A character is trimmed if it is outside the mTrimmedOffset/mTrimmedLength
2387 // range and it is not a significant newline character.
2388 uint32_t index = mSkipCharsIterator.GetOriginalOffset();
2389 return !(
2390 (index >= mTrimmedOffset && index < mTrimmedOffset + mTrimmedLength) ||
2391 (index >= mTrimmedOffset + mTrimmedLength &&
2392 mFrameForTrimCheck->StyleText()->NewlineIsSignificant(
2393 mFrameForTrimCheck) &&
2394 mFrameForTrimCheck->CharacterDataBuffer().CharAt(index) == '\n'));
2395}
2396
2397gfxFloat CharIterator::GetAdvance(nsPresContext* aContext) const {
2398 float cssPxPerDevPx =
2399 nsPresContext::AppUnitsToFloatCSSPixels(aContext->AppUnitsPerDevPixel());
2400
2401 auto& provider =
2402 mFrameIterator.GetRoot()->PropertyProviderFor(GetTextFrame());
2403 uint32_t offset = mSkipCharsIterator.GetSkippedOffset();
2404 gfxFloat advance =
2405 mTextRun->GetAdvanceWidth(Range(offset, offset + 1), &provider);
2406 return aContext->AppUnitsToGfxUnits(advance) * mLengthAdjustScaleFactor *
2407 cssPxPerDevPx;
2408}
2409
2410bool CharIterator::NextCharacter() {
2411 if (AtEnd()) {
2412 return false;
2413 }
2414
2415 mTextElementCharIndex++;
2416
2417 // Advance within the current text run.
2418 mSkipCharsIterator.AdvanceOriginal(1);
2419 if (mSkipCharsIterator.GetOriginalOffset() <
2420 GetTextFrame()->GetContentEnd()) {
2421 // We're still within the part of the text run for the current text frame.
2422 UpdateGlyphStartTextElementCharIndex();
2423 return true;
2424 }
2425
2426 // Advance to the next frame.
2427 mFrameIterator.GetNext();
2428
2429 // Skip any undisplayed characters.
2430 uint32_t undisplayed = mFrameIterator.UndisplayedCharacters();
2431 mTextElementCharIndex += undisplayed;
2432 if (!GetTextFrame()) {
2433 // We're at the end.
2434 mSkipCharsIterator = gfxSkipCharsIterator();
2435 return false;
2436 }
2437
2438 mSkipCharsIterator = GetTextFrame()->EnsureTextRun(nsTextFrame::eInflated);
2439 mTextRun = GetTextFrame()->GetTextRun(nsTextFrame::eInflated);
2440 UpdateGlyphStartTextElementCharIndex();
2441 return true;
2442}
2443
2444bool CharIterator::MatchesFilter() const {
2445 switch (mFilter) {
2446 case CharacterFilter::Original:
2447 return true;
2448 case CharacterFilter::Unskipped:
2449 return !IsOriginalCharSkipped();
2450 case CharacterFilter::Addressable:
2451 return !IsOriginalCharSkipped() && !IsOriginalCharUnaddressable();
2452 }
2453 MOZ_ASSERT_UNREACHABLE("Invalid mFilter 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: "
"Invalid mFilter value" ")", "./../../../layout/svg/SVGTextFrame.cpp"
, 2453); AnnotateMozCrashReason("MOZ_ASSERT" "(" "false" ") ("
"MOZ_ASSERT_UNREACHABLE: " "Invalid mFilter value" ")"); do {
MOZ_CrashSequence(__null, 2453); __attribute__((nomerge)) ::
abort(); } while (false); } } while (false)
;
2454 return true;
2455}
2456
2457// -----------------------------------------------------------------------------
2458// SVGTextDrawPathCallbacks
2459
2460/**
2461 * Text frame draw callback class that paints the text and text decoration parts
2462 * of an nsTextFrame using SVG painting properties, and selection backgrounds
2463 * and decorations as they would normally.
2464 *
2465 * An instance of this class is passed to nsTextFrame::PaintText if painting
2466 * cannot be done directly (e.g. if we are using an SVG pattern fill, stroking
2467 * the text, etc.).
2468 */
2469class SVGTextDrawPathCallbacks final : public nsTextFrame::DrawPathCallbacks {
2470 using imgDrawingParams = image::imgDrawingParams;
2471
2472 public:
2473 /**
2474 * Constructs an SVGTextDrawPathCallbacks.
2475 *
2476 * @param aSVGTextFrame The ancestor text frame.
2477 * @param aContextPaint Used by context-fill and context-stroke.
2478 * @param aContext The context to use for painting.
2479 * @param aFrame The nsTextFrame to paint.
2480 * @param aCanvasTM The transformation matrix to set when painting; this
2481 * should be the FOR_OUTERSVG_TM canvas TM of the text, so that
2482 * paint servers are painted correctly.
2483 * @param aImgParams Whether we need to synchronously decode images.
2484 * @param aShouldPaintSVGGlyphs Whether SVG glyphs should be painted.
2485 */
2486 SVGTextDrawPathCallbacks(SVGTextFrame* aSVGTextFrame,
2487 SVGContextPaint* aContextPaint, gfxContext& aContext,
2488 nsTextFrame* aFrame, const gfxMatrix& aCanvasTM,
2489 imgDrawingParams& aImgParams,
2490 bool aShouldPaintSVGGlyphs)
2491 : DrawPathCallbacks(aShouldPaintSVGGlyphs),
2492 mSVGTextFrame(aSVGTextFrame),
2493 mContextPaint(aContextPaint),
2494 mContext(aContext),
2495 mFrame(aFrame),
2496 mCanvasTM(aCanvasTM),
2497 mImgParams(aImgParams) {}
2498
2499 void NotifySelectionBackgroundNeedsFill(const Rect& aBackgroundRect,
2500 nscolor aColor,
2501 DrawTarget& aDrawTarget) override;
2502 void PaintDecorationLine(Rect aPath, bool aPaintingShadows,
2503 nscolor aColor) override;
2504 void PaintSelectionDecorationLine(Rect aPath, bool aPaintingShadows,
2505 nscolor aColor) override;
2506 void NotifyBeforeText(bool aPaintingShadows, nscolor aColor) override;
2507 void NotifyGlyphPathEmitted() override;
2508 void NotifyAfterText() override;
2509
2510 private:
2511 void SetupContext();
2512
2513 bool IsClipPathChild() const {
2514 return mSVGTextFrame->HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD);
2515 }
2516
2517 /**
2518 * Paints a piece of text geometry. This is called when glyphs
2519 * or text decorations have been emitted to the gfxContext.
2520 */
2521 void HandleTextGeometry();
2522
2523 /**
2524 * Sets the gfxContext paint to the appropriate color or pattern
2525 * for filling text geometry.
2526 */
2527 void MakeFillPattern(GeneralPattern* aOutPattern);
2528
2529 /**
2530 * Fills and strokes a piece of text geometry, using group opacity
2531 * if the selection style requires it.
2532 */
2533 void FillAndStrokeGeometry();
2534
2535 /**
2536 * Fills a piece of text geometry.
2537 */
2538 void FillGeometry();
2539
2540 /**
2541 * Strokes a piece of text geometry.
2542 */
2543 void StrokeGeometry();
2544
2545 /*
2546 * Takes a colour and modifies it to account for opacity properties.
2547 */
2548 void ApplyOpacity(sRGBColor& aColor, const StyleSVGPaint& aPaint,
2549 const StyleSVGOpacity& aOpacity) const;
2550
2551 SVGTextFrame* const mSVGTextFrame;
2552 SVGContextPaint* const mContextPaint;
2553 gfxContext& mContext;
2554 nsTextFrame* const mFrame;
2555 const gfxMatrix& mCanvasTM;
2556 imgDrawingParams& mImgParams;
2557
2558 /**
2559 * The color that we were last told from one of the path callback functions.
2560 * This color can be the special NS_SAME_AS_FOREGROUND_COLOR,
2561 * NS_40PERCENT_FOREGROUND_COLOR and NS_TRANSPARENT colors when we are
2562 * painting selections or IME decorations.
2563 */
2564 nscolor mColor = NS_RGBA(0, 0, 0, 0)((nscolor)(((0) << 24) | ((0) << 16) | ((0) <<
8) | (0)))
;
2565
2566 /**
2567 * Whether we're painting text shadows.
2568 */
2569 bool mPaintingShadows = false;
2570};
2571
2572void SVGTextDrawPathCallbacks::NotifySelectionBackgroundNeedsFill(
2573 const Rect& aBackgroundRect, nscolor aColor, DrawTarget& aDrawTarget) {
2574 if (IsClipPathChild()) {
2575 // Don't paint selection backgrounds when in a clip path.
2576 return;
2577 }
2578
2579 mColor = aColor; // currently needed by MakeFillPattern
2580 mPaintingShadows = false;
2581
2582 GeneralPattern fillPattern;
2583 MakeFillPattern(&fillPattern);
2584 if (fillPattern.GetPattern()) {
2585 DrawOptions drawOptions(aColor == NS_40PERCENT_FOREGROUND_COLOR ? 0.4
2586 : 1.0);
2587 aDrawTarget.FillRect(aBackgroundRect, fillPattern, drawOptions);
2588 }
2589}
2590
2591void SVGTextDrawPathCallbacks::NotifyBeforeText(bool aPaintingShadows,
2592 nscolor aColor) {
2593 mColor = aColor;
2594 mPaintingShadows = aPaintingShadows;
2595 SetupContext();
2596 mContext.NewPath();
2597}
2598
2599void SVGTextDrawPathCallbacks::NotifyGlyphPathEmitted() {
2600 HandleTextGeometry();
2601 mContext.NewPath();
2602}
2603
2604void SVGTextDrawPathCallbacks::NotifyAfterText() { mContext.Restore(); }
2605
2606void SVGTextDrawPathCallbacks::PaintDecorationLine(Rect aPath,
2607 bool aPaintingShadows,
2608 nscolor aColor) {
2609 mColor = aColor;
2610 mPaintingShadows = aPaintingShadows;
2611 AntialiasMode aaMode =
2612 SVGUtils::ToAntialiasMode(mFrame->StyleText()->mTextRendering);
2613
2614 mContext.Save();
2615 mContext.NewPath();
2616 mContext.SetAntialiasMode(aaMode);
2617 mContext.Rectangle(ThebesRect(aPath));
2618 HandleTextGeometry();
2619 mContext.NewPath();
2620 mContext.Restore();
2621}
2622
2623void SVGTextDrawPathCallbacks::PaintSelectionDecorationLine(
2624 Rect aPath, bool aPaintingShadows, nscolor aColor) {
2625 if (IsClipPathChild()) {
2626 // Don't paint selection decorations when in a clip path.
2627 return;
2628 }
2629
2630 mColor = aColor;
2631 mPaintingShadows = aPaintingShadows;
2632
2633 mContext.Save();
2634 mContext.NewPath();
2635 mContext.Rectangle(ThebesRect(aPath));
2636 FillAndStrokeGeometry();
2637 mContext.Restore();
2638}
2639
2640void SVGTextDrawPathCallbacks::SetupContext() {
2641 mContext.Save();
2642
2643 // XXX This is copied from nsSVGGlyphFrame::Render, but cairo doesn't actually
2644 // seem to do anything with the antialias mode. So we can perhaps remove it,
2645 // or make SetAntialiasMode set cairo text antialiasing too.
2646 mContext.SetAntialiasMode(
2647 SVGUtils::ToAntialiasMode(mFrame->StyleText()->mTextRendering));
2648}
2649
2650void SVGTextDrawPathCallbacks::HandleTextGeometry() {
2651 if (IsClipPathChild()) {
2652 RefPtr<Path> path = mContext.GetPath();
2653 ColorPattern white(
2654 DeviceColor(1.f, 1.f, 1.f, 1.f)); // for masking, so no ToDeviceColor
2655 mContext.GetDrawTarget()->Fill(path, white);
2656 } else {
2657 // Normal painting.
2658 gfxContextMatrixAutoSaveRestore saveMatrix(&mContext);
2659 mContext.SetMatrixDouble(mCanvasTM);
2660
2661 FillAndStrokeGeometry();
2662 }
2663}
2664
2665void SVGTextDrawPathCallbacks::ApplyOpacity(
2666 sRGBColor& aColor, const StyleSVGPaint& aPaint,
2667 const StyleSVGOpacity& aOpacity) const {
2668 if (aPaint.kind.tag == StyleSVGPaintKind::Tag::Color) {
2669 aColor.a *=
2670 sRGBColor::FromABGR(aPaint.kind.AsColor().CalcColor(*mFrame->Style()))
2671 .a;
2672 }
2673 aColor.a *= SVGUtils::GetOpacity(aOpacity, mContextPaint);
2674}
2675
2676void SVGTextDrawPathCallbacks::MakeFillPattern(GeneralPattern* aOutPattern) {
2677 if (mColor == NS_SAME_AS_FOREGROUND_COLOR ||
2678 mColor == NS_40PERCENT_FOREGROUND_COLOR) {
2679 SVGUtils::MakeFillPatternFor(mFrame, &mContext, aOutPattern, mImgParams,
2680 mContextPaint);
2681 return;
2682 }
2683
2684 if (mColor == NS_TRANSPARENT) {
2685 return;
2686 }
2687
2688 sRGBColor color(sRGBColor::FromABGR(mColor));
2689 if (mPaintingShadows) {
2690 ApplyOpacity(color, mFrame->StyleSVG()->mFill,
2691 mFrame->StyleSVG()->mFillOpacity);
2692 }
2693 aOutPattern->InitColorPattern(ToDeviceColor(color));
2694}
2695
2696void SVGTextDrawPathCallbacks::FillAndStrokeGeometry() {
2697 gfxGroupForBlendAutoSaveRestore autoGroupForBlend(&mContext);
2698 if (mColor == NS_40PERCENT_FOREGROUND_COLOR) {
2699 autoGroupForBlend.PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, 0.4f);
2700 }
2701
2702 uint32_t paintOrder = mFrame->StyleSVG()->mPaintOrder;
2703 if (!paintOrder) {
2704 FillGeometry();
2705 StrokeGeometry();
2706 } else {
2707 while (paintOrder) {
2708 auto component = StylePaintOrder(paintOrder & kPaintOrderMask);
2709 switch (component) {
2710 case StylePaintOrder::Fill:
2711 FillGeometry();
2712 break;
2713 case StylePaintOrder::Stroke:
2714 StrokeGeometry();
2715 break;
2716 default:
2717 MOZ_FALLTHROUGH_ASSERT("Unknown paint-order value")do { do { } while (false); MOZ_ReportCrash("" "MOZ_FALLTHROUGH_ASSERT: "
"Unknown paint-order value", "./../../../layout/svg/SVGTextFrame.cpp"
, 2717); AnnotateMozCrashReason("MOZ_CRASH(" "MOZ_FALLTHROUGH_ASSERT: "
"Unknown paint-order value" ")"); do { MOZ_CrashSequence(__null
, 2717); __attribute__((nomerge)) ::abort(); } while (false);
} while (false)
;
2718 case StylePaintOrder::Markers:
2719 case StylePaintOrder::Normal:
2720 break;
2721 }
2722 paintOrder >>= kPaintOrderShift;
2723 }
2724 }
2725}
2726
2727void SVGTextDrawPathCallbacks::FillGeometry() {
2728 if (mFrame->StyleSVG()->mFill.kind.IsNone()) {
2729 return;
2730 }
2731 GeneralPattern fillPattern;
2732 MakeFillPattern(&fillPattern);
2733 if (fillPattern.GetPattern()) {
2734 RefPtr<Path> path = mContext.GetPath();
2735 FillRule fillRule = SVGUtils::ToFillRule(mFrame->StyleSVG()->mFillRule);
2736 if (fillRule != path->GetFillRule()) {
2737 Path::SetFillRule(path, fillRule);
2738 }
2739 mContext.GetDrawTarget()->Fill(path, fillPattern);
2740 }
2741}
2742
2743void SVGTextDrawPathCallbacks::StrokeGeometry() {
2744 // We don't paint the stroke when we are filling with a selection color.
2745 if (!(mColor == NS_SAME_AS_FOREGROUND_COLOR ||
2746 mColor == NS_40PERCENT_FOREGROUND_COLOR || mPaintingShadows)) {
2747 return;
2748 }
2749
2750 if (!SVGUtils::HasStroke(mFrame, mContextPaint)) {
2751 return;
2752 }
2753
2754 GeneralPattern strokePattern;
2755 if (mPaintingShadows) {
2756 sRGBColor color(sRGBColor::FromABGR(mColor));
2757 ApplyOpacity(color, mFrame->StyleSVG()->mStroke,
2758 mFrame->StyleSVG()->mStrokeOpacity);
2759 strokePattern.InitColorPattern(ToDeviceColor(color));
2760 } else {
2761 SVGUtils::MakeStrokePatternFor(mFrame, &mContext, &strokePattern,
2762 mImgParams, mContextPaint);
2763 }
2764 if (strokePattern.GetPattern()) {
2765 SVGElement* svgOwner =
2766 SVGElement::FromNode(mFrame->GetParent()->GetContent());
2767
2768 // Apply any stroke-specific transform
2769 if (Maybe<gfxMatrix> userToOuterSVG =
2770 SVGUtils::GetNonScalingStrokeTransform(mFrame)) {
2771 mContext.Multiply(userToOuterSVG->Inverse());
2772 }
2773
2774 RefPtr<Path> path = mContext.GetPath();
2775 SVGContentUtils::AutoStrokeOptions strokeOptions;
2776 SVGContentUtils::GetStrokeOptions(&strokeOptions, svgOwner, mFrame->Style(),
2777 mContextPaint);
2778 DrawOptions drawOptions;
2779 drawOptions.mAntialiasMode =
2780 SVGUtils::ToAntialiasMode(mFrame->StyleText()->mTextRendering);
2781 mContext.GetDrawTarget()->Stroke(path, strokePattern, strokeOptions);
2782 }
2783}
2784
2785// ============================================================================
2786// SVGTextFrame
2787
2788// ----------------------------------------------------------------------------
2789// Display list item
2790
2791class DisplaySVGText final : public DisplaySVGItem {
2792 public:
2793 DisplaySVGText(nsDisplayListBuilder* aBuilder, SVGTextFrame* aFrame)
2794 : DisplaySVGItem(aBuilder, aFrame) {
2795 MOZ_COUNT_CTOR(DisplaySVGText)do { static_assert(std::is_class_v<DisplaySVGText>, "Token '"
"DisplaySVGText" "' is not a class type."); static_assert(!std
::is_base_of_v<nsISupports, DisplaySVGText>, "nsISupports classes don't need to call MOZ_COUNT_CTOR or "
"MOZ_COUNT_DTOR");; NS_LogCtor((void*)this, "DisplaySVGText"
, sizeof(*this)); } while (0)
;
2796 }
2797
2798 MOZ_COUNTED_DTOR_FINAL(DisplaySVGText)~DisplaySVGText() final { do { static_assert(std::is_class_v<
DisplaySVGText>, "Token '" "DisplaySVGText" "' is not a class type."
); static_assert(!std::is_base_of_v<nsISupports, DisplaySVGText
>, "nsISupports classes don't need to call MOZ_COUNT_CTOR or "
"MOZ_COUNT_DTOR");; NS_LogDtor((void*)this, "DisplaySVGText"
, sizeof(*this)); } while (0); }
2799
2800 NS_DISPLAY_DECL_NAME("DisplaySVGText", TYPE_SVG_TEXT)const char* Name() const override { return "DisplaySVGText"; }
constexpr static DisplayItemType ItemType() { return DisplayItemType
::TYPE_SVG_TEXT; } private: void* operator new(size_t aSize, nsDisplayListBuilder
* aBuilder) { return aBuilder->Allocate(aSize, DisplayItemType
::TYPE_SVG_TEXT); } template <typename T, typename F, typename
... Args> friend T* mozilla::MakeDisplayItemWithIndex( nsDisplayListBuilder
* aBuilder, F* aFrame, const uint16_t aIndex, Args&&...
aArgs); public:
2801
2802 nsDisplayItemGeometry* AllocateGeometry(
2803 nsDisplayListBuilder* aBuilder) override {
2804 return new nsDisplayItemGenericGeometry(this, aBuilder);
2805 }
2806
2807 nsRect GetComponentAlphaBounds(
2808 nsDisplayListBuilder* aBuilder) const override {
2809 bool snap;
2810 return GetBounds(aBuilder, &snap);
2811 }
2812};
2813
2814// ---------------------------------------------------------------------
2815// nsQueryFrame methods
2816
2817NS_QUERYFRAME_HEAD(SVGTextFrame)void* SVGTextFrame ::QueryFrame(FrameIID id) const { switch (
id) {
2818 NS_QUERYFRAME_ENTRY(SVGTextFrame)case SVGTextFrame ::kFrameIID: { static_assert( std::is_same_v
<SVGTextFrame, SVGTextFrame ::Has_NS_DECL_QUERYFRAME_TARGET
>, "SVGTextFrame" " must declare itself as a queryframe target"
); return const_cast<SVGTextFrame*>(static_cast<const
SVGTextFrame*>(this)); }
2819NS_QUERYFRAME_TAIL_INHERITING(SVGDisplayContainerFrame)default: break; } return SVGDisplayContainerFrame ::QueryFrame
(id); }
2820
2821} // namespace mozilla
2822
2823// ---------------------------------------------------------------------
2824// Implementation
2825
2826nsIFrame* NS_NewSVGTextFrame(mozilla::PresShell* aPresShell,
2827 mozilla::ComputedStyle* aStyle) {
2828 return new (aPresShell)
2829 mozilla::SVGTextFrame(aStyle, aPresShell->GetPresContext());
2830}
2831
2832namespace mozilla {
2833
2834NS_IMPL_FRAMEARENA_HELPERS(SVGTextFrame)void* SVGTextFrame ::operator new(size_t sz, mozilla::PresShell
* aShell) { return aShell->AllocateFrame(nsQueryFrame::SVGTextFrame_id
, sz); }
2835
2836// ---------------------------------------------------------------------
2837// nsIFrame methods
2838
2839void SVGTextFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
2840 nsIFrame* aPrevInFlow) {
2841 NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::text),do { if (!(aContent->IsSVGElement(nsGkAtoms::text))) { NS_DebugBreak
(NS_DEBUG_ASSERTION, "Content is not an SVG text", "aContent->IsSVGElement(nsGkAtoms::text)"
, "./../../../layout/svg/SVGTextFrame.cpp", 2842); MOZ_PretendNoReturn
(); } } while (0)
2842 "Content is not an SVG text")do { if (!(aContent->IsSVGElement(nsGkAtoms::text))) { NS_DebugBreak
(NS_DEBUG_ASSERTION, "Content is not an SVG text", "aContent->IsSVGElement(nsGkAtoms::text)"
, "./../../../layout/svg/SVGTextFrame.cpp", 2842); MOZ_PretendNoReturn
(); } } while (0)
;
2843
2844 SVGDisplayContainerFrame::Init(aContent, aParent, aPrevInFlow);
2845 AddStateBits(aParent->GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD);
2846
2847 mMutationObserver = new MutationObserver(this);
2848
2849 if (HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) {
2850 // We're inserting a new <text> element into a non-display context.
2851 // Ensure that we get reflowed.
2852 ScheduleReflowSVGNonDisplayText(
2853 IntrinsicDirty::FrameAncestorsAndDescendants);
2854 }
2855}
2856
2857void SVGTextFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
2858 const nsDisplayListSet& aLists) {
2859 if (IsSubtreeDirty()) {
2860 // We can sometimes be asked to paint before reflow happens and we
2861 // have updated mPositions, etc. In this case, we just avoid
2862 // painting.
2863 return;
2864 }
2865 if (!IsVisibleForPainting() && aBuilder->IsForPainting()) {
2866 return;
2867 }
2868 DisplayOutline(aBuilder, aLists);
2869 aLists.Content()->AppendNewToTop<DisplaySVGText>(aBuilder, this);
2870}
2871
2872void SVGTextFrame::DidSetComputedStyle(ComputedStyle* aOldComputedStyle) {
2873 SVGDisplayContainerFrame::DidSetComputedStyle(aOldComputedStyle);
2874 if (StyleSVGReset()->HasNonScalingStroke() &&
2875 (!aOldComputedStyle ||
2876 !aOldComputedStyle->StyleSVGReset()->HasNonScalingStroke())) {
2877 SVGUtils::UpdateNonScalingStrokeStateBit(this);
2878 }
2879}
2880
2881nsresult SVGTextFrame::AttributeChanged(int32_t aNameSpaceID,
2882 nsAtom* aAttribute, AttrModType) {
2883 if (aNameSpaceID != kNameSpaceID_None) {
2884 return NS_OK;
2885 }
2886
2887 if (aAttribute == nsGkAtoms::transform) {
2888 // We don't invalidate for transform changes (the layers code does that).
2889 // Also note that SVGTransformableElement::GetAttributeChangeHint will
2890 // return nsChangeHint_UpdateOverflow for "transform" attribute changes
2891 // and cause DoApplyRenderingChangeToTree to make the SchedulePaint call.
2892
2893 if (!HasAnyStateBits(NS_FRAME_FIRST_REFLOW) && mCanvasTM &&
2894 mCanvasTM->IsSingular()) {
2895 // We won't have calculated the glyph positions correctly.
2896 NotifyGlyphMetricsChange(false);
2897 }
2898 mCanvasTM = nullptr;
2899 } else if (IsGlyphPositioningAttribute(aAttribute) ||
2900 aAttribute == nsGkAtoms::textLength ||
2901 aAttribute == nsGkAtoms::lengthAdjust) {
2902 NotifyGlyphMetricsChange(false);
2903 }
2904
2905 return NS_OK;
2906}
2907
2908void SVGTextFrame::ReflowSVGNonDisplayText() {
2909 MOZ_ASSERT(SVGUtils::AnyOuterSVGIsCallingReflowSVG(this),do { static_assert( mozilla::detail::AssertionConditionType<
decltype(SVGUtils::AnyOuterSVGIsCallingReflowSVG(this))>::
isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(SVGUtils::AnyOuterSVGIsCallingReflowSVG(this)))), 0)
)) { do { } while (false); MOZ_ReportAssertionFailure("SVGUtils::AnyOuterSVGIsCallingReflowSVG(this)"
" (" "only call ReflowSVGNonDisplayText when an outer SVG frame is "
"under ReflowSVG" ")", "./../../../layout/svg/SVGTextFrame.cpp"
, 2911); AnnotateMozCrashReason("MOZ_ASSERT" "(" "SVGUtils::AnyOuterSVGIsCallingReflowSVG(this)"
") (" "only call ReflowSVGNonDisplayText when an outer SVG frame is "
"under ReflowSVG" ")"); do { MOZ_CrashSequence(__null, 2911)
; __attribute__((nomerge)) ::abort(); } while (false); } } while
(false)
2910 "only call ReflowSVGNonDisplayText when an outer SVG frame is "do { static_assert( mozilla::detail::AssertionConditionType<
decltype(SVGUtils::AnyOuterSVGIsCallingReflowSVG(this))>::
isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(SVGUtils::AnyOuterSVGIsCallingReflowSVG(this)))), 0)
)) { do { } while (false); MOZ_ReportAssertionFailure("SVGUtils::AnyOuterSVGIsCallingReflowSVG(this)"
" (" "only call ReflowSVGNonDisplayText when an outer SVG frame is "
"under ReflowSVG" ")", "./../../../layout/svg/SVGTextFrame.cpp"
, 2911); AnnotateMozCrashReason("MOZ_ASSERT" "(" "SVGUtils::AnyOuterSVGIsCallingReflowSVG(this)"
") (" "only call ReflowSVGNonDisplayText when an outer SVG frame is "
"under ReflowSVG" ")"); do { MOZ_CrashSequence(__null, 2911)
; __attribute__((nomerge)) ::abort(); } while (false); } } while
(false)
2911 "under ReflowSVG")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(SVGUtils::AnyOuterSVGIsCallingReflowSVG(this))>::
isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(SVGUtils::AnyOuterSVGIsCallingReflowSVG(this)))), 0)
)) { do { } while (false); MOZ_ReportAssertionFailure("SVGUtils::AnyOuterSVGIsCallingReflowSVG(this)"
" (" "only call ReflowSVGNonDisplayText when an outer SVG frame is "
"under ReflowSVG" ")", "./../../../layout/svg/SVGTextFrame.cpp"
, 2911); AnnotateMozCrashReason("MOZ_ASSERT" "(" "SVGUtils::AnyOuterSVGIsCallingReflowSVG(this)"
") (" "only call ReflowSVGNonDisplayText when an outer SVG frame is "
"under ReflowSVG" ")"); do { MOZ_CrashSequence(__null, 2911)
; __attribute__((nomerge)) ::abort(); } while (false); } } while
(false)
;
2912 MOZ_ASSERT(HasAnyStateBits(NS_FRAME_IS_NONDISPLAY),do { static_assert( mozilla::detail::AssertionConditionType<
decltype(HasAnyStateBits(NS_FRAME_IS_NONDISPLAY))>::isValid
, "invalid assertion condition"); if ((__builtin_expect(!!(!(
!!(HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)))), 0))) { do { } while
(false); MOZ_ReportAssertionFailure("HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)"
" (" "only call ReflowSVGNonDisplayText if the frame is " "NS_FRAME_IS_NONDISPLAY"
")", "./../../../layout/svg/SVGTextFrame.cpp", 2914); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)" ") ("
"only call ReflowSVGNonDisplayText if the frame is " "NS_FRAME_IS_NONDISPLAY"
")"); do { MOZ_CrashSequence(__null, 2914); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
2913 "only call ReflowSVGNonDisplayText if the frame is "do { static_assert( mozilla::detail::AssertionConditionType<
decltype(HasAnyStateBits(NS_FRAME_IS_NONDISPLAY))>::isValid
, "invalid assertion condition"); if ((__builtin_expect(!!(!(
!!(HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)))), 0))) { do { } while
(false); MOZ_ReportAssertionFailure("HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)"
" (" "only call ReflowSVGNonDisplayText if the frame is " "NS_FRAME_IS_NONDISPLAY"
")", "./../../../layout/svg/SVGTextFrame.cpp", 2914); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)" ") ("
"only call ReflowSVGNonDisplayText if the frame is " "NS_FRAME_IS_NONDISPLAY"
")"); do { MOZ_CrashSequence(__null, 2914); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
2914 "NS_FRAME_IS_NONDISPLAY")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(HasAnyStateBits(NS_FRAME_IS_NONDISPLAY))>::isValid
, "invalid assertion condition"); if ((__builtin_expect(!!(!(
!!(HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)))), 0))) { do { } while
(false); MOZ_ReportAssertionFailure("HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)"
" (" "only call ReflowSVGNonDisplayText if the frame is " "NS_FRAME_IS_NONDISPLAY"
")", "./../../../layout/svg/SVGTextFrame.cpp", 2914); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)" ") ("
"only call ReflowSVGNonDisplayText if the frame is " "NS_FRAME_IS_NONDISPLAY"
")"); do { MOZ_CrashSequence(__null, 2914); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
2915
2916 // We had a style change, so we mark this frame as dirty so that the next
2917 // time it is painted, we reflow the anonymous block frame.
2918 this->MarkSubtreeDirty();
2919
2920 // Finally, we need to actually reflow the anonymous block frame and update
2921 // mPositions, in case we are being reflowed immediately after a DOM
2922 // mutation that needs frame reconstruction.
2923 MaybeReflowAnonymousBlockChild();
2924 UpdateGlyphPositioning();
2925}
2926
2927void SVGTextFrame::ScheduleReflowSVGNonDisplayText(IntrinsicDirty aReason) {
2928 MOZ_ASSERT(!SVGUtils::OuterSVGIsCallingReflowSVG(this),do { static_assert( mozilla::detail::AssertionConditionType<
decltype(!SVGUtils::OuterSVGIsCallingReflowSVG(this))>::isValid
, "invalid assertion condition"); if ((__builtin_expect(!!(!(
!!(!SVGUtils::OuterSVGIsCallingReflowSVG(this)))), 0))) { do {
} while (false); MOZ_ReportAssertionFailure("!SVGUtils::OuterSVGIsCallingReflowSVG(this)"
" (" "do not call ScheduleReflowSVGNonDisplayText when the outer SVG "
"frame is under ReflowSVG" ")", "./../../../layout/svg/SVGTextFrame.cpp"
, 2930); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!SVGUtils::OuterSVGIsCallingReflowSVG(this)"
") (" "do not call ScheduleReflowSVGNonDisplayText when the outer SVG "
"frame is under ReflowSVG" ")"); do { MOZ_CrashSequence(__null
, 2930); __attribute__((nomerge)) ::abort(); } while (false);
} } while (false)
2929 "do not call ScheduleReflowSVGNonDisplayText when the outer SVG "do { static_assert( mozilla::detail::AssertionConditionType<
decltype(!SVGUtils::OuterSVGIsCallingReflowSVG(this))>::isValid
, "invalid assertion condition"); if ((__builtin_expect(!!(!(
!!(!SVGUtils::OuterSVGIsCallingReflowSVG(this)))), 0))) { do {
} while (false); MOZ_ReportAssertionFailure("!SVGUtils::OuterSVGIsCallingReflowSVG(this)"
" (" "do not call ScheduleReflowSVGNonDisplayText when the outer SVG "
"frame is under ReflowSVG" ")", "./../../../layout/svg/SVGTextFrame.cpp"
, 2930); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!SVGUtils::OuterSVGIsCallingReflowSVG(this)"
") (" "do not call ScheduleReflowSVGNonDisplayText when the outer SVG "
"frame is under ReflowSVG" ")"); do { MOZ_CrashSequence(__null
, 2930); __attribute__((nomerge)) ::abort(); } while (false);
} } while (false)
2930 "frame is under ReflowSVG")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(!SVGUtils::OuterSVGIsCallingReflowSVG(this))>::isValid
, "invalid assertion condition"); if ((__builtin_expect(!!(!(
!!(!SVGUtils::OuterSVGIsCallingReflowSVG(this)))), 0))) { do {
} while (false); MOZ_ReportAssertionFailure("!SVGUtils::OuterSVGIsCallingReflowSVG(this)"
" (" "do not call ScheduleReflowSVGNonDisplayText when the outer SVG "
"frame is under ReflowSVG" ")", "./../../../layout/svg/SVGTextFrame.cpp"
, 2930); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!SVGUtils::OuterSVGIsCallingReflowSVG(this)"
") (" "do not call ScheduleReflowSVGNonDisplayText when the outer SVG "
"frame is under ReflowSVG" ")"); do { MOZ_CrashSequence(__null
, 2930); __attribute__((nomerge)) ::abort(); } while (false);
} } while (false)
;
2931 MOZ_ASSERT(!HasAnyStateBits(NS_STATE_SVG_TEXT_IN_REFLOW),do { static_assert( mozilla::detail::AssertionConditionType<
decltype(!HasAnyStateBits(NS_STATE_SVG_TEXT_IN_REFLOW))>::
isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(!HasAnyStateBits(NS_STATE_SVG_TEXT_IN_REFLOW)))), 0)
)) { do { } while (false); MOZ_ReportAssertionFailure("!HasAnyStateBits(NS_STATE_SVG_TEXT_IN_REFLOW)"
" (" "do not call ScheduleReflowSVGNonDisplayText while reflowing the "
"anonymous block child" ")", "./../../../layout/svg/SVGTextFrame.cpp"
, 2933); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!HasAnyStateBits(NS_STATE_SVG_TEXT_IN_REFLOW)"
") (" "do not call ScheduleReflowSVGNonDisplayText while reflowing the "
"anonymous block child" ")"); do { MOZ_CrashSequence(__null,
2933); __attribute__((nomerge)) ::abort(); } while (false); }
} while (false)
2932 "do not call ScheduleReflowSVGNonDisplayText while reflowing the "do { static_assert( mozilla::detail::AssertionConditionType<
decltype(!HasAnyStateBits(NS_STATE_SVG_TEXT_IN_REFLOW))>::
isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(!HasAnyStateBits(NS_STATE_SVG_TEXT_IN_REFLOW)))), 0)
)) { do { } while (false); MOZ_ReportAssertionFailure("!HasAnyStateBits(NS_STATE_SVG_TEXT_IN_REFLOW)"
" (" "do not call ScheduleReflowSVGNonDisplayText while reflowing the "
"anonymous block child" ")", "./../../../layout/svg/SVGTextFrame.cpp"
, 2933); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!HasAnyStateBits(NS_STATE_SVG_TEXT_IN_REFLOW)"
") (" "do not call ScheduleReflowSVGNonDisplayText while reflowing the "
"anonymous block child" ")"); do { MOZ_CrashSequence(__null,
2933); __attribute__((nomerge)) ::abort(); } while (false); }
} while (false)
2933 "anonymous block child")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(!HasAnyStateBits(NS_STATE_SVG_TEXT_IN_REFLOW))>::
isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(!HasAnyStateBits(NS_STATE_SVG_TEXT_IN_REFLOW)))), 0)
)) { do { } while (false); MOZ_ReportAssertionFailure("!HasAnyStateBits(NS_STATE_SVG_TEXT_IN_REFLOW)"
" (" "do not call ScheduleReflowSVGNonDisplayText while reflowing the "
"anonymous block child" ")", "./../../../layout/svg/SVGTextFrame.cpp"
, 2933); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!HasAnyStateBits(NS_STATE_SVG_TEXT_IN_REFLOW)"
") (" "do not call ScheduleReflowSVGNonDisplayText while reflowing the "
"anonymous block child" ")"); do { MOZ_CrashSequence(__null,
2933); __attribute__((nomerge)) ::abort(); } while (false); }
} while (false)
;
2934
2935 // We need to find an ancestor frame that we can call FrameNeedsReflow
2936 // on that will cause the document to be marked as needing relayout,
2937 // and for that ancestor (or some further ancestor) to be marked as
2938 // a root to reflow. We choose the closest ancestor frame that is not
2939 // NS_FRAME_IS_NONDISPLAY and which is either an outer SVG frame or a
2940 // non-SVG frame. (We don't consider displayed SVG frame ancestors other
2941 // than SVGOuterSVGFrame, since calling FrameNeedsReflow on those other
2942 // SVG frames would do a bunch of unnecessary work on the SVG frames up to
2943 // the SVGOuterSVGFrame.)
2944
2945 nsIFrame* f = this;
2946 while (f) {
2947 if (!f->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) {
2948 if (f->IsSubtreeDirty()) {
2949 // This is a displayed frame, so if it is already dirty, we will be
2950 // reflowed soon anyway. No need to call FrameNeedsReflow again, then.
2951 return;
2952 }
2953 if (!f->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
2954 break;
2955 }
2956 f->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
2957 }
2958 f = f->GetParent();
2959 }
2960
2961 MOZ_ASSERT(f, "should have found an ancestor frame to reflow")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(f)>::isValid, "invalid assertion condition"); if (
(__builtin_expect(!!(!(!!(f))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("f" " (" "should have found an ancestor frame to reflow" ")"
, "./../../../layout/svg/SVGTextFrame.cpp", 2961); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "f" ") (" "should have found an ancestor frame to reflow"
")"); do { MOZ_CrashSequence(__null, 2961); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
2962
2963 PresShell()->FrameNeedsReflow(f, aReason, NS_FRAME_IS_DIRTY);
2964}
2965
2966NS_IMPL_ISUPPORTS(SVGTextFrame::MutationObserver, nsIMutationObserver)MozExternalRefCountType SVGTextFrame::MutationObserver::AddRef
(void) { static_assert(!std::is_destructible_v<SVGTextFrame
::MutationObserver>, "Reference-counted class " "SVGTextFrame::MutationObserver"
" should not have a public destructor. " "Make this class's destructor non-public"
); do { static_assert( mozilla::detail::AssertionConditionType
<decltype(int32_t(mRefCnt) >= 0)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(int32_t(mRefCnt) >= 0))),
0))) { do { } while (false); MOZ_ReportAssertionFailure("int32_t(mRefCnt) >= 0"
" (" "illegal refcnt" ")", "./../../../layout/svg/SVGTextFrame.cpp"
, 2966); AnnotateMozCrashReason("MOZ_ASSERT" "(" "int32_t(mRefCnt) >= 0"
") (" "illegal refcnt" ")"); do { MOZ_CrashSequence(__null, 2966
); __attribute__((nomerge)) ::abort(); } while (false); } } while
(false); do { static_assert( mozilla::detail::AssertionConditionType
<decltype("SVGTextFrame::MutationObserver" != nullptr)>
::isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!("SVGTextFrame::MutationObserver" != nullptr))), 0)))
{ do { } while (false); MOZ_ReportAssertionFailure("\"SVGTextFrame::MutationObserver\" != nullptr"
" (" "Must specify a name" ")", "./../../../layout/svg/SVGTextFrame.cpp"
, 2966); AnnotateMozCrashReason("MOZ_ASSERT" "(" "\"SVGTextFrame::MutationObserver\" != nullptr"
") (" "Must specify a name" ")"); do { MOZ_CrashSequence(__null
, 2966); __attribute__((nomerge)) ::abort(); } while (false);
} } while (false); if (!mRefCnt.isThreadSafe) _mOwningThread
.AssertOwnership("SVGTextFrame::MutationObserver" " not thread-safe"
); nsrefcnt count = ++mRefCnt; NS_LogAddRef((this), (count), (
"SVGTextFrame::MutationObserver"), (uint32_t)(sizeof(*this)))
; return count; } MozExternalRefCountType SVGTextFrame::MutationObserver
::Release(void) { do { static_assert( mozilla::detail::AssertionConditionType
<decltype(int32_t(mRefCnt) > 0)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(int32_t(mRefCnt) > 0))), 0
))) { do { } while (false); MOZ_ReportAssertionFailure("int32_t(mRefCnt) > 0"
" (" "dup release" ")", "./../../../layout/svg/SVGTextFrame.cpp"
, 2966); AnnotateMozCrashReason("MOZ_ASSERT" "(" "int32_t(mRefCnt) > 0"
") (" "dup release" ")"); do { MOZ_CrashSequence(__null, 2966
); __attribute__((nomerge)) ::abort(); } while (false); } } while
(false); do { static_assert( mozilla::detail::AssertionConditionType
<decltype("SVGTextFrame::MutationObserver" != nullptr)>
::isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!("SVGTextFrame::MutationObserver" != nullptr))), 0)))
{ do { } while (false); MOZ_ReportAssertionFailure("\"SVGTextFrame::MutationObserver\" != nullptr"
" (" "Must specify a name" ")", "./../../../layout/svg/SVGTextFrame.cpp"
, 2966); AnnotateMozCrashReason("MOZ_ASSERT" "(" "\"SVGTextFrame::MutationObserver\" != nullptr"
") (" "Must specify a name" ")"); do { MOZ_CrashSequence(__null
, 2966); __attribute__((nomerge)) ::abort(); } while (false);
} } while (false); if (!mRefCnt.isThreadSafe) _mOwningThread
.AssertOwnership("SVGTextFrame::MutationObserver" " not thread-safe"
); const char* const nametmp = "SVGTextFrame::MutationObserver"
; nsrefcnt count = --mRefCnt; NS_LogRelease((this), (count), (
nametmp)); if (count == 0) { mRefCnt = 1; delete (this); return
0; } return count; } nsresult SVGTextFrame::MutationObserver
::QueryInterface(const nsIID& aIID, void** aInstancePtr) {
do { if (!(aInstancePtr)) { NS_DebugBreak(NS_DEBUG_ASSERTION
, "QueryInterface requires a non-NULL destination!", "aInstancePtr"
, "./../../../layout/svg/SVGTextFrame.cpp", 2966); MOZ_PretendNoReturn
(); } } while (0); nsresult rv = NS_ERROR_FAILURE; static_assert
(1 > 0, "Need more arguments to NS_INTERFACE_TABLE"); static
const QITableEntry table[] = { {&mozilla::detail::kImplementedIID
<SVGTextFrame::MutationObserver, nsIMutationObserver>, int32_t
( reinterpret_cast<char*>(static_cast<nsIMutationObserver
*>((SVGTextFrame::MutationObserver*)0x1000)) - reinterpret_cast
<char*>((SVGTextFrame::MutationObserver*)0x1000))}, {&
mozilla::detail::kImplementedIID<SVGTextFrame::MutationObserver
, nsISupports>, int32_t(reinterpret_cast<char*>(static_cast
<nsISupports*>( static_cast<nsIMutationObserver*>
((SVGTextFrame::MutationObserver*)0x1000))) - reinterpret_cast
<char*>((SVGTextFrame::MutationObserver*)0x1000))}, { nullptr
, 0 } } ; static_assert(std::size(table) > 1, "need at least 1 interface"
); rv = NS_TableDrivenQI(static_cast<void*>(this), aIID
, aInstancePtr, table); return rv; }
2967
2968void SVGTextFrame::MutationObserver::ContentAppended(
2969 nsIContent* aFirstNewContent, const ContentAppendInfo&) {
2970 mFrame->NotifyGlyphMetricsChange(true);
2971}
2972
2973void SVGTextFrame::MutationObserver::ContentInserted(nsIContent* aChild,
2974 const ContentInsertInfo&) {
2975 mFrame->NotifyGlyphMetricsChange(true);
2976}
2977
2978void SVGTextFrame::MutationObserver::ContentWillBeRemoved(
2979 nsIContent* aChild, const ContentRemoveInfo& aInfo) {
2980 if (aInfo.mBatchRemovalState && !aInfo.mBatchRemovalState->mIsFirst) {
2981 return;
2982 }
2983 mFrame->NotifyGlyphMetricsChange(true);
2984}
2985
2986void SVGTextFrame::MutationObserver::CharacterDataChanged(
2987 nsIContent* aContent, const CharacterDataChangeInfo&) {
2988 mFrame->NotifyGlyphMetricsChange(true);
2989}
2990
2991void SVGTextFrame::MutationObserver::AttributeChanged(
2992 Element* aElement, int32_t aNameSpaceID, nsAtom* aAttribute, AttrModType,
2993 const nsAttrValue* aOldValue) {
2994 if (!aElement->IsSVGElement()) {
2995 return;
2996 }
2997
2998 // Attribute changes on this element will be handled by
2999 // SVGTextFrame::AttributeChanged.
3000 if (aElement == mFrame->GetContent()) {
3001 return;
3002 }
3003
3004 mFrame->HandleAttributeChangeInDescendant(aElement, aNameSpaceID, aAttribute);
3005}
3006
3007void SVGTextFrame::HandleAttributeChangeInDescendant(Element* aElement,
3008 int32_t aNameSpaceID,
3009 nsAtom* aAttribute) {
3010 if (aElement->IsSVGElement(nsGkAtoms::textPath)) {
3011 if (aNameSpaceID == kNameSpaceID_None &&
3012 (aAttribute == nsGkAtoms::startOffset ||
3013 aAttribute == nsGkAtoms::path || aAttribute == nsGkAtoms::side)) {
3014 NotifyGlyphMetricsChange(false);
3015 } else if ((aNameSpaceID == kNameSpaceID_XLink4 ||
3016 aNameSpaceID == kNameSpaceID_None) &&
3017 aAttribute == nsGkAtoms::href) {
3018 // Blow away our reference, if any
3019 nsIFrame* childElementFrame = aElement->GetPrimaryFrame();
3020 if (childElementFrame) {
3021 SVGObserverUtils::RemoveTextPathObserver(childElementFrame);
3022 NotifyGlyphMetricsChange(false);
3023 }
3024 }
3025 } else {
3026 if (aNameSpaceID == kNameSpaceID_None &&
3027 IsGlyphPositioningAttribute(aAttribute)) {
3028 NotifyGlyphMetricsChange(false);
3029 }
3030 }
3031}
3032
3033void SVGTextFrame::FindCloserFrameForSelection(
3034 const nsPoint& aPoint, FrameWithDistance* aCurrentBestFrame) {
3035 if (HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) {
3036 return;
3037 }
3038
3039 UpdateGlyphPositioning();
3040
3041 nsPresContext* presContext = PresContext();
3042
3043 // Find the frame that has the closest rendered run rect to aPoint.
3044 TextRenderedRunIterator it(this);
3045 for (TextRenderedRun run = it.Current(); run.mFrame; run = it.Next()) {
3046 TextRenderedRun::GeometryFlags flags(
3047 TextRenderedRun::GeometryFlag::IncludeFill,
3048 TextRenderedRun::GeometryFlag::IncludeStroke,
3049 TextRenderedRun::GeometryFlag::NoHorizontalOverflow);
3050 SVGBBox userRect = run.GetUserSpaceRect(presContext, flags);
3051 float devPxPerCSSPx = presContext->CSSPixelsToDevPixels(1.f);
3052 userRect.Scale(devPxPerCSSPx);
3053
3054 if (!userRect.IsEmpty()) {
3055 gfxMatrix m;
3056 nsRect rect =
3057 SVGUtils::ToCanvasBounds(userRect.ToThebesRect(), m, presContext);
3058
3059 if (nsLayoutUtils::PointIsCloserToRect(aPoint, rect,
3060 aCurrentBestFrame->mXDistance,
3061 aCurrentBestFrame->mYDistance)) {
3062 aCurrentBestFrame->mFrame = run.mFrame;
3063 }
3064 }
3065 }
3066}
3067
3068//----------------------------------------------------------------------
3069// ISVGDisplayableFrame methods
3070
3071void SVGTextFrame::NotifySVGChanged(ChangeFlags aFlags) {
3072 MOZ_ASSERT(aFlags.contains(ChangeFlag::TransformChanged) ||do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aFlags.contains(ChangeFlag::TransformChanged) || aFlags
.contains(ChangeFlag::CoordContextChanged))>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(aFlags.contains(ChangeFlag::
TransformChanged) || aFlags.contains(ChangeFlag::CoordContextChanged
)))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("aFlags.contains(ChangeFlag::TransformChanged) || aFlags.contains(ChangeFlag::CoordContextChanged)"
" (" "Invalidation logic may need adjusting" ")", "./../../../layout/svg/SVGTextFrame.cpp"
, 3074); AnnotateMozCrashReason("MOZ_ASSERT" "(" "aFlags.contains(ChangeFlag::TransformChanged) || aFlags.contains(ChangeFlag::CoordContextChanged)"
") (" "Invalidation logic may need adjusting" ")"); do { MOZ_CrashSequence
(__null, 3074); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
3073 aFlags.contains(ChangeFlag::CoordContextChanged),do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aFlags.contains(ChangeFlag::TransformChanged) || aFlags
.contains(ChangeFlag::CoordContextChanged))>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(aFlags.contains(ChangeFlag::
TransformChanged) || aFlags.contains(ChangeFlag::CoordContextChanged
)))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("aFlags.contains(ChangeFlag::TransformChanged) || aFlags.contains(ChangeFlag::CoordContextChanged)"
" (" "Invalidation logic may need adjusting" ")", "./../../../layout/svg/SVGTextFrame.cpp"
, 3074); AnnotateMozCrashReason("MOZ_ASSERT" "(" "aFlags.contains(ChangeFlag::TransformChanged) || aFlags.contains(ChangeFlag::CoordContextChanged)"
") (" "Invalidation logic may need adjusting" ")"); do { MOZ_CrashSequence
(__null, 3074); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
3074 "Invalidation logic may need adjusting")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aFlags.contains(ChangeFlag::TransformChanged) || aFlags
.contains(ChangeFlag::CoordContextChanged))>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(aFlags.contains(ChangeFlag::
TransformChanged) || aFlags.contains(ChangeFlag::CoordContextChanged
)))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("aFlags.contains(ChangeFlag::TransformChanged) || aFlags.contains(ChangeFlag::CoordContextChanged)"
" (" "Invalidation logic may need adjusting" ")", "./../../../layout/svg/SVGTextFrame.cpp"
, 3074); AnnotateMozCrashReason("MOZ_ASSERT" "(" "aFlags.contains(ChangeFlag::TransformChanged) || aFlags.contains(ChangeFlag::CoordContextChanged)"
") (" "Invalidation logic may need adjusting" ")"); do { MOZ_CrashSequence
(__null, 3074); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
3075
3076 bool needNewBounds = false;
3077 bool needGlyphMetricsUpdate = false;
3078 if (aFlags.contains(ChangeFlag::CoordContextChanged) &&
3079 HasAnyStateBits(NS_STATE_SVG_POSITIONING_MAY_USE_PERCENTAGES)) {
3080 needGlyphMetricsUpdate = true;
3081 }
3082
3083 if (aFlags.contains(ChangeFlag::TransformChanged)) {
3084 if (mCanvasTM && mCanvasTM->IsSingular()) {
3085 // We won't have calculated the glyph positions correctly.
3086 needNewBounds = true;
3087 needGlyphMetricsUpdate = true;
3088 }
3089 mCanvasTM = nullptr;
3090 if (StyleSVGReset()->HasNonScalingStroke()) {
3091 // Stroke currently contributes to our mRect, and our stroke depends on
3092 // the transform to our outer-<svg> if |vector-effect:non-scaling-stroke|.
3093 needNewBounds = true;
3094 }
3095
3096 // If the scale at which we computed our mFontSizeScaleFactor has changed by
3097 // at least a factor of two, reflow the text. This avoids reflowing text at
3098 // every tick of a transform animation, but ensures our glyph metrics
3099 // do not get too far out of sync with the final font size on the screen.
3100 const float scale = GetContextScale(this);
3101 if (scale != mLastContextScale) {
3102 if (mLastContextScale == 0.0f) {
3103 needNewBounds = true;
3104 needGlyphMetricsUpdate = true;
3105 } else {
3106 float change = scale / mLastContextScale;
3107 if (change >= 2.0f || change <= 0.5f) {
3108 needNewBounds = true;
3109 needGlyphMetricsUpdate = true;
3110 }
3111 }
3112 }
3113 }
3114
3115 if (needNewBounds) {
3116 // Ancestor changes can't affect how we render from the perspective of
3117 // any rendering observers that we may have, so we don't need to
3118 // invalidate them. We also don't need to invalidate ourself, since our
3119 // changed ancestor will have invalidated its entire area, which includes
3120 // our area.
3121 ScheduleReflowSVG();
3122 }
3123
3124 if (needGlyphMetricsUpdate) {
3125 // If we are positioned using percentage values we need to update our
3126 // position whenever our viewport's dimensions change. But only do this if
3127 // we have been reflowed once, otherwise the glyph positioning will be
3128 // wrong. (We need to wait until bidi reordering has been done.)
3129 if (!HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
3130 NotifyGlyphMetricsChange(false);
3131 }
3132 }
3133}
3134
3135/**
3136 * Gets the offset into a DOM node that the specified caret is positioned at.
3137 */
3138static int32_t GetCaretOffset(nsCaret* aCaret) {
3139 RefPtr<Selection> selection = aCaret->GetSelection();
3140 if (!selection) {
3141 return -1;
3142 }
3143
3144 return selection->AnchorOffset();
3145}
3146
3147/**
3148 * Returns whether the caret should be painted for a given TextRenderedRun
3149 * by checking whether the caret is in the range covered by the rendered run.
3150 *
3151 * @param aThisRun The TextRenderedRun to be painted.
3152 * @param aCaret The caret.
3153 */
3154static bool ShouldPaintCaret(const TextRenderedRun& aThisRun, nsCaret* aCaret) {
3155 int32_t caretOffset = GetCaretOffset(aCaret);
3156
3157 if (caretOffset < 0) {
3158 return false;
3159 }
3160
3161 return uint32_t(caretOffset) >= aThisRun.mTextFrameContentOffset &&
3162 uint32_t(caretOffset) < aThisRun.mTextFrameContentOffset +
3163 aThisRun.mTextFrameContentLength;
3164}
3165
3166void SVGTextFrame::PaintSVG(gfxContext& aContext, const gfxMatrix& aTransform,
3167 imgDrawingParams& aImgParams) {
3168 DrawTarget& aDrawTarget = *aContext.GetDrawTarget();
3169 nsIFrame* kid = PrincipalChildList().FirstChild();
3170 if (!kid) {
3171 return;
3172 }
3173
3174 if (IsSubtreeDirty()) {
3175 return;
3176 }
3177
3178 if (HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) {
3179 // Text frames inside <clipPath>, <mask>, etc. will never have had
3180 // ReflowSVG called on them, so call UpdateGlyphPositioning to do this now.
3181 UpdateGlyphPositioning();
3182 }
3183
3184 const float epsilon = 0.0001;
3185 if (std::abs(mLengthAdjustScaleFactor) < epsilon) {
3186 // A zero scale factor can be caused by having forced the text length to
3187 // zero. In this situation there is nothing to show.
3188 return;
3189 }
3190
3191 if (aTransform.IsSingular()) {
3192 NS_WARNING("Can't render text element!")NS_DebugBreak(NS_DEBUG_WARNING, "Can't render text element!",
nullptr, "./../../../layout/svg/SVGTextFrame.cpp", 3192)
;
3193 return;
3194 }
3195
3196 gfxMatrix initialMatrix = aContext.CurrentMatrixDouble();
3197
3198 gfxMatrix matrixForPaintServers = aTransform * initialMatrix;
3199
3200 // SVG frames' PaintSVG methods paint in CSS px, but normally frames paint in
3201 // dev pixels. Here we multiply a CSS-px-to-dev-pixel factor onto aTransform
3202 // so our non-SVG nsTextFrame children paint correctly.
3203 nsPresContext* presContext = PresContext();
3204 auto auPerDevPx = presContext->AppUnitsPerDevPixel();
3205 float cssPxPerDevPx = nsPresContext::AppUnitsToFloatCSSPixels(auPerDevPx);
3206 gfxMatrix canvasTMForChildren = aTransform;
3207 canvasTMForChildren.PreScale(cssPxPerDevPx, cssPxPerDevPx);
3208 initialMatrix.PreScale(1 / cssPxPerDevPx, 1 / cssPxPerDevPx);
3209
3210 gfxContextMatrixAutoSaveRestore matSR(&aContext);
3211 aContext.NewPath();
3212 aContext.Multiply(canvasTMForChildren);
3213 gfxMatrix currentMatrix = aContext.CurrentMatrixDouble();
3214
3215 RefPtr<nsCaret> caret = presContext->PresShell()->GetActiveCaret();
3216 nsIFrame* caretFrame = caret->GetPaintGeometry();
3217
3218 gfxContextAutoSaveRestore ctxSR;
3219 TextRenderedRunIterator it(
3220 this, TextRenderedRunIterator::RenderedRunFilter::VisibleFrames);
3221 TextRenderedRun run = it.Current();
3222
3223 SVGContextPaint* outerContextPaint =
3224 SVGContextPaint::GetContextPaint(GetContent());
3225
3226 while (run.mFrame) {
3227 nsTextFrame* frame = run.mFrame;
3228
3229 auto contextPaint = MakeRefPtr<SVGContextPaint>(
3230 &aDrawTarget, initialMatrix, frame, outerContextPaint, aImgParams);
3231 DrawMode drawMode = contextPaint->GetDrawMode();
3232 if (drawMode & DrawMode::GLYPH_STROKE) {
3233 ctxSR.EnsureSaved(&aContext);
3234 // This may change the gfxContext's transform (for non-scaling stroke),
3235 // in which case this needs to happen before we call SetMatrix() below.
3236 SVGUtils::SetupStrokeGeometry(frame->GetParent(), &aContext,
3237 outerContextPaint);
3238 }
3239
3240 nscoord startEdge, endEdge;
3241 run.GetClipEdges(startEdge, endEdge);
3242
3243 // Set up the transform for painting the text frame for the substring
3244 // indicated by the run.
3245 gfxMatrix runTransform = run.GetTransformFromUserSpaceForPainting(
3246 presContext, startEdge, endEdge) *
3247 currentMatrix;
3248 aContext.SetMatrixDouble(runTransform);
3249
3250 if (drawMode != DrawMode(0)) {
3251 bool paintSVGGlyphs;
3252 nsTextFrame::PaintTextParams params(&aContext);
3253 params.framePt = Point();
3254 params.dirtyRect =
3255 LayoutDevicePixel::FromAppUnits(frame->InkOverflowRect(), auPerDevPx);
3256 params.contextPaint = contextPaint;
3257 bool isSelected;
3258 if (HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD)) {
3259 params.state = nsTextFrame::PaintTextParams::GenerateTextMask;
3260 isSelected = false;
3261 } else {
3262 isSelected = frame->IsSelected();
3263 }
3264 gfxGroupForBlendAutoSaveRestore autoGroupForBlend(&aContext);
3265 float opacity = 1.0f;
3266 nsIFrame* ancestor = frame->GetParent();
3267 while (ancestor != this) {
3268 opacity *= ancestor->StyleEffects()->mOpacity;
3269 ancestor = ancestor->GetParent();
3270 }
3271 if (opacity < 1.0f) {
3272 autoGroupForBlend.PushGroupForBlendBack(gfxContentType::COLOR_ALPHA,
3273 opacity);
3274 }
3275
3276 if (ShouldRenderAsPath(frame, outerContextPaint, paintSVGGlyphs)) {
3277 SVGTextDrawPathCallbacks callbacks(this, outerContextPaint, aContext,
3278 frame, matrixForPaintServers,
3279 aImgParams, paintSVGGlyphs);
3280 params.callbacks = &callbacks;
3281 frame->PaintText(params, startEdge, endEdge, nsPoint(), isSelected,
3282 aImgParams);
3283 } else {
3284 frame->PaintText(params, startEdge, endEdge, nsPoint(), isSelected,
3285 aImgParams);
3286 }
3287 }
3288
3289 if (frame == caretFrame && ShouldPaintCaret(run, caret)) {
3290 // XXX Should we be looking at the fill/stroke colours to paint the
3291 // caret with, rather than using the color property?
3292 caret->PaintCaret(aDrawTarget, frame, nsPoint());
3293 aContext.NewPath();
3294 }
3295
3296 run = it.Next();
3297 }
3298}
3299
3300nsIFrame* SVGTextFrame::GetFrameForPoint(const gfxPoint& aPoint) {
3301 NS_ASSERTION(PrincipalChildList().FirstChild(), "must have a child frame")do { if (!(PrincipalChildList().FirstChild())) { NS_DebugBreak
(NS_DEBUG_ASSERTION, "must have a child frame", "PrincipalChildList().FirstChild()"
, "./../../../layout/svg/SVGTextFrame.cpp", 3301); MOZ_PretendNoReturn
(); } } while (0)
;
3302
3303 if (HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) {
3304 // Text frames inside <clipPath> will never have had ReflowSVG called on
3305 // them, so call UpdateGlyphPositioning to do this now. (Text frames
3306 // inside <mask> and other non-display containers will never need to
3307 // be hit tested.)
3308 UpdateGlyphPositioning();
3309 } else {
3310 NS_ASSERTION(!IsSubtreeDirty(), "reflow should have happened")do { if (!(!IsSubtreeDirty())) { NS_DebugBreak(NS_DEBUG_ASSERTION
, "reflow should have happened", "!IsSubtreeDirty()", "./../../../layout/svg/SVGTextFrame.cpp"
, 3310); MOZ_PretendNoReturn(); } } while (0)
;
3311 }
3312
3313 // Hit-testing any clip-path will typically be a lot quicker than the
3314 // hit-testing of our text frames in the loop below, so we do the former up
3315 // front to avoid unnecessarily wasting cycles on the latter.
3316 if (!SVGUtils::HitTestClip(this, aPoint)) {
3317 return nullptr;
3318 }
3319
3320 nsPresContext* presContext = PresContext();
3321
3322 // Ideally we'd iterate backwards so that we can just return the first frame
3323 // that is under aPoint. In practice this will rarely matter though since it
3324 // is rare for text in/under an SVG <text> element to overlap (i.e. the first
3325 // text frame that is hit will likely be the only text frame that is hit).
3326
3327 TextRenderedRunIterator it(this);
3328 nsIFrame* hit = nullptr;
3329 for (TextRenderedRun run = it.Current(); run.mFrame; run = it.Next()) {
3330 if (SVGUtils::GetGeometryHitTestFlags(run.mFrame).isEmpty()) {
3331 continue;
3332 }
3333
3334 gfxMatrix m = run.GetTransformFromRunUserSpaceToUserSpace(presContext);
3335 if (!m.Invert()) {
3336 return nullptr;
3337 }
3338
3339 gfxPoint pointInRunUserSpace = m.TransformPoint(aPoint);
3340 gfxRect frameRect =
3341 run.GetRunUserSpaceRect({TextRenderedRun::GeometryFlag::IncludeFill,
3342 TextRenderedRun::GeometryFlag::IncludeStroke})
3343 .ToThebesRect();
3344
3345 if (frameRect.Contains(pointInRunUserSpace)) {
3346 hit = run.mFrame;
3347 }
3348 }
3349 return hit;
3350}
3351
3352void SVGTextFrame::ReflowSVG() {
3353 MOZ_ASSERT(SVGUtils::AnyOuterSVGIsCallingReflowSVG(this),do { static_assert( mozilla::detail::AssertionConditionType<
decltype(SVGUtils::AnyOuterSVGIsCallingReflowSVG(this))>::
isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(SVGUtils::AnyOuterSVGIsCallingReflowSVG(this)))), 0)
)) { do { } while (false); MOZ_ReportAssertionFailure("SVGUtils::AnyOuterSVGIsCallingReflowSVG(this)"
" (" "This call is probaby a wasteful mistake" ")", "./../../../layout/svg/SVGTextFrame.cpp"
, 3354); AnnotateMozCrashReason("MOZ_ASSERT" "(" "SVGUtils::AnyOuterSVGIsCallingReflowSVG(this)"
") (" "This call is probaby a wasteful mistake" ")"); do { MOZ_CrashSequence
(__null, 3354); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
3354 "This call is probaby a wasteful mistake")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(SVGUtils::AnyOuterSVGIsCallingReflowSVG(this))>::
isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(SVGUtils::AnyOuterSVGIsCallingReflowSVG(this)))), 0)
)) { do { } while (false); MOZ_ReportAssertionFailure("SVGUtils::AnyOuterSVGIsCallingReflowSVG(this)"
" (" "This call is probaby a wasteful mistake" ")", "./../../../layout/svg/SVGTextFrame.cpp"
, 3354); AnnotateMozCrashReason("MOZ_ASSERT" "(" "SVGUtils::AnyOuterSVGIsCallingReflowSVG(this)"
") (" "This call is probaby a wasteful mistake" ")"); do { MOZ_CrashSequence
(__null, 3354); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
3355
3356 MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_IS_NONDISPLAY),do { static_assert( mozilla::detail::AssertionConditionType<
decltype(!HasAnyStateBits(NS_FRAME_IS_NONDISPLAY))>::isValid
, "invalid assertion condition"); if ((__builtin_expect(!!(!(
!!(!HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)))), 0))) { do { }
while (false); MOZ_ReportAssertionFailure("!HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)"
" (" "ReflowSVG mechanism not designed for this" ")", "./../../../layout/svg/SVGTextFrame.cpp"
, 3357); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)"
") (" "ReflowSVG mechanism not designed for this" ")"); do {
MOZ_CrashSequence(__null, 3357); __attribute__((nomerge)) ::
abort(); } while (false); } } while (false)
3357 "ReflowSVG mechanism not designed for this")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(!HasAnyStateBits(NS_FRAME_IS_NONDISPLAY))>::isValid
, "invalid assertion condition"); if ((__builtin_expect(!!(!(
!!(!HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)))), 0))) { do { }
while (false); MOZ_ReportAssertionFailure("!HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)"
" (" "ReflowSVG mechanism not designed for this" ")", "./../../../layout/svg/SVGTextFrame.cpp"
, 3357); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)"
") (" "ReflowSVG mechanism not designed for this" ")"); do {
MOZ_CrashSequence(__null, 3357); __attribute__((nomerge)) ::
abort(); } while (false); } } while (false)
;
3358
3359 if (!SVGUtils::NeedsReflowSVG(this)) {
3360 MOZ_ASSERT(!HasAnyStateBits(NS_STATE_SVG_TEXT_CORRESPONDENCE_DIRTY |do { static_assert( mozilla::detail::AssertionConditionType<
decltype(!HasAnyStateBits(NS_STATE_SVG_TEXT_CORRESPONDENCE_DIRTY
| NS_STATE_SVG_POSITIONING_DIRTY))>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(!HasAnyStateBits(NS_STATE_SVG_TEXT_CORRESPONDENCE_DIRTY
| NS_STATE_SVG_POSITIONING_DIRTY)))), 0))) { do { } while (false
); MOZ_ReportAssertionFailure("!HasAnyStateBits(NS_STATE_SVG_TEXT_CORRESPONDENCE_DIRTY | NS_STATE_SVG_POSITIONING_DIRTY)"
" (" "How did this happen?" ")", "./../../../layout/svg/SVGTextFrame.cpp"
, 3362); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!HasAnyStateBits(NS_STATE_SVG_TEXT_CORRESPONDENCE_DIRTY | NS_STATE_SVG_POSITIONING_DIRTY)"
") (" "How did this happen?" ")"); do { MOZ_CrashSequence(__null
, 3362); __attribute__((nomerge)) ::abort(); } while (false);
} } while (false)
3361 NS_STATE_SVG_POSITIONING_DIRTY),do { static_assert( mozilla::detail::AssertionConditionType<
decltype(!HasAnyStateBits(NS_STATE_SVG_TEXT_CORRESPONDENCE_DIRTY
| NS_STATE_SVG_POSITIONING_DIRTY))>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(!HasAnyStateBits(NS_STATE_SVG_TEXT_CORRESPONDENCE_DIRTY
| NS_STATE_SVG_POSITIONING_DIRTY)))), 0))) { do { } while (false
); MOZ_ReportAssertionFailure("!HasAnyStateBits(NS_STATE_SVG_TEXT_CORRESPONDENCE_DIRTY | NS_STATE_SVG_POSITIONING_DIRTY)"
" (" "How did this happen?" ")", "./../../../layout/svg/SVGTextFrame.cpp"
, 3362); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!HasAnyStateBits(NS_STATE_SVG_TEXT_CORRESPONDENCE_DIRTY | NS_STATE_SVG_POSITIONING_DIRTY)"
") (" "How did this happen?" ")"); do { MOZ_CrashSequence(__null
, 3362); __attribute__((nomerge)) ::abort(); } while (false);
} } while (false)
3362 "How did this happen?")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(!HasAnyStateBits(NS_STATE_SVG_TEXT_CORRESPONDENCE_DIRTY
| NS_STATE_SVG_POSITIONING_DIRTY))>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(!HasAnyStateBits(NS_STATE_SVG_TEXT_CORRESPONDENCE_DIRTY
| NS_STATE_SVG_POSITIONING_DIRTY)))), 0))) { do { } while (false
); MOZ_ReportAssertionFailure("!HasAnyStateBits(NS_STATE_SVG_TEXT_CORRESPONDENCE_DIRTY | NS_STATE_SVG_POSITIONING_DIRTY)"
" (" "How did this happen?" ")", "./../../../layout/svg/SVGTextFrame.cpp"
, 3362); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!HasAnyStateBits(NS_STATE_SVG_TEXT_CORRESPONDENCE_DIRTY | NS_STATE_SVG_POSITIONING_DIRTY)"
") (" "How did this happen?" ")"); do { MOZ_CrashSequence(__null
, 3362); __attribute__((nomerge)) ::abort(); } while (false);
} } while (false)
;
3363 return;
3364 }
3365
3366 MaybeReflowAnonymousBlockChild();
3367 UpdateGlyphPositioning();
3368
3369 nsPresContext* presContext = PresContext();
3370
3371 SVGBBox r;
3372 TextRenderedRunIterator it(
3373 this, TextRenderedRunIterator::RenderedRunFilter::AllFrames);
3374 for (TextRenderedRun run = it.Current(); run.mFrame; run = it.Next()) {
3375 TextRenderedRun::GeometryFlags runFlags;
3376 if (!run.mFrame->StyleSVG()->mFill.kind.IsNone()) {
3377 runFlags += TextRenderedRun::GeometryFlag::IncludeFill;
3378 }
3379 if (SVGUtils::HasStroke(run.mFrame)) {
3380 runFlags += TextRenderedRun::GeometryFlag::IncludeStroke;
3381 }
3382 // Our "visual" overflow rect needs to be valid for building display lists
3383 // for hit testing, which means that for certain values of 'pointer-events'
3384 // it needs to include the geometry of the fill or stroke even when the
3385 // fill/ stroke don't actually render (e.g. when stroke="none" or
3386 // stroke-opacity="0"). GetGeometryHitTestFlags accounts for
3387 // 'pointer-events'. The text-shadow is not part of the hit-test area.
3388 SVGHitTestFlags hitTestFlags =
3389 SVGUtils::GetGeometryHitTestFlags(run.mFrame);
3390 if (hitTestFlags.contains(SVGHitTestFlag::Fill)) {
3391 runFlags += TextRenderedRun::GeometryFlag::IncludeFill;
3392 }
3393 if (hitTestFlags.contains(SVGHitTestFlag::Stroke)) {
3394 runFlags += TextRenderedRun::GeometryFlag::IncludeStroke;
3395 }
3396
3397 if (!runFlags.isEmpty()) {
3398 r.UnionEdges(run.GetUserSpaceRect(presContext, runFlags));
3399 }
3400 }
3401
3402 if (r.IsEmpty()) {
3403 mRect.SetEmpty();
3404 } else {
3405 mRect = nsLayoutUtils::RoundGfxRectToAppRect((const Rect&)r,
3406 AppUnitsPerCSSPixel());
3407
3408 // Due to rounding issues when we have a transform applied, we sometimes
3409 // don't include an additional row of pixels. For now, just inflate our
3410 // covered region.
3411 if (mLastContextScale != 0.0f) {
3412 mRect.Inflate(
3413 ceil(presContext->AppUnitsPerDevPixel() / mLastContextScale));
3414 }
3415 }
3416
3417 if (HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
3418 // Make sure we have our filter property (if any) before calling
3419 // FinishAndStoreOverflow (subsequent filter changes are handled off
3420 // nsChangeHint_UpdateEffects):
3421 SVGObserverUtils::UpdateEffects(this);
3422 }
3423
3424 // Now unset the various reflow bits. Do this before calling
3425 // FinishAndStoreOverflow since FinishAndStoreOverflow can require glyph
3426 // positions (to resolve transform-origin).
3427 RemoveStateBits(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY |
3428 NS_FRAME_HAS_DIRTY_CHILDREN);
3429
3430 nsRect overflow = nsRect(nsPoint(0, 0), mRect.Size());
3431 OverflowAreas overflowAreas(overflow, overflow);
3432 FinishAndStoreOverflow(overflowAreas, mRect.Size());
3433}
3434
3435/**
3436 * Converts SVGUtils::eBBox* flags into TextRenderedRun flags appropriate
3437 * for the specified rendered run.
3438 */
3439static TextRenderedRun::GeometryFlags TextRenderedRunFlagsForBBoxContribution(
3440 const TextRenderedRun& aRun, SVGBBoxFlags aBBoxFlags) {
3441 TextRenderedRun::GeometryFlags flags;
3442 if (aBBoxFlags.contains(SVGBBoxFlag::IncludeFillGeometry)) {
3443 flags += TextRenderedRun::GeometryFlag::IncludeFill;
3444 }
3445 if (aBBoxFlags.contains(SVGBBoxFlag::IncludeStrokeGeometry) ||
3446 (aBBoxFlags.contains(SVGBBoxFlag::IncludeStroke) &&
3447 SVGUtils::HasStroke(aRun.mFrame))) {
3448 flags += TextRenderedRun::GeometryFlag::IncludeStroke;
3449 }
3450 return flags;
3451}
3452
3453SVGBBox SVGTextFrame::GetBBoxContribution(const Matrix& aToBBoxUserspace,
3454 SVGBBoxFlags aFlags) {
3455 NS_ASSERTION(PrincipalChildList().FirstChild(), "must have a child frame")do { if (!(PrincipalChildList().FirstChild())) { NS_DebugBreak
(NS_DEBUG_ASSERTION, "must have a child frame", "PrincipalChildList().FirstChild()"
, "./../../../layout/svg/SVGTextFrame.cpp", 3455); MOZ_PretendNoReturn
(); } } while (0)
;
3456 SVGBBox bbox;
3457
3458 if (aFlags.contains(SVGBBoxFlag::ForGetClientRects)) {
3459 if (!mRect.IsEmpty()) {
3460 Rect rect = NSRectToRect(mRect, AppUnitsPerCSSPixel());
3461 bbox = aToBBoxUserspace.TransformBounds(rect);
3462 }
3463 return bbox;
3464 }
3465
3466 nsIFrame* kid = PrincipalChildList().FirstChild();
3467 if (kid && kid->IsSubtreeDirty()) {
3468 // Return an empty bbox if our kid's subtree is dirty. This may be called
3469 // in that situation, e.g. when we're building a display list after an
3470 // interrupted reflow. This can also be called during reflow before we've
3471 // been reflowed, e.g. if an earlier sibling is calling
3472 // FinishAndStoreOverflow and needs our parent's perspective matrix, which
3473 // depends on the SVG bbox contribution of this frame. In the latter
3474 // situation, when all siblings have been reflowed, the parent will compute
3475 // its perspective and rerun FinishAndStoreOverflow for all its children.
3476 return bbox;
3477 }
3478
3479 UpdateGlyphPositioning();
3480
3481 nsPresContext* presContext = PresContext();
3482
3483 TextRenderedRunIterator it(this);
3484 for (TextRenderedRun run = it.Current(); run.mFrame; run = it.Next()) {
3485 TextRenderedRun::GeometryFlags flags =
3486 TextRenderedRunFlagsForBBoxContribution(run, aFlags);
3487 gfxMatrix m = ThebesMatrix(aToBBoxUserspace);
3488 SVGBBox bboxForRun = run.GetUserSpaceRect(presContext, flags, &m);
3489 if (aFlags.contains(SVGBBoxFlag::DisregardCSSZoom)) {
3490 bboxForRun.Scale(1 / run.mFrame->Style()->EffectiveZoom().ToFloat());
3491 }
3492
3493 bbox.UnionEdges(bboxForRun);
3494 }
3495
3496 return bbox;
3497}
3498
3499//----------------------------------------------------------------------
3500// SVGTextFrame SVG DOM methods
3501
3502/**
3503 * Returns whether the specified node has any non-empty Text
3504 * beneath it.
3505 */
3506static bool HasTextContent(nsIContent* aContent) {
3507 NS_ASSERTION(aContent, "expected non-null aContent")do { if (!(aContent)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "expected non-null aContent"
, "aContent", "./../../../layout/svg/SVGTextFrame.cpp", 3507)
; MOZ_PretendNoReturn(); } } while (0)
;
3508
3509 TextNodeIterator it(aContent);
3510 for (Text* text = it.GetCurrent(); text; text = it.GetNext()) {
3511 if (text->TextLength() != 0) {
3512 return true;
3513 }
3514 }
3515 return false;
3516}
3517
3518/**
3519 * Returns the number of DOM characters beneath the specified node.
3520 */
3521static uint32_t GetTextContentLength(nsIContent* aContent) {
3522 NS_ASSERTION(aContent, "expected non-null aContent")do { if (!(aContent)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "expected non-null aContent"
, "aContent", "./../../../layout/svg/SVGTextFrame.cpp", 3522)
; MOZ_PretendNoReturn(); } } while (0)
;
3523
3524 uint32_t length = 0;
3525 TextNodeIterator it(aContent);
3526 for (Text* text = it.GetCurrent(); text; text = it.GetNext()) {
3527 length += text->TextLength();
3528 }
3529 return length;
3530}
3531
3532int32_t SVGTextFrame::ConvertTextElementCharIndexToAddressableIndex(
3533 int32_t aIndex, dom::SVGTextContentElement* aElement) {
3534 CharIterator it(this, CharIterator::CharacterFilter::Original, aElement);
3535 if (!it.AdvanceToSubtree()) {
3536 return -1;
3537 }
3538 int32_t result = 0;
3539 int32_t textElementCharIndex;
3540 while (!it.AtEnd() && it.IsWithinSubtree()) {
3541 bool addressable = !it.IsOriginalCharUnaddressable();
3542 textElementCharIndex = it.TextElementCharIndex();
3543 it.Next();
3544 uint32_t delta = it.TextElementCharIndex() - textElementCharIndex;
3545 aIndex -= delta;
3546 if (addressable) {
3547 if (aIndex < 0) {
3548 return result;
3549 }
3550 result += delta;
3551 }
3552 }
3553 return -1;
3554}
3555
3556/**
3557 * Implements the SVG DOM GetNumberOfChars method for the specified
3558 * text content element.
3559 */
3560uint32_t SVGTextFrame::GetNumberOfChars(dom::SVGTextContentElement* aElement) {
3561 nsIFrame* kid = PrincipalChildList().FirstChild();
3562 if (kid->IsSubtreeDirty()) {
3563 // We're never reflowed if we're under a non-SVG element that is
3564 // never reflowed (such as the HTML 'caption' element).
3565 return 0;
3566 }
3567
3568 UpdateGlyphPositioning();
3569
3570 uint32_t n = 0;
3571 CharIterator it(this, CharIterator::CharacterFilter::Addressable, aElement);
3572 if (it.AdvanceToSubtree()) {
3573 while (!it.AtEnd() && it.IsWithinSubtree()) {
3574 n++;
3575 it.Next();
3576 }
3577 }
3578 return n;
3579}
3580
3581/**
3582 * Implements the SVG DOM GetComputedTextLength method for the specified
3583 * text child element.
3584 */
3585float SVGTextFrame::GetComputedTextLength(
3586 dom::SVGTextContentElement* aElement) {
3587 nsIFrame* kid = PrincipalChildList().FirstChild();
3588 if (kid->IsSubtreeDirty()) {
3589 // We're never reflowed if we're under a non-SVG element that is
3590 // never reflowed (such as the HTML 'caption' element).
3591 //
3592 // If we ever decide that we need to return accurate values here,
3593 // we could do similar work to GetSubStringLength.
3594 return 0;
3595 }
3596
3597 UpdateGlyphPositioning();
3598
3599 float cssPxPerDevPx = nsPresContext::AppUnitsToFloatCSSPixels(
3600 PresContext()->AppUnitsPerDevPixel());
3601
3602 nscoord length = 0;
3603 TextRenderedRunIterator it(
3604 this, TextRenderedRunIterator::RenderedRunFilter::AllFrames, aElement);
3605 for (TextRenderedRun run = it.Current(); run.mFrame; run = it.Next()) {
3606 length +=
3607 run.GetAdvanceWidth() / run.mFrame->Style()->EffectiveZoom().ToFloat();
3608 }
3609
3610 return PresContext()->AppUnitsToGfxUnits(length) * cssPxPerDevPx *
3611 mLengthAdjustScaleFactor / mFontSizeScaleFactor;
3612}
3613
3614/**
3615 * Implements the SVG DOM SelectSubString method for the specified
3616 * text content element.
3617 */
3618void SVGTextFrame::SelectSubString(dom::SVGTextContentElement* aElement,
3619 uint32_t charnum, uint32_t nchars,
3620 ErrorResult& aRv) {
3621 nsIFrame* kid = PrincipalChildList().FirstChild();
3622 if (kid->IsSubtreeDirty()) {
3623 // We're never reflowed if we're under a non-SVG element that is
3624 // never reflowed (such as the HTML 'caption' element).
3625 // XXXbz Should this just return without throwing like the no-frame case?
3626 aRv.ThrowInvalidStateError("No layout information available for SVG text");
3627 return;
3628 }
3629
3630 UpdateGlyphPositioning();
3631
3632 RefPtr<nsIContent> content;
3633
3634 // Ensure the destructor of CharIterator runs before calling HandleClick.
3635 {
3636 // Convert charnum/nchars from addressable characters relative to
3637 // aElement to global character indices.
3638 CharIterator chit(this, CharIterator::CharacterFilter::Addressable,
3639 aElement);
3640 if (!chit.AdvanceToSubtree() || !chit.Next(charnum) ||
3641 chit.IsAfterSubtree()) {
3642 aRv.ThrowIndexSizeError("Character index out of range");
3643 return;
3644 }
3645 charnum = chit.TextElementCharIndex();
3646 content = chit.GetTextFrame()->GetContent();
3647 chit.NextWithinSubtree(nchars);
3648 nchars = chit.TextElementCharIndex() - charnum;
3649 }
3650
3651 RefPtr<nsFrameSelection> frameSelection = GetFrameSelection();
3652
3653 frameSelection->HandleClick(content, charnum, charnum + nchars,
3654 nsFrameSelection::FocusMode::kCollapseToNewPoint,
3655 CaretAssociationHint::Before);
3656}
3657
3658/**
3659 * For some content we cannot (or currently cannot) compute the length
3660 * without reflowing. In those cases we need to fall back to using
3661 * GetSubStringLengthSlowFallback.
3662 *
3663 * We fall back for textPath since we need glyph positioning in order to
3664 * tell if any characters should be ignored due to having fallen off the
3665 * end of the textPath.
3666 *
3667 * We fall back for bidi because GetTrimmedOffsets does not produce the
3668 * correct results for bidi continuations when passed aPostReflow = false.
3669 * XXX It may be possible to determine which continuations to trim from (and
3670 * which sides), but currently we don't do that. It would require us to
3671 * identify the visual (rather than logical) start and end of the line, to
3672 * avoid trimming at line-internal frame boundaries. Maybe nsBidiPresUtils
3673 * methods like GetFrameToRightOf and GetFrameToLeftOf would help?
3674 *
3675 */
3676bool SVGTextFrame::RequiresSlowFallbackForSubStringLength() {
3677 TextFrameIterator frameIter(this);
3678 for (nsTextFrame* frame = frameIter.GetCurrent(); frame;
3679 frame = frameIter.GetNext()) {
3680 if (frameIter.TextPathFrame() || frame->GetNextContinuation()) {
3681 return true;
3682 }
3683 }
3684 return false;
3685}
3686
3687/**
3688 * Implements the SVG DOM GetSubStringLength method for the specified
3689 * text content element.
3690 */
3691float SVGTextFrame::GetSubStringLengthFastPath(
3692 dom::SVGTextContentElement* aElement, uint32_t charnum, uint32_t nchars,
3693 ErrorResult& aRv) {
3694 MOZ_ASSERT(!RequiresSlowFallbackForSubStringLength())do { static_assert( mozilla::detail::AssertionConditionType<
decltype(!RequiresSlowFallbackForSubStringLength())>::isValid
, "invalid assertion condition"); if ((__builtin_expect(!!(!(
!!(!RequiresSlowFallbackForSubStringLength()))), 0))) { do { }
while (false); MOZ_ReportAssertionFailure("!RequiresSlowFallbackForSubStringLength()"
, "./../../../layout/svg/SVGTextFrame.cpp", 3694); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "!RequiresSlowFallbackForSubStringLength()"
")"); do { MOZ_CrashSequence(__null, 3694); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
3695
3696 // We only need our text correspondence to be up to date (no need to call
3697 // UpdateGlyphPositioning).
3698 TextNodeCorrespondenceRecorder::RecordCorrespondence(this);
3699
3700 // Convert charnum/nchars from addressable characters relative to
3701 // aElement to global character indices.
3702 CharIterator chit(this, CharIterator::CharacterFilter::Addressable, aElement,
3703 /* aPostReflow */ false);
3704 if (!chit.AdvanceToSubtree() || !chit.Next(charnum) ||
3705 chit.IsAfterSubtree()) {
3706 aRv.ThrowIndexSizeError("Character index out of range");
3707 return 0;
3708 }
3709
3710 // We do this after the ThrowIndexSizeError() bit so JS calls correctly throw
3711 // when necessary.
3712 if (nchars == 0) {
3713 return 0.0f;
3714 }
3715
3716 charnum = chit.TextElementCharIndex();
3717 chit.NextWithinSubtree(nchars);
3718 nchars = chit.TextElementCharIndex() - charnum;
3719
3720 // Sum of the substring advances.
3721 nscoord textLength = 0;
3722
3723 TextFrameIterator frit(this); // aSubtree = nullptr
3724
3725 // Index of the first non-skipped char in the frame, and of a subsequent char
3726 // that we're interested in. Both are relative to the index of the first
3727 // non-skipped char in the ancestor <text> element.
3728 uint32_t frameStartTextElementCharIndex = 0;
3729 uint32_t textElementCharIndex;
3730
3731 for (nsTextFrame* frame = frit.GetCurrent(); frame; frame = frit.GetNext()) {
3732 frameStartTextElementCharIndex += frit.UndisplayedCharacters();
3733 textElementCharIndex = frameStartTextElementCharIndex;
3734
3735 // Offset into frame's Text:
3736 const uint32_t untrimmedOffset = frame->GetContentOffset();
3737 const uint32_t untrimmedLength = frame->GetContentEnd() - untrimmedOffset;
3738
3739 // Trim the offset/length to remove any leading/trailing white space.
3740 uint32_t trimmedOffset = untrimmedOffset;
3741 uint32_t trimmedLength = untrimmedLength;
3742 nsTextFrame::TrimmedOffsets trimmedOffsets = frame->GetTrimmedOffsets(
3743 frame->CharacterDataBuffer(),
3744 nsTextFrame::TrimmedOffsetFlags::NotPostReflow);
3745 TrimOffsets(trimmedOffset, trimmedLength, trimmedOffsets);
3746
3747 textElementCharIndex += trimmedOffset - untrimmedOffset;
3748
3749 if (textElementCharIndex >= charnum + nchars) {
3750 break; // we're past the end of the substring
3751 }
3752
3753 uint32_t offset = textElementCharIndex;
3754
3755 // Intersect the substring we are interested in with the range covered by
3756 // the nsTextFrame.
3757 IntersectInterval(offset, trimmedLength, charnum, nchars);
3758
3759 if (trimmedLength != 0) {
3760 // Convert offset into an index into the frame.
3761 offset += trimmedOffset - textElementCharIndex;
3762
3763 gfxSkipCharsIterator it = frame->EnsureTextRun(nsTextFrame::eInflated);
3764 gfxTextRun* textRun = frame->GetTextRun(nsTextFrame::eInflated);
3765 auto& provider = PropertyProviderFor(frame);
3766
3767 Range range = ConvertOriginalToSkipped(it, offset, trimmedLength);
3768
3769 // Accumulate the advance.
3770 textLength += textRun->GetAdvanceWidth(range, &provider) /
3771 frame->Style()->EffectiveZoom().ToFloat();
3772 }
3773
3774 // Advance, ready for next call:
3775 frameStartTextElementCharIndex += untrimmedLength;
3776 }
3777
3778 nsPresContext* presContext = PresContext();
3779 float cssPxPerDevPx = nsPresContext::AppUnitsToFloatCSSPixels(
3780 presContext->AppUnitsPerDevPixel());
3781
3782 return presContext->AppUnitsToGfxUnits(textLength) * cssPxPerDevPx /
3783 mFontSizeScaleFactor;
3784}
3785
3786float SVGTextFrame::GetSubStringLengthSlowFallback(
3787 dom::SVGTextContentElement* aElement, uint32_t charnum, uint32_t nchars,
3788 ErrorResult& aRv) {
3789 UpdateGlyphPositioning();
3790
3791 // Convert charnum/nchars from addressable characters relative to
3792 // aElement to global character indices.
3793 CharIterator chit(this, CharIterator::CharacterFilter::Addressable, aElement);
3794 if (!chit.AdvanceToSubtree() || !chit.Next(charnum) ||
3795 chit.IsAfterSubtree()) {
3796 aRv.ThrowIndexSizeError("Character index out of range");
3797 return 0;
3798 }
3799
3800 if (nchars == 0) {
3801 return 0.0f;
3802 }
3803
3804 charnum = chit.TextElementCharIndex();
3805 chit.NextWithinSubtree(nchars);
3806 nchars = chit.TextElementCharIndex() - charnum;
3807
3808 // Find each rendered run that intersects with the range defined
3809 // by charnum/nchars.
3810 nscoord textLength = 0;
3811 TextRenderedRunIterator runIter(
3812 this, TextRenderedRunIterator::RenderedRunFilter::AllFrames);
3813 TextRenderedRun run = runIter.Current();
3814 while (run.mFrame) {
3815 // If this rendered run is past the substring we are interested in, we
3816 // are done.
3817 uint32_t offset = run.mTextElementCharIndex;
3818 if (offset >= charnum + nchars) {
3819 break;
3820 }
3821
3822 // Intersect the substring we are interested in with the range covered by
3823 // the rendered run.
3824 uint32_t length = run.mTextFrameContentLength;
3825 IntersectInterval(offset, length, charnum, nchars);
3826
3827 if (length != 0) {
3828 // Convert offset into an index into the frame.
3829 offset += run.mTextFrameContentOffset - run.mTextElementCharIndex;
3830
3831 gfxSkipCharsIterator it =
3832 run.mFrame->EnsureTextRun(nsTextFrame::eInflated);
3833 gfxTextRun* textRun = run.mFrame->GetTextRun(nsTextFrame::eInflated);
3834 auto& provider = PropertyProviderFor(run.mFrame);
3835
3836 Range range = ConvertOriginalToSkipped(it, offset, length);
3837
3838 // Accumulate the advance.
3839 textLength += textRun->GetAdvanceWidth(range, &provider) /
3840 run.mFrame->Style()->EffectiveZoom().ToFloat();
3841 }
3842
3843 run = runIter.Next();
3844 }
3845
3846 nsPresContext* presContext = PresContext();
3847 float cssPxPerDevPx = nsPresContext::AppUnitsToFloatCSSPixels(
3848 presContext->AppUnitsPerDevPixel());
3849
3850 return presContext->AppUnitsToGfxUnits(textLength) * cssPxPerDevPx /
3851 mFontSizeScaleFactor;
3852}
3853
3854/**
3855 * Implements the SVG DOM GetCharNumAtPosition method for the specified
3856 * text content element.
3857 */
3858int32_t SVGTextFrame::GetCharNumAtPosition(dom::SVGTextContentElement* aElement,
3859 const gfx::Point& aPoint) {
3860 nsIFrame* kid = PrincipalChildList().FirstChild();
3861 if (kid->IsSubtreeDirty()) {
3862 // We're never reflowed if we're under a non-SVG element that is
3863 // never reflowed (such as the HTML 'caption' element).
3864 return -1;
3865 }
3866
3867 UpdateGlyphPositioning();
3868
3869 nsPresContext* context = PresContext();
3870
3871 gfxPoint p = ThebesPoint(aPoint) * dom::UserSpaceMetrics::GetZoom(aElement);
3872
3873 int32_t result = -1;
3874
3875 TextRenderedRunIterator it(
3876 this, TextRenderedRunIterator::RenderedRunFilter::AllFrames, aElement);
3877 for (TextRenderedRun run = it.Current(); run.mFrame; run = it.Next()) {
3878 // Hit test this rendered run. Later runs will override earlier ones.
3879 int32_t index = run.GetCharNumAtPosition(context, p);
3880 if (index != -1) {
3881 result = index + run.mTextElementCharIndex;
3882 }
3883 }
3884
3885 if (result == -1) {
3886 return result;
3887 }
3888
3889 return ConvertTextElementCharIndexToAddressableIndex(result, aElement);
3890}
3891
3892/**
3893 * Implements the SVG DOM GetStartPositionOfChar method for the specified
3894 * text content element.
3895 */
3896already_AddRefed<DOMSVGPoint> SVGTextFrame::GetStartPositionOfChar(
3897 dom::SVGTextContentElement* aElement, uint32_t aCharNum, ErrorResult& aRv) {
3898 nsIFrame* kid = PrincipalChildList().FirstChild();
3899 if (kid->IsSubtreeDirty()) {
3900 // We're never reflowed if we're under a non-SVG element that is
3901 // never reflowed (such as the HTML 'caption' element).
3902 aRv.ThrowInvalidStateError("No layout information available for SVG text");
3903 return nullptr;
3904 }
3905
3906 UpdateGlyphPositioning();
3907
3908 CharIterator it(this, CharIterator::CharacterFilter::Addressable, aElement);
3909 if (!it.AdvanceToSubtree() || !it.Next(aCharNum) || it.IsAfterSubtree()) {
3910 aRv.ThrowIndexSizeError("Character index out of range");
3911 return nullptr;
3912 }
3913
3914 // We need to return the start position of the whole glyph.
3915 uint32_t startIndex = it.GlyphStartTextElementCharIndex();
3916
3917 return MakeAndAddRef<DOMSVGPoint>(
3918 ToPoint(mPositions[startIndex].mPosition) /
3919 it.GetTextFrame()->Style()->EffectiveZoom().ToFloat());
3920}
3921
3922/**
3923 * Returns the advance of the entire glyph whose starting character is at
3924 * aTextElementCharIndex.
3925 *
3926 * aIterator, if provided, must be a CharIterator that already points to
3927 * aTextElementCharIndex that is restricted to aContent and is using
3928 * filter mode CharacterFilter::Addressable.
3929 */
3930static gfxFloat GetGlyphAdvance(SVGTextFrame* aFrame,
3931 dom::SVGTextContentElement* aElement,
3932 uint32_t aTextElementCharIndex,
3933 CharIterator* aIterator) {
3934 MOZ_ASSERT(!aIterator || (aIterator->Filter() ==do { static_assert( mozilla::detail::AssertionConditionType<
decltype(!aIterator || (aIterator->Filter() == CharIterator
::CharacterFilter::Addressable && aIterator->GetSubtree
() == aElement && aIterator->GlyphStartTextElementCharIndex
() == aTextElementCharIndex))>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(!aIterator || (aIterator->
Filter() == CharIterator::CharacterFilter::Addressable &&
aIterator->GetSubtree() == aElement && aIterator->
GlyphStartTextElementCharIndex() == aTextElementCharIndex))))
, 0))) { do { } while (false); MOZ_ReportAssertionFailure("!aIterator || (aIterator->Filter() == CharIterator::CharacterFilter::Addressable && aIterator->GetSubtree() == aElement && aIterator->GlyphStartTextElementCharIndex() == aTextElementCharIndex)"
" (" "Invalid aIterator" ")", "./../../../layout/svg/SVGTextFrame.cpp"
, 3939); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!aIterator || (aIterator->Filter() == CharIterator::CharacterFilter::Addressable && aIterator->GetSubtree() == aElement && aIterator->GlyphStartTextElementCharIndex() == aTextElementCharIndex)"
") (" "Invalid aIterator" ")"); do { MOZ_CrashSequence(__null
, 3939); __attribute__((nomerge)) ::abort(); } while (false);
} } while (false)
3935 CharIterator::CharacterFilter::Addressable &&do { static_assert( mozilla::detail::AssertionConditionType<
decltype(!aIterator || (aIterator->Filter() == CharIterator
::CharacterFilter::Addressable && aIterator->GetSubtree
() == aElement && aIterator->GlyphStartTextElementCharIndex
() == aTextElementCharIndex))>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(!aIterator || (aIterator->
Filter() == CharIterator::CharacterFilter::Addressable &&
aIterator->GetSubtree() == aElement && aIterator->
GlyphStartTextElementCharIndex() == aTextElementCharIndex))))
, 0))) { do { } while (false); MOZ_ReportAssertionFailure("!aIterator || (aIterator->Filter() == CharIterator::CharacterFilter::Addressable && aIterator->GetSubtree() == aElement && aIterator->GlyphStartTextElementCharIndex() == aTextElementCharIndex)"
" (" "Invalid aIterator" ")", "./../../../layout/svg/SVGTextFrame.cpp"
, 3939); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!aIterator || (aIterator->Filter() == CharIterator::CharacterFilter::Addressable && aIterator->GetSubtree() == aElement && aIterator->GlyphStartTextElementCharIndex() == aTextElementCharIndex)"
") (" "Invalid aIterator" ")"); do { MOZ_CrashSequence(__null
, 3939); __attribute__((nomerge)) ::abort(); } while (false);
} } while (false)
3936 aIterator->GetSubtree() == aElement &&do { static_assert( mozilla::detail::AssertionConditionType<
decltype(!aIterator || (aIterator->Filter() == CharIterator
::CharacterFilter::Addressable && aIterator->GetSubtree
() == aElement && aIterator->GlyphStartTextElementCharIndex
() == aTextElementCharIndex))>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(!aIterator || (aIterator->
Filter() == CharIterator::CharacterFilter::Addressable &&
aIterator->GetSubtree() == aElement && aIterator->
GlyphStartTextElementCharIndex() == aTextElementCharIndex))))
, 0))) { do { } while (false); MOZ_ReportAssertionFailure("!aIterator || (aIterator->Filter() == CharIterator::CharacterFilter::Addressable && aIterator->GetSubtree() == aElement && aIterator->GlyphStartTextElementCharIndex() == aTextElementCharIndex)"
" (" "Invalid aIterator" ")", "./../../../layout/svg/SVGTextFrame.cpp"
, 3939); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!aIterator || (aIterator->Filter() == CharIterator::CharacterFilter::Addressable && aIterator->GetSubtree() == aElement && aIterator->GlyphStartTextElementCharIndex() == aTextElementCharIndex)"
") (" "Invalid aIterator" ")"); do { MOZ_CrashSequence(__null
, 3939); __attribute__((nomerge)) ::abort(); } while (false);
} } while (false)
3937 aIterator->GlyphStartTextElementCharIndex() ==do { static_assert( mozilla::detail::AssertionConditionType<
decltype(!aIterator || (aIterator->Filter() == CharIterator
::CharacterFilter::Addressable && aIterator->GetSubtree
() == aElement && aIterator->GlyphStartTextElementCharIndex
() == aTextElementCharIndex))>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(!aIterator || (aIterator->
Filter() == CharIterator::CharacterFilter::Addressable &&
aIterator->GetSubtree() == aElement && aIterator->
GlyphStartTextElementCharIndex() == aTextElementCharIndex))))
, 0))) { do { } while (false); MOZ_ReportAssertionFailure("!aIterator || (aIterator->Filter() == CharIterator::CharacterFilter::Addressable && aIterator->GetSubtree() == aElement && aIterator->GlyphStartTextElementCharIndex() == aTextElementCharIndex)"
" (" "Invalid aIterator" ")", "./../../../layout/svg/SVGTextFrame.cpp"
, 3939); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!aIterator || (aIterator->Filter() == CharIterator::CharacterFilter::Addressable && aIterator->GetSubtree() == aElement && aIterator->GlyphStartTextElementCharIndex() == aTextElementCharIndex)"
") (" "Invalid aIterator" ")"); do { MOZ_CrashSequence(__null
, 3939); __attribute__((nomerge)) ::abort(); } while (false);
} } while (false)
3938 aTextElementCharIndex),do { static_assert( mozilla::detail::AssertionConditionType<
decltype(!aIterator || (aIterator->Filter() == CharIterator
::CharacterFilter::Addressable && aIterator->GetSubtree
() == aElement && aIterator->GlyphStartTextElementCharIndex
() == aTextElementCharIndex))>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(!aIterator || (aIterator->
Filter() == CharIterator::CharacterFilter::Addressable &&
aIterator->GetSubtree() == aElement && aIterator->
GlyphStartTextElementCharIndex() == aTextElementCharIndex))))
, 0))) { do { } while (false); MOZ_ReportAssertionFailure("!aIterator || (aIterator->Filter() == CharIterator::CharacterFilter::Addressable && aIterator->GetSubtree() == aElement && aIterator->GlyphStartTextElementCharIndex() == aTextElementCharIndex)"
" (" "Invalid aIterator" ")", "./../../../layout/svg/SVGTextFrame.cpp"
, 3939); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!aIterator || (aIterator->Filter() == CharIterator::CharacterFilter::Addressable && aIterator->GetSubtree() == aElement && aIterator->GlyphStartTextElementCharIndex() == aTextElementCharIndex)"
") (" "Invalid aIterator" ")"); do { MOZ_CrashSequence(__null
, 3939); __attribute__((nomerge)) ::abort(); } while (false);
} } while (false)
3939 "Invalid aIterator")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(!aIterator || (aIterator->Filter() == CharIterator
::CharacterFilter::Addressable && aIterator->GetSubtree
() == aElement && aIterator->GlyphStartTextElementCharIndex
() == aTextElementCharIndex))>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(!aIterator || (aIterator->
Filter() == CharIterator::CharacterFilter::Addressable &&
aIterator->GetSubtree() == aElement && aIterator->
GlyphStartTextElementCharIndex() == aTextElementCharIndex))))
, 0))) { do { } while (false); MOZ_ReportAssertionFailure("!aIterator || (aIterator->Filter() == CharIterator::CharacterFilter::Addressable && aIterator->GetSubtree() == aElement && aIterator->GlyphStartTextElementCharIndex() == aTextElementCharIndex)"
" (" "Invalid aIterator" ")", "./../../../layout/svg/SVGTextFrame.cpp"
, 3939); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!aIterator || (aIterator->Filter() == CharIterator::CharacterFilter::Addressable && aIterator->GetSubtree() == aElement && aIterator->GlyphStartTextElementCharIndex() == aTextElementCharIndex)"
") (" "Invalid aIterator" ")"); do { MOZ_CrashSequence(__null
, 3939); __attribute__((nomerge)) ::abort(); } while (false);
} } while (false)
;
3940
3941 Maybe<CharIterator> newIterator;
3942 CharIterator* it = aIterator;
3943 if (!it) {
3944 newIterator.emplace(aFrame, CharIterator::CharacterFilter::Addressable,
3945 aElement);
3946 if (!newIterator->AdvanceToSubtree()) {
3947 MOZ_ASSERT_UNREACHABLE("Invalid aElement")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 aElement" ")", "./../../../layout/svg/SVGTextFrame.cpp"
, 3947); AnnotateMozCrashReason("MOZ_ASSERT" "(" "false" ") ("
"MOZ_ASSERT_UNREACHABLE: " "Invalid aElement" ")"); do { MOZ_CrashSequence
(__null, 3947); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
3948 return 0.0;
3949 }
3950 it = newIterator.ptr();
3951 }
3952
3953 while (it->GlyphStartTextElementCharIndex() != aTextElementCharIndex) {
3954 if (!it->Next()) {
3955 MOZ_ASSERT_UNREACHABLE("Invalid aTextElementCharIndex")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 aTextElementCharIndex" ")", "./../../../layout/svg/SVGTextFrame.cpp"
, 3955); AnnotateMozCrashReason("MOZ_ASSERT" "(" "false" ") ("
"MOZ_ASSERT_UNREACHABLE: " "Invalid aTextElementCharIndex" ")"
); do { MOZ_CrashSequence(__null, 3955); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
3956 return 0.0;
3957 }
3958 }
3959
3960 if (it->AtEnd()) {
3961 MOZ_ASSERT_UNREACHABLE("Invalid aTextElementCharIndex")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 aTextElementCharIndex" ")", "./../../../layout/svg/SVGTextFrame.cpp"
, 3961); AnnotateMozCrashReason("MOZ_ASSERT" "(" "false" ") ("
"MOZ_ASSERT_UNREACHABLE: " "Invalid aTextElementCharIndex" ")"
); do { MOZ_CrashSequence(__null, 3961); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
3962 return 0.0;
3963 }
3964
3965 nsPresContext* presContext = aFrame->PresContext();
3966 gfxFloat advance = 0.0;
3967
3968 for (;;) {
3969 advance += it->GetAdvance(presContext);
3970 if (!it->Next() ||
3971 it->GlyphStartTextElementCharIndex() != aTextElementCharIndex) {
3972 break;
3973 }
3974 }
3975
3976 return advance;
3977}
3978
3979/**
3980 * Implements the SVG DOM GetEndPositionOfChar method for the specified
3981 * text content element.
3982 */
3983already_AddRefed<DOMSVGPoint> SVGTextFrame::GetEndPositionOfChar(
3984 dom::SVGTextContentElement* aElement, uint32_t aCharNum, ErrorResult& aRv) {
3985 nsIFrame* kid = PrincipalChildList().FirstChild();
3986 if (kid->IsSubtreeDirty()) {
3987 // We're never reflowed if we're under a non-SVG element that is
3988 // never reflowed (such as the HTML 'caption' element).
3989 aRv.ThrowInvalidStateError("No layout information available for SVG text");
3990 return nullptr;
3991 }
3992
3993 UpdateGlyphPositioning();
3994
3995 CharIterator it(this, CharIterator::CharacterFilter::Addressable, aElement);
3996 if (!it.AdvanceToSubtree() || !it.Next(aCharNum) || it.IsAfterSubtree()) {
3997 aRv.ThrowIndexSizeError("Character index out of range");
3998 return nullptr;
3999 }
4000
4001 // We need to return the end position of the whole glyph.
4002 uint32_t startIndex = it.GlyphStartTextElementCharIndex();
4003 float zoom = it.GetTextFrame()->Style()->EffectiveZoom().ToFloat();
4004
4005 // Get the advance of the glyph.
4006 gfxFloat advance =
4007 GetGlyphAdvance(this, aElement, startIndex,
4008 it.IsClusterAndLigatureGroupStart() ? &it : nullptr) /
4009 mFontSizeScaleFactor;
4010 const gfxTextRun* textRun = it.TextRun();
4011 if (textRun->IsInlineReversed()) {
4012 advance = -advance;
4013 }
4014 Point p = textRun->IsVertical() ? Point(0, advance) : Point(advance, 0);
4015
4016 // The end position is the start position plus the advance in the direction
4017 // of the glyph's rotation.
4018 Matrix m = Matrix::Rotation(mPositions[startIndex].mAngle) *
4019 Matrix::Translation(ToPoint(mPositions[startIndex].mPosition));
4020
4021 return MakeAndAddRef<DOMSVGPoint>(m.TransformPoint(p) / zoom);
4022}
4023
4024/**
4025 * Implements the SVG DOM GetExtentOfChar method for the specified
4026 * text content element.
4027 */
4028already_AddRefed<SVGRect> SVGTextFrame::GetExtentOfChar(
4029 dom::SVGTextContentElement* aElement, uint32_t aCharNum, ErrorResult& aRv) {
4030 nsIFrame* kid = PrincipalChildList().FirstChild();
4031 if (kid->IsSubtreeDirty()) {
4032 // We're never reflowed if we're under a non-SVG element that is
4033 // never reflowed (such as the HTML 'caption' element).
4034 aRv.ThrowInvalidStateError("No layout information available for SVG text");
4035 return nullptr;
4036 }
4037
4038 UpdateGlyphPositioning();
4039
4040 // Search for the character whose addressable index is aCharNum.
4041 CharIterator it(this, CharIterator::CharacterFilter::Addressable, aElement);
4042 if (!it.AdvanceToSubtree() || !it.Next(aCharNum) || it.IsAfterSubtree()) {
4043 aRv.ThrowIndexSizeError("Character index out of range");
4044 return nullptr;
4045 }
4046
4047 nsPresContext* presContext = PresContext();
4048 float cssPxPerDevPx = nsPresContext::AppUnitsToFloatCSSPixels(
4049 presContext->AppUnitsPerDevPixel());
4050
4051 nsTextFrame* textFrame = it.GetTextFrame();
4052 uint32_t startIndex = it.GlyphStartTextElementCharIndex();
4053 const gfxTextRun* textRun = it.TextRun();
4054
4055 // Get the glyph advance.
4056 gfxFloat advance =
4057 GetGlyphAdvance(this, aElement, startIndex,
4058 it.IsClusterAndLigatureGroupStart() ? &it : nullptr);
4059 gfxFloat x = textRun->IsInlineReversed() ? -advance : 0.0;
4060
4061 // The ascent and descent gives the height of the glyph.
4062 gfxFloat ascent, descent;
4063 GetAscentAndDescentInAppUnits(textFrame, ascent, descent);
4064
4065 // The horizontal extent is the origin of the glyph plus the advance
4066 // in the direction of the glyph's rotation.
4067 gfxMatrix m;
4068 m.PreTranslate(mPositions[startIndex].mPosition);
4069 m.PreRotate(mPositions[startIndex].mAngle);
4070 m.PreScale(1 / mFontSizeScaleFactor, 1 / mFontSizeScaleFactor);
4071
4072 nscoord baseline = GetBaselinePosition(
4073 textFrame, textRun, it.DominantBaseline(), mFontSizeScaleFactor);
4074
4075 gfxRect glyphRect;
4076 if (textRun->IsVertical()) {
4077 glyphRect = gfxRect(
4078 -presContext->AppUnitsToGfxUnits(baseline) * cssPxPerDevPx, x,
4079 presContext->AppUnitsToGfxUnits(ascent + descent) * cssPxPerDevPx,
4080 advance);
4081 } else {
4082 glyphRect = gfxRect(
4083 x, -presContext->AppUnitsToGfxUnits(baseline) * cssPxPerDevPx, advance,
4084 presContext->AppUnitsToGfxUnits(ascent + descent) * cssPxPerDevPx);
4085 }
4086
4087 // Transform the glyph's rect into user space.
4088 gfxRect r = m.TransformBounds(glyphRect);
4089 r.Scale(1 / textFrame->Style()->EffectiveZoom().ToFloat());
4090
4091 return MakeAndAddRef<SVGRect>(aElement, ToRect(r));
4092}
4093
4094/**
4095 * Implements the SVG DOM GetRotationOfChar method for the specified
4096 * text content element.
4097 */
4098float SVGTextFrame::GetRotationOfChar(dom::SVGTextContentElement* aElement,
4099 uint32_t aCharNum, ErrorResult& aRv) {
4100 nsIFrame* kid = PrincipalChildList().FirstChild();
4101 if (kid->IsSubtreeDirty()) {
4102 // We're never reflowed if we're under a non-SVG element that is
4103 // never reflowed (such as the HTML 'caption' element).
4104 aRv.ThrowInvalidStateError("No layout information available for SVG text");
4105 return 0;
4106 }
4107
4108 UpdateGlyphPositioning();
4109
4110 CharIterator it(this, CharIterator::CharacterFilter::Addressable, aElement);
4111 if (!it.AdvanceToSubtree() || !it.Next(aCharNum) || it.IsAfterSubtree()) {
4112 aRv.ThrowIndexSizeError("Character index out of range");
4113 return 0;
4114 }
4115
4116 // We need to account for the glyph's underlying orientation.
4117 const gfxTextRun::GlyphRun& glyphRun = it.GlyphRun();
4118 int32_t glyphOrientation =
4119 90 * (glyphRun.IsSidewaysRight() - glyphRun.IsSidewaysLeft());
4120
4121 return mPositions[it.TextElementCharIndex()].mAngle * 180.0 /
4122 std::numbers::pi +
4123 glyphOrientation;
4124}
4125
4126//----------------------------------------------------------------------
4127// SVGTextFrame text layout methods
4128
4129/**
4130 * Given the character position array before values have been filled in
4131 * to any unspecified positions, and an array of dx/dy values, returns whether
4132 * a character at a given index should start a new rendered run.
4133 *
4134 * @param aPositions The array of character positions before unspecified
4135 * positions have been filled in and dx/dy values have been added to them.
4136 * @param aDeltas The array of dx/dy values.
4137 * @param aIndex The character index in question.
4138 */
4139static bool ShouldStartRunAtIndex(const nsTArray<CharPosition>& aPositions,
4140 const nsTArray<gfxPoint>& aDeltas,
4141 uint32_t aIndex) {
4142 if (aIndex == 0) {
4143 return true;
4144 }
4145
4146 if (aIndex < aPositions.Length()) {
4147 // If an explicit x or y value was given, start a new run.
4148 if (aPositions[aIndex].IsXSpecified() ||
4149 aPositions[aIndex].IsYSpecified()) {
4150 return true;
4151 }
4152
4153 // If a non-zero rotation was given, or the previous character had a non-
4154 // zero rotation, start a new run.
4155 if ((aPositions[aIndex].IsAngleSpecified() &&
4156 aPositions[aIndex].mAngle != 0.0f) ||
4157 (aPositions[aIndex - 1].IsAngleSpecified() &&
4158 (aPositions[aIndex - 1].mAngle != 0.0f))) {
4159 return true;
4160 }
4161 }
4162
4163 if (aIndex < aDeltas.Length()) {
4164 // If a non-zero dx or dy value was given, start a new run.
4165 if (aDeltas[aIndex].x != 0.0 || aDeltas[aIndex].y != 0.0) {
4166 return true;
4167 }
4168 }
4169
4170 return false;
4171}
4172
4173bool SVGTextFrame::ResolvePositionsForNode(nsIContent* aContent,
4174 uint32_t& aIndex, bool aInTextPath,
4175 bool& aForceStartOfChunk,
4176 nsTArray<gfxPoint>& aDeltas) {
4177 if (aContent->IsText()) {
4178 // We found a text node.
4179 uint32_t length = aContent->AsText()->TextLength();
4180 if (length) {
4181 uint32_t end = aIndex + length;
4182 if (MOZ_UNLIKELY(end > mPositions.Length())(__builtin_expect(!!(end > mPositions.Length()), 0))) {
4183 MOZ_ASSERT_UNREACHABLE(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: "
"length of mPositions does not match characters " "found by iterating content"
")", "./../../../layout/svg/SVGTextFrame.cpp", 4185); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "false" ") (" "MOZ_ASSERT_UNREACHABLE: " "length of mPositions does not match characters "
"found by iterating content" ")"); do { MOZ_CrashSequence(__null
, 4185); __attribute__((nomerge)) ::abort(); } while (false);
} } while (false)
4184 "length of mPositions does not match characters "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: "
"length of mPositions does not match characters " "found by iterating content"
")", "./../../../layout/svg/SVGTextFrame.cpp", 4185); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "false" ") (" "MOZ_ASSERT_UNREACHABLE: " "length of mPositions does not match characters "
"found by iterating content" ")"); do { MOZ_CrashSequence(__null
, 4185); __attribute__((nomerge)) ::abort(); } while (false);
} } while (false)
4185 "found by iterating content")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: "
"length of mPositions does not match characters " "found by iterating content"
")", "./../../../layout/svg/SVGTextFrame.cpp", 4185); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "false" ") (" "MOZ_ASSERT_UNREACHABLE: " "length of mPositions does not match characters "
"found by iterating content" ")"); do { MOZ_CrashSequence(__null
, 4185); __attribute__((nomerge)) ::abort(); } while (false);
} } while (false)
;
4186 return false;
4187 }
4188 if (aForceStartOfChunk) {
4189 // Note this character as starting a new anchored chunk.
4190 mPositions[aIndex].mStartOfChunk = true;
4191 aForceStartOfChunk = false;
4192 }
4193 while (aIndex < end) {
4194 // Record whether each of these characters should start a new rendered
4195 // run. That is always the case for characters on a text path.
4196 //
4197 // Run boundaries due to rotate="" values are handled in
4198 // DoGlyphPositioning.
4199 if (aInTextPath || ShouldStartRunAtIndex(mPositions, aDeltas, aIndex)) {
4200 mPositions[aIndex].mRunBoundary = true;
4201 }
4202 aIndex++;
4203 }
4204 }
4205 return true;
4206 }
4207
4208 // Skip past elements that aren't text content elements.
4209 if (!IsTextContentElement(aContent)) {
4210 return true;
4211 }
4212
4213 if (aContent->IsSVGElement(nsGkAtoms::textPath)) {
4214 // Any ‘y’ attributes on horizontal <textPath> elements are ignored.
4215 // Similarly, for vertical <texPath>s x attributes are ignored.
4216 // <textPath> elements behave as if they have x="0" y="0" on them, but only
4217 // if there is not a value for the non-ignored coordinate that got inherited
4218 // from a parent. We skip this if there is no text content, so that empty
4219 // <textPath>s don't interrupt the layout of text in the parent element.
4220 if (HasTextContent(aContent)) {
4221 if (MOZ_UNLIKELY(aIndex >= mPositions.Length())(__builtin_expect(!!(aIndex >= mPositions.Length()), 0))) {
4222 MOZ_ASSERT_UNREACHABLE(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: "
"length of mPositions does not match characters " "found by iterating content"
")", "./../../../layout/svg/SVGTextFrame.cpp", 4224); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "false" ") (" "MOZ_ASSERT_UNREACHABLE: " "length of mPositions does not match characters "
"found by iterating content" ")"); do { MOZ_CrashSequence(__null
, 4224); __attribute__((nomerge)) ::abort(); } while (false);
} } while (false)
4223 "length of mPositions does not match characters "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: "
"length of mPositions does not match characters " "found by iterating content"
")", "./../../../layout/svg/SVGTextFrame.cpp", 4224); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "false" ") (" "MOZ_ASSERT_UNREACHABLE: " "length of mPositions does not match characters "
"found by iterating content" ")"); do { MOZ_CrashSequence(__null
, 4224); __attribute__((nomerge)) ::abort(); } while (false);
} } while (false)
4224 "found by iterating content")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: "
"length of mPositions does not match characters " "found by iterating content"
")", "./../../../layout/svg/SVGTextFrame.cpp", 4224); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "false" ") (" "MOZ_ASSERT_UNREACHABLE: " "length of mPositions does not match characters "
"found by iterating content" ")"); do { MOZ_CrashSequence(__null
, 4224); __attribute__((nomerge)) ::abort(); } while (false);
} } while (false)
;
4225 return false;
4226 }
4227 bool vertical = GetWritingMode().IsVertical();
4228 if (vertical || !mPositions[aIndex].IsXSpecified()) {
4229 mPositions[aIndex].mPosition.x = 0.0;
4230 }
4231 if (!vertical || !mPositions[aIndex].IsYSpecified()) {
4232 mPositions[aIndex].mPosition.y = 0.0;
4233 }
4234 mPositions[aIndex].mStartOfChunk = true;
4235 }
4236 } else if (!aContent->IsSVGElement(nsGkAtoms::a)) {
4237 MOZ_ASSERT(aContent->IsSVGElement())do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aContent->IsSVGElement())>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(aContent->IsSVGElement())
)), 0))) { do { } while (false); MOZ_ReportAssertionFailure("aContent->IsSVGElement()"
, "./../../../layout/svg/SVGTextFrame.cpp", 4237); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "aContent->IsSVGElement()" ")"); do { MOZ_CrashSequence
(__null, 4237); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
4238
4239 // We have a text content element that can have x/y/dx/dy/rotate attributes.
4240 SVGElement* element = static_cast<SVGElement*>(aContent);
4241
4242 // Get x, y, dx, dy.
4243 SVGUserUnitList x, y, dx, dy;
4244 element->GetAnimatedLengthListValues(&x, &y, &dx, &dy, nullptr);
4245
4246 // Get rotate.
4247 const SVGNumberList* rotate = nullptr;
4248 SVGAnimatedNumberList* animatedRotate =
4249 element->GetAnimatedNumberList(nsGkAtoms::rotate);
4250 if (animatedRotate) {
4251 rotate = &animatedRotate->GetAnimValue();
4252 }
4253
4254 bool percentages = false;
4255 uint32_t count = GetTextContentLength(aContent);
4256
4257 if (MOZ_UNLIKELY(aIndex + count > mPositions.Length())(__builtin_expect(!!(aIndex + count > mPositions.Length())
, 0))
) {
4258 MOZ_ASSERT_UNREACHABLE(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: "
"length of mPositions does not match characters " "found by iterating content"
")", "./../../../layout/svg/SVGTextFrame.cpp", 4260); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "false" ") (" "MOZ_ASSERT_UNREACHABLE: " "length of mPositions does not match characters "
"found by iterating content" ")"); do { MOZ_CrashSequence(__null
, 4260); __attribute__((nomerge)) ::abort(); } while (false);
} } while (false)
4259 "length of mPositions does not match characters "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: "
"length of mPositions does not match characters " "found by iterating content"
")", "./../../../layout/svg/SVGTextFrame.cpp", 4260); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "false" ") (" "MOZ_ASSERT_UNREACHABLE: " "length of mPositions does not match characters "
"found by iterating content" ")"); do { MOZ_CrashSequence(__null
, 4260); __attribute__((nomerge)) ::abort(); } while (false);
} } while (false)
4260 "found by iterating content")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: "
"length of mPositions does not match characters " "found by iterating content"
")", "./../../../layout/svg/SVGTextFrame.cpp", 4260); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "false" ") (" "MOZ_ASSERT_UNREACHABLE: " "length of mPositions does not match characters "
"found by iterating content" ")"); do { MOZ_CrashSequence(__null
, 4260); __attribute__((nomerge)) ::abort(); } while (false);
} } while (false)
;
4261 return false;
4262 }
4263
4264 // New text anchoring chunks start at each character assigned a position
4265 // with x="" or y="", or if we forced one with aForceStartOfChunk due to
4266 // being just after a <textPath>.
4267 uint32_t newChunkCount = std::max(x.Length(), y.Length());
4268 if (!newChunkCount && aForceStartOfChunk) {
4269 newChunkCount = 1;
4270 }
4271 for (uint32_t i = 0, j = 0; i < newChunkCount && j < count; j++) {
4272 if (!mPositions[aIndex + j].mUnaddressable) {
4273 mPositions[aIndex + j].mStartOfChunk = true;
4274 i++;
4275 }
4276 }
4277
4278 // Copy dx="" and dy="" values into aDeltas.
4279 if (!dx.IsEmpty() || !dy.IsEmpty()) {
4280 // Any unspecified deltas when we grow the array just get left as 0s.
4281 aDeltas.EnsureLengthAtLeast(aIndex + count);
4282 for (uint32_t i = 0, j = 0; i < dx.Length() && j < count; j++) {
4283 if (!mPositions[aIndex + j].mUnaddressable) {
4284 aDeltas[aIndex + j].x = dx[i];
4285 percentages = percentages || dx.HasPercentageValueAt(i);
4286 i++;
4287 }
4288 }
4289 for (uint32_t i = 0, j = 0; i < dy.Length() && j < count; j++) {
4290 if (!mPositions[aIndex + j].mUnaddressable) {
4291 aDeltas[aIndex + j].y = dy[i];
4292 percentages = percentages || dy.HasPercentageValueAt(i);
4293 i++;
4294 }
4295 }
4296 }
4297
4298 // Copy x="" and y="" values.
4299 for (uint32_t i = 0, j = 0; i < x.Length() && j < count; j++) {
4300 if (!mPositions[aIndex + j].mUnaddressable) {
4301 mPositions[aIndex + j].mPosition.x = x[i];
4302 percentages = percentages || x.HasPercentageValueAt(i);
4303 i++;
4304 }
4305 }
4306 for (uint32_t i = 0, j = 0; i < y.Length() && j < count; j++) {
4307 if (!mPositions[aIndex + j].mUnaddressable) {
4308 mPositions[aIndex + j].mPosition.y = y[i];
4309 percentages = percentages || y.HasPercentageValueAt(i);
4310 i++;
4311 }
4312 }
4313
4314 // Copy rotate="" values.
4315 if (rotate && !rotate->IsEmpty()) {
4316 uint32_t i = 0, j = 0;
4317 while (i < rotate->Length() && j < count) {
4318 if (!mPositions[aIndex + j].mUnaddressable) {
4319 mPositions[aIndex + j].mAngle =
4320 std::numbers::pi * (*rotate)[i] / 180.0;
4321 i++;
4322 }
4323 j++;
4324 }
4325 // Propagate final rotate="" value to the end of this element.
4326 while (j < count) {
4327 mPositions[aIndex + j].mAngle = mPositions[aIndex + j - 1].mAngle;
4328 j++;
4329 }
4330 }
4331
4332 if (percentages) {
4333 AddStateBits(NS_STATE_SVG_POSITIONING_MAY_USE_PERCENTAGES);
4334 }
4335 }
4336
4337 // Recurse to children.
4338 bool inTextPath = aInTextPath || aContent->IsSVGElement(nsGkAtoms::textPath);
4339 for (nsIContent* child = aContent->GetFirstChild(); child;
4340 child = child->GetNextSibling()) {
4341 bool ok = ResolvePositionsForNode(child, aIndex, inTextPath,
4342 aForceStartOfChunk, aDeltas);
4343 if (!ok) {
4344 return false;
4345 }
4346 }
4347
4348 if (aContent->IsSVGElement(nsGkAtoms::textPath)) {
4349 // Force a new anchored chunk just after a <textPath>.
4350 aForceStartOfChunk = true;
4351 }
4352
4353 return true;
4354}
4355
4356bool SVGTextFrame::ResolvePositions(nsTArray<gfxPoint>& aDeltas,
4357 bool aRunPerGlyph) {
4358 NS_ASSERTION(mPositions.IsEmpty(), "expected mPositions to be empty")do { if (!(mPositions.IsEmpty())) { NS_DebugBreak(NS_DEBUG_ASSERTION
, "expected mPositions to be empty", "mPositions.IsEmpty()", "./../../../layout/svg/SVGTextFrame.cpp"
, 4358); MOZ_PretendNoReturn(); } } while (0)
;
4359 RemoveStateBits(NS_STATE_SVG_POSITIONING_MAY_USE_PERCENTAGES);
4360
4361 CharIterator it(this, CharIterator::CharacterFilter::Original,
4362 /* aSubtree */ nullptr);
4363 if (it.AtEnd()) {
4364 return false;
4365 }
4366
4367 // We assume the first character position is (0,0) unless we later see
4368 // otherwise, and note it as unaddressable if it is.
4369 bool firstCharUnaddressable = it.IsOriginalCharUnaddressable();
4370 mPositions.AppendElement(CharPosition::Unspecified(firstCharUnaddressable));
4371
4372 // Fill in unspecified positions for all remaining characters, noting
4373 // them as unaddressable if they are.
4374 uint32_t index = 0;
4375 while (it.Next()) {
4376 while (++index < it.TextElementCharIndex()) {
4377 mPositions.AppendElement(CharPosition::Unspecified(false));
4378 }
4379 mPositions.AppendElement(
4380 CharPosition::Unspecified(it.IsOriginalCharUnaddressable()));
4381 }
4382 while (++index < it.TextElementCharIndex()) {
4383 mPositions.AppendElement(CharPosition::Unspecified(false));
4384 }
4385
4386 // Recurse over the content and fill in character positions as we go.
4387 bool forceStartOfChunk = false;
4388 index = 0;
4389 bool ok = ResolvePositionsForNode(mContent, index, aRunPerGlyph,
4390 forceStartOfChunk, aDeltas);
4391 return ok && index > 0;
4392}
4393
4394void SVGTextFrame::DetermineCharPositions(nsTArray<nsPoint>& aPositions) {
4395 NS_ASSERTION(aPositions.IsEmpty(), "expected aPositions to be empty")do { if (!(aPositions.IsEmpty())) { NS_DebugBreak(NS_DEBUG_ASSERTION
, "expected aPositions to be empty", "aPositions.IsEmpty()", "./../../../layout/svg/SVGTextFrame.cpp"
, 4395); MOZ_PretendNoReturn(); } } while (0)
;
4396
4397 nsPoint position;
4398
4399 TextFrameIterator frit(this);
4400 for (nsTextFrame* frame = frit.GetCurrent(); frame; frame = frit.GetNext()) {
4401 gfxSkipCharsIterator it = frame->EnsureTextRun(nsTextFrame::eInflated);
4402 gfxTextRun* textRun = frame->GetTextRun(nsTextFrame::eInflated);
4403 auto& provider = PropertyProviderFor(frame);
4404
4405 // Reset the position to the new frame's position.
4406 position = frit.Position();
4407 if (textRun->IsVertical()) {
4408 if (textRun->IsInlineReversed()) {
4409 position.y += frame->GetRect().height;
4410 }
4411 position.x += GetBaselinePosition(frame, textRun, frit.DominantBaseline(),
4412 mFontSizeScaleFactor);
4413 } else {
4414 if (textRun->IsInlineReversed()) {
4415 position.x += frame->GetRect().width;
4416 }
4417 position.y += GetBaselinePosition(frame, textRun, frit.DominantBaseline(),
4418 mFontSizeScaleFactor);
4419 }
4420
4421 // Any characters not in a frame, e.g. when display:none.
4422 for (uint32_t i = 0; i < frit.UndisplayedCharacters(); i++) {
4423 aPositions.AppendElement(position);
4424 }
4425
4426 // Any white space characters trimmed at the start of the line of text.
4427 nsTextFrame::TrimmedOffsets trimmedOffsets =
4428 frame->GetTrimmedOffsets(frame->CharacterDataBuffer());
4429 while (it.GetOriginalOffset() < trimmedOffsets.mStart) {
4430 aPositions.AppendElement(position);
4431 it.AdvanceOriginal(1);
4432 }
4433
4434 // Visible characters in the text frame.
4435 while (it.GetOriginalOffset() < frame->GetContentEnd()) {
4436 aPositions.AppendElement(position);
4437 if (!it.IsOriginalCharSkipped()) {
4438 // Accumulate partial ligature advance into position. (We must get
4439 // partial advances rather than get the advance of the whole ligature
4440 // group / cluster at once, since the group may span text frames, and
4441 // the PropertyProvider only has spacing information for the current
4442 // text frame.)
4443 uint32_t offset = it.GetSkippedOffset();
4444 nscoord advance =
4445 textRun->GetAdvanceWidth(Range(offset, offset + 1), &provider);
4446 (textRun->IsVertical() ? position.y : position.x) +=
4447 textRun->IsInlineReversed() ? -advance : advance;
4448 }
4449 it.AdvanceOriginal(1);
4450 }
4451 }
4452
4453 // Finally any characters at the end that are not in a frame.
4454 for (uint32_t i = 0; i < frit.UndisplayedCharacters(); i++) {
4455 aPositions.AppendElement(position);
4456 }
4457
4458 // Clear any cached PropertyProvider, to avoid risk of re-using it after
4459 // style changes or other mutations may have invalidated it.
4460 ForgetCachedProvider();
4461}
4462
4463/**
4464 * Physical text-anchor values.
4465 */
4466enum class TextAnchorSide { Left, Middle, Right };
4467
4468/**
4469 * Converts a logical text-anchor value to its physical value, based on whether
4470 * it is for an RTL frame.
4471 */
4472static TextAnchorSide ConvertLogicalTextAnchorToPhysical(
4473 StyleTextAnchor aTextAnchor, bool aIsRightToLeft) {
4474 NS_ASSERTION(uint8_t(aTextAnchor) <= 3, "unexpected value for aTextAnchor")do { if (!(uint8_t(aTextAnchor) <= 3)) { NS_DebugBreak(NS_DEBUG_ASSERTION
, "unexpected value for aTextAnchor", "uint8_t(aTextAnchor) <= 3"
, "./../../../layout/svg/SVGTextFrame.cpp", 4474); MOZ_PretendNoReturn
(); } } while (0)
;
4475 if (!aIsRightToLeft) {
4476 return TextAnchorSide(uint8_t(aTextAnchor));
4477 }
4478 return TextAnchorSide(2 - uint8_t(aTextAnchor));
4479}
4480
4481/**
4482 * Shifts the recorded character positions for an anchored chunk.
4483 *
4484 * @param aCharPositions The recorded character positions.
4485 * @param aChunkStart The character index the starts the anchored chunk. This
4486 * character's initial position is the anchor point.
4487 * @param aChunkEnd The character index just after the end of the anchored
4488 * chunk.
4489 * @param aVisIStartEdge The left/top-most edge of any of the glyphs within the
4490 * anchored chunk.
4491 * @param aVisIEndEdge The right/bottom-most edge of any of the glyphs within
4492 * the anchored chunk.
4493 * @param aAnchorSide The direction to anchor.
4494 */
4495static void ShiftAnchoredChunk(nsTArray<CharPosition>& aCharPositions,
4496 uint32_t aChunkStart, uint32_t aChunkEnd,
4497 gfxFloat aVisIStartEdge, gfxFloat aVisIEndEdge,
4498 TextAnchorSide aAnchorSide, bool aVertical) {
4499 NS_ASSERTION(aVisIStartEdge <= aVisIEndEdge,do { if (!(aVisIStartEdge <= aVisIEndEdge)) { NS_DebugBreak
(NS_DEBUG_ASSERTION, "unexpected anchored chunk edges", "aVisIStartEdge <= aVisIEndEdge"
, "./../../../layout/svg/SVGTextFrame.cpp", 4500); MOZ_PretendNoReturn
(); } } while (0)
4500 "unexpected anchored chunk edges")do { if (!(aVisIStartEdge <= aVisIEndEdge)) { NS_DebugBreak
(NS_DEBUG_ASSERTION, "unexpected anchored chunk edges", "aVisIStartEdge <= aVisIEndEdge"
, "./../../../layout/svg/SVGTextFrame.cpp", 4500); MOZ_PretendNoReturn
(); } } while (0)
;
4501 NS_ASSERTION(aChunkStart < aChunkEnd,do { if (!(aChunkStart < aChunkEnd)) { NS_DebugBreak(NS_DEBUG_ASSERTION
, "unexpected values for aChunkStart and aChunkEnd", "aChunkStart < aChunkEnd"
, "./../../../layout/svg/SVGTextFrame.cpp", 4502); MOZ_PretendNoReturn
(); } } while (0)
4502 "unexpected values for aChunkStart and aChunkEnd")do { if (!(aChunkStart < aChunkEnd)) { NS_DebugBreak(NS_DEBUG_ASSERTION
, "unexpected values for aChunkStart and aChunkEnd", "aChunkStart < aChunkEnd"
, "./../../../layout/svg/SVGTextFrame.cpp", 4502); MOZ_PretendNoReturn
(); } } while (0)
;
4503
4504 gfxFloat shift = aVertical ? aCharPositions[aChunkStart].mPosition.y
4505 : aCharPositions[aChunkStart].mPosition.x;
4506 switch (aAnchorSide) {
4507 case TextAnchorSide::Left:
4508 shift -= aVisIStartEdge;
4509 break;
4510 case TextAnchorSide::Middle:
4511 shift -= std::midpoint(aVisIStartEdge, aVisIEndEdge);
4512 break;
4513 case TextAnchorSide::Right:
4514 shift -= aVisIEndEdge;
4515 break;
4516 }
4517
4518 if (shift != 0.0) {
4519 if (aVertical) {
4520 for (uint32_t i = aChunkStart; i < aChunkEnd; i++) {
4521 aCharPositions[i].mPosition.y += shift;
4522 }
4523 } else {
4524 for (uint32_t i = aChunkStart; i < aChunkEnd; i++) {
4525 aCharPositions[i].mPosition.x += shift;
4526 }
4527 }
4528 }
4529}
4530
4531void SVGTextFrame::AdjustChunksForLineBreaks() {
4532 nsBlockFrame* block = do_QueryFrame(PrincipalChildList().FirstChild());
4533 NS_ASSERTION(block, "expected block frame")do { if (!(block)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "expected block frame"
, "block", "./../../../layout/svg/SVGTextFrame.cpp", 4533); MOZ_PretendNoReturn
(); } } while (0)
;
4534
4535 nsBlockFrame::LineIterator line = block->LinesBegin();
4536
4537 CharIterator it(this, CharIterator::CharacterFilter::Original,
4538 /* aSubtree */ nullptr);
4539 while (!it.AtEnd() && line != block->LinesEnd()) {
4540 if (it.GetTextFrame() == line->mFirstChild) {
4541 mPositions[it.TextElementCharIndex()].mStartOfChunk = true;
4542 line++;
4543 }
4544 it.AdvancePastCurrentFrame();
4545 }
4546}
4547
4548void SVGTextFrame::AdjustPositionsForClusters() {
4549 nsPresContext* presContext = PresContext();
4550
4551 // Find all of the characters that are in the middle of a cluster or
4552 // ligature group, and adjust their positions and rotations to match
4553 // the first character of the cluster/group.
4554 //
4555 // Also move the boundaries of text rendered runs and anchored chunks to
4556 // not lie in the middle of cluster/group.
4557
4558 // The partial advance of the current cluster or ligature group that we
4559 // have accumulated.
4560 gfxFloat partialAdvance = 0.0;
4561
4562 CharIterator it(this, CharIterator::CharacterFilter::Unskipped,
4563 /* aSubtree */ nullptr);
4564 bool isFirst = true;
4565 while (!it.AtEnd()) {
4566 if (it.IsClusterAndLigatureGroupStart() || isFirst) {
4567 // If we're at the start of a new cluster or ligature group, reset our
4568 // accumulated partial advance. Also treat the beginning of the text as
4569 // an anchor, even if it is a combining character and therefore was
4570 // marked as being a Unicode cluster continuation.
4571 partialAdvance = 0.0;
4572 isFirst = false;
4573 } else {
4574 // Otherwise, we're in the middle of a cluster or ligature group, and
4575 // we need to use the currently accumulated partial advance to adjust
4576 // the character's position and rotation.
4577
4578 // Find the start of the cluster/ligature group.
4579 uint32_t charIndex = it.TextElementCharIndex();
4580 uint32_t startIndex = it.GlyphStartTextElementCharIndex();
4581 MOZ_ASSERT(charIndex != startIndex,do { static_assert( mozilla::detail::AssertionConditionType<
decltype(charIndex != startIndex)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(charIndex != startIndex))), 0
))) { do { } while (false); MOZ_ReportAssertionFailure("charIndex != startIndex"
" (" "If the current character is in the middle of a cluster or "
"ligature group, then charIndex must be different from " "startIndex"
")", "./../../../layout/svg/SVGTextFrame.cpp", 4584); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "charIndex != startIndex" ") (" "If the current character is in the middle of a cluster or "
"ligature group, then charIndex must be different from " "startIndex"
")"); do { MOZ_CrashSequence(__null, 4584); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
4582 "If the current character is in the middle of a cluster or "do { static_assert( mozilla::detail::AssertionConditionType<
decltype(charIndex != startIndex)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(charIndex != startIndex))), 0
))) { do { } while (false); MOZ_ReportAssertionFailure("charIndex != startIndex"
" (" "If the current character is in the middle of a cluster or "
"ligature group, then charIndex must be different from " "startIndex"
")", "./../../../layout/svg/SVGTextFrame.cpp", 4584); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "charIndex != startIndex" ") (" "If the current character is in the middle of a cluster or "
"ligature group, then charIndex must be different from " "startIndex"
")"); do { MOZ_CrashSequence(__null, 4584); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
4583 "ligature group, then charIndex must be different from "do { static_assert( mozilla::detail::AssertionConditionType<
decltype(charIndex != startIndex)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(charIndex != startIndex))), 0
))) { do { } while (false); MOZ_ReportAssertionFailure("charIndex != startIndex"
" (" "If the current character is in the middle of a cluster or "
"ligature group, then charIndex must be different from " "startIndex"
")", "./../../../layout/svg/SVGTextFrame.cpp", 4584); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "charIndex != startIndex" ") (" "If the current character is in the middle of a cluster or "
"ligature group, then charIndex must be different from " "startIndex"
")"); do { MOZ_CrashSequence(__null, 4584); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
4584 "startIndex")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(charIndex != startIndex)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(charIndex != startIndex))), 0
))) { do { } while (false); MOZ_ReportAssertionFailure("charIndex != startIndex"
" (" "If the current character is in the middle of a cluster or "
"ligature group, then charIndex must be different from " "startIndex"
")", "./../../../layout/svg/SVGTextFrame.cpp", 4584); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "charIndex != startIndex" ") (" "If the current character is in the middle of a cluster or "
"ligature group, then charIndex must be different from " "startIndex"
")"); do { MOZ_CrashSequence(__null, 4584); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
4585
4586 mPositions[charIndex].mClusterOrLigatureGroupMiddle = true;
4587
4588 // Don't allow different rotations on ligature parts.
4589 bool rotationAdjusted = false;
4590 double angle = mPositions[startIndex].mAngle;
4591 if (mPositions[charIndex].mAngle != angle) {
4592 mPositions[charIndex].mAngle = angle;
4593 rotationAdjusted = true;
4594 }
4595
4596 // Update the character position.
4597 gfxFloat advance = partialAdvance / mFontSizeScaleFactor;
4598 const gfxTextRun* textRun = it.TextRun();
4599 gfxPoint direction = gfxPoint(cos(angle), sin(angle)) *
4600 (textRun->IsInlineReversed() ? -1.0 : 1.0);
4601 if (textRun->IsVertical()) {
4602 std::swap(direction.x, direction.y);
4603 }
4604 mPositions[charIndex].mPosition =
4605 mPositions[startIndex].mPosition + direction * advance;
4606
4607 // Ensure any runs that would end in the middle of a ligature now end just
4608 // after the ligature.
4609 if (mPositions[charIndex].mRunBoundary) {
4610 mPositions[charIndex].mRunBoundary = false;
4611 if (charIndex + 1 < mPositions.Length()) {
4612 mPositions[charIndex + 1].mRunBoundary = true;
4613 }
4614 } else if (rotationAdjusted) {
4615 if (charIndex + 1 < mPositions.Length()) {
4616 mPositions[charIndex + 1].mRunBoundary = true;
4617 }
4618 }
4619
4620 // Ensure any anchored chunks that would begin in the middle of a ligature
4621 // now begin just after the ligature.
4622 if (mPositions[charIndex].mStartOfChunk) {
4623 mPositions[charIndex].mStartOfChunk = false;
4624 if (charIndex + 1 < mPositions.Length()) {
4625 mPositions[charIndex + 1].mStartOfChunk = true;
4626 }
4627 }
4628 }
4629
4630 // Accumulate the current character's partial advance.
4631 partialAdvance += it.GetAdvance(presContext);
4632
4633 it.Next();
4634 }
4635}
4636
4637already_AddRefed<Path> SVGTextFrame::GetTextPath(nsIFrame* aTextPathFrame) {
4638 nsIContent* content = aTextPathFrame->GetContent();
4639 SVGTextPathElement* tp = static_cast<SVGTextPathElement*>(content);
4640 if (tp->mPath.IsRendered()) {
4641 // This is just an attribute so there's no transform that can apply
4642 // so we can just return the path directly.
4643 return tp->mPath.GetAnimValue().BuildPathForMeasuring(
4644 aTextPathFrame->Style()->EffectiveZoom().ToFloat());
4645 }
4646
4647 SVGGeometryElement* geomElement =
4648 SVGObserverUtils::GetAndObserveTextPathsPath(aTextPathFrame);
4649 if (!geomElement) {
4650 return nullptr;
4651 }
4652
4653 RefPtr<Path> path = geomElement->GetOrBuildPathForMeasuring();
4654 if (!path) {
4655 return nullptr;
4656 }
4657
4658 // Apply the geometry element's transform if appropriate.
4659 auto matrix = geomElement->LocalTransform();
4660 if (!matrix.IsIdentity()) {
4661 Path::Transform(path, matrix);
4662 }
4663
4664 return path.forget();
4665}
4666
4667gfxFloat SVGTextFrame::GetOffsetScale(nsIFrame* aTextPathFrame) {
4668 nsIContent* content = aTextPathFrame->GetContent();
4669 SVGTextPathElement* tp = static_cast<SVGTextPathElement*>(content);
4670 if (tp->mPath.IsRendered()) {
4671 // A path attribute has no pathLength or transform
4672 // so we return a unit scale.
4673 return 1.0;
4674 }
4675
4676 SVGGeometryElement* geomElement =
4677 SVGObserverUtils::GetAndObserveTextPathsPath(aTextPathFrame);
4678 if (!geomElement) {
4679 return 1.0;
4680 }
4681 return geomElement->GetPathLengthScale(
4682 SVGGeometryElement::PathLengthScaleUsageType::TextPath);
4683}
4684
4685gfxFloat SVGTextFrame::GetStartOffset(nsIFrame* aTextPathFrame) {
4686 SVGTextPathElement* tp =
4687 static_cast<SVGTextPathElement*>(aTextPathFrame->GetContent());
4688 SVGAnimatedLength* length =
4689 &tp->mLengthAttributes[SVGTextPathElement::STARTOFFSET];
4690
4691 if (length->IsPercentage()) {
4692 if (!std::isfinite(GetOffsetScale(aTextPathFrame))) {
4693 // Either pathLength="0" for this path or the path has 0 length.
4694 return 0.0;
4695 }
4696 RefPtr<Path> data = GetTextPath(aTextPathFrame);
4697 return data ? length->GetAnimValInSpecifiedUnits() * data->ComputeLength() /
4698 100.0
4699 : 0.0;
4700 }
4701 float lengthValue = length->GetAnimValueWithZoom(tp);
4702 // If offsetScale is infinity we want to return 0 not NaN
4703 return lengthValue == 0 ? 0.0 : lengthValue * GetOffsetScale(aTextPathFrame);
4704}
4705
4706void SVGTextFrame::DoTextPathLayout() {
4707 nsPresContext* context = PresContext();
4708
4709 CharIterator it(this, CharIterator::CharacterFilter::Original,
4710 /* aSubtree */ nullptr);
4711 while (!it.AtEnd()) {
4712 nsIFrame* textPathFrame = it.TextPathFrame();
4713 if (!textPathFrame) {
4714 // Skip past this frame if we're not in a text path.
4715 it.AdvancePastCurrentFrame();
4716 continue;
4717 }
4718
4719 // Get the path itself.
4720 RefPtr<Path> path = GetTextPath(textPathFrame);
4721 if (!path) {
4722 uint32_t start = it.TextElementCharIndex();
4723 it.AdvancePastCurrentTextPathFrame();
4724 uint32_t end = it.TextElementCharIndex();
4725 for (uint32_t i = start; i < end; i++) {
4726 mPositions[i].mHidden = true;
4727 }
4728 continue;
4729 }
4730
4731 SVGTextPathElement* textPath =
4732 static_cast<SVGTextPathElement*>(textPathFrame->GetContent());
4733 uint16_t side =
4734 textPath->EnumAttributes()[SVGTextPathElement::SIDE].GetAnimValue();
4735
4736 gfxFloat offset = GetStartOffset(textPathFrame);
4737 Float pathLength = path->ComputeLength();
4738
4739 // If the first character within the text path is in the middle of a
4740 // cluster or ligature group, just skip it and don't apply text path
4741 // positioning.
4742 while (!it.AtEnd()) {
4743 if (it.IsOriginalCharSkipped()) {
4744 it.Next();
4745 continue;
4746 }
4747 if (it.IsClusterAndLigatureGroupStart()) {
4748 break;
4749 }
4750 it.Next();
4751 }
4752
4753 bool skippedEndOfTextPath = false;
4754
4755 // Loop for each character in the text path.
4756 while (!it.AtEnd() && it.TextPathFrame() &&
4757 it.TextPathFrame()->GetContent() == textPath) {
4758 // The index of the cluster or ligature group's first character.
4759 uint32_t i = it.TextElementCharIndex();
4760
4761 // The index of the next character of the cluster or ligature.
4762 // We track this as we loop over the characters below so that we
4763 // can detect undisplayed characters and append entries into
4764 // partialAdvances for them.
4765 uint32_t j = i + 1;
4766
4767 MOZ_ASSERT(!mPositions[i].mClusterOrLigatureGroupMiddle)do { static_assert( mozilla::detail::AssertionConditionType<
decltype(!mPositions[i].mClusterOrLigatureGroupMiddle)>::isValid
, "invalid assertion condition"); if ((__builtin_expect(!!(!(
!!(!mPositions[i].mClusterOrLigatureGroupMiddle))), 0))) { do
{ } while (false); MOZ_ReportAssertionFailure("!mPositions[i].mClusterOrLigatureGroupMiddle"
, "./../../../layout/svg/SVGTextFrame.cpp", 4767); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "!mPositions[i].mClusterOrLigatureGroupMiddle"
")"); do { MOZ_CrashSequence(__null, 4767); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
4768
4769 const gfxTextRun* textRun = it.TextRun();
4770 bool vertical = textRun->IsVertical();
4771
4772 // Compute cumulative advances for each character of the cluster or
4773 // ligature group.
4774 AutoTArray<gfxFloat, 4> partialAdvances;
4775 gfxFloat partialAdvance = it.GetAdvance(context);
4776 partialAdvances.AppendElement(partialAdvance);
4777 while (it.Next()) {
4778 // Append entries for any undisplayed characters the CharIterator
4779 // skipped over.
4780 MOZ_ASSERT(j <= it.TextElementCharIndex())do { static_assert( mozilla::detail::AssertionConditionType<
decltype(j <= it.TextElementCharIndex())>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(j <= it.TextElementCharIndex
()))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("j <= it.TextElementCharIndex()", "./../../../layout/svg/SVGTextFrame.cpp"
, 4780); AnnotateMozCrashReason("MOZ_ASSERT" "(" "j <= it.TextElementCharIndex()"
")"); do { MOZ_CrashSequence(__null, 4780); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
4781 while (j < it.TextElementCharIndex()) {
4782 partialAdvances.AppendElement(partialAdvance);
4783 ++j;
4784 }
4785 // This loop may end up outside of the current text path, but
4786 // that's OK; we'll consider any complete cluster or ligature
4787 // group that begins inside the text path as being affected
4788 // by it.
4789 if (it.IsOriginalCharSkipped()) {
4790 if (!it.TextPathFrame()) {
4791 skippedEndOfTextPath = true;
4792 break;
4793 }
4794 // Leave partialAdvance unchanged.
4795 } else if (it.IsClusterAndLigatureGroupStart()) {
4796 break;
4797 } else {
4798 partialAdvance += it.GetAdvance(context);
4799 }
4800 partialAdvances.AppendElement(partialAdvance);
4801 }
4802
4803 if (!skippedEndOfTextPath) {
4804 // Any final undisplayed characters the CharIterator skipped over.
4805 MOZ_ASSERT(j <= it.TextElementCharIndex())do { static_assert( mozilla::detail::AssertionConditionType<
decltype(j <= it.TextElementCharIndex())>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(j <= it.TextElementCharIndex
()))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("j <= it.TextElementCharIndex()", "./../../../layout/svg/SVGTextFrame.cpp"
, 4805); AnnotateMozCrashReason("MOZ_ASSERT" "(" "j <= it.TextElementCharIndex()"
")"); do { MOZ_CrashSequence(__null, 4805); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
4806 while (j < it.TextElementCharIndex()) {
4807 partialAdvances.AppendElement(partialAdvance);
4808 ++j;
4809 }
4810 }
4811
4812 gfxFloat halfAdvance =
4813 partialAdvances.LastElement() / mFontSizeScaleFactor / 2.0;
4814 if (textRun->IsInlineReversed()) {
4815 halfAdvance = -halfAdvance;
4816 }
4817 gfxFloat midx =
4818 (vertical ? mPositions[i].mPosition.y : mPositions[i].mPosition.x) +
4819 halfAdvance + offset;
4820
4821 // Hide the character if it falls off the end of the path.
4822 mPositions[i].mHidden = midx < 0 || midx > pathLength;
4823
4824 // Position the character on the path at the right angle.
4825 Point tangent; // Unit vector tangent to the point we find.
4826 Point pt;
4827 if (side == dom::SVGTextPathElement_Binding::TEXTPATH_SIDETYPE_RIGHT) {
4828 pt = path->ComputePointAtLength(Float(pathLength - midx), &tangent);
4829 tangent = -tangent;
4830 } else {
4831 pt = path->ComputePointAtLength(Float(midx), &tangent);
4832 }
4833 Float rotation = vertical ? atan2f(-tangent.x, tangent.y)
4834 : atan2f(tangent.y, tangent.x);
4835 Point normal(-tangent.y, tangent.x); // Unit vector normal to the point.
4836 Point offsetFromPath = normal * (vertical ? -mPositions[i].mPosition.x
4837 : mPositions[i].mPosition.y);
4838 pt += offsetFromPath;
4839 mPositions[i].mPosition =
4840 ThebesPoint(pt) - ThebesPoint(tangent) * halfAdvance;
4841 mPositions[i].mAngle += rotation;
4842 Point direction = textRun->IsInlineReversed() ? -tangent : tangent;
4843
4844 // Position any characters for a partial ligature.
4845 for (uint32_t k = i + 1; k < j; k++) {
4846 gfxPoint partialAdvance = ThebesPoint(direction) *
4847 partialAdvances[k - i] / mFontSizeScaleFactor;
4848 mPositions[k].mPosition = mPositions[i].mPosition + partialAdvance;
4849 mPositions[k].mAngle = mPositions[i].mAngle;
4850 mPositions[k].mHidden = mPositions[i].mHidden;
4851 }
4852 }
4853 }
4854}
4855
4856void SVGTextFrame::DoAnchoring() {
4857 nsPresContext* presContext = PresContext();
4858
4859 CharIterator it(this, CharIterator::CharacterFilter::Original,
4860 /* aSubtree */ nullptr);
4861
4862 // Don't need to worry about skipped or trimmed characters.
4863 while (!it.AtEnd() &&
4864 (it.IsOriginalCharSkipped() || it.IsOriginalCharTrimmed())) {
4865 it.Next();
4866 }
4867
4868 bool vertical = GetWritingMode().IsVertical();
4869 for (uint32_t start = it.TextElementCharIndex(); start < mPositions.Length();
4870 start = it.TextElementCharIndex()) {
4871 it.AdvanceToCharacter(start);
4872 nsTextFrame* chunkFrame = it.GetTextFrame();
4873
4874 // Measure characters in this chunk to find the left-most and right-most
4875 // edges of all glyphs within the chunk.
4876 uint32_t index = it.TextElementCharIndex();
4877 uint32_t end = start;
Value stored to 'end' during its initialization is never read
4878 gfxFloat left = std::numeric_limits<gfxFloat>::infinity();
4879 gfxFloat right = -std::numeric_limits<gfxFloat>::infinity();
4880 do {
4881 if (!it.IsOriginalCharSkipped() && !it.IsOriginalCharTrimmed()) {
4882 gfxFloat advance = it.GetAdvance(presContext) / mFontSizeScaleFactor;
4883 const gfxTextRun* textRun = it.TextRun();
4884 gfxFloat pos = textRun->IsVertical() ? mPositions[index].mPosition.y
4885 : mPositions[index].mPosition.x;
4886 if (textRun->IsInlineReversed()) {
4887 left = std::min(left, pos - advance);
4888 right = std::max(right, pos);
4889 } else {
4890 left = std::min(left, pos);
4891 right = std::max(right, pos + advance);
4892 }
4893 }
4894 it.Next();
4895 index = end = it.TextElementCharIndex();
4896 } while (!it.AtEnd() && !mPositions[end].mStartOfChunk);
4897
4898 if (left != std::numeric_limits<gfxFloat>::infinity()) {
4899 bool isRTL =
4900 chunkFrame->StyleVisibility()->mDirection == StyleDirection::Rtl;
4901 TextAnchorSide anchor = ConvertLogicalTextAnchorToPhysical(
4902 chunkFrame->StyleSVG()->mTextAnchor, isRTL);
4903
4904 ShiftAnchoredChunk(mPositions, start, end, left, right, anchor, vertical);
4905 }
4906 }
4907}
4908
4909void SVGTextFrame::DoGlyphPositioning() {
4910 mPositions.Clear();
4911 RemoveStateBits(NS_STATE_SVG_POSITIONING_DIRTY);
4912
4913 nsIFrame* kid = PrincipalChildList().FirstChild();
4914 if (kid && kid->IsSubtreeDirty()) {
4915 MOZ_ASSERT(false, "should have already reflowed the kid")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(false)>::isValid, "invalid assertion condition");
if ((__builtin_expect(!!(!(!!(false))), 0))) { do { } while (
false); MOZ_ReportAssertionFailure("false" " (" "should have already reflowed the kid"
")", "./../../../layout/svg/SVGTextFrame.cpp", 4915); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "false" ") (" "should have already reflowed the kid"
")"); do { MOZ_CrashSequence(__null, 4915); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
4916 return;
4917 }
4918
4919 // Since we can be called directly via GetBBoxContribution, our correspondence
4920 // may not be up to date.
4921 TextNodeCorrespondenceRecorder::RecordCorrespondence(this);
4922
4923 // Determine the positions of each character in app units.
4924 AutoTArray<nsPoint, 64> charPositions;
4925 DetermineCharPositions(charPositions);
4926
4927 if (charPositions.IsEmpty()) {
4928 // No characters, so nothing to do.
4929 return;
4930 }
4931
4932 // If the textLength="" attribute was specified, then we need ResolvePositions
4933 // to record that a new run starts with each glyph.
4934 SVGTextContentElement* element =
4935 static_cast<SVGTextContentElement*>(GetContent());
4936 SVGAnimatedLength* textLengthAttr =
4937 element->GetAnimatedLength(nsGkAtoms::textLength);
4938 uint16_t lengthAdjust =
4939 element->EnumAttributes()[SVGTextContentElement::LENGTHADJUST]
4940 .GetAnimValue();
4941 bool adjustingTextLength = textLengthAttr->IsExplicitlySet();
4942 float expectedTextLength = textLengthAttr->GetAnimValueWithZoom(element);
4943
4944 if (adjustingTextLength &&
4945 (expectedTextLength < 0.0f || lengthAdjust == LENGTHADJUST_UNKNOWN)) {
4946 // If textLength="" is less than zero or lengthAdjust is unknown, ignore it.
4947 adjustingTextLength = false;
4948 }
4949
4950 // Get the x, y, dx, dy, rotate values for the subtree.
4951 AutoTArray<gfxPoint, 16> deltas;
4952 if (!ResolvePositions(deltas, adjustingTextLength)) {
4953 // If ResolvePositions returned false, it means either there were some
4954 // characters in the DOM but none of them are displayed, or there was
4955 // an error in processing mPositions. Clear out mPositions so that we don't
4956 // attempt to do any painting later.
4957 mPositions.Clear();
4958 return;
4959 }
4960
4961 // XXX We might be able to do less work when there is at most a single
4962 // x/y/dx/dy position.
4963
4964 // Truncate the positioning arrays to the actual number of characters present.
4965 TruncateTo(deltas, charPositions);
4966 TruncateTo(mPositions, charPositions);
4967
4968 // Fill in an unspecified position for the first addressable character.
4969 uint32_t first = 0;
4970 while (first + 1 < mPositions.Length() && mPositions[first].mUnaddressable) {
4971 ++first;
4972 }
4973 if (!mPositions[first].IsXSpecified()) {
4974 mPositions[first].mPosition.x = 0.0;
4975 }
4976 if (!mPositions[first].IsYSpecified()) {
4977 mPositions[first].mPosition.y = 0.0;
4978 }
4979 if (!mPositions[first].IsAngleSpecified()) {
4980 mPositions[first].mAngle = 0.0;
4981 }
4982
4983 nsPresContext* presContext = PresContext();
4984 bool vertical = GetWritingMode().IsVertical();
4985
4986 float cssPxPerDevPx = nsPresContext::AppUnitsToFloatCSSPixels(
4987 presContext->AppUnitsPerDevPixel());
4988 double factor = cssPxPerDevPx / mFontSizeScaleFactor;
4989
4990 // Determine how much to compress or expand glyph positions due to
4991 // textLength="" and lengthAdjust="".
4992 double adjustment = 0.0;
4993 mLengthAdjustScaleFactor = 1.0f;
4994 if (adjustingTextLength) {
4995 nscoord frameLength =
4996 vertical ? PrincipalChildList().FirstChild()->GetRect().height
4997 : PrincipalChildList().FirstChild()->GetRect().width;
4998 float actualTextLength = static_cast<float>(
4999 presContext->AppUnitsToGfxUnits(frameLength) * factor);
5000
5001 switch (lengthAdjust) {
5002 case LENGTHADJUST_SPACINGANDGLYPHS:
5003 // Scale the glyphs and their positions.
5004 if (actualTextLength > 0) {
5005 mLengthAdjustScaleFactor = expectedTextLength / actualTextLength;
5006 }
5007 break;
5008
5009 default:
5010 MOZ_ASSERT(lengthAdjust == LENGTHADJUST_SPACING)do { static_assert( mozilla::detail::AssertionConditionType<
decltype(lengthAdjust == LENGTHADJUST_SPACING)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(lengthAdjust == LENGTHADJUST_SPACING
))), 0))) { do { } while (false); MOZ_ReportAssertionFailure(
"lengthAdjust == LENGTHADJUST_SPACING", "./../../../layout/svg/SVGTextFrame.cpp"
, 5010); AnnotateMozCrashReason("MOZ_ASSERT" "(" "lengthAdjust == LENGTHADJUST_SPACING"
")"); do { MOZ_CrashSequence(__null, 5010); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
5011 // Just add space between each glyph.
5012 int32_t adjustableSpaces = 0;
5013 for (uint32_t i = 1; i < mPositions.Length(); i++) {
5014 if (!mPositions[i].mUnaddressable) {
5015 adjustableSpaces++;
5016 }
5017 }
5018 if (adjustableSpaces) {
5019 adjustment =
5020 (expectedTextLength - actualTextLength) / adjustableSpaces;
5021 }
5022 break;
5023 }
5024 }
5025
5026 // Fill in any unspecified character positions based on the positions recorded
5027 // in charPositions, and also add in the dx/dy values.
5028 if (!deltas.IsEmpty()) {
5029 mPositions[0].mPosition += deltas[0];
5030 }
5031
5032 gfxFloat xLengthAdjustFactor = vertical ? 1.0 : mLengthAdjustScaleFactor;
5033 gfxFloat yLengthAdjustFactor = vertical ? mLengthAdjustScaleFactor : 1.0;
5034 for (uint32_t i = 1; i < mPositions.Length(); i++) {
5035 // Fill in unspecified x position.
5036 if (!mPositions[i].IsXSpecified()) {
5037 nscoord d = charPositions[i].x - charPositions[i - 1].x;
5038 mPositions[i].mPosition.x =
5039 mPositions[i - 1].mPosition.x +
5040 presContext->AppUnitsToGfxUnits(d) * factor * xLengthAdjustFactor;
5041 if (!vertical && !mPositions[i].mUnaddressable) {
5042 mPositions[i].mPosition.x += adjustment;
5043 }
5044 }
5045 // Fill in unspecified y position.
5046 if (!mPositions[i].IsYSpecified()) {
5047 nscoord d = charPositions[i].y - charPositions[i - 1].y;
5048 mPositions[i].mPosition.y =
5049 mPositions[i - 1].mPosition.y +
5050 presContext->AppUnitsToGfxUnits(d) * factor * yLengthAdjustFactor;
5051 if (vertical && !mPositions[i].mUnaddressable) {
5052 mPositions[i].mPosition.y += adjustment;
5053 }
5054 }
5055 // Add in dx/dy.
5056 if (i < deltas.Length()) {
5057 mPositions[i].mPosition += deltas[i];
5058 }
5059 // Fill in unspecified rotation values.
5060 if (!mPositions[i].IsAngleSpecified()) {
5061 mPositions[i].mAngle = 0.0f;
5062 }
5063 }
5064
5065 MOZ_ASSERT(mPositions.Length() == charPositions.Length())do { static_assert( mozilla::detail::AssertionConditionType<
decltype(mPositions.Length() == charPositions.Length())>::
isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(mPositions.Length() == charPositions.Length()))), 0)
)) { do { } while (false); MOZ_ReportAssertionFailure("mPositions.Length() == charPositions.Length()"
, "./../../../layout/svg/SVGTextFrame.cpp", 5065); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "mPositions.Length() == charPositions.Length()"
")"); do { MOZ_CrashSequence(__null, 5065); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
5066
5067 AdjustChunksForLineBreaks();
5068 AdjustPositionsForClusters();
5069 DoAnchoring();
5070 DoTextPathLayout();
5071}
5072
5073bool SVGTextFrame::ShouldRenderAsPath(nsTextFrame* aFrame,
5074 SVGContextPaint* aContextPaint,
5075 bool& aShouldPaintSVGGlyphs) {
5076 // Rendering to a clip path.
5077 if (HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD)) {
5078 aShouldPaintSVGGlyphs = false;
5079 return true;
5080 }
5081
5082 aShouldPaintSVGGlyphs = true;
5083
5084 const nsStyleSVG* style = aFrame->StyleSVG();
5085
5086 // Fill is a non-solid paint or is not opaque.
5087 if (!(style->mFill.kind.IsNone() ||
5088 (style->mFill.kind.IsColor() &&
5089 SVGUtils::GetOpacity(style->mFillOpacity, aContextPaint) == 1.0f))) {
5090 return true;
5091 }
5092
5093 // If we're going to need to draw a non-opaque shadow.
5094 // It's possible nsTextFrame will support non-opaque shadows in the future,
5095 // in which case this test can be removed.
5096 if (style->mFill.kind.IsColor() && aFrame->StyleText()->HasTextShadow() &&
5097 NS_GET_A(style->mFill.kind.AsColor().CalcColor(*aFrame->Style()))((uint8_t)(((style->mFill.kind.AsColor().CalcColor(*aFrame
->Style())) >> 24) & 0xff))
!=
5098 0xFF) {
5099 return true;
5100 }
5101
5102 // Text has a stroke.
5103 if (style->HasStroke()) {
5104 if (style->mStrokeWidth.IsContextValue()) {
5105 return true;
5106 }
5107 if (SVGContentUtils::CoordToFloat(
5108 static_cast<SVGElement*>(GetContent()),
5109 style->mStrokeWidth.AsLengthPercentage()) > 0) {
5110 return true;
5111 }
5112 }
5113
5114 return false;
5115}
5116
5117void SVGTextFrame::ScheduleReflowSVG() {
5118 if (HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) {
5119 ScheduleReflowSVGNonDisplayText(
5120 IntrinsicDirty::FrameAncestorsAndDescendants);
5121 } else {
5122 SVGUtils::ScheduleReflowSVG(this);
5123 }
5124}
5125
5126void SVGTextFrame::NotifyGlyphMetricsChange(bool aUpdateTextCorrespondence) {
5127 if (aUpdateTextCorrespondence) {
5128 AddStateBits(NS_STATE_SVG_TEXT_CORRESPONDENCE_DIRTY);
5129 }
5130 AddStateBits(NS_STATE_SVG_POSITIONING_DIRTY);
5131 nsLayoutUtils::PostRestyleEvent(mContent->AsElement(), RestyleHint{0},
5132 nsChangeHint_InvalidateRenderingObservers);
5133 ScheduleReflowSVG();
5134}
5135
5136void SVGTextFrame::UpdateGlyphPositioning() {
5137 nsIFrame* kid = PrincipalChildList().FirstChild();
5138 if (!kid) {
5139 return;
5140 }
5141
5142 if (HasAnyStateBits(NS_STATE_SVG_POSITIONING_DIRTY)) {
5143 DoGlyphPositioning();
5144 }
5145}
5146
5147void SVGTextFrame::MaybeResolveBidiForAnonymousBlockChild() {
5148 nsIFrame* kid = PrincipalChildList().FirstChild();
5149
5150 if (kid && kid->HasAnyStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION) &&
5151 PresContext()->BidiEnabled()) {
5152 MOZ_ASSERT(static_cast<nsBlockFrame*>(do_QueryFrame(kid)),do { static_assert( mozilla::detail::AssertionConditionType<
decltype(static_cast<nsBlockFrame*>(do_QueryFrame(kid))
)>::isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(static_cast<nsBlockFrame*>(do_QueryFrame(kid))
))), 0))) { do { } while (false); MOZ_ReportAssertionFailure(
"static_cast<nsBlockFrame*>(do_QueryFrame(kid))" " (" "Expect anonymous child to be an nsBlockFrame"
")", "./../../../layout/svg/SVGTextFrame.cpp", 5153); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "static_cast<nsBlockFrame*>(do_QueryFrame(kid))"
") (" "Expect anonymous child to be an nsBlockFrame" ")"); do
{ MOZ_CrashSequence(__null, 5153); __attribute__((nomerge)) ::
abort(); } while (false); } } while (false)
5153 "Expect anonymous child to be an nsBlockFrame")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(static_cast<nsBlockFrame*>(do_QueryFrame(kid))
)>::isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(static_cast<nsBlockFrame*>(do_QueryFrame(kid))
))), 0))) { do { } while (false); MOZ_ReportAssertionFailure(
"static_cast<nsBlockFrame*>(do_QueryFrame(kid))" " (" "Expect anonymous child to be an nsBlockFrame"
")", "./../../../layout/svg/SVGTextFrame.cpp", 5153); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "static_cast<nsBlockFrame*>(do_QueryFrame(kid))"
") (" "Expect anonymous child to be an nsBlockFrame" ")"); do
{ MOZ_CrashSequence(__null, 5153); __attribute__((nomerge)) ::
abort(); } while (false); } } while (false)
;
5154 nsBidiPresUtils::Resolve(static_cast<nsBlockFrame*>(kid));
5155 }
5156}
5157
5158void SVGTextFrame::MaybeReflowAnonymousBlockChild() {
5159 nsIFrame* kid = PrincipalChildList().FirstChild();
5160 if (!kid) {
5161 return;
5162 }
5163
5164 NS_ASSERTION(!kid->HasAnyStateBits(NS_FRAME_IN_REFLOW),do { if (!(!kid->HasAnyStateBits(NS_FRAME_IN_REFLOW))) { NS_DebugBreak
(NS_DEBUG_ASSERTION, "should not be in reflow when about to reflow again"
, "!kid->HasAnyStateBits(NS_FRAME_IN_REFLOW)", "./../../../layout/svg/SVGTextFrame.cpp"
, 5165); MOZ_PretendNoReturn(); } } while (0)
5165 "should not be in reflow when about to reflow again")do { if (!(!kid->HasAnyStateBits(NS_FRAME_IN_REFLOW))) { NS_DebugBreak
(NS_DEBUG_ASSERTION, "should not be in reflow when about to reflow again"
, "!kid->HasAnyStateBits(NS_FRAME_IN_REFLOW)", "./../../../layout/svg/SVGTextFrame.cpp"
, 5165); MOZ_PretendNoReturn(); } } while (0)
;
5166
5167 if (IsSubtreeDirty()) {
5168 if (HasAnyStateBits(NS_FRAME_IS_DIRTY)) {
5169 // If we require a full reflow, ensure our kid is marked fully dirty.
5170 // (Note that our anonymous nsBlockFrame is not an ISVGDisplayableFrame,
5171 // so even when we are called via our ReflowSVG this will not be done for
5172 // us by SVGDisplayContainerFrame::ReflowSVG.)
5173 kid->MarkSubtreeDirty();
5174 }
5175
5176 // The RecordCorrespondence and DoReflow calls can result in new text frames
5177 // being created (due to bidi resolution or reflow). We set this bit to
5178 // guard against unnecessarily calling back in to
5179 // ScheduleReflowSVGNonDisplayText from nsIFrame::DidSetComputedStyle on
5180 // those new text frames.
5181 AddStateBits(NS_STATE_SVG_TEXT_IN_REFLOW);
5182
5183 TextNodeCorrespondenceRecorder::RecordCorrespondence(this);
5184
5185 MOZ_ASSERT(SVGUtils::AnyOuterSVGIsCallingReflowSVG(this),do { static_assert( mozilla::detail::AssertionConditionType<
decltype(SVGUtils::AnyOuterSVGIsCallingReflowSVG(this))>::
isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(SVGUtils::AnyOuterSVGIsCallingReflowSVG(this)))), 0)
)) { do { } while (false); MOZ_ReportAssertionFailure("SVGUtils::AnyOuterSVGIsCallingReflowSVG(this)"
" (" "should be under ReflowSVG" ")", "./../../../layout/svg/SVGTextFrame.cpp"
, 5186); AnnotateMozCrashReason("MOZ_ASSERT" "(" "SVGUtils::AnyOuterSVGIsCallingReflowSVG(this)"
") (" "should be under ReflowSVG" ")"); do { MOZ_CrashSequence
(__null, 5186); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
5186 "should be under ReflowSVG")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(SVGUtils::AnyOuterSVGIsCallingReflowSVG(this))>::
isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(SVGUtils::AnyOuterSVGIsCallingReflowSVG(this)))), 0)
)) { do { } while (false); MOZ_ReportAssertionFailure("SVGUtils::AnyOuterSVGIsCallingReflowSVG(this)"
" (" "should be under ReflowSVG" ")", "./../../../layout/svg/SVGTextFrame.cpp"
, 5186); AnnotateMozCrashReason("MOZ_ASSERT" "(" "SVGUtils::AnyOuterSVGIsCallingReflowSVG(this)"
") (" "should be under ReflowSVG" ")"); do { MOZ_CrashSequence
(__null, 5186); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
5187 nsPresContext::InterruptPreventer noInterrupts(PresContext());
5188 DoReflow();
5189
5190 RemoveStateBits(NS_STATE_SVG_TEXT_IN_REFLOW);
5191 }
5192}
5193
5194void SVGTextFrame::DoReflow() {
5195 MOZ_ASSERT(HasAnyStateBits(NS_STATE_SVG_TEXT_IN_REFLOW))do { static_assert( mozilla::detail::AssertionConditionType<
decltype(HasAnyStateBits(NS_STATE_SVG_TEXT_IN_REFLOW))>::isValid
, "invalid assertion condition"); if ((__builtin_expect(!!(!(
!!(HasAnyStateBits(NS_STATE_SVG_TEXT_IN_REFLOW)))), 0))) { do
{ } while (false); MOZ_ReportAssertionFailure("HasAnyStateBits(NS_STATE_SVG_TEXT_IN_REFLOW)"
, "./../../../layout/svg/SVGTextFrame.cpp", 5195); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "HasAnyStateBits(NS_STATE_SVG_TEXT_IN_REFLOW)"
")"); do { MOZ_CrashSequence(__null, 5195); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
5196
5197 // Since we are going to reflow the anonymous block frame, we will
5198 // need to update mPositions.
5199 // We also mark our text correspondence as dirty since we can end up needing
5200 // reflow in ways that do not set NS_STATE_SVG_TEXT_CORRESPONDENCE_DIRTY.
5201 // (We'd then fail the "expected a TextNodeCorrespondenceProperty" assertion
5202 // when UpdateGlyphPositioning() is called after we return.)
5203 AddStateBits(NS_STATE_SVG_TEXT_CORRESPONDENCE_DIRTY |
5204 NS_STATE_SVG_POSITIONING_DIRTY);
5205
5206 if (HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) {
5207 // Normally, these dirty flags would be cleared in ReflowSVG(), but that
5208 // doesn't get called for non-display frames. We don't want to reflow our
5209 // descendants every time SVGTextFrame::PaintSVG makes sure that we have
5210 // valid positions by calling UpdateGlyphPositioning(), so we need to clear
5211 // these dirty bits. Note that this also breaks an invalidation loop where
5212 // our descendants invalidate as they reflow, which invalidates rendering
5213 // observers, which reschedules the frame that is currently painting by
5214 // referencing us to paint again. See bug 839958 comment 7. Hopefully we
5215 // will break that loop more convincingly at some point.
5216 RemoveStateBits(NS_FRAME_IS_DIRTY | NS_FRAME_HAS_DIRTY_CHILDREN);
5217 }
5218
5219 // Forget any cached measurements of one of our children.
5220 mFrameForCachedRanges = nullptr;
5221
5222 nsPresContext* presContext = PresContext();
5223 nsIFrame* kid = PrincipalChildList().FirstChild();
5224 if (!kid) {
5225 return;
5226 }
5227
5228 std::unique_ptr<gfxContext> renderingContext =
5229 presContext->PresShell()->CreateReferenceRenderingContext();
5230
5231 if (UpdateFontSizeScaleFactor()) {
5232 // If the font size scale factor changed, we need the block to report
5233 // an updated preferred width.
5234 kid->MarkIntrinsicISizesDirty();
5235 }
5236
5237 const IntrinsicSizeInput input(renderingContext.get(), Nothing(), Nothing());
5238 nscoord inlineSize = kid->GetPrefISize(input);
5239 WritingMode wm = kid->GetWritingMode();
5240 ReflowInput reflowInput(presContext, kid, renderingContext.get(),
5241 LogicalSize(wm, inlineSize, NS_UNCONSTRAINEDSIZE));
5242 ReflowOutput desiredSize(reflowInput);
5243 nsReflowStatus status;
5244
5245 NS_ASSERTION(do { if (!(reflowInput.ComputedPhysicalBorderPadding() == nsMargin
(0, 0, 0, 0) && reflowInput.ComputedPhysicalMargin() ==
nsMargin(0, 0, 0, 0))) { NS_DebugBreak(NS_DEBUG_ASSERTION, "style system should ensure that :-moz-svg-text "
"does not get styled", "reflowInput.ComputedPhysicalBorderPadding() == nsMargin(0, 0, 0, 0) && reflowInput.ComputedPhysicalMargin() == nsMargin(0, 0, 0, 0)"
, "./../../../layout/svg/SVGTextFrame.cpp", 5249); MOZ_PretendNoReturn
(); } } while (0)
5246 reflowInput.ComputedPhysicalBorderPadding() == nsMargin(0, 0, 0, 0) &&do { if (!(reflowInput.ComputedPhysicalBorderPadding() == nsMargin
(0, 0, 0, 0) && reflowInput.ComputedPhysicalMargin() ==
nsMargin(0, 0, 0, 0))) { NS_DebugBreak(NS_DEBUG_ASSERTION, "style system should ensure that :-moz-svg-text "
"does not get styled", "reflowInput.ComputedPhysicalBorderPadding() == nsMargin(0, 0, 0, 0) && reflowInput.ComputedPhysicalMargin() == nsMargin(0, 0, 0, 0)"
, "./../../../layout/svg/SVGTextFrame.cpp", 5249); MOZ_PretendNoReturn
(); } } while (0)
5247 reflowInput.ComputedPhysicalMargin() == nsMargin(0, 0, 0, 0),do { if (!(reflowInput.ComputedPhysicalBorderPadding() == nsMargin
(0, 0, 0, 0) && reflowInput.ComputedPhysicalMargin() ==
nsMargin(0, 0, 0, 0))) { NS_DebugBreak(NS_DEBUG_ASSERTION, "style system should ensure that :-moz-svg-text "
"does not get styled", "reflowInput.ComputedPhysicalBorderPadding() == nsMargin(0, 0, 0, 0) && reflowInput.ComputedPhysicalMargin() == nsMargin(0, 0, 0, 0)"
, "./../../../layout/svg/SVGTextFrame.cpp", 5249); MOZ_PretendNoReturn
(); } } while (0)
5248 "style system should ensure that :-moz-svg-text "do { if (!(reflowInput.ComputedPhysicalBorderPadding() == nsMargin
(0, 0, 0, 0) && reflowInput.ComputedPhysicalMargin() ==
nsMargin(0, 0, 0, 0))) { NS_DebugBreak(NS_DEBUG_ASSERTION, "style system should ensure that :-moz-svg-text "
"does not get styled", "reflowInput.ComputedPhysicalBorderPadding() == nsMargin(0, 0, 0, 0) && reflowInput.ComputedPhysicalMargin() == nsMargin(0, 0, 0, 0)"
, "./../../../layout/svg/SVGTextFrame.cpp", 5249); MOZ_PretendNoReturn
(); } } while (0)
5249 "does not get styled")do { if (!(reflowInput.ComputedPhysicalBorderPadding() == nsMargin
(0, 0, 0, 0) && reflowInput.ComputedPhysicalMargin() ==
nsMargin(0, 0, 0, 0))) { NS_DebugBreak(NS_DEBUG_ASSERTION, "style system should ensure that :-moz-svg-text "
"does not get styled", "reflowInput.ComputedPhysicalBorderPadding() == nsMargin(0, 0, 0, 0) && reflowInput.ComputedPhysicalMargin() == nsMargin(0, 0, 0, 0)"
, "./../../../layout/svg/SVGTextFrame.cpp", 5249); MOZ_PretendNoReturn
(); } } while (0)
;
5250
5251 kid->Reflow(presContext, desiredSize, reflowInput, status);
5252 kid->DidReflow(presContext, &reflowInput);
5253 kid->SetSize(wm, desiredSize.Size(wm));
5254}
5255
5256// Usable font size range in devpixels / user-units
5257#define CLAMP_MIN_SIZE8.0 8.0
5258#define CLAMP_MAX_SIZE200.0 200.0
5259#define PRECISE_SIZE200.0 200.0
5260
5261bool SVGTextFrame::UpdateFontSizeScaleFactor() {
5262 float contextScale = GetContextScale(this);
5263 mLastContextScale = contextScale;
5264
5265 double oldFontSizeScaleFactor = mFontSizeScaleFactor;
5266
5267 bool geometricPrecision = false;
5268 // We may need to invert a matrix with these values later.
5269 CSSCoord min = std::sqrt(std::numeric_limits<float>::max());
5270 CSSCoord max = std::sqrt(std::numeric_limits<float>::min());
5271 bool anyText = false;
5272
5273 // Find the minimum and maximum font sizes used over all the
5274 // nsTextFrames.
5275 TextFrameIterator it(this);
5276 nsTextFrame* f = it.GetCurrent();
5277 while (f) {
5278 if (!geometricPrecision) {
5279 // Unfortunately we can't treat text-rendering:geometricPrecision
5280 // separately for each text frame.
5281 geometricPrecision = f->StyleText()->mTextRendering ==
5282 StyleTextRendering::Geometricprecision;
5283 }
5284 const auto& fontSize = f->StyleFont()->mFont.size;
5285 if (!fontSize.IsZero()) {
5286 min = std::min(min, fontSize.ToCSSPixels());
5287 max = std::max(max, fontSize.ToCSSPixels());
5288 anyText = true;
5289 }
5290 f = it.GetNext();
5291 }
5292
5293 if (!anyText) {
5294 // No text, so no need for scaling.
5295 mFontSizeScaleFactor = 1.0;
5296 return mFontSizeScaleFactor != oldFontSizeScaleFactor;
5297 }
5298
5299 if (geometricPrecision) {
5300 // We want to ensure minSize is scaled to PRECISE_SIZE.
5301 mFontSizeScaleFactor = PRECISE_SIZE200.0 / min;
5302 return mFontSizeScaleFactor != oldFontSizeScaleFactor;
5303 }
5304
5305 double minTextRunSize = min * contextScale;
5306 double maxTextRunSize = max * contextScale;
5307
5308 if (minTextRunSize >= CLAMP_MIN_SIZE8.0 && maxTextRunSize <= CLAMP_MAX_SIZE200.0) {
5309 // We are already in the ideal font size range for all text frames,
5310 // so we only have to take into account the contextScale.
5311 mFontSizeScaleFactor = contextScale;
5312 } else if (max / min > CLAMP_MAX_SIZE200.0 / CLAMP_MIN_SIZE8.0) {
5313 // We can't scale the font sizes so that all of the text frames lie
5314 // within our ideal font size range.
5315 // Heuristically, if the maxTextRunSize is within the CLAMP_MAX_SIZE
5316 // as a reasonable value, it's likely to be the user's intent to
5317 // get a valid font for the maxTextRunSize one, we should honor it.
5318 // The same for minTextRunSize.
5319 if (maxTextRunSize <= CLAMP_MAX_SIZE200.0) {
5320 mFontSizeScaleFactor = CLAMP_MAX_SIZE200.0 / max;
5321 } else if (minTextRunSize >= CLAMP_MIN_SIZE8.0) {
5322 mFontSizeScaleFactor = CLAMP_MIN_SIZE8.0 / min;
5323 } else {
5324 // So maxTextRunSize is too big, minTextRunSize is too small,
5325 // we can't really do anything for this case, just leave it as is.
5326 mFontSizeScaleFactor = contextScale;
5327 }
5328 } else if (minTextRunSize < CLAMP_MIN_SIZE8.0) {
5329 mFontSizeScaleFactor = CLAMP_MIN_SIZE8.0 / min;
5330 } else {
5331 mFontSizeScaleFactor = CLAMP_MAX_SIZE200.0 / max;
5332 }
5333
5334 return mFontSizeScaleFactor != oldFontSizeScaleFactor;
5335}
5336
5337double SVGTextFrame::GetFontSizeScaleFactor() const {
5338 return mFontSizeScaleFactor;
5339}
5340
5341/**
5342 * Take aPoint, which is in the <text> element's user space, and convert
5343 * it to the appropriate frame user space of aChildFrame according to
5344 * which rendered run the point hits.
5345 */
5346Point SVGTextFrame::TransformFramePointToTextChild(
5347 const Point& aPoint, const nsIFrame* aChildFrame) {
5348 NS_ASSERTION(aChildFrame && nsLayoutUtils::GetClosestFrameOfType(do { if (!(aChildFrame && nsLayoutUtils::GetClosestFrameOfType
( aChildFrame->GetParent(), LayoutFrameType::SVGText) == this
)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "aChildFrame must be a descendant of this frame"
, "aChildFrame && nsLayoutUtils::GetClosestFrameOfType( aChildFrame->GetParent(), LayoutFrameType::SVGText) == this"
, "./../../../layout/svg/SVGTextFrame.cpp", 5351); MOZ_PretendNoReturn
(); } } while (0)
5349 aChildFrame->GetParent(),do { if (!(aChildFrame && nsLayoutUtils::GetClosestFrameOfType
( aChildFrame->GetParent(), LayoutFrameType::SVGText) == this
)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "aChildFrame must be a descendant of this frame"
, "aChildFrame && nsLayoutUtils::GetClosestFrameOfType( aChildFrame->GetParent(), LayoutFrameType::SVGText) == this"
, "./../../../layout/svg/SVGTextFrame.cpp", 5351); MOZ_PretendNoReturn
(); } } while (0)
5350 LayoutFrameType::SVGText) == this,do { if (!(aChildFrame && nsLayoutUtils::GetClosestFrameOfType
( aChildFrame->GetParent(), LayoutFrameType::SVGText) == this
)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "aChildFrame must be a descendant of this frame"
, "aChildFrame && nsLayoutUtils::GetClosestFrameOfType( aChildFrame->GetParent(), LayoutFrameType::SVGText) == this"
, "./../../../layout/svg/SVGTextFrame.cpp", 5351); MOZ_PretendNoReturn
(); } } while (0)
5351 "aChildFrame must be a descendant of this frame")do { if (!(aChildFrame && nsLayoutUtils::GetClosestFrameOfType
( aChildFrame->GetParent(), LayoutFrameType::SVGText) == this
)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "aChildFrame must be a descendant of this frame"
, "aChildFrame && nsLayoutUtils::GetClosestFrameOfType( aChildFrame->GetParent(), LayoutFrameType::SVGText) == this"
, "./../../../layout/svg/SVGTextFrame.cpp", 5351); MOZ_PretendNoReturn
(); } } while (0)
;
5352
5353 UpdateGlyphPositioning();
5354
5355 nsPresContext* presContext = PresContext();
5356
5357 // Add in the mRect offset to aPoint, as that will have been taken into
5358 // account when transforming the point from the ancestor frame down
5359 // to this one.
5360 float cssPxPerDevPx = nsPresContext::AppUnitsToFloatCSSPixels(
5361 presContext->AppUnitsPerDevPixel());
5362 float factor = AppUnitsPerCSSPixel();
5363 Point framePosition(NSAppUnitsToFloatPixels(mRect.x, factor),
5364 NSAppUnitsToFloatPixels(mRect.y, factor));
5365 Point pointInUserSpace = aPoint * cssPxPerDevPx + framePosition;
5366
5367 // Find the closest rendered run for the text frames beneath aChildFrame.
5368 TextRenderedRunIterator it(
5369 this, TextRenderedRunIterator::RenderedRunFilter::AllFrames, aChildFrame);
5370 TextRenderedRun hit;
5371 gfxPoint pointInRun;
5372 nscoord dx = nscoord_MAX;
5373 nscoord dy = nscoord_MAX;
5374 for (TextRenderedRun run = it.Current(); run.mFrame; run = it.Next()) {
5375 TextRenderedRun::GeometryFlags flags(
5376 TextRenderedRun::GeometryFlag::IncludeFill,
5377 TextRenderedRun::GeometryFlag::IncludeStroke,
5378 TextRenderedRun::GeometryFlag::NoHorizontalOverflow);
5379 gfxRect runRect = run.GetRunUserSpaceRect(flags).ToThebesRect();
5380
5381 gfxMatrix m = run.GetTransformFromRunUserSpaceToUserSpace(presContext);
5382 if (!m.Invert()) {
5383 return aPoint;
5384 }
5385 gfxPoint pointInRunUserSpace =
5386 m.TransformPoint(ThebesPoint(pointInUserSpace));
5387
5388 if (runRect.Contains(pointInRunUserSpace)) {
5389 // The point was inside the rendered run's rect, so we choose it.
5390 dx = 0;
5391 dy = 0;
5392 pointInRun = pointInRunUserSpace;
5393 hit = run;
5394 } else if (nsLayoutUtils::PointIsCloserToRect(pointInRunUserSpace, runRect,
5395 dx, dy)) {
5396 // The point was closer to this rendered run's rect than any others
5397 // we've seen so far.
5398 pointInRun.x =
5399 std::clamp(pointInRunUserSpace.x.value, runRect.X(), runRect.XMost());
5400 pointInRun.y =
5401 std::clamp(pointInRunUserSpace.y.value, runRect.Y(), runRect.YMost());
5402 hit = run;
5403 }
5404 }
5405
5406 if (!hit.mFrame) {
5407 // We didn't find any rendered runs for the frame.
5408 return aPoint;
5409 }
5410
5411 // Return the point in user units relative to the nsTextFrame,
5412 // but taking into account mFontSizeScaleFactor.
5413 gfxMatrix m = hit.GetTransformFromRunUserSpaceToFrameUserSpace(presContext);
5414 m.PreScale(mFontSizeScaleFactor, mFontSizeScaleFactor);
5415 return ToPoint(m.TransformPoint(pointInRun) / cssPxPerDevPx);
5416}
5417
5418/**
5419 * For each rendered run beneath aChildFrame, translate aRect from
5420 * aChildFrame to the run's text frame, transform it then into
5421 * the run's frame user space, intersect it with the run's
5422 * frame user space rect, then transform it up to user space.
5423 * The result is the union of all of these.
5424 */
5425gfxRect SVGTextFrame::TransformFrameRectFromTextChild(
5426 const nsRect& aRect, const nsIFrame* aChildFrame) {
5427 NS_ASSERTION(aChildFrame && nsLayoutUtils::GetClosestFrameOfType(do { if (!(aChildFrame && nsLayoutUtils::GetClosestFrameOfType
( aChildFrame->GetParent(), LayoutFrameType::SVGText) == this
)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "aChildFrame must be a descendant of this frame"
, "aChildFrame && nsLayoutUtils::GetClosestFrameOfType( aChildFrame->GetParent(), LayoutFrameType::SVGText) == this"
, "./../../../layout/svg/SVGTextFrame.cpp", 5430); MOZ_PretendNoReturn
(); } } while (0)
5428 aChildFrame->GetParent(),do { if (!(aChildFrame && nsLayoutUtils::GetClosestFrameOfType
( aChildFrame->GetParent(), LayoutFrameType::SVGText) == this
)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "aChildFrame must be a descendant of this frame"
, "aChildFrame && nsLayoutUtils::GetClosestFrameOfType( aChildFrame->GetParent(), LayoutFrameType::SVGText) == this"
, "./../../../layout/svg/SVGTextFrame.cpp", 5430); MOZ_PretendNoReturn
(); } } while (0)
5429 LayoutFrameType::SVGText) == this,do { if (!(aChildFrame && nsLayoutUtils::GetClosestFrameOfType
( aChildFrame->GetParent(), LayoutFrameType::SVGText) == this
)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "aChildFrame must be a descendant of this frame"
, "aChildFrame && nsLayoutUtils::GetClosestFrameOfType( aChildFrame->GetParent(), LayoutFrameType::SVGText) == this"
, "./../../../layout/svg/SVGTextFrame.cpp", 5430); MOZ_PretendNoReturn
(); } } while (0)
5430 "aChildFrame must be a descendant of this frame")do { if (!(aChildFrame && nsLayoutUtils::GetClosestFrameOfType
( aChildFrame->GetParent(), LayoutFrameType::SVGText) == this
)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "aChildFrame must be a descendant of this frame"
, "aChildFrame && nsLayoutUtils::GetClosestFrameOfType( aChildFrame->GetParent(), LayoutFrameType::SVGText) == this"
, "./../../../layout/svg/SVGTextFrame.cpp", 5430); MOZ_PretendNoReturn
(); } } while (0)
;
5431
5432 UpdateGlyphPositioning();
5433
5434 nsPresContext* presContext = PresContext();
5435
5436 gfxRect result;
5437 TextRenderedRunIterator it(
5438 this, TextRenderedRunIterator::RenderedRunFilter::AllFrames, aChildFrame);
5439 for (TextRenderedRun run = it.Current(); run.mFrame; run = it.Next()) {
5440 // First, translate aRect from aChildFrame to this run's frame.
5441 nsRect rectInTextFrame = aRect + aChildFrame->GetOffsetTo(run.mFrame);
5442
5443 // Scale it into frame user space.
5444 gfxRect rectInFrameUserSpace = AppUnitsToFloatCSSPixels(rectInTextFrame);
5445
5446 // Intersect it with the run.
5447 TextRenderedRun::GeometryFlags flags(
5448 TextRenderedRun::GeometryFlag::IncludeFill,
5449 TextRenderedRun::GeometryFlag::IncludeStroke);
5450
5451 if (rectInFrameUserSpace.IntersectRect(
5452 rectInFrameUserSpace,
5453 run.GetFrameUserSpaceRect(presContext, flags).ToThebesRect())) {
5454 // Transform it up to user space of the <text>
5455 gfxMatrix m = run.GetTransformFromRunUserSpaceToUserSpace(presContext);
5456 gfxRect rectInUserSpace = m.TransformRect(rectInFrameUserSpace);
5457
5458 // Union it into the result.
5459 result.UnionRect(result, rectInUserSpace);
5460 }
5461 }
5462
5463 // Subtract the mRect offset from the result, as our user space for
5464 // this frame is relative to the top-left of mRect.
5465 float factor = AppUnitsPerCSSPixel();
5466 gfxPoint framePosition(NSAppUnitsToFloatPixels(mRect.x, factor),
5467 NSAppUnitsToFloatPixels(mRect.y, factor));
5468
5469 return result - framePosition;
5470}
5471
5472Rect SVGTextFrame::TransformFrameRectFromTextChild(
5473 const Rect& aRect, const nsIFrame* aChildFrame) {
5474 nscoord appUnitsPerDevPixel = PresContext()->AppUnitsPerDevPixel();
5475 nsRect r = LayoutDevicePixel::ToAppUnits(
5476 LayoutDeviceRect::FromUnknownRect(aRect), appUnitsPerDevPixel);
5477 gfxRect resultCssUnits = TransformFrameRectFromTextChild(r, aChildFrame);
5478 float devPixelPerCSSPixel =
5479 float(AppUnitsPerCSSPixel()) / appUnitsPerDevPixel;
5480 resultCssUnits.Scale(devPixelPerCSSPixel);
5481 return ToRect(resultCssUnits);
5482}
5483
5484Point SVGTextFrame::TransformFramePointFromTextChild(
5485 const Point& aPoint, const nsIFrame* aChildFrame) {
5486 return TransformFrameRectFromTextChild(Rect(aPoint, Size(1, 1)), aChildFrame)
5487 .TopLeft();
5488}
5489
5490void SVGTextFrame::AppendDirectlyOwnedAnonBoxes(
5491 nsTArray<OwnedAnonBox>& aResult) {
5492 MOZ_ASSERT(PrincipalChildList().FirstChild(), "Must have our anon box")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(PrincipalChildList().FirstChild())>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(PrincipalChildList().FirstChild
()))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("PrincipalChildList().FirstChild()" " (" "Must have our anon box"
")", "./../../../layout/svg/SVGTextFrame.cpp", 5492); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "PrincipalChildList().FirstChild()" ") (" "Must have our anon box"
")"); do { MOZ_CrashSequence(__null, 5492); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
5493 aResult.AppendElement(OwnedAnonBox(PrincipalChildList().FirstChild()));
5494}
5495
5496} // namespace mozilla