/* -*- 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 // unknwn.h is needed to build with WIN32_LEAN_AND_MEAN #include #include using namespace Gdiplus; #include "nsCOMPtr.h" #include "nsSVGGDIPlusPathGeometry.h" #include "nsISVGRendererPathGeometry.h" #include "nsISVGGDIPlusCanvas.h" #include "nsIDOMSVGMatrix.h" #include "nsSVGGDIPlusRegion.h" #include "nsISVGRendererRegion.h" #include "nsSVGGDIPlusPathBuilder.h" #include "nsISVGPathGeometrySource.h" #include "nsISVGRendererPathBuilder.h" #include "nsSVGGDIPlusGradient.h" #include "nsMemory.h" #include "nsIDOMSVGRect.h" #include "nsSVGTypeCIDs.h" #include "nsIComponentManager.h" #include "nsISVGPathFlatten.h" /** * \addtogroup gdiplus_renderer GDI+ Rendering Engine * @{ */ //////////////////////////////////////////////////////////////////////// /** * GDI+ path geometry implementation */ class nsSVGGDIPlusPathGeometry : public nsISVGRendererPathGeometry { protected: friend nsresult NS_NewSVGGDIPlusPathGeometry(nsISVGRendererPathGeometry **result, nsISVGPathGeometrySource *src); friend void gradCBFill(Graphics *gfx, Brush *brush, void *cbStruct); friend void gradCBStroke(Graphics *gfx, Brush *brush, void *cbStruct); nsSVGGDIPlusPathGeometry(); ~nsSVGGDIPlusPathGeometry(); nsresult Init(nsISVGPathGeometrySource* src); public: // nsISupports interface: NS_DECL_ISUPPORTS // nsISVGRendererPathGeometry interface: NS_DECL_NSISVGRENDERERPATHGEOMETRY protected: void ClearPath() { if (mPath) { delete mPath; mPath=nsnull; } } void ClearFill() { if (mFill) { delete mFill; mFill=nsnull; } } void ClearStroke() { if (mStroke) {delete mStroke; mStroke=nsnull; } } void ClearCoveredRegion() { mCoveredRegion = nsnull; } void ClearHitTestRegion() { if (mHitTestRegion) {delete mHitTestRegion; mHitTestRegion=nsnull; } } GraphicsPath *GetPath(); GraphicsPath *GetFill(); GraphicsPath *GetStroke(); Region *GetHitTestRegion(); void GetGlobalTransform(Matrix *matrix); void RenderPath(GraphicsPath *path, nscolor color, float opacity, nsISVGGDIPlusCanvas *canvas); private: nsCOMPtr mSource; GraphicsPath *mPath; // untransformed path GraphicsPath *mFill; GraphicsPath *mStroke; Region *mHitTestRegion; nsCOMPtr mCoveredRegion; }; /** @} */ //---------------------------------------------------------------------- // implementation: nsSVGGDIPlusPathGeometry::nsSVGGDIPlusPathGeometry() : mPath(nsnull), mFill(nsnull), mStroke(nsnull), mHitTestRegion(nsnull) { } nsSVGGDIPlusPathGeometry::~nsSVGGDIPlusPathGeometry() { ClearPath(); ClearFill(); ClearStroke(); ClearCoveredRegion(); ClearHitTestRegion(); } nsresult nsSVGGDIPlusPathGeometry::Init(nsISVGPathGeometrySource* src) { mSource = src; return NS_OK; } nsresult NS_NewSVGGDIPlusPathGeometry(nsISVGRendererPathGeometry **result, nsISVGPathGeometrySource *src) { nsSVGGDIPlusPathGeometry* pg = new nsSVGGDIPlusPathGeometry(); if (!pg) return NS_ERROR_OUT_OF_MEMORY; NS_ADDREF(pg); nsresult rv = pg->Init(src); if (NS_FAILED(rv)) { NS_RELEASE(pg); return rv; } *result = pg; return rv; } //---------------------------------------------------------------------- // nsISupports methods: NS_IMPL_ADDREF(nsSVGGDIPlusPathGeometry) NS_IMPL_RELEASE(nsSVGGDIPlusPathGeometry) NS_INTERFACE_MAP_BEGIN(nsSVGGDIPlusPathGeometry) NS_INTERFACE_MAP_ENTRY(nsISVGRendererPathGeometry) NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_END //---------------------------------------------------------------------- GraphicsPath * nsSVGGDIPlusPathGeometry::GetPath() { if (mPath) return mPath; mPath = new GraphicsPath(); nsCOMPtr builder; NS_NewSVGGDIPlusPathBuilder(getter_AddRefs(builder), mSource, mPath); mSource->ConstructPath(builder); return mPath; } GraphicsPath * nsSVGGDIPlusPathGeometry::GetFill() { if (mFill) return mFill; GraphicsPath *path = GetPath(); if (!path) return nsnull; mFill = path->Clone(); Matrix m; GetGlobalTransform(&m); mFill->Flatten(&m); return mFill; } GraphicsPath * nsSVGGDIPlusPathGeometry::GetStroke() { if (mStroke) return mStroke; GraphicsPath *path = GetPath(); if (!path) return nsnull; float width; mSource->GetStrokeWidth(&width); if (width==0.0f) return nsnull; mStroke = path->Clone(); // construct the pen: // gdi+ seems to widen paths to >1 width unit. If our pen width is // smaller, we need to widen an appropriately enlarged path and then // scale it down after widening: PRBool scaleToUnitPen = width<1.0f; Pen pen(Color(), scaleToUnitPen ? 1.0f : width); // set linecap style PRUint16 capStyle; LineCap lcap; DashCap dcap; mSource->GetStrokeLinecap(&capStyle); switch (capStyle) { case nsISVGGeometrySource::STROKE_LINECAP_BUTT: lcap = LineCapFlat; dcap = DashCapFlat; break; case nsISVGGeometrySource::STROKE_LINECAP_ROUND: lcap = LineCapRound; dcap = DashCapRound; break; case nsISVGGeometrySource::STROKE_LINECAP_SQUARE: lcap = LineCapSquare; dcap = DashCapFlat; break; } pen.SetLineCap(lcap, lcap, dcap); // set miterlimit: float miterlimit; mSource->GetStrokeMiterlimit(&miterlimit); pen.SetMiterLimit(miterlimit); // set linejoin style: PRUint16 joinStyle; LineJoin join; mSource->GetStrokeLinejoin(&joinStyle); switch(joinStyle) { case nsISVGGeometrySource::STROKE_LINEJOIN_MITER: join = LineJoinMiterClipped; break; case nsISVGGeometrySource::STROKE_LINEJOIN_ROUND: join = LineJoinRound; break; case nsISVGGeometrySource::STROKE_LINEJOIN_BEVEL: join = LineJoinBevel; break; } pen.SetLineJoin(join); // set pen dashpattern float *dashArray; PRUint32 count; mSource->GetStrokeDashArray(&dashArray, &count); if (count>0) { if (!scaleToUnitPen) { for (PRUint32 i=0; iGetStrokeDashoffset(&offset); if (!scaleToUnitPen) offset/=width; pen.SetDashOffset(offset); } Matrix m; GetGlobalTransform(&m); if (scaleToUnitPen) { Matrix enlarge(1/width, 0, 0, 1/width, 0, 0); mStroke->Transform(&enlarge); m.Scale(width,width); } mStroke->Widen(&pen, &m); // mStroke->Outline(); // mStroke->Transform(&m); return mStroke; } Region * nsSVGGDIPlusPathGeometry::GetHitTestRegion() { if (mHitTestRegion) return mHitTestRegion; mHitTestRegion = new Region(); if (!mHitTestRegion) { NS_ERROR("could not construct region"); return nsnull; } mHitTestRegion->MakeEmpty(); PRUint16 mask=0; mSource->GetHittestMask(&mask); if (mask & nsISVGPathGeometrySource::HITTEST_MASK_FILL) { GraphicsPath *fill = GetFill(); if (fill) mHitTestRegion->Union(fill); } if (mask & nsISVGPathGeometrySource::HITTEST_MASK_STROKE) { GraphicsPath *stroke = GetStroke(); if (stroke) mHitTestRegion->Union(stroke); } return mHitTestRegion; } void nsSVGGDIPlusPathGeometry::GetGlobalTransform(Matrix *matrix) { nsCOMPtr ctm; mSource->GetCanvasTM(getter_AddRefs(ctm)); NS_ASSERTION(ctm, "graphic source didn't specify a ctm"); float m[6]; float val; ctm->GetA(&val); m[0] = val; ctm->GetB(&val); m[1] = val; ctm->GetC(&val); m[2] = val; ctm->GetD(&val); m[3] = val; ctm->GetE(&val); m[4] = val; ctm->GetF(&val); m[5] = val; matrix->SetElements(m[0],m[1],m[2],m[3],m[4],m[5]); } void nsSVGGDIPlusPathGeometry::RenderPath(GraphicsPath *path, nscolor color, float opacity, nsISVGGDIPlusCanvas *canvas) { SolidBrush brush(Color((BYTE)(opacity*255),NS_GET_R(color),NS_GET_G(color),NS_GET_B(color))); canvas->GetGraphics()->FillPath(&brush, path); } //---------------------------------------------------------------------- // nsISVGRendererPathGeometry methods: static void gradCBFill(Graphics *gfx, Brush *brush, void *cbStruct) { nsSVGGDIPlusPathGeometry *geom = (nsSVGGDIPlusPathGeometry *)cbStruct; gfx->FillPath(brush, geom->GetFill()); } static void gradCBStroke(Graphics *gfx, Brush *brush, void *cbStruct) { nsSVGGDIPlusPathGeometry *geom = (nsSVGGDIPlusPathGeometry *)cbStruct; gfx->FillPath(brush, geom->GetStroke()); } /** Implements void render(in nsISVGRendererCanvas canvas); */ NS_IMETHODIMP nsSVGGDIPlusPathGeometry::Render(nsISVGRendererCanvas *canvas) { nsCOMPtr gdiplusCanvas = do_QueryInterface(canvas); NS_ASSERTION(gdiplusCanvas, "wrong svg render context for geometry!"); if (!gdiplusCanvas) return NS_ERROR_FAILURE; PRUint16 canvasRenderMode; canvas->GetRenderMode(&canvasRenderMode); if (canvasRenderMode == nsISVGRendererCanvas::SVG_RENDER_MODE_CLIP) { Region *region = gdiplusCanvas->GetClipRegion(); GraphicsPath *path = GetFill(); PRUint16 rule; mSource->GetClipRule(&rule); if (rule == nsISVGGeometrySource::FILL_RULE_EVENODD) path->SetFillMode(FillModeAlternate); else path->SetFillMode(FillModeWinding); region->Union(path); return NS_OK; } PRUint16 renderingMode; mSource->GetShapeRendering(&renderingMode); switch (renderingMode) { case nsISVGPathGeometrySource::SHAPE_RENDERING_OPTIMIZESPEED: case nsISVGPathGeometrySource::SHAPE_RENDERING_CRISPEDGES: gdiplusCanvas->GetGraphics()->SetSmoothingMode(SmoothingModeNone); break; default: gdiplusCanvas->GetGraphics()->SetSmoothingMode(SmoothingModeAntiAlias); break; } gdiplusCanvas->GetGraphics()->SetCompositingQuality(CompositingQualityHighSpeed); // gdiplusCanvas->GetGraphics()->SetPixelOffsetMode(PixelOffsetModeHalf); nscolor color; float opacity; PRUint16 type, serverType = 0; // paint fill: mSource->GetFillPaintType(&type); if (type == nsISVGGeometrySource::PAINT_TYPE_SERVER) { if(NS_FAILED(mSource->GetFillPaintServerType(&serverType))) type = nsISVGGeometrySource::PAINT_TYPE_NONE; } if (type != nsISVGGeometrySource::PAINT_TYPE_NONE && GetFill()) { if (type == nsISVGGeometrySource::PAINT_TYPE_SOLID_COLOR) { mSource->GetFillPaint(&color); mSource->GetFillOpacity(&opacity); RenderPath(GetFill(), color, opacity, gdiplusCanvas); } else { if (serverType == nsISVGGeometrySource::PAINT_TYPE_GRADIENT) { nsCOMPtr aGrad; mSource->GetFillGradient(getter_AddRefs(aGrad)); nsCOMPtr region; GetCoveredRegion(getter_AddRefs(region)); nsCOMPtr aRegion = do_QueryInterface(region); nsCOMPtr ctm; mSource->GetCanvasTM(getter_AddRefs(ctm)); GDIPlusGradient(aRegion, aGrad, ctm, gdiplusCanvas->GetGraphics(), mSource, gradCBFill, this); } } } // paint stroke: mSource->GetStrokePaintType(&type); if (type == nsISVGGeometrySource::PAINT_TYPE_SERVER) { if(NS_FAILED(mSource->GetStrokePaintServerType(&serverType))) type = nsISVGGeometrySource::PAINT_TYPE_NONE; } if (type != nsISVGGeometrySource::PAINT_TYPE_NONE && GetStroke()) { if (type == nsISVGGeometrySource::PAINT_TYPE_SOLID_COLOR) { mSource->GetStrokePaint(&color); mSource->GetStrokeOpacity(&opacity); RenderPath(GetStroke(), color, opacity, gdiplusCanvas); } else { if (serverType == nsISVGGeometrySource::PAINT_TYPE_GRADIENT) { nsCOMPtr aGrad; mSource->GetStrokeGradient(getter_AddRefs(aGrad)); nsCOMPtr region; GetCoveredRegion(getter_AddRefs(region)); nsCOMPtr aRegion = do_QueryInterface(region); nsCOMPtr ctm; mSource->GetCanvasTM(getter_AddRefs(ctm)); GDIPlusGradient(aRegion, aGrad, ctm, gdiplusCanvas->GetGraphics(), mSource, gradCBStroke, this); } } } return NS_OK; } /** Implements nsISVGRendererRegion update(in unsigned long updatemask); */ NS_IMETHODIMP nsSVGGDIPlusPathGeometry::Update(PRUint32 updatemask, nsISVGRendererRegion **_retval) { *_retval = nsnull; const unsigned long pathmask = nsISVGGeometrySource::UPDATEMASK_FILL_RULE | nsISVGPathGeometrySource::UPDATEMASK_PATH; const unsigned long fillmask = pathmask | nsISVGGeometrySource::UPDATEMASK_CANVAS_TM; const unsigned long strokemask = pathmask | nsISVGGeometrySource::UPDATEMASK_STROKE_WIDTH | nsISVGGeometrySource::UPDATEMASK_STROKE_LINECAP | nsISVGGeometrySource::UPDATEMASK_STROKE_LINEJOIN | nsISVGGeometrySource::UPDATEMASK_STROKE_MITERLIMIT | nsISVGGeometrySource::UPDATEMASK_STROKE_DASH_ARRAY | nsISVGGeometrySource::UPDATEMASK_STROKE_DASHOFFSET | nsISVGGeometrySource::UPDATEMASK_CANVAS_TM; const unsigned long hittestregionmask = fillmask | strokemask | nsISVGPathGeometrySource::UPDATEMASK_HITTEST_MASK; const unsigned long coveredregionmask = fillmask | strokemask | nsISVGGeometrySource::UPDATEMASK_FILL_PAINT_TYPE | nsISVGGeometrySource::UPDATEMASK_STROKE_PAINT_TYPE; nsCOMPtr before; // only obtain the 'before' region if we have built a path before: if (mFill || mStroke) GetCoveredRegion(getter_AddRefs(before)); if ((updatemask & pathmask)!=0) ClearPath(); if ((updatemask & fillmask)!=0) ClearFill(); if ((updatemask & strokemask)!=0) ClearStroke(); if ((updatemask & hittestregionmask)!=0) ClearHitTestRegion(); if ((updatemask & coveredregionmask)!=0) { ClearCoveredRegion(); nsCOMPtr after; GetCoveredRegion(getter_AddRefs(after)); if (after) after->Combine(before, _retval); } if (!*_retval) { *_retval = before; NS_IF_ADDREF(*_retval); } return NS_OK; } /** Implements nsISVGRendererRegion getCoveredRegion(); */ NS_IMETHODIMP nsSVGGDIPlusPathGeometry::GetCoveredRegion(nsISVGRendererRegion **_retval) { *_retval = nsnull; if (mCoveredRegion) { *_retval = mCoveredRegion; NS_ADDREF(*_retval); return NS_OK; } PRUint16 type; mSource->GetFillPaintType(&type); bool hasCoveredFill = (type!=nsISVGGeometrySource::PAINT_TYPE_NONE) && GetFill(); mSource->GetStrokePaintType(&type); bool hasCoveredStroke = (type!=nsISVGGeometrySource::PAINT_TYPE_NONE) && GetStroke(); if (!hasCoveredFill && !hasCoveredStroke) return NS_OK; if (hasCoveredFill) { nsCOMPtr reg1; NS_NewSVGGDIPlusPathRegion(getter_AddRefs(reg1), GetFill()); if (hasCoveredStroke) { nsCOMPtr reg2; NS_NewSVGGDIPlusPathRegion(getter_AddRefs(reg2), GetStroke()); reg1->Combine(reg2, _retval); } else { *_retval = reg1; NS_ADDREF(*_retval); } } // covered stroke only else NS_NewSVGGDIPlusPathRegion(_retval, GetStroke()); mCoveredRegion = *_retval; return NS_OK; } /** Implements boolean containsPoint(in float x, in float y); */ NS_IMETHODIMP nsSVGGDIPlusPathGeometry::ContainsPoint(float x, float y, PRBool *_retval) { *_retval = PR_FALSE; if (GetHitTestRegion()->IsVisible(x,y)) { *_retval = PR_TRUE; } return NS_OK; } NS_IMETHODIMP nsSVGGDIPlusPathGeometry::GetBoundingBox(nsIDOMSVGRect * *aBoundingBox) { *aBoundingBox = nsnull; GraphicsPath *path = GetFill(); if (!path) return NS_ERROR_FAILURE; nsCOMPtr rect = do_CreateInstance(NS_SVGRECT_CONTRACTID); NS_ASSERTION(rect, "could not create rect"); if (!rect) return NS_ERROR_FAILURE; RectF bounds; path->GetBounds(&bounds); rect->SetX(bounds.X); rect->SetY(bounds.Y); rect->SetWidth(bounds.Width); rect->SetHeight(bounds.Height); *aBoundingBox = rect; NS_ADDREF(*aBoundingBox); return NS_OK; } NS_IMETHODIMP nsSVGGDIPlusPathGeometry::Flatten(nsSVGPathData **aData) { *aData = nsnull; GraphicsPath *path = GetPath(); if (!path) return NS_ERROR_FAILURE; PathData pdata; path->Flatten(); path->GetPathData(&pdata); *aData = new nsSVGPathData; for (PRInt32 i = 0; i < pdata.Count; i++) { switch (pdata.Types[i] & PathPointTypePathTypeMask) { case PathPointTypeStart: (*aData)->AddPoint(pdata.Points[i].X, pdata.Points[i].Y, NS_SVGPATHFLATTEN_MOVE); break; case PathPointTypeLine: (*aData)->AddPoint(pdata.Points[i].X, pdata.Points[i].Y, NS_SVGPATHFLATTEN_LINE); break; default: /* should never happen with a flattened path */ break; } if ((pdata.Types[i] & PathPointTypeCloseSubpath) && (*aData)->count) { /* find beginning of current subpath */ for (PRUint32 k = (*aData)->count - 1; k >= 0; k--) if ((*aData)->type[k] == NS_SVGPATHFLATTEN_MOVE) { (*aData)->AddPoint((*aData)->x[k], (*aData)->y[k], NS_SVGPATHFLATTEN_LINE); break; } } } return NS_OK; }