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 "nsGkAtoms.h"
27 #include "nsLayoutUtils.h"
28 #include "SVGAnimatedTransformList.h"
29 #include "SVGMarkerFrame.h"
31 using namespace mozilla::dom
;
32 using namespace mozilla::gfx
;
33 using namespace mozilla::image
;
35 //----------------------------------------------------------------------
38 nsIFrame
* NS_NewSVGGeometryFrame(mozilla::PresShell
* aPresShell
,
39 mozilla::ComputedStyle
* aStyle
) {
40 return new (aPresShell
)
41 mozilla::SVGGeometryFrame(aStyle
, aPresShell
->GetPresContext());
46 NS_IMPL_FRAMEARENA_HELPERS(SVGGeometryFrame
)
48 //----------------------------------------------------------------------
49 // nsQueryFrame methods
51 NS_QUERYFRAME_HEAD(SVGGeometryFrame
)
52 NS_QUERYFRAME_ENTRY(ISVGDisplayableFrame
)
53 NS_QUERYFRAME_ENTRY(SVGGeometryFrame
)
54 NS_QUERYFRAME_TAIL_INHERITING(nsIFrame
)
56 //----------------------------------------------------------------------
59 void SVGGeometryFrame::Init(nsIContent
* aContent
, nsContainerFrame
* aParent
,
60 nsIFrame
* aPrevInFlow
) {
61 AddStateBits(aParent
->GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD
);
62 nsIFrame::Init(aContent
, aParent
, aPrevInFlow
);
65 nsresult
SVGGeometryFrame::AttributeChanged(int32_t aNameSpaceID
,
68 // We don't invalidate for transform changes (the layers code does that).
69 // Also note that SVGTransformableElement::GetAttributeChangeHint will
70 // return nsChangeHint_UpdateOverflow for "transform" attribute changes
71 // and cause DoApplyRenderingChangeToTree to make the SchedulePaint call.
73 if (aNameSpaceID
== kNameSpaceID_None
&&
74 (static_cast<SVGGeometryElement
*>(GetContent())
75 ->AttributeDefinesGeometry(aAttribute
))) {
76 nsLayoutUtils::PostRestyleEvent(mContent
->AsElement(), RestyleHint
{0},
77 nsChangeHint_InvalidateRenderingObservers
);
78 SVGUtils::ScheduleReflowSVG(this);
84 void SVGGeometryFrame::DidSetComputedStyle(ComputedStyle
* aOldComputedStyle
) {
85 nsIFrame::DidSetComputedStyle(aOldComputedStyle
);
86 auto* element
= static_cast<SVGGeometryElement
*>(GetContent());
87 if (!aOldComputedStyle
) {
88 element
->ClearAnyCachedPath();
92 const auto* oldStyleSVG
= aOldComputedStyle
->StyleSVG();
93 if (!SVGContentUtils::ShapeTypeHasNoCorners(GetContent())) {
94 if (StyleSVG()->mStrokeLinecap
!= oldStyleSVG
->mStrokeLinecap
&&
95 element
->IsSVGElement(nsGkAtoms::path
)) {
96 // If the stroke-linecap changes to or from "butt" then our element
97 // needs to update its cached Moz2D Path, since SVGPathData::BuildPath
98 // decides whether or not to insert little lines into the path for zero
99 // length subpaths base on that property.
100 element
->ClearAnyCachedPath();
101 } else if (HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD
)) {
102 if (StyleSVG()->mClipRule
!= oldStyleSVG
->mClipRule
) {
103 // Moz2D Path objects are fill-rule specific.
104 // For clipPath we use clip-rule as the path's fill-rule.
105 element
->ClearAnyCachedPath();
108 if (StyleSVG()->mFillRule
!= oldStyleSVG
->mFillRule
) {
109 // Moz2D Path objects are fill-rule specific.
110 element
->ClearAnyCachedPath();
115 if (element
->IsGeometryChangedViaCSS(*Style(), *aOldComputedStyle
)) {
116 element
->ClearAnyCachedPath();
120 bool SVGGeometryFrame::IsSVGTransformed(
121 gfx::Matrix
* aOwnTransform
, gfx::Matrix
* aFromParentTransform
) const {
122 return SVGUtils::IsSVGTransformed(this, aOwnTransform
, aFromParentTransform
);
125 void SVGGeometryFrame::BuildDisplayList(nsDisplayListBuilder
* aBuilder
,
126 const nsDisplayListSet
& aLists
) {
127 if (!static_cast<const SVGElement
*>(GetContent())->HasValidDimensions()) {
131 if (aBuilder
->IsForPainting()) {
132 if (!IsVisibleForPainting()) {
135 if (StyleEffects()->IsTransparent()) {
138 const auto* styleSVG
= StyleSVG();
139 if (styleSVG
->mFill
.kind
.IsNone() && styleSVG
->mStroke
.kind
.IsNone() &&
140 !styleSVG
->HasMarker()) {
144 aBuilder
->BuildCompositorHitTestInfoIfNeeded(this,
145 aLists
.BorderBackground());
148 DisplayOutline(aBuilder
, aLists
);
149 aLists
.Content()->AppendNewToTop
<DisplaySVGGeometry
>(aBuilder
, this);
152 //----------------------------------------------------------------------
153 // ISVGDisplayableFrame methods
155 void SVGGeometryFrame::PaintSVG(gfxContext
& aContext
,
156 const gfxMatrix
& aTransform
,
157 imgDrawingParams
& aImgParams
) {
158 if (!StyleVisibility()->IsVisible()) {
162 // Matrix to the geometry's user space:
163 gfxMatrix newMatrix
=
164 aContext
.CurrentMatrixDouble().PreMultiply(aTransform
).NudgeToIntegers();
165 if (newMatrix
.IsSingular()) {
169 uint32_t paintOrder
= StyleSVG()->mPaintOrder
;
171 Render(&aContext
, eRenderFill
| eRenderStroke
, newMatrix
, aImgParams
);
172 PaintMarkers(aContext
, aTransform
, aImgParams
);
175 auto component
= StylePaintOrder(paintOrder
& kPaintOrderMask
);
177 case StylePaintOrder::Fill
:
178 Render(&aContext
, eRenderFill
, newMatrix
, aImgParams
);
180 case StylePaintOrder::Stroke
:
181 Render(&aContext
, eRenderStroke
, newMatrix
, aImgParams
);
183 case StylePaintOrder::Markers
:
184 PaintMarkers(aContext
, aTransform
, aImgParams
);
187 MOZ_FALLTHROUGH_ASSERT("Unknown paint-order variant, how?");
188 case StylePaintOrder::Normal
:
191 paintOrder
>>= kPaintOrderShift
;
196 nsIFrame
* SVGGeometryFrame::GetFrameForPoint(const gfxPoint
& aPoint
) {
198 uint16_t hitTestFlags
;
199 if (HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD
)) {
200 hitTestFlags
= SVG_HIT_TEST_FILL
;
201 fillRule
= SVGUtils::ToFillRule(StyleSVG()->mClipRule
);
203 hitTestFlags
= SVGUtils::GetGeometryHitTestFlags(this);
207 fillRule
= SVGUtils::ToFillRule(StyleSVG()->mFillRule
);
212 SVGGeometryElement
* content
= static_cast<SVGGeometryElement
*>(GetContent());
214 // Using ScreenReferenceDrawTarget() opens us to Moz2D backend specific hit-
215 // testing bugs. Maybe we should use a BackendType::CAIRO DT for hit-testing
216 // so that we get more consistent/backwards compatible results?
217 RefPtr
<DrawTarget
> drawTarget
=
218 gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
219 RefPtr
<Path
> path
= content
->GetOrBuildPath(drawTarget
, fillRule
);
221 return nullptr; // no path, so we don't paint anything that can be hit
224 if (hitTestFlags
& SVG_HIT_TEST_FILL
) {
225 isHit
= path
->ContainsPoint(ToPoint(aPoint
), {});
227 if (!isHit
&& (hitTestFlags
& SVG_HIT_TEST_STROKE
)) {
228 Point point
= ToPoint(aPoint
);
229 SVGContentUtils::AutoStrokeOptions stroke
;
230 SVGContentUtils::GetStrokeOptions(&stroke
, content
, Style(), nullptr);
231 gfxMatrix userToOuterSVG
;
232 if (SVGUtils::GetNonScalingStrokeTransform(this, &userToOuterSVG
)) {
233 // We need to transform the path back into the appropriate ancestor
234 // coordinate system in order for non-scaled stroke to be correct.
235 // Naturally we also need to transform the point into the same
236 // coordinate system in order to hit-test against the path.
237 point
= ToMatrix(userToOuterSVG
).TransformPoint(point
);
238 RefPtr
<PathBuilder
> builder
=
239 path
->TransformedCopyToBuilder(ToMatrix(userToOuterSVG
), fillRule
);
240 path
= builder
->Finish();
242 isHit
= path
->StrokeContainsPoint(stroke
, point
, {});
245 if (isHit
&& SVGUtils::HitTestClip(this, aPoint
)) {
252 void SVGGeometryFrame::ReflowSVG() {
253 NS_ASSERTION(SVGUtils::OuterSVGIsCallingReflowSVG(this),
254 "This call is probably a wasteful mistake");
256 MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_IS_NONDISPLAY
),
257 "ReflowSVG mechanism not designed for this");
259 if (!SVGUtils::NeedsReflowSVG(this)) {
263 uint32_t flags
= SVGUtils::eBBoxIncludeFill
| SVGUtils::eBBoxIncludeStroke
|
264 SVGUtils::eBBoxIncludeMarkers
;
265 // Our "visual" overflow rect needs to be valid for building display lists
266 // for hit testing, which means that for certain values of 'pointer-events'
267 // it needs to include the geometry of the fill or stroke even when the fill/
268 // stroke don't actually render (e.g. when stroke="none" or
269 // stroke-opacity="0"). GetGeometryHitTestFlags() accounts for
271 uint16_t hitTestFlags
= SVGUtils::GetGeometryHitTestFlags(this);
272 if (hitTestFlags
& SVG_HIT_TEST_FILL
) {
273 flags
|= SVGUtils::eBBoxIncludeFillGeometry
;
275 if (hitTestFlags
& SVG_HIT_TEST_STROKE
) {
276 flags
|= SVGUtils::eBBoxIncludeStrokeGeometry
;
279 gfxRect extent
= GetBBoxContribution({}, flags
).ToThebesRect();
280 mRect
= nsLayoutUtils::RoundGfxRectToAppRect(extent
, AppUnitsPerCSSPixel());
282 if (HasAnyStateBits(NS_FRAME_FIRST_REFLOW
)) {
283 // Make sure we have our filter property (if any) before calling
284 // FinishAndStoreOverflow (subsequent filter changes are handled off
285 // nsChangeHint_UpdateEffects):
286 SVGObserverUtils::UpdateEffects(this);
289 nsRect overflow
= nsRect(nsPoint(0, 0), mRect
.Size());
290 OverflowAreas
overflowAreas(overflow
, overflow
);
291 FinishAndStoreOverflow(overflowAreas
, mRect
.Size());
293 RemoveStateBits(NS_FRAME_FIRST_REFLOW
| NS_FRAME_IS_DIRTY
|
294 NS_FRAME_HAS_DIRTY_CHILDREN
);
296 // Invalidate, but only if this is not our first reflow (since if it is our
297 // first reflow then we haven't had our first paint yet).
298 if (!GetParent()->HasAnyStateBits(NS_FRAME_FIRST_REFLOW
)) {
303 void SVGGeometryFrame::NotifySVGChanged(uint32_t aFlags
) {
304 MOZ_ASSERT(aFlags
& (TRANSFORM_CHANGED
| COORD_CONTEXT_CHANGED
),
305 "Invalidation logic may need adjusting");
307 // Changes to our ancestors may affect how we render when we are rendered as
308 // part of our ancestor (specifically, if our coordinate context changes size
309 // and we have percentage lengths defining our geometry, then we need to be
310 // reflowed). However, ancestor changes cannot affect how we render when we
311 // are rendered as part of any rendering observers that we may have.
312 // Therefore no need to notify rendering observers here.
314 // Don't try to be too smart trying to avoid the ScheduleReflowSVG calls
315 // for the stroke properties examined below. Checking HasStroke() is not
316 // enough, since what we care about is whether we include the stroke in our
317 // overflow rects or not, and we sometimes deliberately include stroke
318 // when it's not visible. See the complexities of GetBBoxContribution.
320 if (aFlags
& COORD_CONTEXT_CHANGED
) {
321 auto* geom
= static_cast<SVGGeometryElement
*>(GetContent());
322 // Stroke currently contributes to our mRect, which is why we have to take
323 // account of stroke-width here. Note that we do not need to take account
324 // of stroke-dashoffset since, although that can have a percentage value
325 // that is resolved against our coordinate context, it does not affect our
327 const auto& strokeWidth
= StyleSVG()->mStrokeWidth
;
328 if (geom
->GeometryDependsOnCoordCtx() ||
329 (strokeWidth
.IsLengthPercentage() &&
330 strokeWidth
.AsLengthPercentage().HasPercent())) {
331 geom
->ClearAnyCachedPath();
332 SVGUtils::ScheduleReflowSVG(this);
336 if ((aFlags
& TRANSFORM_CHANGED
) && StyleSVGReset()->HasNonScalingStroke()) {
337 // Stroke currently contributes to our mRect, and our stroke depends on
338 // the transform to our outer-<svg> if |vector-effect:non-scaling-stroke|.
339 SVGUtils::ScheduleReflowSVG(this);
343 SVGBBox
SVGGeometryFrame::GetBBoxContribution(const Matrix
& aToBBoxUserspace
,
347 if (aToBBoxUserspace
.IsSingular()) {
348 // XXX ReportToConsole
352 if ((aFlags
& SVGUtils::eForGetClientRects
) &&
353 aToBBoxUserspace
.PreservesAxisAlignedRectangles()) {
354 Rect rect
= NSRectToRect(mRect
, AppUnitsPerCSSPixel());
355 bbox
= aToBBoxUserspace
.TransformBounds(rect
);
359 SVGGeometryElement
* element
= static_cast<SVGGeometryElement
*>(GetContent());
361 bool getFill
= (aFlags
& SVGUtils::eBBoxIncludeFillGeometry
) ||
362 ((aFlags
& SVGUtils::eBBoxIncludeFill
) &&
363 !StyleSVG()->mFill
.kind
.IsNone());
366 (aFlags
& SVGUtils::eBBoxIncludeStrokeGeometry
) ||
367 ((aFlags
& SVGUtils::eBBoxIncludeStroke
) && SVGUtils::HasStroke(this));
369 SVGContentUtils::AutoStrokeOptions strokeOptions
;
371 SVGContentUtils::GetStrokeOptions(&strokeOptions
, element
, Style(), nullptr,
372 SVGContentUtils::eIgnoreStrokeDashing
);
374 // Override the default line width of 1.f so that when we call
375 // GetGeometryBounds below the result doesn't include stroke bounds.
376 strokeOptions
.mLineWidth
= 0.f
;
380 bool gotSimpleBounds
= false;
381 gfxMatrix userToOuterSVG
;
383 SVGUtils::GetNonScalingStrokeTransform(this, &userToOuterSVG
)) {
384 Matrix moz2dUserToOuterSVG
= ToMatrix(userToOuterSVG
);
385 if (moz2dUserToOuterSVG
.IsSingular()) {
388 gotSimpleBounds
= element
->GetGeometryBounds(
389 &simpleBounds
, strokeOptions
, aToBBoxUserspace
, &moz2dUserToOuterSVG
);
391 gotSimpleBounds
= element
->GetGeometryBounds(&simpleBounds
, strokeOptions
,
395 if (gotSimpleBounds
) {
398 // Get the bounds using a Moz2D Path object (more expensive):
399 RefPtr
<DrawTarget
> tmpDT
;
400 tmpDT
= gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
402 FillRule fillRule
= SVGUtils::ToFillRule(
403 HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD
) ? StyleSVG()->mClipRule
404 : StyleSVG()->mFillRule
);
405 RefPtr
<Path
> pathInUserSpace
= element
->GetOrBuildPath(tmpDT
, fillRule
);
406 if (!pathInUserSpace
) {
409 RefPtr
<Path
> pathInBBoxSpace
;
410 if (aToBBoxUserspace
.IsIdentity()) {
411 pathInBBoxSpace
= pathInUserSpace
;
413 RefPtr
<PathBuilder
> builder
=
414 pathInUserSpace
->TransformedCopyToBuilder(aToBBoxUserspace
, fillRule
);
415 pathInBBoxSpace
= builder
->Finish();
416 if (!pathInBBoxSpace
) {
421 // Be careful when replacing the following logic to get the fill and stroke
422 // extents independently (instead of computing the stroke extents from the
423 // path extents). You may think that you can just use the stroke extents if
424 // there is both a fill and a stroke. In reality it's necessary to
425 // calculate both the fill and stroke extents, and take the union of the
426 // two. There are two reasons for this:
428 // # Due to stroke dashing, in certain cases the fill extents could
429 // actually extend outside the stroke extents.
430 // # If the stroke is very thin, cairo won't paint any stroke, and so the
431 // stroke bounds that it will return will be empty.
433 Rect pathBBoxExtents
= pathInBBoxSpace
->GetBounds();
434 if (!pathBBoxExtents
.IsFinite()) {
435 // This can happen in the case that we only have a move-to command in the
436 // path commands, in which case we know nothing gets rendered.
442 bbox
= pathBBoxExtents
;
445 // Account for stroke:
448 // This disabled code is how we would calculate the stroke bounds using
449 // Moz2D Path::GetStrokedBounds(). Unfortunately at the time of writing
450 // it there are two problems that prevent us from using it.
452 // First, it seems that some of the Moz2D backends are really dumb. Not
453 // only do some GetStrokeOptions() implementations sometimes
454 // significantly overestimate the stroke bounds, but if an argument is
455 // passed for the aTransform parameter then they just return bounds-of-
456 // transformed-bounds. These two things combined can lead the bounds to
457 // be unacceptably oversized, leading to massive over-invalidation.
459 // Second, the way we account for non-scaling-stroke by transforming the
460 // path using the transform to the outer-<svg> element is not compatible
461 // with the way that SVGGeometryFrame::Reflow() inserts a scale
462 // into aToBBoxUserspace and then scales the bounds that we return.
463 SVGContentUtils::AutoStrokeOptions strokeOptions
;
464 SVGContentUtils::GetStrokeOptions(&strokeOptions
, element
,
466 SVGContentUtils::eIgnoreStrokeDashing
);
467 Rect strokeBBoxExtents
;
468 gfxMatrix userToOuterSVG
;
469 if (SVGUtils::GetNonScalingStrokeTransform(this, &userToOuterSVG
)) {
470 Matrix outerSVGToUser
= ToMatrix(userToOuterSVG
);
471 outerSVGToUser
.Invert();
472 Matrix outerSVGToBBox
= aToBBoxUserspace
* outerSVGToUser
;
473 RefPtr
<PathBuilder
> builder
=
474 pathInUserSpace
->TransformedCopyToBuilder(ToMatrix(userToOuterSVG
));
475 RefPtr
<Path
> pathInOuterSVGSpace
= builder
->Finish();
477 pathInOuterSVGSpace
->GetStrokedBounds(strokeOptions
, outerSVGToBBox
);
480 pathInUserSpace
->GetStrokedBounds(strokeOptions
, aToBBoxUserspace
);
482 MOZ_ASSERT(strokeBBoxExtents
.IsFinite(), "bbox is about to go bad");
483 bbox
.UnionEdges(strokeBBoxExtents
);
485 // For now we just use SVGUtils::PathExtentsToMaxStrokeExtents:
486 gfxRect strokeBBoxExtents
= SVGUtils::PathExtentsToMaxStrokeExtents(
487 ThebesRect(pathBBoxExtents
), this, ThebesMatrix(aToBBoxUserspace
));
488 MOZ_ASSERT(ToRect(strokeBBoxExtents
).IsFinite(),
489 "bbox is about to go bad");
490 bbox
.UnionEdges(strokeBBoxExtents
);
495 // Account for markers:
496 if ((aFlags
& SVGUtils::eBBoxIncludeMarkers
) && element
->IsMarkable()) {
497 SVGMarkerFrame
* markerFrames
[SVGMark::eTypeCount
];
498 if (SVGObserverUtils::GetAndObserveMarkers(this, &markerFrames
)) {
499 nsTArray
<SVGMark
> marks
;
500 element
->GetMarkPoints(&marks
);
501 if (uint32_t num
= marks
.Length()) {
502 float strokeWidth
= SVGUtils::GetStrokeWidth(this);
503 for (uint32_t i
= 0; i
< num
; i
++) {
504 const SVGMark
& mark
= marks
[i
];
505 SVGMarkerFrame
* frame
= markerFrames
[mark
.type
];
507 SVGBBox mbbox
= frame
->GetMarkBBoxContribution(
508 aToBBoxUserspace
, aFlags
, this, mark
, strokeWidth
);
509 MOZ_ASSERT(mbbox
.IsFinite(), "bbox is about to go bad");
510 bbox
.UnionEdges(mbbox
);
520 //----------------------------------------------------------------------
521 // SVGGeometryFrame methods:
523 gfxMatrix
SVGGeometryFrame::GetCanvasTM() {
524 NS_ASSERTION(GetParent(), "null parent");
526 auto* parent
= static_cast<SVGContainerFrame
*>(GetParent());
527 auto* content
= static_cast<SVGGraphicsElement
*>(GetContent());
529 return content
->PrependLocalTransformsTo(parent
->GetCanvasTM());
532 void SVGGeometryFrame::Render(gfxContext
* aContext
, uint32_t aRenderComponents
,
533 const gfxMatrix
& aTransform
,
534 imgDrawingParams
& aImgParams
) {
535 MOZ_ASSERT(!aTransform
.IsSingular());
537 DrawTarget
* drawTarget
= aContext
->GetDrawTarget();
539 MOZ_ASSERT(drawTarget
);
540 if (!drawTarget
->IsValid()) {
544 FillRule fillRule
= SVGUtils::ToFillRule(
545 HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD
) ? StyleSVG()->mClipRule
546 : StyleSVG()->mFillRule
);
548 SVGGeometryElement
* element
= static_cast<SVGGeometryElement
*>(GetContent());
550 AntialiasMode aaMode
=
551 (StyleSVG()->mShapeRendering
== StyleShapeRendering::Optimizespeed
||
552 StyleSVG()->mShapeRendering
== StyleShapeRendering::Crispedges
)
553 ? AntialiasMode::NONE
554 : AntialiasMode::SUBPIXEL
;
556 // We wait as late as possible before setting the transform so that we don't
557 // set it unnecessarily if we return early (it's an expensive operation for
559 gfxContextMatrixAutoSaveRestore
autoRestoreTransform(aContext
);
560 aContext
->SetMatrixDouble(aTransform
);
562 if (HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD
)) {
563 // We don't complicate this code with GetAsSimplePath since the cost of
564 // masking will dwarf Path creation overhead anyway.
565 RefPtr
<Path
> path
= element
->GetOrBuildPath(drawTarget
, fillRule
);
567 ColorPattern
white(ToDeviceColor(sRGBColor(1.0f
, 1.0f
, 1.0f
, 1.0f
)));
568 drawTarget
->Fill(path
, white
,
569 DrawOptions(1.0f
, CompositionOp::OP_OVER
, aaMode
));
574 SVGGeometryElement::SimplePath simplePath
;
577 element
->GetAsSimplePath(&simplePath
);
578 if (!simplePath
.IsPath()) {
579 path
= element
->GetOrBuildPath(drawTarget
, fillRule
);
585 SVGContextPaint
* contextPaint
=
586 SVGContextPaint::GetContextPaint(GetContent());
588 if (aRenderComponents
& eRenderFill
) {
589 GeneralPattern fillPattern
;
590 SVGUtils::MakeFillPatternFor(this, aContext
, &fillPattern
, aImgParams
,
593 if (fillPattern
.GetPattern()) {
594 DrawOptions
drawOptions(1.0f
, CompositionOp::OP_OVER
, aaMode
);
595 if (simplePath
.IsRect()) {
596 drawTarget
->FillRect(simplePath
.AsRect(), fillPattern
, drawOptions
);
598 drawTarget
->Fill(path
, fillPattern
, drawOptions
);
603 if ((aRenderComponents
& eRenderStroke
) &&
604 SVGUtils::HasStroke(this, contextPaint
)) {
605 // Account for vector-effect:non-scaling-stroke:
606 gfxMatrix userToOuterSVG
;
607 if (SVGUtils::GetNonScalingStrokeTransform(this, &userToOuterSVG
)) {
608 // A simple Rect can't be transformed with rotate/skew, so let's switch
609 // to using a real path:
611 path
= element
->GetOrBuildPath(drawTarget
, fillRule
);
617 // We need to transform the path back into the appropriate ancestor
618 // coordinate system, and paint it it that coordinate system, in order
619 // for non-scaled stroke to paint correctly.
620 gfxMatrix outerSVGToUser
= userToOuterSVG
;
621 outerSVGToUser
.Invert();
622 aContext
->Multiply(outerSVGToUser
);
623 RefPtr
<PathBuilder
> builder
=
624 path
->TransformedCopyToBuilder(ToMatrix(userToOuterSVG
), fillRule
);
625 path
= builder
->Finish();
627 GeneralPattern strokePattern
;
628 SVGUtils::MakeStrokePatternFor(this, aContext
, &strokePattern
, aImgParams
,
631 if (strokePattern
.GetPattern()) {
632 SVGContentUtils::AutoStrokeOptions strokeOptions
;
633 SVGContentUtils::GetStrokeOptions(&strokeOptions
,
634 static_cast<SVGElement
*>(GetContent()),
635 Style(), contextPaint
);
636 // GetStrokeOptions may set the line width to zero as an optimization
637 if (strokeOptions
.mLineWidth
<= 0) {
640 DrawOptions
drawOptions(1.0f
, CompositionOp::OP_OVER
, aaMode
);
641 if (simplePath
.IsRect()) {
642 drawTarget
->StrokeRect(simplePath
.AsRect(), strokePattern
,
643 strokeOptions
, drawOptions
);
644 } else if (simplePath
.IsLine()) {
645 drawTarget
->StrokeLine(simplePath
.Point1(), simplePath
.Point2(),
646 strokePattern
, strokeOptions
, drawOptions
);
648 drawTarget
->Stroke(path
, strokePattern
, strokeOptions
, drawOptions
);
654 bool SVGGeometryFrame::IsInvisible() const {
655 if (!StyleVisibility()->IsVisible()) {
659 // Anything below will round to zero later down the pipeline.
660 constexpr float opacity_threshold
= 1.0 / 128.0;
662 if (StyleEffects()->mOpacity
<= opacity_threshold
) {
666 const nsStyleSVG
* style
= StyleSVG();
667 SVGContextPaint
* contextPaint
=
668 SVGContextPaint::GetContextPaint(GetContent());
670 if (!style
->mFill
.kind
.IsNone()) {
671 float opacity
= SVGUtils::GetOpacity(style
->mFillOpacity
, contextPaint
);
672 if (opacity
> opacity_threshold
) {
677 if (!style
->mStroke
.kind
.IsNone()) {
678 float opacity
= SVGUtils::GetOpacity(style
->mStrokeOpacity
, contextPaint
);
679 if (opacity
> opacity_threshold
) {
684 if (style
->HasMarker()) {
691 bool SVGGeometryFrame::CreateWebRenderCommands(
692 mozilla::wr::DisplayListBuilder
& aBuilder
,
693 mozilla::wr::IpcResourceUpdateQueue
& aResources
,
694 const mozilla::layers::StackingContextHelper
& aSc
,
695 mozilla::layers::RenderRootStateManager
* aManager
,
696 nsDisplayListBuilder
* aDisplayListBuilder
, DisplaySVGGeometry
* aItem
,
698 if (!StyleVisibility()->IsVisible()) {
702 SVGGeometryElement
* element
= static_cast<SVGGeometryElement
*>(GetContent());
704 SVGGeometryElement::SimplePath simplePath
;
705 element
->GetAsSimplePath(&simplePath
);
707 if (!simplePath
.IsRect()) {
711 const nsStyleSVG
* style
= StyleSVG();
714 if (!style
->mFill
.kind
.IsColor()) {
718 switch (style
->mFill
.kind
.tag
) {
719 case StyleSVGPaintKind::Tag::Color
:
725 if (!style
->mStroke
.kind
.IsNone()) {
729 if (StyleEffects()->HasMixBlendMode()) {
730 // FIXME: not implemented
734 if (style
->HasMarker() && element
->IsMarkable()) {
735 // Markers aren't suppported yet.
740 auto appUnitsPerDevPx
= PresContext()->AppUnitsPerDevPixel();
741 float scale
= (float)AppUnitsPerCSSPixel() / (float)appUnitsPerDevPx
;
743 auto rect
= simplePath
.AsRect();
746 auto offset
= LayoutDevicePoint::FromAppUnits(
747 aItem
->ToReferenceFrame() - GetPosition(), appUnitsPerDevPx
);
748 rect
.MoveBy(offset
.x
, offset
.y
);
750 auto wrRect
= wr::ToLayoutRect(rect
);
752 SVGContextPaint
* contextPaint
=
753 SVGContextPaint::GetContextPaint(GetContent());
754 // At the moment this code path doesn't support strokes so it fine to
755 // combine the rectangle's opacity (which has to be applied on the result)
756 // of (filling + stroking) with the fill opacity.
757 float elemOpacity
= StyleEffects()->mOpacity
;
758 float fillOpacity
= SVGUtils::GetOpacity(style
->mFillOpacity
, contextPaint
);
759 float opacity
= elemOpacity
* fillOpacity
;
761 auto c
= nsLayoutUtils::GetColor(this, &nsStyleSVG::mFill
);
763 ((float)NS_GET_R(c
)) / 255.0f
, ((float)NS_GET_G(c
)) / 255.0f
,
764 ((float)NS_GET_B(c
)) / 255.0f
, ((float)NS_GET_A(c
)) / 255.0f
* opacity
};
766 aBuilder
.PushRect(wrRect
, wrRect
, !aItem
->BackfaceIsHidden(), true, false,
773 void SVGGeometryFrame::PaintMarkers(gfxContext
& aContext
,
774 const gfxMatrix
& aTransform
,
775 imgDrawingParams
& aImgParams
) {
776 auto* element
= static_cast<SVGGeometryElement
*>(GetContent());
777 if (!element
->IsMarkable()) {
780 SVGMarkerFrame
* markerFrames
[SVGMark::eTypeCount
];
781 if (!SVGObserverUtils::GetAndObserveMarkers(this, &markerFrames
)) {
784 nsTArray
<SVGMark
> marks
;
785 element
->GetMarkPoints(&marks
);
786 if (marks
.IsEmpty()) {
789 float strokeWidth
= GetStrokeWidthForMarkers();
790 for (const SVGMark
& mark
: marks
) {
791 if (auto* frame
= markerFrames
[mark
.type
]) {
792 frame
->PaintMark(aContext
, aTransform
, this, mark
, strokeWidth
,
798 float SVGGeometryFrame::GetStrokeWidthForMarkers() {
799 float strokeWidth
= SVGUtils::GetStrokeWidth(
800 this, SVGContextPaint::GetContextPaint(GetContent()));
801 gfxMatrix userToOuterSVG
;
802 if (SVGUtils::GetNonScalingStrokeTransform(this, &userToOuterSVG
)) {
803 // We're not interested in any translation here so we can treat this as
804 // Singular Value Decomposition (SVD) of a 2 x 2 matrix. That would give us
805 // sx and sy values as the X and Y scales. The value we want is the XY
806 // scale i.e. the normalised hypotenuse, which is sqrt(sx^2 + sy^2) /
807 // sqrt(2). If we use the formulae from
808 // https://scicomp.stackexchange.com/a/14103, we discover that the
809 // normalised hypotenuse is simply the square root of the sum of the squares
810 // of all the 2D matrix elements divided by sqrt(2).
812 // Note that this may need adjusting to support 3D transforms properly.
814 strokeWidth
/= float(sqrt(userToOuterSVG
._11
* userToOuterSVG
._11
+
815 userToOuterSVG
._12
* userToOuterSVG
._12
+
816 userToOuterSVG
._21
* userToOuterSVG
._21
+
817 userToOuterSVG
._22
* userToOuterSVG
._22
) /
823 } // namespace mozilla