VectSharp  2.2.1
A light library for C# vector graphics
SVGContext.cs
1 /*
2  VectSharp - A light library for C# vector graphics.
3  Copyright (C) 2020-2022 Giorgio Bianchini
4 
5  This program is free software: you can redistribute it and/or modify
6  it under the terms of the GNU Lesser General Public License as published by
7  the Free Software Foundation, version 3.
8 
9  This program is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  GNU Lesser General Public License for more details.
13 
14  You should have received a copy of the GNU Lesser General Public License
15  along with this program. If not, see <https://www.gnu.org/licenses/>.
16 */
17 
18 using System;
19 using System.Collections.Generic;
20 using System.Data;
21 using System.IO;
22 using System.Linq;
23 using System.Text;
24 using System.Xml;
25 using VectSharp.Filters;
26 
27 namespace VectSharp.SVG
28 {
29  internal static class MatrixUtils
30  {
31  public static double[] Multiply(double[,] matrix, double[] vector)
32  {
33  double[] tbr = new double[2];
34 
35  tbr[0] = matrix[0, 0] * vector[0] + matrix[0, 1] * vector[1] + matrix[0, 2];
36  tbr[1] = matrix[1, 0] * vector[0] + matrix[1, 1] * vector[1] + matrix[1, 2];
37 
38  return tbr;
39  }
40 
41  public static Point Multiply(double[,] matrix, Point vector)
42  {
43  double[] tbr = new double[2];
44 
45  tbr[0] = matrix[0, 0] * vector.X + matrix[0, 1] * vector.Y + matrix[0, 2];
46  tbr[1] = matrix[1, 0] * vector.X + matrix[1, 1] * vector.Y + matrix[1, 2];
47 
48  return new Point(tbr[0], tbr[1]);
49  }
50 
51  public static double[,] Multiply(double[,] matrix1, double[,] matrix2)
52  {
53  double[,] tbr = new double[3, 3];
54 
55  for (int i = 0; i < 3; i++)
56  {
57  for (int j = 0; j < 3; j++)
58  {
59  for (int k = 0; k < 3; k++)
60  {
61  tbr[i, j] += matrix1[i, k] * matrix2[k, j];
62  }
63  }
64  }
65 
66  return tbr;
67  }
68 
69  public static double[,] Rotate(double[,] matrix, double angle)
70  {
71  double[,] rotationMatrix = new double[3, 3];
72  rotationMatrix[0, 0] = Math.Cos(angle);
73  rotationMatrix[0, 1] = -Math.Sin(angle);
74  rotationMatrix[1, 0] = Math.Sin(angle);
75  rotationMatrix[1, 1] = Math.Cos(angle);
76  rotationMatrix[2, 2] = 1;
77 
78  return Multiply(matrix, rotationMatrix);
79  }
80 
81  public static double[,] Translate(double[,] matrix, double x, double y)
82  {
83  double[,] translationMatrix = new double[3, 3];
84  translationMatrix[0, 0] = 1;
85  translationMatrix[0, 2] = x;
86  translationMatrix[1, 1] = 1;
87  translationMatrix[1, 2] = y;
88  translationMatrix[2, 2] = 1;
89 
90  return Multiply(matrix, translationMatrix);
91  }
92 
93  public static double[,] Scale(double[,] matrix, double scaleX, double scaleY)
94  {
95  double[,] scaleMatrix = new double[3, 3];
96  scaleMatrix[0, 0] = scaleX;
97  scaleMatrix[1, 1] = scaleY;
98  scaleMatrix[2, 2] = 1;
99 
100  return Multiply(matrix, scaleMatrix);
101  }
102 
103  public static double[,] Identity = new double[,] { { 1, 0, 0 }, { 0, 1, 0 }, { 0, 0, 1 } };
104 
105  public static double[,] Invert(double[,] m)
106  {
107  double[,] tbr = new double[3, 3];
108 
109  tbr[0, 0] = (m[1, 1] * m[2, 2] - m[1, 2] * m[2, 1]) / (m[0, 0] * m[1, 1] * m[2, 2] - m[0, 0] * m[1, 2] * m[2, 1] - m[1, 0] * m[0, 1] * m[2, 2] + m[2, 0] * m[0, 1] * m[1, 2] + m[1, 0] * m[0, 2] * m[2, 1] - m[2, 0] * m[0, 2] * m[1, 1]);
110  tbr[0, 1] = -(m[0, 1] * m[2, 2] - m[0, 2] * m[2, 1]) / (m[0, 0] * m[1, 1] * m[2, 2] - m[0, 0] * m[1, 2] * m[2, 1] - m[1, 0] * m[0, 1] * m[2, 2] + m[2, 0] * m[0, 1] * m[1, 2] + m[1, 0] * m[0, 2] * m[2, 1] - m[2, 0] * m[0, 2] * m[1, 1]);
111  tbr[0, 2] = (m[0, 1] * m[1, 2] - m[0, 2] * m[1, 1]) / (m[0, 0] * m[1, 1] * m[2, 2] - m[0, 0] * m[1, 2] * m[2, 1] - m[1, 0] * m[0, 1] * m[2, 2] + m[2, 0] * m[0, 1] * m[1, 2] + m[1, 0] * m[0, 2] * m[2, 1] - m[2, 0] * m[0, 2] * m[1, 1]);
112  tbr[1, 0] = -(m[1, 0] * m[2, 2] - m[1, 2] * m[2, 0]) / (m[0, 0] * m[1, 1] * m[2, 2] - m[0, 0] * m[1, 2] * m[2, 1] - m[1, 0] * m[0, 1] * m[2, 2] + m[2, 0] * m[0, 1] * m[1, 2] + m[1, 0] * m[0, 2] * m[2, 1] - m[2, 0] * m[0, 2] * m[1, 1]);
113  tbr[1, 1] = (m[0, 0] * m[2, 2] - m[0, 2] * m[2, 0]) / (m[0, 0] * m[1, 1] * m[2, 2] - m[0, 0] * m[1, 2] * m[2, 1] - m[1, 0] * m[0, 1] * m[2, 2] + m[2, 0] * m[0, 1] * m[1, 2] + m[1, 0] * m[0, 2] * m[2, 1] - m[2, 0] * m[0, 2] * m[1, 1]);
114  tbr[1, 2] = -(m[0, 0] * m[1, 2] - m[0, 2] * m[1, 0]) / (m[0, 0] * m[1, 1] * m[2, 2] - m[0, 0] * m[1, 2] * m[2, 1] - m[1, 0] * m[0, 1] * m[2, 2] + m[2, 0] * m[0, 1] * m[1, 2] + m[1, 0] * m[0, 2] * m[2, 1] - m[2, 0] * m[0, 2] * m[1, 1]);
115  tbr[2, 0] = (m[1, 0] * m[2, 1] - m[1, 1] * m[2, 0]) / (m[0, 0] * m[1, 1] * m[2, 2] - m[0, 0] * m[1, 2] * m[2, 1] - m[1, 0] * m[0, 1] * m[2, 2] + m[2, 0] * m[0, 1] * m[1, 2] + m[1, 0] * m[0, 2] * m[2, 1] - m[2, 0] * m[0, 2] * m[1, 1]);
116  tbr[2, 1] = -(m[0, 0] * m[2, 1] - m[0, 1] * m[2, 0]) / (m[0, 0] * m[1, 1] * m[2, 2] - m[0, 0] * m[1, 2] * m[2, 1] - m[1, 0] * m[0, 1] * m[2, 2] + m[2, 0] * m[0, 1] * m[1, 2] + m[1, 0] * m[0, 2] * m[2, 1] - m[2, 0] * m[0, 2] * m[1, 1]);
117  tbr[2, 2] = (m[0, 0] * m[1, 1] - m[0, 1] * m[1, 0]) / (m[0, 0] * m[1, 1] * m[2, 2] - m[0, 0] * m[1, 2] * m[2, 1] - m[1, 0] * m[0, 1] * m[2, 2] + m[2, 0] * m[0, 1] * m[1, 2] + m[1, 0] * m[0, 2] * m[2, 1] - m[2, 0] * m[0, 2] * m[1, 1]);
118 
119  return tbr;
120  }
121  }
122 
123  internal class SVGFigure
124  {
125  public Point StartPoint { get; set; }
126  public Point CurrentPoint { get; set; }
127 
128  public string Data { get; set; }
129  public int PointCount { get; set; } = 0;
130  }
131 
132  internal class SVGPathObject
133  {
134  public List<SVGFigure> Figures { get; set; } = new List<SVGFigure>();
135  }
136 
137 
138  internal class SVGContext : IGraphicsContext
139  {
140  public Dictionary<string, FontFamily> UsedFontFamilies;
141  public Dictionary<string, HashSet<char>> UsedChars;
142 
143 
144  public const string SVGNamespace = "http://www.w3.org/2000/svg";
145 
146  public double Width { get; }
147 
148  public double Height { get; }
149 
150  public Font Font { get; set; }
151  public TextBaselines TextBaseline { get; set; }
152 
153  public Brush FillStyle { get; private set; }
154 
155  public Brush StrokeStyle { get; private set; }
156 
157  public double LineWidth { get; set; }
158  public LineCaps LineCap { private get; set; }
159  public LineJoins LineJoin { private get; set; }
160 
161  private LineDash _lineDash;
162 
163  private SVGPathObject currentPath;
164  private SVGFigure currentFigure;
165 
166  public XmlDocument Document;
167  private XmlElement currentElement;
168 
169  public string Tag { get; set; }
170 
171  private double[,] _transform;
172  private Stack<double[,]> states;
173 
174  private string _currClipPath;
175  private Stack<string> clipPaths;
176 
177  private bool TextToPaths = false;
178  private SVGContextInterpreter.TextOptions TextOption = SVGContextInterpreter.TextOptions.SubsetFonts;
179 
180  private Dictionary<string, string> linkDestinations;
181 
182  XmlElement definitions;
183  Dictionary<Brush, string> gradients;
184 
185  private SVGContextInterpreter.FilterOption FilterOption;
186 
187 
188  public SVGContext(double width, double height, bool textToPaths, SVGContextInterpreter.TextOptions textOption, Dictionary<string, string> linkDestinations, SVGContextInterpreter.FilterOption filterOption)
189  {
190  this.linkDestinations = linkDestinations;
191 
192  this.Width = width;
193  this.Height = height;
194 
195  currentPath = new SVGPathObject();
196  currentFigure = new SVGFigure();
197 
198  StrokeStyle = Colour.FromRgba(0, 0, 0, 0);
199  FillStyle = Colour.FromRgb(0, 0, 0);
200  LineWidth = 1;
201 
202  LineCap = LineCaps.Butt;
203  LineJoin = LineJoins.Miter;
204  _lineDash = new LineDash(0, 0, 0);
205 
207 
208  TextBaseline = TextBaselines.Top;
209 
210  _transform = new double[3, 3];
211 
212  _transform[0, 0] = 1;
213  _transform[1, 1] = 1;
214  _transform[2, 2] = 1;
215 
216  states = new Stack<double[,]>();
217 
218  clipPaths = new Stack<string>();
219  clipPaths.Push(null);
220  _currClipPath = null;
221 
222  UsedFontFamilies = new Dictionary<string, FontFamily>();
223  UsedChars = new Dictionary<string, HashSet<char>>();
224 
225  Document = new XmlDocument();
226 
227  Document.InsertBefore(Document.CreateXmlDeclaration("1.0", "UTF-8", null), Document.DocumentElement);
228 
229  currentElement = Document.CreateElement(null, "svg", SVGNamespace);
230  currentElement.SetAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
231  currentElement.SetAttribute("viewBox", "0 0 " + width.ToString(System.Globalization.CultureInfo.InvariantCulture) + " " + height.ToString(System.Globalization.CultureInfo.InvariantCulture));
232  currentElement.SetAttribute("version", "1.1");
233  Document.AppendChild(currentElement);
234 
235  definitions = Document.CreateElement("defs", SVGNamespace);
236  gradients = new Dictionary<Brush, string>();
237  currentElement.AppendChild(definitions);
238 
239  this.TextToPaths = textToPaths;
240  this.TextOption = textOption;
241  this.FilterOption = filterOption;
242  }
243 
244 
245  private void PathText(string text, double x, double y)
246  {
247  GraphicsPath textPath = new GraphicsPath().AddText(x, y, text, Font, TextBaseline);
248 
249  for (int j = 0; j < textPath.Segments.Count; j++)
250  {
251  switch (textPath.Segments[j].Type)
252  {
253  case VectSharp.SegmentType.Move:
254  this.MoveTo(textPath.Segments[j].Point.X, textPath.Segments[j].Point.Y);
255  break;
256  case VectSharp.SegmentType.Line:
257  this.LineTo(textPath.Segments[j].Point.X, textPath.Segments[j].Point.Y);
258  break;
259  case VectSharp.SegmentType.CubicBezier:
260  this.CubicBezierTo(textPath.Segments[j].Points[0].X, textPath.Segments[j].Points[0].Y, textPath.Segments[j].Points[1].X, textPath.Segments[j].Points[1].Y, textPath.Segments[j].Points[2].X, textPath.Segments[j].Points[2].Y);
261  break;
262  case VectSharp.SegmentType.Close:
263  this.Close();
264  break;
265  }
266  }
267  }
268 
269  public void Close()
270  {
271  if (currentFigure.PointCount > 0)
272  {
273  currentFigure.Data += "Z ";
274  currentPath.Figures.Add(currentFigure);
275 
276  currentFigure = new SVGFigure();
277  }
278  }
279 
280  public void CubicBezierTo(double p1X, double p1Y, double p2X, double p2Y, double p3X, double p3Y)
281  {
282  if (currentFigure.PointCount == 0)
283  {
284  currentFigure.StartPoint = new Point(p1X, p1Y);
285  }
286 
287  currentFigure.CurrentPoint = new Point(p3X, p3Y);
288  currentFigure.Data += "C " + p1X.ToString(System.Globalization.CultureInfo.InvariantCulture) + " " + p1Y.ToString(System.Globalization.CultureInfo.InvariantCulture) + ", " +
289  p2X.ToString(System.Globalization.CultureInfo.InvariantCulture) + " " + p2Y.ToString(System.Globalization.CultureInfo.InvariantCulture) + ", " +
290  p3X.ToString(System.Globalization.CultureInfo.InvariantCulture) + " " + p3Y.ToString(System.Globalization.CultureInfo.InvariantCulture) + " ";
291  currentFigure.PointCount += 3;
292  }
293 
294  public void Fill()
295  {
296  if (currentFigure.PointCount > 0)
297  {
298  currentPath.Figures.Add(currentFigure);
299  }
300 
301  XmlElement currElement = currentElement;
302 
303  if (!string.IsNullOrEmpty(_currClipPath))
304  {
305  currentElement = Document.CreateElement("g", SVGNamespace);
306  currentElement.SetAttribute("clip-path", _currClipPath);
307  currElement.AppendChild(currentElement);
308  }
309 
310  XmlElement path = Document.CreateElement("path", SVGNamespace);
311  path.SetAttribute("d", currentPath.Figures.Aggregate("", (a, b) => a + b.Data));
312  path.SetAttribute("stroke", "none");
313 
314  if (FillStyle is SolidColourBrush solid)
315  {
316  path.SetAttribute("fill", solid.Colour.ToCSSString(false));
317  path.SetAttribute("fill-opacity", solid.A.ToString(System.Globalization.CultureInfo.InvariantCulture));
318  }
319  else if (FillStyle is LinearGradientBrush linearGradient)
320  {
321  string gradientName;
322 
323  if (!gradients.TryGetValue(linearGradient, out gradientName))
324  {
325  gradientName = "gradient" + (gradients.Count + 1).ToString(System.Globalization.CultureInfo.InvariantCulture);
326 
327  XmlElement gradientElement = linearGradient.ToLinearGradient(Document, gradientName);
328  this.definitions.AppendChild(gradientElement);
329 
330  gradients.Add(linearGradient, gradientName);
331  }
332 
333  path.SetAttribute("fill", "url(#" + gradientName + ")");
334  }
335  else if (FillStyle is RadialGradientBrush radialGradient)
336  {
337  string gradientName;
338 
339  if (!gradients.TryGetValue(radialGradient, out gradientName))
340  {
341  gradientName = "gradient" + (gradients.Count + 1).ToString(System.Globalization.CultureInfo.InvariantCulture);
342 
343  XmlElement gradientElement = radialGradient.ToRadialGradient(Document, gradientName);
344  this.definitions.AppendChild(gradientElement);
345 
346  gradients.Add(radialGradient, gradientName);
347  }
348 
349  path.SetAttribute("fill", "url(#" + gradientName + ")");
350  }
351 
352  path.SetAttribute("transform", "matrix(" + _transform[0, 0].ToString(System.Globalization.CultureInfo.InvariantCulture) + "," + _transform[1, 0].ToString(System.Globalization.CultureInfo.InvariantCulture) +
353  "," + _transform[0, 1].ToString(System.Globalization.CultureInfo.InvariantCulture) + "," + _transform[1, 1].ToString(System.Globalization.CultureInfo.InvariantCulture) +
354  "," + _transform[0, 2].ToString(System.Globalization.CultureInfo.InvariantCulture) + "," + _transform[1, 2].ToString(System.Globalization.CultureInfo.InvariantCulture) + ")");
355 
356  if (!string.IsNullOrEmpty(Tag))
357  {
358  path.SetAttribute("id", Tag);
359  }
360 
361  if (!string.IsNullOrEmpty(this.Tag) && this.linkDestinations.TryGetValue(this.Tag, out string destination) && !string.IsNullOrEmpty(destination))
362  {
363  XmlElement aElement = Document.CreateElement("a", SVGNamespace);
364  aElement.SetAttribute("href", destination);
365  currentElement.AppendChild(aElement);
366  currentElement = aElement;
367  }
368 
369  currentElement.AppendChild(path);
370 
371  currentElement = currElement;
372 
373  currentPath = new SVGPathObject();
374  currentFigure = new SVGFigure();
375 
376  }
377 
378  public void FillText(string text, double x, double y)
379  {
380  if (!TextToPaths)
381  {
382 
383  if (!UsedFontFamilies.ContainsKey(Font.FontFamily.FileName))
384  {
385  UsedFontFamilies.Add(Font.FontFamily.FileName, Font.FontFamily);
386  UsedChars.Add(Font.FontFamily.FileName, new HashSet<char>());
387  }
388 
389  UsedChars[Font.FontFamily.FileName].UnionWith(text);
390 
392 
393  double[,] currTransform = null;
394  double[,] deltaTransform = MatrixUtils.Identity;
395 
396  switch (TextBaseline)
397  {
398  case TextBaselines.Baseline:
399  currTransform = MatrixUtils.Translate(_transform, x - metrics.LeftSideBearing, y);
400  deltaTransform = MatrixUtils.Translate(deltaTransform, x - metrics.LeftSideBearing, y);
401  break;
402  case TextBaselines.Top:
403  currTransform = MatrixUtils.Translate(_transform, x - metrics.LeftSideBearing, y + metrics.Top);
404  deltaTransform = MatrixUtils.Translate(deltaTransform, x - metrics.LeftSideBearing, y + metrics.Top);
405  break;
406  case TextBaselines.Bottom:
407  currTransform = MatrixUtils.Translate(_transform, x - metrics.LeftSideBearing, y + metrics.Bottom);
408  deltaTransform = MatrixUtils.Translate(deltaTransform, x - metrics.LeftSideBearing, y + metrics.Bottom);
409  break;
410  case TextBaselines.Middle:
411  currTransform = MatrixUtils.Translate(_transform, x - metrics.LeftSideBearing, y + (metrics.Top + metrics.Bottom) * 0.5);
412  deltaTransform = MatrixUtils.Translate(deltaTransform, x - metrics.LeftSideBearing, y + (metrics.Top + metrics.Bottom) * 0.5);
413  break;
414  default:
415  currTransform = MatrixUtils.Translate(_transform, x - metrics.LeftSideBearing, y);
416  deltaTransform = MatrixUtils.Translate(deltaTransform, x - metrics.LeftSideBearing, y);
417  break;
418  }
419 
420  XmlElement currElement = currentElement;
421 
422  if (!string.IsNullOrEmpty(_currClipPath))
423  {
424  currentElement = Document.CreateElement("g", SVGNamespace);
425  currentElement.SetAttribute("clip-path", _currClipPath);
426  currElement.AppendChild(currentElement);
427  }
428 
429  XmlElement textElement = Document.CreateElement("text", SVGNamespace);
430 
431  textElement.SetAttribute("stroke", "none");
432 
433  if (FillStyle is SolidColourBrush solid)
434  {
435  textElement.SetAttribute("fill", solid.Colour.ToCSSString(false));
436  textElement.SetAttribute("fill-opacity", solid.A.ToString(System.Globalization.CultureInfo.InvariantCulture));
437  }
438  else if (FillStyle is LinearGradientBrush linearGradient)
439  {
440  string gradientName = "g" + Guid.NewGuid().ToString("N");
441 
442  XmlElement gradientElement = linearGradient.ToLinearGradient(Document, gradientName);
443 
444  deltaTransform = MatrixUtils.Invert(deltaTransform);
445 
446  gradientElement.SetAttribute("gradientTransform", "matrix(" + deltaTransform[0, 0].ToString(System.Globalization.CultureInfo.InvariantCulture) + "," + deltaTransform[1, 0].ToString(System.Globalization.CultureInfo.InvariantCulture) +
447  "," + deltaTransform[0, 1].ToString(System.Globalization.CultureInfo.InvariantCulture) + "," + deltaTransform[1, 1].ToString(System.Globalization.CultureInfo.InvariantCulture) +
448  "," + deltaTransform[0, 2].ToString(System.Globalization.CultureInfo.InvariantCulture) + "," + deltaTransform[1, 2].ToString(System.Globalization.CultureInfo.InvariantCulture) + ")");
449 
450  this.definitions.AppendChild(gradientElement);
451 
452  textElement.SetAttribute("fill", "url(#" + gradientName + ")");
453  }
454  else if (FillStyle is RadialGradientBrush radialGradient)
455  {
456  string gradientName = "g" + Guid.NewGuid().ToString("N");
457 
458  XmlElement gradientElement = radialGradient.ToRadialGradient(Document, gradientName);
459 
460  deltaTransform = MatrixUtils.Invert(deltaTransform);
461 
462  gradientElement.SetAttribute("gradientTransform", "matrix(" + deltaTransform[0, 0].ToString(System.Globalization.CultureInfo.InvariantCulture) + "," + deltaTransform[1, 0].ToString(System.Globalization.CultureInfo.InvariantCulture) +
463  "," + deltaTransform[0, 1].ToString(System.Globalization.CultureInfo.InvariantCulture) + "," + deltaTransform[1, 1].ToString(System.Globalization.CultureInfo.InvariantCulture) +
464  "," + deltaTransform[0, 2].ToString(System.Globalization.CultureInfo.InvariantCulture) + "," + deltaTransform[1, 2].ToString(System.Globalization.CultureInfo.InvariantCulture) + ")");
465 
466  this.definitions.AppendChild(gradientElement);
467 
468  textElement.SetAttribute("fill", "url(#" + gradientName + ")");
469  }
470 
471  textElement.SetAttribute("transform", "matrix(" + currTransform[0, 0].ToString(System.Globalization.CultureInfo.InvariantCulture) + "," + currTransform[1, 0].ToString(System.Globalization.CultureInfo.InvariantCulture) +
472  "," + currTransform[0, 1].ToString(System.Globalization.CultureInfo.InvariantCulture) + "," + currTransform[1, 1].ToString(System.Globalization.CultureInfo.InvariantCulture) +
473  "," + currTransform[0, 2].ToString(System.Globalization.CultureInfo.InvariantCulture) + "," + currTransform[1, 2].ToString(System.Globalization.CultureInfo.InvariantCulture) + ")");
474 
475  textElement.SetAttribute("x", "0");
476  textElement.SetAttribute("y", "0");
477  textElement.SetAttribute("font-size", Font.FontSize.ToString(System.Globalization.CultureInfo.InvariantCulture));
478  textElement.SetAttribute("font-family", Font.FontFamily.FileName);
479 
480  if (Font.FontFamily.IsBold)
481  {
482  textElement.SetAttribute("font-weight", "bold");
483  }
484  else
485  {
486  textElement.SetAttribute("font-weight", "regular");
487  }
488 
490  {
491  textElement.SetAttribute("font-style", "italic");
492  }
493  else
494  {
495  textElement.SetAttribute("font-style", "normal");
496  }
497 
499  {
500  textElement.SetAttribute("font-style", "oblique");
501  }
502 
503  ProcessText(text, textElement);
504 
505  if (!string.IsNullOrEmpty(Tag))
506  {
507  textElement.SetAttribute("id", Tag);
508  }
509 
510  if (!string.IsNullOrEmpty(this.Tag) && this.linkDestinations.TryGetValue(this.Tag, out string destination) && !string.IsNullOrEmpty(destination))
511  {
512  XmlElement aElement = Document.CreateElement("a", SVGNamespace);
513  aElement.SetAttribute("href", destination);
514  currentElement.AppendChild(aElement);
515  currentElement = aElement;
516  }
517 
518  currentElement.AppendChild(textElement);
519 
520  currentElement = currElement;
521  }
522  else
523  {
524  PathText(text, x, y);
525  Fill();
526  }
527  }
528 
529  public void LineTo(double x, double y)
530  {
531  if (currentFigure.PointCount == 0)
532  {
533  currentFigure.StartPoint = new Point(x, y);
534  }
535 
536  currentFigure.CurrentPoint = new Point(x, y);
537  currentFigure.Data += "L " + x.ToString(System.Globalization.CultureInfo.InvariantCulture) + " " + y.ToString(System.Globalization.CultureInfo.InvariantCulture) + " ";
538  currentFigure.PointCount++;
539  }
540 
541  public void MoveTo(double x, double y)
542  {
543  if (currentFigure.PointCount > 0)
544  {
545  currentPath.Figures.Add(currentFigure);
546  }
547 
548  currentFigure = new SVGFigure();
549  currentFigure.CurrentPoint = new Point(x, y);
550  currentFigure.StartPoint = new Point(x, y);
551  currentFigure.Data += "M " + x.ToString(System.Globalization.CultureInfo.InvariantCulture) + " " + y.ToString(System.Globalization.CultureInfo.InvariantCulture) + " ";
552  currentFigure.PointCount = 1;
553  }
554 
555  public void Rectangle(double x0, double y0, double width, double height)
556  {
557  MoveTo(x0, y0);
558  LineTo(x0 + width, y0);
559  LineTo(x0 + width, y0 + height);
560  LineTo(x0, y0 + height);
561  Close();
562  }
563 
564  public void Restore()
565  {
566  _transform = states.Pop();
567  _currClipPath = clipPaths.Pop();
568  currentPath = new SVGPathObject();
569  currentFigure = new SVGFigure();
570  }
571 
572  public void Rotate(double angle)
573  {
574  _transform = MatrixUtils.Rotate(_transform, angle);
575  currentPath = new SVGPathObject(); currentPath = new SVGPathObject();
576  currentFigure = new SVGFigure();
577  }
578 
579  public void Save()
580  {
581  states.Push((double[,])_transform.Clone());
582  clipPaths.Push(_currClipPath);
583  }
584 
585  public void Scale(double scaleX, double scaleY)
586  {
587  _transform = MatrixUtils.Scale(_transform, scaleX, scaleY);
588  currentPath = new SVGPathObject();
589  currentFigure = new SVGFigure();
590  }
591 
592  public void SetFillStyle((int r, int g, int b, double a) style)
593  {
594  FillStyle = Colour.FromRgba(style);
595  }
596 
597  public void SetFillStyle(Brush style)
598  {
599  FillStyle = style;
600  }
601 
602  public void SetLineDash(LineDash dash)
603  {
604  _lineDash = dash;
605  }
606 
607  public void SetStrokeStyle((int r, int g, int b, double a) style)
608  {
609  StrokeStyle = Colour.FromRgba(style);
610  }
611 
612  public void SetStrokeStyle(Brush style)
613  {
614  StrokeStyle = style;
615  }
616 
617  public void Stroke()
618  {
619  if (currentFigure.PointCount > 0)
620  {
621  currentPath.Figures.Add(currentFigure);
622  }
623 
624  XmlElement currElement = currentElement;
625 
626  if (!string.IsNullOrEmpty(_currClipPath))
627  {
628  currentElement = Document.CreateElement("g", SVGNamespace);
629  currentElement.SetAttribute("clip-path", _currClipPath);
630  currElement.AppendChild(currentElement);
631  }
632 
633  XmlElement path = Document.CreateElement("path", SVGNamespace);
634  path.SetAttribute("d", currentPath.Figures.Aggregate("", (a, b) => a + b.Data));
635 
636  if (StrokeStyle is SolidColourBrush solid)
637  {
638  path.SetAttribute("stroke", solid.Colour.ToCSSString(false));
639  path.SetAttribute("stroke-opacity", solid.A.ToString(System.Globalization.CultureInfo.InvariantCulture));
640  }
641  else if (StrokeStyle is LinearGradientBrush linearGradient)
642  {
643  string gradientName;
644 
645  if (!gradients.TryGetValue(linearGradient, out gradientName))
646  {
647  gradientName = "gradient" + (gradients.Count + 1).ToString(System.Globalization.CultureInfo.InvariantCulture);
648 
649  XmlElement gradientElement = linearGradient.ToLinearGradient(Document, gradientName);
650  this.definitions.AppendChild(gradientElement);
651 
652  gradients.Add(linearGradient, gradientName);
653  }
654 
655  path.SetAttribute("stroke", "url(#" + gradientName + ")");
656  }
657  else if (StrokeStyle is RadialGradientBrush radialGradient)
658  {
659  string gradientName;
660 
661  if (!gradients.TryGetValue(radialGradient, out gradientName))
662  {
663  gradientName = "gradient" + (gradients.Count + 1).ToString(System.Globalization.CultureInfo.InvariantCulture);
664 
665  XmlElement gradientElement = radialGradient.ToRadialGradient(Document, gradientName);
666  this.definitions.AppendChild(gradientElement);
667 
668  gradients.Add(radialGradient, gradientName);
669  }
670 
671  path.SetAttribute("stroke", "url(#" + gradientName + ")");
672  }
673 
674  path.SetAttribute("stroke-width", LineWidth.ToString(System.Globalization.CultureInfo.InvariantCulture));
675 
676  switch (LineCap)
677  {
678  case LineCaps.Butt:
679  path.SetAttribute("stroke-linecap", "butt");
680  break;
681  case LineCaps.Round:
682  path.SetAttribute("stroke-linecap", "round");
683  break;
684  case LineCaps.Square:
685  path.SetAttribute("stroke-linecap", "square");
686  break;
687  }
688 
689  switch (LineJoin)
690  {
691  case LineJoins.Bevel:
692  path.SetAttribute("stroke-linejoin", "bevel");
693  break;
694  case LineJoins.Round:
695  path.SetAttribute("stroke-linejoin", "round");
696  break;
697  case LineJoins.Miter:
698  path.SetAttribute("stroke-linejoin", "miter");
699  break;
700  }
701 
702  if (_lineDash.Phase != 0 || _lineDash.UnitsOn != 0 || _lineDash.UnitsOff != 0)
703  {
704  path.SetAttribute("stroke-dasharray", _lineDash.UnitsOn.ToString(System.Globalization.CultureInfo.InvariantCulture) + " " + _lineDash.UnitsOff.ToString(System.Globalization.CultureInfo.InvariantCulture));
705  path.SetAttribute("stroke-dashoffset", _lineDash.Phase.ToString(System.Globalization.CultureInfo.InvariantCulture));
706  }
707 
708  path.SetAttribute("fill", "none");
709  path.SetAttribute("transform", "matrix(" + _transform[0, 0].ToString(System.Globalization.CultureInfo.InvariantCulture) + "," + _transform[1, 0].ToString(System.Globalization.CultureInfo.InvariantCulture) +
710  "," + _transform[0, 1].ToString(System.Globalization.CultureInfo.InvariantCulture) + "," + _transform[1, 1].ToString(System.Globalization.CultureInfo.InvariantCulture) +
711  "," + _transform[0, 2].ToString(System.Globalization.CultureInfo.InvariantCulture) + "," + _transform[1, 2].ToString(System.Globalization.CultureInfo.InvariantCulture) + ")");
712 
713  if (!string.IsNullOrEmpty(Tag))
714  {
715  path.SetAttribute("id", Tag);
716  }
717 
718  if (!string.IsNullOrEmpty(this.Tag) && this.linkDestinations.TryGetValue(this.Tag, out string destination) && !string.IsNullOrEmpty(destination))
719  {
720  XmlElement aElement = Document.CreateElement("a", SVGNamespace);
721  aElement.SetAttribute("href", destination);
722  currentElement.AppendChild(aElement);
723  currentElement = aElement;
724  }
725 
726  currentElement.AppendChild(path);
727 
728  currentElement = currElement;
729 
730  currentPath = new SVGPathObject();
731  currentFigure = new SVGFigure();
732  }
733 
734  public void StrokeText(string text, double x, double y)
735  {
736  if (!TextToPaths)
737  {
738  if (!UsedFontFamilies.ContainsKey(Font.FontFamily.FileName))
739  {
740  UsedFontFamilies.Add(Font.FontFamily.FileName, Font.FontFamily);
741  UsedChars.Add(Font.FontFamily.FileName, new HashSet<char>());
742  }
743 
744  UsedChars[Font.FontFamily.FileName].UnionWith(text);
745 
747 
748  double[,] currTransform = null;
749  double[,] deltaTransform = MatrixUtils.Identity;
750 
751  switch (TextBaseline)
752  {
753  case TextBaselines.Baseline:
754  currTransform = MatrixUtils.Translate(_transform, x - metrics.LeftSideBearing, y);
755  deltaTransform = MatrixUtils.Translate(deltaTransform, x - metrics.LeftSideBearing, y);
756  break;
757  case TextBaselines.Top:
758  currTransform = MatrixUtils.Translate(_transform, x - metrics.LeftSideBearing, y + metrics.Top);
759  deltaTransform = MatrixUtils.Translate(deltaTransform, x - metrics.LeftSideBearing, y + metrics.Top);
760  break;
761  case TextBaselines.Bottom:
762  currTransform = MatrixUtils.Translate(_transform, x - metrics.LeftSideBearing, y + metrics.Bottom);
763  deltaTransform = MatrixUtils.Translate(deltaTransform, x - metrics.LeftSideBearing, y + metrics.Bottom);
764  break;
765  case TextBaselines.Middle:
766  currTransform = MatrixUtils.Translate(_transform, x - metrics.LeftSideBearing, y + (metrics.Top + metrics.Bottom) * 0.5);
767  deltaTransform = MatrixUtils.Translate(deltaTransform, x - metrics.LeftSideBearing, y + (metrics.Top + metrics.Bottom) * 0.5);
768  break;
769  default:
770  currTransform = MatrixUtils.Translate(_transform, x - metrics.LeftSideBearing, y);
771  deltaTransform = MatrixUtils.Translate(deltaTransform, x - metrics.LeftSideBearing, y);
772  break;
773  }
774 
775  XmlElement currElement = currentElement;
776 
777  if (!string.IsNullOrEmpty(_currClipPath))
778  {
779  currentElement = Document.CreateElement("g", SVGNamespace);
780  currentElement.SetAttribute("clip-path", _currClipPath);
781  currElement.AppendChild(currentElement);
782  }
783 
784  XmlElement textElement = Document.CreateElement("text", SVGNamespace);
785 
786  if (StrokeStyle is SolidColourBrush solid)
787  {
788  textElement.SetAttribute("stroke", solid.Colour.ToCSSString(false));
789  textElement.SetAttribute("stroke-opacity", solid.A.ToString(System.Globalization.CultureInfo.InvariantCulture));
790  }
791  else if (StrokeStyle is LinearGradientBrush linearGradient)
792  {
793  string gradientName = "g" + Guid.NewGuid().ToString("N");
794 
795  XmlElement gradientElement = linearGradient.ToLinearGradient(Document, gradientName);
796 
797  deltaTransform = MatrixUtils.Invert(deltaTransform);
798 
799  gradientElement.SetAttribute("gradientTransform", "matrix(" + deltaTransform[0, 0].ToString(System.Globalization.CultureInfo.InvariantCulture) + "," + deltaTransform[1, 0].ToString(System.Globalization.CultureInfo.InvariantCulture) +
800  "," + deltaTransform[0, 1].ToString(System.Globalization.CultureInfo.InvariantCulture) + "," + deltaTransform[1, 1].ToString(System.Globalization.CultureInfo.InvariantCulture) +
801  "," + deltaTransform[0, 2].ToString(System.Globalization.CultureInfo.InvariantCulture) + "," + deltaTransform[1, 2].ToString(System.Globalization.CultureInfo.InvariantCulture) + ")");
802 
803  this.definitions.AppendChild(gradientElement);
804 
805  textElement.SetAttribute("stroke", "url(#" + gradientName + ")");
806  }
807  else if (StrokeStyle is RadialGradientBrush radialGradient)
808  {
809  string gradientName = "g" + Guid.NewGuid().ToString("N");
810 
811  XmlElement gradientElement = radialGradient.ToRadialGradient(Document, gradientName);
812 
813  deltaTransform = MatrixUtils.Invert(deltaTransform);
814 
815  gradientElement.SetAttribute("gradientTransform", "matrix(" + deltaTransform[0, 0].ToString(System.Globalization.CultureInfo.InvariantCulture) + "," + deltaTransform[1, 0].ToString(System.Globalization.CultureInfo.InvariantCulture) +
816  "," + deltaTransform[0, 1].ToString(System.Globalization.CultureInfo.InvariantCulture) + "," + deltaTransform[1, 1].ToString(System.Globalization.CultureInfo.InvariantCulture) +
817  "," + deltaTransform[0, 2].ToString(System.Globalization.CultureInfo.InvariantCulture) + "," + deltaTransform[1, 2].ToString(System.Globalization.CultureInfo.InvariantCulture) + ")");
818 
819  this.definitions.AppendChild(gradientElement);
820 
821  textElement.SetAttribute("stroke", "url(#" + gradientName + ")");
822  }
823 
824  textElement.SetAttribute("stroke-width", LineWidth.ToString(System.Globalization.CultureInfo.InvariantCulture));
825 
826  switch (LineCap)
827  {
828  case LineCaps.Butt:
829  textElement.SetAttribute("stroke-linecap", "butt");
830  break;
831  case LineCaps.Round:
832  textElement.SetAttribute("stroke-linecap", "round");
833  break;
834  case LineCaps.Square:
835  textElement.SetAttribute("stroke-linecap", "square");
836  break;
837  }
838 
839  switch (LineJoin)
840  {
841  case LineJoins.Bevel:
842  textElement.SetAttribute("stroke-linejoin", "bevel");
843  break;
844  case LineJoins.Round:
845  textElement.SetAttribute("stroke-linejoin", "round");
846  break;
847  case LineJoins.Miter:
848  textElement.SetAttribute("stroke-linejoin", "miter");
849  break;
850  }
851 
852  if (_lineDash.Phase != 0 || _lineDash.UnitsOn != 0 || _lineDash.UnitsOff != 0)
853  {
854  textElement.SetAttribute("stroke-dasharray", _lineDash.UnitsOn.ToString(System.Globalization.CultureInfo.InvariantCulture) + " " + _lineDash.UnitsOff.ToString(System.Globalization.CultureInfo.InvariantCulture));
855  textElement.SetAttribute("stroke-dashoffset", _lineDash.Phase.ToString(System.Globalization.CultureInfo.InvariantCulture));
856  }
857  textElement.SetAttribute("fill", "none");
858  textElement.SetAttribute("transform", "matrix(" + currTransform[0, 0].ToString(System.Globalization.CultureInfo.InvariantCulture) + "," + currTransform[1, 0].ToString(System.Globalization.CultureInfo.InvariantCulture) +
859  "," + currTransform[0, 1].ToString(System.Globalization.CultureInfo.InvariantCulture) + "," + currTransform[1, 1].ToString(System.Globalization.CultureInfo.InvariantCulture) +
860  "," + currTransform[0, 2].ToString(System.Globalization.CultureInfo.InvariantCulture) + "," + currTransform[1, 2].ToString(System.Globalization.CultureInfo.InvariantCulture) + ")");
861 
862  textElement.SetAttribute("x", "0");
863  textElement.SetAttribute("y", "0");
864  textElement.SetAttribute("font-size", Font.FontSize.ToString(System.Globalization.CultureInfo.InvariantCulture));
865  textElement.SetAttribute("font-family", Font.FontFamily.FileName);
866 
867  if (Font.FontFamily.IsBold)
868  {
869  textElement.SetAttribute("font-weight", "bold");
870  }
871  else
872  {
873  textElement.SetAttribute("font-weight", "regular");
874  }
875 
877  {
878  textElement.SetAttribute("font-style", "italic");
879  }
880  else
881  {
882  textElement.SetAttribute("font-style", "normal");
883  }
884 
886  {
887  textElement.SetAttribute("font-style", "oblique");
888  }
889 
890  ProcessText(text, textElement);
891 
892  if (!string.IsNullOrEmpty(Tag))
893  {
894  textElement.SetAttribute("id", Tag);
895  }
896 
897  if (!string.IsNullOrEmpty(this.Tag) && this.linkDestinations.TryGetValue(this.Tag, out string destination) && !string.IsNullOrEmpty(destination))
898  {
899  XmlElement aElement = Document.CreateElement("a", SVGNamespace);
900  aElement.SetAttribute("href", destination);
901  currentElement.AppendChild(aElement);
902  currentElement = aElement;
903  }
904 
905  currentElement.AppendChild(textElement);
906 
907  currentElement = currElement;
908  }
909  else
910  {
911  PathText(text, x, y);
912  Stroke();
913  }
914  }
915 
916  public void Transform(double a, double b, double c, double d, double e, double f)
917  {
918  double[,] transfMatrix = new double[3, 3] { { a, c, e }, { b, d, f }, { 0, 0, 1 } };
919  _transform = MatrixUtils.Multiply(_transform, transfMatrix);
920 
921  currentPath = new SVGPathObject();
922  currentFigure = new SVGFigure();
923  }
924 
925  public void Translate(double x, double y)
926  {
927  _transform = MatrixUtils.Translate(_transform, x, y);
928 
929  currentPath = new SVGPathObject();
930  currentFigure = new SVGFigure();
931  }
932 
933  public void SetClippingPath()
934  {
935  if (currentFigure.PointCount > 0)
936  {
937  currentPath.Figures.Add(currentFigure);
938  }
939 
940  XmlElement clipPath = Document.CreateElement("clipPath", SVGNamespace);
941  string id = Guid.NewGuid().ToString("N");
942  clipPath.SetAttribute("id", id);
943 
944  if (!string.IsNullOrEmpty(_currClipPath))
945  {
946  clipPath.SetAttribute("clip-path", _currClipPath);
947  }
948 
949  XmlElement path = Document.CreateElement("path", SVGNamespace);
950  path.SetAttribute("d", currentPath.Figures.Aggregate("", (a, b) => a + b.Data));
951 
952  if (StrokeStyle is SolidColourBrush solid)
953  {
954  path.SetAttribute("stroke", solid.Colour.ToCSSString(false));
955  path.SetAttribute("stroke-opacity", solid.A.ToString(System.Globalization.CultureInfo.InvariantCulture));
956  }
957  else if (StrokeStyle is LinearGradientBrush linearGradient)
958  {
959  string gradientName;
960 
961  if (!gradients.TryGetValue(linearGradient, out gradientName))
962  {
963  gradientName = "gradient" + (gradients.Count + 1).ToString(System.Globalization.CultureInfo.InvariantCulture);
964 
965  XmlElement gradientElement = linearGradient.ToLinearGradient(Document, gradientName);
966  this.definitions.AppendChild(gradientElement);
967 
968  gradients.Add(linearGradient, gradientName);
969  }
970 
971  path.SetAttribute("stroke", "url(#" + gradientName + ")");
972  }
973  else if (StrokeStyle is RadialGradientBrush radialGradient)
974  {
975  string gradientName;
976 
977  if (!gradients.TryGetValue(radialGradient, out gradientName))
978  {
979  gradientName = "gradient" + (gradients.Count + 1).ToString(System.Globalization.CultureInfo.InvariantCulture);
980 
981  XmlElement gradientElement = radialGradient.ToRadialGradient(Document, gradientName);
982  this.definitions.AppendChild(gradientElement);
983 
984  gradients.Add(radialGradient, gradientName);
985  }
986 
987  path.SetAttribute("stroke", "url(#" + gradientName + ")");
988  }
989 
990  path.SetAttribute("stroke-width", LineWidth.ToString(System.Globalization.CultureInfo.InvariantCulture));
991 
992  switch (LineCap)
993  {
994  case LineCaps.Butt:
995  path.SetAttribute("stroke-linecap", "butt");
996  break;
997  case LineCaps.Round:
998  path.SetAttribute("stroke-linecap", "round");
999  break;
1000  case LineCaps.Square:
1001  path.SetAttribute("stroke-linecap", "square");
1002  break;
1003  }
1004 
1005  switch (LineJoin)
1006  {
1007  case LineJoins.Bevel:
1008  path.SetAttribute("stroke-linejoin", "bevel");
1009  break;
1010  case LineJoins.Round:
1011  path.SetAttribute("stroke-linejoin", "round");
1012  break;
1013  case LineJoins.Miter:
1014  path.SetAttribute("stroke-linejoin", "miter");
1015  break;
1016  }
1017 
1018  if (_lineDash.Phase != 0 || _lineDash.UnitsOn != 0 || _lineDash.UnitsOff != 0)
1019  {
1020  path.SetAttribute("stroke-dasharray", _lineDash.UnitsOn.ToString(System.Globalization.CultureInfo.InvariantCulture) + " " + _lineDash.UnitsOff.ToString(System.Globalization.CultureInfo.InvariantCulture));
1021  path.SetAttribute("stroke-dashoffset", _lineDash.Phase.ToString(System.Globalization.CultureInfo.InvariantCulture));
1022  }
1023 
1024  path.SetAttribute("fill", "none");
1025  path.SetAttribute("transform", "matrix(" + _transform[0, 0].ToString(System.Globalization.CultureInfo.InvariantCulture) + "," + _transform[1, 0].ToString(System.Globalization.CultureInfo.InvariantCulture) +
1026  "," + _transform[0, 1].ToString(System.Globalization.CultureInfo.InvariantCulture) + "," + _transform[1, 1].ToString(System.Globalization.CultureInfo.InvariantCulture) +
1027  "," + _transform[0, 2].ToString(System.Globalization.CultureInfo.InvariantCulture) + "," + _transform[1, 2].ToString(System.Globalization.CultureInfo.InvariantCulture) + ")");
1028 
1029  clipPath.AppendChild(path);
1030 
1031  currentElement.AppendChild(clipPath);
1032 
1033  _currClipPath = "url(#" + id + ")";
1034 
1035  currentPath = new SVGPathObject();
1036  currentFigure = new SVGFigure();
1037  }
1038 
1039  public void DrawRasterImage(int sourceX, int sourceY, int sourceWidth, int sourceHeight, double destinationX, double destinationY, double destinationWidth, double destinationHeight, RasterImage image)
1040  {
1041  Save();
1042 
1043  MoveTo(destinationX, destinationY);
1044  LineTo(destinationX + destinationWidth, destinationY);
1045  LineTo(destinationX + destinationWidth, destinationY + destinationHeight);
1046  LineTo(destinationX, destinationY + destinationHeight);
1047  Close();
1048  SetClippingPath();
1049 
1050  double sourceRectX = (double)sourceX / image.Width;
1051  double sourceRectY = (double)sourceY / image.Height;
1052  double sourceRectWidth = (double)sourceWidth / image.Width;
1053  double sourceRectHeight = (double)sourceHeight / image.Height;
1054 
1055  double scaleX = destinationWidth / sourceRectWidth;
1056  double scaleY = destinationHeight / sourceRectHeight;
1057 
1058  double translationX = destinationX / scaleX - sourceRectX;
1059  double translationY = destinationY / scaleY - sourceRectY;
1060 
1061  Scale(scaleX, scaleY);
1062  Translate(translationX, translationY);
1063 
1064  XmlElement currElement = currentElement;
1065 
1066  if (!string.IsNullOrEmpty(_currClipPath))
1067  {
1068  currentElement = Document.CreateElement("g", SVGNamespace);
1069  currentElement.SetAttribute("clip-path", _currClipPath);
1070  currElement.AppendChild(currentElement);
1071  }
1072 
1073  XmlElement img = Document.CreateElement("image", SVGNamespace);
1074  img.SetAttribute("x", "0");
1075  img.SetAttribute("y", "0");
1076 
1077  img.SetAttribute("width", "1");
1078  img.SetAttribute("height", "1");
1079 
1080  img.SetAttribute("preserveAspectRatio", "none");
1081 
1082  if (image.Interpolate)
1083  {
1084  img.SetAttribute("image-rendering", "optimizeQuality");
1085  }
1086  else
1087  {
1088  img.SetAttribute("image-rendering", "pixelated");
1089  }
1090 
1091  img.SetAttribute("transform", "matrix(" + _transform[0, 0].ToString(System.Globalization.CultureInfo.InvariantCulture) + "," + _transform[1, 0].ToString(System.Globalization.CultureInfo.InvariantCulture) +
1092  "," + _transform[0, 1].ToString(System.Globalization.CultureInfo.InvariantCulture) + "," + _transform[1, 1].ToString(System.Globalization.CultureInfo.InvariantCulture) +
1093  "," + _transform[0, 2].ToString(System.Globalization.CultureInfo.InvariantCulture) + "," + _transform[1, 2].ToString(System.Globalization.CultureInfo.InvariantCulture) + ")");
1094 
1095  if (!string.IsNullOrEmpty(Tag))
1096  {
1097  img.SetAttribute("id", Tag);
1098  }
1099 
1100  img.SetAttribute("href", "http://www.w3.org/1999/xlink", "data:image/png;base64," + Convert.ToBase64String(image.PNGStream.ToArray()));
1101 
1102  if (!string.IsNullOrEmpty(this.Tag) && this.linkDestinations.TryGetValue(this.Tag, out string destination) && !string.IsNullOrEmpty(destination))
1103  {
1104  XmlElement aElement = Document.CreateElement("a", SVGNamespace);
1105  aElement.SetAttribute("href", destination);
1106  currentElement.AppendChild(aElement);
1107  currentElement = aElement;
1108  }
1109 
1110  currentElement.AppendChild(img);
1111 
1112  currentElement = currElement;
1113 
1114  Restore();
1115  }
1116 
1117  private void ProcessText(string text, XmlNode parent)
1118  {
1119  if (Font.EnableKerning && this.TextOption == SVGContextInterpreter.TextOptions.SubsetFonts)
1120  {
1121  List<(string, Point)> tSpans = new List<(string, Point)>();
1122 
1123  StringBuilder currentRun = new StringBuilder();
1124  Point currentKerning = new Point();
1125 
1126  Point currentGlyphPlacementDelta = new Point();
1127  Point currentGlyphAdvanceDelta = new Point();
1128  Point nextGlyphPlacementDelta = new Point();
1129  Point nextGlyphAdvanceDelta = new Point();
1130 
1131  for (int i = 0; i < text.Length; i++)
1132  {
1133  if (i < text.Length - 1)
1134  {
1135  currentGlyphPlacementDelta = nextGlyphPlacementDelta;
1136  currentGlyphAdvanceDelta = nextGlyphAdvanceDelta;
1137  nextGlyphAdvanceDelta = new Point();
1138  nextGlyphPlacementDelta = new Point();
1139 
1140  TrueTypeFile.PairKerning kerning = Font.FontFamily.TrueTypeFile.Get1000EmKerning(text[i], text[i + 1]);
1141 
1142  if (kerning != null)
1143  {
1144  currentGlyphPlacementDelta = new Point(currentGlyphPlacementDelta.X + kerning.Glyph1Placement.X, currentGlyphPlacementDelta.Y + kerning.Glyph1Placement.Y);
1145  currentGlyphAdvanceDelta = new Point(currentGlyphAdvanceDelta.X + kerning.Glyph1Advance.X, currentGlyphAdvanceDelta.Y + kerning.Glyph1Advance.Y);
1146 
1147  nextGlyphPlacementDelta = new Point(nextGlyphPlacementDelta.X + kerning.Glyph2Placement.X, nextGlyphPlacementDelta.Y + kerning.Glyph2Placement.Y);
1148  nextGlyphAdvanceDelta = new Point(nextGlyphAdvanceDelta.X + kerning.Glyph2Advance.X, nextGlyphAdvanceDelta.Y + kerning.Glyph2Advance.Y);
1149  }
1150  }
1151 
1152  if (currentGlyphPlacementDelta.X != 0 || currentGlyphPlacementDelta.Y != 0 || currentGlyphAdvanceDelta.X != 0 || currentGlyphAdvanceDelta.Y != 0)
1153  {
1154  if (currentRun.Length > 0)
1155  {
1156  tSpans.Add((currentRun.ToString(), currentKerning));
1157 
1158  tSpans.Add((text[i].ToString(), new Point(currentGlyphPlacementDelta.X * Font.FontSize / 1000, currentGlyphPlacementDelta.Y * Font.FontSize / 1000)));
1159 
1160  currentRun.Clear();
1161  currentKerning = new Point((currentGlyphAdvanceDelta.X - currentGlyphPlacementDelta.X) * Font.FontSize / 1000, (currentGlyphAdvanceDelta.Y - currentGlyphPlacementDelta.Y) * Font.FontSize / 1000);
1162  }
1163  else
1164  {
1165  tSpans.Add((text[i].ToString(), new Point(currentGlyphPlacementDelta.X * Font.FontSize / 1000 + currentKerning.X, currentGlyphPlacementDelta.Y * Font.FontSize / 1000 + currentKerning.Y)));
1166 
1167  currentRun.Clear();
1168  currentKerning = new Point((currentGlyphAdvanceDelta.X - currentGlyphPlacementDelta.X) * Font.FontSize / 1000, (currentGlyphAdvanceDelta.Y - currentGlyphPlacementDelta.Y) * Font.FontSize / 1000);
1169  }
1170  }
1171  else
1172  {
1173  currentRun.Append(text[i]);
1174  }
1175  }
1176 
1177  if (currentRun.Length > 0)
1178  {
1179  tSpans.Add((currentRun.ToString(), currentKerning));
1180  }
1181 
1182  for (int i = 0; i < tSpans.Count; i++)
1183  {
1184  XmlElement tspanElement = Document.CreateElement("tspan", SVGNamespace);
1185  tspanElement.InnerText = tSpans[i].Item1.Replace(" ", "\u00A0");
1186 
1187  if (tSpans[i].Item2.X != 0)
1188  {
1189  tspanElement.SetAttribute("dx", tSpans[i].Item2.X.ToString(System.Globalization.CultureInfo.InvariantCulture));
1190  }
1191 
1192  if (tSpans[i].Item2.Y != 0)
1193  {
1194  tspanElement.SetAttribute("dy", tSpans[i].Item2.Y.ToString(System.Globalization.CultureInfo.InvariantCulture));
1195  }
1196 
1197  parent.AppendChild(tspanElement);
1198  }
1199 
1200  }
1201  else
1202  {
1203  parent.InnerText = text.Replace(" ", "\u00A0");
1204  }
1205  }
1206 
1207  public void DrawFilteredGraphics(Graphics graphics, IFilter filter)
1208  {
1209  if (FilterOption.Operation == SVGContextInterpreter.FilterOption.FilterOperations.IgnoreAll)
1210  {
1211  graphics.CopyToIGraphicsContext(this);
1212  }
1213  else if (FilterOption.Operation == SVGContextInterpreter.FilterOption.FilterOperations.SkipAll)
1214  {
1215 
1216  }
1217  else
1218  {
1219  bool rasterisationNeeded = false;
1220  bool justDraw = false;
1221 
1222  if (FilterOption.Operation == SVGContextInterpreter.FilterOption.FilterOperations.RasteriseAll)
1223  {
1224  rasterisationNeeded = true;
1225  justDraw = false;
1226  }
1227  else
1228  {
1229  if (FilterOption.Operation == SVGContextInterpreter.FilterOption.FilterOperations.RasteriseIfNecessary)
1230  {
1231  justDraw = false;
1232  rasterisationNeeded = true;
1233  }
1234  else if (FilterOption.Operation == SVGContextInterpreter.FilterOption.FilterOperations.NeverRasteriseAndIgnore)
1235  {
1236  rasterisationNeeded = false;
1237  justDraw = true;
1238  }
1239  else if (FilterOption.Operation == SVGContextInterpreter.FilterOption.FilterOperations.NeverRasteriseAndSkip)
1240  {
1241  rasterisationNeeded = false;
1242  justDraw = false;
1243  }
1244 
1245  if (filter is MaskFilter mask)
1246  {
1247  rasterisationNeeded = false;
1248  justDraw = false;
1249 
1250  XmlElement currElement = currentElement;
1251 
1252  if (!string.IsNullOrEmpty(_currClipPath))
1253  {
1254  currentElement = Document.CreateElement("g", SVGNamespace);
1255  currentElement.SetAttribute("clip-path", _currClipPath);
1256  currElement.AppendChild(currentElement);
1257  }
1258 
1259  XmlElement currentElement2 = currentElement;
1260 
1261  string filterGuid = Guid.NewGuid().ToString("N");
1262 
1263  Rectangle bounds = graphics.GetBounds();
1264 
1265  Point p1 = new Point(bounds.Location.X, bounds.Location.Y);
1266  Point p2 = new Point(bounds.Location.X + bounds.Size.Width, bounds.Location.Y);
1267  Point p3 = new Point(bounds.Location.X + bounds.Size.Width, bounds.Location.Y + bounds.Size.Height);
1268  Point p4 = new Point(bounds.Location.X, bounds.Location.Y + bounds.Size.Height);
1269 
1270  /*p1 = MatrixUtils.Multiply(_transform, p1);
1271  p2 = MatrixUtils.Multiply(_transform, p2);
1272  p3 = MatrixUtils.Multiply(_transform, p3);
1273  p4 = MatrixUtils.Multiply(_transform, p4);*/
1274 
1275  bounds = Point.Bounds(p1, p2, p3, p4);
1276 
1277  XmlElement maskElement = Document.CreateElement("mask", SVGNamespace);
1278  maskElement.SetAttribute("id", filterGuid);
1279  maskElement.SetAttribute("maskUnits", "userSpaceOnUse");
1280  maskElement.SetAttribute("x", bounds.Location.X.ToString(System.Globalization.CultureInfo.InvariantCulture));
1281  maskElement.SetAttribute("y", bounds.Location.Y.ToString(System.Globalization.CultureInfo.InvariantCulture));
1282  maskElement.SetAttribute("width", bounds.Size.Width.ToString(System.Globalization.CultureInfo.InvariantCulture));
1283  maskElement.SetAttribute("height", bounds.Size.Height.ToString(System.Globalization.CultureInfo.InvariantCulture));
1284  this.definitions.AppendChild(maskElement);
1285 
1286  currentElement = maskElement;
1287 
1288  double[,] currTransform = _transform;
1289  _transform = MatrixUtils.Identity;
1290 
1291  mask.Mask.CopyToIGraphicsContext(this);
1292 
1293  _transform = currTransform;
1294 
1295  currentElement = Document.CreateElement("g", SVGNamespace);
1296  currentElement.SetAttribute("mask", "url(#" + filterGuid + ")");
1297  currentElement2.AppendChild(currentElement);
1298 
1299  currentElement.SetAttribute("transform", "matrix(" + _transform[0, 0].ToString(System.Globalization.CultureInfo.InvariantCulture) + "," + _transform[1, 0].ToString(System.Globalization.CultureInfo.InvariantCulture) +
1300  "," + _transform[0, 1].ToString(System.Globalization.CultureInfo.InvariantCulture) + "," + _transform[1, 1].ToString(System.Globalization.CultureInfo.InvariantCulture) +
1301  "," + _transform[0, 2].ToString(System.Globalization.CultureInfo.InvariantCulture) + "," + _transform[1, 2].ToString(System.Globalization.CultureInfo.InvariantCulture) + ")");
1302  currTransform = _transform;
1303  _transform = MatrixUtils.Identity;
1304 
1305  graphics.CopyToIGraphicsContext(this);
1306 
1307  _transform = currTransform;
1308 
1309  currentElement = currElement;
1310  }
1311  if (filter is GaussianBlurFilter gauss)
1312  {
1313  rasterisationNeeded = false;
1314  justDraw = false;
1315  XmlElement currElement = currentElement;
1316 
1317  if (!string.IsNullOrEmpty(_currClipPath))
1318  {
1319  currentElement = Document.CreateElement("g", SVGNamespace);
1320  currentElement.SetAttribute("clip-path", _currClipPath);
1321  currElement.AppendChild(currentElement);
1322  }
1323 
1324  Rectangle bounds = graphics.GetBounds();
1325 
1326  Point p1 = new Point(bounds.Location.X - gauss.StandardDeviation * 3, bounds.Location.Y - gauss.StandardDeviation * 3);
1327  Point p2 = new Point(bounds.Location.X + bounds.Size.Width + gauss.StandardDeviation * 3, bounds.Location.Y - gauss.StandardDeviation * 3);
1328  Point p3 = new Point(bounds.Location.X + bounds.Size.Width + gauss.StandardDeviation * 3, bounds.Location.Y + bounds.Size.Height + gauss.StandardDeviation * 3);
1329  Point p4 = new Point(bounds.Location.X - gauss.StandardDeviation * 3, bounds.Location.Y + bounds.Size.Height + gauss.StandardDeviation * 3);
1330 
1331  /*p1 = MatrixUtils.Multiply(_transform, p1);
1332  p2 = MatrixUtils.Multiply(_transform, p2);
1333  p3 = MatrixUtils.Multiply(_transform, p3);
1334  p4 = MatrixUtils.Multiply(_transform, p4);*/
1335 
1336  bounds = Point.Bounds(p1, p2, p3, p4);
1337 
1338  string filterGuid = Guid.NewGuid().ToString("N");
1339 
1340  XmlElement filterElement = Document.CreateElement("filter", SVGNamespace);
1341  filterElement.SetAttribute("id", filterGuid);
1342  filterElement.SetAttribute("color-interpolation-filters", "sRGB");
1343  filterElement.SetAttribute("filterUnits", "userSpaceOnUse");
1344  filterElement.SetAttribute("x", bounds.Location.X.ToString(System.Globalization.CultureInfo.InvariantCulture));
1345  filterElement.SetAttribute("y", bounds.Location.Y.ToString(System.Globalization.CultureInfo.InvariantCulture));
1346  filterElement.SetAttribute("width", bounds.Size.Width.ToString(System.Globalization.CultureInfo.InvariantCulture));
1347  filterElement.SetAttribute("height", bounds.Size.Height.ToString(System.Globalization.CultureInfo.InvariantCulture));
1348  this.definitions.AppendChild(filterElement);
1349 
1350  XmlElement feElement = Document.CreateElement("feGaussianBlur", SVGNamespace);
1351  feElement.SetAttribute("stdDeviation", gauss.StandardDeviation.ToString(System.Globalization.CultureInfo.InvariantCulture));
1352  filterElement.AppendChild(feElement);
1353 
1354  XmlElement currentElement2 = currentElement;
1355 
1356  currentElement = Document.CreateElement("g", SVGNamespace);
1357  currentElement.SetAttribute("filter", "url(#" + filterGuid + ")");
1358  currentElement2.AppendChild(currentElement);
1359 
1360  currentElement.SetAttribute("transform", "matrix(" + _transform[0, 0].ToString(System.Globalization.CultureInfo.InvariantCulture) + "," + _transform[1, 0].ToString(System.Globalization.CultureInfo.InvariantCulture) +
1361  "," + _transform[0, 1].ToString(System.Globalization.CultureInfo.InvariantCulture) + "," + _transform[1, 1].ToString(System.Globalization.CultureInfo.InvariantCulture) +
1362  "," + _transform[0, 2].ToString(System.Globalization.CultureInfo.InvariantCulture) + "," + _transform[1, 2].ToString(System.Globalization.CultureInfo.InvariantCulture) + ")");
1363  double[,] currTransform = _transform;
1364  _transform = MatrixUtils.Identity;
1365 
1366  graphics.CopyToIGraphicsContext(this);
1367 
1368  currentElement = currElement;
1369  }
1370  else if (filter is ColourMatrixFilter cmf)
1371  {
1372  rasterisationNeeded = false;
1373  justDraw = false;
1374  XmlElement currElement = currentElement;
1375 
1376  if (!string.IsNullOrEmpty(_currClipPath))
1377  {
1378  currentElement = Document.CreateElement("g", SVGNamespace);
1379  currentElement.SetAttribute("clip-path", _currClipPath);
1380  currElement.AppendChild(currentElement);
1381  }
1382 
1383  Rectangle bounds = graphics.GetBounds();
1384 
1385  Point p1 = new Point(bounds.Location.X, bounds.Location.Y);
1386  Point p2 = new Point(bounds.Location.X + bounds.Size.Width, bounds.Location.Y);
1387  Point p3 = new Point(bounds.Location.X + bounds.Size.Width, bounds.Location.Y + bounds.Size.Height);
1388  Point p4 = new Point(bounds.Location.X, bounds.Location.Y + bounds.Size.Height);
1389 
1390  /* p1 = MatrixUtils.Multiply(_transform, p1);
1391  p2 = MatrixUtils.Multiply(_transform, p2);
1392  p3 = MatrixUtils.Multiply(_transform, p3);
1393  p4 = MatrixUtils.Multiply(_transform, p4);*/
1394 
1395  bounds = Point.Bounds(p1, p2, p3, p4);
1396 
1397  string filterGuid = Guid.NewGuid().ToString("N");
1398 
1399  XmlElement filterElement = Document.CreateElement("filter", SVGNamespace);
1400  filterElement.SetAttribute("id", filterGuid);
1401  filterElement.SetAttribute("color-interpolation-filters", "sRGB");
1402  filterElement.SetAttribute("filterUnits", "userSpaceOnUse");
1403  filterElement.SetAttribute("x", bounds.Location.X.ToString(System.Globalization.CultureInfo.InvariantCulture));
1404  filterElement.SetAttribute("y", bounds.Location.Y.ToString(System.Globalization.CultureInfo.InvariantCulture));
1405  filterElement.SetAttribute("width", bounds.Size.Width.ToString(System.Globalization.CultureInfo.InvariantCulture));
1406  filterElement.SetAttribute("height", bounds.Size.Height.ToString(System.Globalization.CultureInfo.InvariantCulture));
1407  this.definitions.AppendChild(filterElement);
1408 
1409  XmlElement feElement = Document.CreateElement("feColorMatrix", SVGNamespace);
1410  feElement.SetAttribute("type", "matrix");
1411 
1412  StringBuilder matrix = new StringBuilder();
1413 
1414  for (int i = 0; i < 4; i++)
1415  {
1416  for (int j = 0; j < 5; j++)
1417  {
1418  matrix.Append(cmf.ColourMatrix[i, j].ToString(System.Globalization.CultureInfo.InvariantCulture));
1419  if (i != 3 || j != 4)
1420  {
1421  matrix.Append(" ");
1422  }
1423  }
1424  }
1425 
1426  feElement.SetAttribute("values", matrix.ToString());
1427  filterElement.AppendChild(feElement);
1428 
1429  XmlElement currentElement2 = currentElement;
1430 
1431  currentElement = Document.CreateElement("g", SVGNamespace);
1432  currentElement.SetAttribute("filter", "url(#" + filterGuid + ")");
1433  currentElement2.AppendChild(currentElement);
1434 
1435  currentElement.SetAttribute("transform", "matrix(" + _transform[0, 0].ToString(System.Globalization.CultureInfo.InvariantCulture) + "," + _transform[1, 0].ToString(System.Globalization.CultureInfo.InvariantCulture) +
1436  "," + _transform[0, 1].ToString(System.Globalization.CultureInfo.InvariantCulture) + "," + _transform[1, 1].ToString(System.Globalization.CultureInfo.InvariantCulture) +
1437  "," + _transform[0, 2].ToString(System.Globalization.CultureInfo.InvariantCulture) + "," + _transform[1, 2].ToString(System.Globalization.CultureInfo.InvariantCulture) + ")");
1438  double[,] currTransform = _transform;
1439  _transform = MatrixUtils.Identity;
1440 
1441  graphics.CopyToIGraphicsContext(this);
1442 
1443  currentElement = currElement;
1444  }
1445  else if (filter is CompositeLocationInvariantFilter comp)
1446  {
1447  bool allSupported = true;
1448 
1449  foreach (IFilter filter2 in comp.Filters)
1450  {
1451  if (!(filter2 is GaussianBlurFilter) && !(filter2 is ColourMatrixFilter))
1452  {
1453  allSupported = false;
1454  break;
1455  }
1456  }
1457 
1458  if (allSupported)
1459  {
1460  rasterisationNeeded = false;
1461  justDraw = false;
1462 
1463  XmlElement currElement = currentElement;
1464 
1465  if (!string.IsNullOrEmpty(_currClipPath))
1466  {
1467  currentElement = Document.CreateElement("g", SVGNamespace);
1468  currentElement.SetAttribute("clip-path", _currClipPath);
1469  currElement.AppendChild(currentElement);
1470  }
1471 
1472  Rectangle bounds = graphics.GetBounds();
1473 
1474  Point p1 = new Point(bounds.Location.X - comp.TopLeftMargin.X, bounds.Location.Y - comp.TopLeftMargin.Y);
1475  Point p2 = new Point(bounds.Location.X + bounds.Size.Width + comp.BottomRightMargin.X, bounds.Location.Y - comp.TopLeftMargin.Y);
1476  Point p3 = new Point(bounds.Location.X + bounds.Size.Width + comp.BottomRightMargin.X, bounds.Location.Y + bounds.Size.Height + comp.BottomRightMargin.Y);
1477  Point p4 = new Point(bounds.Location.X - comp.TopLeftMargin.X, bounds.Location.Y + bounds.Size.Height + comp.BottomRightMargin.Y);
1478 
1479  /* p1 = MatrixUtils.Multiply(_transform, p1);
1480  p2 = MatrixUtils.Multiply(_transform, p2);
1481  p3 = MatrixUtils.Multiply(_transform, p3);
1482  p4 = MatrixUtils.Multiply(_transform, p4);*/
1483 
1484  bounds = Point.Bounds(p1, p2, p3, p4);
1485 
1486  string filterGuid = Guid.NewGuid().ToString("N");
1487 
1488  XmlElement filterElement = Document.CreateElement("filter", SVGNamespace);
1489  filterElement.SetAttribute("id", filterGuid);
1490  filterElement.SetAttribute("color-interpolation-filters", "sRGB");
1491  filterElement.SetAttribute("filterUnits", "userSpaceOnUse");
1492  filterElement.SetAttribute("x", bounds.Location.X.ToString(System.Globalization.CultureInfo.InvariantCulture));
1493  filterElement.SetAttribute("y", bounds.Location.Y.ToString(System.Globalization.CultureInfo.InvariantCulture));
1494  filterElement.SetAttribute("width", bounds.Size.Width.ToString(System.Globalization.CultureInfo.InvariantCulture));
1495  filterElement.SetAttribute("height", bounds.Size.Height.ToString(System.Globalization.CultureInfo.InvariantCulture));
1496  this.definitions.AppendChild(filterElement);
1497 
1498  foreach (IFilter filter2 in comp.Filters)
1499  {
1500  if (filter2 is GaussianBlurFilter gauss2)
1501  {
1502  XmlElement feElement = Document.CreateElement("feGaussianBlur", SVGNamespace);
1503  feElement.SetAttribute("stdDeviation", gauss2.StandardDeviation.ToString(System.Globalization.CultureInfo.InvariantCulture));
1504  filterElement.AppendChild(feElement);
1505  }
1506  else if (filter2 is ColourMatrixFilter cmf2)
1507  {
1508  XmlElement feElement = Document.CreateElement("feColorMatrix", SVGNamespace);
1509  feElement.SetAttribute("type", "matrix");
1510 
1511  StringBuilder matrix = new StringBuilder();
1512 
1513  for (int i = 0; i < 4; i++)
1514  {
1515  for (int j = 0; j < 5; j++)
1516  {
1517  matrix.Append(cmf2.ColourMatrix[i, j].ToString(System.Globalization.CultureInfo.InvariantCulture));
1518  if (i != 3 || j != 4)
1519  {
1520  matrix.Append(" ");
1521  }
1522  }
1523  }
1524 
1525  feElement.SetAttribute("values", matrix.ToString());
1526  filterElement.AppendChild(feElement);
1527  }
1528  }
1529 
1530  XmlElement currentElement2 = currentElement;
1531 
1532  currentElement = Document.CreateElement("g", SVGNamespace);
1533  currentElement.SetAttribute("filter", "url(#" + filterGuid + ")");
1534  currentElement2.AppendChild(currentElement);
1535 
1536  currentElement.SetAttribute("transform", "matrix(" + _transform[0, 0].ToString(System.Globalization.CultureInfo.InvariantCulture) + "," + _transform[1, 0].ToString(System.Globalization.CultureInfo.InvariantCulture) +
1537  "," + _transform[0, 1].ToString(System.Globalization.CultureInfo.InvariantCulture) + "," + _transform[1, 1].ToString(System.Globalization.CultureInfo.InvariantCulture) +
1538  "," + _transform[0, 2].ToString(System.Globalization.CultureInfo.InvariantCulture) + "," + _transform[1, 2].ToString(System.Globalization.CultureInfo.InvariantCulture) + ")");
1539  double[,] currTransform = _transform;
1540  _transform = MatrixUtils.Identity;
1541 
1542  graphics.CopyToIGraphicsContext(this);
1543 
1544  currentElement = currElement;
1545 
1546 
1547  }
1548  }
1549  }
1550 
1551  if (rasterisationNeeded)
1552  {
1553  double scale = FilterOption.RasterisationResolution;
1554 
1555  Rectangle bounds = graphics.GetBounds();
1556 
1557  bounds = new Rectangle(bounds.Location.X - filter.TopLeftMargin.X, bounds.Location.Y - filter.TopLeftMargin.Y, bounds.Size.Width + filter.TopLeftMargin.X + filter.BottomRightMargin.X, bounds.Size.Height + filter.TopLeftMargin.Y + filter.BottomRightMargin.Y);
1558 
1559  if (bounds.Size.Width > 0 && bounds.Size.Height > 0)
1560  {
1561  if (!FilterOption.RasterisationResolutionRelative)
1562  {
1563  scale = scale / Math.Min(bounds.Size.Width, bounds.Size.Height);
1564  }
1565 
1566  if (graphics.TryRasterise(bounds, scale, true, out RasterImage rasterised))
1567  {
1568  RasterImage filtered = null;
1569 
1570  if (filter is ILocationInvariantFilter locInvFilter)
1571  {
1572  filtered = locInvFilter.Filter(rasterised, scale);
1573  }
1574  else if (filter is IFilterWithLocation filterWithLoc)
1575  {
1576  filtered = filterWithLoc.Filter(rasterised, bounds, scale);
1577  }
1578 
1579  if (filtered != null)
1580  {
1581  rasterised.Dispose();
1582 
1583  DrawRasterImage(0, 0, filtered.Width, filtered.Height, bounds.Location.X, bounds.Location.Y, bounds.Size.Width, bounds.Size.Height, filtered);
1584  }
1585  }
1586  else
1587  {
1588  throw new NotImplementedException(@"The filter could not be rasterised! You can avoid this error by doing one of the following:
1589  • Add a reference to VectSharp.Raster or VectSharp.Raster.ImageSharp (you may also need to add a using directive somewhere to force the assembly to be loaded).
1590  • Provide your own implementation of Graphics.RasterisationMethod.
1591  • Set the FilterOption.Operation to ""NeverRasteriseAndIgnore"", ""NeverRasteriseAndSkip"", ""IgnoreAll"" or ""SkipAll"".");
1592  }
1593  }
1594  }
1595 
1596  if (justDraw)
1597  {
1598  graphics.CopyToIGraphicsContext(this);
1599  }
1600  }
1601  }
1602  }
1603 
1604 
1605  /// <summary>
1606  /// Contains methods to render a <see cref="Page"/> as an SVG file.
1607  /// </summary>
1608  public static class SVGContextInterpreter
1609  {
1610 
1611  /// <summary>
1612  /// Render the page to an SVG file.
1613  /// </summary>
1614  /// <param name="page">The <see cref="Page"/> to render.</param>
1615  /// <param name="fileName">The full path to the file to save. If it exists, it will be overwritten.</param>
1616  /// <param name="textOption">Defines whether the used fonts should be included in the file.</param>
1617  /// <param name="linkDestinations">A dictionary associating element tags to link targets. If this is provided, objects that have been drawn with a tag contained in the dictionary will become hyperlink to the destination specified in the dictionary. If the destination starts with a hash (#), it is interpreted as the tag of another object in the current document; otherwise, it is interpreted as an external URI.</param>
1618  /// <param name="filterOption">Defines how and whether image filters should be rasterised when rendering the image.</param>
1619  public static void SaveAsSVG(this Page page, string fileName, TextOptions textOption = TextOptions.SubsetFonts, Dictionary<string, string> linkDestinations = null, FilterOption filterOption = default)
1620  {
1621  using (FileStream sr = new FileStream(fileName, FileMode.Create))
1622  {
1623  page.SaveAsSVG(sr, textOption, linkDestinations, filterOption);
1624  }
1625  }
1626 
1627  /// <summary>
1628  /// Defines whether the used fonts should be included in the file.
1629  /// </summary>
1630  public enum TextOptions
1631  {
1632  /// <summary>
1633  /// Embeds the full font files.
1634  /// </summary>
1635  EmbedFonts,
1636 
1637  /// <summary>
1638  /// Embeds subsetted font files containing only the glyphs for the characters that have been used.
1639  /// </summary>
1640  SubsetFonts,
1641 
1642  /// <summary>
1643  /// Does not embed any font file and converts all text items into paths.
1644  /// </summary>
1645  ConvertIntoPaths,
1646 
1647  /// <summary>
1648  /// Does not embed any font file, but still encodes text items as such.
1649  /// </summary>
1650  DoNotEmbed
1651  }
1652 
1653  /// <summary>
1654  /// Determines how and whether image filters are rasterised.
1655  /// </summary>
1656  public class FilterOption
1657  {
1658  /// <summary>
1659  /// Defines whether image filters should be rasterised or not.
1660  /// </summary>
1661  public enum FilterOperations
1662  {
1663  /// <summary>
1664  /// Image filters will always be rasterised.
1665  /// </summary>
1666  RasteriseAll,
1667 
1668  /// <summary>
1669  /// Image filters will only be rasterised if they are not supported natively by the output file format.
1670  /// </summary>
1671  RasteriseIfNecessary,
1672 
1673  /// <summary>
1674  /// Image filters will never be rasterised; for filters that are not supported, the filter will be ignored.
1675  /// </summary>
1676  NeverRasteriseAndIgnore,
1677 
1678  /// <summary>
1679  /// Image filters will never be rasterised; if an image should be drawn with an unsupported filter, the image will not be drawn at all.
1680  /// </summary>
1681  NeverRasteriseAndSkip,
1682 
1683  /// <summary>
1684  /// All image filters (supported and unsupported) will be ignored.
1685  /// </summary>
1686  IgnoreAll,
1687 
1688  /// <summary>
1689  /// All the images that should be drawn with a filter will be ignored.
1690  /// </summary>
1691  SkipAll
1692  }
1693 
1694  /// <summary>
1695  /// Defines whether image filters should be rasterised or not.
1696  /// </summary>
1697  public FilterOperations Operation { get; } = FilterOperations.RasteriseIfNecessary;
1698 
1699  /// <summary>
1700  /// The resolution that will be used to rasterise image filters. Depending on the value of <see cref="RasterisationResolutionRelative"/>, this can either be an absolute resolution (i.e. a size in pixel), or a scale factor that is applied to the image size in graphics units.
1701  /// </summary>
1702  public double RasterisationResolution { get; } = 1;
1703 
1704  /// <summary>
1705  /// Determines whether the value of <see cref="RasterisationResolution"/> is absolute (i.e. a size in pixel), or relative (i.e. a scale factor that is applied to the image size in graphics units).
1706  /// </summary>
1707  public bool RasterisationResolutionRelative { get; } = true;
1708 
1709  /// <summary>
1710  /// The default options for image filter rasterisation.
1711  /// </summary>
1712  public static FilterOption Default = new FilterOption(FilterOperations.RasteriseIfNecessary, 1, true);
1713 
1714  /// <summary>
1715  /// Create a new <see cref="FilterOption"/> object.
1716  /// </summary>
1717  /// <param name="operation">Defines whether image filters should be rasterised or not.</param>
1718  /// <param name="rasterisationResolution">The resolution that will be used to rasterise image filters. Depending on the value of <see cref="RasterisationResolutionRelative"/>, this can either be an absolute resolution (i.e. a size in pixel), or a scale factor that is applied to the image size in graphics units.</param>
1719  /// <param name="rasterisationResolutionRelative">Determines whether the value of <see cref="RasterisationResolution"/> is absolute (i.e. a size in pixel), or relative (i.e. a scale factor that is applied to the image size in graphics units).</param>
1720  public FilterOption(FilterOperations operation, double rasterisationResolution, bool rasterisationResolutionRelative)
1721  {
1722  this.Operation = operation;
1723  this.RasterisationResolution = rasterisationResolution;
1724  this.RasterisationResolutionRelative = rasterisationResolutionRelative;
1725  }
1726  }
1727 
1728  /// <summary>
1729  /// Render the page to an SVG stream.
1730  /// </summary>
1731  /// <param name="page">The <see cref="Page"/> to render.</param>
1732  /// <param name="stream">The stream to which the SVG data will be written.</param>
1733  /// <param name="textOption">Defines whether the used fonts should be included in the file.</param>
1734  /// <param name="linkDestinations">A dictionary associating element tags to link targets. If this is provided, objects that have been drawn with a tag contained in the dictionary will become hyperlink to the destination specified in the dictionary. If the destination starts with a hash (#), it is interpreted as the tag of another object in the current document; otherwise, it is interpreted as an external URI.</param>
1735  /// <param name="filterOption">Defines how and whether image filters should be rasterised when rendering the image.</param>
1736  public static void SaveAsSVG(this Page page, Stream stream, TextOptions textOption = TextOptions.SubsetFonts, Dictionary<string, string> linkDestinations = null, FilterOption filterOption = default)
1737  {
1738  if (linkDestinations == null)
1739  {
1740  linkDestinations = new Dictionary<string, string>();
1741  }
1742 
1743  if (filterOption == null)
1744  {
1745  filterOption = FilterOption.Default;
1746  }
1747 
1748  bool textToPaths = textOption == TextOptions.ConvertIntoPaths;
1749 
1750  SVGContext ctx = new SVGContext(page.Width, page.Height, textToPaths, textOption, linkDestinations, filterOption);
1751 
1752  ctx.Rectangle(0, 0, page.Width, page.Height);
1753  ctx.SetFillStyle(page.Background);
1754  ctx.Fill();
1755  ctx.SetFillStyle((0, 0, 0, 1));
1756 
1757  page.Graphics.CopyToIGraphicsContext(ctx);
1758 
1759  if (!textToPaths && textOption != TextOptions.DoNotEmbed)
1760  {
1761  bool subsetFonts = textOption == TextOptions.SubsetFonts;
1762 
1763  StringBuilder cssFonts = new StringBuilder();
1764 
1765  Dictionary<string, string> newFontFamilies = new Dictionary<string, string>();
1766 
1767  foreach (KeyValuePair<string, FontFamily> kvp in ctx.UsedFontFamilies)
1768  {
1769  TrueTypeFile subsettedFont;
1770 
1771  if (subsetFonts)
1772  {
1773  newFontFamilies[kvp.Key] = kvp.Value.TrueTypeFile.GetFontFamilyName() + "-" + Guid.NewGuid().ToString();
1774  subsettedFont = kvp.Value.TrueTypeFile.SubsetFont(new string(ctx.UsedChars[kvp.Key].ToArray()));
1775  }
1776  else
1777  {
1778  newFontFamilies[kvp.Key] = kvp.Value.TrueTypeFile.GetFontName();
1779  subsettedFont = kvp.Value.TrueTypeFile;
1780  }
1781 
1782  byte[] fontBytes;
1783 
1784  using (MemoryStream fontStream = new MemoryStream((int)subsettedFont.FontStream.Length))
1785  {
1786  subsettedFont.FontStream.Seek(0, SeekOrigin.Begin);
1787  subsettedFont.FontStream.CopyTo(fontStream);
1788 
1789  fontBytes = fontStream.ToArray();
1790  }
1791 
1792 
1793  cssFonts.Append("\n\t\t@font-face\n\t\t{\t\t\tfont-family: \"" + newFontFamilies[kvp.Key] + "\";\n\t\t\tsrc: url(\"data:font/ttf;charset=utf-8;base64,");
1794  cssFonts.Append(Convert.ToBase64String(fontBytes));
1795  cssFonts.Append("\");\n\t\t}\n");
1796  }
1797 
1798  XmlElement style = ctx.Document.CreateElement("style", SVGContext.SVGNamespace);
1799  style.InnerText = cssFonts.ToString();
1800 
1801  XmlNode svgElement = ctx.Document.GetElementsByTagName("svg")[0];
1802 
1803  svgElement.InsertBefore(style, svgElement.FirstChild);
1804 
1805  foreach (XmlNode text in ctx.Document.GetElementsByTagName("text"))
1806  {
1807  string fontFamily = text.Attributes["font-family"].Value;
1808 
1809  string fallbackFontFamily = "";
1810 
1811  switch (fontFamily)
1812  {
1813  case "Helvetica":
1814  case "Helvetica-Bold":
1815  case "Helvetica-Oblique":
1816  case "Helvetica-BoldOblique":
1817  fallbackFontFamily = "sans-serif";
1818  break;
1819 
1820  case "Times-Roman":
1821  case "Times-Bold":
1822  case "Times-Italic":
1823  case "Times-BoldItalic":
1824  fallbackFontFamily = "serif";
1825  break;
1826 
1827  case "Courier":
1828  case "Courier-Bold":
1829  case "Courier-Oblique":
1830  case "Courier-BoldOblique":
1831  fallbackFontFamily = "monospace";
1832  break;
1833 
1834  default:
1835  if (ctx.UsedFontFamilies[fontFamily].TrueTypeFile.IsFixedPitch())
1836  {
1837  fallbackFontFamily = "monospace";
1838  }
1839  else if (ctx.UsedFontFamilies[fontFamily].TrueTypeFile.IsScript() || ctx.UsedFontFamilies[fontFamily].TrueTypeFile.IsSerif())
1840  {
1841  fallbackFontFamily = "serif";
1842  }
1843  else
1844  {
1845  fallbackFontFamily = "sans-serif";
1846  }
1847  break;
1848  }
1849 
1850 
1851  if (!string.IsNullOrEmpty(fallbackFontFamily))
1852  {
1853  ((XmlElement)text).SetAttribute("font-family", newFontFamilies[fontFamily] + ", " + fallbackFontFamily);
1854  }
1855  else
1856  {
1857  ((XmlElement)text).SetAttribute("font-family", newFontFamilies[fontFamily]);
1858  }
1859 
1860  }
1861  }
1862  else if (!textToPaths && textOption == TextOptions.DoNotEmbed)
1863  {
1864  foreach (XmlNode text in ctx.Document.GetElementsByTagName("text"))
1865  {
1866  string fontFamily = text.Attributes["font-family"].Value;
1867 
1868  string newFontFamily = ctx.UsedFontFamilies[fontFamily].TrueTypeFile.GetFontFamilyName();
1869 
1870  switch (fontFamily)
1871  {
1872  case "Helvetica":
1873  case "Helvetica-Bold":
1874  case "Helvetica-Oblique":
1875  case "Helvetica-BoldOblique":
1876  newFontFamily = newFontFamily + ", sans-serif";
1877  break;
1878 
1879  case "Times-Roman":
1880  case "Times-Bold":
1881  case "Times-Italic":
1882  case "Times-BoldItalic":
1883  newFontFamily = newFontFamily + ", serif";
1884  break;
1885 
1886  case "Courier":
1887  case "Courier-Bold":
1888  case "Courier-Oblique":
1889  case "Courier-BoldOblique":
1890  newFontFamily = newFontFamily + ", monospace";
1891  break;
1892 
1893  default:
1894  if (ctx.UsedFontFamilies[fontFamily].TrueTypeFile.IsFixedPitch())
1895  {
1896  newFontFamily = newFontFamily + ", monospace";
1897  }
1898  else if (ctx.UsedFontFamilies[fontFamily].TrueTypeFile.IsScript())
1899  {
1900  newFontFamily = newFontFamily + ", cursive";
1901  }
1902  else if (ctx.UsedFontFamilies[fontFamily].TrueTypeFile.IsSerif())
1903  {
1904  newFontFamily = newFontFamily + ", serif";
1905  }
1906  else
1907  {
1908  newFontFamily = newFontFamily + ", sans-serif";
1909  }
1910  break;
1911  }
1912 
1913  ((XmlElement)text).SetAttribute("font-family", newFontFamily);
1914  }
1915  }
1916 
1917  ctx.Document.DocumentElement.SetAttribute("style", "font-synthesis: none;");
1918 
1919  WriteXMLToStream(ctx.Document.DocumentElement, stream);
1920 
1921  //ctx.Document.Save(stream);
1922  }
1923 
1924  internal static XmlElement ToLinearGradient(this LinearGradientBrush brush, XmlDocument document, string gradientId)
1925  {
1926  XmlElement gradient = document.CreateElement("linearGradient", SVGContext.SVGNamespace);
1927 
1928  gradient.SetAttribute("id", gradientId);
1929 
1930  gradient.SetAttribute("gradientUnits", "userSpaceOnUse");
1931 
1932  gradient.SetAttribute("x1", brush.StartPoint.X.ToString(System.Globalization.CultureInfo.InvariantCulture));
1933  gradient.SetAttribute("y1", brush.StartPoint.Y.ToString(System.Globalization.CultureInfo.InvariantCulture));
1934  gradient.SetAttribute("x2", brush.EndPoint.X.ToString(System.Globalization.CultureInfo.InvariantCulture));
1935  gradient.SetAttribute("y2", brush.EndPoint.Y.ToString(System.Globalization.CultureInfo.InvariantCulture));
1936 
1937  foreach (GradientStop stop in brush.GradientStops)
1938  {
1939  XmlElement gradientStop = document.CreateElement("stop", SVGContext.SVGNamespace);
1940 
1941  gradientStop.SetAttribute("offset", stop.Offset.ToString(System.Globalization.CultureInfo.InvariantCulture));
1942  gradientStop.SetAttribute("stop-color", stop.Colour.ToCSSString(false));
1943  gradientStop.SetAttribute("stop-opacity", stop.Colour.A.ToString(System.Globalization.CultureInfo.InvariantCulture));
1944 
1945  gradient.AppendChild(gradientStop);
1946  }
1947 
1948  return gradient;
1949  }
1950 
1951  internal static XmlElement ToRadialGradient(this RadialGradientBrush brush, XmlDocument document, string gradientId)
1952  {
1953  XmlElement gradient = document.CreateElement("radialGradient", SVGContext.SVGNamespace);
1954 
1955  gradient.SetAttribute("id", gradientId);
1956 
1957  gradient.SetAttribute("gradientUnits", "userSpaceOnUse");
1958 
1959  gradient.SetAttribute("cx", brush.Centre.X.ToString(System.Globalization.CultureInfo.InvariantCulture));
1960  gradient.SetAttribute("cy", brush.Centre.Y.ToString(System.Globalization.CultureInfo.InvariantCulture));
1961  gradient.SetAttribute("r", brush.Radius.ToString(System.Globalization.CultureInfo.InvariantCulture));
1962  gradient.SetAttribute("fx", brush.FocalPoint.X.ToString(System.Globalization.CultureInfo.InvariantCulture));
1963  gradient.SetAttribute("fy", brush.FocalPoint.Y.ToString(System.Globalization.CultureInfo.InvariantCulture));
1964 
1965  foreach (GradientStop stop in brush.GradientStops)
1966  {
1967  XmlElement gradientStop = document.CreateElement("stop", SVGContext.SVGNamespace);
1968 
1969  gradientStop.SetAttribute("offset", stop.Offset.ToString(System.Globalization.CultureInfo.InvariantCulture));
1970  gradientStop.SetAttribute("stop-color", stop.Colour.ToCSSString(false));
1971  gradientStop.SetAttribute("stop-opacity", stop.Colour.A.ToString(System.Globalization.CultureInfo.InvariantCulture));
1972 
1973  gradient.AppendChild(gradientStop);
1974  }
1975 
1976  return gradient;
1977  }
1978 
1979  // Adapted from http://www.ericwhite.com/blog/2011/05/09/custom-formatting-of-xml-using-linq-to-xml-2/
1980  private static void WriteStartElement(XmlWriter writer, XmlElement e)
1981  {
1982  writer.WriteStartElement(e.Prefix, e.LocalName, e.NamespaceURI);
1983 
1984  foreach (XmlAttribute a in e.Attributes)
1985  {
1986  writer.WriteAttributeString(a.Prefix, a.LocalName, a.NamespaceURI, a.Value);
1987  }
1988  }
1989 
1990  private static void WriteElement(XmlWriter writer, XmlElement e)
1991  {
1992  if (e.Name == "text")
1993  {
1994  XmlWriterSettings settings = new XmlWriterSettings();
1995  settings.Indent = false;
1996  settings.OmitXmlDeclaration = true;
1997  settings.ConformanceLevel = ConformanceLevel.Fragment;
1998  settings.NamespaceHandling = NamespaceHandling.OmitDuplicates;
1999 
2000  WriteStartElement(writer, e);
2001 
2002  StringBuilder sb = new StringBuilder();
2003 
2004  using (XmlWriter newWriter = XmlWriter.Create(sb, settings))
2005  {
2006  foreach (XmlNode n in e.ChildNodes)
2007  {
2008  n.WriteTo(newWriter);
2009  }
2010  }
2011 
2012  writer.WriteRaw(sb.ToString().Replace(" xmlns=\"http://www.w3.org/2000/svg\">", ">"));
2013 
2014  writer.WriteEndElement();
2015  }
2016  else
2017  {
2018  WriteStartElement(writer, e);
2019  foreach (XmlNode n in e.ChildNodes)
2020  {
2021  if (n is XmlElement element)
2022  {
2023  WriteElement(writer, element);
2024  }
2025  else
2026  {
2027  n.WriteTo(writer);
2028  }
2029  }
2030  writer.WriteEndElement();
2031  }
2032  }
2033 
2034  private static void WriteXMLToStream(XmlElement element, Stream output)
2035  {
2036  XmlWriterSettings settings = new XmlWriterSettings();
2037  settings.Indent = true;
2038 
2039  using (XmlWriter writer = XmlWriter.Create(output, settings))
2040  {
2041  WriteElement(writer, element);
2042  }
2043  }
2044  }
2045 }
VectSharp.Rectangle
Represents a rectangle.
Definition: Point.cs:173
VectSharp.RasterImage.PNGStream
MemoryStream PNGStream
Contains a representation of the image in PNG format. Generated at the first access and cached until ...
Definition: RasterImage.cs:141
VectSharp.TrueTypeFile
Represents a font file in TrueType format. Reference: http://stevehanov.ca/blog/?id=143,...
Definition: TrueType.cs:31
VectSharp.Filters.IFilter.TopLeftMargin
Point TopLeftMargin
Determines how much the area of the filter's subject should be expanded on the top-left to accommodat...
Definition: Filters.cs:30
VectSharp.SVG.SVGContextInterpreter.FilterOption
Determines how and whether image filters are rasterised.
Definition: SVGContext.cs:1657
VectSharp.TrueTypeFile.Get1000EmKerning
PairKerning Get1000EmKerning(char glyph1, char glyph2)
Gets the kerning between two glyphs.
Definition: TrueType.cs:2490
VectSharp.Colour
Represents an RGB colour.
Definition: Colour.cs:26
VectSharp.GradientStop.Offset
double Offset
The offset of the gradient stop. Range: [0, 1].
Definition: Brush.cs:119
VectSharp.LineDash.UnitsOff
double UnitsOff
Length of the "off" (not painted) segment.
Definition: Enums.cs:127
VectSharp.SVG
Definition: SVGContext.cs:28
VectSharp.Page.Height
double Height
Height of the page.
Definition: Document.cs:57
VectSharp.RasterImage
Represents a raster image, created from raw pixel data. Consider using the derived classes included i...
Definition: RasterImage.cs:99
VectSharp.GraphicsPath
Represents a graphics path that can be filled or stroked.
Definition: GraphicsPath.cs:29
VectSharp.Page.Background
Colour Background
Background colour of the page.
Definition: Document.cs:67
VectSharp
Definition: Brush.cs:26
VectSharp.Filters.IFilter
Represents a filter. Do not implement this interface directly; instead, implement ILocationInvariantF...
Definition: Filters.cs:26
VectSharp.FontFamily.IsItalic
bool IsItalic
Whether this font is italic or oblique or not. This is set based on the information included in the O...
Definition: Font.cs:596
VectSharp.Graphics.CopyToIGraphicsContext
void CopyToIGraphicsContext(IGraphicsContext destinationContext)
Copy the current graphics to an instance of a class implementing IGraphicsContext.
Definition: Graphics.cs:599
VectSharp.Filters.ColourMatrixFilter
Represents a filter that applies a Filters.ColourMatrix to the colours of the image.
Definition: ColourMatrixFilter.cs:568
VectSharp.Document
Represents a collection of pages.
Definition: Document.cs:28
VectSharp.Font.MeasureTextAdvanced
DetailedFontMetrics MeasureTextAdvanced(string text)
Measure all the metrics of a text string when typeset with this font.
Definition: Font.cs:358
VectSharp.Page.Width
double Width
Width of the page.
Definition: Document.cs:52
VectSharp.Page
Represents a Graphics object with a width and height.
Definition: Document.cs:48
VectSharp.RasterImage.Width
int Width
The width in pixels of the image.
Definition: RasterImage.cs:123
VectSharp.Font.DetailedFontMetrics.Top
double Top
Height of the tallest glyph in the string over the baseline. Always >= 0.
Definition: Font.cs:126
VectSharp.Brush
Represents a brush used to fill or stroke graphics elements. This could be a solid colour,...
Definition: Brush.cs:31
VectSharp.RadialGradientBrush
Represents a brush painting with a radial gradient.
Definition: Brush.cs:368
VectSharp.GradientBrush.GradientStops
GradientStops GradientStops
The colour stops in the gradient.
Definition: Brush.cs:236
VectSharp.Colour.A
double A
Alpha component of the colour. Range: [0, 1].
Definition: Colour.cs:45
VectSharp.Size.Height
double Height
Height of the object.
Definition: Point.cs:155
VectSharp.SegmentType.Close
@ Close
The segment represents the closing segment of a figure.
VectSharp.LineCaps
LineCaps
Represents line caps.
Definition: Enums.cs:71
VectSharp.Font
Represents a typeface with a specific size.
Definition: Font.cs:29
VectSharp.TextBaselines
TextBaselines
Represent text baselines.
Definition: Enums.cs:24
VectSharp.RasterImage.Interpolate
bool Interpolate
Determines whether the image should be interpolated when it is resized.
Definition: RasterImage.cs:133
VectSharp.SVG.SVGContextInterpreter.FilterOption.Default
static FilterOption Default
The default options for image filter rasterisation.
Definition: SVGContext.cs:1712
VectSharp.Graphics
Represents an abstract drawing surface.
Definition: Graphics.cs:262
VectSharp.Rectangle.Size
Size Size
The size of the rectangle.
Definition: Point.cs:187
VectSharp.FontFamily.ResolveFontFamily
static FontFamily ResolveFontFamily(string fontFamily)
Create a new font family from the specified family name or true type file. If the family name or the ...
VectSharp.GraphicsPath.AddText
GraphicsPath AddText(double originX, double originY, string text, Font font, TextBaselines textBaseline=TextBaselines.Top)
Add the contour of a text string to the current path.
Definition: GraphicsPath.cs:312
VectSharp.LineJoins
LineJoins
Represents line joining options.
Definition: Enums.cs:92
VectSharp.LineDash.UnitsOn
double UnitsOn
Length of the "on" (painted) segment.
Definition: Enums.cs:122
VectSharp.SVG.SVGContextInterpreter.TextOptions
TextOptions
Defines whether the used fonts should be included in the file.
Definition: SVGContext.cs:1631
VectSharp.FontFamily
Represents a typeface.
Definition: Font.cs:421
VectSharp.Graphics.TryRasterise
bool TryRasterise(Rectangle region, double scale, bool interpolate, out RasterImage output)
Tries to rasterise specified region of this Graphics object using the default rasterisation method.
Definition: Graphics.cs:1404
VectSharp.LinearGradientBrush.EndPoint
Point EndPoint
The end point of the gradient. Note that this is relative to the current coordinate system when the g...
Definition: Brush.cs:254
VectSharp.GradientStop
Represents a colour stop in a gradient.
Definition: Brush.cs:110
VectSharp.Page.Graphics
Graphics Graphics
Graphics surface of the page.
Definition: Document.cs:62
VectSharp.SVG.SVGContextInterpreter.FilterOption.RasterisationResolutionRelative
bool RasterisationResolutionRelative
Determines whether the value of RasterisationResolution is absolute (i.e. a size in pixel),...
Definition: SVGContext.cs:1707
VectSharp.FontFamily.IsOblique
bool IsOblique
Whether this font is oblique or not. This is set based on the information included in the OS/2 table ...
Definition: Font.cs:601
VectSharp.SVG.SVGContextInterpreter
Contains methods to render a Page as an SVG file.
Definition: SVGContext.cs:1609
VectSharp.SVG.SVGContextInterpreter.SaveAsSVG
static void SaveAsSVG(this Page page, Stream stream, TextOptions textOption=TextOptions.SubsetFonts, Dictionary< string, string > linkDestinations=null, FilterOption filterOption=default)
Render the page to an SVG stream.
Definition: SVGContext.cs:1736
VectSharp.Font.EnableKerning
static bool EnableKerning
Determines whether text kerning is enabled. Note that, even when this is set to false,...
Definition: Font.cs:33
VectSharp.Colour.ToCSSString
string ToCSSString(bool includeAlpha)
Convert the Colour object into a hex string that is constituted by a "#" followed by two-digit hexade...
Definition: Colour.cs:208
VectSharp.SVG.SVGContextInterpreter.FilterOption.Operation
FilterOperations Operation
Defines whether image filters should be rasterised or not.
Definition: SVGContext.cs:1697
VectSharp.Font.DetailedFontMetrics
Represents detailed information about the metrics of a text string when drawn with a certain font.
Definition: Font.cs:102
VectSharp.FontFamily.IsBold
bool IsBold
Whether this font is bold or not. This is set based on the information included in the OS/2 table of ...
Definition: Font.cs:591
VectSharp.LinearGradientBrush.StartPoint
Point StartPoint
The starting point of the gradient. Note that this is relative to the current coordinate system when ...
Definition: Brush.cs:249
VectSharp.Point.X
double X
Horizontal (x) coordinate, measured to the right of the origin.
Definition: Point.cs:32
VectSharp.RasterImage.Height
int Height
The height in pixels of the image.
Definition: RasterImage.cs:128
VectSharp.TrueTypeFile.FontStream
Stream FontStream
A stream pointing to the TrueType file source (either on disk or in memory). Never dispose this strea...
Definition: TrueType.cs:47
VectSharp.FontFamily.StandardFontFamilies
StandardFontFamilies
The 14 standard font families.
Definition: Font.cs:500
VectSharp.Font.FontFamily
FontFamily FontFamily
Font typeface.
Definition: Font.cs:158
VectSharp.LinearGradientBrush
Represents a brush painting with a linear gradient.
Definition: Brush.cs:245
VectSharp.Filters
Definition: BoxBlurFilter.cs:22
VectSharp.Filters.MaskFilter
Represents a filter that uses the luminance of an image to mask another image.
Definition: MaskFilter.cs:27
VectSharp.LineDash
Represents instructions on how to paint a dashed line.
Definition: Enums.cs:113
VectSharp.Rectangle.Location
Point Location
The top-left corner of the rectangle.
Definition: Point.cs:182
VectSharp.Size.Width
double Width
Width of the object.
Definition: Point.cs:150
VectSharp.Filters.IFilter.BottomRightMargin
Point BottomRightMargin
Determines how much the area of the filter's subject should be expanded on the bottom-right to accomm...
Definition: Filters.cs:35
VectSharp.TrueTypeFile.PairKerning
Contains information describing how the position of two glyphs in a kerning pair should be altered.
Definition: TrueType.cs:4252
VectSharp.Filters.IFilterWithLocation
Represents a filter whose results depend on the position of the subject image on the graphics surface...
Definition: Filters.cs:56
VectSharp.Filters.ILocationInvariantFilter
Represents a filter that can be applied to an image regardless of its location on the graphics surfac...
Definition: Filters.cs:42
VectSharp.SolidColourBrush
Represents a brush painting with a single solid colour.
Definition: Brush.cs:55
VectSharp.Filters.CompositeLocationInvariantFilter
Represents a filter that corresponds to applying multiple ILocationInvariantFilters one after the oth...
Definition: CompositeFilter.cs:27
VectSharp.SVG.SVGContextInterpreter.FilterOption.RasterisationResolution
double RasterisationResolution
The resolution that will be used to rasterise image filters. Depending on the value of RasterisationR...
Definition: SVGContext.cs:1702
VectSharp.SVG.SVGContextInterpreter.SaveAsSVG
static void SaveAsSVG(this Page page, string fileName, TextOptions textOption=TextOptions.SubsetFonts, Dictionary< string, string > linkDestinations=null, FilterOption filterOption=default)
Render the page to an SVG file.
Definition: SVGContext.cs:1619
VectSharp.IGraphicsContext
This interface should be implemented by classes intended to provide graphics output capability to a G...
Definition: Graphics.cs:36
VectSharp.Font.FontSize
double FontSize
Font size, in graphics units.
Definition: Font.cs:153
VectSharp.Point
Represents a point relative to an origin in the top-left corner.
Definition: Point.cs:28
VectSharp.Graphics.GetBounds
Rectangle GetBounds()
Computes the rectangular bounds of the region affected by the drawing operations performed on the Gra...
Definition: Graphics.cs:1313
VectSharp.SVG.SVGContextInterpreter.FilterOption.FilterOperations
FilterOperations
Defines whether image filters should be rasterised or not.
Definition: SVGContext.cs:1662
VectSharp.LineDash.Phase
double Phase
Position in the dash pattern at which the line starts.
Definition: Enums.cs:132
VectSharp.Filters.GaussianBlurFilter
Represents a filter that applies a Gaussian blur effect.
Definition: GaussianBlurFilter.cs:27
VectSharp.Colour.FromRgb
static Colour FromRgb(double r, double g, double b)
Create a new colour from RGB (red, green and blue) values.
Definition: Colour.cs:62
VectSharp.Point.Bounds
static Rectangle Bounds(IEnumerable< Point > points)
Computes the smallest Rectangle that contains all the specified points.
Definition: Point.cs:107
VectSharp.Point.Y
double Y
Vertical (y) coordinate, measured to the bottom of the origin.
Definition: Point.cs:37
VectSharp.FontFamily.TrueTypeFile
TrueTypeFile TrueTypeFile
Parsed TrueType font file for this font family. See also: VectSharp.TrueTypeFile.
Definition: Font.cs:586
VectSharp.SVG.SVGContextInterpreter.FilterOption.FilterOption
FilterOption(FilterOperations operation, double rasterisationResolution, bool rasterisationResolutionRelative)
Create a new FilterOption object.
Definition: SVGContext.cs:1720
VectSharp.GradientStop.Colour
Colour Colour
The Colour at the gradient stop.
Definition: Brush.cs:114
VectSharp.FontFamily.FileName
string FileName
Full path to the TrueType font file for this font family (or, if this is a standard font family,...
Definition: Font.cs:575
VectSharp.GraphicsPath.Segments
List< Segment > Segments
The segments that make up the path.
Definition: GraphicsPath.cs:33
VectSharp.Colour.FromRgba
static Colour FromRgba(double r, double g, double b, double a)
Create a new colour from RGBA (red, green, blue and alpha) values.
Definition: Colour.cs:99