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;
}
}
}
}