Line data Source code
1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* This Source Code Form is subject to the terms of the Mozilla Public
3 : * License, v. 2.0. If a copy of the MPL was not distributed with this
4 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 :
6 : #ifndef MOZILLA_SVGTEXTFRAME_H
7 : #define MOZILLA_SVGTEXTFRAME_H
8 :
9 : #include "mozilla/Attributes.h"
10 : #include "mozilla/RefPtr.h"
11 : #include "mozilla/gfx/2D.h"
12 : #include "gfxMatrix.h"
13 : #include "gfxRect.h"
14 : #include "gfxTextRun.h"
15 : #include "nsAutoPtr.h"
16 : #include "nsIContent.h" // for GetContent
17 : #include "nsStubMutationObserver.h"
18 : #include "nsSVGContainerFrame.h"
19 :
20 : class gfxContext;
21 : class nsDisplaySVGText;
22 : class SVGTextFrame;
23 : class nsTextFrame;
24 :
25 : namespace mozilla {
26 :
27 : class CharIterator;
28 : class nsISVGPoint;
29 : class TextFrameIterator;
30 : class TextNodeCorrespondenceRecorder;
31 : struct TextRenderedRun;
32 : class TextRenderedRunIterator;
33 :
34 : namespace dom {
35 : class SVGIRect;
36 : class SVGPathElement;
37 : } // namespace dom
38 :
39 : /**
40 : * Information about the positioning for a single character in an SVG <text>
41 : * element.
42 : *
43 : * During SVG text layout, we use infinity values to represent positions and
44 : * rotations that are not explicitly specified with x/y/rotate attributes.
45 : */
46 : struct CharPosition
47 : {
48 : CharPosition()
49 : : mAngle(0),
50 : mHidden(false),
51 : mUnaddressable(false),
52 : mClusterOrLigatureGroupMiddle(false),
53 : mRunBoundary(false),
54 : mStartOfChunk(false)
55 : {
56 : }
57 :
58 0 : CharPosition(gfxPoint aPosition, double aAngle)
59 0 : : mPosition(aPosition),
60 : mAngle(aAngle),
61 : mHidden(false),
62 : mUnaddressable(false),
63 : mClusterOrLigatureGroupMiddle(false),
64 : mRunBoundary(false),
65 0 : mStartOfChunk(false)
66 : {
67 0 : }
68 :
69 0 : static CharPosition Unspecified(bool aUnaddressable)
70 : {
71 0 : CharPosition cp(UnspecifiedPoint(), UnspecifiedAngle());
72 0 : cp.mUnaddressable = aUnaddressable;
73 0 : return cp;
74 : }
75 :
76 0 : bool IsAngleSpecified() const
77 : {
78 0 : return mAngle != UnspecifiedAngle();
79 : }
80 :
81 0 : bool IsXSpecified() const
82 : {
83 0 : return mPosition.x != UnspecifiedCoord();
84 : }
85 :
86 0 : bool IsYSpecified() const
87 : {
88 0 : return mPosition.y != UnspecifiedCoord();
89 : }
90 :
91 : gfxPoint mPosition;
92 : double mAngle;
93 :
94 : // not displayed due to falling off the end of a <textPath>
95 : bool mHidden;
96 :
97 : // skipped in positioning attributes due to being collapsed-away white space
98 : bool mUnaddressable;
99 :
100 : // a preceding character is what positioning attributes address
101 : bool mClusterOrLigatureGroupMiddle;
102 :
103 : // rendering is split here since an explicit position or rotation was given
104 : bool mRunBoundary;
105 :
106 : // an anchored chunk begins here
107 : bool mStartOfChunk;
108 :
109 : private:
110 0 : static gfxFloat UnspecifiedCoord()
111 : {
112 0 : return std::numeric_limits<gfxFloat>::infinity();
113 : }
114 :
115 0 : static double UnspecifiedAngle()
116 : {
117 0 : return std::numeric_limits<double>::infinity();
118 : }
119 :
120 0 : static gfxPoint UnspecifiedPoint()
121 : {
122 0 : return gfxPoint(UnspecifiedCoord(), UnspecifiedCoord());
123 : }
124 : };
125 :
126 : /**
127 : * A runnable to mark glyph positions as needing to be recomputed
128 : * and to invalid the bounds of the SVGTextFrame frame.
129 : */
130 : class GlyphMetricsUpdater : public Runnable {
131 : public:
132 : NS_DECL_NSIRUNNABLE
133 : explicit GlyphMetricsUpdater(SVGTextFrame* aFrame)
134 : : Runnable("GlyphMetricsUpdater")
135 : , mFrame(aFrame)
136 : {
137 : }
138 : static void Run(SVGTextFrame* aFrame);
139 : void Revoke() { mFrame = nullptr; }
140 : private:
141 : SVGTextFrame* mFrame;
142 : };
143 :
144 : } // namespace mozilla
145 :
146 : /**
147 : * Frame class for SVG <text> elements.
148 : *
149 : * An SVGTextFrame manages SVG text layout, painting and interaction for
150 : * all descendent text content elements. The frame tree will look like this:
151 : *
152 : * SVGTextFrame -- for <text>
153 : * <anonymous block frame>
154 : * ns{Block,Inline,Text}Frames -- for text nodes, <tspan>s, <a>s, etc.
155 : *
156 : * SVG text layout is done by:
157 : *
158 : * 1. Reflowing the anonymous block frame.
159 : * 2. Inspecting the (app unit) positions of the glyph for each character in
160 : * the nsTextFrames underneath the anonymous block frame.
161 : * 3. Determining the (user unit) positions for each character in the <text>
162 : * using the x/y/dx/dy/rotate attributes on all the text content elements,
163 : * and using the step 2 results to fill in any gaps.
164 : * 4. Applying any other SVG specific text layout (anchoring and text paths)
165 : * to the positions computed in step 3.
166 : *
167 : * Rendering of the text is done by splitting up each nsTextFrame into ranges
168 : * that can be contiguously painted. (For example <text x="10 20">abcd</text>
169 : * would have two contiguous ranges: one for the "a" and one for the "bcd".)
170 : * Each range is called a "text rendered run", represented by a TextRenderedRun
171 : * object. The TextRenderedRunIterator class performs that splitting and
172 : * returns a TextRenderedRun for each bit of text to be painted separately.
173 : *
174 : * Each rendered run is painted by calling nsTextFrame::PaintText. If the text
175 : * formatting is simple enough (solid fill, no stroking, etc.), PaintText will
176 : * itself do the painting. Otherwise, a DrawPathCallback is passed to
177 : * PaintText so that we can fill the text geometry with SVG paint servers.
178 : */
179 : class SVGTextFrame final : public nsSVGDisplayContainerFrame
180 : {
181 : friend nsIFrame*
182 : NS_NewSVGTextFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
183 :
184 : friend class mozilla::CharIterator;
185 : friend class mozilla::GlyphMetricsUpdater;
186 : friend class mozilla::TextFrameIterator;
187 : friend class mozilla::TextNodeCorrespondenceRecorder;
188 : friend struct mozilla::TextRenderedRun;
189 : friend class mozilla::TextRenderedRunIterator;
190 : friend class MutationObserver;
191 : friend class nsDisplaySVGText;
192 :
193 : typedef gfxTextRun::Range Range;
194 : typedef mozilla::gfx::DrawTarget DrawTarget;
195 : typedef mozilla::gfx::Path Path;
196 : typedef mozilla::gfx::Point Point;
197 :
198 : protected:
199 0 : explicit SVGTextFrame(nsStyleContext* aContext)
200 0 : : nsSVGDisplayContainerFrame(aContext, kClassID)
201 : , mTrailingUndisplayedCharacters(0)
202 : , mFontSizeScaleFactor(1.0f)
203 : , mLastContextScale(1.0f)
204 0 : , mLengthAdjustScaleFactor(1.0f)
205 : {
206 0 : AddStateBits(NS_STATE_SVG_POSITIONING_DIRTY);
207 0 : }
208 :
209 0 : ~SVGTextFrame() {}
210 :
211 : public:
212 : NS_DECL_QUERYFRAME
213 0 : NS_DECL_FRAMEARENA_HELPERS(SVGTextFrame)
214 :
215 : // nsIFrame:
216 : virtual void Init(nsIContent* aContent,
217 : nsContainerFrame* aParent,
218 : nsIFrame* aPrevInFlow) override;
219 :
220 : virtual nsresult AttributeChanged(int32_t aNamespaceID,
221 : nsIAtom* aAttribute,
222 : int32_t aModType) override;
223 :
224 0 : virtual nsContainerFrame* GetContentInsertionFrame() override
225 : {
226 0 : return PrincipalChildList().FirstChild()->GetContentInsertionFrame();
227 : }
228 :
229 : virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
230 : const nsRect& aDirtyRect,
231 : const nsDisplayListSet& aLists) override;
232 :
233 : #ifdef DEBUG_FRAME_DUMP
234 0 : virtual nsresult GetFrameName(nsAString& aResult) const override
235 : {
236 0 : return MakeFrameName(NS_LITERAL_STRING("SVGText"), aResult);
237 : }
238 : #endif
239 :
240 : virtual void DidSetStyleContext(nsStyleContext* aOldStyleContext) override;
241 :
242 : /**
243 : * Finds the nsTextFrame for the closest rendered run to the specified point.
244 : */
245 : virtual void FindCloserFrameForSelection(nsPoint aPoint,
246 : FrameWithDistance* aCurrentBestFrame) override;
247 :
248 :
249 :
250 : // nsSVGDisplayableFrame interface:
251 : virtual void NotifySVGChanged(uint32_t aFlags) override;
252 : virtual void PaintSVG(gfxContext& aContext,
253 : const gfxMatrix& aTransform,
254 : imgDrawingParams& aImgParams,
255 : const nsIntRect* aDirtyRect = nullptr) override;
256 : virtual nsIFrame* GetFrameForPoint(const gfxPoint& aPoint) override;
257 : virtual void ReflowSVG() override;
258 : virtual SVGBBox GetBBoxContribution(const Matrix& aToBBoxUserspace,
259 : uint32_t aFlags) override;
260 :
261 : // nsSVGContainerFrame methods:
262 : virtual gfxMatrix GetCanvasTM() override;
263 :
264 : // SVG DOM text methods:
265 : uint32_t GetNumberOfChars(nsIContent* aContent);
266 : float GetComputedTextLength(nsIContent* aContent);
267 : nsresult SelectSubString(nsIContent* aContent, uint32_t charnum, uint32_t nchars);
268 : nsresult GetSubStringLength(nsIContent* aContent, uint32_t charnum,
269 : uint32_t nchars, float* aResult);
270 : int32_t GetCharNumAtPosition(nsIContent* aContent, mozilla::nsISVGPoint* point);
271 :
272 : nsresult GetStartPositionOfChar(nsIContent* aContent, uint32_t aCharNum,
273 : mozilla::nsISVGPoint** aResult);
274 : nsresult GetEndPositionOfChar(nsIContent* aContent, uint32_t aCharNum,
275 : mozilla::nsISVGPoint** aResult);
276 : nsresult GetExtentOfChar(nsIContent* aContent, uint32_t aCharNum,
277 : mozilla::dom::SVGIRect** aResult);
278 : nsresult GetRotationOfChar(nsIContent* aContent, uint32_t aCharNum,
279 : float* aResult);
280 :
281 : // SVGTextFrame methods:
282 :
283 : /**
284 : * Handles a base or animated attribute value change to a descendant
285 : * text content element.
286 : */
287 : void HandleAttributeChangeInDescendant(mozilla::dom::Element* aElement,
288 : int32_t aNameSpaceID,
289 : nsIAtom* aAttribute);
290 :
291 : /**
292 : * Schedules mPositions to be recomputed and the covered region to be
293 : * updated.
294 : */
295 : void NotifyGlyphMetricsChange();
296 :
297 : /**
298 : * Calls ScheduleReflowSVGNonDisplayText if this is a non-display frame,
299 : * and nsSVGUtils::ScheduleReflowSVG otherwise.
300 : */
301 : void ScheduleReflowSVG();
302 :
303 : /**
304 : * Reflows the anonymous block frame of this non-display SVGTextFrame.
305 : *
306 : * When we are under nsSVGDisplayContainerFrame::ReflowSVG, we need to
307 : * reflow any SVGTextFrame frames in the subtree in case they are
308 : * being observed (by being for example in a <mask>) and the change
309 : * that caused the reflow would not already have caused a reflow.
310 : *
311 : * Note that displayed SVGTextFrames are reflowed as needed, when PaintSVG
312 : * is called or some SVG DOM method is called on the element.
313 : */
314 : void ReflowSVGNonDisplayText();
315 :
316 : /**
317 : * This is a function that behaves similarly to nsSVGUtils::ScheduleReflowSVG,
318 : * but which will skip over any ancestor non-display container frames on the
319 : * way to the nsSVGOuterSVGFrame. It exists for the situation where a
320 : * non-display <text> element has changed and needs to ensure ReflowSVG will
321 : * be called on its closest display container frame, so that
322 : * nsSVGDisplayContainerFrame::ReflowSVG will call ReflowSVGNonDisplayText on
323 : * it.
324 : *
325 : * We have to do this in two cases: in response to a style change on a
326 : * non-display <text>, where aReason will be eStyleChange (the common case),
327 : * and also in response to glyphs changes on non-display <text> (i.e.,
328 : * animated SVG-in-OpenType glyphs), in which case aReason will be eResize,
329 : * since layout doesn't need to be recomputed.
330 : */
331 : void ScheduleReflowSVGNonDisplayText(nsIPresShell::IntrinsicDirty aReason);
332 :
333 : /**
334 : * Updates the mFontSizeScaleFactor value by looking at the range of
335 : * font-sizes used within the <text>.
336 : *
337 : * @return Whether mFontSizeScaleFactor changed.
338 : */
339 : bool UpdateFontSizeScaleFactor();
340 :
341 : double GetFontSizeScaleFactor() const;
342 :
343 : /**
344 : * Takes a point from the <text> element's user space and
345 : * converts it to the appropriate frame user space of aChildFrame,
346 : * according to which rendered run the point hits.
347 : */
348 : Point TransformFramePointToTextChild(const Point& aPoint,
349 : nsIFrame* aChildFrame);
350 :
351 : /**
352 : * Takes an app unit rectangle in the coordinate space of a given descendant
353 : * frame of this frame, and returns a rectangle in the <text> element's user
354 : * space that covers all parts of rendered runs that intersect with the
355 : * rectangle.
356 : */
357 : gfxRect TransformFrameRectFromTextChild(const nsRect& aRect,
358 : nsIFrame* aChildFrame);
359 :
360 : // Return our ::-moz-svg-text anonymous box.
361 : void AppendDirectlyOwnedAnonBoxes(nsTArray<OwnedAnonBox>& aResult) override;
362 :
363 : private:
364 : /**
365 : * Mutation observer used to watch for text positioning attribute changes
366 : * on descendent text content elements (like <tspan>s).
367 : */
368 : class MutationObserver final : public nsStubMutationObserver {
369 : public:
370 0 : explicit MutationObserver(SVGTextFrame* aFrame)
371 0 : : mFrame(aFrame)
372 : {
373 0 : MOZ_ASSERT(mFrame, "MutationObserver needs a non-null frame");
374 0 : mFrame->GetContent()->AddMutationObserver(this);
375 0 : }
376 :
377 : // nsISupports
378 : NS_DECL_ISUPPORTS
379 :
380 : // nsIMutationObserver
381 : NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
382 : NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
383 : NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
384 : NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED
385 : NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
386 :
387 : private:
388 0 : ~MutationObserver()
389 0 : {
390 0 : mFrame->GetContent()->RemoveMutationObserver(this);
391 0 : }
392 :
393 : SVGTextFrame* const mFrame;
394 : };
395 :
396 : /**
397 : * Reflows the anonymous block child if it is dirty or has dirty
398 : * children, or if the SVGTextFrame itself is dirty.
399 : */
400 : void MaybeReflowAnonymousBlockChild();
401 :
402 : /**
403 : * Performs the actual work of reflowing the anonymous block child.
404 : */
405 : void DoReflow();
406 :
407 : /**
408 : * Recomputes mPositions by calling DoGlyphPositioning if this information
409 : * is out of date.
410 : */
411 : void UpdateGlyphPositioning();
412 :
413 : /**
414 : * Populates mPositions with positioning information for each character
415 : * within the <text>.
416 : */
417 : void DoGlyphPositioning();
418 :
419 : /**
420 : * Converts the specified index into mPositions to an addressable
421 : * character index (as can be used with the SVG DOM text methods)
422 : * relative to the specified text child content element.
423 : *
424 : * @param aIndex The global character index.
425 : * @param aContent The descendant text child content element that
426 : * the returned addressable index will be relative to; null
427 : * means the same as the <text> element.
428 : * @return The addressable index, or -1 if the index cannot be
429 : * represented as an addressable index relative to aContent.
430 : */
431 : int32_t
432 : ConvertTextElementCharIndexToAddressableIndex(int32_t aIndex,
433 : nsIContent* aContent);
434 :
435 : /**
436 : * Recursive helper for ResolvePositions below.
437 : *
438 : * @param aContent The current node.
439 : * @param aIndex (in/out) The current character index.
440 : * @param aInTextPath Whether we are currently under a <textPath> element.
441 : * @param aForceStartOfChunk (in/out) Whether the next character we find
442 : * should start a new anchored chunk.
443 : * @param aDeltas (in/out) Receives the resolved dx/dy values for each
444 : * character.
445 : * @return false if we discover that mPositions did not have enough
446 : * elements; true otherwise.
447 : */
448 : bool ResolvePositionsForNode(nsIContent* aContent, uint32_t& aIndex,
449 : bool aInTextPath, bool& aForceStartOfChunk,
450 : nsTArray<gfxPoint>& aDeltas);
451 :
452 : /**
453 : * Initializes mPositions with character position information based on
454 : * x/y/rotate attributes, leaving unspecified values in the array if a position
455 : * was not given for that character. Also fills aDeltas with values based on
456 : * dx/dy attributes.
457 : *
458 : * @param aDeltas (in/out) Receives the resolved dx/dy values for each
459 : * character.
460 : * @param aRunPerGlyph Whether mPositions should record that a new run begins
461 : * at each glyph.
462 : * @return false if we did not record any positions (due to having no
463 : * displayed characters) or if we discover that mPositions did not have
464 : * enough elements; true otherwise.
465 : */
466 : bool ResolvePositions(nsTArray<gfxPoint>& aDeltas, bool aRunPerGlyph);
467 :
468 : /**
469 : * Determines the position, in app units, of each character in the <text> as
470 : * laid out by reflow, and appends them to aPositions. Any characters that
471 : * are undisplayed or trimmed away just get the last position.
472 : */
473 : void DetermineCharPositions(nsTArray<nsPoint>& aPositions);
474 :
475 : /**
476 : * Sets mStartOfChunk to true for each character in mPositions that starts a
477 : * line of text.
478 : */
479 : void AdjustChunksForLineBreaks();
480 :
481 : /**
482 : * Adjusts recorded character positions in mPositions to account for glyph
483 : * boundaries. Four things are done:
484 : *
485 : * 1. mClusterOrLigatureGroupMiddle is set to true for all such characters.
486 : *
487 : * 2. Any run and anchored chunk boundaries that begin in the middle of a
488 : * cluster/ligature group get moved to the start of the next
489 : * cluster/ligature group.
490 : *
491 : * 3. The position of any character in the middle of a cluster/ligature
492 : * group is updated to take into account partial ligatures and any
493 : * rotation the glyph as a whole has. (The values that come out of
494 : * DetermineCharPositions which then get written into mPositions in
495 : * ResolvePositions store the same position value for each part of the
496 : * ligature.)
497 : *
498 : * 4. The rotation of any character in the middle of a cluster/ligature
499 : * group is set to the rotation of the first character.
500 : */
501 : void AdjustPositionsForClusters();
502 :
503 : /**
504 : * Updates the character positions stored in mPositions to account for
505 : * text anchoring.
506 : */
507 : void DoAnchoring();
508 :
509 : /**
510 : * Updates character positions in mPositions for those characters inside a
511 : * <textPath>.
512 : */
513 : void DoTextPathLayout();
514 :
515 : /**
516 : * Returns whether we need to render the text using
517 : * nsTextFrame::DrawPathCallbacks rather than directly painting
518 : * the text frames.
519 : *
520 : * @param aShouldPaintSVGGlyphs (out) Whether SVG glyphs in the text
521 : * should be painted.
522 : */
523 : bool ShouldRenderAsPath(nsTextFrame* aFrame, bool& aShouldPaintSVGGlyphs);
524 :
525 : // Methods to get information for a <textPath> frame.
526 : mozilla::dom::SVGPathElement*
527 : GetTextPathPathElement(nsIFrame* aTextPathFrame);
528 : already_AddRefed<Path> GetTextPath(nsIFrame* aTextPathFrame);
529 : gfxFloat GetOffsetScale(nsIFrame* aTextPathFrame);
530 : gfxFloat GetStartOffset(nsIFrame* aTextPathFrame);
531 :
532 : /**
533 : * The MutationObserver we have registered for the <text> element subtree.
534 : */
535 : RefPtr<MutationObserver> mMutationObserver;
536 :
537 : /**
538 : * Cached canvasTM value.
539 : */
540 : nsAutoPtr<gfxMatrix> mCanvasTM;
541 :
542 : /**
543 : * The number of characters in the DOM after the final nsTextFrame. For
544 : * example, with
545 : *
546 : * <text>abcd<tspan display="none">ef</tspan></text>
547 : *
548 : * mTrailingUndisplayedCharacters would be 2.
549 : */
550 : uint32_t mTrailingUndisplayedCharacters;
551 :
552 : /**
553 : * Computed position information for each DOM character within the <text>.
554 : */
555 : nsTArray<mozilla::CharPosition> mPositions;
556 :
557 : /**
558 : * mFontSizeScaleFactor is used to cause the nsTextFrames to create text
559 : * runs with a font size different from the actual font-size property value.
560 : * This is used so that, for example with:
561 : *
562 : * <svg>
563 : * <g transform="scale(2)">
564 : * <text font-size="10">abc</text>
565 : * </g>
566 : * </svg>
567 : *
568 : * a font size of 20 would be used. It's preferable to use a font size that
569 : * is identical or close to the size that the text will appear on the screen,
570 : * because at very small or large font sizes, text metrics will be computed
571 : * differently due to the limited precision that text runs have.
572 : *
573 : * mFontSizeScaleFactor is the amount the actual font-size property value
574 : * should be multiplied by to cause the text run font size to (a) be within a
575 : * "reasonable" range, and (b) be close to the actual size to be painted on
576 : * screen. (The "reasonable" range as determined by some #defines in
577 : * SVGTextFrame.cpp is 8..200.)
578 : */
579 : float mFontSizeScaleFactor;
580 :
581 : /**
582 : * The scale of the context that we last used to compute mFontSizeScaleFactor.
583 : * We record this so that we can tell when our scale transform has changed
584 : * enough to warrant reflowing the text.
585 : */
586 : float mLastContextScale;
587 :
588 : /**
589 : * The amount that we need to scale each rendered run to account for
590 : * lengthAdjust="spacingAndGlyphs".
591 : */
592 : float mLengthAdjustScaleFactor;
593 : };
594 :
595 : #endif
|