/* -*- 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 IBM Corporation. * Portions created by the Initial Developer are Copyright (C) 2005 * the Initial Developer. All Rights Reserved. * * Contributor(s): * * 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 "nsSVGLength.h" #include "nsIDOMDocument.h" #include "nsIDOMSVGElement.h" #include "nsIDOMSVGSVGElement.h" #include "nsStyleCoord.h" #include "nsPresContext.h" #include "nsSVGCoordCtxProvider.h" #include "nsIContent.h" #include "nsIDocument.h" #include "nsIFrame.h" #include "nsLayoutAtoms.h" #include "nsIURI.h" #include "nsStyleStruct.h" #include "nsIPresShell.h" #include "nsSVGUtils.h" #include "nsISVGGlyphFragmentLeaf.h" #include "nsNetUtil.h" #include "nsIDOMSVGRect.h" #include "nsFrameList.h" #include "nsISVGChildFrame.h" #include "nsContentDLF.h" #include "nsContentUtils.h" #include "nsISVGRenderer.h" #include "nsSVGFilterFrame.h" #include "nsINameSpaceManager.h" #include "nsIDOMSVGPoint.h" #include "nsSVGPoint.h" #include "nsDOMError.h" #include "nsSVGOuterSVGFrame.h" #include "nsISVGRendererCanvas.h" #include "nsIDOMSVGAnimPresAspRatio.h" #include "nsIDOMSVGPresAspectRatio.h" #include "nsSVGMatrix.h" #include "nsSVGFilterFrame.h" #include "nsSVGClipPathFrame.h" #include "nsSVGMaskFrame.h" #include "nsSVGContainerFrame.h" #include "nsSVGLength2.h" #include "nsGenericElement.h" #include "nsAttrValue.h" #include "nsSVGGeometryFrame.h" #include "nsIScriptError.h" #include "cairo.h" #include "nsISVGCairoCanvas.h" struct nsSVGFilterProperty { nsRect mFilterRect; nsISVGFilterFrame *mFilter; }; cairo_surface_t *nsSVGUtils::mCairoComputationalSurface = nsnull; static PRBool gSVGEnabled; static PRBool gSVGRendererAvailable = PR_FALSE; static const char SVG_PREF_STR[] = "svg.enabled"; PR_STATIC_CALLBACK(int) SVGPrefChanged(const char *aPref, void *aClosure) { PRBool prefVal = nsContentUtils::GetBoolPref(SVG_PREF_STR); if (prefVal == gSVGEnabled) return 0; gSVGEnabled = prefVal; if (gSVGRendererAvailable) { if (gSVGEnabled) nsContentDLF::RegisterSVG(); else nsContentDLF::UnregisterSVG(); } return 0; } PRBool NS_SVGEnabled() { static PRBool sInitialized = PR_FALSE; if (!sInitialized) { gSVGRendererAvailable = PR_TRUE; /* check and register ourselves with the pref */ gSVGEnabled = nsContentUtils::GetBoolPref(SVG_PREF_STR); nsContentUtils::RegisterPrefCallback(SVG_PREF_STR, SVGPrefChanged, nsnull); sInitialized = PR_TRUE; } return gSVGEnabled && gSVGRendererAvailable; } nsresult nsSVGUtils::ReportToConsole(nsIDocument* doc, const char* aWarning, const PRUnichar **aParams, PRUint32 aParamsLength) { return nsContentUtils::ReportToConsole(nsContentUtils::eSVG_PROPERTIES, aWarning, aParams, aParamsLength, doc ? doc->GetDocumentURI() : nsnull, EmptyString(), 0, 0, nsIScriptError::warningFlag, "SVG"); } float nsSVGUtils::CoordToFloat(nsPresContext *aPresContext, nsIContent *aContent, const nsStyleCoord &aCoord) { float val = 0.0f; switch (aCoord.GetUnit()) { case eStyleUnit_Factor: // user units val = aCoord.GetFactorValue(); break; case eStyleUnit_Coord: val = aCoord.GetCoordValue() / aPresContext->ScaledPixelsToTwips(); break; case eStyleUnit_Percent: { nsCOMPtr element = do_QueryInterface(aContent); nsCOMPtr owner; element->GetOwnerSVGElement(getter_AddRefs(owner)); nsCOMPtr ctx = do_QueryInterface(owner); nsCOMPtr length; NS_NewSVGLength(getter_AddRefs(length), aCoord.GetPercentValue() * 100.0f, nsIDOMSVGLength::SVG_LENGTHTYPE_PERCENTAGE); if (!ctx || !length) break; length->SetContext(nsRefPtr(ctx->GetContextUnspecified())); length->GetValue(&val); break; } default: break; } return val; } nsresult nsSVGUtils::GetReferencedFrame(nsIFrame **aRefFrame, nsIURI* aURI, nsIContent *aContent, nsIPresShell *aPresShell) { *aRefFrame = nsnull; nsIContent* content = nsContentUtils::GetReferencedElement(aURI, aContent); if (!content) return NS_ERROR_FAILURE; // Get the Primary Frame NS_ASSERTION(aPresShell, "Get referenced SVG frame -- no pres shell provided"); if (!aPresShell) return NS_ERROR_FAILURE; *aRefFrame = aPresShell->GetPrimaryFrameFor(content); if (!(*aRefFrame)) return NS_ERROR_FAILURE; return NS_OK; } nsresult nsSVGUtils::GetBBox(nsFrameList *aFrames, nsIDOMSVGRect **_retval) { *_retval = nsnull; float minx, miny, maxx, maxy; minx = miny = FLT_MAX; maxx = maxy = -1.0 * FLT_MAX; nsCOMPtr unionRect; nsIFrame* kid = aFrames->FirstChild(); while (kid) { nsISVGChildFrame* SVGFrame = nsnull; CallQueryInterface(kid, &SVGFrame); if (SVGFrame) { nsCOMPtr box; SVGFrame->GetBBox(getter_AddRefs(box)); if (box) { float bminx, bminy, bmaxx, bmaxy, width, height; box->GetX(&bminx); box->GetY(&bminy); box->GetWidth(&width); box->GetHeight(&height); bmaxx = bminx+width; bmaxy = bminy+height; if (!unionRect) unionRect = box; minx = PR_MIN(minx, bminx); miny = PR_MIN(miny, bminy); maxx = PR_MAX(maxx, bmaxx); maxy = PR_MAX(maxy, bmaxy); } } kid = kid->GetNextSibling(); } if (unionRect) { unionRect->SetX(minx); unionRect->SetY(miny); unionRect->SetWidth(maxx - minx); unionRect->SetHeight(maxy - miny); *_retval = unionRect; NS_ADDREF(*_retval); return NS_OK; } return NS_ERROR_FAILURE; } nsRect nsSVGUtils::FindFilterInvalidation(nsIFrame *aFrame) { nsRect rect; while (aFrame) { if (aFrame->GetStateBits() & NS_STATE_IS_OUTER_SVG) break; if (aFrame->GetStateBits() & NS_STATE_SVG_FILTERED) { nsSVGFilterProperty *property; property = NS_STATIC_CAST(nsSVGFilterProperty *, aFrame->GetProperty(nsGkAtoms::filter)); rect = property->mFilterRect; } aFrame = aFrame->GetParent(); } return rect; } float nsSVGUtils::ObjectSpace(nsIDOMSVGRect *aRect, nsSVGLength2 *aLength) { float fraction, axis; switch (aLength->GetCtxType()) { case X: aRect->GetWidth(&axis); break; case Y: aRect->GetHeight(&axis); break; case XY: { float width, height; aRect->GetWidth(&width); aRect->GetHeight(&height); axis = sqrt(width * width + height * height)/sqrt(2.0f); } } if (aLength->GetSpecifiedUnitType() == nsIDOMSVGLength::SVG_LENGTHTYPE_PERCENTAGE) { fraction = aLength->GetAnimValInSpecifiedUnits() / 100; } else fraction = aLength->GetAnimValue(NS_STATIC_CAST(nsSVGCoordCtxProvider*, nsnull)); return fraction * axis; } float nsSVGUtils::UserSpace(nsSVGElement *aSVGElement, nsSVGLength2 *aLength) { return aLength->GetAnimValue(aSVGElement); } void nsSVGUtils::TransformPoint(nsIDOMSVGMatrix *matrix, float *x, float *y) { nsCOMPtr point; NS_NewSVGPoint(getter_AddRefs(point), *x, *y); if (!point) return; nsCOMPtr xfpoint; point->MatrixTransform(matrix, getter_AddRefs(xfpoint)); if (!xfpoint) return; xfpoint->GetX(x); xfpoint->GetY(y); } float nsSVGUtils::AngleBisect(float a1, float a2) { float delta = fmod(a2 - a1, NS_STATIC_CAST(float, 2*M_PI)); if (delta < 0) { delta += 2*M_PI; } /* delta is now the angle from a1 around to a2, in the range [0, 2*M_PI) */ float r = a1 + delta/2; if (delta >= M_PI) { /* the arc from a2 to a1 is smaller, so use the ray on that side */ r += M_PI; } return r; } nsSVGOuterSVGFrame * nsSVGUtils::GetOuterSVGFrame(nsIFrame *aFrame) { while (aFrame) { if (aFrame->GetStateBits() & NS_STATE_IS_OUTER_SVG) { return NS_STATIC_CAST(nsSVGOuterSVGFrame*, aFrame); } aFrame = aFrame->GetParent(); } return nsnull; } already_AddRefed nsSVGUtils::GetViewBoxTransform(float aViewportWidth, float aViewportHeight, float aViewboxX, float aViewboxY, float aViewboxWidth, float aViewboxHeight, nsIDOMSVGAnimatedPreserveAspectRatio *aPreserveAspectRatio, PRBool aIgnoreAlign) { PRUint16 align, meetOrSlice; { nsCOMPtr par; aPreserveAspectRatio->GetAnimVal(getter_AddRefs(par)); NS_ASSERTION(par, "could not get preserveAspectRatio"); par->GetAlign(&align); par->GetMeetOrSlice(&meetOrSlice); } // default to the defaults if (align == nsIDOMSVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_UNKNOWN) align = nsIDOMSVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_XMIDYMID; if (meetOrSlice == nsIDOMSVGPreserveAspectRatio::SVG_MEETORSLICE_UNKNOWN) meetOrSlice = nsIDOMSVGPreserveAspectRatio::SVG_MEETORSLICE_MEET; // alignment disabled for this matrix setup if (aIgnoreAlign) align = nsIDOMSVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_XMINYMIN; float a, d, e, f; a = aViewportWidth / aViewboxWidth; d = aViewportHeight / aViewboxHeight; e = 0.0f; f = 0.0f; if (align != nsIDOMSVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_NONE && a != d) { if (meetOrSlice == nsIDOMSVGPreserveAspectRatio::SVG_MEETORSLICE_MEET && a < d || meetOrSlice == nsIDOMSVGPreserveAspectRatio::SVG_MEETORSLICE_SLICE && d < a) { d = a; switch (align) { case nsIDOMSVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_XMINYMIN: case nsIDOMSVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_XMIDYMIN: case nsIDOMSVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_XMAXYMIN: break; case nsIDOMSVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_XMINYMID: case nsIDOMSVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_XMIDYMID: case nsIDOMSVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_XMAXYMID: f = (aViewportHeight - a * aViewboxHeight) / 2.0f; break; case nsIDOMSVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_XMINYMAX: case nsIDOMSVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_XMIDYMAX: case nsIDOMSVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_XMAXYMAX: f = aViewportHeight - a * aViewboxHeight; break; default: NS_NOTREACHED("Unknown value for align"); } } else if ( meetOrSlice == nsIDOMSVGPreserveAspectRatio::SVG_MEETORSLICE_MEET && d < a || meetOrSlice == nsIDOMSVGPreserveAspectRatio::SVG_MEETORSLICE_SLICE && a < d) { a = d; switch (align) { case nsIDOMSVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_XMINYMIN: case nsIDOMSVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_XMINYMID: case nsIDOMSVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_XMINYMAX: break; case nsIDOMSVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_XMIDYMIN: case nsIDOMSVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_XMIDYMID: case nsIDOMSVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_XMIDYMAX: e = (aViewportWidth - a * aViewboxWidth) / 2.0f; break; case nsIDOMSVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_XMAXYMIN: case nsIDOMSVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_XMAXYMID: case nsIDOMSVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_XMAXYMAX: e = aViewportWidth - a * aViewboxWidth; break; default: NS_NOTREACHED("Unknown value for align"); } } else NS_NOTREACHED("Unknown value for meetOrSlice"); } if (aViewboxX) e += -a * aViewboxX; if (aViewboxY) f += -d * aViewboxY; nsIDOMSVGMatrix *retval; NS_NewSVGMatrix(&retval, a, 0.0f, 0.0f, d, e, f); return retval; } // This is ugly and roc will want to kill me... already_AddRefed nsSVGUtils::GetCanvasTM(nsIFrame *aFrame) { if (!aFrame->IsLeaf()) { nsSVGContainerFrame *containerFrame = NS_STATIC_CAST(nsSVGContainerFrame*, aFrame); return containerFrame->GetCanvasTM(); } nsSVGGeometryFrame *geometryFrame = NS_STATIC_CAST(nsSVGGeometryFrame*, aFrame); nsCOMPtr matrix; nsIDOMSVGMatrix *retval; geometryFrame->GetCanvasTM(getter_AddRefs(matrix)); retval = matrix.get(); NS_IF_ADDREF(retval); return retval; } void nsSVGUtils::AddObserver(nsISupports *aObserver, nsISupports *aTarget) { nsISVGValueObserver *observer = nsnull; nsISVGValue *v = nsnull; CallQueryInterface(aObserver, &observer); CallQueryInterface(aTarget, &v); if (observer && v) v->AddObserver(observer); } void nsSVGUtils::RemoveObserver(nsISupports *aObserver, nsISupports *aTarget) { nsISVGValueObserver *observer = nsnull; nsISVGValue *v = nsnull; CallQueryInterface(aObserver, &observer); CallQueryInterface(aTarget, &v); if (observer && v) v->RemoveObserver(observer); } // ************************************************************ // Effect helper functions static void FilterPropertyDtor(void *aObject, nsIAtom *aPropertyName, void *aPropertyValue, void *aData) { nsSVGFilterProperty *property = NS_STATIC_CAST(nsSVGFilterProperty *, aPropertyValue); nsSVGUtils::RemoveObserver(NS_STATIC_CAST(nsIFrame *, aObject), property->mFilter); delete property; } static void InvalidateFilterRegion(nsIFrame *aFrame) { nsSVGOuterSVGFrame *outerSVGFrame = nsSVGUtils::GetOuterSVGFrame(aFrame); if (outerSVGFrame) { nsRect rect = nsSVGUtils::FindFilterInvalidation(aFrame); outerSVGFrame->InvalidateRect(rect); } } static void AddEffectProperties(nsIFrame *aFrame) { const nsStyleSVGReset *style = aFrame->GetStyleSVGReset(); if (style->mFilter && !(aFrame->GetStateBits() & NS_STATE_SVG_FILTERED)) { nsISVGFilterFrame *filter; NS_GetSVGFilterFrame(&filter, style->mFilter, aFrame->GetContent()); if (filter) { nsSVGUtils::AddObserver(aFrame, filter); nsSVGFilterProperty *property = new nsSVGFilterProperty; if (!property) { NS_ERROR("Could not create filter property"); return; } property->mFilter = filter; property->mFilterRect = filter->GetInvalidationRegion(aFrame); aFrame->SetProperty(nsGkAtoms::filter, property, FilterPropertyDtor); aFrame->AddStateBits(NS_STATE_SVG_FILTERED); } } if (style->mClipPath && !(aFrame->GetStateBits() & NS_STATE_SVG_CLIPPED_MASK)) { nsSVGClipPathFrame *clip; NS_GetSVGClipPathFrame(&clip, style->mClipPath, aFrame->GetContent()); if (clip) { aFrame->SetProperty(nsGkAtoms::clipPath, clip); PRBool trivialClip; clip->IsTrivial(&trivialClip); if (trivialClip) aFrame->AddStateBits(NS_STATE_SVG_CLIPPED_TRIVIAL); else aFrame->AddStateBits(NS_STATE_SVG_CLIPPED_COMPLEX); } } if (style->mMask && !(aFrame->GetStateBits() & NS_STATE_SVG_MASKED)) { nsSVGMaskFrame *mask; NS_GetSVGMaskFrame(&mask, style->mMask, aFrame->GetContent()); if (mask) { aFrame->SetProperty(nsGkAtoms::mask, mask); aFrame->AddStateBits(NS_STATE_SVG_MASKED); } } } static cairo_pattern_t * GetComplexClipSurface(nsISVGRendererCanvas *aCanvas, nsIFrame *aFrame) { cairo_pattern_t *pattern = nsnull; if (aFrame->GetStateBits() & NS_STATE_SVG_CLIPPED_COMPLEX) { nsISVGChildFrame *svgChildFrame; CallQueryInterface(aFrame, &svgChildFrame); nsSVGClipPathFrame *clip; clip = NS_STATIC_CAST(nsSVGClipPathFrame *, aFrame->GetProperty(nsGkAtoms::clipPath)); nsCOMPtr cairoCanvas = do_QueryInterface(aCanvas); cairo_t *ctx = cairoCanvas->GetContext(); cairo_push_group(ctx); nsCOMPtr matrix = nsSVGUtils::GetCanvasTM(aFrame); nsresult rv = clip->ClipPaint(aCanvas, svgChildFrame, matrix); pattern = cairo_pop_group(ctx); if (NS_FAILED(rv) && pattern) { cairo_pattern_destroy(pattern); pattern = nsnull; } } return pattern; } static cairo_pattern_t * GetMaskSurface(nsISVGRendererCanvas *aCanvas, nsIFrame *aFrame, float opacity) { if (aFrame->GetStateBits() & NS_STATE_SVG_MASKED) { nsISVGChildFrame *svgChildFrame; CallQueryInterface(aFrame, &svgChildFrame); nsSVGMaskFrame *mask; mask = NS_STATIC_CAST(nsSVGMaskFrame *, aFrame->GetProperty(nsGkAtoms::mask)); nsCOMPtr matrix = nsSVGUtils::GetCanvasTM(aFrame); return mask->ComputeMaskAlpha(aCanvas, svgChildFrame, matrix, opacity); } return nsnull; } // ************************************************************ void nsSVGUtils::PaintChildWithEffects(nsISVGRendererCanvas *aCanvas, nsRect *aDirtyRect, nsIFrame *aFrame) { nsISVGChildFrame *svgChildFrame; CallQueryInterface(aFrame, &svgChildFrame); if (!svgChildFrame) return; float opacity = aFrame->GetStyleDisplay()->mOpacity; /* Properties are added lazily and may have been removed by a restyle, so make sure all applicable ones are set again. */ AddEffectProperties(aFrame); nsFrameState state = aFrame->GetStateBits(); /* Check if we need to draw anything */ if (aDirtyRect) { if (state & NS_STATE_SVG_FILTERED) { if (!aDirtyRect->Intersects(FindFilterInvalidation(aFrame))) return; } else if (svgChildFrame->HasValidCoveredRect()) { if (!aDirtyRect->Intersects(aFrame->GetRect())) return; } } /* SVG defines the following rendering model: * * 1. Render geometry * 2. Apply filter * 3. Apply clipping, masking, group opacity * * We follow this, but perform a couple optimizations: * * + Use cairo's clipPath when representable natively (single object * clip region). * * + Merge opacity and masking if both used together. */ cairo_t *ctx = nsnull; /* Check if we need to do additional operations on this child's * rendering, which necessitates rendering into another surface. */ if (opacity != 1.0 || state & (NS_STATE_SVG_CLIPPED_COMPLEX | NS_STATE_SVG_MASKED)) { nsCOMPtr cairoCanvas = do_QueryInterface(aCanvas); ctx = cairoCanvas->GetContext(); cairo_save(ctx); cairo_push_group(ctx); } /* 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 (state & NS_STATE_SVG_CLIPPED_TRIVIAL) { nsSVGClipPathFrame *clip; clip = NS_STATIC_CAST(nsSVGClipPathFrame *, aFrame->GetProperty(nsGkAtoms::clipPath)); aCanvas->PushClip(); nsCOMPtr matrix = GetCanvasTM(aFrame); clip->ClipPaint(aCanvas, svgChildFrame, matrix); } /* Paint the child */ if (state & NS_STATE_SVG_FILTERED) { nsSVGFilterProperty *property; property = NS_STATIC_CAST(nsSVGFilterProperty *, aFrame->GetProperty(nsGkAtoms::filter)); property->mFilter->FilterPaint(aCanvas, svgChildFrame); } else { svgChildFrame->PaintSVG(aCanvas, aDirtyRect); } if (state & NS_STATE_SVG_CLIPPED_TRIVIAL) { aCanvas->PopClip(); } /* No more effects, we're done. */ if (!ctx) return; cairo_pop_group_to_source(ctx); cairo_pattern_t *maskSurface = GetMaskSurface(aCanvas, aFrame, opacity); cairo_pattern_t *clipMaskSurface = GetComplexClipSurface(aCanvas, aFrame); if (clipMaskSurface) { // Still more set after clipping, so clip to another surface if (maskSurface || opacity != 1.0) { cairo_push_group(ctx); cairo_mask(ctx, clipMaskSurface); cairo_pop_group_to_source(ctx); } else { cairo_mask(ctx, clipMaskSurface); } } if (maskSurface) { cairo_mask(ctx, maskSurface); } else if (opacity != 1.0) { cairo_paint_with_alpha(ctx, opacity); } cairo_restore(ctx); if (maskSurface) cairo_pattern_destroy(maskSurface); if (clipMaskSurface) cairo_pattern_destroy(clipMaskSurface); } void nsSVGUtils::StyleEffects(nsIFrame *aFrame) { nsFrameState state = aFrame->GetStateBits(); /* clear out all effects */ if (state & NS_STATE_SVG_CLIPPED_MASK) { aFrame->DeleteProperty(nsGkAtoms::clipPath); } if (state & NS_STATE_SVG_FILTERED) { aFrame->DeleteProperty(nsGkAtoms::filter); } if (state & NS_STATE_SVG_MASKED) { aFrame->DeleteProperty(nsGkAtoms::mask); } aFrame->RemoveStateBits(NS_STATE_SVG_CLIPPED_MASK | NS_STATE_SVG_FILTERED | NS_STATE_SVG_MASKED); } void nsSVGUtils::WillModifyEffects(nsIFrame *aFrame, nsISVGValue *observable, nsISVGValue::modificationType aModType) { nsISVGFilterFrame *filter; CallQueryInterface(observable, &filter); if (filter) InvalidateFilterRegion(aFrame); } void nsSVGUtils::DidModifyEffects(nsIFrame *aFrame, nsISVGValue *observable, nsISVGValue::modificationType aModType) { nsISVGFilterFrame *filter; CallQueryInterface(observable, &filter); if (filter) { InvalidateFilterRegion(aFrame); if (aModType == nsISVGValue::mod_die) { aFrame->DeleteProperty(nsGkAtoms::filter); aFrame->RemoveStateBits(NS_STATE_SVG_FILTERED); } } } PRBool nsSVGUtils::HitTestClip(nsIFrame *aFrame, float x, float y) { PRBool clipHit = PR_TRUE; nsISVGChildFrame* SVGFrame; CallQueryInterface(aFrame, &SVGFrame); if (aFrame->GetStateBits() & NS_STATE_SVG_CLIPPED_MASK) { nsSVGClipPathFrame *clip; clip = NS_STATIC_CAST(nsSVGClipPathFrame *, aFrame->GetProperty(nsGkAtoms::clipPath)); nsCOMPtr matrix = GetCanvasTM(aFrame); clip->ClipHitTest(SVGFrame, matrix, x, y, &clipHit); } return clipHit; } void nsSVGUtils::HitTestChildren(nsIFrame *aFrame, float x, float y, nsIFrame **aResult) { // XXX: The frame's children are linked in a singly-linked list in document // order. If we were to hit test the children in this order we would need to // hit test *every* SVG frame, since even if we get a hit, later SVG frames // may lie on top of the matching frame. We really want to traverse SVG // frames in reverse order so we can stop at the first match. Since we don't // have a doubly-linked list, for the time being we traverse the // singly-linked list backwards by first reversing the nextSibling pointers // in place, and then restoring them when done. // // Note: While the child list pointers are reversed, any method which walks // the list would only encounter a single child! *aResult = nsnull; nsIFrame* current = nsnull; nsIFrame* next = aFrame->GetFirstChild(nsnull); // reverse sibling pointers while (next) { nsIFrame* temp = next->GetNextSibling(); next->SetNextSibling(current); current = next; next = temp; } // now do the backwards traversal while (current) { nsISVGChildFrame* SVGFrame; CallQueryInterface(current, &SVGFrame); if (SVGFrame) { if (NS_SUCCEEDED(SVGFrame->GetFrameForPointSVG(x, y, aResult)) && *aResult) break; } // restore current frame's sibling pointer nsIFrame* temp = current->GetNextSibling(); current->SetNextSibling(next); next = current; current = temp; } // restore remaining pointers while (current) { nsIFrame* temp = current->GetNextSibling(); current->SetNextSibling(next); next = current; current = temp; } if (*aResult && !HitTestClip(aFrame, x, y)) *aResult = nsnull; } already_AddRefed nsSVGUtils::GetCoordContextProvider(nsSVGElement *aElement) { nsCOMPtr owner; nsresult rv = aElement->GetOwnerSVGElement(getter_AddRefs(owner)); // GetOwnerSVGElement can fail during teardown if (NS_FAILED(rv) || !owner) return nsnull; nsSVGCoordCtxProvider *ctx; CallQueryInterface(owner, &ctx); return ctx; } nsRect nsSVGUtils::GetCoveredRegion(const nsFrameList &aFrames) { nsRect rect; for (nsIFrame* kid = aFrames.FirstChild(); kid; kid = kid->GetNextSibling()) { nsISVGChildFrame* child = nsnull; CallQueryInterface(kid, &child); if (child) { nsRect childRect = child->GetCoveredRegion(); rect.UnionRect(rect, childRect); } } return rect; } nsRect nsSVGUtils::ToBoundingPixelRect(double xmin, double ymin, double xmax, double ymax) { return nsRect(nscoord(floor(xmin)), nscoord(floor(ymin)), nscoord(ceil(xmax) - floor(xmin)), nscoord(ceil(ymax) - floor(ymin))); } cairo_surface_t * nsSVGUtils::GetCairoComputationalSurface() { if (!mCairoComputationalSurface) mCairoComputationalSurface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1); return mCairoComputationalSurface; } cairo_matrix_t nsSVGUtils::ConvertSVGMatrixToCairo(nsIDOMSVGMatrix *aMatrix) { float A, B, C, D, E, F; aMatrix->GetA(&A); aMatrix->GetB(&B); aMatrix->GetC(&C); aMatrix->GetD(&D); aMatrix->GetE(&E); aMatrix->GetF(&F); cairo_matrix_t m = { A, B, C, D, E, F }; return m; } PRBool nsSVGUtils::HitTestRect(nsIDOMSVGMatrix *aMatrix, float aRX, float aRY, float aRWidth, float aRHeight, float aX, float aY) { PRBool result = PR_TRUE; if (aMatrix) { cairo_matrix_t matrix = ConvertSVGMatrixToCairo(aMatrix); cairo_t *ctx = cairo_create(GetCairoComputationalSurface()); cairo_set_tolerance(ctx, 1.0); cairo_set_matrix(ctx, &matrix); cairo_new_path(ctx); cairo_rectangle(ctx, aRX, aRY, aRWidth, aRHeight); cairo_identity_matrix(ctx); if (!cairo_in_fill(ctx, aX, aY)) result = PR_FALSE; cairo_destroy(ctx); } return result; } void nsSVGUtils::UserToDeviceBBox(cairo_t *ctx, double *xmin, double *ymin, double *xmax, double *ymax) { double x[3], y[3]; x[0] = *xmin; y[0] = *ymax; x[1] = *xmax; y[1] = *ymax; x[2] = *xmax; y[2] = *ymin; cairo_user_to_device(ctx, xmin, ymin); *xmax = *xmin; *ymax = *ymin; for (int i = 0; i < 3; i++) { cairo_user_to_device(ctx, &x[i], &y[i]); *xmin = PR_MIN(*xmin, x[i]); *xmax = PR_MAX(*xmax, x[i]); *ymin = PR_MIN(*ymin, y[i]); *ymax = PR_MAX(*ymax, y[i]); } }