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/. */
8 #include "SVGGeometryFrame.h"
10 // Keep others in (case-insensitive) order:
11 #include "gfx2DGlue.h"
12 #include "gfxContext.h"
13 #include "gfxPlatform.h"
15 #include "mozilla/dom/SVGGeometryElement.h"
16 #include "mozilla/dom/SVGGraphicsElement.h"
17 #include "mozilla/gfx/2D.h"
18 #include "mozilla/gfx/Helpers.h"
19 #include "mozilla/ArrayUtils.h"
20 #include "mozilla/PresShell.h"
21 #include "mozilla/RefPtr.h"
22 #include "mozilla/SVGContextPaint.h"
23 #include "mozilla/SVGContentUtils.h"
24 #include "mozilla/SVGObserverUtils.h"
25 #include "mozilla/SVGUtils.h"
26 #include "nsDisplayList.h"
27 #include "nsGkAtoms.h"
28 #include "nsLayoutUtils.h"
29 #include "SVGAnimatedTransformList.h"
30 #include "SVGMarkerFrame.h"
32 using namespace mozilla::dom
;
33 using namespace mozilla::gfx
;
34 using namespace mozilla::image
;
36 //----------------------------------------------------------------------
39 nsIFrame
* NS_NewSVGGeometryFrame(mozilla::PresShell
* aPresShell
,
40 mozilla::ComputedStyle
* aStyle
) {
41 return new (aPresShell
)
42 mozilla::SVGGeometryFrame(aStyle
, aPresShell
->GetPresContext());
47 NS_IMPL_FRAMEARENA_HELPERS(SVGGeometryFrame
)
49 //----------------------------------------------------------------------
50 // nsQueryFrame methods
52 NS_QUERYFRAME_HEAD(SVGGeometryFrame
)
53 NS_QUERYFRAME_ENTRY(ISVGDisplayableFrame
)
54 NS_QUERYFRAME_ENTRY(SVGGeometryFrame
)
55 NS_QUERYFRAME_TAIL_INHERITING(nsIFrame
)
57 void DisplaySVGGeometry::HitTest(nsDisplayListBuilder
* aBuilder
,
58 const nsRect
& aRect
, HitTestState
* aState
,
59 nsTArray
<nsIFrame
*>* aOutFrames
) {
60 SVGGeometryFrame
* frame
= static_cast<SVGGeometryFrame
*>(mFrame
);
61 nsPoint pointRelativeToReferenceFrame
= aRect
.Center();
62 // ToReferenceFrame() includes frame->GetPosition(), our user space position.
63 nsPoint userSpacePtInAppUnits
= pointRelativeToReferenceFrame
-
64 (ToReferenceFrame() - frame
->GetPosition());
65 gfxPoint userSpacePt
=
66 gfxPoint(userSpacePtInAppUnits
.x
, userSpacePtInAppUnits
.y
) /
67 AppUnitsPerCSSPixel();
68 if (frame
->GetFrameForPoint(userSpacePt
)) {
69 aOutFrames
->AppendElement(frame
);
73 void DisplaySVGGeometry::Paint(nsDisplayListBuilder
* aBuilder
,
75 uint32_t appUnitsPerDevPixel
= mFrame
->PresContext()->AppUnitsPerDevPixel();
77 // ToReferenceFrame includes our mRect offset, but painting takes
78 // account of that too. To avoid double counting, we subtract that
80 nsPoint offset
= ToReferenceFrame() - mFrame
->GetPosition();
82 gfxPoint devPixelOffset
=
83 nsLayoutUtils::PointToGfxPoint(offset
, appUnitsPerDevPixel
);
85 gfxMatrix tm
= SVGUtils::GetCSSPxToDevPxMatrix(mFrame
) *
86 gfxMatrix::Translation(devPixelOffset
);
87 imgDrawingParams
imgParams(aBuilder
->GetImageDecodeFlags());
88 static_cast<SVGGeometryFrame
*>(mFrame
)->PaintSVG(*aCtx
, tm
, imgParams
);
91 //----------------------------------------------------------------------
94 void SVGGeometryFrame::Init(nsIContent
* aContent
, nsContainerFrame
* aParent
,
95 nsIFrame
* aPrevInFlow
) {
96 AddStateBits(aParent
->GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD
);
97 nsIFrame::Init(aContent
, aParent
, aPrevInFlow
);
98 AddStateBits(NS_FRAME_MAY_BE_TRANSFORMED
);
101 nsresult
SVGGeometryFrame::AttributeChanged(int32_t aNameSpaceID
,
104 // We don't invalidate for transform changes (the layers code does that).
105 // Also note that SVGTransformableElement::GetAttributeChangeHint will
106 // return nsChangeHint_UpdateOverflow for "transform" attribute changes
107 // and cause DoApplyRenderingChangeToTree to make the SchedulePaint call.
109 if (aNameSpaceID
== kNameSpaceID_None
&&
110 (static_cast<SVGGeometryElement
*>(GetContent())
111 ->AttributeDefinesGeometry(aAttribute
))) {
112 nsLayoutUtils::PostRestyleEvent(mContent
->AsElement(), RestyleHint
{0},
113 nsChangeHint_InvalidateRenderingObservers
);
114 SVGUtils::ScheduleReflowSVG(this);
120 void SVGGeometryFrame::DidSetComputedStyle(ComputedStyle
* aOldComputedStyle
) {
121 nsIFrame::DidSetComputedStyle(aOldComputedStyle
);
122 auto* element
= static_cast<SVGGeometryElement
*>(GetContent());
123 if (!aOldComputedStyle
) {
124 element
->ClearAnyCachedPath();
128 const auto* oldStyleSVG
= aOldComputedStyle
->StyleSVG();
129 if (!SVGContentUtils::ShapeTypeHasNoCorners(GetContent())) {
130 if (StyleSVG()->mStrokeLinecap
!= oldStyleSVG
->mStrokeLinecap
&&
131 element
->IsSVGElement(nsGkAtoms::path
)) {
132 // If the stroke-linecap changes to or from "butt" then our element
133 // needs to update its cached Moz2D Path, since SVGPathData::BuildPath
134 // decides whether or not to insert little lines into the path for zero
135 // length subpaths base on that property.
136 element
->ClearAnyCachedPath();
137 } else if (HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD
)) {
138 if (StyleSVG()->mClipRule
!= oldStyleSVG
->mClipRule
) {
139 // Moz2D Path objects are fill-rule specific.
140 // For clipPath we use clip-rule as the path's fill-rule.
141 element
->ClearAnyCachedPath();
144 if (StyleSVG()->mFillRule
!= oldStyleSVG
->mFillRule
) {
145 // Moz2D Path objects are fill-rule specific.
146 element
->ClearAnyCachedPath();
151 if (element
->IsGeometryChangedViaCSS(*Style(), *aOldComputedStyle
)) {
152 element
->ClearAnyCachedPath();
156 bool SVGGeometryFrame::IsSVGTransformed(
157 gfx::Matrix
* aOwnTransform
, gfx::Matrix
* aFromParentTransform
) const {
158 bool foundTransform
= false;
160 // Check if our parent has children-only transforms:
161 nsIFrame
* parent
= GetParent();
163 parent
->IsFrameOfType(nsIFrame::eSVG
| nsIFrame::eSVGContainer
)) {
165 static_cast<SVGContainerFrame
*>(parent
)->HasChildrenOnlyTransform(
166 aFromParentTransform
);
169 SVGElement
* content
= static_cast<SVGElement
*>(GetContent());
170 SVGAnimatedTransformList
* transformList
= content
->GetAnimatedTransformList();
171 if ((transformList
&& transformList
->HasTransform()) ||
172 content
->GetAnimateMotionTransform()) {
174 *aOwnTransform
= gfx::ToMatrix(
175 content
->PrependLocalTransformsTo(gfxMatrix(), eUserSpaceToParent
));
177 foundTransform
= true;
179 return foundTransform
;
182 void SVGGeometryFrame::BuildDisplayList(nsDisplayListBuilder
* aBuilder
,
183 const nsDisplayListSet
& aLists
) {
184 if (!static_cast<const SVGElement
*>(GetContent())->HasValidDimensions()) {
188 if (aBuilder
->IsForPainting()) {
189 if (!IsVisibleForPainting()) {
192 if (StyleEffects()->mOpacity
== 0.0f
) {
195 const auto* styleSVG
= StyleSVG();
196 if (Type() != LayoutFrameType::SVGImage
&& styleSVG
->mFill
.kind
.IsNone() &&
197 styleSVG
->mStroke
.kind
.IsNone() && styleSVG
->mMarkerEnd
.IsNone() &&
198 styleSVG
->mMarkerMid
.IsNone() && styleSVG
->mMarkerStart
.IsNone()) {
202 aBuilder
->BuildCompositorHitTestInfoIfNeeded(this,
203 aLists
.BorderBackground());
206 DisplayOutline(aBuilder
, aLists
);
207 aLists
.Content()->AppendNewToTop
<DisplaySVGGeometry
>(aBuilder
, this);
210 //----------------------------------------------------------------------
211 // ISVGDisplayableFrame methods
213 void SVGGeometryFrame::PaintSVG(gfxContext
& aContext
,
214 const gfxMatrix
& aTransform
,
215 imgDrawingParams
& aImgParams
,
216 const nsIntRect
* aDirtyRect
) {
217 if (!StyleVisibility()->IsVisible()) {
221 // Matrix to the geometry's user space:
222 gfxMatrix newMatrix
=
223 aContext
.CurrentMatrixDouble().PreMultiply(aTransform
).NudgeToIntegers();
224 if (newMatrix
.IsSingular()) {
228 uint32_t paintOrder
= StyleSVG()->mPaintOrder
;
230 Render(&aContext
, eRenderFill
| eRenderStroke
, newMatrix
, aImgParams
);
231 PaintMarkers(aContext
, aTransform
, aImgParams
);
234 auto component
= StylePaintOrder(paintOrder
& kPaintOrderMask
);
236 case StylePaintOrder::Fill
:
237 Render(&aContext
, eRenderFill
, newMatrix
, aImgParams
);
239 case StylePaintOrder::Stroke
:
240 Render(&aContext
, eRenderStroke
, newMatrix
, aImgParams
);
242 case StylePaintOrder::Markers
:
243 PaintMarkers(aContext
, aTransform
, aImgParams
);
246 MOZ_FALLTHROUGH_ASSERT("Unknown paint-order variant, how?");
247 case StylePaintOrder::Normal
:
250 paintOrder
>>= kPaintOrderShift
;
255 nsIFrame
* SVGGeometryFrame::GetFrameForPoint(const gfxPoint
& aPoint
) {
257 uint16_t hitTestFlags
;
258 if (HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD
)) {
259 hitTestFlags
= SVG_HIT_TEST_FILL
;
260 fillRule
= SVGUtils::ToFillRule(StyleSVG()->mClipRule
);
262 hitTestFlags
= GetHitTestFlags();
266 if (hitTestFlags
& SVG_HIT_TEST_CHECK_MRECT
) {
267 gfxRect rect
= nsLayoutUtils::RectToGfxRect(mRect
, AppUnitsPerCSSPixel());
268 if (!rect
.Contains(aPoint
)) {
272 fillRule
= SVGUtils::ToFillRule(StyleSVG()->mFillRule
);
277 SVGGeometryElement
* content
= static_cast<SVGGeometryElement
*>(GetContent());
279 // Using ScreenReferenceDrawTarget() opens us to Moz2D backend specific hit-
280 // testing bugs. Maybe we should use a BackendType::CAIRO DT for hit-testing
281 // so that we get more consistent/backwards compatible results?
282 RefPtr
<DrawTarget
> drawTarget
=
283 gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
284 RefPtr
<Path
> path
= content
->GetOrBuildPath(drawTarget
, fillRule
);
286 return nullptr; // no path, so we don't paint anything that can be hit
289 if (hitTestFlags
& SVG_HIT_TEST_FILL
) {
290 isHit
= path
->ContainsPoint(ToPoint(aPoint
), Matrix());
292 if (!isHit
&& (hitTestFlags
& SVG_HIT_TEST_STROKE
)) {
293 Point point
= ToPoint(aPoint
);
294 SVGContentUtils::AutoStrokeOptions stroke
;
295 SVGContentUtils::GetStrokeOptions(&stroke
, content
, Style(), nullptr);
296 gfxMatrix userToOuterSVG
;
297 if (SVGUtils::GetNonScalingStrokeTransform(this, &userToOuterSVG
)) {
298 // We need to transform the path back into the appropriate ancestor
299 // coordinate system in order for non-scaled stroke to be correct.
300 // Naturally we also need to transform the point into the same
301 // coordinate system in order to hit-test against the path.
302 point
= ToMatrix(userToOuterSVG
).TransformPoint(point
);
303 RefPtr
<PathBuilder
> builder
=
304 path
->TransformedCopyToBuilder(ToMatrix(userToOuterSVG
), fillRule
);
305 path
= builder
->Finish();
307 isHit
= path
->StrokeContainsPoint(stroke
, point
, Matrix());
310 if (isHit
&& SVGUtils::HitTestClip(this, aPoint
)) {
317 void SVGGeometryFrame::ReflowSVG() {
318 NS_ASSERTION(SVGUtils::OuterSVGIsCallingReflowSVG(this),
319 "This call is probably a wasteful mistake");
321 MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_IS_NONDISPLAY
),
322 "ReflowSVG mechanism not designed for this");
324 if (!SVGUtils::NeedsReflowSVG(this)) {
328 uint32_t flags
= SVGUtils::eBBoxIncludeFill
| SVGUtils::eBBoxIncludeStroke
|
329 SVGUtils::eBBoxIncludeMarkers
;
330 // Our "visual" overflow rect needs to be valid for building display lists
331 // for hit testing, which means that for certain values of 'pointer-events'
332 // it needs to include the geometry of the fill or stroke even when the fill/
333 // stroke don't actually render (e.g. when stroke="none" or
334 // stroke-opacity="0"). GetHitTestFlags() accounts for 'pointer-events'.
335 uint16_t hitTestFlags
= GetHitTestFlags();
336 if ((hitTestFlags
& SVG_HIT_TEST_FILL
)) {
337 flags
|= SVGUtils::eBBoxIncludeFillGeometry
;
339 if ((hitTestFlags
& SVG_HIT_TEST_STROKE
)) {
340 flags
|= SVGUtils::eBBoxIncludeStrokeGeometry
;
343 gfxRect extent
= GetBBoxContribution(Matrix(), flags
).ToThebesRect();
344 mRect
= nsLayoutUtils::RoundGfxRectToAppRect(extent
, AppUnitsPerCSSPixel());
346 if (mState
& NS_FRAME_FIRST_REFLOW
) {
347 // Make sure we have our filter property (if any) before calling
348 // FinishAndStoreOverflow (subsequent filter changes are handled off
349 // nsChangeHint_UpdateEffects):
350 SVGObserverUtils::UpdateEffects(this);
353 nsRect overflow
= nsRect(nsPoint(0, 0), mRect
.Size());
354 OverflowAreas
overflowAreas(overflow
, overflow
);
355 FinishAndStoreOverflow(overflowAreas
, mRect
.Size());
357 RemoveStateBits(NS_FRAME_FIRST_REFLOW
| NS_FRAME_IS_DIRTY
|
358 NS_FRAME_HAS_DIRTY_CHILDREN
);
360 // Invalidate, but only if this is not our first reflow (since if it is our
361 // first reflow then we haven't had our first paint yet).
362 if (!GetParent()->HasAnyStateBits(NS_FRAME_FIRST_REFLOW
)) {
367 void SVGGeometryFrame::NotifySVGChanged(uint32_t aFlags
) {
368 MOZ_ASSERT(aFlags
& (TRANSFORM_CHANGED
| COORD_CONTEXT_CHANGED
),
369 "Invalidation logic may need adjusting");
371 // Changes to our ancestors may affect how we render when we are rendered as
372 // part of our ancestor (specifically, if our coordinate context changes size
373 // and we have percentage lengths defining our geometry, then we need to be
374 // reflowed). However, ancestor changes cannot affect how we render when we
375 // are rendered as part of any rendering observers that we may have.
376 // Therefore no need to notify rendering observers here.
378 // Don't try to be too smart trying to avoid the ScheduleReflowSVG calls
379 // for the stroke properties examined below. Checking HasStroke() is not
380 // enough, since what we care about is whether we include the stroke in our
381 // overflow rects or not, and we sometimes deliberately include stroke
382 // when it's not visible. See the complexities of GetBBoxContribution.
384 if (aFlags
& COORD_CONTEXT_CHANGED
) {
385 auto* geom
= static_cast<SVGGeometryElement
*>(GetContent());
386 // Stroke currently contributes to our mRect, which is why we have to take
387 // account of stroke-width here. Note that we do not need to take account
388 // of stroke-dashoffset since, although that can have a percentage value
389 // that is resolved against our coordinate context, it does not affect our
391 const auto& strokeWidth
= StyleSVG()->mStrokeWidth
;
392 if (geom
->GeometryDependsOnCoordCtx() ||
393 (strokeWidth
.IsLengthPercentage() &&
394 strokeWidth
.AsLengthPercentage().HasPercent())) {
395 geom
->ClearAnyCachedPath();
396 SVGUtils::ScheduleReflowSVG(this);
400 if ((aFlags
& TRANSFORM_CHANGED
) && StyleSVGReset()->HasNonScalingStroke()) {
401 // Stroke currently contributes to our mRect, and our stroke depends on
402 // the transform to our outer-<svg> if |vector-effect:non-scaling-stroke|.
403 SVGUtils::ScheduleReflowSVG(this);
407 SVGBBox
SVGGeometryFrame::GetBBoxContribution(const Matrix
& aToBBoxUserspace
,
411 if (aToBBoxUserspace
.IsSingular()) {
412 // XXX ReportToConsole
416 if ((aFlags
& SVGUtils::eForGetClientRects
) &&
417 aToBBoxUserspace
.PreservesAxisAlignedRectangles()) {
418 Rect rect
= NSRectToRect(mRect
, AppUnitsPerCSSPixel());
419 bbox
= aToBBoxUserspace
.TransformBounds(rect
);
423 SVGGeometryElement
* element
= static_cast<SVGGeometryElement
*>(GetContent());
425 bool getFill
= (aFlags
& SVGUtils::eBBoxIncludeFillGeometry
) ||
426 ((aFlags
& SVGUtils::eBBoxIncludeFill
) &&
427 !StyleSVG()->mFill
.kind
.IsNone());
430 (aFlags
& SVGUtils::eBBoxIncludeStrokeGeometry
) ||
431 ((aFlags
& SVGUtils::eBBoxIncludeStroke
) && SVGUtils::HasStroke(this));
433 SVGContentUtils::AutoStrokeOptions strokeOptions
;
435 SVGContentUtils::GetStrokeOptions(&strokeOptions
, element
, Style(), nullptr,
436 SVGContentUtils::eIgnoreStrokeDashing
);
438 // Override the default line width of 1.f so that when we call
439 // GetGeometryBounds below the result doesn't include stroke bounds.
440 strokeOptions
.mLineWidth
= 0.f
;
444 bool gotSimpleBounds
= false;
445 gfxMatrix userToOuterSVG
;
447 SVGUtils::GetNonScalingStrokeTransform(this, &userToOuterSVG
)) {
448 Matrix moz2dUserToOuterSVG
= ToMatrix(userToOuterSVG
);
449 if (moz2dUserToOuterSVG
.IsSingular()) {
452 gotSimpleBounds
= element
->GetGeometryBounds(
453 &simpleBounds
, strokeOptions
, aToBBoxUserspace
, &moz2dUserToOuterSVG
);
455 gotSimpleBounds
= element
->GetGeometryBounds(&simpleBounds
, strokeOptions
,
459 if (gotSimpleBounds
) {
462 // Get the bounds using a Moz2D Path object (more expensive):
463 RefPtr
<DrawTarget
> tmpDT
;
464 tmpDT
= gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
466 FillRule fillRule
= SVGUtils::ToFillRule(
467 HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD
) ? StyleSVG()->mClipRule
468 : StyleSVG()->mFillRule
);
469 RefPtr
<Path
> pathInUserSpace
= element
->GetOrBuildPath(tmpDT
, fillRule
);
470 if (!pathInUserSpace
) {
473 RefPtr
<Path
> pathInBBoxSpace
;
474 if (aToBBoxUserspace
.IsIdentity()) {
475 pathInBBoxSpace
= pathInUserSpace
;
477 RefPtr
<PathBuilder
> builder
=
478 pathInUserSpace
->TransformedCopyToBuilder(aToBBoxUserspace
, fillRule
);
479 pathInBBoxSpace
= builder
->Finish();
480 if (!pathInBBoxSpace
) {
485 // Be careful when replacing the following logic to get the fill and stroke
486 // extents independently (instead of computing the stroke extents from the
487 // path extents). You may think that you can just use the stroke extents if
488 // there is both a fill and a stroke. In reality it's necessary to
489 // calculate both the fill and stroke extents, and take the union of the
490 // two. There are two reasons for this:
492 // # Due to stroke dashing, in certain cases the fill extents could
493 // actually extend outside the stroke extents.
494 // # If the stroke is very thin, cairo won't paint any stroke, and so the
495 // stroke bounds that it will return will be empty.
497 Rect pathBBoxExtents
= pathInBBoxSpace
->GetBounds();
498 if (!pathBBoxExtents
.IsFinite()) {
499 // This can happen in the case that we only have a move-to command in the
500 // path commands, in which case we know nothing gets rendered.
506 bbox
= pathBBoxExtents
;
509 // Account for stroke:
512 // This disabled code is how we would calculate the stroke bounds using
513 // Moz2D Path::GetStrokedBounds(). Unfortunately at the time of writing
514 // it there are two problems that prevent us from using it.
516 // First, it seems that some of the Moz2D backends are really dumb. Not
517 // only do some GetStrokeOptions() implementations sometimes
518 // significantly overestimate the stroke bounds, but if an argument is
519 // passed for the aTransform parameter then they just return bounds-of-
520 // transformed-bounds. These two things combined can lead the bounds to
521 // be unacceptably oversized, leading to massive over-invalidation.
523 // Second, the way we account for non-scaling-stroke by transforming the
524 // path using the transform to the outer-<svg> element is not compatible
525 // with the way that SVGGeometryFrame::Reflow() inserts a scale
526 // into aToBBoxUserspace and then scales the bounds that we return.
527 SVGContentUtils::AutoStrokeOptions strokeOptions
;
528 SVGContentUtils::GetStrokeOptions(&strokeOptions
, element
,
530 SVGContentUtils::eIgnoreStrokeDashing
);
531 Rect strokeBBoxExtents
;
532 gfxMatrix userToOuterSVG
;
533 if (SVGUtils::GetNonScalingStrokeTransform(this, &userToOuterSVG
)) {
534 Matrix outerSVGToUser
= ToMatrix(userToOuterSVG
);
535 outerSVGToUser
.Invert();
536 Matrix outerSVGToBBox
= aToBBoxUserspace
* outerSVGToUser
;
537 RefPtr
<PathBuilder
> builder
=
538 pathInUserSpace
->TransformedCopyToBuilder(ToMatrix(userToOuterSVG
));
539 RefPtr
<Path
> pathInOuterSVGSpace
= builder
->Finish();
541 pathInOuterSVGSpace
->GetStrokedBounds(strokeOptions
, outerSVGToBBox
);
544 pathInUserSpace
->GetStrokedBounds(strokeOptions
, aToBBoxUserspace
);
546 MOZ_ASSERT(strokeBBoxExtents
.IsFinite(), "bbox is about to go bad");
547 bbox
.UnionEdges(strokeBBoxExtents
);
549 // For now we just use SVGUtils::PathExtentsToMaxStrokeExtents:
550 gfxRect strokeBBoxExtents
= SVGUtils::PathExtentsToMaxStrokeExtents(
551 ThebesRect(pathBBoxExtents
), this, ThebesMatrix(aToBBoxUserspace
));
552 MOZ_ASSERT(ToRect(strokeBBoxExtents
).IsFinite(),
553 "bbox is about to go bad");
554 bbox
.UnionEdges(strokeBBoxExtents
);
559 // Account for markers:
560 if ((aFlags
& SVGUtils::eBBoxIncludeMarkers
) != 0 && element
->IsMarkable()) {
561 SVGMarkerFrame
* markerFrames
[SVGMark::eTypeCount
];
562 if (SVGObserverUtils::GetAndObserveMarkers(this, &markerFrames
)) {
563 nsTArray
<SVGMark
> marks
;
564 element
->GetMarkPoints(&marks
);
565 if (uint32_t num
= marks
.Length()) {
566 float strokeWidth
= SVGUtils::GetStrokeWidth(this);
567 for (uint32_t i
= 0; i
< num
; i
++) {
568 const SVGMark
& mark
= marks
[i
];
569 SVGMarkerFrame
* frame
= markerFrames
[mark
.type
];
571 SVGBBox mbbox
= frame
->GetMarkBBoxContribution(
572 aToBBoxUserspace
, aFlags
, this, mark
, strokeWidth
);
573 MOZ_ASSERT(mbbox
.IsFinite(), "bbox is about to go bad");
574 bbox
.UnionEdges(mbbox
);
584 //----------------------------------------------------------------------
585 // SVGGeometryFrame methods:
587 gfxMatrix
SVGGeometryFrame::GetCanvasTM() {
588 NS_ASSERTION(GetParent(), "null parent");
590 auto* parent
= static_cast<SVGContainerFrame
*>(GetParent());
591 auto* content
= static_cast<SVGGraphicsElement
*>(GetContent());
593 return content
->PrependLocalTransformsTo(parent
->GetCanvasTM());
596 void SVGGeometryFrame::Render(gfxContext
* aContext
, uint32_t aRenderComponents
,
597 const gfxMatrix
& aTransform
,
598 imgDrawingParams
& aImgParams
) {
599 MOZ_ASSERT(!aTransform
.IsSingular());
601 DrawTarget
* drawTarget
= aContext
->GetDrawTarget();
603 MOZ_ASSERT(drawTarget
);
604 if (!drawTarget
->IsValid()) {
608 FillRule fillRule
= SVGUtils::ToFillRule(
609 HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD
) ? StyleSVG()->mClipRule
610 : StyleSVG()->mFillRule
);
612 SVGGeometryElement
* element
= static_cast<SVGGeometryElement
*>(GetContent());
614 AntialiasMode aaMode
=
615 (StyleSVG()->mShapeRendering
== StyleShapeRendering::Optimizespeed
||
616 StyleSVG()->mShapeRendering
== StyleShapeRendering::Crispedges
)
617 ? AntialiasMode::NONE
618 : AntialiasMode::SUBPIXEL
;
620 // We wait as late as possible before setting the transform so that we don't
621 // set it unnecessarily if we return early (it's an expensive operation for
623 gfxContextMatrixAutoSaveRestore
autoRestoreTransform(aContext
);
624 aContext
->SetMatrixDouble(aTransform
);
626 if (HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD
)) {
627 // We don't complicate this code with GetAsSimplePath since the cost of
628 // masking will dwarf Path creation overhead anyway.
629 RefPtr
<Path
> path
= element
->GetOrBuildPath(drawTarget
, fillRule
);
631 ColorPattern
white(ToDeviceColor(sRGBColor(1.0f
, 1.0f
, 1.0f
, 1.0f
)));
632 drawTarget
->Fill(path
, white
,
633 DrawOptions(1.0f
, CompositionOp::OP_OVER
, aaMode
));
638 SVGGeometryElement::SimplePath simplePath
;
641 element
->GetAsSimplePath(&simplePath
);
642 if (!simplePath
.IsPath()) {
643 path
= element
->GetOrBuildPath(drawTarget
, fillRule
);
649 SVGContextPaint
* contextPaint
=
650 SVGContextPaint::GetContextPaint(GetContent());
652 if (aRenderComponents
& eRenderFill
) {
653 GeneralPattern fillPattern
;
654 SVGUtils::MakeFillPatternFor(this, aContext
, &fillPattern
, aImgParams
,
657 if (fillPattern
.GetPattern()) {
658 DrawOptions
drawOptions(1.0f
, CompositionOp::OP_OVER
, aaMode
);
659 if (simplePath
.IsRect()) {
660 drawTarget
->FillRect(simplePath
.AsRect(), fillPattern
, drawOptions
);
662 drawTarget
->Fill(path
, fillPattern
, drawOptions
);
667 if ((aRenderComponents
& eRenderStroke
) &&
668 SVGUtils::HasStroke(this, contextPaint
)) {
669 // Account for vector-effect:non-scaling-stroke:
670 gfxMatrix userToOuterSVG
;
671 if (SVGUtils::GetNonScalingStrokeTransform(this, &userToOuterSVG
)) {
672 // A simple Rect can't be transformed with rotate/skew, so let's switch
673 // to using a real path:
675 path
= element
->GetOrBuildPath(drawTarget
, fillRule
);
681 // We need to transform the path back into the appropriate ancestor
682 // coordinate system, and paint it it that coordinate system, in order
683 // for non-scaled stroke to paint correctly.
684 gfxMatrix outerSVGToUser
= userToOuterSVG
;
685 outerSVGToUser
.Invert();
686 aContext
->Multiply(outerSVGToUser
);
687 RefPtr
<PathBuilder
> builder
=
688 path
->TransformedCopyToBuilder(ToMatrix(userToOuterSVG
), fillRule
);
689 path
= builder
->Finish();
691 GeneralPattern strokePattern
;
692 SVGUtils::MakeStrokePatternFor(this, aContext
, &strokePattern
, aImgParams
,
695 if (strokePattern
.GetPattern()) {
696 SVGContentUtils::AutoStrokeOptions strokeOptions
;
697 SVGContentUtils::GetStrokeOptions(&strokeOptions
,
698 static_cast<SVGElement
*>(GetContent()),
699 Style(), contextPaint
);
700 // GetStrokeOptions may set the line width to zero as an optimization
701 if (strokeOptions
.mLineWidth
<= 0) {
704 DrawOptions
drawOptions(1.0f
, CompositionOp::OP_OVER
, aaMode
);
705 if (simplePath
.IsRect()) {
706 drawTarget
->StrokeRect(simplePath
.AsRect(), strokePattern
,
707 strokeOptions
, drawOptions
);
708 } else if (simplePath
.IsLine()) {
709 drawTarget
->StrokeLine(simplePath
.Point1(), simplePath
.Point2(),
710 strokePattern
, strokeOptions
, drawOptions
);
712 drawTarget
->Stroke(path
, strokePattern
, strokeOptions
, drawOptions
);
718 bool SVGGeometryFrame::IsInvisible() const {
719 if (!StyleVisibility()->IsVisible()) {
723 const nsStyleSVG
* style
= StyleSVG();
724 SVGContextPaint
* contextPaint
=
725 SVGContextPaint::GetContextPaint(GetContent());
727 // Anything below will round to zero later down the pipeline.
728 float opacity_threshold
= 1.0 / 128.0;
730 float elemOpacity
= StyleEffects()->mOpacity
;
731 if (elemOpacity
<= opacity_threshold
) {
735 if (IsSVGImageFrame()) {
739 if (!style
->mFill
.kind
.IsNone()) {
740 float opacity
= SVGUtils::GetOpacity(style
->mFillOpacity
, contextPaint
);
741 if (opacity
> opacity_threshold
) {
746 if (!style
->mStroke
.kind
.IsNone()) {
747 float opacity
= SVGUtils::GetOpacity(style
->mStrokeOpacity
, contextPaint
);
748 if (opacity
> opacity_threshold
) {
753 if (style
->mMarkerStart
.IsUrl() || style
->mMarkerMid
.IsUrl() ||
754 style
->mMarkerEnd
.IsUrl()) {
761 bool SVGGeometryFrame::CreateWebRenderCommands(
762 mozilla::wr::DisplayListBuilder
& aBuilder
,
763 mozilla::wr::IpcResourceUpdateQueue
& aResources
,
764 const mozilla::layers::StackingContextHelper
& aSc
,
765 mozilla::layers::RenderRootStateManager
* aManager
,
766 nsDisplayListBuilder
* aDisplayListBuilder
, DisplaySVGGeometry
* aItem
,
768 if (!StyleVisibility()->IsVisible()) {
772 SVGGeometryElement
* element
= static_cast<SVGGeometryElement
*>(GetContent());
774 SVGGeometryElement::SimplePath simplePath
;
775 element
->GetAsSimplePath(&simplePath
);
777 if (!simplePath
.IsRect()) {
781 const nsStyleSVG
* style
= StyleSVG();
784 if (!style
->mFill
.kind
.IsColor()) {
788 switch (style
->mFill
.kind
.tag
) {
789 case StyleSVGPaintKind::Tag::Color
:
795 if (!style
->mStroke
.kind
.IsNone()) {
799 if (StyleEffects()->mMixBlendMode
!= StyleBlend::Normal
) {
800 // FIXME: not implemented
804 SVGMarkerFrame
* markerFrames
[SVGMark::eTypeCount
];
805 if (element
->IsMarkable() &&
806 SVGObserverUtils::GetAndObserveMarkers(this, &markerFrames
)) {
807 // Markers aren't suppported yet.
812 auto appUnitsPerDevPx
= PresContext()->AppUnitsPerDevPixel();
813 float scale
= (float)AppUnitsPerCSSPixel() / (float)appUnitsPerDevPx
;
815 auto rect
= simplePath
.AsRect();
818 auto offset
= LayoutDevicePoint::FromAppUnits(
819 aItem
->ToReferenceFrame() - GetPosition(), appUnitsPerDevPx
);
820 rect
.MoveBy(offset
.x
, offset
.y
);
822 auto wrRect
= wr::ToLayoutRect(rect
);
824 SVGContextPaint
* contextPaint
=
825 SVGContextPaint::GetContextPaint(GetContent());
826 // At the moment this code path doesn't support strokes so it fine to
827 // combine the rectangle's opacity (which has to be applied on the result)
828 // of (filling + stroking) with the fill opacity.
829 float elemOpacity
= StyleEffects()->mOpacity
;
830 float fillOpacity
= SVGUtils::GetOpacity(style
->mFillOpacity
, contextPaint
);
831 float opacity
= elemOpacity
* fillOpacity
;
833 auto c
= nsLayoutUtils::GetColor(this, &nsStyleSVG::mFill
);
835 ((float)NS_GET_R(c
)) / 255.0f
, ((float)NS_GET_G(c
)) / 255.0f
,
836 ((float)NS_GET_B(c
)) / 255.0f
, ((float)NS_GET_A(c
)) / 255.0f
* opacity
};
838 aBuilder
.PushRect(wrRect
, wrRect
, !aItem
->BackfaceIsHidden(), true, false,
845 void SVGGeometryFrame::PaintMarkers(gfxContext
& aContext
,
846 const gfxMatrix
& aTransform
,
847 imgDrawingParams
& aImgParams
) {
848 auto* element
= static_cast<SVGGeometryElement
*>(GetContent());
849 if (!element
->IsMarkable()) {
852 SVGMarkerFrame
* markerFrames
[SVGMark::eTypeCount
];
853 if (!SVGObserverUtils::GetAndObserveMarkers(this, &markerFrames
)) {
856 nsTArray
<SVGMark
> marks
;
857 element
->GetMarkPoints(&marks
);
858 if (marks
.IsEmpty()) {
861 float strokeWidth
= GetStrokeWidthForMarkers();
862 for (const SVGMark
& mark
: marks
) {
863 if (auto* frame
= markerFrames
[mark
.type
]) {
864 frame
->PaintMark(aContext
, aTransform
, this, mark
, strokeWidth
,
870 float SVGGeometryFrame::GetStrokeWidthForMarkers() {
871 float strokeWidth
= SVGUtils::GetStrokeWidth(
872 this, SVGContextPaint::GetContextPaint(GetContent()));
873 gfxMatrix userToOuterSVG
;
874 if (SVGUtils::GetNonScalingStrokeTransform(this, &userToOuterSVG
)) {
875 // We're not interested in any translation here so we can treat this as
876 // Singular Value Decomposition (SVD) of a 2 x 2 matrix. That would give us
877 // sx and sy values as the X and Y scales. The value we want is the XY
878 // scale i.e. the normalised hypotenuse, which is sqrt(sx^2 + sy^2) /
879 // sqrt(2). If we use the formulae from
880 // https://scicomp.stackexchange.com/a/14103, we discover that the
881 // normalised hypotenuse is simply the square root of the sum of the squares
882 // of all the 2D matrix elements divided by sqrt(2).
884 // Note that this may need adjusting to support 3D transforms properly.
886 strokeWidth
/= float(sqrt(userToOuterSVG
._11
* userToOuterSVG
._11
+
887 userToOuterSVG
._12
* userToOuterSVG
._12
+
888 userToOuterSVG
._21
* userToOuterSVG
._21
+
889 userToOuterSVG
._22
* userToOuterSVG
._22
) /
895 uint16_t SVGGeometryFrame::GetHitTestFlags() {
896 return SVGUtils::GetGeometryHitTestFlags(this);
898 } // namespace mozilla