diff options
Diffstat (limited to 'layout/svg/nsSVGUtils.cpp')
-rw-r--r-- | layout/svg/nsSVGUtils.cpp | 1829 |
1 files changed, 1829 insertions, 0 deletions
diff --git a/layout/svg/nsSVGUtils.cpp b/layout/svg/nsSVGUtils.cpp new file mode 100644 index 000000000..ff74d5baf --- /dev/null +++ b/layout/svg/nsSVGUtils.cpp @@ -0,0 +1,1829 @@ +/* -*- 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: +// This is also necessary to ensure our definition of M_SQRT1_2 is picked up +#include "nsSVGUtils.h" +#include <algorithm> + +// Keep others in (case-insensitive) order: +#include "gfx2DGlue.h" +#include "gfxContext.h" +#include "gfxMatrix.h" +#include "gfxPlatform.h" +#include "gfxRect.h" +#include "gfxUtils.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/PatternHelpers.h" +#include "mozilla/Preferences.h" +#include "mozilla/SVGContextPaint.h" +#include "nsCSSClipPathInstance.h" +#include "nsCSSFrameConstructor.h" +#include "nsDisplayList.h" +#include "nsFilterInstance.h" +#include "nsFrameList.h" +#include "nsGkAtoms.h" +#include "nsIContent.h" +#include "nsIDocument.h" +#include "nsIFrame.h" +#include "nsIPresShell.h" +#include "nsISVGChildFrame.h" +#include "nsLayoutUtils.h" +#include "nsPresContext.h" +#include "nsStyleCoord.h" +#include "nsStyleStruct.h" +#include "nsSVGClipPathFrame.h" +#include "nsSVGContainerFrame.h" +#include "nsSVGEffects.h" +#include "nsSVGFilterPaintCallback.h" +#include "nsSVGForeignObjectFrame.h" +#include "nsSVGInnerSVGFrame.h" +#include "nsSVGIntegrationUtils.h" +#include "nsSVGLength2.h" +#include "nsSVGMaskFrame.h" +#include "nsSVGOuterSVGFrame.h" +#include "mozilla/dom/SVGClipPathElement.h" +#include "mozilla/dom/SVGPathElement.h" +#include "nsSVGPathGeometryElement.h" +#include "nsSVGPathGeometryFrame.h" +#include "nsSVGPaintServerFrame.h" +#include "mozilla/dom/SVGSVGElement.h" +#include "nsTextFrame.h" +#include "SVGContentUtils.h" +#include "SVGTextFrame.h" +#include "mozilla/Unused.h" + +using namespace mozilla; +using namespace mozilla::dom; +using namespace mozilla::gfx; + +static bool sSVGPathCachingEnabled; +static bool sSVGDisplayListHitTestingEnabled; +static bool sSVGDisplayListPaintingEnabled; +static bool sSVGNewGetBBoxEnabled; + +bool +NS_SVGPathCachingEnabled() +{ + return sSVGPathCachingEnabled; +} + +bool +NS_SVGDisplayListHitTestingEnabled() +{ + return sSVGDisplayListHitTestingEnabled; +} + +bool +NS_SVGDisplayListPaintingEnabled() +{ + return sSVGDisplayListPaintingEnabled; +} + +bool +NS_SVGNewGetBBoxEnabled() +{ + return sSVGNewGetBBoxEnabled; +} + + +// we only take the address of this: +static mozilla::gfx::UserDataKey sSVGAutoRenderStateKey; + +SVGAutoRenderState::SVGAutoRenderState(DrawTarget* aDrawTarget + MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL) + : mDrawTarget(aDrawTarget) + , mOriginalRenderState(nullptr) + , mPaintingToWindow(false) +{ + MOZ_GUARD_OBJECT_NOTIFIER_INIT; + mOriginalRenderState = + aDrawTarget->RemoveUserData(&sSVGAutoRenderStateKey); + // We always remove ourselves from aContext before it dies, so + // passing nullptr as the destroy function is okay. + aDrawTarget->AddUserData(&sSVGAutoRenderStateKey, this, nullptr); +} + +SVGAutoRenderState::~SVGAutoRenderState() +{ + mDrawTarget->RemoveUserData(&sSVGAutoRenderStateKey); + if (mOriginalRenderState) { + mDrawTarget->AddUserData(&sSVGAutoRenderStateKey, + mOriginalRenderState, nullptr); + } +} + +void +SVGAutoRenderState::SetPaintingToWindow(bool aPaintingToWindow) +{ + mPaintingToWindow = aPaintingToWindow; +} + +/* static */ bool +SVGAutoRenderState::IsPaintingToWindow(DrawTarget* aDrawTarget) +{ + void *state = aDrawTarget->GetUserData(&sSVGAutoRenderStateKey); + if (state) { + return static_cast<SVGAutoRenderState*>(state)->mPaintingToWindow; + } + return false; +} + +void +nsSVGUtils::Init() +{ + Preferences::AddBoolVarCache(&sSVGPathCachingEnabled, + "svg.path-caching.enabled"); + + Preferences::AddBoolVarCache(&sSVGDisplayListHitTestingEnabled, + "svg.display-lists.hit-testing.enabled"); + + Preferences::AddBoolVarCache(&sSVGDisplayListPaintingEnabled, + "svg.display-lists.painting.enabled"); + + Preferences::AddBoolVarCache(&sSVGNewGetBBoxEnabled, + "svg.new-getBBox.enabled"); +} + +nsSVGDisplayContainerFrame* +nsSVGUtils::GetNearestSVGViewport(nsIFrame *aFrame) +{ + NS_ASSERTION(aFrame->IsFrameOfType(nsIFrame::eSVG), "SVG frame expected"); + if (aFrame->GetType() == nsGkAtoms::svgOuterSVGFrame) { + return nullptr; + } + while ((aFrame = aFrame->GetParent())) { + NS_ASSERTION(aFrame->IsFrameOfType(nsIFrame::eSVG), "SVG frame expected"); + if (aFrame->GetType() == nsGkAtoms::svgInnerSVGFrame || + aFrame->GetType() == nsGkAtoms::svgOuterSVGFrame) { + return do_QueryFrame(aFrame); + } + } + NS_NOTREACHED("This is not reached. It's only needed to compile."); + return nullptr; +} + +nsRect +nsSVGUtils::GetPostFilterVisualOverflowRect(nsIFrame *aFrame, + const nsRect &aPreFilterRect) +{ + MOZ_ASSERT(aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT, + "Called on invalid frame type"); + + nsSVGFilterProperty *property = nsSVGEffects::GetFilterProperty(aFrame); + if (!property || !property->ReferencesValidResources()) { + return aPreFilterRect; + } + + return nsFilterInstance::GetPostFilterBounds(aFrame, nullptr, &aPreFilterRect); +} + +bool +nsSVGUtils::OuterSVGIsCallingReflowSVG(nsIFrame *aFrame) +{ + return GetOuterSVGFrame(aFrame)->IsCallingReflowSVG(); +} + +bool +nsSVGUtils::AnyOuterSVGIsCallingReflowSVG(nsIFrame* aFrame) +{ + nsSVGOuterSVGFrame* outer = GetOuterSVGFrame(aFrame); + do { + if (outer->IsCallingReflowSVG()) { + return true; + } + outer = GetOuterSVGFrame(outer->GetParent()); + } while (outer); + return false; +} + +void +nsSVGUtils::ScheduleReflowSVG(nsIFrame *aFrame) +{ + MOZ_ASSERT(aFrame->IsFrameOfType(nsIFrame::eSVG), + "Passed bad frame!"); + + // If this is triggered, the callers should be fixed to call us before + // ReflowSVG is called. If we try to mark dirty bits on frames while we're + // in the process of removing them, things will get messed up. + NS_ASSERTION(!OuterSVGIsCallingReflowSVG(aFrame), + "Do not call under nsISVGChildFrame::ReflowSVG!"); + + // We don't call nsSVGEffects::InvalidateRenderingObservers here because + // we should only be called under InvalidateAndScheduleReflowSVG (which + // calls InvalidateBounds) or nsSVGDisplayContainerFrame::InsertFrames + // (at which point the frame has no observers). + + if (aFrame->GetStateBits() & NS_FRAME_IS_NONDISPLAY) { + return; + } + + if (aFrame->GetStateBits() & + (NS_FRAME_IS_DIRTY | NS_FRAME_FIRST_REFLOW)) { + // Nothing to do if we're already dirty, or if the outer-<svg> + // hasn't yet had its initial reflow. + return; + } + + nsSVGOuterSVGFrame *outerSVGFrame = nullptr; + + // We must not add dirty bits to the nsSVGOuterSVGFrame or else + // PresShell::FrameNeedsReflow won't work when we pass it in below. + if (aFrame->GetStateBits() & NS_STATE_IS_OUTER_SVG) { + outerSVGFrame = static_cast<nsSVGOuterSVGFrame*>(aFrame); + } else { + aFrame->AddStateBits(NS_FRAME_IS_DIRTY); + + nsIFrame *f = aFrame->GetParent(); + while (f && !(f->GetStateBits() & NS_STATE_IS_OUTER_SVG)) { + if (f->GetStateBits() & + (NS_FRAME_IS_DIRTY | NS_FRAME_HAS_DIRTY_CHILDREN)) { + return; + } + f->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN); + f = f->GetParent(); + MOZ_ASSERT(f->IsFrameOfType(nsIFrame::eSVG), + "NS_STATE_IS_OUTER_SVG check above not valid!"); + } + + outerSVGFrame = static_cast<nsSVGOuterSVGFrame*>(f); + + MOZ_ASSERT(outerSVGFrame && + outerSVGFrame->GetType() == nsGkAtoms::svgOuterSVGFrame, + "Did not find nsSVGOuterSVGFrame!"); + } + + if (outerSVGFrame->GetStateBits() & NS_FRAME_IN_REFLOW) { + // We're currently under an nsSVGOuterSVGFrame::Reflow call so there is no + // need to call PresShell::FrameNeedsReflow, since we have an + // nsSVGOuterSVGFrame::DidReflow call pending. + return; + } + + nsFrameState dirtyBit = + (outerSVGFrame == aFrame ? NS_FRAME_IS_DIRTY : NS_FRAME_HAS_DIRTY_CHILDREN); + + aFrame->PresContext()->PresShell()->FrameNeedsReflow( + outerSVGFrame, nsIPresShell::eResize, dirtyBit); +} + +bool +nsSVGUtils::NeedsReflowSVG(nsIFrame *aFrame) +{ + MOZ_ASSERT(aFrame->IsFrameOfType(nsIFrame::eSVG), + "SVG uses bits differently!"); + + // The flags we test here may change, hence why we have this separate + // function. + return NS_SUBTREE_DIRTY(aFrame); +} + +void +nsSVGUtils::NotifyAncestorsOfFilterRegionChange(nsIFrame *aFrame) +{ + MOZ_ASSERT(!(aFrame->GetStateBits() & NS_STATE_IS_OUTER_SVG), + "Not expecting to be called on the outer SVG Frame"); + + aFrame = aFrame->GetParent(); + + while (aFrame) { + if (aFrame->GetStateBits() & NS_STATE_IS_OUTER_SVG) + return; + + nsSVGFilterProperty *property = nsSVGEffects::GetFilterProperty(aFrame); + if (property) { + property->Invalidate(); + } + aFrame = aFrame->GetParent(); + } +} + +Size +nsSVGUtils::GetContextSize(const nsIFrame* aFrame) +{ + Size size; + + MOZ_ASSERT(aFrame->GetContent()->IsSVGElement(), "bad cast"); + const nsSVGElement* element = static_cast<nsSVGElement*>(aFrame->GetContent()); + + SVGSVGElement* ctx = element->GetCtx(); + if (ctx) { + size.width = ctx->GetLength(SVGContentUtils::X); + size.height = ctx->GetLength(SVGContentUtils::Y); + } + return size; +} + +float +nsSVGUtils::ObjectSpace(const gfxRect &aRect, const nsSVGLength2 *aLength) +{ + float axis; + + switch (aLength->GetCtxType()) { + case SVGContentUtils::X: + axis = aRect.Width(); + break; + case SVGContentUtils::Y: + axis = aRect.Height(); + break; + case SVGContentUtils::XY: + axis = float(SVGContentUtils::ComputeNormalizedHypotenuse( + aRect.Width(), aRect.Height())); + break; + default: + NS_NOTREACHED("unexpected ctx type"); + axis = 0.0f; + break; + } + if (aLength->IsPercentage()) { + // Multiply first to avoid precision errors: + return axis * aLength->GetAnimValInSpecifiedUnits() / 100; + } + return aLength->GetAnimValue(static_cast<SVGSVGElement*>(nullptr)) * axis; +} + +float +nsSVGUtils::UserSpace(nsSVGElement *aSVGElement, const nsSVGLength2 *aLength) +{ + return aLength->GetAnimValue(aSVGElement); +} + +float +nsSVGUtils::UserSpace(nsIFrame *aNonSVGContext, const nsSVGLength2 *aLength) +{ + return aLength->GetAnimValue(aNonSVGContext); +} + +float +nsSVGUtils::UserSpace(const UserSpaceMetrics& aMetrics, const nsSVGLength2 *aLength) +{ + return aLength->GetAnimValue(aMetrics); +} + +nsSVGOuterSVGFrame * +nsSVGUtils::GetOuterSVGFrame(nsIFrame *aFrame) +{ + while (aFrame) { + if (aFrame->GetStateBits() & NS_STATE_IS_OUTER_SVG) { + return static_cast<nsSVGOuterSVGFrame*>(aFrame); + } + aFrame = aFrame->GetParent(); + } + + return nullptr; +} + +nsIFrame* +nsSVGUtils::GetOuterSVGFrameAndCoveredRegion(nsIFrame* aFrame, nsRect* aRect) +{ + nsISVGChildFrame* svg = do_QueryFrame(aFrame); + if (!svg) + return nullptr; + nsSVGOuterSVGFrame* outer = GetOuterSVGFrame(aFrame); + if (outer == svg) { + return nullptr; + } + nsMargin bp = outer->GetUsedBorderAndPadding(); + *aRect = ((aFrame->GetStateBits() & NS_FRAME_IS_NONDISPLAY) ? + nsRect(0, 0, 0, 0) : svg->GetCoveredRegion()) + + nsPoint(bp.left, bp.top); + return outer; +} + +gfxMatrix +nsSVGUtils::GetCanvasTM(nsIFrame *aFrame) +{ + // XXX yuck, we really need a common interface for GetCanvasTM + + if (!aFrame->IsFrameOfType(nsIFrame::eSVG)) { + return nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(aFrame); + } + + nsIAtom* type = aFrame->GetType(); + if (type == nsGkAtoms::svgForeignObjectFrame) { + return static_cast<nsSVGForeignObjectFrame*>(aFrame)->GetCanvasTM(); + } + if (type == nsGkAtoms::svgOuterSVGFrame) { + return nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(aFrame); + } + + nsSVGContainerFrame *containerFrame = do_QueryFrame(aFrame); + if (containerFrame) { + return containerFrame->GetCanvasTM(); + } + + return static_cast<nsSVGPathGeometryFrame*>(aFrame)->GetCanvasTM(); +} + +gfxMatrix +nsSVGUtils::GetUserToCanvasTM(nsIFrame *aFrame) +{ + nsISVGChildFrame* svgFrame = do_QueryFrame(aFrame); + NS_ASSERTION(svgFrame, "bad frame"); + + gfxMatrix tm; + if (svgFrame) { + nsSVGElement *content = static_cast<nsSVGElement*>(aFrame->GetContent()); + tm = content->PrependLocalTransformsTo( + GetCanvasTM(aFrame->GetParent()), + eUserSpaceToParent); + } + return tm; +} + +void +nsSVGUtils::NotifyChildrenOfSVGChange(nsIFrame *aFrame, uint32_t aFlags) +{ + for (nsIFrame* kid : aFrame->PrincipalChildList()) { + nsISVGChildFrame* SVGFrame = do_QueryFrame(kid); + if (SVGFrame) { + SVGFrame->NotifySVGChanged(aFlags); + } else { + NS_ASSERTION(kid->IsFrameOfType(nsIFrame::eSVG) || kid->IsSVGText(), + "SVG frame expected"); + // recurse into the children of container frames e.g. <clipPath>, <mask> + // in case they have child frames with transformation matrices + if (kid->IsFrameOfType(nsIFrame::eSVG)) { + NotifyChildrenOfSVGChange(kid, aFlags); + } + } + } +} + +// ************************************************************ + +class SVGPaintCallback : public nsSVGFilterPaintCallback +{ +public: + virtual DrawResult Paint(gfxContext& aContext, nsIFrame *aTarget, + const gfxMatrix& aTransform, + const nsIntRect* aDirtyRect) override + { + nsISVGChildFrame *svgChildFrame = do_QueryFrame(aTarget); + NS_ASSERTION(svgChildFrame, "Expected SVG frame here"); + + nsIntRect* dirtyRect = nullptr; + nsIntRect tmpDirtyRect; + + // aDirtyRect is in user-space pixels, we need to convert to + // outer-SVG-frame-relative device pixels. + if (aDirtyRect) { + gfxMatrix userToDeviceSpace = aTransform; + if (userToDeviceSpace.IsSingular()) { + return DrawResult::SUCCESS; + } + gfxRect dirtyBounds = userToDeviceSpace.TransformBounds( + gfxRect(aDirtyRect->x, aDirtyRect->y, aDirtyRect->width, aDirtyRect->height)); + dirtyBounds.RoundOut(); + if (gfxUtils::GfxRectToIntRect(dirtyBounds, &tmpDirtyRect)) { + dirtyRect = &tmpDirtyRect; + } + } + + return svgChildFrame->PaintSVG(aContext, aTransform, dirtyRect); + } +}; + +float +nsSVGUtils::ComputeOpacity(nsIFrame* aFrame, bool aHandleOpacity) +{ + float opacity = aFrame->StyleEffects()->mOpacity; + + if (opacity != 1.0f && + (nsSVGUtils::CanOptimizeOpacity(aFrame) || !aHandleOpacity)) { + return 1.0f; + } + + return opacity; +} + +void +nsSVGUtils::DetermineMaskUsage(nsIFrame* aFrame, bool aHandleOpacity, + MaskUsage& aUsage) +{ + aUsage.opacity = ComputeOpacity(aFrame, aHandleOpacity); + + nsIFrame* firstFrame = + nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame); + + nsSVGEffects::EffectProperties effectProperties = + nsSVGEffects::GetEffectProperties(firstFrame); + const nsStyleSVGReset *svgReset = firstFrame->StyleSVGReset(); + + nsTArray<nsSVGMaskFrame*> maskFrames = effectProperties.GetMaskFrames(); + +#ifdef MOZ_ENABLE_MASK_AS_SHORTHAND + // For a HTML doc: + // According to css-masking spec, always create a mask surface when we + // have any item in maskFrame even if all of those items are + // non-resolvable <mask-sources> or <images>, we still need to create a + // transparent black mask layer under this condition. + // For a SVG doc: + // SVG 1.1 say that if we fail to resolve a mask, we should draw the + // object unmasked. + aUsage.shouldGenerateMaskLayer = + (aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT) + ? maskFrames.Length() == 1 && maskFrames[0] + : maskFrames.Length() > 0; +#else + // Since we do not support image mask so far, we should treat any + // unresolvable mask as no mask. Otherwise, any object with a valid image + // mask, e.g. url("xxx.png"), will become invisible just because we can not + // handle image mask correctly. (See bug 1294171) + aUsage.shouldGenerateMaskLayer = maskFrames.Length() == 1 && maskFrames[0]; +#endif + + bool isOK = effectProperties.HasNoFilterOrHasValidFilter(); + nsSVGClipPathFrame *clipPathFrame = effectProperties.GetClipPathFrame(&isOK); + MOZ_ASSERT_IF(clipPathFrame, + svgReset->mClipPath.GetType() == StyleShapeSourceType::URL); + + switch (svgReset->mClipPath.GetType()) { + case StyleShapeSourceType::URL: + if (clipPathFrame) { + if (clipPathFrame->IsTrivial()) { + aUsage.shouldApplyClipPath = true; + } else { + aUsage.shouldGenerateClipMaskLayer = true; + } + } + break; + case StyleShapeSourceType::Shape: + case StyleShapeSourceType::Box: + aUsage.shouldApplyBasicShape = true; + break; + case StyleShapeSourceType::None: + MOZ_ASSERT(!aUsage.shouldGenerateClipMaskLayer && + !aUsage.shouldApplyClipPath && !aUsage.shouldApplyBasicShape); + break; + default: + MOZ_ASSERT_UNREACHABLE("Unsupported clip-path type."); + break; + } +} + +static IntRect +ComputeClipExtsInDeviceSpace(gfxContext& aCtx) +{ + gfxContextMatrixAutoSaveRestore matRestore(&aCtx); + + // Get the clip extents in device space. + aCtx.SetMatrix(gfxMatrix()); + gfxRect clippedFrameSurfaceRect = aCtx.GetClipExtents(); + clippedFrameSurfaceRect.RoundOut(); + + IntRect result; + ToRect(clippedFrameSurfaceRect).ToIntRect(&result); + return mozilla::gfx::Factory::CheckSurfaceSize(result.Size()) ? result + : IntRect(); +} + +static already_AddRefed<gfxContext> +CreateBlendTarget(gfxContext* aContext, IntPoint& aTargetOffset) +{ + // Create a temporary context to draw to so we can blend it back with + // another operator. + IntRect drawRect = ComputeClipExtsInDeviceSpace(*aContext); + + RefPtr<DrawTarget> targetDT = + aContext->GetDrawTarget()->CreateSimilarDrawTarget(drawRect.Size(), + SurfaceFormat::B8G8R8A8); + if (!targetDT || !targetDT->IsValid()) { + return nullptr; + } + + RefPtr<gfxContext> target = gfxContext::CreateOrNull(targetDT); + MOZ_ASSERT(target); // already checked the draw target above + target->SetMatrix(aContext->CurrentMatrix() * + gfxMatrix::Translation(-drawRect.TopLeft())); + aTargetOffset = drawRect.TopLeft(); + + return target.forget(); +} + +static void +BlendToTarget(nsIFrame* aFrame, gfxContext* aSource, gfxContext* aTarget, + const IntPoint& aTargetOffset) +{ + MOZ_ASSERT(aFrame->StyleEffects()->mMixBlendMode != NS_STYLE_BLEND_NORMAL); + + RefPtr<DrawTarget> targetDT = aTarget->GetDrawTarget(); + RefPtr<SourceSurface> targetSurf = targetDT->Snapshot(); + + gfxContextAutoSaveRestore save(aSource); + aSource->SetMatrix(gfxMatrix()); // This will be restored right after. + RefPtr<gfxPattern> pattern = new gfxPattern(targetSurf, Matrix::Translation(aTargetOffset.x, aTargetOffset.y)); + aSource->SetPattern(pattern); + aSource->Paint(); +} + +DrawResult +nsSVGUtils::PaintFrameWithEffects(nsIFrame *aFrame, + gfxContext& aContext, + const gfxMatrix& aTransform, + const nsIntRect *aDirtyRect) +{ + NS_ASSERTION(!NS_SVGDisplayListPaintingEnabled() || + (aFrame->GetStateBits() & NS_FRAME_IS_NONDISPLAY) || + aFrame->PresContext()->IsGlyph(), + "If display lists are enabled, only painting of non-display " + "SVG should take this code path"); + + nsISVGChildFrame *svgChildFrame = do_QueryFrame(aFrame); + if (!svgChildFrame) + return DrawResult::SUCCESS; + + MaskUsage maskUsage; + DetermineMaskUsage(aFrame, true, maskUsage); + if (maskUsage.opacity == 0.0f) { + return DrawResult::SUCCESS; + } + + const nsIContent* content = aFrame->GetContent(); + if (content->IsSVGElement() && + !static_cast<const nsSVGElement*>(content)->HasValidDimensions()) { + return DrawResult::SUCCESS; + } + + if (aDirtyRect && + !(aFrame->GetStateBits() & NS_FRAME_IS_NONDISPLAY)) { + // Here we convert aFrame's paint bounds to outer-<svg> device space, + // compare it to aDirtyRect, and return early if they don't intersect. + // We don't do this optimization for nondisplay SVG since nondisplay + // SVG doesn't maintain bounds/overflow rects. + nsRect overflowRect = aFrame->GetVisualOverflowRectRelativeToSelf(); + if (aFrame->IsFrameOfType(nsIFrame::eSVGGeometry) || + aFrame->IsSVGText()) { + // Unlike containers, leaf frames do not include GetPosition() in + // GetCanvasTM(). + overflowRect = overflowRect + aFrame->GetPosition(); + } + int32_t appUnitsPerDevPx = aFrame->PresContext()->AppUnitsPerDevPixel(); + gfxMatrix tm = aTransform; + if (aFrame->IsFrameOfType(nsIFrame::eSVG | nsIFrame::eSVGContainer)) { + gfx::Matrix childrenOnlyTM; + if (static_cast<nsSVGContainerFrame*>(aFrame)-> + HasChildrenOnlyTransform(&childrenOnlyTM)) { + // Undo the children-only transform: + if (!childrenOnlyTM.Invert()) { + return DrawResult::SUCCESS; + } + tm = ThebesMatrix(childrenOnlyTM) * tm; + } + } + nsIntRect bounds = TransformFrameRectToOuterSVG(overflowRect, + tm, aFrame->PresContext()). + ToOutsidePixels(appUnitsPerDevPx); + if (!aDirtyRect->Intersects(bounds)) { + return DrawResult::SUCCESS; + } + } + + /* SVG defines the following rendering model: + * + * 1. Render fill + * 2. Render stroke + * 3. Render markers + * 4. Apply filter + * 5. Apply clipping, masking, group opacity + * + * We follow this, but perform a couple of optimizations: + * + * + Use cairo's clipPath when representable natively (single object + * clip region). + *f + * + Merge opacity and masking if both used together. + */ + + /* Properties are added lazily and may have been removed by a restyle, + so make sure all applicable ones are set again. */ + nsSVGEffects::EffectProperties effectProperties = + nsSVGEffects::GetEffectProperties(aFrame); + bool isOK = effectProperties.HasNoFilterOrHasValidFilter(); + nsSVGClipPathFrame *clipPathFrame = effectProperties.GetClipPathFrame(&isOK); + nsSVGMaskFrame *maskFrame = effectProperties.GetFirstMaskFrame(&isOK); + if (!isOK) { + // Some resource is invalid. We shouldn't paint anything. + return DrawResult::SUCCESS; + } + + // These are used if we require a temporary surface for a custom blend mode. + // Clip the source context first, so that we can generate a smaller temporary + // surface. (Since we will clip this context in SetupContextMatrix, a pair + // of save/restore is needed.) + aContext.Save(); + if (!(aFrame->GetStateBits() & NS_FRAME_IS_NONDISPLAY)) { + // aFrame has a valid visual overflow rect, so clip to it before calling + // PushGroup() to minimize the size of the surfaces we'll composite: + gfxContextMatrixAutoSaveRestore matrixAutoSaveRestore(&aContext); + aContext.Multiply(aTransform); + nsRect overflowRect = aFrame->GetVisualOverflowRectRelativeToSelf(); + if (aFrame->IsFrameOfType(nsIFrame::eSVGGeometry) || + aFrame->IsSVGText()) { + // Unlike containers, leaf frames do not include GetPosition() in + // GetCanvasTM(). + overflowRect = overflowRect + aFrame->GetPosition(); + } + aContext.Clip(NSRectToSnappedRect(overflowRect, + aFrame->PresContext()->AppUnitsPerDevPixel(), + *aContext.GetDrawTarget())); + } + IntPoint targetOffset; + RefPtr<gfxContext> target = + (aFrame->StyleEffects()->mMixBlendMode == NS_STYLE_BLEND_NORMAL) + ? RefPtr<gfxContext>(&aContext).forget() + : CreateBlendTarget(&aContext, targetOffset); + aContext.Restore(); + + if (!target) { + return DrawResult::TEMPORARY_ERROR; + } + + /* Check if we need to do additional operations on this child's + * rendering, which necessitates rendering into another surface. */ + bool shouldGenerateMask = (maskUsage.opacity != 1.0f || + maskUsage.shouldGenerateClipMaskLayer || + maskUsage.shouldGenerateMaskLayer || + aFrame->StyleEffects()->mMixBlendMode != NS_STYLE_BLEND_NORMAL); + + if (shouldGenerateMask) { + Matrix maskTransform; + RefPtr<SourceSurface> maskSurface; + + if (maskUsage.shouldGenerateMaskLayer) { + maskSurface = + maskFrame->GetMaskForMaskedFrame(&aContext, aFrame, aTransform, + maskUsage.opacity, &maskTransform); + + if (!maskSurface) { + // Entire surface is clipped out. + return DrawResult::SUCCESS; + } + } + + if (maskUsage.shouldGenerateClipMaskLayer) { + Matrix clippedMaskTransform; + RefPtr<SourceSurface> clipMaskSurface = + clipPathFrame->GetClipMask(aContext, aFrame, aTransform, + &clippedMaskTransform, maskSurface, + maskTransform); + + if (clipMaskSurface) { + maskSurface = clipMaskSurface; + maskTransform = clippedMaskTransform; + } + } + + // SVG mask multiply opacity into maskSurface already, so we do not bother + // to apply opacity again. + float opacity = maskFrame ? 1.0 : maskUsage.opacity; + target->PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, opacity, + maskSurface, maskTransform); + } + + /* If this frame has only a trivial clipPath, set up cairo's clipping now so + * we can just do normal painting and get it clipped appropriately. + */ + if (maskUsage.shouldApplyClipPath || maskUsage.shouldApplyBasicShape) { + if (maskUsage.shouldApplyClipPath) { + clipPathFrame->ApplyClipPath(aContext, aFrame, aTransform); + } else { + nsCSSClipPathInstance::ApplyBasicShapeClip(aContext, aFrame); + } + } + + DrawResult result = DrawResult::SUCCESS; + + /* Paint the child */ + if (effectProperties.HasValidFilter()) { + nsRegion* dirtyRegion = nullptr; + nsRegion tmpDirtyRegion; + if (aDirtyRect) { + // aDirtyRect is in outer-<svg> device pixels, but the filter code needs + // it in frame space. + gfxMatrix userToDeviceSpace = GetUserToCanvasTM(aFrame); + if (userToDeviceSpace.IsSingular()) { + return DrawResult::SUCCESS; + } + gfxMatrix deviceToUserSpace = userToDeviceSpace; + deviceToUserSpace.Invert(); + gfxRect dirtyBounds = deviceToUserSpace.TransformBounds( + gfxRect(aDirtyRect->x, aDirtyRect->y, + aDirtyRect->width, aDirtyRect->height)); + tmpDirtyRegion = + nsLayoutUtils::RoundGfxRectToAppRect( + dirtyBounds, aFrame->PresContext()->AppUnitsPerCSSPixel()) - + aFrame->GetPosition(); + dirtyRegion = &tmpDirtyRegion; + } + SVGPaintCallback paintCallback; + nsFilterInstance::PaintFilteredFrame(aFrame, target->GetDrawTarget(), + aTransform, &paintCallback, + dirtyRegion); + } else { + result = svgChildFrame->PaintSVG(*target, aTransform, aDirtyRect); + } + + if (maskUsage.shouldApplyClipPath || maskUsage.shouldApplyBasicShape) { + aContext.PopClip(); + } + + if (shouldGenerateMask) { + target->PopGroupAndBlend(); + } + + if (aFrame->StyleEffects()->mMixBlendMode != NS_STYLE_BLEND_NORMAL) { + MOZ_ASSERT(target != &aContext); + BlendToTarget(aFrame, &aContext, target, targetOffset); + } + + return result; +} + +bool +nsSVGUtils::HitTestClip(nsIFrame *aFrame, const gfxPoint &aPoint) +{ + nsSVGEffects::EffectProperties props = + nsSVGEffects::GetEffectProperties(aFrame); + if (!props.mClipPath) { + const nsStyleSVGReset *style = aFrame->StyleSVGReset(); + if (style->HasClipPath()) { + return nsCSSClipPathInstance::HitTestBasicShapeClip(aFrame, aPoint); + } + return true; + } + + bool isOK = true; + nsSVGClipPathFrame *clipPathFrame = props.GetClipPathFrame(&isOK); + if (!isOK) { + // clipPath is not a valid resource, so nothing gets painted, so + // hit-testing must fail. + return false; + } + if (!clipPathFrame) { + // clipPath doesn't exist, ignore it. + return true; + } + + return clipPathFrame->PointIsInsideClipPath(aFrame, aPoint); +} + +nsIFrame * +nsSVGUtils::HitTestChildren(nsSVGDisplayContainerFrame* aFrame, + const gfxPoint& aPoint) +{ + // First we transform aPoint into the coordinate space established by aFrame + // for its children (e.g. take account of any 'viewBox' attribute): + gfxPoint point = aPoint; + if (aFrame->GetContent()->IsSVGElement()) { // must check before cast + gfxMatrix m = static_cast<const nsSVGElement*>(aFrame->GetContent())-> + PrependLocalTransformsTo(gfxMatrix(), + eChildToUserSpace); + if (!m.IsIdentity()) { + if (!m.Invert()) { + return nullptr; + } + point = m.Transform(point); + } + } + + // Traverse the list in reverse order, so that if we get a hit we know that's + // the topmost frame that intersects the point; then we can just return it. + nsIFrame* result = nullptr; + for (nsIFrame* current = aFrame->PrincipalChildList().LastChild(); + current; + current = current->GetPrevSibling()) { + nsISVGChildFrame* SVGFrame = do_QueryFrame(current); + if (SVGFrame) { + const nsIContent* content = current->GetContent(); + if (content->IsSVGElement() && + !static_cast<const nsSVGElement*>(content)->HasValidDimensions()) { + continue; + } + // GetFrameForPoint() expects a point in its frame's SVG user space, so + // we need to convert to that space: + gfxPoint p = point; + if (content->IsSVGElement()) { // must check before cast + gfxMatrix m = static_cast<const nsSVGElement*>(content)-> + PrependLocalTransformsTo(gfxMatrix(), + eUserSpaceToParent); + if (!m.IsIdentity()) { + if (!m.Invert()) { + continue; + } + p = m.Transform(p); + } + } + result = SVGFrame->GetFrameForPoint(p); + if (result) + break; + } + } + + if (result && !HitTestClip(aFrame, aPoint)) + result = nullptr; + + return result; +} + +nsRect +nsSVGUtils::GetCoveredRegion(const nsFrameList &aFrames) +{ + nsRect rect; + + for (nsIFrame* kid = aFrames.FirstChild(); + kid; + kid = kid->GetNextSibling()) { + nsISVGChildFrame* child = do_QueryFrame(kid); + if (child) { + nsRect childRect = child->GetCoveredRegion(); + rect.UnionRect(rect, childRect); + } + } + + return rect; +} + +nsRect +nsSVGUtils::TransformFrameRectToOuterSVG(const nsRect& aRect, + const gfxMatrix& aMatrix, + nsPresContext* aPresContext) +{ + gfxRect r(aRect.x, aRect.y, aRect.width, aRect.height); + r.Scale(1.0 / nsPresContext::AppUnitsPerCSSPixel()); + return nsLayoutUtils::RoundGfxRectToAppRect( + aMatrix.TransformBounds(r), aPresContext->AppUnitsPerDevPixel()); +} + +IntSize +nsSVGUtils::ConvertToSurfaceSize(const gfxSize& aSize, + bool *aResultOverflows) +{ + IntSize surfaceSize(ClampToInt(ceil(aSize.width)), ClampToInt(ceil(aSize.height))); + + *aResultOverflows = surfaceSize.width != ceil(aSize.width) || + surfaceSize.height != ceil(aSize.height); + + if (!Factory::CheckSurfaceSize(surfaceSize)) { + surfaceSize.width = std::min(NS_SVG_OFFSCREEN_MAX_DIMENSION, + surfaceSize.width); + surfaceSize.height = std::min(NS_SVG_OFFSCREEN_MAX_DIMENSION, + surfaceSize.height); + *aResultOverflows = true; + } + + return surfaceSize; +} + +bool +nsSVGUtils::HitTestRect(const gfx::Matrix &aMatrix, + float aRX, float aRY, float aRWidth, float aRHeight, + float aX, float aY) +{ + gfx::Rect rect(aRX, aRY, aRWidth, aRHeight); + if (rect.IsEmpty() || aMatrix.IsSingular()) { + return false; + } + gfx::Matrix toRectSpace = aMatrix; + toRectSpace.Invert(); + gfx::Point p = toRectSpace.TransformPoint(gfx::Point(aX, aY)); + return rect.x <= p.x && p.x <= rect.XMost() && + rect.y <= p.y && p.y <= rect.YMost(); +} + +gfxRect +nsSVGUtils::GetClipRectForFrame(nsIFrame *aFrame, + float aX, float aY, float aWidth, float aHeight) +{ + const nsStyleDisplay* disp = aFrame->StyleDisplay(); + const nsStyleEffects* effects = aFrame->StyleEffects(); + + if (!(effects->mClipFlags & NS_STYLE_CLIP_RECT)) { + NS_ASSERTION(effects->mClipFlags == NS_STYLE_CLIP_AUTO, + "We don't know about this type of clip."); + return gfxRect(aX, aY, aWidth, aHeight); + } + + if (disp->mOverflowX == NS_STYLE_OVERFLOW_HIDDEN || + disp->mOverflowY == NS_STYLE_OVERFLOW_HIDDEN) { + + nsIntRect clipPxRect = + effects->mClip.ToOutsidePixels(aFrame->PresContext()->AppUnitsPerDevPixel()); + gfxRect clipRect = + gfxRect(clipPxRect.x, clipPxRect.y, clipPxRect.width, clipPxRect.height); + + if (NS_STYLE_CLIP_RIGHT_AUTO & effects->mClipFlags) { + clipRect.width = aWidth - clipRect.X(); + } + if (NS_STYLE_CLIP_BOTTOM_AUTO & effects->mClipFlags) { + clipRect.height = aHeight - clipRect.Y(); + } + + if (disp->mOverflowX != NS_STYLE_OVERFLOW_HIDDEN) { + clipRect.x = aX; + clipRect.width = aWidth; + } + if (disp->mOverflowY != NS_STYLE_OVERFLOW_HIDDEN) { + clipRect.y = aY; + clipRect.height = aHeight; + } + + return clipRect; + } + return gfxRect(aX, aY, aWidth, aHeight); +} + +void +nsSVGUtils::SetClipRect(gfxContext *aContext, + const gfxMatrix &aCTM, + const gfxRect &aRect) +{ + if (aCTM.IsSingular()) + return; + + gfxContextMatrixAutoSaveRestore matrixAutoSaveRestore(aContext); + aContext->Multiply(aCTM); + aContext->Clip(aRect); +} + +gfxRect +nsSVGUtils::GetBBox(nsIFrame *aFrame, uint32_t aFlags) +{ + if (aFrame->GetContent()->IsNodeOfType(nsINode::eTEXT)) { + aFrame = aFrame->GetParent(); + } + gfxRect bbox; + nsISVGChildFrame *svg = do_QueryFrame(aFrame); + if (svg || aFrame->IsSVGText()) { + // It is possible to apply a gradient, pattern, clipping path, mask or + // filter to text. When one of these facilities is applied to text + // the bounding box is the entire text element in all + // cases. + if (aFrame->IsSVGText()) { + nsIFrame* ancestor = GetFirstNonAAncestorFrame(aFrame); + if (ancestor && ancestor->IsSVGText()) { + while (ancestor->GetType() != nsGkAtoms::svgTextFrame) { + ancestor = ancestor->GetParent(); + } + } + svg = do_QueryFrame(ancestor); + } + nsIContent* content = aFrame->GetContent(); + if (content->IsSVGElement() && + !static_cast<const nsSVGElement*>(content)->HasValidDimensions()) { + return bbox; + } + + FrameProperties props = aFrame->Properties(); + + if (aFlags == eBBoxIncludeFillGeometry) { + gfxRect* prop = props.Get(ObjectBoundingBoxProperty()); + if (prop) { + return *prop; + } + } + + gfxMatrix matrix; + if (aFrame->GetType() == nsGkAtoms::svgForeignObjectFrame || + aFrame->GetType() == nsGkAtoms::svgUseFrame) { + // The spec says getBBox "Returns the tight bounding box in *current user + // space*". So we should really be doing this for all elements, but that + // needs investigation to check that we won't break too much content. + // NOTE: When changing this to apply to other frame types, make sure to + // also update nsSVGUtils::FrameSpaceInCSSPxToUserSpaceOffset. + MOZ_ASSERT(content->IsSVGElement(), "bad cast"); + nsSVGElement *element = static_cast<nsSVGElement*>(content); + matrix = element->PrependLocalTransformsTo(matrix, eChildToUserSpace); + } + bbox = svg->GetBBoxContribution(ToMatrix(matrix), aFlags).ToThebesRect(); + // Account for 'clipped'. + if (aFlags & nsSVGUtils::eBBoxIncludeClipped) { + gfxRect clipRect(0, 0, 0, 0); + float x, y, width, height; + gfxMatrix tm; + gfxRect fillBBox = + svg->GetBBoxContribution(ToMatrix(tm), + nsSVGUtils::eBBoxIncludeFill).ToThebesRect(); + x = fillBBox.x; + y = fillBBox.y; + width = fillBBox.width; + height = fillBBox.height; + bool hasClip = aFrame->StyleDisplay()->IsScrollableOverflow(); + if (hasClip) { + clipRect = + nsSVGUtils::GetClipRectForFrame(aFrame, x, y, width, height); + if (aFrame->GetType() == nsGkAtoms::svgForeignObjectFrame || + aFrame->GetType() == nsGkAtoms::svgUseFrame) { + clipRect = matrix.TransformBounds(clipRect); + } + } + nsSVGEffects::EffectProperties effectProperties = + nsSVGEffects::GetEffectProperties(aFrame); + bool isOK = true; + nsSVGClipPathFrame *clipPathFrame = + effectProperties.GetClipPathFrame(&isOK); + if (clipPathFrame && isOK) { + SVGClipPathElement *clipContent = + static_cast<SVGClipPathElement*>(clipPathFrame->GetContent()); + RefPtr<SVGAnimatedEnumeration> units = clipContent->ClipPathUnits(); + if (units->AnimVal() == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) { + matrix.Translate(gfxPoint(x, y)); + matrix.Scale(width, height); + } else if (aFrame->GetType() == nsGkAtoms::svgForeignObjectFrame) { + matrix.Reset(); + } + bbox = + clipPathFrame->GetBBoxForClipPathFrame(bbox, matrix).ToThebesRect(); + if (hasClip) { + bbox = bbox.Intersect(clipRect); + } + } else { + if (!isOK) { + bbox = gfxRect(0, 0, 0, 0); + } else { + if (hasClip) { + bbox = bbox.Intersect(clipRect); + } + } + } + if (bbox.IsEmpty()) { + bbox = gfxRect(0, 0, 0, 0); + } + } + + if (aFlags == eBBoxIncludeFillGeometry) { + // Obtaining the bbox for objectBoundingBox calculations is common so we + // cache the result for future calls, since calculation can be expensive: + props.Set(ObjectBoundingBoxProperty(), new gfxRect(bbox)); + } + + return bbox; + } + return nsSVGIntegrationUtils::GetSVGBBoxForNonSVGFrame(aFrame); +} + +gfxPoint +nsSVGUtils::FrameSpaceInCSSPxToUserSpaceOffset(nsIFrame *aFrame) +{ + if (!(aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT)) { + // The user space for non-SVG frames is defined as the bounding box of the + // frame's border-box rects over all continuations. + return gfxPoint(); + } + + // Leaf frames apply their own offset inside their user space. + if (aFrame->IsFrameOfType(nsIFrame::eSVGGeometry) || + aFrame->IsSVGText()) { + return nsLayoutUtils::RectToGfxRect(aFrame->GetRect(), + nsPresContext::AppUnitsPerCSSPixel()).TopLeft(); + } + + // For foreignObject frames, nsSVGUtils::GetBBox applies their local + // transform, so we need to do the same here. + if (aFrame->GetType() == nsGkAtoms::svgForeignObjectFrame || + aFrame->GetType() == nsGkAtoms::svgUseFrame) { + gfxMatrix transform = static_cast<nsSVGElement*>(aFrame->GetContent())-> + PrependLocalTransformsTo(gfxMatrix(), eChildToUserSpace); + NS_ASSERTION(!transform.HasNonTranslation(), "we're relying on this being an offset-only transform"); + return transform.GetTranslation(); + } + + return gfxPoint(); +} + +static gfxRect +GetBoundingBoxRelativeRect(const nsSVGLength2 *aXYWH, + const gfxRect& aBBox) +{ + return gfxRect(aBBox.x + nsSVGUtils::ObjectSpace(aBBox, &aXYWH[0]), + aBBox.y + nsSVGUtils::ObjectSpace(aBBox, &aXYWH[1]), + nsSVGUtils::ObjectSpace(aBBox, &aXYWH[2]), + nsSVGUtils::ObjectSpace(aBBox, &aXYWH[3])); +} + +gfxRect +nsSVGUtils::GetRelativeRect(uint16_t aUnits, const nsSVGLength2 *aXYWH, + const gfxRect& aBBox, + const UserSpaceMetrics& aMetrics) +{ + if (aUnits == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) { + return GetBoundingBoxRelativeRect(aXYWH, aBBox); + } + return gfxRect(UserSpace(aMetrics, &aXYWH[0]), + UserSpace(aMetrics, &aXYWH[1]), + UserSpace(aMetrics, &aXYWH[2]), + UserSpace(aMetrics, &aXYWH[3])); +} + +gfxRect +nsSVGUtils::GetRelativeRect(uint16_t aUnits, const nsSVGLength2 *aXYWH, + const gfxRect& aBBox, nsIFrame *aFrame) +{ + if (aUnits == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) { + return GetBoundingBoxRelativeRect(aXYWH, aBBox); + } + nsIContent* content = aFrame->GetContent(); + if (content->IsSVGElement()) { + nsSVGElement* svgElement = static_cast<nsSVGElement*>(content); + return GetRelativeRect(aUnits, aXYWH, aBBox, SVGElementMetrics(svgElement)); + } + return GetRelativeRect(aUnits, aXYWH, aBBox, NonSVGFrameUserSpaceMetrics(aFrame)); +} + +bool +nsSVGUtils::CanOptimizeOpacity(nsIFrame *aFrame) +{ + if (!(aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT)) { + return false; + } + nsIAtom *type = aFrame->GetType(); + if (type != nsGkAtoms::svgImageFrame && + type != nsGkAtoms::svgPathGeometryFrame) { + return false; + } + if (aFrame->StyleEffects()->HasFilters()) { + return false; + } + // XXX The SVG WG is intending to allow fill, stroke and markers on <image> + if (type == nsGkAtoms::svgImageFrame) { + return true; + } + const nsStyleSVG *style = aFrame->StyleSVG(); + if (style->HasMarker()) { + return false; + } + if (!style->HasFill() || !HasStroke(aFrame)) { + return true; + } + return false; +} + +gfxMatrix +nsSVGUtils::AdjustMatrixForUnits(const gfxMatrix &aMatrix, + nsSVGEnum *aUnits, + nsIFrame *aFrame) +{ + if (aFrame && + aUnits->GetAnimValue() == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) { + gfxRect bbox = GetBBox(aFrame); + gfxMatrix tm = aMatrix; + tm.Translate(gfxPoint(bbox.X(), bbox.Y())); + tm.Scale(bbox.Width(), bbox.Height()); + return tm; + } + return aMatrix; +} + +nsIFrame* +nsSVGUtils::GetFirstNonAAncestorFrame(nsIFrame* aStartFrame) +{ + for (nsIFrame *ancestorFrame = aStartFrame; ancestorFrame; + ancestorFrame = ancestorFrame->GetParent()) { + if (ancestorFrame->GetType() != nsGkAtoms::svgAFrame) { + return ancestorFrame; + } + } + return nullptr; +} + +bool +nsSVGUtils::GetNonScalingStrokeTransform(nsIFrame *aFrame, + gfxMatrix* aUserToOuterSVG) +{ + if (aFrame->GetContent()->IsNodeOfType(nsINode::eTEXT)) { + aFrame = aFrame->GetParent(); + } + + if (!aFrame->StyleSVGReset()->HasNonScalingStroke()) { + return false; + } + + nsIContent *content = aFrame->GetContent(); + MOZ_ASSERT(content->IsSVGElement(), "bad cast"); + + *aUserToOuterSVG = ThebesMatrix(SVGContentUtils::GetCTM( + static_cast<nsSVGElement*>(content), true)); + + return !aUserToOuterSVG->IsIdentity(); +} + +// The logic here comes from _cairo_stroke_style_max_distance_from_path +static gfxRect +PathExtentsToMaxStrokeExtents(const gfxRect& aPathExtents, + nsIFrame* aFrame, + double aStyleExpansionFactor, + const gfxMatrix& aMatrix) +{ + double style_expansion = + aStyleExpansionFactor * nsSVGUtils::GetStrokeWidth(aFrame); + + gfxMatrix matrix = aMatrix; + + gfxMatrix outerSVGToUser; + if (nsSVGUtils::GetNonScalingStrokeTransform(aFrame, &outerSVGToUser)) { + outerSVGToUser.Invert(); + matrix.PreMultiply(outerSVGToUser); + } + + double dx = style_expansion * (fabs(matrix._11) + fabs(matrix._21)); + double dy = style_expansion * (fabs(matrix._22) + fabs(matrix._12)); + + gfxRect strokeExtents = aPathExtents; + strokeExtents.Inflate(dx, dy); + return strokeExtents; +} + +/*static*/ gfxRect +nsSVGUtils::PathExtentsToMaxStrokeExtents(const gfxRect& aPathExtents, + nsTextFrame* aFrame, + const gfxMatrix& aMatrix) +{ + NS_ASSERTION(aFrame->IsSVGText(), "expected an nsTextFrame for SVG text"); + return ::PathExtentsToMaxStrokeExtents(aPathExtents, aFrame, 0.5, aMatrix); +} + +/*static*/ gfxRect +nsSVGUtils::PathExtentsToMaxStrokeExtents(const gfxRect& aPathExtents, + nsSVGPathGeometryFrame* aFrame, + const gfxMatrix& aMatrix) +{ + bool strokeMayHaveCorners = + !SVGContentUtils::ShapeTypeHasNoCorners(aFrame->GetContent()); + + // For a shape without corners the stroke can only extend half the stroke + // width from the path in the x/y-axis directions. For shapes with corners + // the stroke can extend by sqrt(1/2) (think 45 degree rotated rect, or line + // with stroke-linecaps="square"). + double styleExpansionFactor = strokeMayHaveCorners ? M_SQRT1_2 : 0.5; + + // The stroke can extend even further for paths that can be affected by + // stroke-miterlimit. + bool affectedByMiterlimit = + aFrame->GetContent()->IsAnyOfSVGElements(nsGkAtoms::path, + nsGkAtoms::polyline, + nsGkAtoms::polygon); + + if (affectedByMiterlimit) { + const nsStyleSVG* style = aFrame->StyleSVG(); + if (style->mStrokeLinejoin == NS_STYLE_STROKE_LINEJOIN_MITER && + styleExpansionFactor < style->mStrokeMiterlimit / 2.0) { + styleExpansionFactor = style->mStrokeMiterlimit / 2.0; + } + } + + return ::PathExtentsToMaxStrokeExtents(aPathExtents, + aFrame, + styleExpansionFactor, + aMatrix); +} + +// ---------------------------------------------------------------------- + +/* static */ nscolor +nsSVGUtils::GetFallbackOrPaintColor(nsStyleContext *aStyleContext, + nsStyleSVGPaint nsStyleSVG::*aFillOrStroke) +{ + const nsStyleSVGPaint &paint = aStyleContext->StyleSVG()->*aFillOrStroke; + nsStyleContext *styleIfVisited = aStyleContext->GetStyleIfVisited(); + bool isServer = paint.Type() == eStyleSVGPaintType_Server || + paint.Type() == eStyleSVGPaintType_ContextFill || + paint.Type() == eStyleSVGPaintType_ContextStroke; + nscolor color = isServer ? paint.GetFallbackColor() : paint.GetColor(); + if (styleIfVisited) { + const nsStyleSVGPaint &paintIfVisited = + styleIfVisited->StyleSVG()->*aFillOrStroke; + // To prevent Web content from detecting if a user has visited a URL + // (via URL loading triggered by paint servers or performance + // differences between paint servers or between a paint server and a + // color), we do not allow whether links are visited to change which + // paint server is used or switch between paint servers and simple + // colors. A :visited style may only override a simple color with + // another simple color. + if (paintIfVisited.Type() == eStyleSVGPaintType_Color && + paint.Type() == eStyleSVGPaintType_Color) { + nscolor colors[2] = { color, paintIfVisited.GetColor() }; + return nsStyleContext::CombineVisitedColors( + colors, aStyleContext->RelevantLinkVisited()); + } + } + return color; +} + +/* static */ void +nsSVGUtils::MakeFillPatternFor(nsIFrame* aFrame, + gfxContext* aContext, + GeneralPattern* aOutPattern, + SVGContextPaint* aContextPaint) +{ + const nsStyleSVG* style = aFrame->StyleSVG(); + if (style->mFill.Type() == eStyleSVGPaintType_None) { + return; + } + + const float opacity = aFrame->StyleEffects()->mOpacity; + + float fillOpacity = GetOpacity(style->FillOpacitySource(), + style->mFillOpacity, + aContextPaint); + if (opacity < 1.0f && + nsSVGUtils::CanOptimizeOpacity(aFrame)) { + // Combine the group opacity into the fill opacity (we will have skipped + // creating an offscreen surface to apply the group opacity). + fillOpacity *= opacity; + } + + const DrawTarget* dt = aContext->GetDrawTarget(); + + nsSVGPaintServerFrame *ps = + nsSVGEffects::GetPaintServer(aFrame, &nsStyleSVG::mFill, + nsSVGEffects::FillProperty()); + if (ps) { + RefPtr<gfxPattern> pattern = + ps->GetPaintServerPattern(aFrame, dt, aContext->CurrentMatrix(), + &nsStyleSVG::mFill, fillOpacity); + if (pattern) { + pattern->CacheColorStops(dt); + aOutPattern->Init(*pattern->GetPattern(dt)); + return; + } + } + + if (aContextPaint) { + RefPtr<gfxPattern> pattern; + switch (style->mFill.Type()) { + case eStyleSVGPaintType_ContextFill: + pattern = aContextPaint->GetFillPattern(dt, fillOpacity, + aContext->CurrentMatrix()); + break; + case eStyleSVGPaintType_ContextStroke: + pattern = aContextPaint->GetStrokePattern(dt, fillOpacity, + aContext->CurrentMatrix()); + break; + default: + ; + } + if (pattern) { + aOutPattern->Init(*pattern->GetPattern(dt)); + return; + } + } + + // On failure, use the fallback colour in case we have an + // objectBoundingBox where the width or height of the object is zero. + // See http://www.w3.org/TR/SVG11/coords.html#ObjectBoundingBox + Color color(Color::FromABGR(GetFallbackOrPaintColor(aFrame->StyleContext(), + &nsStyleSVG::mFill))); + color.a *= fillOpacity; + aOutPattern->InitColorPattern(ToDeviceColor(color)); +} + +/* static */ void +nsSVGUtils::MakeStrokePatternFor(nsIFrame* aFrame, + gfxContext* aContext, + GeneralPattern* aOutPattern, + SVGContextPaint* aContextPaint) +{ + const nsStyleSVG* style = aFrame->StyleSVG(); + if (style->mStroke.Type() == eStyleSVGPaintType_None) { + return; + } + + const float opacity = aFrame->StyleEffects()->mOpacity; + + float strokeOpacity = GetOpacity(style->StrokeOpacitySource(), + style->mStrokeOpacity, + aContextPaint); + if (opacity < 1.0f && + nsSVGUtils::CanOptimizeOpacity(aFrame)) { + // Combine the group opacity into the stroke opacity (we will have skipped + // creating an offscreen surface to apply the group opacity). + strokeOpacity *= opacity; + } + + const DrawTarget* dt = aContext->GetDrawTarget(); + + nsSVGPaintServerFrame *ps = + nsSVGEffects::GetPaintServer(aFrame, &nsStyleSVG::mStroke, + nsSVGEffects::StrokeProperty()); + if (ps) { + RefPtr<gfxPattern> pattern = + ps->GetPaintServerPattern(aFrame, dt, aContext->CurrentMatrix(), + &nsStyleSVG::mStroke, strokeOpacity); + if (pattern) { + pattern->CacheColorStops(dt); + aOutPattern->Init(*pattern->GetPattern(dt)); + return; + } + } + + if (aContextPaint) { + RefPtr<gfxPattern> pattern; + switch (style->mStroke.Type()) { + case eStyleSVGPaintType_ContextFill: + pattern = aContextPaint->GetFillPattern(dt, strokeOpacity, + aContext->CurrentMatrix()); + break; + case eStyleSVGPaintType_ContextStroke: + pattern = aContextPaint->GetStrokePattern(dt, strokeOpacity, + aContext->CurrentMatrix()); + break; + default: + ; + } + if (pattern) { + aOutPattern->Init(*pattern->GetPattern(dt)); + return; + } + } + + // On failure, use the fallback colour in case we have an + // objectBoundingBox where the width or height of the object is zero. + // See http://www.w3.org/TR/SVG11/coords.html#ObjectBoundingBox + Color color(Color::FromABGR(GetFallbackOrPaintColor(aFrame->StyleContext(), + &nsStyleSVG::mStroke))); + color.a *= strokeOpacity; + aOutPattern->InitColorPattern(ToDeviceColor(color)); +} + +/* static */ float +nsSVGUtils::GetOpacity(nsStyleSVGOpacitySource aOpacityType, + const float& aOpacity, + SVGContextPaint *aContextPaint) +{ + float opacity = 1.0f; + switch (aOpacityType) { + case eStyleSVGOpacitySource_Normal: + opacity = aOpacity; + break; + case eStyleSVGOpacitySource_ContextFillOpacity: + if (aContextPaint) { + opacity = aContextPaint->GetFillOpacity(); + } else { + NS_WARNING("Content used context-fill-opacity when not in a context element"); + } + break; + case eStyleSVGOpacitySource_ContextStrokeOpacity: + if (aContextPaint) { + opacity = aContextPaint->GetStrokeOpacity(); + } else { + NS_WARNING("Content used context-stroke-opacity when not in a context element"); + } + break; + default: + NS_NOTREACHED("Unknown object opacity inheritance type for SVG glyph"); + } + return opacity; +} + +bool +nsSVGUtils::HasStroke(nsIFrame* aFrame, SVGContextPaint* aContextPaint) +{ + const nsStyleSVG *style = aFrame->StyleSVG(); + return style->HasStroke() && GetStrokeWidth(aFrame, aContextPaint) > 0; +} + +float +nsSVGUtils::GetStrokeWidth(nsIFrame* aFrame, SVGContextPaint* aContextPaint) +{ + const nsStyleSVG *style = aFrame->StyleSVG(); + if (aContextPaint && style->StrokeWidthFromObject()) { + return aContextPaint->GetStrokeWidth(); + } + + nsIContent* content = aFrame->GetContent(); + if (content->IsNodeOfType(nsINode::eTEXT)) { + content = content->GetParent(); + } + + nsSVGElement *ctx = static_cast<nsSVGElement*>(content); + + return SVGContentUtils::CoordToFloat(ctx, style->mStrokeWidth); +} + +static bool +GetStrokeDashData(nsIFrame* aFrame, + nsTArray<gfxFloat>& aDashes, + gfxFloat* aDashOffset, + SVGContextPaint* aContextPaint) +{ + const nsStyleSVG* style = aFrame->StyleSVG(); + nsIContent *content = aFrame->GetContent(); + nsSVGElement *ctx = static_cast<nsSVGElement*> + (content->IsNodeOfType(nsINode::eTEXT) ? + content->GetParent() : content); + + gfxFloat totalLength = 0.0; + if (aContextPaint && style->StrokeDasharrayFromObject()) { + aDashes = aContextPaint->GetStrokeDashArray(); + + for (uint32_t i = 0; i < aDashes.Length(); i++) { + if (aDashes[i] < 0.0) { + return false; + } + totalLength += aDashes[i]; + } + + } else { + uint32_t count = style->mStrokeDasharray.Length(); + if (!count || !aDashes.SetLength(count, fallible)) { + return false; + } + + gfxFloat pathScale = 1.0; + + if (content->IsSVGElement(nsGkAtoms::path)) { + pathScale = static_cast<SVGPathElement*>(content)-> + GetPathLengthScale(SVGPathElement::eForStroking); + if (pathScale <= 0) { + return false; + } + } + + const nsTArray<nsStyleCoord>& dasharray = style->mStrokeDasharray; + + for (uint32_t i = 0; i < count; i++) { + aDashes[i] = SVGContentUtils::CoordToFloat(ctx, + dasharray[i]) * pathScale; + if (aDashes[i] < 0.0) { + return false; + } + totalLength += aDashes[i]; + } + } + + if (aContextPaint && style->StrokeDashoffsetFromObject()) { + *aDashOffset = aContextPaint->GetStrokeDashOffset(); + } else { + *aDashOffset = SVGContentUtils::CoordToFloat(ctx, + style->mStrokeDashoffset); + } + + return (totalLength > 0.0); +} + +void +nsSVGUtils::SetupCairoStrokeGeometry(nsIFrame* aFrame, + gfxContext *aContext, + SVGContextPaint* aContextPaint) +{ + float width = GetStrokeWidth(aFrame, aContextPaint); + if (width <= 0) + return; + aContext->SetLineWidth(width); + + // Apply any stroke-specific transform + gfxMatrix outerSVGToUser; + if (GetNonScalingStrokeTransform(aFrame, &outerSVGToUser) && + outerSVGToUser.Invert()) { + aContext->Multiply(outerSVGToUser); + } + + const nsStyleSVG* style = aFrame->StyleSVG(); + + switch (style->mStrokeLinecap) { + case NS_STYLE_STROKE_LINECAP_BUTT: + aContext->SetLineCap(CapStyle::BUTT); + break; + case NS_STYLE_STROKE_LINECAP_ROUND: + aContext->SetLineCap(CapStyle::ROUND); + break; + case NS_STYLE_STROKE_LINECAP_SQUARE: + aContext->SetLineCap(CapStyle::SQUARE); + break; + } + + aContext->SetMiterLimit(style->mStrokeMiterlimit); + + switch (style->mStrokeLinejoin) { + case NS_STYLE_STROKE_LINEJOIN_MITER: + aContext->SetLineJoin(JoinStyle::MITER_OR_BEVEL); + break; + case NS_STYLE_STROKE_LINEJOIN_ROUND: + aContext->SetLineJoin(JoinStyle::ROUND); + break; + case NS_STYLE_STROKE_LINEJOIN_BEVEL: + aContext->SetLineJoin(JoinStyle::BEVEL); + break; + } + + AutoTArray<gfxFloat, 10> dashes; + gfxFloat dashOffset; + if (GetStrokeDashData(aFrame, dashes, &dashOffset, aContextPaint)) { + aContext->SetDash(dashes.Elements(), dashes.Length(), dashOffset); + } +} + +uint16_t +nsSVGUtils::GetGeometryHitTestFlags(nsIFrame* aFrame) +{ + uint16_t flags = 0; + + switch (aFrame->StyleUserInterface()->mPointerEvents) { + case NS_STYLE_POINTER_EVENTS_NONE: + break; + case NS_STYLE_POINTER_EVENTS_AUTO: + case NS_STYLE_POINTER_EVENTS_VISIBLEPAINTED: + if (aFrame->StyleVisibility()->IsVisible()) { + if (aFrame->StyleSVG()->mFill.Type() != eStyleSVGPaintType_None) + flags |= SVG_HIT_TEST_FILL; + if (aFrame->StyleSVG()->mStroke.Type() != eStyleSVGPaintType_None) + flags |= SVG_HIT_TEST_STROKE; + if (aFrame->StyleSVG()->mStrokeOpacity > 0) + flags |= SVG_HIT_TEST_CHECK_MRECT; + } + break; + case NS_STYLE_POINTER_EVENTS_VISIBLEFILL: + if (aFrame->StyleVisibility()->IsVisible()) { + flags |= SVG_HIT_TEST_FILL; + } + break; + case NS_STYLE_POINTER_EVENTS_VISIBLESTROKE: + if (aFrame->StyleVisibility()->IsVisible()) { + flags |= SVG_HIT_TEST_STROKE; + } + break; + case NS_STYLE_POINTER_EVENTS_VISIBLE: + if (aFrame->StyleVisibility()->IsVisible()) { + flags |= SVG_HIT_TEST_FILL | SVG_HIT_TEST_STROKE; + } + break; + case NS_STYLE_POINTER_EVENTS_PAINTED: + if (aFrame->StyleSVG()->mFill.Type() != eStyleSVGPaintType_None) + flags |= SVG_HIT_TEST_FILL; + if (aFrame->StyleSVG()->mStroke.Type() != eStyleSVGPaintType_None) + flags |= SVG_HIT_TEST_STROKE; + if (aFrame->StyleSVG()->mStrokeOpacity) + flags |= SVG_HIT_TEST_CHECK_MRECT; + break; + case NS_STYLE_POINTER_EVENTS_FILL: + flags |= SVG_HIT_TEST_FILL; + break; + case NS_STYLE_POINTER_EVENTS_STROKE: + flags |= SVG_HIT_TEST_STROKE; + break; + case NS_STYLE_POINTER_EVENTS_ALL: + flags |= SVG_HIT_TEST_FILL | SVG_HIT_TEST_STROKE; + break; + default: + NS_ERROR("not reached"); + break; + } + + return flags; +} + +bool +nsSVGUtils::PaintSVGGlyph(Element* aElement, gfxContext* aContext) +{ + nsIFrame* frame = aElement->GetPrimaryFrame(); + nsISVGChildFrame* svgFrame = do_QueryFrame(frame); + if (!svgFrame) { + return false; + } + gfxMatrix m; + if (frame->GetContent()->IsSVGElement()) { + // PaintSVG() expects the passed transform to be the transform to its own + // SVG user space, so we need to account for any 'transform' attribute: + m = static_cast<nsSVGElement*>(frame->GetContent())-> + PrependLocalTransformsTo(gfxMatrix(), eUserSpaceToParent); + } + DrawResult result = svgFrame->PaintSVG(*aContext, m); + return (result == DrawResult::SUCCESS); +} + +bool +nsSVGUtils::GetSVGGlyphExtents(Element* aElement, + const gfxMatrix& aSVGToAppSpace, + gfxRect* aResult) +{ + nsIFrame* frame = aElement->GetPrimaryFrame(); + nsISVGChildFrame* svgFrame = do_QueryFrame(frame); + if (!svgFrame) { + return false; + } + + gfxMatrix transform(aSVGToAppSpace); + nsIContent* content = frame->GetContent(); + if (content->IsSVGElement()) { + transform = static_cast<nsSVGElement*>(content)-> + PrependLocalTransformsTo(aSVGToAppSpace); + } + + *aResult = svgFrame->GetBBoxContribution(gfx::ToMatrix(transform), + nsSVGUtils::eBBoxIncludeFill | nsSVGUtils::eBBoxIncludeFillGeometry | + nsSVGUtils::eBBoxIncludeStroke | nsSVGUtils::eBBoxIncludeStrokeGeometry | + nsSVGUtils::eBBoxIncludeMarkers).ToThebesRect(); + return true; +} + +nsRect +nsSVGUtils::ToCanvasBounds(const gfxRect &aUserspaceRect, + const gfxMatrix &aToCanvas, + const nsPresContext *presContext) +{ + return nsLayoutUtils::RoundGfxRectToAppRect( + aToCanvas.TransformBounds(aUserspaceRect), + presContext->AppUnitsPerDevPixel()); +} |