/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the Mozilla SVG project. * * The Initial Developer of the Original Code is * Crocodile Clips Ltd.. * Portions created by the Initial Developer are Copyright (C) 2002 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Alex Fritze (original author) * * Alternatively, the contents of this file may be used under the terms of * either of the GNU General Public License Version 2 or later (the "GPL"), * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "nsSVGPathGeometryFrame.h" #include "nsGkAtoms.h" #include "nsSVGMarkerFrame.h" #include "nsSVGMatrix.h" #include "nsSVGUtils.h" #include "nsSVGEffects.h" #include "nsSVGGraphicElement.h" #include "nsSVGOuterSVGFrame.h" #include "nsSVGRect.h" #include "nsSVGPathGeometryElement.h" #include "gfxContext.h" //---------------------------------------------------------------------- // Implementation nsIFrame* NS_NewSVGPathGeometryFrame(nsIPresShell* aPresShell, nsIContent* aContent, nsStyleContext* aContext) { return new (aPresShell) nsSVGPathGeometryFrame(aContext); } //---------------------------------------------------------------------- // nsISupports methods NS_INTERFACE_MAP_BEGIN(nsSVGPathGeometryFrame) NS_INTERFACE_MAP_ENTRY(nsISVGChildFrame) NS_INTERFACE_MAP_END_INHERITING(nsSVGPathGeometryFrameBase) //---------------------------------------------------------------------- // nsIFrame methods NS_IMETHODIMP nsSVGPathGeometryFrame::AttributeChanged(PRInt32 aNameSpaceID, nsIAtom* aAttribute, PRInt32 aModType) { if (aNameSpaceID == kNameSpaceID_None && (static_cast (mContent)->IsDependentAttribute(aAttribute) || aAttribute == nsGkAtoms::transform)) nsSVGUtils::UpdateGraphic(this); return NS_OK; } /* virtual */ void nsSVGPathGeometryFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext) { nsSVGPathGeometryFrameBase::DidSetStyleContext(aOldStyleContext); nsSVGOuterSVGFrame *outerSVGFrame = nsSVGUtils::GetOuterSVGFrame(this); if (outerSVGFrame) { // invalidate here while we still have the filter information outerSVGFrame->InvalidateCoveredRegion(this); } // XXX: we'd like to use the style_hint mechanism and the // ContentStateChanged/AttributeChanged functions for style changes // to get slightly finer granularity, but unfortunately the // style_hints don't map very well onto svg. Here seems to be the // best place to deal with style changes: nsSVGUtils::UpdateGraphic(this); } nsIAtom * nsSVGPathGeometryFrame::GetType() const { return nsGkAtoms::svgPathGeometryFrame; } //---------------------------------------------------------------------- // nsISVGChildFrame methods NS_IMETHODIMP nsSVGPathGeometryFrame::PaintSVG(nsSVGRenderState *aContext, const nsIntRect *aDirtyRect) { if (!GetStyleVisibility()->IsVisible()) return NS_OK; /* render */ Render(aContext); if (static_cast(mContent)->IsMarkable()) { MarkerProperties properties = GetMarkerProperties(this); if (properties.MarkersExist()) { float strokeWidth = GetStrokeWidth(); nsTArray marks; static_cast (mContent)->GetMarkPoints(&marks); PRUint32 num = marks.Length(); if (num) { nsSVGMarkerFrame *frame = properties.GetMarkerStartFrame(); if (frame) frame->PaintMark(aContext, this, &marks[0], strokeWidth); frame = properties.GetMarkerMidFrame(); if (frame) { for (PRUint32 i = 1; i < num - 1; i++) frame->PaintMark(aContext, this, &marks[i], strokeWidth); } frame = properties.GetMarkerEndFrame(); if (frame) frame->PaintMark(aContext, this, &marks[num-1], strokeWidth); } } } return NS_OK; } NS_IMETHODIMP_(nsIFrame*) nsSVGPathGeometryFrame::GetFrameForPoint(const nsPoint &aPoint) { PRUint16 fillRule, mask; // check if we're a clipPath - cheaper than IsClipChild(), and we shouldn't // get in here for other nondisplay children if (GetStateBits() & NS_STATE_SVG_NONDISPLAY_CHILD) { NS_ASSERTION(IsClipChild(), "should be in clipPath but we're not"); mask = HITTEST_MASK_FILL; fillRule = GetClipRule(); } else { mask = GetHittestMask(); if (!mask || (!(mask & HITTEST_MASK_FORCE_TEST) && !mRect.Contains(aPoint))) return nsnull; fillRule = GetStyleSVG()->mFillRule; } PRBool isHit = PR_FALSE; gfxContext context(nsSVGUtils::GetThebesComputationalSurface()); GeneratePath(&context); gfxPoint userSpacePoint = context.DeviceToUser(gfxPoint(PresContext()->AppUnitsToGfxUnits(aPoint.x), PresContext()->AppUnitsToGfxUnits(aPoint.y))); if (fillRule == NS_STYLE_FILL_RULE_EVENODD) context.SetFillRule(gfxContext::FILL_RULE_EVEN_ODD); else context.SetFillRule(gfxContext::FILL_RULE_WINDING); if (mask & HITTEST_MASK_FILL) isHit = context.PointInFill(userSpacePoint); if (!isHit && (mask & HITTEST_MASK_STROKE) && SetupCairoStrokeHitGeometry(&context)) { isHit = context.PointInStroke(userSpacePoint); } if (isHit && nsSVGUtils::HitTestClip(this, aPoint)) return this; return nsnull; } NS_IMETHODIMP_(nsRect) nsSVGPathGeometryFrame::GetCoveredRegion() { if (static_cast(mContent)->IsMarkable()) { MarkerProperties properties = GetMarkerProperties(this); if (!properties.MarkersExist()) return mRect; nsRect rect(mRect); float strokeWidth = GetStrokeWidth(); nsTArray marks; static_cast(mContent)->GetMarkPoints(&marks); PRUint32 num = marks.Length(); if (num) { nsSVGMarkerFrame *frame = properties.GetMarkerStartFrame(); if (frame) { nsRect mark = frame->RegionMark(this, &marks[0], strokeWidth); rect.UnionRect(rect, mark); } frame = properties.GetMarkerMidFrame(); if (frame) { for (PRUint32 i = 1; i < num - 1; i++) { nsRect mark = frame->RegionMark(this, &marks[i], strokeWidth); rect.UnionRect(rect, mark); } } frame = properties.GetMarkerEndFrame(); if (frame) { nsRect mark = frame->RegionMark(this, &marks[num-1], strokeWidth); rect.UnionRect(rect, mark); } } return rect; } return mRect; } NS_IMETHODIMP nsSVGPathGeometryFrame::UpdateCoveredRegion() { mRect.Empty(); gfxContext context(nsSVGUtils::GetThebesComputationalSurface()); static_cast(mContent)->ConstructPath(&context); gfxRect extent = gfxRect(0, 0, 0, 0); if (SetupCairoStrokeGeometry(&context)) { extent = context.GetUserStrokeExtent(); } if (GetStyleSVG()->mFill.mType != eStyleSVGPaintType_None) { extent = extent.Union(context.GetUserPathExtent()); } if (!extent.IsEmpty()) { nsCOMPtr ctm; GetCanvasTM(getter_AddRefs(ctm)); NS_ASSERTION(ctm, "graphic source didn't specify a ctm"); gfxMatrix matrix = nsSVGUtils::ConvertSVGMatrixToThebes(ctm); extent = matrix.TransformBounds(extent); mRect = nsSVGUtils::ToAppPixelRect(PresContext(), extent); } // Add in markers mRect = GetCoveredRegion(); return NS_OK; } NS_IMETHODIMP nsSVGPathGeometryFrame::InitialUpdate() { NS_ASSERTION(GetStateBits() & NS_FRAME_FIRST_REFLOW, "Yikes! We've been called already! Hopefully we weren't called " "before our nsSVGOuterSVGFrame's initial Reflow()!!!"); nsSVGUtils::UpdateGraphic(this); NS_ASSERTION(!(mState & NS_FRAME_IN_REFLOW), "We don't actually participate in reflow"); // Do unset the various reflow bits, though. mState &= ~(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY | NS_FRAME_HAS_DIRTY_CHILDREN); return NS_OK; } void nsSVGPathGeometryFrame::NotifySVGChanged(PRUint32 aFlags) { if (!(aFlags & SUPPRESS_INVALIDATION)) { nsSVGUtils::UpdateGraphic(this); } } NS_IMETHODIMP nsSVGPathGeometryFrame::NotifyRedrawSuspended() { // XXX should we cache the fact that redraw is suspended? return NS_OK; } NS_IMETHODIMP nsSVGPathGeometryFrame::NotifyRedrawUnsuspended() { if (GetStateBits() & NS_STATE_SVG_DIRTY) nsSVGUtils::UpdateGraphic(this); return NS_OK; } NS_IMETHODIMP nsSVGPathGeometryFrame::SetMatrixPropagation(PRBool aPropagate) { if (aPropagate) { AddStateBits(NS_STATE_SVG_PROPAGATE_TRANSFORM); } else { RemoveStateBits(NS_STATE_SVG_PROPAGATE_TRANSFORM); } return NS_OK; } PRBool nsSVGPathGeometryFrame::GetMatrixPropagation() { return (GetStateBits() & NS_STATE_SVG_PROPAGATE_TRANSFORM) != 0; } NS_IMETHODIMP nsSVGPathGeometryFrame::GetBBox(nsIDOMSVGRect **_retval) { gfxContext context(nsSVGUtils::GetThebesComputationalSurface()); GeneratePath(&context); context.IdentityMatrix(); return NS_NewSVGRect(_retval, context.GetUserPathExtent()); } //---------------------------------------------------------------------- // nsSVGGeometryFrame methods: /* readonly attribute nsIDOMSVGMatrix canvasTM; */ NS_IMETHODIMP nsSVGPathGeometryFrame::GetCanvasTM(nsIDOMSVGMatrix * *aCTM) { *aCTM = nsnull; if (!GetMatrixPropagation()) { return NS_NewSVGMatrix(aCTM); } nsSVGContainerFrame *containerFrame = static_cast (mParent); nsCOMPtr parentTM = containerFrame->GetCanvasTM(); NS_ASSERTION(parentTM, "null TM"); // append our local transformations if we have any: nsSVGGraphicElement *element = static_cast(mContent); nsCOMPtr localTM = element->GetLocalTransformMatrix(); if (localTM) return parentTM->Multiply(localTM, aCTM); *aCTM = parentTM; NS_ADDREF(*aCTM); return NS_OK; } //---------------------------------------------------------------------- // nsSVGPathGeometryFrame methods: nsSVGPathGeometryFrame::MarkerProperties nsSVGPathGeometryFrame::GetMarkerProperties(nsSVGPathGeometryFrame *aFrame) { NS_ASSERTION(!aFrame->GetPrevContinuation(), "aFrame should be first continuation"); MarkerProperties result; const nsStyleSVG *style = aFrame->GetStyleSVG(); result.mMarkerStart = nsSVGEffects::GetMarkerProperty( style->mMarkerStart, aFrame, nsGkAtoms::marker_start); result.mMarkerMid = nsSVGEffects::GetMarkerProperty( style->mMarkerMid, aFrame, nsGkAtoms::marker_mid); result.mMarkerEnd = nsSVGEffects::GetMarkerProperty( style->mMarkerEnd, aFrame, nsGkAtoms::marker_end); return result; } nsSVGMarkerFrame * nsSVGPathGeometryFrame::MarkerProperties::GetMarkerStartFrame() { if (!mMarkerStart) return nsnull; return static_cast (mMarkerStart->GetReferencedFrame(nsGkAtoms::svgMarkerFrame, nsnull)); } nsSVGMarkerFrame * nsSVGPathGeometryFrame::MarkerProperties::GetMarkerMidFrame() { if (!mMarkerMid) return nsnull; return static_cast (mMarkerMid->GetReferencedFrame(nsGkAtoms::svgMarkerFrame, nsnull)); } nsSVGMarkerFrame * nsSVGPathGeometryFrame::MarkerProperties::GetMarkerEndFrame() { if (!mMarkerEnd) return nsnull; return static_cast (mMarkerEnd->GetReferencedFrame(nsGkAtoms::svgMarkerFrame, nsnull)); } void nsSVGPathGeometryFrame::Render(nsSVGRenderState *aContext) { gfxContext *gfx = aContext->GetGfxContext(); PRUint16 renderMode = aContext->GetRenderMode(); /* save/pop the state so we don't screw up the xform */ gfx->Save(); GeneratePath(gfx); if (renderMode != nsSVGRenderState::NORMAL) { gfx->Restore(); if (GetClipRule() == NS_STYLE_FILL_RULE_EVENODD) gfx->SetFillRule(gfxContext::FILL_RULE_EVEN_ODD); else gfx->SetFillRule(gfxContext::FILL_RULE_WINDING); if (renderMode == nsSVGRenderState::CLIP_MASK) { gfx->SetAntialiasMode(gfxContext::MODE_ALIASED); gfx->SetColor(gfxRGBA(1.0f, 1.0f, 1.0f, 1.0f)); gfx->Fill(); gfx->NewPath(); } return; } switch (GetStyleSVG()->mShapeRendering) { case NS_STYLE_SHAPE_RENDERING_OPTIMIZESPEED: case NS_STYLE_SHAPE_RENDERING_CRISPEDGES: gfx->SetAntialiasMode(gfxContext::MODE_ALIASED); break; default: gfx->SetAntialiasMode(gfxContext::MODE_COVERAGE); break; } if (SetupCairoFill(gfx)) { gfx->Fill(); } if (SetupCairoStroke(gfx)) { gfx->Stroke(); } gfx->NewPath(); gfx->Restore(); } void nsSVGPathGeometryFrame::GeneratePath(gfxContext* aContext) { nsCOMPtr ctm; GetCanvasTM(getter_AddRefs(ctm)); NS_ASSERTION(ctm, "graphic source didn't specify a ctm"); gfxMatrix matrix = nsSVGUtils::ConvertSVGMatrixToThebes(ctm); if (matrix.IsSingular()) { aContext->IdentityMatrix(); aContext->NewPath(); return; } aContext->Multiply(matrix); aContext->NewPath(); static_cast(mContent)->ConstructPath(aContext); } PRUint16 nsSVGPathGeometryFrame::GetHittestMask() { PRUint16 mask = 0; switch(GetStyleSVG()->mPointerEvents) { case NS_STYLE_POINTER_EVENTS_NONE: break; case NS_STYLE_POINTER_EVENTS_VISIBLEPAINTED: if (GetStyleVisibility()->IsVisible()) { if (GetStyleSVG()->mFill.mType != eStyleSVGPaintType_None) mask |= HITTEST_MASK_FILL; if (GetStyleSVG()->mStroke.mType != eStyleSVGPaintType_None) mask |= HITTEST_MASK_STROKE; } break; case NS_STYLE_POINTER_EVENTS_VISIBLEFILL: if (GetStyleVisibility()->IsVisible()) { mask |= HITTEST_MASK_FILL | HITTEST_MASK_FORCE_TEST; } break; case NS_STYLE_POINTER_EVENTS_VISIBLESTROKE: if (GetStyleVisibility()->IsVisible()) { mask |= HITTEST_MASK_STROKE | HITTEST_MASK_FORCE_TEST; } break; case NS_STYLE_POINTER_EVENTS_VISIBLE: if (GetStyleVisibility()->IsVisible()) { mask |= HITTEST_MASK_FILL | HITTEST_MASK_STROKE | HITTEST_MASK_FORCE_TEST; } break; case NS_STYLE_POINTER_EVENTS_PAINTED: if (GetStyleSVG()->mFill.mType != eStyleSVGPaintType_None) mask |= HITTEST_MASK_FILL; if (GetStyleSVG()->mStroke.mType != eStyleSVGPaintType_None) mask |= HITTEST_MASK_STROKE; break; case NS_STYLE_POINTER_EVENTS_FILL: mask |= HITTEST_MASK_FILL | HITTEST_MASK_FORCE_TEST; break; case NS_STYLE_POINTER_EVENTS_STROKE: mask |= HITTEST_MASK_STROKE | HITTEST_MASK_FORCE_TEST; break; case NS_STYLE_POINTER_EVENTS_ALL: mask |= HITTEST_MASK_FILL | HITTEST_MASK_STROKE | HITTEST_MASK_FORCE_TEST; break; default: NS_ERROR("not reached"); break; } return mask; }