using System; using System.Xml; using System.Drawing; using System.Drawing.Drawing2D; using System.Text.RegularExpressions; using SharpVectors.Dom.Css; using SharpVectors.Dom.Svg; namespace SharpVectors.Renderers.Gdi { public class GdiRendering : GdiRenderingBase { #region Private Fields private Matrix _transformMatrix; internal Color _uniqueColor; internal GdiGraphicsContainer _graphicsContainer; #endregion #region Constructors and Destructor public GdiRendering(SvgElement element) : base(element) { _uniqueColor = Color.Empty; } #endregion #region Public Properties public Color UniqueColor { get { return _uniqueColor; } } public Matrix TransformMatrix { get { return _transformMatrix; } set { _transformMatrix = value; } } #endregion #region Public Methods public override void BeforeRender(GdiGraphicsRenderer renderer) { if (renderer == null) { return; } if (_uniqueColor.IsEmpty) _uniqueColor = renderer.GetNextColor(element); GdiGraphicsWrapper graphics = renderer.GraphicsWrapper; if (graphics == null) { return; } _graphicsContainer = graphics.BeginContainer(); SetQuality(graphics); Transform(graphics); Clip(graphics); } public override void AfterRender(GdiGraphicsRenderer renderer) { if (renderer == null) { return; } GdiGraphicsWrapper graphics = renderer.GraphicsWrapper; if (graphics == null) { return; } graphics.EndContainer(_graphicsContainer); } #endregion #region Public Static Methods public static GdiRendering Create(ISvgElement element) { if (element == null) { return null; } SvgRenderingHint hint = element.RenderingHint; // For the shapes and text contents... if (hint == SvgRenderingHint.Shape) { return new GdiPathRendering((SvgElement)element); } if (hint == SvgRenderingHint.Text) { return new GdiTextRendering((SvgElement)element); } string localName = element.LocalName; if (String.IsNullOrEmpty(localName)) { return new GdiRendering((SvgElement)element); } switch (localName) { case "svg": return new GdiRootRendering((SvgElement)element); case "image": return new GdiImageRendering((SvgElement)element); case "marker": return new GdiMarkerRendering((SvgElement)element); } return new GdiRendering((SvgElement)element); } /// /// Generates a new RenderingNode that /// corresponds to the given Uri. /// /// /// The base Uri. /// /// /// The url. /// /// /// The generated RenderingNode that /// corresponds to the given Uri. /// public static GdiRendering CreateByUri(SvgDocument document, string baseUri, string url) { if (url.StartsWith("#")) { // do nothing } else if (baseUri != "") { Uri absoluteUri = new Uri(new Uri(baseUri), url); url = absoluteUri.AbsoluteUri; } else { // TODO: Handle xml:base here? // Right now just skip this... it can't be resolved, must assume it is absolute } ISvgElement elm = document.GetNodeByUri(url) as ISvgElement; if (elm != null) { return GdiRendering.Create(elm); } else { return null; } } #endregion #region Protected Methods protected void Clip(GdiGraphicsWrapper gr) { if (element == null) { return; } SvgRenderingHint hint = element.RenderingHint; // todo: should we correct the clipping to adjust to the off-one-pixel drawing? gr.TranslateClip(1, 1); #region Clip with clip // see http://www.w3.org/TR/SVG/masking.html#OverflowAndClipProperties if (element is ISvgSvgElement || element is ISvgMarkerElement || element is ISvgSymbolElement || element is ISvgPatternElement) { // check overflow property CssValue overflow = ((SvgElement)element).GetComputedCssValue("overflow", String.Empty) as CssValue; // TODO: clip can have "rect(10 10 auto 10)" CssPrimitiveValue clip = ((SvgElement)element).GetComputedCssValue("clip", String.Empty) as CssPrimitiveValue; string sOverflow = null; if (overflow != null || overflow.CssText == "") { sOverflow = overflow.CssText; } else { if (this is ISvgSvgElement) sOverflow = "hidden"; } if (sOverflow != null) { // "If the 'overflow' property has a value other than hidden or scroll, the property has no effect (i.e., a clipping rectangle is not created)." if (sOverflow == "hidden" || sOverflow == "scroll") { RectangleF clipRect = RectangleF.Empty; if (clip != null && clip.PrimitiveType == CssPrimitiveType.Rect) { if (element is ISvgSvgElement) { ISvgSvgElement svgElement = (ISvgSvgElement)element; SvgRect viewPort = svgElement.Viewport as SvgRect; clipRect = GdiConverter.ToRectangle(viewPort); ICssRect clipShape = (CssRect)clip.GetRectValue(); if (clipShape.Top.PrimitiveType != CssPrimitiveType.Ident) clipRect.Y += (float)clipShape.Top.GetFloatValue(CssPrimitiveType.Number); if (clipShape.Left.PrimitiveType != CssPrimitiveType.Ident) clipRect.X += (float)clipShape.Left.GetFloatValue(CssPrimitiveType.Number); if (clipShape.Right.PrimitiveType != CssPrimitiveType.Ident) clipRect.Width = (clipRect.Right - clipRect.X) - (float)clipShape.Right.GetFloatValue(CssPrimitiveType.Number); if (clipShape.Bottom.PrimitiveType != CssPrimitiveType.Ident) clipRect.Height = (clipRect.Bottom - clipRect.Y) - (float)clipShape.Bottom.GetFloatValue(CssPrimitiveType.Number); } } else if (clip == null || (clip.PrimitiveType == CssPrimitiveType.Ident && clip.GetStringValue() == "auto")) { if (element is ISvgSvgElement) { ISvgSvgElement svgElement = (ISvgSvgElement)element; SvgRect viewPort = svgElement.Viewport as SvgRect; clipRect = GdiConverter.ToRectangle(viewPort); } else if (element is ISvgMarkerElement || element is ISvgSymbolElement || element is ISvgPatternElement) { // TODO: what to do here? } } if (clipRect != RectangleF.Empty) { gr.SetClip(clipRect); } } } } #endregion #region Clip with clip-path // see: http://www.w3.org/TR/SVG/masking.html#EstablishingANewClippingPath if (hint == SvgRenderingHint.Shape || hint == SvgRenderingHint.Text || hint == SvgRenderingHint.Clipping || hint == SvgRenderingHint.Masking || hint == SvgRenderingHint.Containment) { CssPrimitiveValue clipPath = ((SvgElement)element).GetComputedCssValue("clip-path", String.Empty) as CssPrimitiveValue; if (clipPath != null && clipPath.PrimitiveType == CssPrimitiveType.Uri) { string absoluteUri = ((SvgElement)element).ResolveUri(clipPath.GetStringValue()); SvgClipPathElement eClipPath = element.OwnerDocument.GetNodeByUri(absoluteUri) as SvgClipPathElement; if (eClipPath != null) { GraphicsPath gpClip = CreateClippingRegion(eClipPath); RectangleF clipBounds = gpClip != null ? gpClip.GetBounds() : RectangleF.Empty; if (clipBounds.Width == 0 || clipBounds.Height == 0) { return; } SvgUnitType pathUnits = (SvgUnitType)eClipPath.ClipPathUnits.AnimVal; if (pathUnits == SvgUnitType.ObjectBoundingBox) { SvgTransformableElement transElement = element as SvgTransformableElement; if (transElement != null) { ISvgRect bbox = transElement.GetBBox(); // scale clipping path Matrix matrix = new Matrix(); matrix.Scale((float)bbox.Width, (float)bbox.Height); gpClip.Transform(matrix); gr.SetClip(gpClip); // offset clip gr.TranslateClip((float)bbox.X, (float)bbox.Y); } else { throw new NotImplementedException("clip-path with SvgUnitType.ObjectBoundingBox " + "not supported for this type of element: " + element.GetType()); } } else { gr.SetClip(gpClip); } gpClip.Dispose(); gpClip = null; } } } #endregion } protected void SetQuality(GdiGraphicsWrapper gr) { Graphics graphics = gr.Graphics; string colorRendering = ((SvgElement)element).GetComputedStringValue("color-rendering", String.Empty); switch (colorRendering) { case "optimizeSpeed": graphics.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighSpeed; break; case "optimizeQuality": graphics.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality; break; default: // "auto" // todo: could use AssumeLinear for slightly better graphics.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.Default; break; } if (element is SvgTextContentElement) { // Unfortunately the text rendering hints are not applied because the // text path is recorded and painted to the Graphics object as a path // not as text. string textRendering = ((SvgElement)element).GetComputedStringValue("text-rendering", String.Empty); switch (textRendering) { case "optimizeSpeed": graphics.SmoothingMode = SmoothingMode.HighSpeed; //graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.SingleBitPerPixel; break; case "optimizeLegibility": graphics.SmoothingMode = SmoothingMode.HighQuality; //graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit; break; case "geometricPrecision": graphics.SmoothingMode = SmoothingMode.AntiAlias; //graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias; break; default: // "auto" graphics.SmoothingMode = SmoothingMode.AntiAlias; //graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.SystemDefault; break; } } else { string shapeRendering = ((SvgElement)element).GetComputedStringValue("shape-rendering", String.Empty); switch (shapeRendering) { case "optimizeSpeed": graphics.SmoothingMode = SmoothingMode.HighSpeed; break; case "crispEdges": graphics.SmoothingMode = SmoothingMode.None; break; case "geometricPrecision": graphics.SmoothingMode = SmoothingMode.HighQuality; break; default: // "auto" graphics.SmoothingMode = SmoothingMode.AntiAlias; break; } } } protected void Transform(GdiGraphicsWrapper gr) { if (element is ISvgTransformable) { if (_transformMatrix == null) { ISvgTransformable transElm = (ISvgTransformable)element; SvgTransformList svgTList = (SvgTransformList)transElm.Transform.AnimVal; SvgMatrix svgMatrix = ((SvgTransformList)transElm.Transform.AnimVal).TotalMatrix; _transformMatrix = new Matrix((float)svgMatrix.A, (float)svgMatrix.B, (float)svgMatrix.C, (float)svgMatrix.D, (float)svgMatrix.E, (float)svgMatrix.F); } gr.Transform = _transformMatrix; } } protected void FitToViewbox(GdiGraphicsWrapper graphics, RectangleF elmRect) { if (element is ISvgFitToViewBox) { ISvgFitToViewBox fitToVBElm = (ISvgFitToViewBox)element; SvgPreserveAspectRatio spar = (SvgPreserveAspectRatio)fitToVBElm.PreserveAspectRatio.AnimVal; double[] translateAndScale = spar.FitToViewBox( (SvgRect)fitToVBElm.ViewBox.AnimVal, new SvgRect(elmRect.X, elmRect.Y, elmRect.Width, elmRect.Height)); graphics.TranslateTransform((float)translateAndScale[0], (float)translateAndScale[1]); graphics.ScaleTransform((float)translateAndScale[2], (float)translateAndScale[3]); } } #endregion #region Private Methods private GraphicsPath CreateClippingRegion(SvgClipPathElement clipPath) { GraphicsPath path = new GraphicsPath(); foreach (XmlNode node in clipPath.ChildNodes) { if (node.NodeType != XmlNodeType.Element) { continue; } // Handle a case where the clip element has "use" element as a child... if (String.Equals(node.LocalName, "use")) { SvgUseElement useElement = (SvgUseElement)node; XmlElement refEl = useElement.ReferencedElement; if (refEl != null) { XmlElement refElParent = (XmlElement)refEl.ParentNode; useElement.OwnerDocument.Static = true; useElement.CopyToReferencedElement(refEl); refElParent.RemoveChild(refEl); useElement.AppendChild(refEl); foreach (XmlNode useChild in useElement.ChildNodes) { if (useChild.NodeType != XmlNodeType.Element) { continue; } SvgStyleableElement element = useChild as SvgStyleableElement; if (element != null && element.RenderingHint == SvgRenderingHint.Shape) { GraphicsPath childPath = CreatePath(element); if (childPath != null) { string clipRule = element.GetPropertyValue("clip-rule"); path.FillMode = (clipRule == "evenodd") ? FillMode.Alternate : FillMode.Winding; path.AddPath(childPath, true); } } } useElement.RemoveChild(refEl); useElement.RestoreReferencedElement(refEl); refElParent.AppendChild(refEl); useElement.OwnerDocument.Static = false; } } else { SvgStyleableElement element = node as SvgStyleableElement; if (element != null && element.RenderingHint == SvgRenderingHint.Shape) { GraphicsPath childPath = CreatePath(element); if (childPath != null) { string clipRule = element.GetPropertyValue("clip-rule"); path.FillMode = (clipRule == "evenodd") ? FillMode.Alternate : FillMode.Winding; path.AddPath(childPath, true); } } } } return path; } #endregion #region GraphicsPath Methods public static GraphicsPath CreatePath(ISvgElement element) { if (element == null || element.RenderingHint != SvgRenderingHint.Shape) { return null; } string localName = element.LocalName; switch (localName) { case "ellipse": return CreatePath((SvgEllipseElement)element); case "rect": return CreatePath((SvgRectElement)element); case "line": return CreatePath((SvgLineElement)element); case "path": return CreatePath((SvgPathElement)element); case "circle": return CreatePath((SvgCircleElement)element); case "polyline": return CreatePath((SvgPolylineElement)element); case "polygon": return CreatePath((SvgPolygonElement)element); } return null; } #region SvgEllipseElement Path public static GraphicsPath CreatePath(SvgEllipseElement element) { GraphicsPath gp = new GraphicsPath(); float _cx = (float)element.Cx.AnimVal.Value; float _cy = (float)element.Cy.AnimVal.Value; float _rx = (float)element.Rx.AnimVal.Value; float _ry = (float)element.Ry.AnimVal.Value; /*if (_cx <= 1 && _cy <= 1 && _rx <= 1 && _ry <= 1) { gp.AddEllipse(_cx-_rx, _cy-_ry, _rx*2, _ry*2); } else { gp.AddEllipse(_cx-_rx, _cy-_ry, _rx*2 - 1, _ry*2 - 1); }*/ gp.AddEllipse(_cx - _rx, _cy - _ry, _rx * 2, _ry * 2); return gp; } #endregion #region SvgRectElement Path public static GraphicsPath CreatePath(SvgRectElement element) { GraphicsPath gp = new GraphicsPath(); RectangleF rect = new RectangleF((float)element.X.AnimVal.Value, (float)element.Y.AnimVal.Value, (float)element.Width.AnimVal.Value, (float)element.Height.AnimVal.Value); float rx = (float)element.Rx.AnimVal.Value; float ry = (float)element.Ry.AnimVal.Value; if (rx == 0F && ry == 0F) { gp.AddRectangle(rect); } else { AddRoundedRect(gp, rect, rx, ry); } return gp; } public static void AddRoundedRect(GraphicsPath path, RectangleF rect, float rx, float ry) { if (rx == 0F) rx = ry; else if (ry == 0F) ry = rx; rx = Math.Min(rect.Width / 2, rx); ry = Math.Min(rect.Height / 2, ry); float a = rect.X + rect.Width - rx; path.AddLine(rect.X + rx, rect.Y, a, rect.Y); path.AddArc(a - rx, rect.Y, rx * 2, ry * 2, 270, 90); float right = rect.X + rect.Width; // rightmost X float b = rect.Y + rect.Height - ry; path.AddLine(right, rect.Y + ry, right, b); path.AddArc(right - rx * 2, b - ry, rx * 2, ry * 2, 0, 90); path.AddLine(right - rx, rect.Y + rect.Height, rect.X + rx, rect.Y + rect.Height); path.AddArc(rect.X, b - ry, rx * 2, ry * 2, 90, 90); path.AddLine(rect.X, b, rect.X, rect.Y + ry); path.AddArc(rect.X, rect.Y, rx * 2, ry * 2, 180, 90); } #endregion #region SvgLineElement Path public static GraphicsPath CreatePath(SvgLineElement element) { GraphicsPath gp = new GraphicsPath(); gp.AddLine((float)element.X1.AnimVal.Value, (float)element.Y1.AnimVal.Value, (float)element.X2.AnimVal.Value, (float)element.Y2.AnimVal.Value); return gp; } #endregion #region SvgPathElement Path public static GraphicsPath CreatePath(SvgPathElement element) { GraphicsPath gp = new GraphicsPath(); SvgPointF initPoint = new SvgPointF(0, 0); SvgPointF lastPoint = new SvgPointF(0, 0); ISvgPathSeg segment = null; SvgPathSegMoveto pathMoveTo = null; SvgPathSegLineto pathLineTo = null; SvgPathSegCurveto pathCurveTo = null; SvgPathSegArc pathArc = null; ISvgPathSegList segments = element.PathSegList; int nElems = segments.NumberOfItems; for (int i = 0; i < nElems; i++) { segment = segments.GetItem(i); if (DynamicCast.Cast(segment, out pathMoveTo)) { //SvgPathSegMoveto seg = (SvgPathSegMoveto)segment; gp.StartFigure(); lastPoint = initPoint = pathMoveTo.AbsXY; } else if (DynamicCast.Cast(segment, out pathLineTo)) { //SvgPathSegLineto seg = (SvgPathSegLineto)segment; SvgPointF p = pathLineTo.AbsXY; gp.AddLine(lastPoint.X, lastPoint.Y, p.X, p.Y); lastPoint = p; } else if (DynamicCast.Cast(segment, out pathCurveTo)) { // SvgPathSegCurveto seg = (SvgPathSegCurveto)segment; SvgPointF xy = pathCurveTo.AbsXY; SvgPointF x1y1 = pathCurveTo.CubicX1Y1; SvgPointF x2y2 = pathCurveTo.CubicX2Y2; gp.AddBezier(lastPoint.X, lastPoint.Y, x1y1.X, x1y1.Y, x2y2.X, x2y2.Y, xy.X, xy.Y); lastPoint = xy; } else if (DynamicCast.Cast(segment, out pathArc)) { //SvgPathSegArc seg = (SvgPathSegArc)segment; SvgPointF p = pathArc.AbsXY; if (lastPoint.Equals(p)) { // If the endpoints (x, y) and (x0, y0) are identical, then this // is equivalent to omitting the elliptical arc segment entirely. } else if (pathArc.R1 == 0 || pathArc.R2 == 0) { // Ensure radii are valid gp.AddLine(lastPoint.X, lastPoint.Y, p.X, p.Y); } else { CalculatedArcValues calcValues = pathArc.GetCalculatedArcValues(); GraphicsPath gp2 = new GraphicsPath(); gp2.StartFigure(); gp2.AddArc((float)(calcValues.Cx - calcValues.CorrRx), (float)(calcValues.Cy - calcValues.CorrRy), (float)calcValues.CorrRx * 2, (float)calcValues.CorrRy * 2, (float)calcValues.AngleStart, (float)calcValues.AngleExtent); Matrix matrix = new Matrix(); matrix.Translate(-(float)calcValues.Cx, -(float)calcValues.Cy); gp2.Transform(matrix); matrix = new Matrix(); matrix.Rotate((float)pathArc.Angle); gp2.Transform(matrix); matrix = new Matrix(); matrix.Translate((float)calcValues.Cx, (float)calcValues.Cy); gp2.Transform(matrix); gp.AddPath(gp2, true); } lastPoint = p; } else if (segment is SvgPathSegClosePath) { gp.CloseFigure(); lastPoint = initPoint; } } string fillRule = element.GetPropertyValue("fill-rule"); if (fillRule == "evenodd") gp.FillMode = FillMode.Alternate; else gp.FillMode = FillMode.Winding; return gp; } #endregion #region SvgCircleElement Path public static GraphicsPath CreatePath(SvgCircleElement element) { GraphicsPath gp = new GraphicsPath(); float _cx = (float)element.Cx.AnimVal.Value; float _cy = (float)element.Cy.AnimVal.Value; float _r = (float)element.R.AnimVal.Value; gp.AddEllipse(_cx - _r, _cy - _r, _r * 2, _r * 2); return gp; } #endregion #region SvgPolylineElement Path public static GraphicsPath CreatePath(SvgPolylineElement element) { GraphicsPath gp = new GraphicsPath(); ISvgPointList list = element.AnimatedPoints; ulong nElems = list.NumberOfItems; PointF[] points = new PointF[nElems]; for (uint i = 0; i < nElems; i++) { points[i] = new PointF((float)list.GetItem(i).X, (float)list.GetItem(i).Y); } gp.AddLines(points); string fillRule = element.GetPropertyValue("fill-rule"); if (fillRule == "evenodd") gp.FillMode = FillMode.Alternate; else gp.FillMode = FillMode.Winding; return gp; } #endregion #region SvgPolygonElement Path public static GraphicsPath CreatePath(SvgPolygonElement element) { GraphicsPath gp = new GraphicsPath(); ISvgPointList list = element.AnimatedPoints; ulong nElems = list.NumberOfItems; PointF[] points = new PointF[nElems]; for (uint i = 0; i < nElems; i++) { points[i] = new PointF((float)list.GetItem(i).X, (float)list.GetItem(i).Y); } gp.AddPolygon(points); string fillRule = element.GetPropertyValue("fill-rule"); if (fillRule == "evenodd") gp.FillMode = FillMode.Alternate; else gp.FillMode = FillMode.Winding; return gp; } #endregion #endregion #region Marker Methods protected static string ExtractMarkerUrl(string propValue) { Regex reUrl = new Regex(@"^url\((?.+)\)$"); Match match = reUrl.Match(propValue); if (match.Success) { return match.Groups["uri"].Value; } else { return String.Empty; } } protected static void PaintMarkers(GdiGraphicsRenderer renderer, SvgStyleableElement styleElm, GdiGraphicsWrapper gr) { // OPTIMIZE if (styleElm is ISharpMarkerHost) { string markerStartUrl = ExtractMarkerUrl(styleElm.GetPropertyValue("marker-start", "marker")); string markerMiddleUrl = ExtractMarkerUrl(styleElm.GetPropertyValue("marker-mid", "marker")); string markerEndUrl = ExtractMarkerUrl(styleElm.GetPropertyValue("marker-end", "marker")); if (markerStartUrl.Length > 0) { GdiMarkerRendering grNode = CreateByUri(styleElm.OwnerDocument, styleElm.BaseURI, markerStartUrl) as GdiMarkerRendering; if (grNode != null) { grNode.PaintMarker(renderer, gr, SvgMarkerPosition.Start, styleElm); } } if (markerMiddleUrl.Length > 0) { // TODO markerMiddleUrl != markerStartUrl GdiMarkerRendering grNode = CreateByUri(styleElm.OwnerDocument, styleElm.BaseURI, markerMiddleUrl) as GdiMarkerRendering; if (grNode != null) { grNode.PaintMarker(renderer, gr, SvgMarkerPosition.Mid, styleElm); } } if (markerEndUrl.Length > 0) { // TODO: markerEndUrl != markerMiddleUrl GdiMarkerRendering grNode = CreateByUri(styleElm.OwnerDocument, styleElm.BaseURI, markerEndUrl) as GdiMarkerRendering; if (grNode != null) { grNode.PaintMarker(renderer, gr, SvgMarkerPosition.End, styleElm); } } } } #endregion #region IDisposable Members protected override void Dispose(bool disposing) { if (disposing) { if (_transformMatrix != null) { _transformMatrix.Dispose(); _transformMatrix = null; } } base.Dispose(disposing); } #endregion } }