summaryrefslogtreecommitdiffstats
path: root/layout/svg/nsSVGPathGeometryFrame.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'layout/svg/nsSVGPathGeometryFrame.cpp')
-rw-r--r--layout/svg/nsSVGPathGeometryFrame.cpp928
1 files changed, 928 insertions, 0 deletions
diff --git a/layout/svg/nsSVGPathGeometryFrame.cpp b/layout/svg/nsSVGPathGeometryFrame.cpp
new file mode 100644
index 000000000..32cd0e496
--- /dev/null
+++ b/layout/svg/nsSVGPathGeometryFrame.cpp
@@ -0,0 +1,928 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Main header first:
+#include "nsSVGPathGeometryFrame.h"
+
+// Keep others in (case-insensitive) order:
+#include "gfx2DGlue.h"
+#include "gfxContext.h"
+#include "gfxPlatform.h"
+#include "gfxUtils.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/Helpers.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/SVGContextPaint.h"
+#include "nsDisplayList.h"
+#include "nsGkAtoms.h"
+#include "nsLayoutUtils.h"
+#include "nsRenderingContext.h"
+#include "nsSVGEffects.h"
+#include "nsSVGIntegrationUtils.h"
+#include "nsSVGMarkerFrame.h"
+#include "nsSVGPathGeometryElement.h"
+#include "nsSVGUtils.h"
+#include "mozilla/ArrayUtils.h"
+#include "SVGAnimatedTransformList.h"
+#include "SVGContentUtils.h"
+#include "SVGGraphicsElement.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using namespace mozilla::image;
+
+//----------------------------------------------------------------------
+// Implementation
+
+nsIFrame*
+NS_NewSVGPathGeometryFrame(nsIPresShell* aPresShell,
+ nsStyleContext* aContext)
+{
+ return new (aPresShell) nsSVGPathGeometryFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsSVGPathGeometryFrame)
+
+//----------------------------------------------------------------------
+// nsQueryFrame methods
+
+NS_QUERYFRAME_HEAD(nsSVGPathGeometryFrame)
+ NS_QUERYFRAME_ENTRY(nsISVGChildFrame)
+ NS_QUERYFRAME_ENTRY(nsSVGPathGeometryFrame)
+NS_QUERYFRAME_TAIL_INHERITING(nsFrame)
+
+//----------------------------------------------------------------------
+// Display list item:
+
+class nsDisplaySVGPathGeometry : public nsDisplayItem {
+public:
+ nsDisplaySVGPathGeometry(nsDisplayListBuilder* aBuilder,
+ nsSVGPathGeometryFrame* aFrame)
+ : nsDisplayItem(aBuilder, aFrame)
+ {
+ MOZ_COUNT_CTOR(nsDisplaySVGPathGeometry);
+ MOZ_ASSERT(aFrame, "Must have a frame!");
+ }
+#ifdef NS_BUILD_REFCNT_LOGGING
+ virtual ~nsDisplaySVGPathGeometry() {
+ MOZ_COUNT_DTOR(nsDisplaySVGPathGeometry);
+ }
+#endif
+
+ NS_DISPLAY_DECL_NAME("nsDisplaySVGPathGeometry", TYPE_SVG_PATH_GEOMETRY)
+
+ virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
+ HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames) override;
+ virtual void Paint(nsDisplayListBuilder* aBuilder,
+ nsRenderingContext* aCtx) override;
+
+ nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) override
+ {
+ return new nsDisplayItemGenericImageGeometry(this, aBuilder);
+ }
+
+ void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
+ const nsDisplayItemGeometry* aGeometry,
+ nsRegion *aInvalidRegion) override;
+};
+
+void
+nsDisplaySVGPathGeometry::HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
+ HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames)
+{
+ nsSVGPathGeometryFrame *frame = static_cast<nsSVGPathGeometryFrame*>(mFrame);
+ nsPoint pointRelativeToReferenceFrame = aRect.Center();
+ // ToReferenceFrame() includes frame->GetPosition(), our user space position.
+ nsPoint userSpacePtInAppUnits = pointRelativeToReferenceFrame -
+ (ToReferenceFrame() - frame->GetPosition());
+ gfxPoint userSpacePt =
+ gfxPoint(userSpacePtInAppUnits.x, userSpacePtInAppUnits.y) /
+ frame->PresContext()->AppUnitsPerCSSPixel();
+ if (frame->GetFrameForPoint(userSpacePt)) {
+ aOutFrames->AppendElement(frame);
+ }
+}
+
+void
+nsDisplaySVGPathGeometry::Paint(nsDisplayListBuilder* aBuilder,
+ nsRenderingContext* aCtx)
+{
+ uint32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
+
+ // ToReferenceFrame includes our mRect offset, but painting takes
+ // account of that too. To avoid double counting, we subtract that
+ // here.
+ nsPoint offset = ToReferenceFrame() - mFrame->GetPosition();
+
+ gfxPoint devPixelOffset =
+ nsLayoutUtils::PointToGfxPoint(offset, appUnitsPerDevPixel);
+
+ gfxMatrix tm = nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(mFrame) *
+ gfxMatrix::Translation(devPixelOffset);
+ DrawResult result =
+ static_cast<nsSVGPathGeometryFrame*>(mFrame)->PaintSVG(*aCtx->ThebesContext(), tm);
+
+ nsDisplayItemGenericImageGeometry::UpdateDrawResult(this, result);
+}
+
+void
+nsDisplaySVGPathGeometry::ComputeInvalidationRegion(
+ nsDisplayListBuilder* aBuilder,
+ const nsDisplayItemGeometry* aGeometry,
+ nsRegion* aInvalidRegion)
+{
+ auto geometry =
+ static_cast<const nsDisplayItemGenericImageGeometry*>(aGeometry);
+
+ if (aBuilder->ShouldSyncDecodeImages() &&
+ geometry->ShouldInvalidateToSyncDecodeImages()) {
+ bool snap;
+ aInvalidRegion->Or(*aInvalidRegion, GetBounds(aBuilder, &snap));
+ }
+
+ nsDisplayItem::ComputeInvalidationRegion(aBuilder, aGeometry, aInvalidRegion);
+}
+
+//----------------------------------------------------------------------
+// nsIFrame methods
+
+void
+nsSVGPathGeometryFrame::Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow)
+{
+ AddStateBits(aParent->GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD);
+ nsFrame::Init(aContent, aParent, aPrevInFlow);
+}
+
+nsresult
+nsSVGPathGeometryFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType)
+{
+ // We don't invalidate for transform changes (the layers code does that).
+ // Also note that SVGTransformableElement::GetAttributeChangeHint will
+ // return nsChangeHint_UpdateOverflow for "transform" attribute changes
+ // and cause DoApplyRenderingChangeToTree to make the SchedulePaint call.
+
+ if (aNameSpaceID == kNameSpaceID_None &&
+ (static_cast<nsSVGPathGeometryElement*>
+ (mContent)->AttributeDefinesGeometry(aAttribute))) {
+ nsLayoutUtils::PostRestyleEvent(
+ mContent->AsElement(), nsRestyleHint(0),
+ nsChangeHint_InvalidateRenderingObservers);
+ nsSVGUtils::ScheduleReflowSVG(this);
+ }
+ return NS_OK;
+}
+
+/* virtual */ void
+nsSVGPathGeometryFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext)
+{
+ nsFrame::DidSetStyleContext(aOldStyleContext);
+
+ if (aOldStyleContext) {
+ auto oldStyleEffects = aOldStyleContext->PeekStyleEffects();
+ if (oldStyleEffects &&
+ StyleEffects()->mOpacity != oldStyleEffects->mOpacity &&
+ nsSVGUtils::CanOptimizeOpacity(this)) {
+ // nsIFrame::BuildDisplayListForStackingContext() is not going to create an
+ // nsDisplayOpacity display list item, so DLBI won't invalidate for us.
+ InvalidateFrame();
+ }
+
+ nsSVGPathGeometryElement* element =
+ static_cast<nsSVGPathGeometryElement*>(mContent);
+
+ auto oldStyleSVG = aOldStyleContext->PeekStyleSVG();
+ if (oldStyleSVG && !SVGContentUtils::ShapeTypeHasNoCorners(mContent)) {
+ if (StyleSVG()->mStrokeLinecap != oldStyleSVG->mStrokeLinecap &&
+ element->IsSVGElement(nsGkAtoms::path)) {
+ // If the stroke-linecap changes to or from "butt" then our element
+ // needs to update its cached Moz2D Path, since SVGPathData::BuildPath
+ // decides whether or not to insert little lines into the path for zero
+ // length subpaths base on that property.
+ element->ClearAnyCachedPath();
+ } else if (GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD) {
+ if (StyleSVG()->mClipRule != oldStyleSVG->mClipRule) {
+ // Moz2D Path objects are fill-rule specific.
+ // For clipPath we use clip-rule as the path's fill-rule.
+ element->ClearAnyCachedPath();
+ }
+ } else {
+ if (StyleSVG()->mFillRule != oldStyleSVG->mFillRule) {
+ // Moz2D Path objects are fill-rule specific.
+ element->ClearAnyCachedPath();
+ }
+ }
+ }
+ }
+}
+
+nsIAtom *
+nsSVGPathGeometryFrame::GetType() const
+{
+ return nsGkAtoms::svgPathGeometryFrame;
+}
+
+bool
+nsSVGPathGeometryFrame::IsSVGTransformed(gfx::Matrix *aOwnTransform,
+ gfx::Matrix *aFromParentTransform) const
+{
+ bool foundTransform = false;
+
+ // Check if our parent has children-only transforms:
+ nsIFrame *parent = GetParent();
+ if (parent &&
+ parent->IsFrameOfType(nsIFrame::eSVG | nsIFrame::eSVGContainer)) {
+ foundTransform = static_cast<nsSVGContainerFrame*>(parent)->
+ HasChildrenOnlyTransform(aFromParentTransform);
+ }
+
+ nsSVGElement *content = static_cast<nsSVGElement*>(mContent);
+ nsSVGAnimatedTransformList* transformList =
+ content->GetAnimatedTransformList();
+ if ((transformList && transformList->HasTransform()) ||
+ content->GetAnimateMotionTransform()) {
+ if (aOwnTransform) {
+ *aOwnTransform = gfx::ToMatrix(
+ content->PrependLocalTransformsTo(
+ gfxMatrix(),
+ eUserSpaceToParent));
+ }
+ foundTransform = true;
+ }
+ return foundTransform;
+}
+
+void
+nsSVGPathGeometryFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists)
+{
+ if (!static_cast<const nsSVGElement*>(mContent)->HasValidDimensions() ||
+ (!IsVisibleForPainting(aBuilder) && aBuilder->IsForPainting())) {
+ return;
+ }
+ DisplayOutline(aBuilder, aLists);
+ aLists.Content()->AppendNewToTop(
+ new (aBuilder) nsDisplaySVGPathGeometry(aBuilder, this));
+}
+
+//----------------------------------------------------------------------
+// nsISVGChildFrame methods
+
+DrawResult
+nsSVGPathGeometryFrame::PaintSVG(gfxContext& aContext,
+ const gfxMatrix& aTransform,
+ const nsIntRect* aDirtyRect)
+{
+ if (!StyleVisibility()->IsVisible())
+ return DrawResult::SUCCESS;
+
+ gfxMatrix childToUserSpace = aTransform;
+ if (GetContent()->IsSVGElement()) {
+ childToUserSpace = static_cast<const nsSVGElement*>(GetContent())->
+ PrependLocalTransformsTo(childToUserSpace,
+ eChildToUserSpace);
+ }
+
+ // Matrix to the geometry's user space:
+ gfxMatrix newMatrix =
+ aContext.CurrentMatrix().PreMultiply(childToUserSpace).NudgeToIntegers();
+ if (newMatrix.IsSingular()) {
+ return DrawResult::BAD_ARGS;
+ }
+
+ uint32_t paintOrder = StyleSVG()->mPaintOrder;
+ if (paintOrder == NS_STYLE_PAINT_ORDER_NORMAL) {
+ Render(&aContext, eRenderFill | eRenderStroke, newMatrix);
+ PaintMarkers(aContext, aTransform);
+ } else {
+ while (paintOrder) {
+ uint32_t component =
+ paintOrder & ((1 << NS_STYLE_PAINT_ORDER_BITWIDTH) - 1);
+ switch (component) {
+ case NS_STYLE_PAINT_ORDER_FILL:
+ Render(&aContext, eRenderFill, newMatrix);
+ break;
+ case NS_STYLE_PAINT_ORDER_STROKE:
+ Render(&aContext, eRenderStroke, newMatrix);
+ break;
+ case NS_STYLE_PAINT_ORDER_MARKERS:
+ PaintMarkers(aContext, aTransform);
+ break;
+ }
+ paintOrder >>= NS_STYLE_PAINT_ORDER_BITWIDTH;
+ }
+ }
+
+ return DrawResult::SUCCESS;
+}
+
+nsIFrame*
+nsSVGPathGeometryFrame::GetFrameForPoint(const gfxPoint& aPoint)
+{
+ FillRule fillRule;
+ uint16_t hitTestFlags;
+ if (GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD) {
+ hitTestFlags = SVG_HIT_TEST_FILL;
+ fillRule = nsSVGUtils::ToFillRule(StyleSVG()->mClipRule);
+ } else {
+ hitTestFlags = GetHitTestFlags();
+ if (!hitTestFlags) {
+ return nullptr;
+ }
+ if (hitTestFlags & SVG_HIT_TEST_CHECK_MRECT) {
+ gfxRect rect =
+ nsLayoutUtils::RectToGfxRect(mRect, PresContext()->AppUnitsPerCSSPixel());
+ if (!rect.Contains(aPoint)) {
+ return nullptr;
+ }
+ }
+ fillRule = nsSVGUtils::ToFillRule(StyleSVG()->mFillRule);
+ }
+
+ bool isHit = false;
+
+ nsSVGPathGeometryElement* content =
+ static_cast<nsSVGPathGeometryElement*>(mContent);
+
+ // Using ScreenReferenceDrawTarget() opens us to Moz2D backend specific hit-
+ // testing bugs. Maybe we should use a BackendType::CAIRO DT for hit-testing
+ // so that we get more consistent/backwards compatible results?
+ RefPtr<DrawTarget> drawTarget =
+ gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
+ RefPtr<Path> path = content->GetOrBuildPath(*drawTarget, fillRule);
+ if (!path) {
+ return nullptr; // no path, so we don't paint anything that can be hit
+ }
+
+ if (hitTestFlags & SVG_HIT_TEST_FILL) {
+ isHit = path->ContainsPoint(ToPoint(aPoint), Matrix());
+ }
+ if (!isHit && (hitTestFlags & SVG_HIT_TEST_STROKE)) {
+ Point point = ToPoint(aPoint);
+ SVGContentUtils::AutoStrokeOptions stroke;
+ SVGContentUtils::GetStrokeOptions(&stroke, content, StyleContext(), nullptr);
+ gfxMatrix userToOuterSVG;
+ if (nsSVGUtils::GetNonScalingStrokeTransform(this, &userToOuterSVG)) {
+ // We need to transform the path back into the appropriate ancestor
+ // coordinate system in order for non-scaled stroke to be correct.
+ // Naturally we also need to transform the point into the same
+ // coordinate system in order to hit-test against the path.
+ point = ToMatrix(userToOuterSVG).TransformPoint(point);
+ RefPtr<PathBuilder> builder =
+ path->TransformedCopyToBuilder(ToMatrix(userToOuterSVG), fillRule);
+ path = builder->Finish();
+ }
+ isHit = path->StrokeContainsPoint(stroke, point, Matrix());
+ }
+
+ if (isHit && nsSVGUtils::HitTestClip(this, aPoint))
+ return this;
+
+ return nullptr;
+}
+
+nsRect
+nsSVGPathGeometryFrame::GetCoveredRegion()
+{
+ gfxMatrix canvasTM = GetCanvasTM();
+ if (canvasTM.PreservesAxisAlignedRectangles()) {
+ return nsSVGUtils::TransformFrameRectToOuterSVG(
+ mRect, canvasTM, PresContext());
+ }
+
+ // To get tight bounds we need to compute directly in outer SVG coordinates
+ uint32_t flags = nsSVGUtils::eBBoxIncludeFill |
+ nsSVGUtils::eBBoxIncludeStroke |
+ nsSVGUtils::eBBoxIncludeMarkers;
+ gfxRect extent =
+ GetBBoxContribution(ToMatrix(canvasTM), flags).ToThebesRect();
+ nsRect region = nsLayoutUtils::RoundGfxRectToAppRect(
+ extent, PresContext()->AppUnitsPerCSSPixel());
+
+ return nsSVGUtils::TransformFrameRectToOuterSVG(
+ region, gfxMatrix(), PresContext());
+}
+
+void
+nsSVGPathGeometryFrame::ReflowSVG()
+{
+ NS_ASSERTION(nsSVGUtils::OuterSVGIsCallingReflowSVG(this),
+ "This call is probably a wasteful mistake");
+
+ MOZ_ASSERT(!(GetStateBits() & NS_FRAME_IS_NONDISPLAY),
+ "ReflowSVG mechanism not designed for this");
+
+ if (!nsSVGUtils::NeedsReflowSVG(this)) {
+ return;
+ }
+
+ uint32_t flags = nsSVGUtils::eBBoxIncludeFill |
+ nsSVGUtils::eBBoxIncludeStroke |
+ nsSVGUtils::eBBoxIncludeMarkers;
+ // Our "visual" overflow rect needs to be valid for building display lists
+ // for hit testing, which means that for certain values of 'pointer-events'
+ // it needs to include the geometry of the fill or stroke even when the fill/
+ // stroke don't actually render (e.g. when stroke="none" or
+ // stroke-opacity="0"). GetHitTestFlags() accounts for 'pointer-events'.
+ uint16_t hitTestFlags = GetHitTestFlags();
+ if ((hitTestFlags & SVG_HIT_TEST_FILL)) {
+ flags |= nsSVGUtils::eBBoxIncludeFillGeometry;
+ }
+ if ((hitTestFlags & SVG_HIT_TEST_STROKE)) {
+ flags |= nsSVGUtils::eBBoxIncludeStrokeGeometry;
+ }
+
+ gfxRect extent = GetBBoxContribution(Matrix(), flags).ToThebesRect();
+ mRect = nsLayoutUtils::RoundGfxRectToAppRect(extent,
+ PresContext()->AppUnitsPerCSSPixel());
+
+ if (mState & NS_FRAME_FIRST_REFLOW) {
+ // Make sure we have our filter property (if any) before calling
+ // FinishAndStoreOverflow (subsequent filter changes are handled off
+ // nsChangeHint_UpdateEffects):
+ nsSVGEffects::UpdateEffects(this);
+ }
+
+ nsRect overflow = nsRect(nsPoint(0,0), mRect.Size());
+ nsOverflowAreas overflowAreas(overflow, overflow);
+ FinishAndStoreOverflow(overflowAreas, mRect.Size());
+
+ mState &= ~(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY |
+ NS_FRAME_HAS_DIRTY_CHILDREN);
+
+ // Invalidate, but only if this is not our first reflow (since if it is our
+ // first reflow then we haven't had our first paint yet).
+ if (!(GetParent()->GetStateBits() & NS_FRAME_FIRST_REFLOW)) {
+ InvalidateFrame();
+ }
+}
+
+void
+nsSVGPathGeometryFrame::NotifySVGChanged(uint32_t aFlags)
+{
+ MOZ_ASSERT(aFlags & (TRANSFORM_CHANGED | COORD_CONTEXT_CHANGED),
+ "Invalidation logic may need adjusting");
+
+ // Changes to our ancestors may affect how we render when we are rendered as
+ // part of our ancestor (specifically, if our coordinate context changes size
+ // and we have percentage lengths defining our geometry, then we need to be
+ // reflowed). However, ancestor changes cannot affect how we render when we
+ // are rendered as part of any rendering observers that we may have.
+ // Therefore no need to notify rendering observers here.
+
+ // Don't try to be too smart trying to avoid the ScheduleReflowSVG calls
+ // for the stroke properties examined below. Checking HasStroke() is not
+ // enough, since what we care about is whether we include the stroke in our
+ // overflow rects or not, and we sometimes deliberately include stroke
+ // when it's not visible. See the complexities of GetBBoxContribution.
+
+ if (aFlags & COORD_CONTEXT_CHANGED) {
+ // Stroke currently contributes to our mRect, which is why we have to take
+ // account of stroke-width here. Note that we do not need to take account
+ // of stroke-dashoffset since, although that can have a percentage value
+ // that is resolved against our coordinate context, it does not affect our
+ // mRect.
+ if (static_cast<nsSVGPathGeometryElement*>(mContent)->GeometryDependsOnCoordCtx() ||
+ StyleSVG()->mStrokeWidth.HasPercent()) {
+ static_cast<nsSVGPathGeometryElement*>(mContent)->ClearAnyCachedPath();
+ nsSVGUtils::ScheduleReflowSVG(this);
+ }
+ }
+
+ if ((aFlags & TRANSFORM_CHANGED) && StyleSVGReset()->HasNonScalingStroke()) {
+ // Stroke currently contributes to our mRect, and our stroke depends on
+ // the transform to our outer-<svg> if |vector-effect:non-scaling-stroke|.
+ nsSVGUtils::ScheduleReflowSVG(this);
+ }
+}
+
+SVGBBox
+nsSVGPathGeometryFrame::GetBBoxContribution(const Matrix &aToBBoxUserspace,
+ uint32_t aFlags)
+{
+ SVGBBox bbox;
+
+ if (aToBBoxUserspace.IsSingular()) {
+ // XXX ReportToConsole
+ return bbox;
+ }
+
+ nsSVGPathGeometryElement* element =
+ static_cast<nsSVGPathGeometryElement*>(mContent);
+
+ bool getFill = (aFlags & nsSVGUtils::eBBoxIncludeFillGeometry) ||
+ ((aFlags & nsSVGUtils::eBBoxIncludeFill) &&
+ StyleSVG()->mFill.Type() != eStyleSVGPaintType_None);
+
+ bool getStroke = (aFlags & nsSVGUtils::eBBoxIncludeStrokeGeometry) ||
+ ((aFlags & nsSVGUtils::eBBoxIncludeStroke) &&
+ nsSVGUtils::HasStroke(this));
+
+ SVGContentUtils::AutoStrokeOptions strokeOptions;
+ if (getStroke) {
+ SVGContentUtils::GetStrokeOptions(&strokeOptions, element,
+ StyleContext(), nullptr,
+ SVGContentUtils::eIgnoreStrokeDashing);
+ } else {
+ // Override the default line width of 1.f so that when we call
+ // GetGeometryBounds below the result doesn't include stroke bounds.
+ strokeOptions.mLineWidth = 0.f;
+ }
+
+ Rect simpleBounds;
+ bool gotSimpleBounds = false;
+ gfxMatrix userToOuterSVG;
+ if (getStroke &&
+ nsSVGUtils::GetNonScalingStrokeTransform(this, &userToOuterSVG)) {
+ Matrix moz2dUserToOuterSVG = ToMatrix(userToOuterSVG);
+ if (moz2dUserToOuterSVG.IsSingular()) {
+ return bbox;
+ }
+ gotSimpleBounds = element->GetGeometryBounds(&simpleBounds,
+ strokeOptions,
+ aToBBoxUserspace,
+ &moz2dUserToOuterSVG);
+ } else {
+ gotSimpleBounds = element->GetGeometryBounds(&simpleBounds,
+ strokeOptions,
+ aToBBoxUserspace);
+ }
+
+ if (gotSimpleBounds) {
+ bbox = simpleBounds;
+ } else {
+ // Get the bounds using a Moz2D Path object (more expensive):
+ RefPtr<DrawTarget> tmpDT;
+#ifdef XP_WIN
+ // Unfortunately D2D backed DrawTarget produces bounds with rounding errors
+ // when whole number results are expected, even in the case of trivial
+ // calculations. To avoid that and meet the expectations of web content we
+ // have to use a CAIRO DrawTarget. The most efficient way to do that is to
+ // wrap the cached cairo_surface_t from ScreenReferenceSurface():
+ RefPtr<gfxASurface> refSurf =
+ gfxPlatform::GetPlatform()->ScreenReferenceSurface();
+ tmpDT = gfxPlatform::GetPlatform()->
+ CreateDrawTargetForSurface(refSurf, IntSize(1, 1));
+#else
+ tmpDT = gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
+#endif
+
+ FillRule fillRule = nsSVGUtils::ToFillRule(StyleSVG()->mFillRule);
+ RefPtr<Path> pathInUserSpace = element->GetOrBuildPath(*tmpDT, fillRule);
+ if (!pathInUserSpace) {
+ return bbox;
+ }
+ RefPtr<Path> pathInBBoxSpace;
+ if (aToBBoxUserspace.IsIdentity()) {
+ pathInBBoxSpace = pathInUserSpace;
+ } else {
+ RefPtr<PathBuilder> builder =
+ pathInUserSpace->TransformedCopyToBuilder(aToBBoxUserspace, fillRule);
+ pathInBBoxSpace = builder->Finish();
+ if (!pathInBBoxSpace) {
+ return bbox;
+ }
+ }
+
+ // Be careful when replacing the following logic to get the fill and stroke
+ // extents independently (instead of computing the stroke extents from the
+ // path extents). You may think that you can just use the stroke extents if
+ // there is both a fill and a stroke. In reality it's necessary to
+ // calculate both the fill and stroke extents, and take the union of the
+ // two. There are two reasons for this:
+ //
+ // # Due to stroke dashing, in certain cases the fill extents could
+ // actually extend outside the stroke extents.
+ // # If the stroke is very thin, cairo won't paint any stroke, and so the
+ // stroke bounds that it will return will be empty.
+
+ Rect pathBBoxExtents = pathInBBoxSpace->GetBounds();
+ if (!pathBBoxExtents.IsFinite()) {
+ // This can happen in the case that we only have a move-to command in the
+ // path commands, in which case we know nothing gets rendered.
+ return bbox;
+ }
+
+ // Account for fill:
+ if (getFill) {
+ bbox = pathBBoxExtents;
+ }
+
+ // Account for stroke:
+ if (getStroke) {
+#if 0
+ // This disabled code is how we would calculate the stroke bounds using
+ // Moz2D Path::GetStrokedBounds(). Unfortunately at the time of writing
+ // it there are two problems that prevent us from using it.
+ //
+ // First, it seems that some of the Moz2D backends are really dumb. Not
+ // only do some GetStrokeOptions() implementations sometimes
+ // significantly overestimate the stroke bounds, but if an argument is
+ // passed for the aTransform parameter then they just return bounds-of-
+ // transformed-bounds. These two things combined can lead the bounds to
+ // be unacceptably oversized, leading to massive over-invalidation.
+ //
+ // Second, the way we account for non-scaling-stroke by transforming the
+ // path using the transform to the outer-<svg> element is not compatible
+ // with the way that nsSVGPathGeometryFrame::Reflow() inserts a scale
+ // into aToBBoxUserspace and then scales the bounds that we return.
+ SVGContentUtils::AutoStrokeOptions strokeOptions;
+ SVGContentUtils::GetStrokeOptions(&strokeOptions, element,
+ StyleContext(), nullptr,
+ SVGContentUtils::eIgnoreStrokeDashing);
+ Rect strokeBBoxExtents;
+ gfxMatrix userToOuterSVG;
+ if (nsSVGUtils::GetNonScalingStrokeTransform(this, &userToOuterSVG)) {
+ Matrix outerSVGToUser = ToMatrix(userToOuterSVG);
+ outerSVGToUser.Invert();
+ Matrix outerSVGToBBox = aToBBoxUserspace * outerSVGToUser;
+ RefPtr<PathBuilder> builder =
+ pathInUserSpace->TransformedCopyToBuilder(ToMatrix(userToOuterSVG));
+ RefPtr<Path> pathInOuterSVGSpace = builder->Finish();
+ strokeBBoxExtents =
+ pathInOuterSVGSpace->GetStrokedBounds(strokeOptions, outerSVGToBBox);
+ } else {
+ strokeBBoxExtents =
+ pathInUserSpace->GetStrokedBounds(strokeOptions, aToBBoxUserspace);
+ }
+ MOZ_ASSERT(strokeBBoxExtents.IsFinite(), "bbox is about to go bad");
+ bbox.UnionEdges(strokeBBoxExtents);
+#else
+ // For now we just use nsSVGUtils::PathExtentsToMaxStrokeExtents:
+ gfxRect strokeBBoxExtents =
+ nsSVGUtils::PathExtentsToMaxStrokeExtents(ThebesRect(pathBBoxExtents),
+ this,
+ ThebesMatrix(aToBBoxUserspace));
+ MOZ_ASSERT(ToRect(strokeBBoxExtents).IsFinite(), "bbox is about to go bad");
+ bbox.UnionEdges(strokeBBoxExtents);
+#endif
+ }
+ }
+
+ // Account for markers:
+ if ((aFlags & nsSVGUtils::eBBoxIncludeMarkers) != 0 &&
+ static_cast<nsSVGPathGeometryElement*>(mContent)->IsMarkable()) {
+
+ float strokeWidth = nsSVGUtils::GetStrokeWidth(this);
+ MarkerProperties properties = GetMarkerProperties(this);
+
+ if (properties.MarkersExist()) {
+ nsTArray<nsSVGMark> marks;
+ static_cast<nsSVGPathGeometryElement*>(mContent)->GetMarkPoints(&marks);
+ uint32_t num = marks.Length();
+
+ // These are in the same order as the nsSVGMark::Type constants.
+ nsSVGMarkerFrame* markerFrames[] = {
+ properties.GetMarkerStartFrame(),
+ properties.GetMarkerMidFrame(),
+ properties.GetMarkerEndFrame(),
+ };
+ static_assert(MOZ_ARRAY_LENGTH(markerFrames) == nsSVGMark::eTypeCount,
+ "Number of Marker frames should be equal to eTypeCount");
+
+ for (uint32_t i = 0; i < num; i++) {
+ nsSVGMark& mark = marks[i];
+ nsSVGMarkerFrame* frame = markerFrames[mark.type];
+ if (frame) {
+ SVGBBox mbbox =
+ frame->GetMarkBBoxContribution(aToBBoxUserspace, aFlags, this,
+ &marks[i], strokeWidth);
+ MOZ_ASSERT(mbbox.IsFinite(), "bbox is about to go bad");
+ bbox.UnionEdges(mbbox);
+ }
+ }
+ }
+ }
+
+ return bbox;
+}
+
+//----------------------------------------------------------------------
+// nsSVGPathGeometryFrame methods:
+
+gfxMatrix
+nsSVGPathGeometryFrame::GetCanvasTM()
+{
+ NS_ASSERTION(GetParent(), "null parent");
+
+ nsSVGContainerFrame *parent = static_cast<nsSVGContainerFrame*>(GetParent());
+ dom::SVGGraphicsElement *content = static_cast<dom::SVGGraphicsElement*>(mContent);
+
+ return content->PrependLocalTransformsTo(parent->GetCanvasTM());
+}
+
+nsSVGPathGeometryFrame::MarkerProperties
+nsSVGPathGeometryFrame::GetMarkerProperties(nsSVGPathGeometryFrame *aFrame)
+{
+ NS_ASSERTION(!aFrame->GetPrevContinuation(), "aFrame should be first continuation");
+
+ MarkerProperties result;
+ nsCOMPtr<nsIURI> markerURL =
+ nsSVGEffects::GetMarkerURI(aFrame, &nsStyleSVG::mMarkerStart);
+ result.mMarkerStart =
+ nsSVGEffects::GetMarkerProperty(markerURL, aFrame,
+ nsSVGEffects::MarkerBeginProperty());
+
+ markerURL = nsSVGEffects::GetMarkerURI(aFrame, &nsStyleSVG::mMarkerMid);
+ result.mMarkerMid =
+ nsSVGEffects::GetMarkerProperty(markerURL, aFrame,
+ nsSVGEffects::MarkerMiddleProperty());
+
+ markerURL = nsSVGEffects::GetMarkerURI(aFrame, &nsStyleSVG::mMarkerEnd);
+ result.mMarkerEnd =
+ nsSVGEffects::GetMarkerProperty(markerURL, aFrame,
+ nsSVGEffects::MarkerEndProperty());
+ return result;
+}
+
+nsSVGMarkerFrame *
+nsSVGPathGeometryFrame::MarkerProperties::GetMarkerStartFrame()
+{
+ if (!mMarkerStart)
+ return nullptr;
+ return static_cast<nsSVGMarkerFrame *>
+ (mMarkerStart->GetReferencedFrame(nsGkAtoms::svgMarkerFrame, nullptr));
+}
+
+nsSVGMarkerFrame *
+nsSVGPathGeometryFrame::MarkerProperties::GetMarkerMidFrame()
+{
+ if (!mMarkerMid)
+ return nullptr;
+ return static_cast<nsSVGMarkerFrame *>
+ (mMarkerMid->GetReferencedFrame(nsGkAtoms::svgMarkerFrame, nullptr));
+}
+
+nsSVGMarkerFrame *
+nsSVGPathGeometryFrame::MarkerProperties::GetMarkerEndFrame()
+{
+ if (!mMarkerEnd)
+ return nullptr;
+ return static_cast<nsSVGMarkerFrame *>
+ (mMarkerEnd->GetReferencedFrame(nsGkAtoms::svgMarkerFrame, nullptr));
+}
+
+void
+nsSVGPathGeometryFrame::Render(gfxContext* aContext,
+ uint32_t aRenderComponents,
+ const gfxMatrix& aNewTransform)
+{
+ MOZ_ASSERT(!aNewTransform.IsSingular());
+
+ DrawTarget* drawTarget = aContext->GetDrawTarget();
+
+ FillRule fillRule =
+ nsSVGUtils::ToFillRule((GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD) ?
+ StyleSVG()->mClipRule : StyleSVG()->mFillRule);
+
+ nsSVGPathGeometryElement* element =
+ static_cast<nsSVGPathGeometryElement*>(mContent);
+
+ AntialiasMode aaMode =
+ (StyleSVG()->mShapeRendering == NS_STYLE_SHAPE_RENDERING_OPTIMIZESPEED ||
+ StyleSVG()->mShapeRendering == NS_STYLE_SHAPE_RENDERING_CRISPEDGES) ?
+ AntialiasMode::NONE : AntialiasMode::SUBPIXEL;
+
+ // We wait as late as possible before setting the transform so that we don't
+ // set it unnecessarily if we return early (it's an expensive operation for
+ // some backends).
+ gfxContextMatrixAutoSaveRestore autoRestoreTransform(aContext);
+ aContext->SetMatrix(aNewTransform);
+
+ if (GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD) {
+ // We don't complicate this code with GetAsSimplePath since the cost of
+ // masking will dwarf Path creation overhead anyway.
+ RefPtr<Path> path = element->GetOrBuildPath(*drawTarget, fillRule);
+ if (path) {
+ ColorPattern white(ToDeviceColor(Color(1.0f, 1.0f, 1.0f, 1.0f)));
+ drawTarget->Fill(path, white,
+ DrawOptions(1.0f, CompositionOp::OP_OVER, aaMode));
+ }
+ return;
+ }
+
+ nsSVGPathGeometryElement::SimplePath simplePath;
+ RefPtr<Path> path;
+
+ element->GetAsSimplePath(&simplePath);
+ if (!simplePath.IsPath()) {
+ path = element->GetOrBuildPath(*drawTarget, fillRule);
+ if (!path) {
+ return;
+ }
+ }
+
+ SVGContextPaint* contextPaint = SVGContextPaint::GetContextPaint(mContent);
+
+ if (aRenderComponents & eRenderFill) {
+ GeneralPattern fillPattern;
+ nsSVGUtils::MakeFillPatternFor(this, aContext, &fillPattern, contextPaint);
+ if (fillPattern.GetPattern()) {
+ DrawOptions drawOptions(1.0f, CompositionOp::OP_OVER, aaMode);
+ if (simplePath.IsRect()) {
+ drawTarget->FillRect(simplePath.AsRect(), fillPattern, drawOptions);
+ } else if (path) {
+ drawTarget->Fill(path, fillPattern, drawOptions);
+ }
+ }
+ }
+
+ if ((aRenderComponents & eRenderStroke) &&
+ nsSVGUtils::HasStroke(this, contextPaint)) {
+ // Account for vector-effect:non-scaling-stroke:
+ gfxMatrix userToOuterSVG;
+ if (nsSVGUtils::GetNonScalingStrokeTransform(this, &userToOuterSVG)) {
+ // A simple Rect can't be transformed with rotate/skew, so let's switch
+ // to using a real path:
+ if (!path) {
+ path = element->GetOrBuildPath(*drawTarget, fillRule);
+ if (!path) {
+ return;
+ }
+ simplePath.Reset();
+ }
+ // We need to transform the path back into the appropriate ancestor
+ // coordinate system, and paint it it that coordinate system, in order
+ // for non-scaled stroke to paint correctly.
+ gfxMatrix outerSVGToUser = userToOuterSVG;
+ outerSVGToUser.Invert();
+ aContext->Multiply(outerSVGToUser);
+ RefPtr<PathBuilder> builder =
+ path->TransformedCopyToBuilder(ToMatrix(userToOuterSVG), fillRule);
+ path = builder->Finish();
+ }
+ GeneralPattern strokePattern;
+ nsSVGUtils::MakeStrokePatternFor(this, aContext, &strokePattern, contextPaint);
+ if (strokePattern.GetPattern()) {
+ SVGContentUtils::AutoStrokeOptions strokeOptions;
+ SVGContentUtils::GetStrokeOptions(&strokeOptions,
+ static_cast<nsSVGElement*>(mContent),
+ StyleContext(), contextPaint);
+ // GetStrokeOptions may set the line width to zero as an optimization
+ if (strokeOptions.mLineWidth <= 0) {
+ return;
+ }
+ DrawOptions drawOptions(1.0f, CompositionOp::OP_OVER, aaMode);
+ if (simplePath.IsRect()) {
+ drawTarget->StrokeRect(simplePath.AsRect(), strokePattern,
+ strokeOptions, drawOptions);
+ } else if (simplePath.IsLine()) {
+ drawTarget->StrokeLine(simplePath.Point1(), simplePath.Point2(),
+ strokePattern, strokeOptions, drawOptions);
+ } else {
+ drawTarget->Stroke(path, strokePattern, strokeOptions, drawOptions);
+ }
+ }
+ }
+}
+
+void
+nsSVGPathGeometryFrame::PaintMarkers(gfxContext& aContext,
+ const gfxMatrix& aTransform)
+{
+ SVGContextPaint* contextPaint = SVGContextPaint::GetContextPaint(mContent);
+
+ if (static_cast<nsSVGPathGeometryElement*>(mContent)->IsMarkable()) {
+ MarkerProperties properties = GetMarkerProperties(this);
+
+ if (properties.MarkersExist()) {
+ float strokeWidth = nsSVGUtils::GetStrokeWidth(this, contextPaint);
+
+ nsTArray<nsSVGMark> marks;
+ static_cast<nsSVGPathGeometryElement*>
+ (mContent)->GetMarkPoints(&marks);
+
+ uint32_t num = marks.Length();
+ if (num) {
+ // These are in the same order as the nsSVGMark::Type constants.
+ nsSVGMarkerFrame* markerFrames[] = {
+ properties.GetMarkerStartFrame(),
+ properties.GetMarkerMidFrame(),
+ properties.GetMarkerEndFrame(),
+ };
+ static_assert(MOZ_ARRAY_LENGTH(markerFrames) == nsSVGMark::eTypeCount,
+ "Number of Marker frames should be equal to eTypeCount");
+
+ for (uint32_t i = 0; i < num; i++) {
+ nsSVGMark& mark = marks[i];
+ nsSVGMarkerFrame* frame = markerFrames[mark.type];
+ if (frame) {
+ frame->PaintMark(aContext, aTransform, this, &mark, strokeWidth);
+ }
+ }
+ }
+ }
+ }
+}
+
+uint16_t
+nsSVGPathGeometryFrame::GetHitTestFlags()
+{
+ return nsSVGUtils::GetGeometryHitTestFlags(this);
+}