Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/layout/svg/SVGGeometryFrame.cpp
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
// Main header first:
8
#include "SVGGeometryFrame.h"
9
10
// Keep others in (case-insensitive) order:
11
#include "gfx2DGlue.h"
12
#include "gfxContext.h"
13
#include "gfxPlatform.h"
14
#include "gfxUtils.h"
15
#include "mozilla/gfx/2D.h"
16
#include "mozilla/gfx/Helpers.h"
17
#include "mozilla/RefPtr.h"
18
#include "mozilla/SVGContextPaint.h"
19
#include "nsDisplayList.h"
20
#include "nsGkAtoms.h"
21
#include "nsLayoutUtils.h"
22
#include "SVGObserverUtils.h"
23
#include "nsSVGIntegrationUtils.h"
24
#include "nsSVGMarkerFrame.h"
25
#include "SVGGeometryElement.h"
26
#include "nsSVGUtils.h"
27
#include "mozilla/ArrayUtils.h"
28
#include "SVGAnimatedTransformList.h"
29
#include "SVGContentUtils.h"
30
#include "SVGGraphicsElement.h"
31
32
using namespace mozilla;
33
using namespace mozilla::dom;
34
using namespace mozilla::gfx;
35
using namespace mozilla::image;
36
37
//----------------------------------------------------------------------
38
// Implementation
39
40
nsIFrame*
41
NS_NewSVGGeometryFrame(nsIPresShell* aPresShell,
42
                       ComputedStyle* aStyle)
43
0
{
44
0
  return new (aPresShell) SVGGeometryFrame(aStyle);
45
0
}
46
47
NS_IMPL_FRAMEARENA_HELPERS(SVGGeometryFrame)
48
49
//----------------------------------------------------------------------
50
// nsQueryFrame methods
51
52
0
NS_QUERYFRAME_HEAD(SVGGeometryFrame)
53
0
  NS_QUERYFRAME_ENTRY(nsSVGDisplayableFrame)
54
0
  NS_QUERYFRAME_ENTRY(SVGGeometryFrame)
55
0
NS_QUERYFRAME_TAIL_INHERITING(nsFrame)
56
57
//----------------------------------------------------------------------
58
// Display list item:
59
60
class nsDisplaySVGGeometry final : public nsDisplayItem
61
{
62
  typedef mozilla::image::imgDrawingParams imgDrawingParams;
63
64
public:
65
  nsDisplaySVGGeometry(nsDisplayListBuilder* aBuilder,
66
                       SVGGeometryFrame* aFrame)
67
    : nsDisplayItem(aBuilder, aFrame)
68
0
  {
69
0
    MOZ_COUNT_CTOR(nsDisplaySVGGeometry);
70
0
    MOZ_ASSERT(aFrame, "Must have a frame!");
71
0
  }
72
#ifdef NS_BUILD_REFCNT_LOGGING
73
  virtual ~nsDisplaySVGGeometry() {
74
    MOZ_COUNT_DTOR(nsDisplaySVGGeometry);
75
  }
76
#endif
77
78
  NS_DISPLAY_DECL_NAME("nsDisplaySVGGeometry", TYPE_SVG_GEOMETRY)
79
80
  virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
81
                       HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames) override;
82
  virtual void Paint(nsDisplayListBuilder* aBuilder,
83
                     gfxContext* aCtx) override;
84
85
  nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) override
86
0
  {
87
0
    return new nsDisplayItemGenericImageGeometry(this, aBuilder);
88
0
  }
89
90
  void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
91
                                 const nsDisplayItemGeometry* aGeometry,
92
                                 nsRegion *aInvalidRegion) const override;
93
};
94
95
void
96
nsDisplaySVGGeometry::HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
97
                              HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames)
98
0
{
99
0
  SVGGeometryFrame *frame = static_cast<SVGGeometryFrame*>(mFrame);
100
0
  nsPoint pointRelativeToReferenceFrame = aRect.Center();
101
0
  // ToReferenceFrame() includes frame->GetPosition(), our user space position.
102
0
  nsPoint userSpacePtInAppUnits = pointRelativeToReferenceFrame -
103
0
                                   (ToReferenceFrame() - frame->GetPosition());
104
0
  gfxPoint userSpacePt =
105
0
    gfxPoint(userSpacePtInAppUnits.x, userSpacePtInAppUnits.y) /
106
0
      AppUnitsPerCSSPixel();
107
0
  if (frame->GetFrameForPoint(userSpacePt)) {
108
0
    aOutFrames->AppendElement(frame);
109
0
  }
110
0
}
111
112
void
113
nsDisplaySVGGeometry::Paint(nsDisplayListBuilder* aBuilder,
114
                            gfxContext* aCtx)
