Files
Mozilla/mozilla/layout/svg/base/src/nsSVGUtils.cpp
longsonr%gmail.com db2c6b1d24 Bug 354831 - remove repeated include in nsSVGUtils.cpp. r+sr=tor
git-svn-id: svn://10.0.0.236/trunk@213104 18797224-902f-48f8-a5cc-f745e15eee43
2006-10-04 09:35:43 +00:00

982 lines
28 KiB
C++

/* -*- 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<nsIDOMSVGElement> element = do_QueryInterface(aContent);
nsCOMPtr<nsIDOMSVGSVGElement> owner;
element->GetOwnerSVGElement(getter_AddRefs(owner));
nsCOMPtr<nsSVGCoordCtxProvider> ctx = do_QueryInterface(owner);
nsCOMPtr<nsISVGLength> length;
NS_NewSVGLength(getter_AddRefs(length), aCoord.GetPercentValue() * 100.0f,
nsIDOMSVGLength::SVG_LENGTHTYPE_PERCENTAGE);
if (!ctx || !length)
break;
length->SetContext(nsRefPtr<nsSVGCoordCtx>(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<nsIDOMSVGRect> unionRect;
nsIFrame* kid = aFrames->FirstChild();
while (kid) {
nsISVGChildFrame* SVGFrame = nsnull;
CallQueryInterface(kid, &SVGFrame);
if (SVGFrame) {
nsCOMPtr<nsIDOMSVGRect> 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<nsIDOMSVGPoint> point;
NS_NewSVGPoint(getter_AddRefs(point), *x, *y);
if (!point)
return;
nsCOMPtr<nsIDOMSVGPoint> 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<nsIDOMSVGMatrix>
nsSVGUtils::GetViewBoxTransform(float aViewportWidth, float aViewportHeight,
float aViewboxX, float aViewboxY,
float aViewboxWidth, float aViewboxHeight,
nsIDOMSVGAnimatedPreserveAspectRatio *aPreserveAspectRatio,
PRBool aIgnoreAlign)
{
PRUint16 align, meetOrSlice;
{
nsCOMPtr<nsIDOMSVGPreserveAspectRatio> 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<nsIDOMSVGMatrix>
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<nsIDOMSVGMatrix> 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<nsISVGCairoCanvas> cairoCanvas = do_QueryInterface(aCanvas);
cairo_t *ctx = cairoCanvas->GetContext();
cairo_push_group(ctx);
nsCOMPtr<nsIDOMSVGMatrix> 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<nsIDOMSVGMatrix> 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<nsISVGCairoCanvas> 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<nsIDOMSVGMatrix> 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<nsIDOMSVGMatrix> 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<nsSVGCoordCtxProvider>
nsSVGUtils::GetCoordContextProvider(nsSVGElement *aElement)
{
nsCOMPtr<nsIDOMSVGSVGElement> 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]);
}
}