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