115
0
{
116
0
  uint32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
117
0
118
0
  // ToReferenceFrame includes our mRect offset, but painting takes
119
0
  // account of that too. To avoid double counting, we subtract that
120
0
  // here.
121
0
  nsPoint offset = ToReferenceFrame() - mFrame->GetPosition();
122
0
123
0
  gfxPoint devPixelOffset =
124
0
    nsLayoutUtils::PointToGfxPoint(offset, appUnitsPerDevPixel);
125
0
126
0
  gfxMatrix tm = nsSVGUtils::GetCSSPxToDevPxMatrix(mFrame) *
127
0
                   gfxMatrix::Translation(devPixelOffset);
128
0
  imgDrawingParams imgParams(aBuilder->ShouldSyncDecodeImages()
129
0
                             ? imgIContainer::FLAG_SYNC_DECODE
130
0
                             : imgIContainer::FLAG_SYNC_DECODE_IF_FAST);
131
0
132
0
  static_cast<SVGGeometryFrame*>(mFrame)->PaintSVG(*aCtx,
133
0
                                                   tm, imgParams);
134
0
135
0
  nsDisplayItemGenericImageGeometry::UpdateDrawResult(this, imgParams.result);
136
0
}
137
138
void
139
nsDisplaySVGGeometry::ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
140
                                                const nsDisplayItemGeometry* aGeometry,
141
                                                nsRegion* aInvalidRegion) const
