Coverage Report

Created: 2018-09-25 14:53

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