Backed out changeset 36e95068e103 (bug 1848160) for bc failures on browser_preference...
[gecko.git] / layout / svg / SVGGeometryFrame.cpp
blob7d0bd7cc4c304384c19e83aea4f0cfffc7f460db
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/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 //----------------------------------------------------------------------
36 // Implementation
38 nsIFrame* NS_NewSVGGeometryFrame(mozilla::PresShell* aPresShell,
39 mozilla::ComputedStyle* aStyle) {
40 return new (aPresShell)
41 mozilla::SVGGeometryFrame(aStyle, aPresShell->GetPresContext());
44 namespace mozilla {
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 //----------------------------------------------------------------------
57 // nsIFrame methods
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,
66 nsAtom* aAttribute,
67 int32_t aModType) {
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);
80 return NS_OK;
83 /* virtual */
84 void SVGGeometryFrame::DidSetComputedStyle(ComputedStyle* aOldComputedStyle) {
85 nsIFrame::DidSetComputedStyle(aOldComputedStyle);
86 auto* element = static_cast<SVGGeometryElement*>(GetContent());
87 if (!aOldComputedStyle) {
88 element->ClearAnyCachedPath();
89 return;
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();
107 } else {
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()) {
128 return;
131 if (aBuilder->IsForPainting()) {
132 if (!IsVisibleForPainting()) {
133 return;
135 if (StyleEffects()->IsTransparent()) {
136 return;
138 const auto* styleSVG = StyleSVG();
139 if (styleSVG->mFill.kind.IsNone() && styleSVG->mStroke.kind.IsNone() &&
140 !styleSVG->HasMarker()) {
141 return;
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()) {
159 return;
162 // Matrix to the geometry's user space:
163 gfxMatrix newMatrix =
164 aContext.CurrentMatrixDouble().PreMultiply(aTransform).NudgeToIntegers();
165 if (newMatrix.IsSingular()) {
166 return;
169 uint32_t paintOrder = StyleSVG()->mPaintOrder;
170 if (!paintOrder) {
171 Render(&aContext, eRenderFill | eRenderStroke, newMatrix, aImgParams);
172 PaintMarkers(aContext, aTransform, aImgParams);
173 } else {
174 while (paintOrder) {
175 auto component = StylePaintOrder(paintOrder & kPaintOrderMask);
176 switch (component) {
177 case StylePaintOrder::Fill:
178 Render(&aContext, eRenderFill, newMatrix, aImgParams);
179 break;
180 case StylePaintOrder::Stroke:
181 Render(&aContext, eRenderStroke, newMatrix, aImgParams);
182 break;
183 case StylePaintOrder::Markers:
184 PaintMarkers(aContext, aTransform, aImgParams);
185 break;
186 default:
187 MOZ_FALLTHROUGH_ASSERT("Unknown paint-order variant, how?");
188 case StylePaintOrder::Normal:
189 break;
191 paintOrder >>= kPaintOrderShift;
196 nsIFrame* SVGGeometryFrame::GetFrameForPoint(const gfxPoint& aPoint) {
197 FillRule fillRule;
198 uint16_t hitTestFlags;
199 if (HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD)) {
200 hitTestFlags = SVG_HIT_TEST_FILL;
201 fillRule = SVGUtils::ToFillRule(StyleSVG()->mClipRule);
202 } else {
203 hitTestFlags = SVGUtils::GetGeometryHitTestFlags(this);
204 if (!hitTestFlags) {
205 return nullptr;
207 fillRule = SVGUtils::ToFillRule(StyleSVG()->mFillRule);
210 bool isHit = false;
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);
220 if (!path) {
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)) {
246 return this;
249 return nullptr;
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)) {
260 return;
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
270 // 'pointer-events'.
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)) {
299 InvalidateFrame();
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
326 // mRect.
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,
344 uint32_t aFlags) {
345 SVGBBox bbox;
347 if (aToBBoxUserspace.IsSingular()) {
348 // XXX ReportToConsole
349 return bbox;
352 if ((aFlags & SVGUtils::eForGetClientRects) &&
353 aToBBoxUserspace.PreservesAxisAlignedRectangles()) {
354 Rect rect = NSRectToRect(mRect, AppUnitsPerCSSPixel());
355 bbox = aToBBoxUserspace.TransformBounds(rect);
356 return bbox;
359 SVGGeometryElement* element = static_cast<SVGGeometryElement*>(GetContent());
361 bool getFill = (aFlags & SVGUtils::eBBoxIncludeFillGeometry) ||
362 ((aFlags & SVGUtils::eBBoxIncludeFill) &&
363 !StyleSVG()->mFill.kind.IsNone());
365 bool getStroke =
366 (aFlags & SVGUtils::eBBoxIncludeStrokeGeometry) ||
367 ((aFlags & SVGUtils::eBBoxIncludeStroke) && SVGUtils::HasStroke(this));
369 SVGContentUtils::AutoStrokeOptions strokeOptions;
370 if (getStroke) {
371 SVGContentUtils::GetStrokeOptions(&strokeOptions, element, Style(), nullptr,
372 SVGContentUtils::eIgnoreStrokeDashing);
373 } else {
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;
379 Rect simpleBounds;
380 bool gotSimpleBounds = false;
381 gfxMatrix userToOuterSVG;
382 if (getStroke &&
383 SVGUtils::GetNonScalingStrokeTransform(this, &userToOuterSVG)) {
384 Matrix moz2dUserToOuterSVG = ToMatrix(userToOuterSVG);
385 if (moz2dUserToOuterSVG.IsSingular()) {
386 return bbox;
388 gotSimpleBounds = element->GetGeometryBounds(
389 &simpleBounds, strokeOptions, aToBBoxUserspace, &moz2dUserToOuterSVG);
390 } else {
391 gotSimpleBounds = element->GetGeometryBounds(&simpleBounds, strokeOptions,
392 aToBBoxUserspace);
395 if (gotSimpleBounds) {
396 bbox = simpleBounds;
397 } else {
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) {
407 return bbox;
409 RefPtr<Path> pathInBBoxSpace;
410 if (aToBBoxUserspace.IsIdentity()) {
411 pathInBBoxSpace = pathInUserSpace;
412 } else {
413 RefPtr<PathBuilder> builder =
414 pathInUserSpace->TransformedCopyToBuilder(aToBBoxUserspace, fillRule);
415 pathInBBoxSpace = builder->Finish();
416 if (!pathInBBoxSpace) {
417 return bbox;
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.
437 return bbox;
440 // Account for fill:
441 if (getFill) {
442 bbox = pathBBoxExtents;
445 // Account for stroke:
446 if (getStroke) {
447 #if 0
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,
465 Style(), nullptr,
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();
476 strokeBBoxExtents =
477 pathInOuterSVGSpace->GetStrokedBounds(strokeOptions, outerSVGToBBox);
478 } else {
479 strokeBBoxExtents =
480 pathInUserSpace->GetStrokedBounds(strokeOptions, aToBBoxUserspace);
482 MOZ_ASSERT(strokeBBoxExtents.IsFinite(), "bbox is about to go bad");
483 bbox.UnionEdges(strokeBBoxExtents);
484 #else
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);
491 #endif
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];
506 if (frame) {
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);
517 return bbox;
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()) {
541 return;
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
558 // some backends).
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);
566 if (path) {
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));
571 return;
574 SVGGeometryElement::SimplePath simplePath;
575 RefPtr<Path> path;
577 element->GetAsSimplePath(&simplePath);
578 if (!simplePath.IsPath()) {
579 path = element->GetOrBuildPath(drawTarget, fillRule);
580 if (!path) {
581 return;
585 SVGContextPaint* contextPaint =
586 SVGContextPaint::GetContextPaint(GetContent());
588 if (aRenderComponents & eRenderFill) {
589 GeneralPattern fillPattern;
590 SVGUtils::MakeFillPatternFor(this, aContext, &fillPattern, aImgParams,
591 contextPaint);
593 if (fillPattern.GetPattern()) {
594 DrawOptions drawOptions(1.0f, CompositionOp::OP_OVER, aaMode);
595 if (simplePath.IsRect()) {
596 drawTarget->FillRect(simplePath.AsRect(), fillPattern, drawOptions);
597 } else if (path) {
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:
610 if (!path) {
611 path = element->GetOrBuildPath(drawTarget, fillRule);
612 if (!path) {
613 return;
615 simplePath.Reset();
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,
629 contextPaint);
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) {
638 return;
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);
647 } else {
648 drawTarget->Stroke(path, strokePattern, strokeOptions, drawOptions);
654 bool SVGGeometryFrame::IsInvisible() const {
655 if (!StyleVisibility()->IsVisible()) {
656 return true;
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) {
663 return true;
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) {
673 return false;
677 if (!style->mStroke.kind.IsNone()) {
678 float opacity = SVGUtils::GetOpacity(style->mStrokeOpacity, contextPaint);
679 if (opacity > opacity_threshold) {
680 return false;
684 if (style->HasMarker()) {
685 return false;
688 return true;
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,
697 bool aDryRun) {
698 if (!StyleVisibility()->IsVisible()) {
699 return true;
702 SVGGeometryElement* element = static_cast<SVGGeometryElement*>(GetContent());
704 SVGGeometryElement::SimplePath simplePath;
705 element->GetAsSimplePath(&simplePath);
707 if (!simplePath.IsRect()) {
708 return false;
711 const nsStyleSVG* style = StyleSVG();
712 MOZ_ASSERT(style);
714 if (!style->mFill.kind.IsColor()) {
715 return false;
718 switch (style->mFill.kind.tag) {
719 case StyleSVGPaintKind::Tag::Color:
720 break;
721 default:
722 return false;
725 if (!style->mStroke.kind.IsNone()) {
726 return false;
729 if (StyleEffects()->HasMixBlendMode()) {
730 // FIXME: not implemented
731 return false;
734 if (style->HasMarker() && element->IsMarkable()) {
735 // Markers aren't suppported yet.
736 return false;
739 if (!aDryRun) {
740 auto appUnitsPerDevPx = PresContext()->AppUnitsPerDevPixel();
741 float scale = (float)AppUnitsPerCSSPixel() / (float)appUnitsPerDevPx;
743 auto rect = simplePath.AsRect();
744 rect.Scale(scale);
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);
762 wr::ColorF color{
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,
767 color);
770 return true;
773 void SVGGeometryFrame::PaintMarkers(gfxContext& aContext,
774 const gfxMatrix& aTransform,
775 imgDrawingParams& aImgParams) {
776 auto* element = static_cast<SVGGeometryElement*>(GetContent());
777 if (!element->IsMarkable()) {
778 return;
780 SVGMarkerFrame* markerFrames[SVGMark::eTypeCount];
781 if (!SVGObserverUtils::GetAndObserveMarkers(this, &markerFrames)) {
782 return;
784 nsTArray<SVGMark> marks;
785 element->GetMarkPoints(&marks);
786 if (marks.IsEmpty()) {
787 return;
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,
793 aImgParams);
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) /
818 M_SQRT2);
820 return strokeWidth;
823 } // namespace mozilla