142
0
{
143
0
  auto geometry =
144
0
    static_cast<const nsDisplayItemGenericImageGeometry*>(aGeometry);
145
0
146
0
  if (aBuilder->ShouldSyncDecodeImages() &&
147
0
      geometry->ShouldInvalidateToSyncDecodeImages()) {
148
0
    bool snap;
149
0
    aInvalidRegion->Or(*aInvalidRegion, GetBounds(aBuilder, &snap));
150
0
  }
151
0
152
0
  nsDisplayItem::ComputeInvalidationRegion(aBuilder, aGeometry, aInvalidRegion);
153
0
}
154
155
namespace mozilla {
156
157
//----------------------------------------------------------------------
158
// nsIFrame methods
159
160
void
161
SVGGeometryFrame::Init(nsIContent*       aContent,
162
                       nsContainerFrame* aParent,
163
                       nsIFrame*         aPrevInFlow)
164
0
{
165
0
  AddStateBits(aParent->GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD);
166
0
  nsFrame::Init(aContent, aParent, aPrevInFlow);
167
0
}
168
169
nsresult
170
SVGGeometryFrame::AttributeChanged(int32_t         aNameSpaceID,
171
                                   nsAtom*        aAttribute,
172
                                   int32_t         aModType)
173
0
{
174
0
  // We don't invalidate for transform changes (the layers code does that).
175
0
  // Also note that SVGTransformableElement::GetAttributeChangeHint will
176
0
  // return nsChangeHint_UpdateOverflow for "transform" attribute changes
177
0
  // and cause DoApplyRenderingChangeToTree to make the SchedulePaint call.
178
0
179
0
  if (aNameSpaceID == kNameSpaceID_None &&
180
0
      (static_cast<SVGGeometryElement*>
181
0
                  (GetContent())->AttributeDefinesGeometry(aAttribute))) {
182
0
    nsLayoutUtils::PostRestyleEvent(
183
0
      mContent->AsElement(), nsRestyleHint(0),
184
0
      nsChangeHint_InvalidateRenderingObservers);
185
0
    nsSVGUtils::ScheduleReflowSVG(this);
186
0
  }
187
0
  return NS_OK;
188
0
}
189
190
/* virtual */ void
191
SVGGeometryFrame::DidSetComputedStyle(ComputedStyle* aOldComputedStyle)
192
0
{
193
0
  nsFrame::DidSetComputedStyle(aOldComputedStyle);
194
0
195
0
  if (aOldComputedStyle) {
196
0
    SVGGeometryElement* element =
197
0
      static_cast<SVGGeometryElement*>(GetContent());
198
0
199
0
    auto oldStyleSVG = aOldComputedStyle->PeekStyleSVG();
200
0
    if (oldStyleSVG && !SVGContentUtils::ShapeTypeHasNoCorners(GetContent())) {
201
0
      if (StyleSVG()->mStrokeLinecap != oldStyleSVG->mStrokeLinecap &&
202
0
          element->IsSVGElement(nsGkAtoms::path)) {
203
0
        // If the stroke-linecap changes to or from "butt" then our element
204
0
        // needs to update its cached Moz2D Path, since SVGPathData::BuildPath
205
0
        // decides whether or not to insert little lines into the path for zero
206
0
        // length subpaths base on that property.
207
0
        element->ClearAnyCachedPath();
208
0
      } else if (GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD) {
209
0
        if (StyleSVG()->mClipRule != oldStyleSVG->mClipRule) {
210
0
          // Moz2D Path objects are fill-rule specific.
211
0
          // For clipPath we use clip-rule as the path's fill-rule.
212
0
          element->ClearAnyCachedPath();
213
0
        }
214
0
      } else {
215
0
        if (StyleSVG()->mFillRule != oldStyleSVG->mFillRule) {
216
0
          // Moz2D Path objects are fill-rule specific.
217
0
          element->ClearAnyCachedPath();
218
0
        }
219
0
      }
220
0
    }
221
0
  }
222
0
}
223
224
bool
225
SVGGeometryFrame::IsSVGTransformed(gfx::Matrix *aOwnTransform,
226
                                   gfx::Matrix *aFromParentTransform) const
227
0
{
228
0
  bool foundTransform = false;
229
0
230
0
  // Check if our parent has children-only transforms:
231
0
  nsIFrame *parent = GetParent();
232
0
  if (parent &&
233
0
      parent->IsFrameOfType(nsIFrame::eSVG | nsIFrame::eSVGContainer)) {
234
0
    foundTransform = static_cast<nsSVGContainerFrame*>(parent)->
235
0
                       HasChildrenOnlyTransform(aFromParentTransform);
236
0
  }
237
0
238
0
  nsSVGElement *content = static_cast<nsSVGElement*>(GetContent());
239
0
  nsSVGAnimatedTransformList* transformList =
240
0
    content->GetAnimatedTransformList();
241
0
  if ((transformList && transformList->HasTransform()) ||
242
0
      content->GetAnimateMotionTransform()) {
243
0
    if (aOwnTransform) {
244
0
      *aOwnTransform = gfx::ToMatrix(
245
0
                         content->PrependLocalTransformsTo(
246
0
                           gfxMatrix(),
247
0
                           eUserSpaceToParent));
248
0
    }
249
0
    foundTransform = true;
250
0
  }
251
0
  return foundTransform;
252
0
}
253
254
void
255
SVGGeometryFrame::BuildDisplayList(nsDisplayListBuilder*   aBuilder,
256
                                   const nsDisplayListSet& aLists)
257
0
{
258
0
  if (!static_cast<const nsSVGElement*>(GetContent())->HasValidDimensions() ||
259
0
      (!IsVisibleForPainting(aBuilder) && aBuilder->IsForPainting())) {
260
0
    return;
261
0
  }
262
0
  DisplayOutline(aBuilder, aLists);
263
0
  aLists.Content()->AppendToTop(
264
0
    MakeDisplayItem<nsDisplaySVGGeometry>(aBuilder, this));
265
0
}
266
267
//----------------------------------------------------------------------
268
// nsSVGDisplayableFrame methods
269
270
void
271
SVGGeometryFrame::PaintSVG(gfxContext& aContext,
272
                           const gfxMatrix& aTransform,
273
                           imgDrawingParams& aImgParams,
274
                           const nsIntRect* aDirtyRect)
275
0
{
276
0
  if (!StyleVisibility()->IsVisible())
277
0
    return;
278
0
279
0
  // Matrix to the geometry's user space:
280
0
  gfxMatrix newMatrix =
281
0
    aContext.CurrentMatrixDouble().PreMultiply(aTransform).NudgeToIntegers();
282
0
  if (newMatrix.IsSingular()) {
283
0
    return;
284
0
  }
285
0
286
0
  uint32_t paintOrder = StyleSVG()->mPaintOrder;
287
0
288
0
  if (paintOrder == NS_STYLE_PAINT_ORDER_NORMAL) {
289
0
    Render(&aContext, eRenderFill | eRenderStroke, newMatrix, aImgParams);
290
0
    PaintMarkers(aContext, aTransform, aImgParams);
291
0
  } else {
292
0
    while (paintOrder) {
293
0
      uint32_t component =
294
0
        paintOrder & ((1 << NS_STYLE_PAINT_ORDER_BITWIDTH) - 1);
295
0
      switch (component) {
296
0
        case NS_STYLE_PAINT_ORDER_FILL:
297
0
          Render(&aContext, eRenderFill, newMatrix, aImgParams);
298
0
          break;
299
0
        case NS_STYLE_PAINT_ORDER_STROKE:
300
0
          Render(&aContext, eRenderStroke, newMatrix, aImgParams);
301
0
          break;
302
0
        case NS_STYLE_PAINT_ORDER_MARKERS:
303
0
          PaintMarkers(aContext, aTransform, aImgParams);
304
0
          break;
305
0
      }
306
0
      paintOrder >>= NS_STYLE_PAINT_ORDER_BITWIDTH;
307
0
    }
308
0
  }
309
0
}
310
311
nsIFrame*
312
SVGGeometryFrame::GetFrameForPoint(const gfxPoint& aPoint)
313
0
{
314
0
  FillRule fillRule;
315
0
  uint16_t hitTestFlags;
316
0
  if (GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD) {
317
0
    hitTestFlags = SVG_HIT_TEST_FILL;
318
0
    fillRule = nsSVGUtils::ToFillRule(StyleSVG()->mClipRule);
319
0
  } else {
320
0
    hitTestFlags = GetHitTestFlags();
321
0
    if (!hitTestFlags) {
322
0
      return nullptr;
323
0
    }
324
0
    if (hitTestFlags & SVG_HIT_TEST_CHECK_MRECT) {
325
0
      gfxRect rect =
326
0
        nsLayoutUtils::RectToGfxRect(mRect, AppUnitsPerCSSPixel());
327
0
      if (!rect.Contains(aPoint)) {
328
0
        return nullptr;
329
0
      }
330
0
    }
331
0
    fillRule = nsSVGUtils::ToFillRule(StyleSVG()->mFillRule);
332
0
  }
333
0
334
0
  bool isHit = false;
335
0
336
0
  SVGGeometryElement* content =
337
0
    static_cast<SVGGeometryElement*>(GetContent());
338
0
339
0
  // Using ScreenReferenceDrawTarget() opens us to Moz2D backend specific hit-
340
0
  // testing bugs. Maybe we should use a BackendType::CAIRO DT for hit-testing
341
0
  // so that we get more consistent/backwards compatible results?
342
0
  RefPtr<DrawTarget> drawTarget =
343
0
    gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
344
0
  RefPtr<Path> path = content->GetOrBuildPath(drawTarget, fillRule);
345
0
  if (!path) {
346
0
    return nullptr; // no path, so we don't paint anything that can be hit
347
0
  }
348
0
349
0
  if (hitTestFlags & SVG_HIT_TEST_FILL) {
350
0
    isHit = path->ContainsPoint(ToPoint(aPoint), Matrix());
351
0
  }
352
0
  if (!isHit && (hitTestFlags & SVG_HIT_TEST_STROKE)) {
353
0
    Point point = ToPoint(aPoint);
354
0
    SVGContentUtils::AutoStrokeOptions stroke;
355
0
    SVGContentUtils::GetStrokeOptions(&stroke, content, Style(), nullptr);
356
0
    gfxMatrix userToOuterSVG;
357
0
    if (nsSVGUtils::GetNonScalingStrokeTransform(this, &userToOuterSVG)) {
358
0
      // We need to transform the path back into the appropriate ancestor
359
0
      // coordinate system in order for non-scaled stroke to be correct.
360
0
      // Naturally we also need to transform the point into the same
361
0
      // coordinate system in order to hit-test against the path.
362
0
      point = ToMatrix(userToOuterSVG).TransformPoint(point);
363
0
      RefPtr<PathBuilder> builder =
364
0
        path->TransformedCopyToBuilder(ToMatrix(userToOuterSVG), fillRule);
365
0
      path = builder->Finish();
366
0
    }
367
0
    isHit = path->StrokeContainsPoint(stroke, point, Matrix());
368
0
  }
369
0
370
0
  if (isHit && nsSVGUtils::HitTestClip(this, aPoint))
371
0
    return this;
372
0
373
0
  return nullptr;
374
0
}
375
376
void
377
SVGGeometryFrame::ReflowSVG()
378
0
{
379
0
  NS_ASSERTION(nsSVGUtils::OuterSVGIsCallingReflowSVG(this),
380
0
               "This call is probably a wasteful mistake");
381
0
382
0
  MOZ_ASSERT(!(GetStateBits() & NS_FRAME_IS_NONDISPLAY),
383
0
             "ReflowSVG mechanism not designed for this");
384
0
385
0
  if (!nsSVGUtils::NeedsReflowSVG(this)) {
386
0
    return;
387
0
  }
388
0
389
0
  uint32_t flags = nsSVGUtils::eBBoxIncludeFill |
390
0
                   nsSVGUtils::eBBoxIncludeStroke |
391
0
                   nsSVGUtils::eBBoxIncludeMarkers;
392
0
  // Our "visual" overflow rect needs to be valid for building display lists
393
0
  // for hit testing, which means that for certain values of 'pointer-events'
394
0
  // it needs to include the geometry of the fill or stroke even when the fill/
395
0
  // stroke don't actually render (e.g. when stroke="none" or
396
0
  // stroke-opacity="0"). GetHitTestFlags() accounts for 'pointer-events'.
397
0
  uint16_t hitTestFlags = GetHitTestFlags();
398
0
  if ((hitTestFlags & SVG_HIT_TEST_FILL)) {
399
0
   flags |= nsSVGUtils::eBBoxIncludeFillGeometry;
400
0
  }
401
0
  if ((hitTestFlags & SVG_HIT_TEST_STROKE)) {
402
0
   flags |= nsSVGUtils::eBBoxIncludeStrokeGeometry;
403
0
  }
404
0
405
0
  gfxRect extent = GetBBoxContribution(Matrix(), flags).ToThebesRect();
406
0
  mRect = nsLayoutUtils::RoundGfxRectToAppRect(extent,
407
0
            AppUnitsPerCSSPixel());
408
0
409
0
  if (mState & NS_FRAME_FIRST_REFLOW) {
410
0
    // Make sure we have our filter property (if any) before calling
411
0
    // FinishAndStoreOverflow (subsequent filter changes are handled off
412
0
    // nsChangeHint_UpdateEffects):
413
0
    SVGObserverUtils::UpdateEffects(this);
414
0
  }
415
0
416
0
  nsRect overflow = nsRect(nsPoint(0,0), mRect.Size());
417
0
  nsOverflowAreas overflowAreas(overflow, overflow);
418
0
  FinishAndStoreOverflow(overflowAreas, mRect.Size());
419
0
420
0
  RemoveStateBits(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY |
421
0
                  NS_FRAME_HAS_DIRTY_CHILDREN);
422
0
423
0
  // Invalidate, but only if this is not our first reflow (since if it is our
424
0
  // first reflow then we haven't had our first paint yet).
425
0
  if (!(GetParent()->GetStateBits() & NS_FRAME_FIRST_REFLOW)) {
426
0
    InvalidateFrame();
427
0
  }
428
0
}
429
430
void
431
SVGGeometryFrame::NotifySVGChanged(uint32_t aFlags)
432
0
{
433
0
  MOZ_ASSERT(aFlags & (TRANSFORM_CHANGED | COORD_CONTEXT_CHANGED),
434
0
             "Invalidation logic may need adjusting");
435
0
436
0
  // Changes to our ancestors may affect how we render when we are rendered as
437
0
  // part of our ancestor (specifically, if our coordinate context changes size
438
0
  // and we have percentage lengths defining our geometry, then we need to be
439
0
  // reflowed). However, ancestor changes cannot affect how we render when we
440
0
  // are rendered as part of any rendering observers that we may have.
441
0
  // Therefore no need to notify rendering observers here.
442
0
443
0
  // Don't try to be too smart trying to avoid the ScheduleReflowSVG calls
444
0
  // for the stroke properties examined below. Checking HasStroke() is not
445
0
  // enough, since what we care about is whether we include the stroke in our
446
0
  // overflow rects or not, and we sometimes deliberately include stroke
447
0
  // when it's not visible. See the complexities of GetBBoxContribution.
448
0
449
0
  if (aFlags & COORD_CONTEXT_CHANGED) {
450
0
    // Stroke currently contributes to our mRect, which is why we have to take
451
0
    // account of stroke-width here. Note that we do not need to take account
452
0
    // of stroke-dashoffset since, although that can have a percentage value
453
0
    // that is resolved against our coordinate context, it does not affect our
454
0
    // mRect.
455
0
    if (static_cast<SVGGeometryElement*>(GetContent())->GeometryDependsOnCoordCtx() ||
456
0
        StyleSVG()->mStrokeWidth.HasPercent()) {
457
0
      static_cast<SVGGeometryElement*>(GetContent())->ClearAnyCachedPath();
458
0
      nsSVGUtils::ScheduleReflowSVG(this);
459
0
    }
460
0
  }
461
0
462
0
  if ((aFlags & TRANSFORM_CHANGED) && StyleSVGReset()->HasNonScalingStroke()) {
463
0
    // Stroke currently contributes to our mRect, and our stroke depends on
464
0
    // the transform to our outer-<svg> if |vector-effect:non-scaling-stroke|.
465
0
    nsSVGUtils::ScheduleReflowSVG(this);
466
0
  }
467
0
}
468
469
SVGBBox
470
SVGGeometryFrame::GetBBoxContribution(const Matrix &aToBBoxUserspace,
471
                                      uint32_t aFlags)
472
0
{
473
0
  SVGBBox bbox;
474
0
475
0
  if (aToBBoxUserspace.IsSingular()) {
476
0
    // XXX ReportToConsole
477
0
    return bbox;
478
0
  }
479
0
480
0
  if ((aFlags & nsSVGUtils::eForGetClientRects) &&
481
0
      aToBBoxUserspace.PreservesAxisAlignedRectangles()) {
482
0
    Rect rect = NSRectToRect(mRect, AppUnitsPerCSSPixel());
483
0
    bbox = aToBBoxUserspace.TransformBounds(rect);
484
0
    return bbox;
485
0
  }
486
0
487
0
  SVGGeometryElement* element =
488
0
    static_cast<SVGGeometryElement*>(GetContent());
489
0
490
0
  bool getFill = (aFlags & nsSVGUtils::eBBoxIncludeFillGeometry) ||
491
0
                 ((aFlags & nsSVGUtils::eBBoxIncludeFill) &&
492
0
                  StyleSVG()->mFill.Type() != eStyleSVGPaintType_None);
493
0
494
0
  bool getStroke = (aFlags & nsSVGUtils::eBBoxIncludeStrokeGeometry) ||
495
0
                   ((aFlags & nsSVGUtils::eBBoxIncludeStroke) &&
496
0
                    nsSVGUtils::HasStroke(this));
497
0
498
0
  SVGContentUtils::AutoStrokeOptions strokeOptions;
499
0
  if (getStroke) {
500
0
    SVGContentUtils::GetStrokeOptions(&strokeOptions, element,
501
0
                                      Style(), nullptr,
502
0
                                      SVGContentUtils::eIgnoreStrokeDashing);
503
0
  } else {
504
0
    // Override the default line width of 1.f so that when we call
505
0
    // GetGeometryBounds below the result doesn't include stroke bounds.
506
0
    strokeOptions.mLineWidth = 0.f;
507
0
  }
508
0
509
0
  Rect simpleBounds;
510
0
  bool gotSimpleBounds = false;
511
0
  gfxMatrix userToOuterSVG;
512
0
  if (getStroke &&
513
0
      nsSVGUtils::GetNonScalingStrokeTransform(this, &userToOuterSVG)) {
514
0
    Matrix moz2dUserToOuterSVG = ToMatrix(userToOuterSVG);
515
0
    if (moz2dUserToOuterSVG.IsSingular()) {
516
0
      return bbox;
517
0
    }
518
0
    gotSimpleBounds = element->GetGeometryBounds(&simpleBounds,
519
0
                                                 strokeOptions,
520
0
                                                 aToBBoxUserspace,
521
0
                                                 &moz2dUserToOuterSVG);
522
0
  } else {
523
0
    gotSimpleBounds = element->GetGeometryBounds(&simpleBounds,
524
0
                                                 strokeOptions,
525
0
                                                 aToBBoxUserspace);
526
0
  }
527
0
528
0
  if (gotSimpleBounds) {
529
0
    bbox = simpleBounds;
530
0
  } else {
531
0
    // Get the bounds using a Moz2D Path object (more expensive):
532
0
    RefPtr<DrawTarget> tmpDT;
533
#ifdef XP_WIN
534
    // Unfortunately D2D backed DrawTarget produces bounds with rounding errors
535
    // when whole number results are expected, even in the case of trivial
536
    // calculations. To avoid that and meet the expectations of web content we
537
    // have to use a CAIRO DrawTarget. The most efficient way to do that is to
538
    // wrap the cached cairo_surface_t from ScreenReferenceSurface():
539
    RefPtr<gfxASurface> refSurf =
540
      gfxPlatform::GetPlatform()->ScreenReferenceSurface();
541
    tmpDT = gfxPlatform::GetPlatform()->
542
      CreateDrawTargetForSurface(refSurf, IntSize(1, 1));
543
#else
544
    tmpDT = gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
545
0
#endif
546
0
547
0
    FillRule fillRule = nsSVGUtils::ToFillRule(
548
0
        (GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD)
549
0
      ? StyleSVG()->mClipRule
550
0
      : StyleSVG()->mFillRule);
551
0
    RefPtr<Path> pathInUserSpace = element->GetOrBuildPath(tmpDT, fillRule);
552
0
    if (!pathInUserSpace) {
553
0
      return bbox;
554
0
    }
555
0
    RefPtr<Path> pathInBBoxSpace;
556
0
    if (aToBBoxUserspace.IsIdentity()) {
557
0
      pathInBBoxSpace = pathInUserSpace;
558
0
    } else {
559
0
      RefPtr<PathBuilder> builder =
560
0
        pathInUserSpace->TransformedCopyToBuilder(aToBBoxUserspace, fillRule);
561
0
      pathInBBoxSpace = builder->Finish();
562
0
      if (!pathInBBoxSpace) {
563
0
        return bbox;
564
0
      }
565
0
    }
566
0
567
0
    // Be careful when replacing the following logic to get the fill and stroke
568
0
    // extents independently (instead of computing the stroke extents from the
569
0
    // path extents). You may think that you can just use the stroke extents if
570
0
    // there is both a fill and a stroke. In reality it's necessary to
571
0
    // calculate both the fill and stroke extents, and take the union of the
572
0
    // two. There are two reasons for this:
573
0
    //
574
0
    // # Due to stroke dashing, in certain cases the fill extents could
575
0
    //   actually extend outside the stroke extents.
576
0
    // # If the stroke is very thin, cairo won't paint any stroke, and so the
577
0
    //   stroke bounds that it will return will be empty.
578
0
579
0
    Rect pathBBoxExtents = pathInBBoxSpace->GetBounds();
580
0
    if (!pathBBoxExtents.IsFinite()) {
581
0
      // This can happen in the case that we only have a move-to command in the
582
0
      // path commands, in which case we know nothing gets rendered.
583
0
      return bbox;
584
0
    }
585
0
586
0
    // Account for fill:
587
0
    if (getFill) {
588
0
      bbox = pathBBoxExtents;
589
0
    }
590
0
591
0
    // Account for stroke:
592
0
    if (getStroke) {
593
#if 0
594
      // This disabled code is how we would calculate the stroke bounds using
595
      // Moz2D Path::GetStrokedBounds(). Unfortunately at the time of writing
596
      // it there are two problems that prevent us from using it.
597
      //
598
      // First, it seems that some of the Moz2D backends are really dumb. Not
599
      // only do some GetStrokeOptions() implementations sometimes
600
      // significantly overestimate the stroke bounds, but if an argument is
601
      // passed for the aTransform parameter then they just return bounds-of-
602
      // transformed-bounds.  These two things combined can lead the bounds to
603
      // be unacceptably oversized, leading to massive over-invalidation.
604
      //
605
      // Second, the way we account for non-scaling-stroke by transforming the
606
      // path using the transform to the outer-<svg> element is not compatible
607
      // with the way that SVGGeometryFrame::Reflow() inserts a scale
608
      // into aToBBoxUserspace and then scales the bounds that we return.
609
      SVGContentUtils::AutoStrokeOptions strokeOptions;
610
      SVGContentUtils::GetStrokeOptions(&strokeOptions, element,
611
                                        Style(), nullptr,
612
                                        SVGContentUtils::eIgnoreStrokeDashing);
613
      Rect strokeBBoxExtents;
614
      gfxMatrix userToOuterSVG;
615
      if (nsSVGUtils::GetNonScalingStrokeTransform(this, &userToOuterSVG)) {
616
        Matrix outerSVGToUser = ToMatrix(userToOuterSVG);
617
        outerSVGToUser.Invert();
618
        Matrix outerSVGToBBox = aToBBoxUserspace * outerSVGToUser;
619
        RefPtr<PathBuilder> builder =
620
          pathInUserSpace->TransformedCopyToBuilder(ToMatrix(userToOuterSVG));
621
        RefPtr<Path> pathInOuterSVGSpace = builder->Finish();
622
        strokeBBoxExtents =
623
          pathInOuterSVGSpace->GetStrokedBounds(strokeOptions, outerSVGToBBox);
624
      } else {
625
        strokeBBoxExtents =
626
          pathInUserSpace->GetStrokedBounds(strokeOptions, aToBBoxUserspace);
627
      }
628
      MOZ_ASSERT(strokeBBoxExtents.IsFinite(), "bbox is about to go bad");
629
      bbox.UnionEdges(strokeBBoxExtents);
630
#else
631
    // For now we just use nsSVGUtils::PathExtentsToMaxStrokeExtents:
632
0
      gfxRect strokeBBoxExtents =
633
0
        nsSVGUtils::PathExtentsToMaxStrokeExtents(ThebesRect(pathBBoxExtents),
634
0
                                                  this,
635
0
                                                  ThebesMatrix(aToBBoxUserspace));
636
0
      MOZ_ASSERT(ToRect(strokeBBoxExtents).IsFinite(), "bbox is about to go bad");
637
0
      bbox.UnionEdges(strokeBBoxExtents);
638
0
#endif
639
0
    }
640
0
  }
641
0
642
0
  // Account for markers:
643
0
  if ((aFlags & nsSVGUtils::eBBoxIncludeMarkers) != 0 &&
644
0
      element->IsMarkable()) {
645
0
    nsSVGMarkerFrame* markerFrames[nsSVGMark::eTypeCount];
646
0
    if (SVGObserverUtils::GetMarkerFrames(this, &markerFrames)) {
647
0
      nsTArray<nsSVGMark> marks;
648
0
      element->GetMarkPoints(&marks);
649
0
      if (uint32_t num = marks.Length()) {
650
0
        float strokeWidth = nsSVGUtils::GetStrokeWidth(this);
651
0
        for (uint32_t i = 0; i < num; i++) {
652
0
          const nsSVGMark& mark = marks[i];
653
0
          nsSVGMarkerFrame* frame = markerFrames[mark.type];
654
0
          if (frame) {
655
0
            SVGBBox mbbox =
656
0
              frame->GetMarkBBoxContribution(aToBBoxUserspace, aFlags, this,
657
0
                                             mark, strokeWidth);
658
0
            MOZ_ASSERT(mbbox.IsFinite(), "bbox is about to go bad");
659
0
            bbox.UnionEdges(mbbox);
660
0
          }
661
0
        }
662
0
      }
663
0
    }
664
0
  }
665
0
666
0
  return bbox;
667
0
}
668
669
//----------------------------------------------------------------------
670
// SVGGeometryFrame methods:
671
672
gfxMatrix
673
SVGGeometryFrame::GetCanvasTM()
674
0
{
675
0
  NS_ASSERTION(GetParent(), "null parent");
676
0
677
0
  nsSVGContainerFrame *parent = static_cast<nsSVGContainerFrame*>(GetParent());
678
0
  SVGGraphicsElement *content = static_cast<SVGGraphicsElement*>(GetContent());
679
0
680
0
  return content->PrependLocalTransformsTo(parent->GetCanvasTM());
681
0
}
682
683
void
684
SVGGeometryFrame::Render(gfxContext* aContext,
685
                         uint32_t aRenderComponents,
686
                         const gfxMatrix& aNewTransform,
687
                         imgDrawingParams& aImgParams)
688
0
{
689
0
  MOZ_ASSERT(!aNewTransform.IsSingular());
690
0
691
0
  DrawTarget* drawTarget = aContext->GetDrawTarget();
692
0
693
0
  FillRule fillRule =
694
0
    nsSVGUtils::ToFillRule((GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD) ?
695
0
                             StyleSVG()->mClipRule : StyleSVG()->mFillRule);
696
0
697
0
  SVGGeometryElement* element =
698
0
    static_cast<SVGGeometryElement*>(GetContent());
699
0
700
0
  AntialiasMode aaMode =
701
0
    (StyleSVG()->mShapeRendering == NS_STYLE_SHAPE_RENDERING_OPTIMIZESPEED ||
702
0
     StyleSVG()->mShapeRendering == NS_STYLE_SHAPE_RENDERING_CRISPEDGES) ?
703
0
    AntialiasMode::NONE : AntialiasMode::SUBPIXEL;
704
0
705
0
  // We wait as late as possible before setting the transform so that we don't
706
0
  // set it unnecessarily if we return early (it's an expensive operation for
707
0
  // some backends).
708
0
  gfxContextMatrixAutoSaveRestore autoRestoreTransform(aContext);
709
0
  aContext->SetMatrixDouble(aNewTransform);
710
0
711
0
  if (GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD) {
712
0
    // We don't complicate this code with GetAsSimplePath since the cost of
713
0
    // masking will dwarf Path creation overhead anyway.
714
0
    RefPtr<Path> path = element->GetOrBuildPath(drawTarget, fillRule);
715
0
    if (path) {
716
0
      ColorPattern white(ToDeviceColor(Color(1.0f, 1.0f, 1.0f, 1.0f)));
717
0
      drawTarget->Fill(path, white,
718
0
                       DrawOptions(1.0f, CompositionOp::OP_OVER, aaMode));
719
0
    }
720
0
    return;
721
0
  }
722
0
723
0
  SVGGeometryElement::SimplePath simplePath;
724
0
  RefPtr<Path> path;
725
0
726
0
  element->GetAsSimplePath(&simplePath);
727
0
  if (!simplePath.IsPath()) {
728
0
    path = element->GetOrBuildPath(drawTarget, fillRule);
729
0
    if (!path) {
730
0
      return;
731
0
    }
732
0
  }
733
0
734
0
  SVGContextPaint* contextPaint = SVGContextPaint::GetContextPaint(GetContent());
735
0
736
0
  if (aRenderComponents & eRenderFill) {
737
0
    GeneralPattern fillPattern;
738
0
    nsSVGUtils::MakeFillPatternFor(this, aContext, &fillPattern, aImgParams,
739
0
                                   contextPaint);
740
0
741
0
    if (fillPattern.GetPattern()) {
742
0
      DrawOptions drawOptions(1.0f, CompositionOp::OP_OVER, aaMode);
743
0
      if (simplePath.IsRect()) {
744
0
        drawTarget->FillRect(simplePath.AsRect(), fillPattern, drawOptions);
745
0
      } else if (path) {
746
0
        drawTarget->Fill(path, fillPattern, drawOptions);
747
0
      }
748
0
    }
749
0
  }
750
0
751
0
  if ((aRenderComponents & eRenderStroke) &&
752
0
      nsSVGUtils::HasStroke(this, contextPaint)) {
753
0
    // Account for vector-effect:non-scaling-stroke:
754
0
    gfxMatrix userToOuterSVG;
755
0
    if (nsSVGUtils::GetNonScalingStrokeTransform(this, &userToOuterSVG)) {
756
0
      // A simple Rect can't be transformed with rotate/skew, so let's switch
757
0
      // to using a real path:
758
0
      if (!path) {
759
0
        path = element->GetOrBuildPath(drawTarget, fillRule);
760
0
        if (!path) {
761
0
          return;
762
0
        }
763
0
        simplePath.Reset();
764
0
      }
765
0
      // We need to transform the path back into the appropriate ancestor
766
0
      // coordinate system, and paint it it that coordinate system, in order
767
0
      // for non-scaled stroke to paint correctly.
768
0
      gfxMatrix outerSVGToUser = userToOuterSVG;
769
0
      outerSVGToUser.Invert();
770
0
      aContext->Multiply(outerSVGToUser);
771
0
      RefPtr<PathBuilder> builder =
772
0
        path->TransformedCopyToBuilder(ToMatrix(userToOuterSVG), fillRule);
773
0
      path = builder->Finish();
774
0
    }
775
0
    GeneralPattern strokePattern;
776
0
    nsSVGUtils::MakeStrokePatternFor(this, aContext, &strokePattern,
777
0
                                     aImgParams, contextPaint);
778
0
779
0
    if (strokePattern.GetPattern()) {
780
0
      SVGContentUtils::AutoStrokeOptions strokeOptions;
781
0
      SVGContentUtils::GetStrokeOptions(&strokeOptions,
782
0
                                        static_cast<nsSVGElement*>(GetContent()),
783
0
                                        Style(), contextPaint);
784
0
      // GetStrokeOptions may set the line width to zero as an optimization
785
0
      if (strokeOptions.mLineWidth <= 0) {
786
0
        return;
787
0
      }
788
0
      DrawOptions drawOptions(1.0f, CompositionOp::OP_OVER, aaMode);
789
0
      if (simplePath.IsRect()) {
790
0
        drawTarget->StrokeRect(simplePath.AsRect(), strokePattern,
791
0
                               strokeOptions, drawOptions);
792
0
      } else if (simplePath.IsLine()) {
793
0
        drawTarget->StrokeLine(simplePath.Point1(), simplePath.Point2(),
794
0
                               strokePattern, strokeOptions, drawOptions);
795
0
      } else {
796
0
        drawTarget->Stroke(path, strokePattern, strokeOptions, drawOptions);
797
0
      }
798
0
    }
799
0
  }
800
0
}
801
802
void
803
SVGGeometryFrame::PaintMarkers(gfxContext& aContext,
804
                               const gfxMatrix& aTransform,
805
                               imgDrawingParams& aImgParams)
806
0
{
807
0
  auto element = static_cast<SVGGeometryElement*>(GetContent());
808
0
809
0
  if (element->IsMarkable()) {
810
0
    nsSVGMarkerFrame* markerFrames[nsSVGMark::eTypeCount];
811
0
    if (SVGObserverUtils::GetMarkerFrames(this, &markerFrames)) {
812
0
      nsTArray<nsSVGMark> marks;
813
0
      element->GetMarkPoints(&marks);
814
0
      if (uint32_t num = marks.Length()) {
815
0
        SVGContextPaint* contextPaint =
816
0
          SVGContextPaint::GetContextPaint(GetContent());
817
0
        float strokeWidth = nsSVGUtils::GetStrokeWidth(this, contextPaint);
818
0
        for (uint32_t i = 0; i < num; i++) {
819
0
          const nsSVGMark& mark = marks[i];
820
0
          nsSVGMarkerFrame* frame = markerFrames[mark.type];
821
0
          if (frame) {
822
0
            frame->PaintMark(aContext, aTransform, this, mark, strokeWidth,
823
0
                             aImgParams);
824
0
          }
825
0
        }
826
0
      }
827
0
    }
828
0
  }
829
0
}
830
831
uint16_t
832
SVGGeometryFrame::GetHitTestFlags()
833
0
{
834
0
  return nsSVGUtils::GetGeometryHitTestFlags(this);
835
0
}
836
} // namespace mozilla