Backed out 2 changesets (bug 1733498) for causing perma Localised repacks build busta...
[gecko.git] / layout / svg / SVGGeometryFrame.cpp
blob6d8fd34be03de9a4aedc907f91d8706e0544d6a3
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/. */
7 // Main header first:
8 #include "SVGGeometryFrame.h"
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/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 //----------------------------------------------------------------------
37 // Implementation
39 nsIFrame* NS_NewSVGGeometryFrame(mozilla::PresShell* aPresShell,
40 mozilla::ComputedStyle* aStyle) {
41 return new (aPresShell)
42 mozilla::SVGGeometryFrame(aStyle, aPresShell->GetPresContext());
45 namespace mozilla {
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 //----------------------------------------------------------------------
58 // nsIFrame methods
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,
67 nsAtom* aAttribute,
68 int32_t aModType) {
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);
81 return NS_OK;
84 /* virtual */
85 void SVGGeometryFrame::DidSetComputedStyle(ComputedStyle* aOldComputedStyle) {
86 nsIFrame::DidSetComputedStyle(aOldComputedStyle);
87 auto* element = static_cast<SVGGeometryElement*>(GetContent());
88 if (!aOldComputedStyle) {
89 element->ClearAnyCachedPath();
90 return;
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()) {
133 return;
136 if (aBuilder->IsForPainting()) {
137 if (!IsVisibleForPainting()) {
138 return;
140 if (StyleEffects()->IsTransparent() && SVGUtils::CanOptimizeOpacity(this)) {
141 return;
143 const auto* styleSVG = StyleSVG();
144 if (styleSVG->mFill.kind.IsNone() && styleSVG->mStroke.kind.IsNone() &&
145 !styleSVG->HasMarker()) {
146 return;
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()) {
164 return;
167 // Matrix to the geometry's user space:
168 gfxMatrix newMatrix =
169 aContext.CurrentMatrixDouble().PreMultiply(aTransform).NudgeToIntegers();
170 if (newMatrix.IsSingular()) {
171 return;
174 uint32_t paintOrder = StyleSVG()->mPaintOrder;
175 if (!paintOrder) {
176 Render(&aContext, eRenderFill | eRenderStroke, newMatrix, aImgParams);
177 PaintMarkers(aContext, aTransform, aImgParams);
178 } else {
179 while (paintOrder) {
180 auto component = StylePaintOrder(paintOrder & kPaintOrderMask);
181 switch (component) {
182 case StylePaintOrder::Fill:
183 Render(&aContext, eRenderFill, newMatrix, aImgParams);
184 break;
185 case StylePaintOrder::Stroke:
186 Render(&aContext, eRenderStroke, newMatrix, aImgParams);
187 break;
188 case StylePaintOrder::Markers:
189 PaintMarkers(aContext, aTransform, aImgParams);
190 break;
191 default:
192 MOZ_FALLTHROUGH_ASSERT("Unknown paint-order variant, how?");
193 case StylePaintOrder::Normal:
194 break;
196 paintOrder >>= kPaintOrderShift;
201 nsIFrame* SVGGeometryFrame::GetFrameForPoint(const gfxPoint& aPoint) {
202 FillRule fillRule;
203 uint16_t hitTestFlags;
204 if (HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD)) {
205 hitTestFlags = SVG_HIT_TEST_FILL;
206 fillRule = SVGUtils::ToFillRule(StyleSVG()->mClipRule);
207 } else {
208 hitTestFlags = SVGUtils::GetGeometryHitTestFlags(this);
209 if (!hitTestFlags) {
210 return nullptr;
212 fillRule = SVGUtils::ToFillRule(StyleSVG()->mFillRule);
215 bool isHit = false;
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);
225 if (!path) {
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)) {
249 return this;
252 return nullptr;
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)) {
263 return;
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
273 // 'pointer-events'.
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)) {
302 InvalidateFrame();
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
329 // mRect.
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,
347 uint32_t aFlags) {
348 SVGBBox bbox;
350 if (aToBBoxUserspace.IsSingular()) {
351 // XXX ReportToConsole
352 return bbox;
355 if ((aFlags & SVGUtils::eForGetClientRects) &&
356 aToBBoxUserspace.PreservesAxisAlignedRectangles()) {
357 Rect rect = NSRectToRect(mRect, AppUnitsPerCSSPixel());
358 bbox = aToBBoxUserspace.TransformBounds(rect);
359 return bbox;
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
378 // Note:
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;
391 if (getStroke) {
392 SVGContentUtils::GetStrokeOptions(&strokeOptions, element, Style(), nullptr,
393 SVGContentUtils::eIgnoreStrokeDashing);
394 } else {
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;
400 Rect simpleBounds;
401 bool gotSimpleBounds = false;
402 gfxMatrix userToOuterSVG;
403 if (getStroke &&
404 SVGUtils::GetNonScalingStrokeTransform(this, &userToOuterSVG)) {
405 Matrix moz2dUserToOuterSVG = ToMatrix(userToOuterSVG);
406 if (moz2dUserToOuterSVG.IsSingular()) {
407 return bbox;
409 gotSimpleBounds = element->GetGeometryBounds(
410 &simpleBounds, strokeOptions, aToBBoxUserspace, &moz2dUserToOuterSVG);
411 } else {
412 gotSimpleBounds = element->GetGeometryBounds(&simpleBounds, strokeOptions,
413 aToBBoxUserspace);
416 if (gotSimpleBounds) {
417 bbox = simpleBounds;
418 } else {
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) {
428 return bbox;
430 RefPtr<Path> pathInBBoxSpace;
431 if (aToBBoxUserspace.IsIdentity()) {
432 pathInBBoxSpace = pathInUserSpace;
433 } else {
434 RefPtr<PathBuilder> builder =
435 pathInUserSpace->TransformedCopyToBuilder(aToBBoxUserspace, fillRule);
436 pathInBBoxSpace = builder->Finish();
437 if (!pathInBBoxSpace) {
438 return bbox;
442 // Account for fill:
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.
448 return bbox;
450 bbox = pathBBoxExtents;
453 // Account for stroke:
454 if (getStroke) {
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);
480 } else {
481 strokeBBoxExtents = pathInUserSpace->GetStrokedBounds(
482 strokeOptions, aToBBoxUserspace);
484 if (strokeBBoxExtents.IsEmpty() && getFill) {
485 strokeBBoxExtents = pathInBBoxSpace->GetBounds();
486 if (!strokeBBoxExtents.IsFinite()) {
487 return bbox;
490 } else {
491 Rect pathBBoxExtents = pathInBBoxSpace->GetBounds();
492 if (!pathBBoxExtents.IsFinite()) {
493 return bbox;
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];
514 if (frame) {
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);
525 return bbox;
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()) {
548 return;
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
561 // some backends).
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);
569 if (path) {
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));
574 return;
577 SVGGeometryElement::SimplePath simplePath;
578 RefPtr<Path> path;
580 element->GetAsSimplePath(&simplePath);
581 if (!simplePath.IsPath()) {
582 path = element->GetOrBuildPath(drawTarget, fillRule);
583 if (!path) {
584 return;
588 SVGContextPaint* contextPaint =
589 SVGContextPaint::GetContextPaint(GetContent());
591 if (aRenderComponents & eRenderFill) {
592 GeneralPattern fillPattern;
593 SVGUtils::MakeFillPatternFor(this, aContext, &fillPattern, aImgParams,
594 contextPaint);
596 if (fillPattern.GetPattern()) {
597 DrawOptions drawOptions(1.0f, CompositionOp::OP_OVER, aaMode);
598 if (simplePath.IsRect()) {
599 drawTarget->FillRect(simplePath.AsRect(), fillPattern, drawOptions);
600 } else if (path) {
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:
613 if (!path) {
614 path = element->GetOrBuildPath(drawTarget, fillRule);
615 if (!path) {
616 return;
618 simplePath.Reset();
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,
630 contextPaint);
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) {
639 return;
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);
648 } else {
649 drawTarget->Stroke(path, strokePattern, strokeOptions, drawOptions);
655 bool SVGGeometryFrame::IsInvisible() const {
656 if (!StyleVisibility()->IsVisible()) {
657 return true;
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)) {
665 return true;
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) {
675 return false;
679 if (!style->mStroke.kind.IsNone()) {
680 float opacity = SVGUtils::GetOpacity(style->mStrokeOpacity, contextPaint);
681 if (opacity > opacity_threshold) {
682 return false;
686 if (style->HasMarker()) {
687 return false;
690 return true;
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,
699 bool aDryRun) {
700 if (!StyleVisibility()->IsVisible()) {
701 return true;
704 SVGGeometryElement* element = static_cast<SVGGeometryElement*>(GetContent());
706 SVGGeometryElement::SimplePath simplePath;
707 element->GetAsSimplePath(&simplePath);
709 if (!simplePath.IsRect()) {
710 return false;
713 const nsStyleSVG* style = StyleSVG();
714 MOZ_ASSERT(style);
716 if (!style->mFill.kind.IsColor()) {
717 return false;
720 switch (style->mFill.kind.tag) {
721 case StyleSVGPaintKind::Tag::Color:
722 break;
723 default:
724 return false;
727 if (!style->mStroke.kind.IsNone()) {
728 return false;
731 if (StyleEffects()->HasMixBlendMode()) {
732 // FIXME: not implemented
733 return false;
736 if (style->HasMarker() && element->IsMarkable()) {
737 // Markers aren't suppported yet.
738 return false;
741 if (!aDryRun) {
742 auto appUnitsPerDevPx = PresContext()->AppUnitsPerDevPixel();
743 float scale = (float)AppUnitsPerCSSPixel() / (float)appUnitsPerDevPx;
745 auto rect = simplePath.AsRect();
746 rect.Scale(scale);
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)));
770 color.a *= opacity;
771 aBuilder.PushRect(wrRect, wrRect, !aItem->BackfaceIsHidden(), true, false,
772 color);
775 return true;
778 void SVGGeometryFrame::PaintMarkers(gfxContext& aContext,
779 const gfxMatrix& aTransform,
780 imgDrawingParams& aImgParams) {
781 auto* element = static_cast<SVGGeometryElement*>(GetContent());
782 if (!element->IsMarkable()) {
783 return;
785 SVGMarkerFrame* markerFrames[SVGMark::eTypeCount];
786 if (!SVGObserverUtils::GetAndObserveMarkers(this, &markerFrames)) {
787 return;
789 nsTArray<SVGMark> marks;
790 element->GetMarkPoints(&marks);
791 if (marks.IsEmpty()) {
792 return;
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,
798 aImgParams);
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) /
823 M_SQRT2);
825 return strokeWidth;
828 } // namespace mozilla