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/StaticPrefs_svg.h"
23 #include "mozilla/SVGContextPaint.h"
24 #include "mozilla/SVGContentUtils.h"
25 #include "mozilla/SVGObserverUtils.h"
26 #include "mozilla/SVGUtils.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 //----------------------------------------------------------------------
60 void SVGGeometryFrame::Init(nsIContent
* aContent
, nsContainerFrame
* aParent
,
61 nsIFrame
* aPrevInFlow
) {
62 AddStateBits(aParent
->GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD
);
63 nsIFrame::Init(aContent
, aParent
, aPrevInFlow
);
66 nsresult
SVGGeometryFrame::AttributeChanged(int32_t aNameSpaceID
,
69 // We don't invalidate for transform changes (the layers code does that).
70 // Also note that SVGTransformableElement::GetAttributeChangeHint will
71 // return nsChangeHint_UpdateOverflow for "transform" attribute changes
72 // and cause DoApplyRenderingChangeToTree to make the SchedulePaint call.
74 if (aNameSpaceID
== kNameSpaceID_None
&&
75 (static_cast<SVGGeometryElement
*>(GetContent())
76 ->AttributeDefinesGeometry(aAttribute
))) {
77 nsLayoutUtils::PostRestyleEvent(mContent
->AsElement(), RestyleHint
{0},
78 nsChangeHint_InvalidateRenderingObservers
);
79 SVGUtils::ScheduleReflowSVG(this);
85 void SVGGeometryFrame::DidSetComputedStyle(ComputedStyle
* aOldComputedStyle
) {
86 nsIFrame::DidSetComputedStyle(aOldComputedStyle
);
87 auto* element
= static_cast<SVGGeometryElement
*>(GetContent());
88 if (!aOldComputedStyle
) {
89 element
->ClearAnyCachedPath();
93 const auto* oldStyleSVG
= aOldComputedStyle
->StyleSVG();
94 if (!SVGContentUtils::ShapeTypeHasNoCorners(GetContent())) {
95 if (StyleSVG()->mStrokeLinecap
!= oldStyleSVG
->mStrokeLinecap
&&
96 element
->IsSVGElement(nsGkAtoms::path
)) {
97 // If the stroke-linecap changes to or from "butt" then our element
98 // needs to update its cached Moz2D Path, since SVGPathData::BuildPath
99 // decides whether or not to insert little lines into the path for zero
100 // length subpaths base on that property.
101 element
->ClearAnyCachedPath();
102 } else if (HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD
)) {
103 if (StyleSVG()->mClipRule
!= oldStyleSVG
->mClipRule
) {
104 // Moz2D Path objects are fill-rule specific.
105 // For clipPath we use clip-rule as the path's fill-rule.
106 element
->ClearAnyCachedPath();
108 } else if (StyleSVG()->mFillRule
!= oldStyleSVG
->mFillRule
) {
109 // Moz2D Path objects are fill-rule specific.
110 element
->ClearAnyCachedPath();
114 if (StyleDisplay()->CalcTransformPropertyDifference(
115 *aOldComputedStyle
->StyleDisplay())) {
116 NotifySVGChanged(TRANSFORM_CHANGED
);
119 if (element
->IsGeometryChangedViaCSS(*Style(), *aOldComputedStyle
)) {
120 element
->ClearAnyCachedPath();
121 SVGObserverUtils::InvalidateRenderingObservers(this);
125 bool SVGGeometryFrame::DoGetParentSVGTransforms(
126 gfx::Matrix
* aFromParentTransform
) const {
127 return SVGUtils::GetParentSVGTransforms(this, aFromParentTransform
);
130 void SVGGeometryFrame::BuildDisplayList(nsDisplayListBuilder
* aBuilder
,
131 const nsDisplayListSet
& aLists
) {
132 if (!static_cast<const SVGElement
*>(GetContent())->HasValidDimensions()) {
136 if (aBuilder
->IsForPainting()) {
137 if (!IsVisibleForPainting()) {
140 if (StyleEffects()->IsTransparent() && SVGUtils::CanOptimizeOpacity(this)) {
143 const auto* styleSVG
= StyleSVG();
144 if (styleSVG
->mFill
.kind
.IsNone() && styleSVG
->mStroke
.kind
.IsNone() &&
145 !styleSVG
->HasMarker()) {
149 aBuilder
->BuildCompositorHitTestInfoIfNeeded(this,
150 aLists
.BorderBackground());
153 DisplayOutline(aBuilder
, aLists
);
154 aLists
.Content()->AppendNewToTop
<DisplaySVGGeometry
>(aBuilder
, this);
157 //----------------------------------------------------------------------
158 // ISVGDisplayableFrame methods
160 void SVGGeometryFrame::PaintSVG(gfxContext
& aContext
,
161 const gfxMatrix
& aTransform
,
162 imgDrawingParams
& aImgParams
) {
163 if (!StyleVisibility()->IsVisible()) {
167 // Matrix to the geometry's user space:
168 gfxMatrix newMatrix
=
169 aContext
.CurrentMatrixDouble().PreMultiply(aTransform
).NudgeToIntegers();
170 if (newMatrix
.IsSingular()) {
174 uint32_t paintOrder
= StyleSVG()->mPaintOrder
;
176 Render(&aContext
, eRenderFill
| eRenderStroke
, newMatrix
, aImgParams
);
177 PaintMarkers(aContext
, aTransform
, aImgParams
);
180 auto component
= StylePaintOrder(paintOrder
& kPaintOrderMask
);
182 case StylePaintOrder::Fill
:
183 Render(&aContext
, eRenderFill
, newMatrix
, aImgParams
);
185 case StylePaintOrder::Stroke
:
186 Render(&aContext
, eRenderStroke
, newMatrix
, aImgParams
);
188 case StylePaintOrder::Markers
:
189 PaintMarkers(aContext
, aTransform
, aImgParams
);
192 MOZ_FALLTHROUGH_ASSERT("Unknown paint-order variant, how?");
193 case StylePaintOrder::Normal
:
196 paintOrder
>>= kPaintOrderShift
;
201 nsIFrame
* SVGGeometryFrame::GetFrameForPoint(const gfxPoint
& aPoint
) {
203 uint16_t hitTestFlags
;
204 if (HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD
)) {
205 hitTestFlags
= SVG_HIT_TEST_FILL
;
206 fillRule
= SVGUtils::ToFillRule(StyleSVG()->mClipRule
);
208 hitTestFlags
= SVGUtils::GetGeometryHitTestFlags(this);
212 fillRule
= SVGUtils::ToFillRule(StyleSVG()->mFillRule
);
217 SVGGeometryElement
* content
= static_cast<SVGGeometryElement
*>(GetContent());
219 // Using ScreenReferenceDrawTarget() opens us to Moz2D backend specific hit-
220 // testing bugs. Maybe we should use a BackendType::CAIRO DT for hit-testing
221 // so that we get more consistent/backwards compatible results?
222 RefPtr
<DrawTarget
> drawTarget
=
223 gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
224 RefPtr
<Path
> path
= content
->GetOrBuildPath(drawTarget
, fillRule
);
226 return nullptr; // no path, so we don't paint anything that can be hit
229 if (hitTestFlags
& SVG_HIT_TEST_FILL
) {
230 isHit
= path
->ContainsPoint(ToPoint(aPoint
), {});
232 if (!isHit
&& (hitTestFlags
& SVG_HIT_TEST_STROKE
)) {
233 Point point
= ToPoint(aPoint
);
234 SVGContentUtils::AutoStrokeOptions stroke
;
235 SVGContentUtils::GetStrokeOptions(&stroke
, content
, Style(), nullptr);
236 gfxMatrix userToOuterSVG
;
237 if (SVGUtils::GetNonScalingStrokeTransform(this, &userToOuterSVG
)) {
238 // We need to transform the path back into the appropriate ancestor
239 // coordinate system in order for non-scaled stroke to be correct.
240 // Naturally we also need to transform the point into the same
241 // coordinate system in order to hit-test against the path.
242 point
= ToMatrix(userToOuterSVG
).TransformPoint(point
);
243 Path::TransformAndSetFillRule(path
, ToMatrix(userToOuterSVG
), fillRule
);
245 isHit
= path
->StrokeContainsPoint(stroke
, point
, {});
248 if (isHit
&& SVGUtils::HitTestClip(this, aPoint
)) {
255 void SVGGeometryFrame::ReflowSVG() {
256 NS_ASSERTION(SVGUtils::OuterSVGIsCallingReflowSVG(this),
257 "This call is probably a wasteful mistake");
259 MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_IS_NONDISPLAY
),
260 "ReflowSVG mechanism not designed for this");
262 if (!SVGUtils::NeedsReflowSVG(this)) {
266 uint32_t flags
= SVGUtils::eBBoxIncludeFill
| SVGUtils::eBBoxIncludeStroke
|
267 SVGUtils::eBBoxIncludeMarkers
;
268 // Our "visual" overflow rect needs to be valid for building display lists
269 // for hit testing, which means that for certain values of 'pointer-events'
270 // it needs to include the geometry of the fill or stroke even when the fill/
271 // stroke don't actually render (e.g. when stroke="none" or
272 // stroke-opacity="0"). GetGeometryHitTestFlags() accounts for
274 uint16_t hitTestFlags
= SVGUtils::GetGeometryHitTestFlags(this);
275 if (hitTestFlags
& SVG_HIT_TEST_FILL
) {
276 flags
|= SVGUtils::eBBoxIncludeFillGeometry
;
278 if (hitTestFlags
& SVG_HIT_TEST_STROKE
) {
279 flags
|= SVGUtils::eBBoxIncludeStrokeGeometry
;
282 gfxRect extent
= GetBBoxContribution({}, flags
).ToThebesRect();
283 mRect
= nsLayoutUtils::RoundGfxRectToAppRect(extent
, AppUnitsPerCSSPixel());
285 if (HasAnyStateBits(NS_FRAME_FIRST_REFLOW
)) {
286 // Make sure we have our filter property (if any) before calling
287 // FinishAndStoreOverflow (subsequent filter changes are handled off
288 // nsChangeHint_UpdateEffects):
289 SVGObserverUtils::UpdateEffects(this);
292 nsRect overflow
= nsRect(nsPoint(0, 0), mRect
.Size());
293 OverflowAreas
overflowAreas(overflow
, overflow
);
294 FinishAndStoreOverflow(overflowAreas
, mRect
.Size());
296 RemoveStateBits(NS_FRAME_FIRST_REFLOW
| NS_FRAME_IS_DIRTY
|
297 NS_FRAME_HAS_DIRTY_CHILDREN
);
299 // Invalidate, but only if this is not our first reflow (since if it is our
300 // first reflow then we haven't had our first paint yet).
301 if (!GetParent()->HasAnyStateBits(NS_FRAME_FIRST_REFLOW
)) {
306 void SVGGeometryFrame::NotifySVGChanged(uint32_t aFlags
) {
307 MOZ_ASSERT(aFlags
& (TRANSFORM_CHANGED
| COORD_CONTEXT_CHANGED
),
308 "Invalidation logic may need adjusting");
310 // Changes to our ancestors may affect how we render when we are rendered as
311 // part of our ancestor (specifically, if our coordinate context changes size
312 // and we have percentage lengths defining our geometry, then we need to be
313 // reflowed). However, ancestor changes cannot affect how we render when we
314 // are rendered as part of any rendering observers that we may have.
315 // Therefore no need to notify rendering observers here.
317 // Don't try to be too smart trying to avoid the ScheduleReflowSVG calls
318 // for the stroke properties examined below. Checking HasStroke() is not
319 // enough, since what we care about is whether we include the stroke in our
320 // overflow rects or not, and we sometimes deliberately include stroke
321 // when it's not visible. See the complexities of GetBBoxContribution.
323 if (aFlags
& COORD_CONTEXT_CHANGED
) {
324 auto* geom
= static_cast<SVGGeometryElement
*>(GetContent());
325 // Stroke currently contributes to our mRect, which is why we have to take
326 // account of stroke-width here. Note that we do not need to take account
327 // of stroke-dashoffset since, although that can have a percentage value
328 // that is resolved against our coordinate context, it does not affect our
330 const auto& strokeWidth
= StyleSVG()->mStrokeWidth
;
331 if (geom
->GeometryDependsOnCoordCtx() ||
332 (strokeWidth
.IsLengthPercentage() &&
333 strokeWidth
.AsLengthPercentage().HasPercent())) {
334 geom
->ClearAnyCachedPath();
335 SVGUtils::ScheduleReflowSVG(this);
339 if ((aFlags
& TRANSFORM_CHANGED
) && StyleSVGReset()->HasNonScalingStroke()) {
340 // Stroke currently contributes to our mRect, and our stroke depends on
341 // the transform to our outer-<svg> if |vector-effect:non-scaling-stroke|.
342 SVGUtils::ScheduleReflowSVG(this);
346 SVGBBox
SVGGeometryFrame::GetBBoxContribution(const Matrix
& aToBBoxUserspace
,
350 if (aToBBoxUserspace
.IsSingular()) {
351 // XXX ReportToConsole
355 if ((aFlags
& SVGUtils::eForGetClientRects
) &&
356 aToBBoxUserspace
.PreservesAxisAlignedRectangles()) {
357 Rect rect
= NSRectToRect(mRect
, AppUnitsPerCSSPixel());
358 bbox
= aToBBoxUserspace
.TransformBounds(rect
);
362 SVGGeometryElement
* element
= static_cast<SVGGeometryElement
*>(GetContent());
364 const bool getFill
= (aFlags
& SVGUtils::eBBoxIncludeFillGeometry
) ||
365 ((aFlags
& SVGUtils::eBBoxIncludeFill
) &&
366 !StyleSVG()->mFill
.kind
.IsNone());
368 const bool getStroke
=
369 ((aFlags
& SVGUtils::eBBoxIncludeStrokeGeometry
) ||
370 ((aFlags
& SVGUtils::eBBoxIncludeStroke
) &&
371 SVGUtils::HasStroke(this))) &&
372 // If this frame has non-scaling-stroke and we would like to compute its
373 // stroke, it may cause a potential cyclical dependency if the caller is
374 // for transform. In this case, we have to fall back to fill-box, so make
375 // |getStroke| be false.
376 // https://github.com/w3c/csswg-drafts/issues/9640
379 // 1. We don't care about the computation of the markers below in this
380 // function because we know the callers don't set
381 // SVGUtils::eBBoxIncludeMarkers.
382 // See nsStyleTransformMatrix::GetSVGBox() and
383 // MotionPathUtils::GetRayContainReferenceSize() for more details.
384 // 2. We have to break the dependency here *again* because the geometry
385 // frame may be in the subtree of a SVGContainerFrame, which may not
386 // set non-scaling-stroke.
387 !(StyleSVGReset()->HasNonScalingStroke() &&
388 (aFlags
& SVGUtils::eAvoidCycleIfNonScalingStroke
));
390 SVGContentUtils::AutoStrokeOptions strokeOptions
;
392 SVGContentUtils::GetStrokeOptions(&strokeOptions
, element
, Style(), nullptr,
393 SVGContentUtils::eIgnoreStrokeDashing
);
395 // Override the default line width of 1.f so that when we call
396 // GetGeometryBounds below the result doesn't include stroke bounds.
397 strokeOptions
.mLineWidth
= 0.f
;
401 bool gotSimpleBounds
= false;
402 gfxMatrix userToOuterSVG
;
404 SVGUtils::GetNonScalingStrokeTransform(this, &userToOuterSVG
)) {
405 Matrix moz2dUserToOuterSVG
= ToMatrix(userToOuterSVG
);
406 if (moz2dUserToOuterSVG
.IsSingular()) {
409 gotSimpleBounds
= element
->GetGeometryBounds(
410 &simpleBounds
, strokeOptions
, aToBBoxUserspace
, &moz2dUserToOuterSVG
);
412 gotSimpleBounds
= element
->GetGeometryBounds(&simpleBounds
, strokeOptions
,
416 if (gotSimpleBounds
) {
419 // Get the bounds using a Moz2D Path object (more expensive):
420 RefPtr
<DrawTarget
> tmpDT
;
421 tmpDT
= gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
423 FillRule fillRule
= SVGUtils::ToFillRule(
424 HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD
) ? StyleSVG()->mClipRule
425 : StyleSVG()->mFillRule
);
426 RefPtr
<Path
> pathInUserSpace
= element
->GetOrBuildPath(tmpDT
, fillRule
);
427 if (!pathInUserSpace
) {
430 RefPtr
<Path
> pathInBBoxSpace
;
431 if (aToBBoxUserspace
.IsIdentity()) {
432 pathInBBoxSpace
= pathInUserSpace
;
434 RefPtr
<PathBuilder
> builder
=
435 pathInUserSpace
->TransformedCopyToBuilder(aToBBoxUserspace
, fillRule
);
436 pathInBBoxSpace
= builder
->Finish();
437 if (!pathInBBoxSpace
) {
443 if (getFill
&& !getStroke
) {
444 Rect pathBBoxExtents
= pathInBBoxSpace
->GetBounds();
445 if (!pathBBoxExtents
.IsFinite()) {
446 // This can happen in the case that we only have a move-to command in
447 // the path commands, in which case we know nothing gets rendered.
450 bbox
= pathBBoxExtents
;
453 // Account for stroke:
455 // Be careful when replacing the following logic to get the fill and
456 // stroke extents independently.
457 // You may think that you can just use the stroke extents if
458 // there is both a fill and a stroke. In reality it may be necessary to
459 // calculate both the fill and stroke extents.
460 // There are two reasons for this:
462 // # Due to stroke dashing, in certain cases the fill extents could
463 // actually extend outside the stroke extents.
464 // # If the stroke is very thin, cairo won't paint any stroke, and so the
465 // stroke bounds that it will return will be empty.
467 Rect strokeBBoxExtents
;
468 if (StaticPrefs::svg_Moz2D_strokeBounds_enabled()) {
469 gfxMatrix userToOuterSVG
;
470 if (SVGUtils::GetNonScalingStrokeTransform(this, &userToOuterSVG
)) {
471 Matrix outerSVGToUser
= ToMatrix(userToOuterSVG
);
472 outerSVGToUser
.Invert();
473 Matrix outerSVGToBBox
= aToBBoxUserspace
* outerSVGToUser
;
474 RefPtr
<PathBuilder
> builder
=
475 pathInUserSpace
->TransformedCopyToBuilder(
476 ToMatrix(userToOuterSVG
));
477 RefPtr
<Path
> pathInOuterSVGSpace
= builder
->Finish();
478 strokeBBoxExtents
= pathInOuterSVGSpace
->GetStrokedBounds(
479 strokeOptions
, outerSVGToBBox
);
481 strokeBBoxExtents
= pathInUserSpace
->GetStrokedBounds(
482 strokeOptions
, aToBBoxUserspace
);
484 if (strokeBBoxExtents
.IsEmpty() && getFill
) {
485 strokeBBoxExtents
= pathInBBoxSpace
->GetBounds();
486 if (!strokeBBoxExtents
.IsFinite()) {
491 Rect pathBBoxExtents
= pathInBBoxSpace
->GetBounds();
492 if (!pathBBoxExtents
.IsFinite()) {
495 strokeBBoxExtents
= ToRect(SVGUtils::PathExtentsToMaxStrokeExtents(
496 ThebesRect(pathBBoxExtents
), this, ThebesMatrix(aToBBoxUserspace
)));
498 MOZ_ASSERT(strokeBBoxExtents
.IsFinite(), "bbox is about to go bad");
499 bbox
.UnionEdges(strokeBBoxExtents
);
503 // Account for markers:
504 if ((aFlags
& SVGUtils::eBBoxIncludeMarkers
) && element
->IsMarkable()) {
505 SVGMarkerFrame
* markerFrames
[SVGMark::eTypeCount
];
506 if (SVGObserverUtils::GetAndObserveMarkers(this, &markerFrames
)) {
507 nsTArray
<SVGMark
> marks
;
508 element
->GetMarkPoints(&marks
);
509 if (uint32_t num
= marks
.Length()) {
510 float strokeWidth
= SVGUtils::GetStrokeWidth(this);
511 for (uint32_t i
= 0; i
< num
; i
++) {
512 const SVGMark
& mark
= marks
[i
];
513 SVGMarkerFrame
* frame
= markerFrames
[mark
.type
];
515 SVGBBox mbbox
= frame
->GetMarkBBoxContribution(
516 aToBBoxUserspace
, aFlags
, this, mark
, strokeWidth
);
517 MOZ_ASSERT(mbbox
.IsFinite(), "bbox is about to go bad");
518 bbox
.UnionEdges(mbbox
);
528 //----------------------------------------------------------------------
529 // SVGGeometryFrame methods:
531 gfxMatrix
SVGGeometryFrame::GetCanvasTM() {
532 NS_ASSERTION(GetParent(), "null parent");
534 auto* parent
= static_cast<SVGContainerFrame
*>(GetParent());
535 auto* content
= static_cast<SVGGraphicsElement
*>(GetContent());
536 return content
->ChildToUserSpaceTransform() * parent
->GetCanvasTM();
539 void SVGGeometryFrame::Render(gfxContext
* aContext
, uint32_t aRenderComponents
,
540 const gfxMatrix
& aTransform
,
541 imgDrawingParams
& aImgParams
) {
542 MOZ_ASSERT(!aTransform
.IsSingular());
544 DrawTarget
* drawTarget
= aContext
->GetDrawTarget();
546 MOZ_ASSERT(drawTarget
);
547 if (!drawTarget
->IsValid()) {
551 FillRule fillRule
= SVGUtils::ToFillRule(
552 HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD
) ? StyleSVG()->mClipRule
553 : StyleSVG()->mFillRule
);
555 SVGGeometryElement
* element
= static_cast<SVGGeometryElement
*>(GetContent());
557 AntialiasMode aaMode
= SVGUtils::ToAntialiasMode(StyleSVG()->mShapeRendering
);
559 // We wait as late as possible before setting the transform so that we don't
560 // set it unnecessarily if we return early (it's an expensive operation for
562 gfxContextMatrixAutoSaveRestore
autoRestoreTransform(aContext
);
563 aContext
->SetMatrixDouble(aTransform
);
565 if (HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD
)) {
566 // We don't complicate this code with GetAsSimplePath since the cost of
567 // masking will dwarf Path creation overhead anyway.
568 RefPtr
<Path
> path
= element
->GetOrBuildPath(drawTarget
, fillRule
);
570 ColorPattern
white(ToDeviceColor(sRGBColor(1.0f
, 1.0f
, 1.0f
, 1.0f
)));
571 drawTarget
->Fill(path
, white
,
572 DrawOptions(1.0f
, CompositionOp::OP_OVER
, aaMode
));
577 SVGGeometryElement::SimplePath simplePath
;
580 element
->GetAsSimplePath(&simplePath
);
581 if (!simplePath
.IsPath()) {
582 path
= element
->GetOrBuildPath(drawTarget
, fillRule
);
588 SVGContextPaint
* contextPaint
=
589 SVGContextPaint::GetContextPaint(GetContent());
591 if (aRenderComponents
& eRenderFill
) {
592 GeneralPattern fillPattern
;
593 SVGUtils::MakeFillPatternFor(this, aContext
, &fillPattern
, aImgParams
,
596 if (fillPattern
.GetPattern()) {
597 DrawOptions
drawOptions(1.0f
, CompositionOp::OP_OVER
, aaMode
);
598 if (simplePath
.IsRect()) {
599 drawTarget
->FillRect(simplePath
.AsRect(), fillPattern
, drawOptions
);
601 drawTarget
->Fill(path
, fillPattern
, drawOptions
);
606 if ((aRenderComponents
& eRenderStroke
) &&
607 SVGUtils::HasStroke(this, contextPaint
)) {
608 // Account for vector-effect:non-scaling-stroke:
609 gfxMatrix userToOuterSVG
;
610 if (SVGUtils::GetNonScalingStrokeTransform(this, &userToOuterSVG
)) {
611 // A simple Rect can't be transformed with rotate/skew, so let's switch
612 // to using a real path:
614 path
= element
->GetOrBuildPath(drawTarget
, fillRule
);
620 // We need to transform the path back into the appropriate ancestor
621 // coordinate system, and paint it it that coordinate system, in order
622 // for non-scaled stroke to paint correctly.
623 gfxMatrix outerSVGToUser
= userToOuterSVG
;
624 outerSVGToUser
.Invert();
625 aContext
->Multiply(outerSVGToUser
);
626 Path::TransformAndSetFillRule(path
, ToMatrix(userToOuterSVG
), fillRule
);
628 GeneralPattern strokePattern
;
629 SVGUtils::MakeStrokePatternFor(this, aContext
, &strokePattern
, aImgParams
,
632 if (strokePattern
.GetPattern()) {
633 SVGContentUtils::AutoStrokeOptions strokeOptions
;
634 SVGContentUtils::GetStrokeOptions(&strokeOptions
,
635 static_cast<SVGElement
*>(GetContent()),
636 Style(), contextPaint
);
637 // GetStrokeOptions may set the line width to zero as an optimization
638 if (strokeOptions
.mLineWidth
<= 0) {
641 DrawOptions
drawOptions(1.0f
, CompositionOp::OP_OVER
, aaMode
);
642 if (simplePath
.IsRect()) {
643 drawTarget
->StrokeRect(simplePath
.AsRect(), strokePattern
,
644 strokeOptions
, drawOptions
);
645 } else if (simplePath
.IsLine()) {
646 drawTarget
->StrokeLine(simplePath
.Point1(), simplePath
.Point2(),
647 strokePattern
, strokeOptions
, drawOptions
);
649 drawTarget
->Stroke(path
, strokePattern
, strokeOptions
, drawOptions
);
655 bool SVGGeometryFrame::IsInvisible() const {
656 if (!StyleVisibility()->IsVisible()) {
660 // Anything below will round to zero later down the pipeline.
661 constexpr float opacity_threshold
= 1.0 / 128.0;
663 if (StyleEffects()->mOpacity
<= opacity_threshold
&&
664 SVGUtils::CanOptimizeOpacity(this)) {
668 const nsStyleSVG
* style
= StyleSVG();
669 SVGContextPaint
* contextPaint
=
670 SVGContextPaint::GetContextPaint(GetContent());
672 if (!style
->mFill
.kind
.IsNone()) {
673 float opacity
= SVGUtils::GetOpacity(style
->mFillOpacity
, contextPaint
);
674 if (opacity
> opacity_threshold
) {
679 if (!style
->mStroke
.kind
.IsNone()) {
680 float opacity
= SVGUtils::GetOpacity(style
->mStrokeOpacity
, contextPaint
);
681 if (opacity
> opacity_threshold
) {
686 if (style
->HasMarker()) {
693 bool SVGGeometryFrame::CreateWebRenderCommands(
694 mozilla::wr::DisplayListBuilder
& aBuilder
,
695 mozilla::wr::IpcResourceUpdateQueue
& aResources
,
696 const mozilla::layers::StackingContextHelper
& aSc
,
697 mozilla::layers::RenderRootStateManager
* aManager
,
698 nsDisplayListBuilder
* aDisplayListBuilder
, DisplaySVGGeometry
* aItem
,
700 if (!StyleVisibility()->IsVisible()) {
704 SVGGeometryElement
* element
= static_cast<SVGGeometryElement
*>(GetContent());
706 SVGGeometryElement::SimplePath simplePath
;
707 element
->GetAsSimplePath(&simplePath
);
709 if (!simplePath
.IsRect()) {
713 const nsStyleSVG
* style
= StyleSVG();
716 if (!style
->mFill
.kind
.IsColor()) {
720 switch (style
->mFill
.kind
.tag
) {
721 case StyleSVGPaintKind::Tag::Color
:
727 if (!style
->mStroke
.kind
.IsNone()) {
731 if (StyleEffects()->HasMixBlendMode()) {
732 // FIXME: not implemented
736 if (style
->HasMarker() && element
->IsMarkable()) {
737 // Markers aren't suppported yet.
742 auto appUnitsPerDevPx
= PresContext()->AppUnitsPerDevPixel();
743 float scale
= (float)AppUnitsPerCSSPixel() / (float)appUnitsPerDevPx
;
745 auto rect
= simplePath
.AsRect();
748 auto offset
= LayoutDevicePoint::FromAppUnits(
749 aItem
->ToReferenceFrame() - GetPosition(), appUnitsPerDevPx
);
750 rect
.MoveBy(offset
.x
, offset
.y
);
752 auto wrRect
= wr::ToLayoutRect(rect
);
754 SVGContextPaint
* contextPaint
=
755 SVGContextPaint::GetContextPaint(GetContent());
756 // At the moment this code path doesn't support strokes so it fine to
757 // combine the rectangle's opacity (which has to be applied on the result)
758 // of (filling + stroking) with the fill opacity.
760 float elemOpacity
= 1.0f
;
761 if (SVGUtils::CanOptimizeOpacity(this)) {
762 elemOpacity
= StyleEffects()->mOpacity
;
765 float fillOpacity
= SVGUtils::GetOpacity(style
->mFillOpacity
, contextPaint
);
766 float opacity
= elemOpacity
* fillOpacity
;
768 auto color
= wr::ToColorF(
769 ToDeviceColor(StyleSVG()->mFill
.kind
.AsColor().CalcColor(this)));
771 aBuilder
.PushRect(wrRect
, wrRect
, !aItem
->BackfaceIsHidden(), true, false,
778 void SVGGeometryFrame::PaintMarkers(gfxContext
& aContext
,
779 const gfxMatrix
& aTransform
,
780 imgDrawingParams
& aImgParams
) {
781 auto* element
= static_cast<SVGGeometryElement
*>(GetContent());
782 if (!element
->IsMarkable()) {
785 SVGMarkerFrame
* markerFrames
[SVGMark::eTypeCount
];
786 if (!SVGObserverUtils::GetAndObserveMarkers(this, &markerFrames
)) {
789 nsTArray
<SVGMark
> marks
;
790 element
->GetMarkPoints(&marks
);
791 if (marks
.IsEmpty()) {
794 float strokeWidth
= GetStrokeWidthForMarkers();
795 for (const SVGMark
& mark
: marks
) {
796 if (auto* frame
= markerFrames
[mark
.type
]) {
797 frame
->PaintMark(aContext
, aTransform
, this, mark
, strokeWidth
,
803 float SVGGeometryFrame::GetStrokeWidthForMarkers() {
804 float strokeWidth
= SVGUtils::GetStrokeWidth(
805 this, SVGContextPaint::GetContextPaint(GetContent()));
806 gfxMatrix userToOuterSVG
;
807 if (SVGUtils::GetNonScalingStrokeTransform(this, &userToOuterSVG
)) {
808 // We're not interested in any translation here so we can treat this as
809 // Singular Value Decomposition (SVD) of a 2 x 2 matrix. That would give us
810 // sx and sy values as the X and Y scales. The value we want is the XY
811 // scale i.e. the normalised hypotenuse, which is sqrt(sx^2 + sy^2) /
812 // sqrt(2). If we use the formulae from
813 // https://scicomp.stackexchange.com/a/14103, we discover that the
814 // normalised hypotenuse is simply the square root of the sum of the squares
815 // of all the 2D matrix elements divided by sqrt(2).
817 // Note that this may need adjusting to support 3D transforms properly.
819 strokeWidth
/= float(sqrt(userToOuterSVG
._11
* userToOuterSVG
._11
+
820 userToOuterSVG
._12
* userToOuterSVG
._12
+
821 userToOuterSVG
._21
* userToOuterSVG
._21
+
822 userToOuterSVG
._22
* userToOuterSVG
._22
) /
828 } // namespace mozilla