no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD CLOSED TREE
[gecko.git] / layout / svg / SVGTextFrame.h
blob6be2bcf01e3706414bcba255ab7debcce730e799
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #ifndef LAYOUT_SVG_SVGTEXTFRAME_H_
8 #define LAYOUT_SVG_SVGTEXTFRAME_H_
10 #include "mozilla/Attributes.h"
11 #include "mozilla/PresShellForwards.h"
12 #include "mozilla/RefPtr.h"
13 #include "mozilla/SVGContainerFrame.h"
14 #include "mozilla/gfx/2D.h"
15 #include "gfxMatrix.h"
16 #include "gfxRect.h"
17 #include "gfxTextRun.h"
18 #include "nsIContent.h" // for GetContent
19 #include "nsStubMutationObserver.h"
20 #include "nsTextFrame.h"
22 class gfxContext;
24 namespace mozilla {
26 class CharIterator;
27 class DisplaySVGText;
28 class SVGTextFrame;
29 class TextFrameIterator;
30 class TextNodeCorrespondenceRecorder;
31 struct TextRenderedRun;
32 class TextRenderedRunIterator;
34 namespace dom {
35 struct DOMPointInit;
36 class DOMSVGPoint;
37 class SVGRect;
38 class SVGGeometryElement;
39 } // namespace dom
40 } // namespace mozilla
42 nsIFrame* NS_NewSVGTextFrame(mozilla::PresShell* aPresShell,
43 mozilla::ComputedStyle* aStyle);
45 namespace mozilla {
47 /**
48 * Information about the positioning for a single character in an SVG <text>
49 * element.
51 * During SVG text layout, we use infinity values to represent positions and
52 * rotations that are not explicitly specified with x/y/rotate attributes.
54 struct CharPosition {
55 CharPosition()
56 : mAngle(0),
57 mHidden(false),
58 mUnaddressable(false),
59 mClusterOrLigatureGroupMiddle(false),
60 mRunBoundary(false),
61 mStartOfChunk(false) {}
63 CharPosition(gfxPoint aPosition, double aAngle)
64 : mPosition(aPosition),
65 mAngle(aAngle),
66 mHidden(false),
67 mUnaddressable(false),
68 mClusterOrLigatureGroupMiddle(false),
69 mRunBoundary(false),
70 mStartOfChunk(false) {}
72 static CharPosition Unspecified(bool aUnaddressable) {
73 CharPosition cp(UnspecifiedPoint(), UnspecifiedAngle());
74 cp.mUnaddressable = aUnaddressable;
75 return cp;
78 bool IsAngleSpecified() const { return mAngle != UnspecifiedAngle(); }
80 bool IsXSpecified() const { return mPosition.x != UnspecifiedCoord(); }
82 bool IsYSpecified() const { return mPosition.y != UnspecifiedCoord(); }
84 gfxPoint mPosition;
85 double mAngle;
87 // not displayed due to falling off the end of a <textPath>
88 bool mHidden;
90 // skipped in positioning attributes due to being collapsed-away white space
91 bool mUnaddressable;
93 // a preceding character is what positioning attributes address
94 bool mClusterOrLigatureGroupMiddle;
96 // rendering is split here since an explicit position or rotation was given
97 bool mRunBoundary;
99 // an anchored chunk begins here
100 bool mStartOfChunk;
102 private:
103 static gfxFloat UnspecifiedCoord() {
104 return std::numeric_limits<gfxFloat>::infinity();
107 static double UnspecifiedAngle() {
108 return std::numeric_limits<double>::infinity();
111 static gfxPoint UnspecifiedPoint() {
112 return gfxPoint(UnspecifiedCoord(), UnspecifiedCoord());
117 * A runnable to mark glyph positions as needing to be recomputed
118 * and to invalid the bounds of the SVGTextFrame frame.
120 class GlyphMetricsUpdater : public Runnable {
121 public:
122 NS_DECL_NSIRUNNABLE
123 explicit GlyphMetricsUpdater(SVGTextFrame* aFrame)
124 : Runnable("GlyphMetricsUpdater"), mFrame(aFrame) {}
125 static void Run(SVGTextFrame* aFrame);
126 void Revoke() { mFrame = nullptr; }
128 private:
129 SVGTextFrame* mFrame;
133 * Frame class for SVG <text> elements.
135 * An SVGTextFrame manages SVG text layout, painting and interaction for
136 * all descendent text content elements. The frame tree will look like this:
138 * SVGTextFrame -- for <text>
139 * <anonymous block frame>
140 * ns{Block,Inline,Text}Frames -- for text nodes, <tspan>s, <a>s, etc.
142 * SVG text layout is done by:
144 * 1. Reflowing the anonymous block frame.
145 * 2. Inspecting the (app unit) positions of the glyph for each character in
146 * the nsTextFrames underneath the anonymous block frame.
147 * 3. Determining the (user unit) positions for each character in the <text>
148 * using the x/y/dx/dy/rotate attributes on all the text content elements,
149 * and using the step 2 results to fill in any gaps.
150 * 4. Applying any other SVG specific text layout (anchoring and text paths)
151 * to the positions computed in step 3.
153 * Rendering of the text is done by splitting up each nsTextFrame into ranges
154 * that can be contiguously painted. (For example <text x="10 20">abcd</text>
155 * would have two contiguous ranges: one for the "a" and one for the "bcd".)
156 * Each range is called a "text rendered run", represented by a TextRenderedRun
157 * object. The TextRenderedRunIterator class performs that splitting and
158 * returns a TextRenderedRun for each bit of text to be painted separately.
160 * Each rendered run is painted by calling nsTextFrame::PaintText. If the text
161 * formatting is simple enough (solid fill, no stroking, etc.), PaintText will
162 * itself do the painting. Otherwise, a DrawPathCallback is passed to
163 * PaintText so that we can fill the text geometry with SVG paint servers.
165 class SVGTextFrame final : public SVGDisplayContainerFrame {
166 friend nsIFrame* ::NS_NewSVGTextFrame(mozilla::PresShell* aPresShell,
167 ComputedStyle* aStyle);
169 friend class CharIterator;
170 friend class DisplaySVGText;
171 friend class GlyphMetricsUpdater;
172 friend class MutationObserver;
173 friend class TextFrameIterator;
174 friend class TextNodeCorrespondenceRecorder;
175 friend struct TextRenderedRun;
176 friend class TextRenderedRunIterator;
178 using Range = gfxTextRun::Range;
179 using DrawTarget = gfx::DrawTarget;
180 using Path = gfx::Path;
181 using Point = gfx::Point;
182 using Rect = gfx::Rect;
184 protected:
185 explicit SVGTextFrame(ComputedStyle* aStyle, nsPresContext* aPresContext)
186 : SVGDisplayContainerFrame(aStyle, aPresContext, kClassID),
187 mTrailingUndisplayedCharacters(0),
188 mFontSizeScaleFactor(1.0f),
189 mLastContextScale(1.0f),
190 mLengthAdjustScaleFactor(1.0f) {
191 AddStateBits(NS_FRAME_SVG_LAYOUT | NS_FRAME_IS_SVG_TEXT |
192 NS_STATE_SVG_TEXT_CORRESPONDENCE_DIRTY |
193 NS_STATE_SVG_POSITIONING_DIRTY);
196 ~SVGTextFrame() = default;
198 public:
199 NS_DECL_QUERYFRAME
200 NS_DECL_FRAMEARENA_HELPERS(SVGTextFrame)
202 // nsIFrame:
203 void Init(nsIContent* aContent, nsContainerFrame* aParent,
204 nsIFrame* aPrevInFlow) override;
206 nsresult AttributeChanged(int32_t aNamespaceID, nsAtom* aAttribute,
207 int32_t aModType) override;
209 nsContainerFrame* GetContentInsertionFrame() override {
210 return PrincipalChildList().FirstChild()->GetContentInsertionFrame();
213 void BuildDisplayList(nsDisplayListBuilder* aBuilder,
214 const nsDisplayListSet& aLists) override;
216 #ifdef DEBUG_FRAME_DUMP
217 nsresult GetFrameName(nsAString& aResult) const override {
218 return MakeFrameName(u"SVGText"_ns, aResult);
220 #endif
223 * Finds the nsTextFrame for the closest rendered run to the specified point.
225 void FindCloserFrameForSelection(
226 const nsPoint& aPoint, FrameWithDistance* aCurrentBestFrame) override;
228 // ISVGDisplayableFrame interface:
229 void NotifySVGChanged(uint32_t aFlags) override;
230 void PaintSVG(gfxContext& aContext, const gfxMatrix& aTransform,
231 imgDrawingParams& aImgParams) override;
232 nsIFrame* GetFrameForPoint(const gfxPoint& aPoint) override;
233 void ReflowSVG() override;
234 SVGBBox GetBBoxContribution(const Matrix& aToBBoxUserspace,
235 uint32_t aFlags) override;
237 // SVG DOM text methods:
238 uint32_t GetNumberOfChars(nsIContent* aContent);
239 float GetComputedTextLength(nsIContent* aContent);
240 MOZ_CAN_RUN_SCRIPT_BOUNDARY void SelectSubString(nsIContent* aContent,
241 uint32_t charnum,
242 uint32_t nchars,
243 ErrorResult& aRv);
244 bool RequiresSlowFallbackForSubStringLength();
245 float GetSubStringLengthFastPath(nsIContent* aContent, uint32_t charnum,
246 uint32_t nchars, ErrorResult& aRv);
248 * This fallback version of GetSubStringLength takes
249 * into account glyph positioning and requires us to have flushed layout
250 * before calling it. As per the SVG 2 spec, typically glyph
251 * positioning does not affect the results of getSubStringLength, but one
252 * exception is text in a textPath where we need to ignore characters that
253 * fall off the end of the textPath path.
255 float GetSubStringLengthSlowFallback(nsIContent* aContent, uint32_t charnum,
256 uint32_t nchars, ErrorResult& aRv);
258 int32_t GetCharNumAtPosition(nsIContent* aContent,
259 const dom::DOMPointInit& aPoint);
261 already_AddRefed<dom::DOMSVGPoint> GetStartPositionOfChar(
262 nsIContent* aContent, uint32_t aCharNum, ErrorResult& aRv);
263 already_AddRefed<dom::DOMSVGPoint> GetEndPositionOfChar(nsIContent* aContent,
264 uint32_t aCharNum,
265 ErrorResult& aRv);
266 already_AddRefed<dom::SVGRect> GetExtentOfChar(nsIContent* aContent,
267 uint32_t aCharNum,
268 ErrorResult& aRv);
269 float GetRotationOfChar(nsIContent* aContent, uint32_t aCharNum,
270 ErrorResult& aRv);
272 // SVGTextFrame methods:
275 * Handles a base or animated attribute value change to a descendant
276 * text content element.
278 void HandleAttributeChangeInDescendant(dom::Element* aElement,
279 int32_t aNameSpaceID,
280 nsAtom* aAttribute);
283 * Calls ScheduleReflowSVGNonDisplayText if this is a non-display frame,
284 * and SVGUtils::ScheduleReflowSVG otherwise.
286 void ScheduleReflowSVG();
289 * Reflows the anonymous block frame of this non-display SVGTextFrame.
291 * When we are under SVGDisplayContainerFrame::ReflowSVG, we need to
292 * reflow any SVGTextFrame frames in the subtree in case they are
293 * being observed (by being for example in a <mask>) and the change
294 * that caused the reflow would not already have caused a reflow.
296 * Note that displayed SVGTextFrames are reflowed as needed, when PaintSVG
297 * is called or some SVG DOM method is called on the element.
299 void ReflowSVGNonDisplayText();
302 * This is a function that behaves similarly to SVGUtils::ScheduleReflowSVG,
303 * but which will skip over any ancestor non-display container frames on the
304 * way to the SVGOuterSVGFrame. It exists for the situation where a
305 * non-display <text> element has changed and needs to ensure ReflowSVG will
306 * be called on its closest display container frame, so that
307 * SVGDisplayContainerFrame::ReflowSVG will call ReflowSVGNonDisplayText on
308 * it.
310 * We have to do this in two cases: in response to a style change on a
311 * non-display <text>, where aReason will be
312 * IntrinsicDirty::FrameAncestorsAndDescendants (the common case), and also in
313 * response to glyphs changes on non-display <text> (i.e., animated
314 * SVG-in-OpenType glyphs), in which case aReason will be None, since layout
315 * doesn't need to be recomputed.
317 void ScheduleReflowSVGNonDisplayText(IntrinsicDirty aReason);
320 * Updates the mFontSizeScaleFactor value by looking at the range of
321 * font-sizes used within the <text>.
323 * @return Whether mFontSizeScaleFactor changed.
325 bool UpdateFontSizeScaleFactor();
327 double GetFontSizeScaleFactor() const;
330 * Takes a point from the <text> element's user space and
331 * converts it to the appropriate frame user space of aChildFrame,
332 * according to which rendered run the point hits.
334 Point TransformFramePointToTextChild(const Point& aPoint,
335 const nsIFrame* aChildFrame);
338 * Takes an app unit rectangle in the coordinate space of a given descendant
339 * frame of this frame, and returns a rectangle in the <text> element's user
340 * space that covers all parts of rendered runs that intersect with the
341 * rectangle.
343 gfxRect TransformFrameRectFromTextChild(const nsRect& aRect,
344 const nsIFrame* aChildFrame);
346 /** As above, but taking and returning a device px rect. */
347 Rect TransformFrameRectFromTextChild(const Rect& aRect,
348 const nsIFrame* aChildFrame);
350 /** As above, but with a single point */
351 Point TransformFramePointFromTextChild(const Point& aPoint,
352 const nsIFrame* aChildFrame);
354 // Return our ::-moz-svg-text anonymous box.
355 void AppendDirectlyOwnedAnonBoxes(nsTArray<OwnedAnonBox>& aResult) override;
357 private:
359 * Mutation observer used to watch for text positioning attribute changes
360 * on descendent text content elements (like <tspan>s).
362 class MutationObserver final : public nsStubMutationObserver {
363 public:
364 explicit MutationObserver(SVGTextFrame* aFrame) : mFrame(aFrame) {
365 MOZ_ASSERT(mFrame, "MutationObserver needs a non-null frame");
366 mFrame->GetContent()->AddMutationObserver(this);
367 SetEnabledCallbacks(kCharacterDataChanged | kAttributeChanged |
368 kContentAppended | kContentInserted |
369 kContentRemoved);
372 // nsISupports
373 NS_DECL_ISUPPORTS
375 // nsIMutationObserver
376 NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
377 NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
378 NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
379 NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED
380 NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
382 private:
383 ~MutationObserver() { mFrame->GetContent()->RemoveMutationObserver(this); }
385 SVGTextFrame* const mFrame;
389 * Resolves Bidi for the anonymous block child if it needs it.
391 void MaybeResolveBidiForAnonymousBlockChild();
394 * Reflows the anonymous block child if it is dirty or has dirty
395 * children, or if the SVGTextFrame itself is dirty.
397 void MaybeReflowAnonymousBlockChild();
400 * Performs the actual work of reflowing the anonymous block child.
402 void DoReflow();
405 * Schedules mPositions to be recomputed and the covered region to be
406 * updated.
408 void NotifyGlyphMetricsChange(bool aUpdateTextCorrespondence);
411 * Recomputes mPositions by calling DoGlyphPositioning if this information
412 * is out of date.
414 void UpdateGlyphPositioning();
417 * Populates mPositions with positioning information for each character
418 * within the <text>.
420 void DoGlyphPositioning();
423 * Converts the specified index into mPositions to an addressable
424 * character index (as can be used with the SVG DOM text methods)
425 * relative to the specified text child content element.
427 * @param aIndex The global character index.
428 * @param aContent The descendant text child content element that
429 * the returned addressable index will be relative to; null
430 * means the same as the <text> element.
431 * @return The addressable index, or -1 if the index cannot be
432 * represented as an addressable index relative to aContent.
434 int32_t ConvertTextElementCharIndexToAddressableIndex(int32_t aIndex,
435 nsIContent* aContent);
438 * Recursive helper for ResolvePositions below.
440 * @param aContent The current node.
441 * @param aIndex (in/out) The current character index.
442 * @param aInTextPath Whether we are currently under a <textPath> element.
443 * @param aForceStartOfChunk (in/out) Whether the next character we find
444 * should start a new anchored chunk.
445 * @param aDeltas (in/out) Receives the resolved dx/dy values for each
446 * character.
447 * @return false if we discover that mPositions did not have enough
448 * elements; true otherwise.
450 bool ResolvePositionsForNode(nsIContent* aContent, uint32_t& aIndex,
451 bool aInTextPath, bool& aForceStartOfChunk,
452 nsTArray<gfxPoint>& aDeltas);
455 * Initializes mPositions with character position information based on
456 * x/y/rotate attributes, leaving unspecified values in the array if a
457 * position was not given for that character. Also fills aDeltas with values
458 * based on dx/dy attributes.
460 * @param aDeltas (in/out) Receives the resolved dx/dy values for each
461 * character.
462 * @param aRunPerGlyph Whether mPositions should record that a new run begins
463 * at each glyph.
464 * @return false if we did not record any positions (due to having no
465 * displayed characters) or if we discover that mPositions did not have
466 * enough elements; true otherwise.
468 bool ResolvePositions(nsTArray<gfxPoint>& aDeltas, bool aRunPerGlyph);
471 * Determines the position, in app units, of each character in the <text> as
472 * laid out by reflow, and appends them to aPositions. Any characters that
473 * are undisplayed or trimmed away just get the last position.
475 void DetermineCharPositions(nsTArray<nsPoint>& aPositions);
478 * Sets mStartOfChunk to true for each character in mPositions that starts a
479 * line of text.
481 void AdjustChunksForLineBreaks();
484 * Adjusts recorded character positions in mPositions to account for glyph
485 * boundaries. Four things are done:
487 * 1. mClusterOrLigatureGroupMiddle is set to true for all such characters.
489 * 2. Any run and anchored chunk boundaries that begin in the middle of a
490 * cluster/ligature group get moved to the start of the next
491 * cluster/ligature group.
493 * 3. The position of any character in the middle of a cluster/ligature
494 * group is updated to take into account partial ligatures and any
495 * rotation the glyph as a whole has. (The values that come out of
496 * DetermineCharPositions which then get written into mPositions in
497 * ResolvePositions store the same position value for each part of the
498 * ligature.)
500 * 4. The rotation of any character in the middle of a cluster/ligature
501 * group is set to the rotation of the first character.
503 void AdjustPositionsForClusters();
506 * Updates the character positions stored in mPositions to account for
507 * text anchoring.
509 void DoAnchoring();
512 * Updates character positions in mPositions for those characters inside a
513 * <textPath>.
515 void DoTextPathLayout();
518 * Returns whether we need to render the text using
519 * nsTextFrame::DrawPathCallbacks rather than directly painting
520 * the text frames.
522 * @param aShouldPaintSVGGlyphs (out) Whether SVG glyphs in the text
523 * should be painted.
525 bool ShouldRenderAsPath(nsTextFrame* aFrame, bool& aShouldPaintSVGGlyphs);
527 // Methods to get information for a <textPath> frame.
528 already_AddRefed<Path> GetTextPath(nsIFrame* aTextPathFrame);
529 gfxFloat GetOffsetScale(nsIFrame* aTextPathFrame);
530 gfxFloat GetStartOffset(nsIFrame* aTextPathFrame);
533 * The MutationObserver we have registered for the <text> element subtree.
535 RefPtr<MutationObserver> mMutationObserver;
538 * The number of characters in the DOM after the final nsTextFrame. For
539 * example, with
541 * <text>abcd<tspan display="none">ef</tspan></text>
543 * mTrailingUndisplayedCharacters would be 2.
545 uint32_t mTrailingUndisplayedCharacters;
548 * Computed position information for each DOM character within the <text>.
550 nsTArray<CharPosition> mPositions;
553 * mFontSizeScaleFactor is used to cause the nsTextFrames to create text
554 * runs with a font size different from the actual font-size property value.
555 * This is used so that, for example with:
557 * <svg>
558 * <g transform="scale(2)">
559 * <text font-size="10">abc</text>
560 * </g>
561 * </svg>
563 * a font size of 20 would be used. It's preferable to use a font size that
564 * is identical or close to the size that the text will appear on the screen,
565 * because at very small or large font sizes, text metrics will be computed
566 * differently due to the limited precision that text runs have.
568 * mFontSizeScaleFactor is the amount the actual font-size property value
569 * should be multiplied by to cause the text run font size to (a) be within a
570 * "reasonable" range, and (b) be close to the actual size to be painted on
571 * screen. (The "reasonable" range as determined by some #defines in
572 * SVGTextFrame.cpp is 8..200.)
574 float mFontSizeScaleFactor;
577 * The scale of the context that we last used to compute mFontSizeScaleFactor.
578 * We record this so that we can tell when our scale transform has changed
579 * enough to warrant reflowing the text.
581 float mLastContextScale;
584 * The amount that we need to scale each rendered run to account for
585 * lengthAdjust="spacingAndGlyphs".
587 float mLengthAdjustScaleFactor;
590 } // namespace mozilla
592 #endif // LAYOUT_SVG_SVGTEXTFRAME_H_