using System; using System.Collections.Generic; using System.Drawing; using System.Drawing.Drawing2D; using System.Drawing.Imaging; using System.Drawing.Text; using System.Globalization; using System.IO; using System.Linq; using System.Text; using FastReport.Utils; namespace FastReport { /// /// Specifies the image format in SVG export. /// public enum SVGImageFormat { /// /// Specifies the .png format. /// Png, /// /// Specifies the .jpg format. /// Jpeg } /// /// Drawing objects to a svg /// public class SvgGraphics : IGraphics { private XmlDocument xmlDocument; private Graphics graphics; private Bitmap internalImage; private XmlItem root; private XmlItem oldRoot; private SizeF sizeCache; private bool disposedValue = false; // To detect redundant calls private NumberFormatInfo numberFormat = CultureInfo.InvariantCulture.NumberFormat; private RectangleF viewBox; private RectangleF viewPort; private StringFormat measureFormat; private int imageId, patternId, clipId, imageFileId; private System.Drawing.Drawing2D.Matrix oldTransform; private Region oldClip; private bool needStateCheck; private Dictionary fontStyles; private readonly float fontDpiRatio = DrawUtils.ScreenDpi / 96f; private string prefixStyle = string.Empty; public XmlDocument XmlDocument { get { return xmlDocument; } } public SizeF Size { get { return sizeCache; } set { sizeCache = value; xmlDocument.Root.SetProp("width", GetString(value.Width)); xmlDocument.Root.SetProp("height", GetString(value.Height)); } } public RectangleF ViewBox { get { return viewBox; } set { viewBox = value; xmlDocument.Root.SetProp("viewBox", String.Format("{0} {1} {2} {3}", GetString(value.Left), GetString(value.Top), GetString(value.Width), GetString(value.Height) )); } } public RectangleF ViewPort { get { return viewPort; } set { viewPort = value; xmlDocument.Root.SetProp("viewPort", String.Format("{0} {1} {2} {3}", GetString(value.Left), GetString(value.Top), GetString(value.Width), GetString(value.Height) )); } } /// /// For setting namespace, clear all attributes on setting, therefore use this property before setting other svg options /// public IEnumerable> Attributes { get { foreach (XmlProperty prop in xmlDocument.Root.Properties) { yield return new KeyValuePair(prop.Key, prop.Value); } } set { xmlDocument.Root.Properties = null; foreach (KeyValuePair kv in value) xmlDocument.Root.SetProp(kv.Key, kv.Value); } } public float DpiX => 96; public float DpiY => 96; // TODO: the following 4 properties public TextRenderingHint TextRenderingHint { get; set; } public InterpolationMode InterpolationMode { get; set; } public SmoothingMode SmoothingMode { get; set; } public CompositingQuality CompositingQuality { get; set; } public Graphics Graphics => graphics; public System.Drawing.Drawing2D.Matrix Transform { get { needStateCheck = true; return graphics.Transform; } set { needStateCheck = true; graphics.Transform = value; } } public GraphicsUnit PageUnit { get; set; } public bool IsClipEmpty => graphics.IsClipEmpty; public Region Clip { get { needStateCheck = true; return graphics.Clip; } set { needStateCheck = true; graphics.Clip = value; } } public bool EmbeddedImages { get; set; } public SVGImageFormat SvgImageFormat { get; set; } public string ImageFilePrefix { get; set; } /// /// Initialize a new Graphics for SVG, it's rendered to xml, layer by layer, not one image, /// set the Size of this graphics in Size property /// public SvgGraphics(XmlDocument xmlDocument) { this.xmlDocument = xmlDocument; root = this.xmlDocument.Root; oldRoot = root; root.Name = "svg"; root.SetProp("xmlns", "http://www.w3.org/2000/svg"); // create "style" and "defs" items at the top of document root.FindItem("style").SetProp("type", "text/css"); root.FindItem("defs"); this.internalImage = new Bitmap(1, 1); this.graphics = Graphics.FromImage(internalImage); this.measureFormat = StringFormat.GenericTypographic; oldTransform = new System.Drawing.Drawing2D.Matrix(); oldClip = new Region(); fontStyles = new Dictionary(); needStateCheck = true; EmbeddedImages = true; ImageFilePrefix = "Image"; } /// /// Sets or gets prefix for style and object ids /// public string PrefixStyle { get { return prefixStyle; } set { prefixStyle = value; } } #region IDisposable Support protected virtual void Dispose(bool disposing) { if (!disposedValue) { if (disposing) { if (graphics != null) graphics.Dispose(); graphics = null; if (internalImage != null) internalImage.Dispose(); internalImage = null; if (oldTransform != null) oldTransform.Dispose(); oldTransform = null; if (oldClip != null) oldClip.Dispose(); oldClip = null; } disposedValue = true; } } // TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources. // ~SVGGraphicsRenderer() { // // Do not change this code. Put cleanup code in Dispose(bool disposing) above. // Dispose(false); // } // This code added to correctly implement the disposable pattern. public void Dispose() { // Do not change this code. Put cleanup code in Dispose(bool disposing) above. Dispose(true); // TODO: uncomment the following line if the finalizer is overridden above. // GC.SuppressFinalize(this); } #endregion #region Internal methods private string GetString(float value) { return Math.Round(value, 2).ToString(numberFormat); } private float SpaceWidth(Font drawFont) { return graphics.MeasureString("1 2", drawFont, int.MaxValue, measureFormat).Width - graphics.MeasureString("12", drawFont, int.MaxValue, measureFormat).Width; } private string GetStringAlpha(Color color) { return GetString(color.A / 255f); } private string GetStringRGB(Color color) { return ColorTranslator.ToHtml(color); } internal XmlItem OpenContainer(string name) { // AddItem can change root node XmlItem item = AddItem(name); oldRoot = root; root = item; return item; } internal void CloseContainer() { root = oldRoot; } private XmlItem AddItem(string name) { // these comparisons are quite expensive, try to avoid them with needStateCheck bool transformChanged = needStateCheck ? !oldTransform.Equals(Transform) : false; bool clipChanged = needStateCheck ? !oldClip.Equals(Clip, graphics) : false; if (transformChanged || clipChanged) { root = xmlDocument.Root.Add(); root.Name = "g"; // text positioning compatible with .net (commented out due to problems in some SVG readers). Replaced with top offset in AddString //root.SetProp("dominant-baseline", "text-before-edge"); // transform float[] m = Transform.Elements; if (m.Length == 6 && (m[0] != 1f || m[1] != 0f || m[2] != 0f || m[3] != 1f || m[4] != 0f || m[5] != 0f)) { root.SetProp("transform", String.Format("matrix({0},{1},{2},{3},{4},{5})", GetString(m[0]), GetString(m[1]), GetString(m[2]), GetString(m[3]), GetString(m[4]), GetString(m[5]))); } // clip if (!Clip.IsInfinite(graphics)) { root.SetProp("clip-path", "url(#" + AddDefsClip() + ")"); } oldTransform.Dispose(); oldTransform = Transform.Clone(); oldClip.Dispose(); oldClip = Clip.Clone(); } needStateCheck = false; XmlItem result = root.Add(); result.Name = name; return result; } private string AddDefsClip() { System.Drawing.Drawing2D.Matrix identityMatrix = new System.Drawing.Drawing2D.Matrix(); RectangleF[] clipRects = Clip.GetRegionScans(identityMatrix); identityMatrix.Dispose(); XmlItem defsItem = xmlDocument.Root.FindItem("defs"); // search for existing clip if (clipRects.Length == 1) { string xStr = GetString(clipRects[0].X); string yStr = GetString(clipRects[0].Y); string widthStr = GetString(clipRects[0].Width); string heightStr = GetString(clipRects[0].Height); foreach (XmlItem item in defsItem.Items) { if (item.Name == "clipPath" && item.Count == 1) { if (item[0].GetProp("x") == xStr && item[0].GetProp("y") == yStr && item[0].GetProp("width") == widthStr && item[0].GetProp("height") == heightStr) return item.GetProp("id"); } } } clipId++; string clipIdStr = prefixStyle + "clip" + clipId.ToString(); XmlItem clipItem = defsItem.Add(); clipItem.Name = "clipPath"; clipItem.SetProp("id", clipIdStr); foreach (RectangleF rect in clipRects) { XmlItem clipRectItem = clipItem.Add(); clipRectItem.Name = "rect"; clipRectItem.SetProp("x", GetString(rect.X)); clipRectItem.SetProp("y", GetString(rect.Y)); clipRectItem.SetProp("width", GetString(rect.Width)); clipRectItem.SetProp("height", GetString(rect.Height)); } return clipIdStr; } private string AddDefsImage(Image bmp, float width, float height) { string href = ""; using (MemoryStream stream = new MemoryStream()) { bmp.Save(stream, ImageFormat.Png); href = "data:image/png; base64," + Convert.ToBase64String(stream.ToArray()); } string widthStr = GetString(width); string heightStr = GetString(height); XmlItem defsItem = xmlDocument.Root.FindItem("defs"); // search for existing image foreach (XmlItem item in defsItem.Items) { if (item.Name == "image") { if (item.GetProp("width") == widthStr && item.GetProp("height") == heightStr && item.GetProp("href") == href) return item.GetProp("id"); } } imageId++; string result = prefixStyle + "img" + imageId.ToString(); XmlItem imgItem = defsItem.Add(); imgItem.Name = "image"; imgItem.SetProp("id", result); imgItem.SetProp("width", widthStr); imgItem.SetProp("height", heightStr); imgItem.SetProp("href", href); return result; } private string AddDefsImagePattern(Image bmp, float x, float y, float width, float height) { string xStr = GetString(x); string yStr = GetString(y); string widthStr = GetString(width); string heightStr = GetString(height); string imgId = AddDefsImage(bmp, width, height); XmlItem defsItem = xmlDocument.Root.FindItem("defs"); // search for existing pattern foreach (XmlItem item in defsItem.Items) { if (item.Name == "pattern") { if (item.GetProp("x") == xStr && item.GetProp("y") == yStr && item.GetProp("width") == widthStr && item.GetProp("height") == heightStr) { if (item.Count == 1 && item.Items[0].Name == "use" && item.Items[0].GetProp("href") == "#" + imgId) return item.GetProp("id"); } } } patternId++; string result = prefixStyle + "p" + patternId.ToString(); XmlItem pItem = defsItem.Add(); pItem.Name = "pattern"; pItem.SetProp("id", result); pItem.SetProp("patternUnits", "userSpaceOnUse"); pItem.SetProp("x", xStr); pItem.SetProp("y", yStr); pItem.SetProp("width", widthStr); pItem.SetProp("height", heightStr); XmlItem imgItem = pItem.Add(); imgItem.Name = "use"; imgItem.SetProp("href", "#" + imgId); return result; } private void AddStroke(List properties, Pen pen) { if (pen != null) { if (pen.Width != 1) properties.Add(XmlProperty.Create("stroke-width", GetString(pen.Width))); properties.Add(XmlProperty.Create("stroke", GetStringRGB(pen.Color))); if (pen.Color.A < 255) properties.Add(XmlProperty.Create("stroke-opacity", GetStringAlpha(pen.Color))); if (pen.DashStyle != DashStyle.Solid) { string strokeDashArray = ""; foreach (float f in pen.DashPattern) { strokeDashArray += GetString(f * pen.Width) + " "; } strokeDashArray = strokeDashArray.Trim(); properties.Add(XmlProperty.Create("stroke-dasharray", strokeDashArray)); } } } private void AddFill(List properties, Brush brush) { if (brush == null) { properties.Add(XmlProperty.Create("fill", "none")); } else if (brush is SolidBrush) { SolidBrush b = brush as SolidBrush; if (b.Color != Color.Black) { properties.Add(XmlProperty.Create("fill", GetStringRGB(b.Color))); if (b.Color.A < 255) properties.Add(XmlProperty.Create("fill-opacity", GetStringAlpha(b.Color))); } } else if (brush is LinearGradientBrush || brush is PathGradientBrush) { RectangleF rect = new RectangleF(); WrapMode wrapMode = WrapMode.Tile; if (brush is LinearGradientBrush) { rect = (brush as LinearGradientBrush).Rectangle; wrapMode = (brush as LinearGradientBrush).WrapMode; } else if (brush is PathGradientBrush) { rect = (brush as PathGradientBrush).Rectangle; wrapMode = (brush as PathGradientBrush).WrapMode; } switch (wrapMode) { case WrapMode.TileFlipX: rect = new RectangleF(rect.X, rect.Y, rect.Width * 2, rect.Height); break; case WrapMode.TileFlipY: rect = new RectangleF(rect.X, rect.Y, rect.Width, rect.Height * 2); break; case WrapMode.TileFlipXY: rect = new RectangleF(rect.X, rect.Y, rect.Width * 2, rect.Height * 2); break; } using (Bitmap bmp = new Bitmap((int)rect.Width, (int)rect.Height)) { using (Graphics g = Graphics.FromImage(bmp)) { g.Clear(Color.Transparent); g.TranslateTransform(-rect.X, -rect.Y); g.FillRectangle(brush, rect); } properties.Add(XmlProperty.Create("fill", "url(#" + AddDefsImagePattern(bmp, rect.X, rect.Y, rect.Width, rect.Height) + ")")); } } else if (brush is HatchBrush) { HatchBrush b = brush as HatchBrush; using (Bitmap bmp = new Bitmap(8, 8)) { using (Graphics g = Graphics.FromImage(bmp)) { g.Clear(Color.Transparent); g.FillRectangle(b, 0, 0, 8, 8); } properties.Add(XmlProperty.Create("fill", "url(#" + AddDefsImagePattern(bmp, 0, 0, 8, 8) + ")")); } } else if (brush is TextureBrush) { TextureBrush b = brush as TextureBrush; //create a new Bitmap object with twice the height and width of the original image using (Bitmap bmp = new Bitmap(b.Image.Width * 2, b.Image.Height * 2)) { using (Graphics g = Graphics.FromImage(bmp)) { g.Clear(Color.Transparent); g.FillRectangle(b, new RectangleF(0, 0, bmp.Width, bmp.Height)); } properties.Add(XmlProperty.Create("fill", "url(#" + AddDefsImagePattern(bmp, 0, 0, bmp.Width, bmp.Height) + ")")); } } } private void AddEllipse(Pen pen, Brush brush, float left, float top, float width, float height) { XmlItem obj = AddItem("ellipse"); List properties = new List(); properties.Add(XmlProperty.Create("cx", GetString(left + width / 2))); properties.Add(XmlProperty.Create("cy", GetString(top + height / 2))); properties.Add(XmlProperty.Create("rx", GetString(width / 2))); properties.Add(XmlProperty.Create("ry", GetString(height / 2))); AddStroke(properties, pen); AddFill(properties, brush); obj.Properties = properties.ToArray(); } private void AddRectangle(Pen pen, Brush brush, float left, float top, float width, float height) { XmlItem obj = AddItem("rect"); List properties = new List(); properties.Add(XmlProperty.Create("x", GetString(left))); properties.Add(XmlProperty.Create("y", GetString(top))); properties.Add(XmlProperty.Create("width", GetString(width))); properties.Add(XmlProperty.Create("height", GetString(height))); AddStroke(properties, pen); AddFill(properties, brush); obj.Properties = properties.ToArray(); } private void AddPolygon(Pen pen, Brush brush, PointF[] points) { if (points.Length == 0) return; XmlItem obj = AddItem("polygon"); List properties = new List(); StringBuilder sbPoints = new StringBuilder(); foreach (PointF point in points) sbPoints.Append(GetString(point.X)).Append(" ").Append(GetString(point.Y)).Append(" "); if (sbPoints.Length > 0) sbPoints.Length--; properties.Add(XmlProperty.Create("points", sbPoints.ToString())); AddStroke(properties, pen); AddFill(properties, brush); obj.Properties = properties.ToArray(); } private void AddPath(Pen pen, Brush brush, GraphicsPath path) { if (path.PointCount == 0) return; XmlItem obj = AddItem("path"); List properties = new List(); StringBuilder sbd = new StringBuilder(); byte[] types = path.PathTypes; PointF[] points = path.PathPoints; int c_count = 0; for (int i = 0; i < points.Length; i++) { byte t = types[i]; if ((t & 0x7) == 0) { // MoveTo command c_count = 0; sbd.Append("M").Append(GetString(points[i].X)).Append(",").Append(GetString(points[i].Y)).Append(" "); } else if ((t & 0x7) == 1) { // LineTo command c_count = 0; sbd.Append("L").Append(GetString(points[i].X)).Append(",").Append(GetString(points[i].Y)).Append(" "); } else if ((t & 0x7) == 3) { // Cubic bezier curve command has 3 points. The C symbol must be added once at the start of sequence if (c_count == 0) sbd.Append("C"); c_count++; if (c_count >= 3) c_count = 0; sbd.Append(GetString(points[i].X)).Append(",").Append(GetString(points[i].Y)).Append(" "); } if ((t & 0x80) != 0) { // Close figure flag sbd.Append("z "); } } if (sbd.Length > 0) sbd.Length--; properties.Add(XmlProperty.Create("d", sbd.ToString())); AddStroke(properties, pen); AddFill(properties, brush); obj.Properties = properties.ToArray(); } private void AddFontStyle(List properties, Font font) { if (!fontStyles.ContainsKey(font)) { fontStyles.Add(font, prefixStyle + "st" + fontStyles.Count.ToString()); string styles = "\r\n "; foreach (KeyValuePair kv in fontStyles) { styles += " ." + kv.Value + "{" + "font-family:" + kv.Key.Name + ";" + "font-size:" + GetString(kv.Key.Size / 0.75f * fontDpiRatio) + "px;"; if ((kv.Key.Style & FontStyle.Italic) == FontStyle.Italic) styles += "font-style:italic;"; if ((kv.Key.Style & FontStyle.Bold) == FontStyle.Bold) styles += "font-weight:bold;"; string decoration = ""; if ((kv.Key.Style & FontStyle.Strikeout) != 0) decoration = "line-through"; if ((kv.Key.Style & FontStyle.Underline) != 0) decoration += " underline"; if (decoration != "") styles += "text-decoration:" + decoration + ";"; styles += "}\r\n "; } xmlDocument.Root.FindItem("style").Value = styles; } properties.Add(XmlProperty.Create("class", fontStyles[font])); } private void AddString(string text, Font font, Brush brush, float left, float top, string anchor, string direction) { XmlItem obj = AddItem("text"); List properties = new List(); properties.Add(XmlProperty.Create("x", GetString(left + SpaceWidth(font) / 2))); properties.Add(XmlProperty.Create("y", GetString(top + font.Height * 0.75f))); properties.Add(XmlProperty.Create("xml:space", "preserve")); if (anchor != "") properties.Add(XmlProperty.Create("text-anchor", anchor)); if (direction != "") properties.Add(XmlProperty.Create("direction", direction)); AddFontStyle(properties, font); AddFill(properties, brush); obj.Value = text; obj.Properties = properties.ToArray(); } #endregion #region Draw and measure text public void DrawString(string text, Font font, Brush brush, float left, float top) { AddString(text, font, brush, left, top, "", ""); } public void DrawString(string text, Font font, Brush brush, RectangleF rect, StringFormat format) { // TODO: trimming float lineHeight = font.GetHeight(graphics); List lines = new List(); if (rect.Width == 0 && rect.Height == 0) { lines.Add(text); } else { if ((format.FormatFlags & StringFormatFlags.NoWrap) == 0) format.Trimming = StringTrimming.Word; format.FormatFlags |= StringFormatFlags.LineLimit; SizeF bound = new SizeF(rect.Width, lineHeight * 1.25f); string[] paragraphs = text.Split('\n'); for (int i = 0; i < paragraphs.Length; i++) { string s = paragraphs[i]; s = s.Trim('\r'); if (s.Length == 0) lines.Add(""); while (s.Length > 0) { int charFill; int linesFill; graphics.MeasureString(s, font, bound, format, out charFill, out linesFill); if (linesFill == 0) break; lines.Add(s.Substring(0, charFill)); s = s.Substring(charFill); } } } float dx = 0; string anchor = ""; if (format.Alignment == StringAlignment.Center) { dx = (rect.Width - SpaceWidth(font)) / 2; anchor = "middle"; } else if (format.Alignment == StringAlignment.Far) { dx = rect.Width - SpaceWidth(font); anchor = "end"; } float dy = 0; if (format.LineAlignment == StringAlignment.Center) { dy = (rect.Height - lines.Count * lineHeight) / 2; } else if (format.LineAlignment == StringAlignment.Far) { dy = rect.Height - lines.Count * lineHeight; } string direction = ""; if ((format.FormatFlags & StringFormatFlags.DirectionRightToLeft) != 0) { direction = "rtl"; if (format.Alignment == StringAlignment.Far) { dx = 0; anchor = "end"; } else if (format.Alignment == StringAlignment.Near) { dx = rect.Width - SpaceWidth(font); anchor = ""; } } for (int i = 0; i < lines.Count; i++) { AddString(lines[i], font, brush, rect.Left + dx, rect.Top + dy + lineHeight * i, anchor, direction); } } public void DrawString(string text, Font font, Brush brush, RectangleF rect) { DrawString(text, font, brush, rect, StringFormat.GenericDefault); } public void DrawString(string text, Font font, Brush brush, float left, float top, StringFormat format) { DrawString(text, font, brush, new RectangleF(left, top, 0, 0), format); } public void DrawString(string text, Font font, Brush brush, PointF point, StringFormat format) { DrawString(text, font, brush, point.X, point.Y, format); } public Region[] MeasureCharacterRanges(string text, Font font, RectangleF textRect, StringFormat format) { return this.graphics.MeasureCharacterRanges(text, font, textRect, format); } public SizeF MeasureString(string text, Font font, SizeF layoutArea, StringFormat format) { return this.graphics.MeasureString(text, font, layoutArea, format); } public SizeF MeasureString(string text, Font font) { return this.graphics.MeasureString(text, font); } public SizeF MeasureString(string text, Font font, SizeF size) { return this.graphics.MeasureString(text, font, size); } public SizeF MeasureString(string text, Font font, int width, StringFormat format) { return this.graphics.MeasureString(text, font, width, format); } public void MeasureString(string text, Font font, SizeF size, StringFormat format, out int charsFit, out int linesFit) { this.graphics.MeasureString(text, font, size, format, out charsFit, out linesFit); } #endregion #region Draw images public void DrawImage(Image image, RectangleF destRect, RectangleF srcRect, GraphicsUnit srcUnit) { if (image == null || image.Width == 0 || image.Height == 0) return; if (srcRect.X != 0 || srcRect.Y != 0 || srcRect.Width != image.Width || srcRect.Height != image.Height) { // crop source image using (Bitmap newImage = new Bitmap((int)srcRect.Width, (int)srcRect.Height)) { using (Graphics g = Graphics.FromImage(newImage)) { g.Clear(Color.Transparent); g.DrawImageUnscaled(image, 0, 0); } DrawImage(newImage, destRect); } } else { DrawImage(image, destRect); } } public void DrawImage(Image image, RectangleF rect) { DrawImage(image, rect.X, rect.Y, rect.Width, rect.Height); } public void DrawImage(Image image, float x, float y, float width, float height) { if (image == null || image.Width == 0 || image.Height == 0) return; XmlItem obj = AddItem("image"); List properties = new List(); properties.Add(XmlProperty.Create("x", GetString(x))); properties.Add(XmlProperty.Create("y", GetString(y))); properties.Add(XmlProperty.Create("width", GetString(width))); properties.Add(XmlProperty.Create("height", GetString(height))); string href = ""; if (EmbeddedImages) { using (MemoryStream stream = new MemoryStream()) { image.Save(stream, SvgImageFormat == SVGImageFormat.Png ? ImageFormat.Png : ImageFormat.Jpeg); href = "data:image/" + (SvgImageFormat == SVGImageFormat.Png ? "png" : "jpg") + "; base64," + Convert.ToBase64String(stream.ToArray()); } } else { href = ImageFilePrefix + "." + imageFileId.ToString() + "." + (SvgImageFormat == SVGImageFormat.Png ? "png" : "jpg"); image.Save(href); href = Path.GetFileName(href); imageFileId++; } properties.Add(XmlProperty.Create("href", href)); obj.Properties = properties.ToArray(); } public void DrawImage(Image image, PointF[] points) { if (image == null || image.Width == 0 || image.Height == 0) return; if (points.Length != 3) return; PointF p0 = points[0]; PointF p1 = points[1]; PointF p2 = points[2]; RectangleF rect = new RectangleF(0, 0, image.Width, image.Height); float m11 = (p1.X - p0.X) / rect.Width; float m12 = (p1.Y - p0.Y) / rect.Width; float m21 = (p2.X - p0.X) / rect.Height; float m22 = (p2.Y - p0.Y) / rect.Height; IGraphicsState state = Save(); MultiplyTransform(new System.Drawing.Drawing2D.Matrix(m11, m12, m21, m22, p0.X, p0.Y), MatrixOrder.Prepend); DrawImage(image, rect); Restore(state); } public void DrawImage(Image image, float x, float y) { if (image == null) return; DrawImage(image, x, y, image.Width, image.Height); } public void DrawImage(Image image, Rectangle destRect, int srcX, int srcY, int srcWidth, int srcHeight, GraphicsUnit srcUnit, ImageAttributes imageAttr) { DrawImage(image, destRect, srcX, srcY, srcWidth, srcHeight, srcUnit, imageAttr); } public void DrawImage(Image image, Rectangle destRect, float srcX, float srcY, float srcWidth, float srcHeight, GraphicsUnit srcUnit, ImageAttributes imageAttrs) { using (Image newImage = new Bitmap(image.Width, image.Height)) { using (Graphics g = Graphics.FromImage(newImage)) { g.Clear(Color.Transparent); g.DrawImage(image, new Rectangle(0, 0, image.Width, image.Height), 0, 0, image.Width, image.Height, srcUnit, imageAttrs); } DrawImage(newImage, destRect, new RectangleF(srcX, srcY, srcWidth, srcHeight), srcUnit); } } public void DrawImageUnscaled(Image image, Rectangle rect) { if (image == null) return; DrawImage(image, rect, 0, 0, rect.Width, rect.Height, GraphicsUnit.Pixel, null); } #endregion #region Draw geometry public void DrawArc(Pen pen, float x, float y, float width, float height, float startAngle, float sweepAngle) { using (GraphicsPath path = new GraphicsPath()) { path.AddArc(x, y, width, height, startAngle, sweepAngle); AddPath(pen, null, path); } } public void DrawCurve(Pen pen, PointF[] points, int offset, int numberOfSegments, float tension) { using (GraphicsPath path = new GraphicsPath()) { path.AddCurve(points, offset, numberOfSegments, tension); AddPath(pen, null, path); } } public void DrawEllipse(Pen pen, float left, float top, float width, float height) { AddEllipse(pen, null, left, top, width, height); } public void DrawEllipse(Pen pen, RectangleF rect) { DrawEllipse(pen, rect.Left, rect.Top, rect.Width, rect.Height); } public void DrawLine(Pen pen, float x1, float y1, float x2, float y2) { //TODO add caps XmlItem obj = AddItem("line"); List properties = new List(); properties.Add(XmlProperty.Create("x1", GetString(x1))); properties.Add(XmlProperty.Create("y1", GetString(y1))); properties.Add(XmlProperty.Create("x2", GetString(x2))); properties.Add(XmlProperty.Create("y2", GetString(y2))); AddStroke(properties, pen); obj.Properties = properties.ToArray(); } public void DrawLine(Pen pen, PointF p1, PointF p2) { DrawLine(pen, p1.X, p1.Y, p2.X, p2.Y); } public void DrawLines(Pen pen, PointF[] points) { if (points.Length < 2) return; for (int i = 0; i < points.Length - 1; i++) { DrawLine(pen, points[i].X, points[i].Y, points[i + 1].X, points[i + 1].Y); } } public void DrawPath(Pen outlinePen, GraphicsPath path) { AddPath(outlinePen, null, path); } public void DrawPie(Pen pen, float x, float y, float width, float height, float startAngle, float sweepAngle) { using (GraphicsPath path = new GraphicsPath()) { path.AddPie(x, y, width, height, startAngle, sweepAngle); AddPath(pen, null, path); } } public void DrawPolygon(Pen pen, PointF[] points) { AddPolygon(pen, null, points); } public void DrawPolygon(Pen pen, Point[] points) { if (points.Length == 0) return; PointF[] pointsF = new PointF[points.Length]; for (int i = 0; i < points.Length; i++) pointsF[i] = points[i]; DrawPolygon(pen, pointsF); } public void DrawRectangle(Pen pen, float left, float top, float width, float height) { AddRectangle(pen, null, left, top, width, height); } public void DrawRectangle(Pen pen, Rectangle rect) { DrawRectangle(pen, rect.Left, rect.Top, rect.Width, rect.Height); } #endregion #region Fill geometry public void FillEllipse(Brush brush, float left, float top, float width, float height) { AddEllipse(null, brush, left, top, width, height); } public void FillEllipse(Brush brush, RectangleF rect) { FillEllipse(brush, rect.Left, rect.Top, rect.Width, rect.Height); } public void FillPath(Brush brush, GraphicsPath path) { AddPath(null, brush, path); } public void FillPie(Brush brush, float x, float y, float width, float height, float startAngle, float sweepAngle) { using (GraphicsPath path = new GraphicsPath()) { path.AddPie(x, y, width, height, startAngle, sweepAngle); AddPath(null, brush, path); } } public void FillPolygon(Brush brush, PointF[] points) { AddPolygon(null, brush, points); } public void FillPolygon(Brush brush, Point[] points) { if (points.Length == 0) return; PointF[] pointsF = new PointF[points.Length]; for (int i = 0; i < points.Length; i++) pointsF[i] = points[i]; FillPolygon(brush, pointsF); } public void FillRectangle(Brush brush, float left, float top, float width, float height) { AddRectangle(null, brush, left, top, width, height); } public void FillRectangle(Brush brush, RectangleF rect) { FillRectangle(brush, rect.Left, rect.Top, rect.Width, rect.Height); } public void FillRegion(Brush brush, Region region) { // TODO: check this case //throw new NotImplementedException(); } public void FillAndDrawPath(Pen pen, Brush brush, GraphicsPath path) { AddPath(pen, brush, path); } public void FillAndDrawEllipse(Pen pen, Brush brush, RectangleF rect) { AddEllipse(pen, brush, rect.Left, rect.Top, rect.Width, rect.Height); } public void FillAndDrawEllipse(Pen pen, Brush brush, float left, float top, float width, float height) { AddEllipse(pen, brush, left, top, width, height); } public void FillAndDrawPolygon(Pen pen, Brush brush, Point[] points) { var pointsF = points.Select(point => (PointF)point).ToArray(); AddPolygon(pen, brush, pointsF); } public void FillAndDrawPolygon(Pen pen, Brush brush, PointF[] points) { AddPolygon(pen, brush, points); } public void FillAndDrawRectangle(Pen pen, Brush brush, float left, float top, float width, float height) { AddRectangle(pen, brush, left, top, width, height); } #endregion #region Transform public void MultiplyTransform(System.Drawing.Drawing2D.Matrix matrix, MatrixOrder order) { needStateCheck = true; graphics.MultiplyTransform(matrix, order); } public void ScaleTransform(float scaleX, float scaleY) { needStateCheck = true; graphics.ScaleTransform(scaleX, scaleY); } public void TranslateTransform(float left, float top) { needStateCheck = true; graphics.TranslateTransform(left, top); } public void RotateTransform(float angle) { needStateCheck = true; graphics.RotateTransform(angle); } #endregion #region State public IGraphicsState Save() { return new SvgGraphicsState(graphics.Save()); } public void Restore(IGraphicsState state) { needStateCheck = true; if (state is SvgGraphicsState) { graphics.Restore((state as SvgGraphicsState).State); } } #endregion #region Clip public bool IsVisible(RectangleF rect) { return true;// measureGraphics.IsVisible(rect); } public void ResetClip() { needStateCheck = true; graphics.ResetClip(); } public void SetClip(RectangleF rect) { needStateCheck = true; graphics.SetClip(rect); } public void SetClip(RectangleF rect, CombineMode combineMode) { needStateCheck = true; graphics.SetClip(rect, combineMode); } public void SetClip(GraphicsPath path, CombineMode combineMode) { needStateCheck = true; graphics.SetClip(path, combineMode); } #endregion public class SvgGraphicsState : IGraphicsState { public GraphicsState State { get; } public SvgGraphicsState(GraphicsState state) { State = state; } } } }