diff options
Diffstat (limited to 'layout/svg/nsSVGMarkerFrame.cpp')
-rw-r--r-- | layout/svg/nsSVGMarkerFrame.cpp | 278 |
1 files changed, 278 insertions, 0 deletions
diff --git a/layout/svg/nsSVGMarkerFrame.cpp b/layout/svg/nsSVGMarkerFrame.cpp new file mode 100644 index 000000000..93b76b17f --- /dev/null +++ b/layout/svg/nsSVGMarkerFrame.cpp @@ -0,0 +1,278 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Main header first: +#include "nsSVGMarkerFrame.h" + +// Keep others in (case-insensitive) order: +#include "gfxContext.h" +#include "nsSVGEffects.h" +#include "mozilla/dom/SVGMarkerElement.h" +#include "nsSVGPathGeometryElement.h" +#include "nsSVGPathGeometryFrame.h" + +using namespace mozilla::dom; +using namespace mozilla::gfx; + +nsContainerFrame* +NS_NewSVGMarkerFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + return new (aPresShell) nsSVGMarkerFrame(aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsSVGMarkerFrame) + +//---------------------------------------------------------------------- +// nsIFrame methods: + +nsresult +nsSVGMarkerFrame::AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) +{ + if (aNameSpaceID == kNameSpaceID_None && + (aAttribute == nsGkAtoms::markerUnits || + aAttribute == nsGkAtoms::refX || + aAttribute == nsGkAtoms::refY || + aAttribute == nsGkAtoms::markerWidth || + aAttribute == nsGkAtoms::markerHeight || + aAttribute == nsGkAtoms::orient || + aAttribute == nsGkAtoms::preserveAspectRatio || + aAttribute == nsGkAtoms::viewBox)) { + nsSVGEffects::InvalidateDirectRenderingObservers(this); + } + + return nsSVGContainerFrame::AttributeChanged(aNameSpaceID, + aAttribute, aModType); +} + +#ifdef DEBUG +void +nsSVGMarkerFrame::Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) +{ + NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::marker), "Content is not an SVG marker"); + + nsSVGContainerFrame::Init(aContent, aParent, aPrevInFlow); +} +#endif /* DEBUG */ + +nsIAtom * +nsSVGMarkerFrame::GetType() const +{ + return nsGkAtoms::svgMarkerFrame; +} + +//---------------------------------------------------------------------- +// nsSVGContainerFrame methods: + +gfxMatrix +nsSVGMarkerFrame::GetCanvasTM() +{ + NS_ASSERTION(mMarkedFrame, "null nsSVGPathGeometry frame"); + + if (mInUse2) { + // We're going to be bailing drawing the marker, so return an identity. + return gfxMatrix(); + } + + SVGMarkerElement *content = static_cast<SVGMarkerElement*>(mContent); + + mInUse2 = true; + gfxMatrix markedTM = mMarkedFrame->GetCanvasTM(); + mInUse2 = false; + + Matrix markerTM = content->GetMarkerTransform(mStrokeWidth, mX, mY, + mAutoAngle, mIsStart); + Matrix viewBoxTM = content->GetViewBoxTransform(); + + return ThebesMatrix(viewBoxTM * markerTM) * markedTM; +} + +static nsIFrame* +GetAnonymousChildFrame(nsIFrame* aFrame) +{ + nsIFrame* kid = aFrame->PrincipalChildList().FirstChild(); + MOZ_ASSERT(kid && kid->GetType() == nsGkAtoms::svgMarkerAnonChildFrame, + "expected to find anonymous child of marker frame"); + return kid; +} + +nsresult +nsSVGMarkerFrame::PaintMark(gfxContext& aContext, + const gfxMatrix& aToMarkedFrameUserSpace, + nsSVGPathGeometryFrame *aMarkedFrame, + nsSVGMark *aMark, float aStrokeWidth) +{ + // If the flag is set when we get here, it means this marker frame + // has already been used painting the current mark, and the document + // has a marker reference loop. + if (mInUse) + return NS_OK; + + AutoMarkerReferencer markerRef(this, aMarkedFrame); + + SVGMarkerElement *marker = static_cast<SVGMarkerElement*>(mContent); + if (!marker->HasValidDimensions()) { + return NS_OK; + } + + const nsSVGViewBoxRect viewBox = marker->GetViewBoxRect(); + + if (viewBox.width <= 0.0f || viewBox.height <= 0.0f) { + // We must disable rendering if the viewBox width or height are zero. + return NS_OK; + } + + mStrokeWidth = aStrokeWidth; + mX = aMark->x; + mY = aMark->y; + mAutoAngle = aMark->angle; + mIsStart = aMark->type == nsSVGMark::eStart; + + Matrix viewBoxTM = marker->GetViewBoxTransform(); + + Matrix markerTM = marker->GetMarkerTransform(mStrokeWidth, mX, mY, + mAutoAngle, mIsStart); + + gfxMatrix markTM = ThebesMatrix(viewBoxTM) * ThebesMatrix(markerTM) * + aToMarkedFrameUserSpace; + + if (StyleDisplay()->IsScrollableOverflow()) { + aContext.Save(); + gfxRect clipRect = + nsSVGUtils::GetClipRectForFrame(this, viewBox.x, viewBox.y, + viewBox.width, viewBox.height); + nsSVGUtils::SetClipRect(&aContext, markTM, clipRect); + } + + + nsIFrame* kid = GetAnonymousChildFrame(this); + nsISVGChildFrame* SVGFrame = do_QueryFrame(kid); + // The CTM of each frame referencing us may be different. + SVGFrame->NotifySVGChanged(nsISVGChildFrame::TRANSFORM_CHANGED); + DrawResult result = nsSVGUtils::PaintFrameWithEffects(kid, aContext, markTM); + + if (StyleDisplay()->IsScrollableOverflow()) + aContext.Restore(); + + return (result == DrawResult::SUCCESS) ? NS_OK : NS_ERROR_FAILURE; +} + +SVGBBox +nsSVGMarkerFrame::GetMarkBBoxContribution(const Matrix &aToBBoxUserspace, + uint32_t aFlags, + nsSVGPathGeometryFrame *aMarkedFrame, + const nsSVGMark *aMark, + float aStrokeWidth) +{ + SVGBBox bbox; + + // If the flag is set when we get here, it means this marker frame + // has already been used in calculating the current mark bbox, and + // the document has a marker reference loop. + if (mInUse) + return bbox; + + AutoMarkerReferencer markerRef(this, aMarkedFrame); + + SVGMarkerElement *content = static_cast<SVGMarkerElement*>(mContent); + if (!content->HasValidDimensions()) { + return bbox; + } + + const nsSVGViewBoxRect viewBox = content->GetViewBoxRect(); + + if (viewBox.width <= 0.0f || viewBox.height <= 0.0f) { + return bbox; + } + + mStrokeWidth = aStrokeWidth; + mX = aMark->x; + mY = aMark->y; + mAutoAngle = aMark->angle; + mIsStart = aMark->type == nsSVGMark::eStart; + + Matrix markerTM = + content->GetMarkerTransform(mStrokeWidth, mX, mY, mAutoAngle, mIsStart); + Matrix viewBoxTM = content->GetViewBoxTransform(); + + Matrix tm = viewBoxTM * markerTM * aToBBoxUserspace; + + nsISVGChildFrame* child = do_QueryFrame(GetAnonymousChildFrame(this)); + // When we're being called to obtain the invalidation area, we need to + // pass down all the flags so that stroke is included. However, once DOM + // getBBox() accepts flags, maybe we should strip some of those here? + + // We need to include zero width/height vertical/horizontal lines, so we have + // to use UnionEdges. + bbox.UnionEdges(child->GetBBoxContribution(tm, aFlags)); + + return bbox; +} + +void +nsSVGMarkerFrame::SetParentCoordCtxProvider(SVGSVGElement *aContext) +{ + SVGMarkerElement *marker = static_cast<SVGMarkerElement*>(mContent); + marker->SetParentCoordCtxProvider(aContext); +} + +//---------------------------------------------------------------------- +// helper class + +nsSVGMarkerFrame::AutoMarkerReferencer::AutoMarkerReferencer( + nsSVGMarkerFrame *aFrame, + nsSVGPathGeometryFrame *aMarkedFrame + MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL) + : mFrame(aFrame) +{ + MOZ_GUARD_OBJECT_NOTIFIER_INIT; + mFrame->mInUse = true; + mFrame->mMarkedFrame = aMarkedFrame; + + SVGSVGElement *ctx = + static_cast<nsSVGElement*>(aMarkedFrame->GetContent())->GetCtx(); + mFrame->SetParentCoordCtxProvider(ctx); +} + +nsSVGMarkerFrame::AutoMarkerReferencer::~AutoMarkerReferencer() +{ + mFrame->SetParentCoordCtxProvider(nullptr); + + mFrame->mMarkedFrame = nullptr; + mFrame->mInUse = false; +} + +//---------------------------------------------------------------------- +// Implementation of nsSVGMarkerAnonChildFrame + +nsContainerFrame* +NS_NewSVGMarkerAnonChildFrame(nsIPresShell* aPresShell, + nsStyleContext* aContext) +{ + return new (aPresShell) nsSVGMarkerAnonChildFrame(aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsSVGMarkerAnonChildFrame) + +#ifdef DEBUG +void +nsSVGMarkerAnonChildFrame::Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) +{ + MOZ_ASSERT(aParent->GetType() == nsGkAtoms::svgMarkerFrame, + "Unexpected parent"); + nsSVGDisplayContainerFrame::Init(aContent, aParent, aPrevInFlow); +} +#endif + +nsIAtom * +nsSVGMarkerAnonChildFrame::GetType() const +{ + return nsGkAtoms::svgMarkerAnonChildFrame; +} |