michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: // Main header first: michael@0: // This is also necessary to ensure our definition of M_SQRT1_2 is picked up michael@0: #include "nsSVGUtils.h" michael@0: #include michael@0: michael@0: // Keep others in (case-insensitive) order: michael@0: #include "gfx2DGlue.h" michael@0: #include "gfxContext.h" michael@0: #include "gfxMatrix.h" michael@0: #include "gfxPlatform.h" michael@0: #include "gfxRect.h" michael@0: #include "gfxUtils.h" michael@0: #include "mozilla/gfx/2D.h" michael@0: #include "mozilla/Preferences.h" michael@0: #include "nsCSSFrameConstructor.h" michael@0: #include "nsDisplayList.h" michael@0: #include "nsFilterInstance.h" michael@0: #include "nsFrameList.h" michael@0: #include "nsGkAtoms.h" michael@0: #include "nsIContent.h" michael@0: #include "nsIDocument.h" michael@0: #include "nsIFrame.h" michael@0: #include "nsIPresShell.h" michael@0: #include "nsISVGChildFrame.h" michael@0: #include "nsPresContext.h" michael@0: #include "nsRenderingContext.h" michael@0: #include "nsStyleCoord.h" michael@0: #include "nsStyleStruct.h" michael@0: #include "nsSVGClipPathFrame.h" michael@0: #include "nsSVGContainerFrame.h" michael@0: #include "nsSVGEffects.h" michael@0: #include "nsSVGFilterPaintCallback.h" michael@0: #include "nsSVGForeignObjectFrame.h" michael@0: #include "gfxSVGGlyphs.h" michael@0: #include "nsSVGInnerSVGFrame.h" michael@0: #include "nsSVGIntegrationUtils.h" michael@0: #include "nsSVGLength2.h" michael@0: #include "nsSVGMaskFrame.h" michael@0: #include "nsSVGOuterSVGFrame.h" michael@0: #include "mozilla/dom/SVGPathElement.h" michael@0: #include "nsSVGPathGeometryElement.h" michael@0: #include "nsSVGPathGeometryFrame.h" michael@0: #include "nsSVGPaintServerFrame.h" michael@0: #include "mozilla/dom/SVGSVGElement.h" michael@0: #include "nsTextFrame.h" michael@0: #include "SVGContentUtils.h" michael@0: #include "mozilla/unused.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::dom; michael@0: using namespace mozilla::gfx; michael@0: michael@0: static bool sSVGDisplayListHitTestingEnabled; michael@0: static bool sSVGDisplayListPaintingEnabled; michael@0: michael@0: bool michael@0: NS_SVGDisplayListHitTestingEnabled() michael@0: { michael@0: return sSVGDisplayListHitTestingEnabled; michael@0: } michael@0: michael@0: bool michael@0: NS_SVGDisplayListPaintingEnabled() michael@0: { michael@0: return sSVGDisplayListPaintingEnabled; michael@0: } michael@0: michael@0: // we only take the address of this: michael@0: static mozilla::gfx::UserDataKey sSVGAutoRenderStateKey; michael@0: michael@0: SVGAutoRenderState::SVGAutoRenderState(nsRenderingContext *aContext, michael@0: RenderMode aMode michael@0: MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL) michael@0: : mContext(aContext) michael@0: , mOriginalRenderState(nullptr) michael@0: , mMode(aMode) michael@0: , mPaintingToWindow(false) michael@0: { michael@0: MOZ_GUARD_OBJECT_NOTIFIER_INIT; michael@0: mOriginalRenderState = aContext->RemoveUserData(&sSVGAutoRenderStateKey); michael@0: // We always remove ourselves from aContext before it dies, so michael@0: // passing nullptr as the destroy function is okay. michael@0: aContext->AddUserData(&sSVGAutoRenderStateKey, this, nullptr); michael@0: } michael@0: michael@0: SVGAutoRenderState::~SVGAutoRenderState() michael@0: { michael@0: mContext->RemoveUserData(&sSVGAutoRenderStateKey); michael@0: if (mOriginalRenderState) { michael@0: mContext->AddUserData(&sSVGAutoRenderStateKey, mOriginalRenderState, nullptr); michael@0: } michael@0: } michael@0: michael@0: void michael@0: SVGAutoRenderState::SetPaintingToWindow(bool aPaintingToWindow) michael@0: { michael@0: mPaintingToWindow = aPaintingToWindow; michael@0: } michael@0: michael@0: /* static */ SVGAutoRenderState::RenderMode michael@0: SVGAutoRenderState::GetRenderMode(nsRenderingContext *aContext) michael@0: { michael@0: void *state = aContext->GetUserData(&sSVGAutoRenderStateKey); michael@0: if (state) { michael@0: return static_cast(state)->mMode; michael@0: } michael@0: return NORMAL; michael@0: } michael@0: michael@0: /* static */ bool michael@0: SVGAutoRenderState::IsPaintingToWindow(nsRenderingContext *aContext) michael@0: { michael@0: void *state = aContext->GetUserData(&sSVGAutoRenderStateKey); michael@0: if (state) { michael@0: return static_cast(state)->mPaintingToWindow; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: void michael@0: nsSVGUtils::Init() michael@0: { michael@0: Preferences::AddBoolVarCache(&sSVGDisplayListHitTestingEnabled, michael@0: "svg.display-lists.hit-testing.enabled"); michael@0: michael@0: Preferences::AddBoolVarCache(&sSVGDisplayListPaintingEnabled, michael@0: "svg.display-lists.painting.enabled"); michael@0: } michael@0: michael@0: nsSVGDisplayContainerFrame* michael@0: nsSVGUtils::GetNearestSVGViewport(nsIFrame *aFrame) michael@0: { michael@0: NS_ASSERTION(aFrame->IsFrameOfType(nsIFrame::eSVG), "SVG frame expected"); michael@0: if (aFrame->GetType() == nsGkAtoms::svgOuterSVGFrame) { michael@0: return nullptr; michael@0: } michael@0: while ((aFrame = aFrame->GetParent())) { michael@0: NS_ASSERTION(aFrame->IsFrameOfType(nsIFrame::eSVG), "SVG frame expected"); michael@0: if (aFrame->GetType() == nsGkAtoms::svgInnerSVGFrame || michael@0: aFrame->GetType() == nsGkAtoms::svgOuterSVGFrame) { michael@0: return do_QueryFrame(aFrame); michael@0: } michael@0: } michael@0: NS_NOTREACHED("This is not reached. It's only needed to compile."); michael@0: return nullptr; michael@0: } michael@0: michael@0: nsRect michael@0: nsSVGUtils::GetPostFilterVisualOverflowRect(nsIFrame *aFrame, michael@0: const nsRect &aPreFilterRect) michael@0: { michael@0: NS_ABORT_IF_FALSE(aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT, michael@0: "Called on invalid frame type"); michael@0: michael@0: nsSVGFilterProperty *property = nsSVGEffects::GetFilterProperty(aFrame); michael@0: if (!property || !property->ReferencesValidResources()) { michael@0: return aPreFilterRect; michael@0: } michael@0: michael@0: return nsFilterInstance::GetPostFilterBounds(aFrame, nullptr, &aPreFilterRect); michael@0: } michael@0: michael@0: bool michael@0: nsSVGUtils::OuterSVGIsCallingReflowSVG(nsIFrame *aFrame) michael@0: { michael@0: return GetOuterSVGFrame(aFrame)->IsCallingReflowSVG(); michael@0: } michael@0: michael@0: bool michael@0: nsSVGUtils::AnyOuterSVGIsCallingReflowSVG(nsIFrame* aFrame) michael@0: { michael@0: nsSVGOuterSVGFrame* outer = GetOuterSVGFrame(aFrame); michael@0: do { michael@0: if (outer->IsCallingReflowSVG()) { michael@0: return true; michael@0: } michael@0: outer = GetOuterSVGFrame(outer->GetParent()); michael@0: } while (outer); michael@0: return false; michael@0: } michael@0: michael@0: void michael@0: nsSVGUtils::ScheduleReflowSVG(nsIFrame *aFrame) michael@0: { michael@0: NS_ABORT_IF_FALSE(aFrame->IsFrameOfType(nsIFrame::eSVG), michael@0: "Passed bad frame!"); michael@0: michael@0: // If this is triggered, the callers should be fixed to call us before michael@0: // ReflowSVG is called. If we try to mark dirty bits on frames while we're michael@0: // in the process of removing them, things will get messed up. michael@0: NS_ASSERTION(!OuterSVGIsCallingReflowSVG(aFrame), michael@0: "Do not call under nsISVGChildFrame::ReflowSVG!"); michael@0: michael@0: // We don't call nsSVGEffects::InvalidateRenderingObservers here because michael@0: // we should only be called under InvalidateAndScheduleReflowSVG (which michael@0: // calls InvalidateBounds) or nsSVGDisplayContainerFrame::InsertFrames michael@0: // (at which point the frame has no observers). michael@0: michael@0: if (aFrame->GetStateBits() & NS_FRAME_IS_NONDISPLAY) { michael@0: return; michael@0: } michael@0: michael@0: if (aFrame->GetStateBits() & michael@0: (NS_FRAME_IS_DIRTY | NS_FRAME_FIRST_REFLOW)) { michael@0: // Nothing to do if we're already dirty, or if the outer- michael@0: // hasn't yet had its initial reflow. michael@0: return; michael@0: } michael@0: michael@0: nsSVGOuterSVGFrame *outerSVGFrame = nullptr; michael@0: michael@0: // We must not add dirty bits to the nsSVGOuterSVGFrame or else michael@0: // PresShell::FrameNeedsReflow won't work when we pass it in below. michael@0: if (aFrame->GetStateBits() & NS_STATE_IS_OUTER_SVG) { michael@0: outerSVGFrame = static_cast(aFrame); michael@0: } else { michael@0: aFrame->AddStateBits(NS_FRAME_IS_DIRTY); michael@0: michael@0: nsIFrame *f = aFrame->GetParent(); michael@0: while (f && !(f->GetStateBits() & NS_STATE_IS_OUTER_SVG)) { michael@0: if (f->GetStateBits() & michael@0: (NS_FRAME_IS_DIRTY | NS_FRAME_HAS_DIRTY_CHILDREN)) { michael@0: return; michael@0: } michael@0: f->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN); michael@0: f = f->GetParent(); michael@0: NS_ABORT_IF_FALSE(f->IsFrameOfType(nsIFrame::eSVG), michael@0: "NS_STATE_IS_OUTER_SVG check above not valid!"); michael@0: } michael@0: michael@0: outerSVGFrame = static_cast(f); michael@0: michael@0: NS_ABORT_IF_FALSE(outerSVGFrame && michael@0: outerSVGFrame->GetType() == nsGkAtoms::svgOuterSVGFrame, michael@0: "Did not find nsSVGOuterSVGFrame!"); michael@0: } michael@0: michael@0: if (outerSVGFrame->GetStateBits() & NS_FRAME_IN_REFLOW) { michael@0: // We're currently under an nsSVGOuterSVGFrame::Reflow call so there is no michael@0: // need to call PresShell::FrameNeedsReflow, since we have an michael@0: // nsSVGOuterSVGFrame::DidReflow call pending. michael@0: return; michael@0: } michael@0: michael@0: nsFrameState dirtyBit = michael@0: (outerSVGFrame == aFrame ? NS_FRAME_IS_DIRTY : NS_FRAME_HAS_DIRTY_CHILDREN); michael@0: michael@0: aFrame->PresContext()->PresShell()->FrameNeedsReflow( michael@0: outerSVGFrame, nsIPresShell::eResize, dirtyBit); michael@0: } michael@0: michael@0: bool michael@0: nsSVGUtils::NeedsReflowSVG(nsIFrame *aFrame) michael@0: { michael@0: NS_ABORT_IF_FALSE(aFrame->IsFrameOfType(nsIFrame::eSVG), michael@0: "SVG uses bits differently!"); michael@0: michael@0: // The flags we test here may change, hence why we have this separate michael@0: // function. michael@0: return NS_SUBTREE_DIRTY(aFrame); michael@0: } michael@0: michael@0: void michael@0: nsSVGUtils::NotifyAncestorsOfFilterRegionChange(nsIFrame *aFrame) michael@0: { michael@0: NS_ABORT_IF_FALSE(!(aFrame->GetStateBits() & NS_STATE_IS_OUTER_SVG), michael@0: "Not expecting to be called on the outer SVG Frame"); michael@0: michael@0: aFrame = aFrame->GetParent(); michael@0: michael@0: while (aFrame) { michael@0: if (aFrame->GetStateBits() & NS_STATE_IS_OUTER_SVG) michael@0: return; michael@0: michael@0: nsSVGFilterProperty *property = nsSVGEffects::GetFilterProperty(aFrame); michael@0: if (property) { michael@0: property->Invalidate(); michael@0: } michael@0: aFrame = aFrame->GetParent(); michael@0: } michael@0: } michael@0: michael@0: float michael@0: nsSVGUtils::ObjectSpace(const gfxRect &aRect, const nsSVGLength2 *aLength) michael@0: { michael@0: float axis; michael@0: michael@0: switch (aLength->GetCtxType()) { michael@0: case SVGContentUtils::X: michael@0: axis = aRect.Width(); michael@0: break; michael@0: case SVGContentUtils::Y: michael@0: axis = aRect.Height(); michael@0: break; michael@0: case SVGContentUtils::XY: michael@0: axis = float(SVGContentUtils::ComputeNormalizedHypotenuse( michael@0: aRect.Width(), aRect.Height())); michael@0: break; michael@0: default: michael@0: NS_NOTREACHED("unexpected ctx type"); michael@0: axis = 0.0f; michael@0: break; michael@0: } michael@0: if (aLength->IsPercentage()) { michael@0: // Multiply first to avoid precision errors: michael@0: return axis * aLength->GetAnimValInSpecifiedUnits() / 100; michael@0: } michael@0: return aLength->GetAnimValue(static_cast(nullptr)) * axis; michael@0: } michael@0: michael@0: float michael@0: nsSVGUtils::UserSpace(nsSVGElement *aSVGElement, const nsSVGLength2 *aLength) michael@0: { michael@0: return aLength->GetAnimValue(aSVGElement); michael@0: } michael@0: michael@0: float michael@0: nsSVGUtils::UserSpace(nsIFrame *aNonSVGContext, const nsSVGLength2 *aLength) michael@0: { michael@0: return aLength->GetAnimValue(aNonSVGContext); michael@0: } michael@0: michael@0: nsSVGOuterSVGFrame * michael@0: nsSVGUtils::GetOuterSVGFrame(nsIFrame *aFrame) michael@0: { michael@0: while (aFrame) { michael@0: if (aFrame->GetStateBits() & NS_STATE_IS_OUTER_SVG) { michael@0: return static_cast(aFrame); michael@0: } michael@0: aFrame = aFrame->GetParent(); michael@0: } michael@0: michael@0: return nullptr; michael@0: } michael@0: michael@0: nsIFrame* michael@0: nsSVGUtils::GetOuterSVGFrameAndCoveredRegion(nsIFrame* aFrame, nsRect* aRect) michael@0: { michael@0: nsISVGChildFrame* svg = do_QueryFrame(aFrame); michael@0: if (!svg) michael@0: return nullptr; michael@0: *aRect = (aFrame->GetStateBits() & NS_FRAME_IS_NONDISPLAY) ? michael@0: nsRect(0, 0, 0, 0) : svg->GetCoveredRegion(); michael@0: return GetOuterSVGFrame(aFrame); michael@0: } michael@0: michael@0: gfxMatrix michael@0: nsSVGUtils::GetCanvasTM(nsIFrame *aFrame, uint32_t aFor, michael@0: nsIFrame* aTransformRoot) michael@0: { michael@0: // XXX yuck, we really need a common interface for GetCanvasTM michael@0: michael@0: if (!aFrame->IsFrameOfType(nsIFrame::eSVG)) { michael@0: return nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(aFrame); michael@0: } michael@0: michael@0: if (!(aFrame->GetStateBits() & NS_FRAME_IS_NONDISPLAY) && michael@0: !aTransformRoot) { michael@0: if ((aFor == nsISVGChildFrame::FOR_PAINTING && michael@0: NS_SVGDisplayListPaintingEnabled()) || michael@0: (aFor == nsISVGChildFrame::FOR_HIT_TESTING && michael@0: NS_SVGDisplayListHitTestingEnabled())) { michael@0: return nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(aFrame); michael@0: } michael@0: } michael@0: michael@0: nsIAtom* type = aFrame->GetType(); michael@0: if (type == nsGkAtoms::svgForeignObjectFrame) { michael@0: return static_cast(aFrame)-> michael@0: GetCanvasTM(aFor, aTransformRoot); michael@0: } michael@0: if (type == nsGkAtoms::svgOuterSVGFrame) { michael@0: return nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(aFrame); michael@0: } michael@0: michael@0: nsSVGContainerFrame *containerFrame = do_QueryFrame(aFrame); michael@0: if (containerFrame) { michael@0: return containerFrame->GetCanvasTM(aFor, aTransformRoot); michael@0: } michael@0: michael@0: return static_cast(aFrame)-> michael@0: GetCanvasTM(aFor, aTransformRoot); michael@0: } michael@0: michael@0: gfxMatrix michael@0: nsSVGUtils::GetUserToCanvasTM(nsIFrame *aFrame, uint32_t aFor) michael@0: { michael@0: NS_ASSERTION(aFor == nsISVGChildFrame::FOR_OUTERSVG_TM, michael@0: "Unexpected aFor?"); michael@0: michael@0: nsISVGChildFrame* svgFrame = do_QueryFrame(aFrame); michael@0: NS_ASSERTION(svgFrame, "bad frame"); michael@0: michael@0: gfxMatrix tm; michael@0: if (svgFrame) { michael@0: nsSVGElement *content = static_cast(aFrame->GetContent()); michael@0: tm = content->PrependLocalTransformsTo( michael@0: GetCanvasTM(aFrame->GetParent(), aFor), michael@0: nsSVGElement::eUserSpaceToParent); michael@0: } michael@0: return tm; michael@0: } michael@0: michael@0: void michael@0: nsSVGUtils::NotifyChildrenOfSVGChange(nsIFrame *aFrame, uint32_t aFlags) michael@0: { michael@0: nsIFrame *kid = aFrame->GetFirstPrincipalChild(); michael@0: michael@0: while (kid) { michael@0: nsISVGChildFrame* SVGFrame = do_QueryFrame(kid); michael@0: if (SVGFrame) { michael@0: SVGFrame->NotifySVGChanged(aFlags); michael@0: } else { michael@0: NS_ASSERTION(kid->IsFrameOfType(nsIFrame::eSVG) || kid->IsSVGText(), michael@0: "SVG frame expected"); michael@0: // recurse into the children of container frames e.g. , michael@0: // in case they have child frames with transformation matrices michael@0: if (kid->IsFrameOfType(nsIFrame::eSVG)) { michael@0: NotifyChildrenOfSVGChange(kid, aFlags); michael@0: } michael@0: } michael@0: kid = kid->GetNextSibling(); michael@0: } michael@0: } michael@0: michael@0: // ************************************************************ michael@0: michael@0: class SVGPaintCallback : public nsSVGFilterPaintCallback michael@0: { michael@0: public: michael@0: virtual void Paint(nsRenderingContext *aContext, nsIFrame *aTarget, michael@0: const nsIntRect* aDirtyRect, michael@0: nsIFrame* aTransformRoot) MOZ_OVERRIDE michael@0: { michael@0: nsISVGChildFrame *svgChildFrame = do_QueryFrame(aTarget); michael@0: NS_ASSERTION(svgChildFrame, "Expected SVG frame here"); michael@0: michael@0: nsIntRect* dirtyRect = nullptr; michael@0: nsIntRect tmpDirtyRect; michael@0: michael@0: // aDirtyRect is in user-space pixels, we need to convert to michael@0: // outer-SVG-frame-relative device pixels. michael@0: if (aDirtyRect) { michael@0: gfxMatrix userToDeviceSpace = michael@0: nsSVGUtils::GetCanvasTM(aTarget, nsISVGChildFrame::FOR_PAINTING, aTransformRoot); michael@0: if (userToDeviceSpace.IsSingular()) { michael@0: return; michael@0: } michael@0: gfxRect dirtyBounds = userToDeviceSpace.TransformBounds( michael@0: gfxRect(aDirtyRect->x, aDirtyRect->y, aDirtyRect->width, aDirtyRect->height)); michael@0: dirtyBounds.RoundOut(); michael@0: if (gfxUtils::GfxRectToIntRect(dirtyBounds, &tmpDirtyRect)) { michael@0: dirtyRect = &tmpDirtyRect; michael@0: } michael@0: } michael@0: michael@0: svgChildFrame->PaintSVG(aContext, dirtyRect, aTransformRoot); michael@0: } michael@0: }; michael@0: michael@0: void michael@0: nsSVGUtils::PaintFrameWithEffects(nsRenderingContext *aContext, michael@0: const nsIntRect *aDirtyRect, michael@0: nsIFrame *aFrame, michael@0: nsIFrame *aTransformRoot) michael@0: { michael@0: NS_ASSERTION(!NS_SVGDisplayListPaintingEnabled() || michael@0: (aFrame->GetStateBits() & NS_FRAME_IS_NONDISPLAY) || michael@0: aFrame->PresContext()->IsGlyph(), michael@0: "If display lists are enabled, only painting of non-display " michael@0: "SVG should take this code path"); michael@0: michael@0: nsISVGChildFrame *svgChildFrame = do_QueryFrame(aFrame); michael@0: if (!svgChildFrame) michael@0: return; michael@0: michael@0: float opacity = aFrame->StyleDisplay()->mOpacity; michael@0: if (opacity == 0.0f) michael@0: return; michael@0: michael@0: const nsIContent* content = aFrame->GetContent(); michael@0: if (content->IsSVG() && michael@0: !static_cast(content)->HasValidDimensions()) { michael@0: return; michael@0: } michael@0: michael@0: /* Properties are added lazily and may have been removed by a restyle, michael@0: so make sure all applicable ones are set again. */ michael@0: michael@0: nsSVGEffects::EffectProperties effectProperties = michael@0: nsSVGEffects::GetEffectProperties(aFrame); michael@0: michael@0: bool isOK = effectProperties.HasNoFilterOrHasValidFilter(); michael@0: michael@0: if (aDirtyRect && michael@0: !(aFrame->GetStateBits() & NS_FRAME_IS_NONDISPLAY)) { michael@0: // Here we convert aFrame's paint bounds to outer- device space, michael@0: // compare it to aDirtyRect, and return early if they don't intersect. michael@0: // We don't do this optimization for nondisplay SVG since nondisplay michael@0: // SVG doesn't maintain bounds/overflow rects. michael@0: nsRect overflowRect = aFrame->GetVisualOverflowRectRelativeToSelf(); michael@0: if (aFrame->IsFrameOfType(nsIFrame::eSVGGeometry) || michael@0: aFrame->IsSVGText()) { michael@0: // Unlike containers, leaf frames do not include GetPosition() in michael@0: // GetCanvasTM(). michael@0: overflowRect = overflowRect + aFrame->GetPosition(); michael@0: } michael@0: int32_t appUnitsPerDevPx = aFrame->PresContext()->AppUnitsPerDevPixel(); michael@0: gfxMatrix tm = GetCanvasTM(aFrame, nsISVGChildFrame::FOR_PAINTING, aTransformRoot); michael@0: if (aFrame->IsFrameOfType(nsIFrame::eSVG | nsIFrame::eSVGContainer)) { michael@0: gfx::Matrix childrenOnlyTM; michael@0: if (static_cast(aFrame)-> michael@0: HasChildrenOnlyTransform(&childrenOnlyTM)) { michael@0: // Undo the children-only transform: michael@0: tm = ThebesMatrix(childrenOnlyTM).Invert() * tm; michael@0: } michael@0: } michael@0: nsIntRect bounds = TransformFrameRectToOuterSVG(overflowRect, michael@0: tm, aFrame->PresContext()). michael@0: ToOutsidePixels(appUnitsPerDevPx); michael@0: if (!aDirtyRect->Intersects(bounds)) { michael@0: return; michael@0: } michael@0: } michael@0: michael@0: /* SVG defines the following rendering model: michael@0: * michael@0: * 1. Render fill michael@0: * 2. Render stroke michael@0: * 3. Render markers michael@0: * 4. Apply filter michael@0: * 5. Apply clipping, masking, group opacity michael@0: * michael@0: * We follow this, but perform a couple of optimizations: michael@0: * michael@0: * + Use cairo's clipPath when representable natively (single object michael@0: * clip region). michael@0: * michael@0: * + Merge opacity and masking if both used together. michael@0: */ michael@0: michael@0: if (opacity != 1.0f && CanOptimizeOpacity(aFrame)) michael@0: opacity = 1.0f; michael@0: michael@0: gfxContext *gfx = aContext->ThebesContext(); michael@0: bool complexEffects = false; michael@0: michael@0: nsSVGClipPathFrame *clipPathFrame = effectProperties.GetClipPathFrame(&isOK); michael@0: nsSVGMaskFrame *maskFrame = effectProperties.GetMaskFrame(&isOK); michael@0: michael@0: bool isTrivialClip = clipPathFrame ? clipPathFrame->IsTrivial() : true; michael@0: michael@0: if (!isOK) { michael@0: // Some resource is invalid. We shouldn't paint anything. michael@0: return; michael@0: } michael@0: michael@0: gfxMatrix matrix; michael@0: if (clipPathFrame || maskFrame) michael@0: matrix = GetCanvasTM(aFrame, nsISVGChildFrame::FOR_PAINTING, aTransformRoot); michael@0: michael@0: /* Check if we need to do additional operations on this child's michael@0: * rendering, which necessitates rendering into another surface. */ michael@0: if (opacity != 1.0f || maskFrame || (clipPathFrame && !isTrivialClip) michael@0: || aFrame->StyleDisplay()->mMixBlendMode != NS_STYLE_BLEND_NORMAL) { michael@0: complexEffects = true; michael@0: gfx->Save(); michael@0: if (!(aFrame->GetStateBits() & NS_FRAME_IS_NONDISPLAY)) { michael@0: // aFrame has a valid visual overflow rect, so clip to it before calling michael@0: // PushGroup() to minimize the size of the surfaces we'll composite: michael@0: gfxContextMatrixAutoSaveRestore matrixAutoSaveRestore(gfx); michael@0: gfx->Multiply(GetCanvasTM(aFrame, nsISVGChildFrame::FOR_PAINTING, aTransformRoot)); michael@0: nsRect overflowRect = aFrame->GetVisualOverflowRectRelativeToSelf(); michael@0: if (aFrame->IsFrameOfType(nsIFrame::eSVGGeometry) || michael@0: aFrame->IsSVGText()) { michael@0: // Unlike containers, leaf frames do not include GetPosition() in michael@0: // GetCanvasTM(). michael@0: overflowRect = overflowRect + aFrame->GetPosition(); michael@0: } michael@0: aContext->IntersectClip(overflowRect); michael@0: } michael@0: gfx->PushGroup(gfxContentType::COLOR_ALPHA); michael@0: } michael@0: michael@0: /* If this frame has only a trivial clipPath, set up cairo's clipping now so michael@0: * we can just do normal painting and get it clipped appropriately. michael@0: */ michael@0: if (clipPathFrame && isTrivialClip) { michael@0: gfx->Save(); michael@0: clipPathFrame->ClipPaint(aContext, aFrame, matrix); michael@0: } michael@0: michael@0: /* Paint the child */ michael@0: if (effectProperties.HasValidFilter()) { michael@0: nsRegion* dirtyRegion = nullptr; michael@0: nsRegion tmpDirtyRegion; michael@0: if (aDirtyRect) { michael@0: // aDirtyRect is in outer- device pixels, but the filter code needs michael@0: // it in frame space. michael@0: gfxMatrix userToDeviceSpace = michael@0: GetUserToCanvasTM(aFrame, nsISVGChildFrame::FOR_OUTERSVG_TM); michael@0: if (userToDeviceSpace.IsSingular()) { michael@0: return; michael@0: } michael@0: gfxMatrix deviceToUserSpace = userToDeviceSpace; michael@0: deviceToUserSpace.Invert(); michael@0: gfxRect dirtyBounds = deviceToUserSpace.TransformBounds( michael@0: gfxRect(aDirtyRect->x, aDirtyRect->y, michael@0: aDirtyRect->width, aDirtyRect->height)); michael@0: tmpDirtyRegion = michael@0: nsLayoutUtils::RoundGfxRectToAppRect( michael@0: dirtyBounds, aFrame->PresContext()->AppUnitsPerCSSPixel()) - michael@0: aFrame->GetPosition(); michael@0: dirtyRegion = &tmpDirtyRegion; michael@0: } michael@0: SVGPaintCallback paintCallback; michael@0: nsFilterInstance::PaintFilteredFrame(aContext, aFrame, &paintCallback, michael@0: dirtyRegion, aTransformRoot); michael@0: } else { michael@0: svgChildFrame->PaintSVG(aContext, aDirtyRect, aTransformRoot); michael@0: } michael@0: michael@0: if (clipPathFrame && isTrivialClip) { michael@0: gfx->Restore(); michael@0: } michael@0: michael@0: /* No more effects, we're done. */ michael@0: if (!complexEffects) michael@0: return; michael@0: michael@0: gfx->PopGroupToSource(); michael@0: michael@0: nsRefPtr maskSurface = michael@0: maskFrame ? maskFrame->ComputeMaskAlpha(aContext, aFrame, michael@0: matrix, opacity) : nullptr; michael@0: michael@0: nsRefPtr clipMaskSurface; michael@0: if (clipPathFrame && !isTrivialClip) { michael@0: gfx->PushGroup(gfxContentType::COLOR_ALPHA); michael@0: michael@0: nsresult rv = clipPathFrame->ClipPaint(aContext, aFrame, matrix); michael@0: clipMaskSurface = gfx->PopGroup(); michael@0: michael@0: if (NS_SUCCEEDED(rv) && clipMaskSurface) { michael@0: // Still more set after clipping, so clip to another surface michael@0: if (maskSurface || opacity != 1.0f) { michael@0: gfx->PushGroup(gfxContentType::COLOR_ALPHA); michael@0: gfx->Mask(clipMaskSurface); michael@0: gfx->PopGroupToSource(); michael@0: } else { michael@0: gfx->Mask(clipMaskSurface); michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (maskSurface) { michael@0: gfx->Mask(maskSurface); michael@0: } else if (opacity != 1.0f) { michael@0: gfx->Paint(opacity); michael@0: } michael@0: michael@0: gfx->Restore(); michael@0: } michael@0: michael@0: bool michael@0: nsSVGUtils::HitTestClip(nsIFrame *aFrame, const nsPoint &aPoint) michael@0: { michael@0: nsSVGEffects::EffectProperties props = michael@0: nsSVGEffects::GetEffectProperties(aFrame); michael@0: if (!props.mClipPath) michael@0: return true; michael@0: michael@0: bool isOK = true; michael@0: nsSVGClipPathFrame *clipPathFrame = props.GetClipPathFrame(&isOK); michael@0: if (!isOK) { michael@0: // clipPath is not a valid resource, so nothing gets painted, so michael@0: // hit-testing must fail. michael@0: return false; michael@0: } michael@0: if (!clipPathFrame) { michael@0: // clipPath doesn't exist, ignore it. michael@0: return true; michael@0: } michael@0: michael@0: return clipPathFrame->ClipHitTest(aFrame, GetCanvasTM(aFrame, michael@0: nsISVGChildFrame::FOR_HIT_TESTING), aPoint); michael@0: } michael@0: michael@0: nsIFrame * michael@0: nsSVGUtils::HitTestChildren(nsIFrame *aFrame, const nsPoint &aPoint) michael@0: { michael@0: // Traverse the list in reverse order, so that if we get a hit we know that's michael@0: // the topmost frame that intersects the point; then we can just return it. michael@0: nsIFrame* result = nullptr; michael@0: for (nsIFrame* current = aFrame->PrincipalChildList().LastChild(); michael@0: current; michael@0: current = current->GetPrevSibling()) { michael@0: nsISVGChildFrame* SVGFrame = do_QueryFrame(current); michael@0: if (SVGFrame) { michael@0: const nsIContent* content = current->GetContent(); michael@0: if (content->IsSVG() && michael@0: !static_cast(content)->HasValidDimensions()) { michael@0: continue; michael@0: } michael@0: result = SVGFrame->GetFrameForPoint(aPoint); michael@0: if (result) michael@0: break; michael@0: } michael@0: } michael@0: michael@0: if (result && !HitTestClip(aFrame, aPoint)) michael@0: result = nullptr; michael@0: michael@0: return result; michael@0: } michael@0: michael@0: nsRect michael@0: nsSVGUtils::GetCoveredRegion(const nsFrameList &aFrames) michael@0: { michael@0: nsRect rect; michael@0: michael@0: for (nsIFrame* kid = aFrames.FirstChild(); michael@0: kid; michael@0: kid = kid->GetNextSibling()) { michael@0: nsISVGChildFrame* child = do_QueryFrame(kid); michael@0: if (child) { michael@0: nsRect childRect = child->GetCoveredRegion(); michael@0: rect.UnionRect(rect, childRect); michael@0: } michael@0: } michael@0: michael@0: return rect; michael@0: } michael@0: michael@0: nsPoint michael@0: nsSVGUtils::TransformOuterSVGPointToChildFrame(nsPoint aPoint, michael@0: const gfxMatrix& aFrameToCanvasTM, michael@0: nsPresContext* aPresContext) michael@0: { michael@0: NS_ABORT_IF_FALSE(!aFrameToCanvasTM.IsSingular(), michael@0: "Callers must not pass a singular matrix"); michael@0: gfxMatrix canvasDevToFrameUserSpace = aFrameToCanvasTM; michael@0: canvasDevToFrameUserSpace.Invert(); michael@0: gfxPoint devPt = gfxPoint(aPoint.x, aPoint.y) / michael@0: aPresContext->AppUnitsPerDevPixel(); michael@0: gfxPoint userPt = canvasDevToFrameUserSpace.Transform(devPt); michael@0: gfxPoint appPt = (userPt * aPresContext->AppUnitsPerCSSPixel()).Round(); michael@0: userPt.x = clamped(appPt.x, gfxFloat(nscoord_MIN), gfxFloat(nscoord_MAX)); michael@0: userPt.y = clamped(appPt.y, gfxFloat(nscoord_MIN), gfxFloat(nscoord_MAX)); michael@0: // now guaranteed to be safe: michael@0: return nsPoint(nscoord(userPt.x), nscoord(userPt.y)); michael@0: } michael@0: michael@0: nsRect michael@0: nsSVGUtils::TransformFrameRectToOuterSVG(const nsRect& aRect, michael@0: const gfxMatrix& aMatrix, michael@0: nsPresContext* aPresContext) michael@0: { michael@0: gfxRect r(aRect.x, aRect.y, aRect.width, aRect.height); michael@0: r.Scale(1.0 / nsPresContext::AppUnitsPerCSSPixel()); michael@0: return nsLayoutUtils::RoundGfxRectToAppRect( michael@0: aMatrix.TransformBounds(r), aPresContext->AppUnitsPerDevPixel()); michael@0: } michael@0: michael@0: gfxIntSize michael@0: nsSVGUtils::ConvertToSurfaceSize(const gfxSize& aSize, michael@0: bool *aResultOverflows) michael@0: { michael@0: gfxIntSize surfaceSize(ClampToInt(ceil(aSize.width)), ClampToInt(ceil(aSize.height))); michael@0: michael@0: *aResultOverflows = surfaceSize.width != ceil(aSize.width) || michael@0: surfaceSize.height != ceil(aSize.height); michael@0: michael@0: if (!gfxASurface::CheckSurfaceSize(surfaceSize)) { michael@0: surfaceSize.width = std::min(NS_SVG_OFFSCREEN_MAX_DIMENSION, michael@0: surfaceSize.width); michael@0: surfaceSize.height = std::min(NS_SVG_OFFSCREEN_MAX_DIMENSION, michael@0: surfaceSize.height); michael@0: *aResultOverflows = true; michael@0: } michael@0: michael@0: return surfaceSize; michael@0: } michael@0: michael@0: bool michael@0: nsSVGUtils::HitTestRect(const gfx::Matrix &aMatrix, michael@0: float aRX, float aRY, float aRWidth, float aRHeight, michael@0: float aX, float aY) michael@0: { michael@0: gfx::Rect rect(aRX, aRY, aRWidth, aRHeight); michael@0: if (rect.IsEmpty() || aMatrix.IsSingular()) { michael@0: return false; michael@0: } michael@0: gfx::Matrix toRectSpace = aMatrix; michael@0: toRectSpace.Invert(); michael@0: gfx::Point p = toRectSpace * gfx::Point(aX, aY); michael@0: return rect.x <= p.x && p.x <= rect.XMost() && michael@0: rect.y <= p.y && p.y <= rect.YMost(); michael@0: } michael@0: michael@0: gfxRect michael@0: nsSVGUtils::GetClipRectForFrame(nsIFrame *aFrame, michael@0: float aX, float aY, float aWidth, float aHeight) michael@0: { michael@0: const nsStyleDisplay* disp = aFrame->StyleDisplay(); michael@0: michael@0: if (!(disp->mClipFlags & NS_STYLE_CLIP_RECT)) { michael@0: NS_ASSERTION(disp->mClipFlags == NS_STYLE_CLIP_AUTO, michael@0: "We don't know about this type of clip."); michael@0: return gfxRect(aX, aY, aWidth, aHeight); michael@0: } michael@0: michael@0: if (disp->mOverflowX == NS_STYLE_OVERFLOW_HIDDEN || michael@0: disp->mOverflowY == NS_STYLE_OVERFLOW_HIDDEN) { michael@0: michael@0: nsIntRect clipPxRect = michael@0: disp->mClip.ToOutsidePixels(aFrame->PresContext()->AppUnitsPerDevPixel()); michael@0: gfxRect clipRect = michael@0: gfxRect(clipPxRect.x, clipPxRect.y, clipPxRect.width, clipPxRect.height); michael@0: michael@0: if (NS_STYLE_CLIP_RIGHT_AUTO & disp->mClipFlags) { michael@0: clipRect.width = aWidth - clipRect.X(); michael@0: } michael@0: if (NS_STYLE_CLIP_BOTTOM_AUTO & disp->mClipFlags) { michael@0: clipRect.height = aHeight - clipRect.Y(); michael@0: } michael@0: michael@0: if (disp->mOverflowX != NS_STYLE_OVERFLOW_HIDDEN) { michael@0: clipRect.x = aX; michael@0: clipRect.width = aWidth; michael@0: } michael@0: if (disp->mOverflowY != NS_STYLE_OVERFLOW_HIDDEN) { michael@0: clipRect.y = aY; michael@0: clipRect.height = aHeight; michael@0: } michael@0: michael@0: return clipRect; michael@0: } michael@0: return gfxRect(aX, aY, aWidth, aHeight); michael@0: } michael@0: michael@0: void michael@0: nsSVGUtils::SetClipRect(gfxContext *aContext, michael@0: const gfxMatrix &aCTM, michael@0: const gfxRect &aRect) michael@0: { michael@0: if (aCTM.IsSingular()) michael@0: return; michael@0: michael@0: gfxContextMatrixAutoSaveRestore matrixAutoSaveRestore(aContext); michael@0: aContext->Multiply(aCTM); michael@0: aContext->Clip(aRect); michael@0: } michael@0: michael@0: gfxRect michael@0: nsSVGUtils::GetBBox(nsIFrame *aFrame, uint32_t aFlags) michael@0: { michael@0: if (aFrame->GetContent()->IsNodeOfType(nsINode::eTEXT)) { michael@0: aFrame = aFrame->GetParent(); michael@0: } michael@0: gfxRect bbox; michael@0: nsISVGChildFrame *svg = do_QueryFrame(aFrame); michael@0: if (svg || aFrame->IsSVGText()) { michael@0: // It is possible to apply a gradient, pattern, clipping path, mask or michael@0: // filter to text. When one of these facilities is applied to text michael@0: // the bounding box is the entire text element in all michael@0: // cases. michael@0: if (aFrame->IsSVGText()) { michael@0: nsIFrame* ancestor = GetFirstNonAAncestorFrame(aFrame); michael@0: if (ancestor && ancestor->IsSVGText()) { michael@0: while (ancestor->GetType() != nsGkAtoms::svgTextFrame) { michael@0: ancestor = ancestor->GetParent(); michael@0: } michael@0: } michael@0: svg = do_QueryFrame(ancestor); michael@0: } michael@0: nsIContent* content = aFrame->GetContent(); michael@0: if (content->IsSVG() && michael@0: !static_cast(content)->HasValidDimensions()) { michael@0: return bbox; michael@0: } michael@0: gfxMatrix matrix; michael@0: if (aFrame->GetType() == nsGkAtoms::svgForeignObjectFrame) { michael@0: // The spec says getBBox "Returns the tight bounding box in *current user michael@0: // space*". So we should really be doing this for all elements, but that michael@0: // needs investigation to check that we won't break too much content. michael@0: // NOTE: When changing this to apply to other frame types, make sure to michael@0: // also update nsSVGUtils::FrameSpaceInCSSPxToUserSpaceOffset. michael@0: NS_ABORT_IF_FALSE(content->IsSVG(), "bad cast"); michael@0: nsSVGElement *element = static_cast(content); michael@0: matrix = element->PrependLocalTransformsTo(matrix, michael@0: nsSVGElement::eChildToUserSpace); michael@0: } michael@0: return svg->GetBBoxContribution(ToMatrix(matrix), aFlags).ToThebesRect(); michael@0: } michael@0: return nsSVGIntegrationUtils::GetSVGBBoxForNonSVGFrame(aFrame); michael@0: } michael@0: michael@0: gfxPoint michael@0: nsSVGUtils::FrameSpaceInCSSPxToUserSpaceOffset(nsIFrame *aFrame) michael@0: { michael@0: if (!(aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT)) { michael@0: // The user space for non-SVG frames is defined as the bounding box of the michael@0: // frame's border-box rects over all continuations. michael@0: return gfxPoint(); michael@0: } michael@0: michael@0: // Leaf frames apply their own offset inside their user space. michael@0: if (aFrame->IsFrameOfType(nsIFrame::eSVGGeometry) || michael@0: aFrame->IsSVGText()) { michael@0: return nsLayoutUtils::RectToGfxRect(aFrame->GetRect(), michael@0: nsPresContext::AppUnitsPerCSSPixel()).TopLeft(); michael@0: } michael@0: michael@0: // For foreignObject frames, nsSVGUtils::GetBBox applies their local michael@0: // transform, so we need to do the same here. michael@0: if (aFrame->GetType() == nsGkAtoms::svgForeignObjectFrame) { michael@0: gfxMatrix transform = static_cast(aFrame->GetContent())-> michael@0: PrependLocalTransformsTo(gfxMatrix(), michael@0: nsSVGElement::eChildToUserSpace); michael@0: NS_ASSERTION(!transform.HasNonTranslation(), "we're relying on this being an offset-only transform"); michael@0: return transform.GetTranslation(); michael@0: } michael@0: michael@0: return gfxPoint(); michael@0: } michael@0: michael@0: gfxRect michael@0: nsSVGUtils::GetRelativeRect(uint16_t aUnits, const nsSVGLength2 *aXYWH, michael@0: const gfxRect &aBBox, nsIFrame *aFrame) michael@0: { michael@0: float x, y, width, height; michael@0: if (aUnits == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) { michael@0: x = aBBox.X() + ObjectSpace(aBBox, &aXYWH[0]); michael@0: y = aBBox.Y() + ObjectSpace(aBBox, &aXYWH[1]); michael@0: width = ObjectSpace(aBBox, &aXYWH[2]); michael@0: height = ObjectSpace(aBBox, &aXYWH[3]); michael@0: } else { michael@0: x = UserSpace(aFrame, &aXYWH[0]); michael@0: y = UserSpace(aFrame, &aXYWH[1]); michael@0: width = UserSpace(aFrame, &aXYWH[2]); michael@0: height = UserSpace(aFrame, &aXYWH[3]); michael@0: } michael@0: return gfxRect(x, y, width, height); michael@0: } michael@0: michael@0: bool michael@0: nsSVGUtils::CanOptimizeOpacity(nsIFrame *aFrame) michael@0: { michael@0: if (!(aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT)) { michael@0: return false; michael@0: } michael@0: nsIAtom *type = aFrame->GetType(); michael@0: if (type != nsGkAtoms::svgImageFrame && michael@0: type != nsGkAtoms::svgPathGeometryFrame) { michael@0: return false; michael@0: } michael@0: if (aFrame->StyleSVGReset()->HasFilters()) { michael@0: return false; michael@0: } michael@0: // XXX The SVG WG is intending to allow fill, stroke and markers on michael@0: if (type == nsGkAtoms::svgImageFrame) { michael@0: return true; michael@0: } michael@0: const nsStyleSVG *style = aFrame->StyleSVG(); michael@0: if (style->HasMarker()) { michael@0: return false; michael@0: } michael@0: if (!style->HasFill() || !HasStroke(aFrame)) { michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: gfxMatrix michael@0: nsSVGUtils::AdjustMatrixForUnits(const gfxMatrix &aMatrix, michael@0: nsSVGEnum *aUnits, michael@0: nsIFrame *aFrame) michael@0: { michael@0: if (aFrame && michael@0: aUnits->GetAnimValue() == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) { michael@0: gfxRect bbox = GetBBox(aFrame); michael@0: return gfxMatrix().Scale(bbox.Width(), bbox.Height()) * michael@0: gfxMatrix().Translate(gfxPoint(bbox.X(), bbox.Y())) * michael@0: aMatrix; michael@0: } michael@0: return aMatrix; michael@0: } michael@0: michael@0: nsIFrame* michael@0: nsSVGUtils::GetFirstNonAAncestorFrame(nsIFrame* aStartFrame) michael@0: { michael@0: for (nsIFrame *ancestorFrame = aStartFrame; ancestorFrame; michael@0: ancestorFrame = ancestorFrame->GetParent()) { michael@0: if (ancestorFrame->GetType() != nsGkAtoms::svgAFrame) { michael@0: return ancestorFrame; michael@0: } michael@0: } michael@0: return nullptr; michael@0: } michael@0: michael@0: gfxMatrix michael@0: nsSVGUtils::GetStrokeTransform(nsIFrame *aFrame) michael@0: { michael@0: if (aFrame->GetContent()->IsNodeOfType(nsINode::eTEXT)) { michael@0: aFrame = aFrame->GetParent(); michael@0: } michael@0: michael@0: if (aFrame->StyleSVGReset()->mVectorEffect == michael@0: NS_STYLE_VECTOR_EFFECT_NON_SCALING_STROKE) { michael@0: michael@0: nsIContent *content = aFrame->GetContent(); michael@0: NS_ABORT_IF_FALSE(content->IsSVG(), "bad cast"); michael@0: michael@0: // a non-scaling stroke is in the screen co-ordinate michael@0: // space rather so we need to invert the transform michael@0: // to the screen co-ordinate space to get there. michael@0: // See http://www.w3.org/TR/SVGTiny12/painting.html#NonScalingStroke michael@0: gfx::Matrix transform = SVGContentUtils::GetCTM( michael@0: static_cast(content), true); michael@0: if (!transform.IsSingular()) { michael@0: transform.Invert(); michael@0: return ThebesMatrix(transform); michael@0: } michael@0: } michael@0: return gfxMatrix(); michael@0: } michael@0: michael@0: // The logic here comes from _cairo_stroke_style_max_distance_from_path michael@0: static gfxRect michael@0: PathExtentsToMaxStrokeExtents(const gfxRect& aPathExtents, michael@0: nsIFrame* aFrame, michael@0: double aStyleExpansionFactor, michael@0: const gfxMatrix& aMatrix) michael@0: { michael@0: double style_expansion = michael@0: aStyleExpansionFactor * nsSVGUtils::GetStrokeWidth(aFrame); michael@0: michael@0: gfxMatrix matrix = aMatrix; michael@0: matrix.Multiply(nsSVGUtils::GetStrokeTransform(aFrame)); michael@0: michael@0: double dx = style_expansion * (fabs(matrix.xx) + fabs(matrix.xy)); michael@0: double dy = style_expansion * (fabs(matrix.yy) + fabs(matrix.yx)); michael@0: michael@0: gfxRect strokeExtents = aPathExtents; michael@0: strokeExtents.Inflate(dx, dy); michael@0: return strokeExtents; michael@0: } michael@0: michael@0: /*static*/ gfxRect michael@0: nsSVGUtils::PathExtentsToMaxStrokeExtents(const gfxRect& aPathExtents, michael@0: nsTextFrame* aFrame, michael@0: const gfxMatrix& aMatrix) michael@0: { michael@0: NS_ASSERTION(aFrame->IsSVGText(), "expected an nsTextFrame for SVG text"); michael@0: return ::PathExtentsToMaxStrokeExtents(aPathExtents, aFrame, 0.5, aMatrix); michael@0: } michael@0: michael@0: /*static*/ gfxRect michael@0: nsSVGUtils::PathExtentsToMaxStrokeExtents(const gfxRect& aPathExtents, michael@0: nsSVGPathGeometryFrame* aFrame, michael@0: const gfxMatrix& aMatrix) michael@0: { michael@0: double styleExpansionFactor = 0.5; michael@0: michael@0: if (static_cast(aFrame->GetContent())->IsMarkable()) { michael@0: const nsStyleSVG* style = aFrame->StyleSVG(); michael@0: michael@0: if (style->mStrokeLinecap == NS_STYLE_STROKE_LINECAP_SQUARE) { michael@0: styleExpansionFactor = M_SQRT1_2; michael@0: } michael@0: michael@0: if (style->mStrokeLinejoin == NS_STYLE_STROKE_LINEJOIN_MITER && michael@0: styleExpansionFactor < style->mStrokeMiterlimit && michael@0: aFrame->GetContent()->Tag() != nsGkAtoms::line) { michael@0: styleExpansionFactor = style->mStrokeMiterlimit; michael@0: } michael@0: } michael@0: michael@0: return ::PathExtentsToMaxStrokeExtents(aPathExtents, michael@0: aFrame, michael@0: styleExpansionFactor, michael@0: aMatrix); michael@0: } michael@0: michael@0: // ---------------------------------------------------------------------- michael@0: michael@0: /* static */ nscolor michael@0: nsSVGUtils::GetFallbackOrPaintColor(gfxContext *aContext, nsStyleContext *aStyleContext, michael@0: nsStyleSVGPaint nsStyleSVG::*aFillOrStroke) michael@0: { michael@0: const nsStyleSVGPaint &paint = aStyleContext->StyleSVG()->*aFillOrStroke; michael@0: nsStyleContext *styleIfVisited = aStyleContext->GetStyleIfVisited(); michael@0: bool isServer = paint.mType == eStyleSVGPaintType_Server || michael@0: paint.mType == eStyleSVGPaintType_ContextFill || michael@0: paint.mType == eStyleSVGPaintType_ContextStroke; michael@0: nscolor color = isServer ? paint.mFallbackColor : paint.mPaint.mColor; michael@0: if (styleIfVisited) { michael@0: const nsStyleSVGPaint &paintIfVisited = michael@0: styleIfVisited->StyleSVG()->*aFillOrStroke; michael@0: // To prevent Web content from detecting if a user has visited a URL michael@0: // (via URL loading triggered by paint servers or performance michael@0: // differences between paint servers or between a paint server and a michael@0: // color), we do not allow whether links are visited to change which michael@0: // paint server is used or switch between paint servers and simple michael@0: // colors. A :visited style may only override a simple color with michael@0: // another simple color. michael@0: if (paintIfVisited.mType == eStyleSVGPaintType_Color && michael@0: paint.mType == eStyleSVGPaintType_Color) { michael@0: nscolor colors[2] = { color, paintIfVisited.mPaint.mColor }; michael@0: return nsStyleContext::CombineVisitedColors( michael@0: colors, aStyleContext->RelevantLinkVisited()); michael@0: } michael@0: } michael@0: return color; michael@0: } michael@0: michael@0: static void michael@0: SetupFallbackOrPaintColor(gfxContext *aContext, nsStyleContext *aStyleContext, michael@0: nsStyleSVGPaint nsStyleSVG::*aFillOrStroke, michael@0: float aOpacity) michael@0: { michael@0: nscolor color = nsSVGUtils::GetFallbackOrPaintColor( michael@0: aContext, aStyleContext, aFillOrStroke); michael@0: michael@0: aContext->SetColor(gfxRGBA(NS_GET_R(color)/255.0, michael@0: NS_GET_G(color)/255.0, michael@0: NS_GET_B(color)/255.0, michael@0: NS_GET_A(color)/255.0 * aOpacity)); michael@0: } michael@0: michael@0: static float michael@0: MaybeOptimizeOpacity(nsIFrame *aFrame, float aFillOrStrokeOpacity) michael@0: { michael@0: float opacity = aFrame->StyleDisplay()->mOpacity; michael@0: if (opacity < 1 && nsSVGUtils::CanOptimizeOpacity(aFrame)) { michael@0: return aFillOrStrokeOpacity * opacity; michael@0: } michael@0: return aFillOrStrokeOpacity; michael@0: } michael@0: michael@0: /* static */ bool michael@0: nsSVGUtils::SetupContextPaint(gfxContext *aContext, michael@0: gfxTextContextPaint *aContextPaint, michael@0: const nsStyleSVGPaint &aPaint, michael@0: float aOpacity) michael@0: { michael@0: nsRefPtr pattern; michael@0: michael@0: if (!aContextPaint) { michael@0: return false; michael@0: } michael@0: michael@0: switch (aPaint.mType) { michael@0: case eStyleSVGPaintType_ContextFill: michael@0: pattern = aContextPaint->GetFillPattern(aOpacity, aContext->CurrentMatrix()); michael@0: break; michael@0: case eStyleSVGPaintType_ContextStroke: michael@0: pattern = aContextPaint->GetStrokePattern(aOpacity, aContext->CurrentMatrix()); michael@0: break; michael@0: default: michael@0: return false; michael@0: } michael@0: michael@0: if (!pattern) { michael@0: return false; michael@0: } michael@0: michael@0: aContext->SetPattern(pattern); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: nsSVGUtils::SetupCairoFillPaint(nsIFrame *aFrame, gfxContext* aContext, michael@0: gfxTextContextPaint *aContextPaint) michael@0: { michael@0: const nsStyleSVG* style = aFrame->StyleSVG(); michael@0: if (style->mFill.mType == eStyleSVGPaintType_None) michael@0: return false; michael@0: michael@0: if (style->mFillRule == NS_STYLE_FILL_RULE_EVENODD) michael@0: aContext->SetFillRule(gfxContext::FILL_RULE_EVEN_ODD); michael@0: else michael@0: aContext->SetFillRule(gfxContext::FILL_RULE_WINDING); michael@0: michael@0: float opacity = MaybeOptimizeOpacity(aFrame, michael@0: GetOpacity(style->mFillOpacitySource, michael@0: style->mFillOpacity, michael@0: aContextPaint)); michael@0: nsSVGPaintServerFrame *ps = michael@0: nsSVGEffects::GetPaintServer(aFrame, &style->mFill, nsSVGEffects::FillProperty()); michael@0: if (ps && ps->SetupPaintServer(aContext, aFrame, &nsStyleSVG::mFill, opacity)) michael@0: return true; michael@0: michael@0: if (SetupContextPaint(aContext, aContextPaint, style->mFill, opacity)) { michael@0: return true; michael@0: } michael@0: michael@0: // On failure, use the fallback colour in case we have an michael@0: // objectBoundingBox where the width or height of the object is zero. michael@0: // See http://www.w3.org/TR/SVG11/coords.html#ObjectBoundingBox michael@0: SetupFallbackOrPaintColor(aContext, aFrame->StyleContext(), michael@0: &nsStyleSVG::mFill, opacity); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: nsSVGUtils::SetupCairoStrokePaint(nsIFrame *aFrame, gfxContext* aContext, michael@0: gfxTextContextPaint *aContextPaint) michael@0: { michael@0: const nsStyleSVG* style = aFrame->StyleSVG(); michael@0: if (style->mStroke.mType == eStyleSVGPaintType_None) michael@0: return false; michael@0: michael@0: float opacity = MaybeOptimizeOpacity(aFrame, michael@0: GetOpacity(style->mStrokeOpacitySource, michael@0: style->mStrokeOpacity, michael@0: aContextPaint)); michael@0: michael@0: nsSVGPaintServerFrame *ps = michael@0: nsSVGEffects::GetPaintServer(aFrame, &style->mStroke, nsSVGEffects::StrokeProperty()); michael@0: if (ps && ps->SetupPaintServer(aContext, aFrame, &nsStyleSVG::mStroke, opacity)) michael@0: return true; michael@0: michael@0: if (SetupContextPaint(aContext, aContextPaint, style->mStroke, opacity)) { michael@0: return true; michael@0: } michael@0: michael@0: // On failure, use the fallback colour in case we have an michael@0: // objectBoundingBox where the width or height of the object is zero. michael@0: // See http://www.w3.org/TR/SVG11/coords.html#ObjectBoundingBox michael@0: SetupFallbackOrPaintColor(aContext, aFrame->StyleContext(), michael@0: &nsStyleSVG::mStroke, opacity); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: /* static */ float michael@0: nsSVGUtils::GetOpacity(nsStyleSVGOpacitySource aOpacityType, michael@0: const float& aOpacity, michael@0: gfxTextContextPaint *aOuterContextPaint) michael@0: { michael@0: float opacity = 1.0f; michael@0: switch (aOpacityType) { michael@0: case eStyleSVGOpacitySource_Normal: michael@0: opacity = aOpacity; michael@0: break; michael@0: case eStyleSVGOpacitySource_ContextFillOpacity: michael@0: if (aOuterContextPaint) { michael@0: opacity = aOuterContextPaint->GetFillOpacity(); michael@0: } else { michael@0: NS_WARNING("context-fill-opacity used outside of an SVG glyph"); michael@0: } michael@0: break; michael@0: case eStyleSVGOpacitySource_ContextStrokeOpacity: michael@0: if (aOuterContextPaint) { michael@0: opacity = aOuterContextPaint->GetStrokeOpacity(); michael@0: } else { michael@0: NS_WARNING("context-stroke-opacity used outside of an SVG glyph"); michael@0: } michael@0: break; michael@0: default: michael@0: NS_NOTREACHED("Unknown object opacity inheritance type for SVG glyph"); michael@0: } michael@0: return opacity; michael@0: } michael@0: michael@0: bool michael@0: nsSVGUtils::HasStroke(nsIFrame* aFrame, gfxTextContextPaint *aContextPaint) michael@0: { michael@0: const nsStyleSVG *style = aFrame->StyleSVG(); michael@0: return style->HasStroke() && GetStrokeWidth(aFrame, aContextPaint) > 0; michael@0: } michael@0: michael@0: float michael@0: nsSVGUtils::GetStrokeWidth(nsIFrame* aFrame, gfxTextContextPaint *aContextPaint) michael@0: { michael@0: const nsStyleSVG *style = aFrame->StyleSVG(); michael@0: if (aContextPaint && style->mStrokeWidthFromObject) { michael@0: return aContextPaint->GetStrokeWidth(); michael@0: } michael@0: michael@0: nsIContent* content = aFrame->GetContent(); michael@0: if (content->IsNodeOfType(nsINode::eTEXT)) { michael@0: content = content->GetParent(); michael@0: } michael@0: michael@0: nsSVGElement *ctx = static_cast(content); michael@0: michael@0: return SVGContentUtils::CoordToFloat(aFrame->PresContext(), ctx, michael@0: style->mStrokeWidth); michael@0: } michael@0: michael@0: void michael@0: nsSVGUtils::SetupCairoStrokeBBoxGeometry(nsIFrame* aFrame, michael@0: gfxContext *aContext, michael@0: gfxTextContextPaint *aContextPaint) michael@0: { michael@0: float width = GetStrokeWidth(aFrame, aContextPaint); michael@0: if (width <= 0) michael@0: return; michael@0: aContext->SetLineWidth(width); michael@0: michael@0: // Apply any stroke-specific transform michael@0: gfxMatrix strokeTransform = GetStrokeTransform(aFrame); michael@0: if (!strokeTransform.IsIdentity()) { michael@0: aContext->Multiply(strokeTransform); michael@0: } michael@0: michael@0: const nsStyleSVG* style = aFrame->StyleSVG(); michael@0: michael@0: switch (style->mStrokeLinecap) { michael@0: case NS_STYLE_STROKE_LINECAP_BUTT: michael@0: aContext->SetLineCap(gfxContext::LINE_CAP_BUTT); michael@0: break; michael@0: case NS_STYLE_STROKE_LINECAP_ROUND: michael@0: aContext->SetLineCap(gfxContext::LINE_CAP_ROUND); michael@0: break; michael@0: case NS_STYLE_STROKE_LINECAP_SQUARE: michael@0: aContext->SetLineCap(gfxContext::LINE_CAP_SQUARE); michael@0: break; michael@0: } michael@0: michael@0: aContext->SetMiterLimit(style->mStrokeMiterlimit); michael@0: michael@0: switch (style->mStrokeLinejoin) { michael@0: case NS_STYLE_STROKE_LINEJOIN_MITER: michael@0: aContext->SetLineJoin(gfxContext::LINE_JOIN_MITER); michael@0: break; michael@0: case NS_STYLE_STROKE_LINEJOIN_ROUND: michael@0: aContext->SetLineJoin(gfxContext::LINE_JOIN_ROUND); michael@0: break; michael@0: case NS_STYLE_STROKE_LINEJOIN_BEVEL: michael@0: aContext->SetLineJoin(gfxContext::LINE_JOIN_BEVEL); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: static bool michael@0: GetStrokeDashData(nsIFrame* aFrame, michael@0: FallibleTArray& aDashes, michael@0: gfxFloat* aDashOffset, michael@0: gfxTextContextPaint *aContextPaint) michael@0: { michael@0: const nsStyleSVG* style = aFrame->StyleSVG(); michael@0: nsPresContext *presContext = aFrame->PresContext(); michael@0: nsIContent *content = aFrame->GetContent(); michael@0: nsSVGElement *ctx = static_cast michael@0: (content->IsNodeOfType(nsINode::eTEXT) ? michael@0: content->GetParent() : content); michael@0: michael@0: gfxFloat totalLength = 0.0; michael@0: if (aContextPaint && style->mStrokeDasharrayFromObject) { michael@0: aDashes = aContextPaint->GetStrokeDashArray(); michael@0: michael@0: for (uint32_t i = 0; i < aDashes.Length(); i++) { michael@0: if (aDashes[i] < 0.0) { michael@0: return false; michael@0: } michael@0: totalLength += aDashes[i]; michael@0: } michael@0: michael@0: } else { michael@0: uint32_t count = style->mStrokeDasharrayLength; michael@0: if (!count || !aDashes.SetLength(count)) { michael@0: return false; michael@0: } michael@0: michael@0: gfxFloat pathScale = 1.0; michael@0: michael@0: if (content->Tag() == nsGkAtoms::path) { michael@0: pathScale = static_cast(content)-> michael@0: GetPathLengthScale(SVGPathElement::eForStroking); michael@0: if (pathScale <= 0) { michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: const nsStyleCoord *dasharray = style->mStrokeDasharray; michael@0: michael@0: for (uint32_t i = 0; i < count; i++) { michael@0: aDashes[i] = SVGContentUtils::CoordToFloat(presContext, michael@0: ctx, michael@0: dasharray[i]) * pathScale; michael@0: if (aDashes[i] < 0.0) { michael@0: return false; michael@0: } michael@0: totalLength += aDashes[i]; michael@0: } michael@0: } michael@0: michael@0: if (aContextPaint && style->mStrokeDashoffsetFromObject) { michael@0: *aDashOffset = aContextPaint->GetStrokeDashOffset(); michael@0: } else { michael@0: *aDashOffset = SVGContentUtils::CoordToFloat(presContext, michael@0: ctx, michael@0: style->mStrokeDashoffset); michael@0: } michael@0: michael@0: return (totalLength > 0.0); michael@0: } michael@0: michael@0: void michael@0: nsSVGUtils::SetupCairoStrokeGeometry(nsIFrame* aFrame, gfxContext* aContext, michael@0: gfxTextContextPaint *aContextPaint) michael@0: { michael@0: SetupCairoStrokeBBoxGeometry(aFrame, aContext, aContextPaint); michael@0: michael@0: AutoFallibleTArray dashes; michael@0: gfxFloat dashOffset; michael@0: if (GetStrokeDashData(aFrame, dashes, &dashOffset, aContextPaint)) { michael@0: aContext->SetDash(dashes.Elements(), dashes.Length(), dashOffset); michael@0: } michael@0: } michael@0: michael@0: uint16_t michael@0: nsSVGUtils::GetGeometryHitTestFlags(nsIFrame* aFrame) michael@0: { michael@0: uint16_t flags = 0; michael@0: michael@0: switch(aFrame->StyleVisibility()->mPointerEvents) { michael@0: case NS_STYLE_POINTER_EVENTS_NONE: michael@0: break; michael@0: case NS_STYLE_POINTER_EVENTS_AUTO: michael@0: case NS_STYLE_POINTER_EVENTS_VISIBLEPAINTED: michael@0: if (aFrame->StyleVisibility()->IsVisible()) { michael@0: if (aFrame->StyleSVG()->mFill.mType != eStyleSVGPaintType_None) michael@0: flags |= SVG_HIT_TEST_FILL; michael@0: if (aFrame->StyleSVG()->mStroke.mType != eStyleSVGPaintType_None) michael@0: flags |= SVG_HIT_TEST_STROKE; michael@0: if (aFrame->StyleSVG()->mStrokeOpacity > 0) michael@0: flags |= SVG_HIT_TEST_CHECK_MRECT; michael@0: } michael@0: break; michael@0: case NS_STYLE_POINTER_EVENTS_VISIBLEFILL: michael@0: if (aFrame->StyleVisibility()->IsVisible()) { michael@0: flags |= SVG_HIT_TEST_FILL; michael@0: } michael@0: break; michael@0: case NS_STYLE_POINTER_EVENTS_VISIBLESTROKE: michael@0: if (aFrame->StyleVisibility()->IsVisible()) { michael@0: flags |= SVG_HIT_TEST_STROKE; michael@0: } michael@0: break; michael@0: case NS_STYLE_POINTER_EVENTS_VISIBLE: michael@0: if (aFrame->StyleVisibility()->IsVisible()) { michael@0: flags |= SVG_HIT_TEST_FILL | SVG_HIT_TEST_STROKE; michael@0: } michael@0: break; michael@0: case NS_STYLE_POINTER_EVENTS_PAINTED: michael@0: if (aFrame->StyleSVG()->mFill.mType != eStyleSVGPaintType_None) michael@0: flags |= SVG_HIT_TEST_FILL; michael@0: if (aFrame->StyleSVG()->mStroke.mType != eStyleSVGPaintType_None) michael@0: flags |= SVG_HIT_TEST_STROKE; michael@0: if (aFrame->StyleSVG()->mStrokeOpacity) michael@0: flags |= SVG_HIT_TEST_CHECK_MRECT; michael@0: break; michael@0: case NS_STYLE_POINTER_EVENTS_FILL: michael@0: flags |= SVG_HIT_TEST_FILL; michael@0: break; michael@0: case NS_STYLE_POINTER_EVENTS_STROKE: michael@0: flags |= SVG_HIT_TEST_STROKE; michael@0: break; michael@0: case NS_STYLE_POINTER_EVENTS_ALL: michael@0: flags |= SVG_HIT_TEST_FILL | SVG_HIT_TEST_STROKE; michael@0: break; michael@0: default: michael@0: NS_ERROR("not reached"); michael@0: break; michael@0: } michael@0: michael@0: return flags; michael@0: } michael@0: michael@0: bool michael@0: nsSVGUtils::SetupCairoStroke(nsIFrame* aFrame, gfxContext* aContext, michael@0: gfxTextContextPaint *aContextPaint) michael@0: { michael@0: if (!HasStroke(aFrame, aContextPaint)) { michael@0: return false; michael@0: } michael@0: SetupCairoStrokeGeometry(aFrame, aContext, aContextPaint); michael@0: michael@0: return SetupCairoStrokePaint(aFrame, aContext, aContextPaint); michael@0: } michael@0: michael@0: bool michael@0: nsSVGUtils::PaintSVGGlyph(Element* aElement, gfxContext* aContext, michael@0: DrawMode aDrawMode, michael@0: gfxTextContextPaint* aContextPaint) michael@0: { michael@0: nsIFrame* frame = aElement->GetPrimaryFrame(); michael@0: nsISVGChildFrame* svgFrame = do_QueryFrame(frame); michael@0: if (!svgFrame) { michael@0: return false; michael@0: } michael@0: nsRefPtr context(new nsRenderingContext()); michael@0: context->Init(frame->PresContext()->DeviceContext(), aContext); michael@0: context->AddUserData(&gfxTextContextPaint::sUserDataKey, aContextPaint, michael@0: nullptr); michael@0: svgFrame->NotifySVGChanged(nsISVGChildFrame::TRANSFORM_CHANGED); michael@0: nsresult rv = svgFrame->PaintSVG(context, nullptr, frame); michael@0: return NS_SUCCEEDED(rv); michael@0: } michael@0: michael@0: bool michael@0: nsSVGUtils::GetSVGGlyphExtents(Element* aElement, michael@0: const gfxMatrix& aSVGToAppSpace, michael@0: gfxRect* aResult) michael@0: { michael@0: nsIFrame* frame = aElement->GetPrimaryFrame(); michael@0: nsISVGChildFrame* svgFrame = do_QueryFrame(frame); michael@0: if (!svgFrame) { michael@0: return false; michael@0: } michael@0: michael@0: gfxMatrix transform(aSVGToAppSpace); michael@0: nsIContent* content = frame->GetContent(); michael@0: if (content->IsSVG()) { michael@0: transform = static_cast(content)-> michael@0: PrependLocalTransformsTo(aSVGToAppSpace); michael@0: } michael@0: michael@0: *aResult = svgFrame->GetBBoxContribution(gfx::ToMatrix(transform), michael@0: nsSVGUtils::eBBoxIncludeFill | nsSVGUtils::eBBoxIncludeFillGeometry | michael@0: nsSVGUtils::eBBoxIncludeStroke | nsSVGUtils::eBBoxIncludeStrokeGeometry | michael@0: nsSVGUtils::eBBoxIncludeMarkers).ToThebesRect(); michael@0: return true; michael@0: } michael@0: michael@0: nsRect michael@0: nsSVGUtils::ToCanvasBounds(const gfxRect &aUserspaceRect, michael@0: const gfxMatrix &aToCanvas, michael@0: const nsPresContext *presContext) michael@0: { michael@0: return nsLayoutUtils::RoundGfxRectToAppRect( michael@0: aToCanvas.TransformBounds(aUserspaceRect), michael@0: presContext->AppUnitsPerDevPixel()); michael@0: }