19 using System.Collections.Generic;
29 internal static class MatrixUtils
31 public static double[] Multiply(
double[,] matrix,
double[] vector)
33 double[] tbr =
new double[2];
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];
41 public static Point Multiply(
double[,] matrix,
Point vector)
43 double[] tbr =
new double[2];
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];
48 return new Point(tbr[0], tbr[1]);
51 public static double[,] Multiply(
double[,] matrix1,
double[,] matrix2)
53 double[,] tbr =
new double[3, 3];
55 for (
int i = 0; i < 3; i++)
57 for (
int j = 0; j < 3; j++)
59 for (
int k = 0; k < 3; k++)
61 tbr[i, j] += matrix1[i, k] * matrix2[k, j];
69 public static double[,] Rotate(
double[,] matrix,
double angle)
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;
78 return Multiply(matrix, rotationMatrix);
81 public static double[,] Translate(
double[,] matrix,
double x,
double y)
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;
90 return Multiply(matrix, translationMatrix);
93 public static double[,] Scale(
double[,] matrix,
double scaleX,
double scaleY)
95 double[,] scaleMatrix =
new double[3, 3];
96 scaleMatrix[0, 0] = scaleX;
97 scaleMatrix[1, 1] = scaleY;
98 scaleMatrix[2, 2] = 1;
100 return Multiply(matrix, scaleMatrix);
103 public static double[,] Identity =
new double[,] { { 1, 0, 0 }, { 0, 1, 0 }, { 0, 0, 1 } };
105 public static double[,] Invert(
double[,] m)
107 double[,] tbr =
new double[3, 3];
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]);
123 internal class SVGFigure
125 public Point StartPoint {
get;
set; }
126 public Point CurrentPoint {
get;
set; }
128 public string Data {
get;
set; }
129 public int PointCount {
get;
set; } = 0;
132 internal class SVGPathObject
134 public List<SVGFigure> Figures {
get;
set; } =
new List<SVGFigure>();
140 public Dictionary<string, FontFamily> UsedFontFamilies;
141 public Dictionary<string, HashSet<char>> UsedChars;
144 public const string SVGNamespace =
"http://www.w3.org/2000/svg";
146 public double Width {
get; }
148 public double Height {
get; }
153 public Brush FillStyle {
get;
private set; }
155 public Brush StrokeStyle {
get;
private set; }
157 public double LineWidth {
get;
set; }
158 public LineCaps LineCap {
private get;
set; }
159 public LineJoins LineJoin {
private get;
set; }
163 private SVGPathObject currentPath;
164 private SVGFigure currentFigure;
167 private XmlElement currentElement;
169 public string Tag {
get;
set; }
171 private double[,] _transform;
172 private Stack<double[,]> states;
174 private string _currClipPath;
175 private Stack<string> clipPaths;
177 private bool TextToPaths =
false;
180 private Dictionary<string, string> linkDestinations;
182 XmlElement definitions;
183 Dictionary<Brush, string> gradients;
190 this.linkDestinations = linkDestinations;
193 this.Height = height;
195 currentPath =
new SVGPathObject();
196 currentFigure =
new SVGFigure();
210 _transform =
new double[3, 3];
212 _transform[0, 0] = 1;
213 _transform[1, 1] = 1;
214 _transform[2, 2] = 1;
216 states =
new Stack<double[,]>();
218 clipPaths =
new Stack<string>();
219 clipPaths.Push(
null);
220 _currClipPath =
null;
222 UsedFontFamilies =
new Dictionary<string, FontFamily>();
223 UsedChars =
new Dictionary<string, HashSet<char>>();
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);
235 definitions =
Document.CreateElement(
"defs", SVGNamespace);
236 gradients =
new Dictionary<Brush, string>();
237 currentElement.AppendChild(definitions);
239 this.TextToPaths = textToPaths;
240 this.TextOption = textOption;
241 this.FilterOption = filterOption;
245 private void PathText(
string text,
double x,
double y)
249 for (
int j = 0; j < textPath.
Segments.Count; j++)
271 if (currentFigure.PointCount > 0)
273 currentFigure.Data +=
"Z ";
274 currentPath.Figures.Add(currentFigure);
276 currentFigure =
new SVGFigure();
280 public void CubicBezierTo(
double p1X,
double p1Y,
double p2X,
double p2Y,
double p3X,
double p3Y)
282 if (currentFigure.PointCount == 0)
284 currentFigure.StartPoint =
new Point(p1X, p1Y);
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;
296 if (currentFigure.PointCount > 0)
298 currentPath.Figures.Add(currentFigure);
301 XmlElement currElement = currentElement;
303 if (!
string.IsNullOrEmpty(_currClipPath))
305 currentElement =
Document.CreateElement(
"g", SVGNamespace);
306 currentElement.SetAttribute(
"clip-path", _currClipPath);
307 currElement.AppendChild(currentElement);
310 XmlElement path =
Document.CreateElement(
"path", SVGNamespace);
311 path.SetAttribute(
"d", currentPath.Figures.Aggregate(
"", (a, b) => a + b.Data));
312 path.SetAttribute(
"stroke",
"none");
316 path.SetAttribute(
"fill", solid.Colour.ToCSSString(
false));
317 path.SetAttribute(
"fill-opacity", solid.A.ToString(System.Globalization.CultureInfo.InvariantCulture));
323 if (!gradients.TryGetValue(linearGradient, out gradientName))
325 gradientName =
"gradient" + (gradients.Count + 1).ToString(System.Globalization.CultureInfo.InvariantCulture);
327 XmlElement gradientElement = linearGradient.ToLinearGradient(
Document, gradientName);
328 this.definitions.AppendChild(gradientElement);
330 gradients.Add(linearGradient, gradientName);
333 path.SetAttribute(
"fill",
"url(#" + gradientName +
")");
339 if (!gradients.TryGetValue(radialGradient, out gradientName))
341 gradientName =
"gradient" + (gradients.Count + 1).ToString(System.Globalization.CultureInfo.InvariantCulture);
343 XmlElement gradientElement = radialGradient.ToRadialGradient(
Document, gradientName);
344 this.definitions.AppendChild(gradientElement);
346 gradients.Add(radialGradient, gradientName);
349 path.SetAttribute(
"fill",
"url(#" + gradientName +
")");
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) +
")");
356 if (!
string.IsNullOrEmpty(Tag))
358 path.SetAttribute(
"id", Tag);
361 if (!
string.IsNullOrEmpty(this.Tag) && this.linkDestinations.TryGetValue(this.Tag, out
string destination) && !
string.IsNullOrEmpty(destination))
363 XmlElement aElement =
Document.CreateElement(
"a", SVGNamespace);
364 aElement.SetAttribute(
"href", destination);
365 currentElement.AppendChild(aElement);
366 currentElement = aElement;
369 currentElement.AppendChild(path);
371 currentElement = currElement;
373 currentPath =
new SVGPathObject();
374 currentFigure =
new SVGFigure();
378 public void FillText(
string text,
double x,
double y)
393 double[,] currTransform =
null;
394 double[,] deltaTransform = MatrixUtils.Identity;
396 switch (TextBaseline)
399 currTransform = MatrixUtils.Translate(_transform, x - metrics.LeftSideBearing, y);
400 deltaTransform = MatrixUtils.Translate(deltaTransform, x - metrics.LeftSideBearing, y);
403 currTransform = MatrixUtils.Translate(_transform, x - metrics.LeftSideBearing, y + metrics.Top);
404 deltaTransform = MatrixUtils.Translate(deltaTransform, x - metrics.LeftSideBearing, y + metrics.Top);
407 currTransform = MatrixUtils.Translate(_transform, x - metrics.LeftSideBearing, y + metrics.Bottom);
408 deltaTransform = MatrixUtils.Translate(deltaTransform, x - metrics.LeftSideBearing, y + metrics.Bottom);
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);
415 currTransform = MatrixUtils.Translate(_transform, x - metrics.LeftSideBearing, y);
416 deltaTransform = MatrixUtils.Translate(deltaTransform, x - metrics.LeftSideBearing, y);
420 XmlElement currElement = currentElement;
422 if (!
string.IsNullOrEmpty(_currClipPath))
424 currentElement =
Document.CreateElement(
"g", SVGNamespace);
425 currentElement.SetAttribute(
"clip-path", _currClipPath);
426 currElement.AppendChild(currentElement);
429 XmlElement textElement =
Document.CreateElement(
"text", SVGNamespace);
431 textElement.SetAttribute(
"stroke",
"none");
435 textElement.SetAttribute(
"fill", solid.Colour.ToCSSString(
false));
436 textElement.SetAttribute(
"fill-opacity", solid.A.ToString(System.Globalization.CultureInfo.InvariantCulture));
440 string gradientName =
"g" + Guid.NewGuid().ToString(
"N");
442 XmlElement gradientElement = linearGradient.ToLinearGradient(
Document, gradientName);
444 deltaTransform = MatrixUtils.Invert(deltaTransform);
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) +
")");
450 this.definitions.AppendChild(gradientElement);
452 textElement.SetAttribute(
"fill",
"url(#" + gradientName +
")");
456 string gradientName =
"g" + Guid.NewGuid().ToString(
"N");
458 XmlElement gradientElement = radialGradient.ToRadialGradient(
Document, gradientName);
460 deltaTransform = MatrixUtils.Invert(deltaTransform);
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) +
")");
466 this.definitions.AppendChild(gradientElement);
468 textElement.SetAttribute(
"fill",
"url(#" + gradientName +
")");
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) +
")");
475 textElement.SetAttribute(
"x",
"0");
476 textElement.SetAttribute(
"y",
"0");
477 textElement.SetAttribute(
"font-size",
Font.
FontSize.ToString(System.Globalization.CultureInfo.InvariantCulture));
482 textElement.SetAttribute(
"font-weight",
"bold");
486 textElement.SetAttribute(
"font-weight",
"regular");
491 textElement.SetAttribute(
"font-style",
"italic");
495 textElement.SetAttribute(
"font-style",
"normal");
500 textElement.SetAttribute(
"font-style",
"oblique");
503 ProcessText(text, textElement);
505 if (!
string.IsNullOrEmpty(Tag))
507 textElement.SetAttribute(
"id", Tag);
510 if (!
string.IsNullOrEmpty(this.Tag) && this.linkDestinations.TryGetValue(
this.Tag, out
string destination) && !
string.IsNullOrEmpty(destination))
512 XmlElement aElement =
Document.CreateElement(
"a", SVGNamespace);
513 aElement.SetAttribute(
"href", destination);
514 currentElement.AppendChild(aElement);
515 currentElement = aElement;
518 currentElement.AppendChild(textElement);
520 currentElement = currElement;
524 PathText(text, x, y);
529 public void LineTo(
double x,
double y)
531 if (currentFigure.PointCount == 0)
533 currentFigure.StartPoint =
new Point(x, y);
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++;
541 public void MoveTo(
double x,
double y)
543 if (currentFigure.PointCount > 0)
545 currentPath.Figures.Add(currentFigure);
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;
555 public void Rectangle(
double x0,
double y0,
double width,
double height)
558 LineTo(x0 + width, y0);
559 LineTo(x0 + width, y0 + height);
560 LineTo(x0, y0 + height);
564 public void Restore()
566 _transform = states.Pop();
567 _currClipPath = clipPaths.Pop();
568 currentPath =
new SVGPathObject();
569 currentFigure =
new SVGFigure();
572 public void Rotate(
double angle)
574 _transform = MatrixUtils.Rotate(_transform, angle);
575 currentPath =
new SVGPathObject(); currentPath =
new SVGPathObject();
576 currentFigure =
new SVGFigure();
581 states.Push((
double[,])_transform.Clone());
582 clipPaths.Push(_currClipPath);
585 public void Scale(
double scaleX,
double scaleY)
587 _transform = MatrixUtils.Scale(_transform, scaleX, scaleY);
588 currentPath =
new SVGPathObject();
589 currentFigure =
new SVGFigure();
592 public void SetFillStyle((
int r,
int g,
int b,
double a) style)
597 public void SetFillStyle(
Brush style)
602 public void SetLineDash(
LineDash dash)
607 public void SetStrokeStyle((
int r,
int g,
int b,
double a) style)
612 public void SetStrokeStyle(
Brush style)
619 if (currentFigure.PointCount > 0)
621 currentPath.Figures.Add(currentFigure);
624 XmlElement currElement = currentElement;
626 if (!
string.IsNullOrEmpty(_currClipPath))
628 currentElement =
Document.CreateElement(
"g", SVGNamespace);
629 currentElement.SetAttribute(
"clip-path", _currClipPath);
630 currElement.AppendChild(currentElement);
633 XmlElement path =
Document.CreateElement(
"path", SVGNamespace);
634 path.SetAttribute(
"d", currentPath.Figures.Aggregate(
"", (a, b) => a + b.Data));
638 path.SetAttribute(
"stroke", solid.Colour.ToCSSString(
false));
639 path.SetAttribute(
"stroke-opacity", solid.A.ToString(System.Globalization.CultureInfo.InvariantCulture));
645 if (!gradients.TryGetValue(linearGradient, out gradientName))
647 gradientName =
"gradient" + (gradients.Count + 1).ToString(System.Globalization.CultureInfo.InvariantCulture);
649 XmlElement gradientElement = linearGradient.ToLinearGradient(
Document, gradientName);
650 this.definitions.AppendChild(gradientElement);
652 gradients.Add(linearGradient, gradientName);
655 path.SetAttribute(
"stroke",
"url(#" + gradientName +
")");
661 if (!gradients.TryGetValue(radialGradient, out gradientName))
663 gradientName =
"gradient" + (gradients.Count + 1).ToString(System.Globalization.CultureInfo.InvariantCulture);
665 XmlElement gradientElement = radialGradient.ToRadialGradient(
Document, gradientName);
666 this.definitions.AppendChild(gradientElement);
668 gradients.Add(radialGradient, gradientName);
671 path.SetAttribute(
"stroke",
"url(#" + gradientName +
")");
674 path.SetAttribute(
"stroke-width", LineWidth.ToString(System.Globalization.CultureInfo.InvariantCulture));
679 path.SetAttribute(
"stroke-linecap",
"butt");
682 path.SetAttribute(
"stroke-linecap",
"round");
685 path.SetAttribute(
"stroke-linecap",
"square");
692 path.SetAttribute(
"stroke-linejoin",
"bevel");
695 path.SetAttribute(
"stroke-linejoin",
"round");
698 path.SetAttribute(
"stroke-linejoin",
"miter");
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));
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) +
")");
713 if (!
string.IsNullOrEmpty(Tag))
715 path.SetAttribute(
"id", Tag);
718 if (!
string.IsNullOrEmpty(this.Tag) && this.linkDestinations.TryGetValue(this.Tag, out
string destination) && !
string.IsNullOrEmpty(destination))
720 XmlElement aElement =
Document.CreateElement(
"a", SVGNamespace);
721 aElement.SetAttribute(
"href", destination);
722 currentElement.AppendChild(aElement);
723 currentElement = aElement;
726 currentElement.AppendChild(path);
728 currentElement = currElement;
730 currentPath =
new SVGPathObject();
731 currentFigure =
new SVGFigure();
734 public void StrokeText(
string text,
double x,
double y)
748 double[,] currTransform =
null;
749 double[,] deltaTransform = MatrixUtils.Identity;
751 switch (TextBaseline)
754 currTransform = MatrixUtils.Translate(_transform, x - metrics.LeftSideBearing, y);
755 deltaTransform = MatrixUtils.Translate(deltaTransform, x - metrics.LeftSideBearing, y);
758 currTransform = MatrixUtils.Translate(_transform, x - metrics.LeftSideBearing, y + metrics.Top);
759 deltaTransform = MatrixUtils.Translate(deltaTransform, x - metrics.LeftSideBearing, y + metrics.Top);
762 currTransform = MatrixUtils.Translate(_transform, x - metrics.LeftSideBearing, y + metrics.Bottom);
763 deltaTransform = MatrixUtils.Translate(deltaTransform, x - metrics.LeftSideBearing, y + metrics.Bottom);
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);
770 currTransform = MatrixUtils.Translate(_transform, x - metrics.LeftSideBearing, y);
771 deltaTransform = MatrixUtils.Translate(deltaTransform, x - metrics.LeftSideBearing, y);
775 XmlElement currElement = currentElement;
777 if (!
string.IsNullOrEmpty(_currClipPath))
779 currentElement =
Document.CreateElement(
"g", SVGNamespace);
780 currentElement.SetAttribute(
"clip-path", _currClipPath);
781 currElement.AppendChild(currentElement);
784 XmlElement textElement =
Document.CreateElement(
"text", SVGNamespace);
788 textElement.SetAttribute(
"stroke", solid.Colour.ToCSSString(
false));
789 textElement.SetAttribute(
"stroke-opacity", solid.A.ToString(System.Globalization.CultureInfo.InvariantCulture));
793 string gradientName =
"g" + Guid.NewGuid().ToString(
"N");
795 XmlElement gradientElement = linearGradient.ToLinearGradient(
Document, gradientName);
797 deltaTransform = MatrixUtils.Invert(deltaTransform);
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) +
")");
803 this.definitions.AppendChild(gradientElement);
805 textElement.SetAttribute(
"stroke",
"url(#" + gradientName +
")");
809 string gradientName =
"g" + Guid.NewGuid().ToString(
"N");
811 XmlElement gradientElement = radialGradient.ToRadialGradient(
Document, gradientName);
813 deltaTransform = MatrixUtils.Invert(deltaTransform);
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) +
")");
819 this.definitions.AppendChild(gradientElement);
821 textElement.SetAttribute(
"stroke",
"url(#" + gradientName +
")");
824 textElement.SetAttribute(
"stroke-width", LineWidth.ToString(System.Globalization.CultureInfo.InvariantCulture));
829 textElement.SetAttribute(
"stroke-linecap",
"butt");
832 textElement.SetAttribute(
"stroke-linecap",
"round");
835 textElement.SetAttribute(
"stroke-linecap",
"square");
842 textElement.SetAttribute(
"stroke-linejoin",
"bevel");
845 textElement.SetAttribute(
"stroke-linejoin",
"round");
848 textElement.SetAttribute(
"stroke-linejoin",
"miter");
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));
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) +
")");
862 textElement.SetAttribute(
"x",
"0");
863 textElement.SetAttribute(
"y",
"0");
864 textElement.SetAttribute(
"font-size",
Font.
FontSize.ToString(System.Globalization.CultureInfo.InvariantCulture));
869 textElement.SetAttribute(
"font-weight",
"bold");
873 textElement.SetAttribute(
"font-weight",
"regular");
878 textElement.SetAttribute(
"font-style",
"italic");
882 textElement.SetAttribute(
"font-style",
"normal");
887 textElement.SetAttribute(
"font-style",
"oblique");
890 ProcessText(text, textElement);
892 if (!
string.IsNullOrEmpty(Tag))
894 textElement.SetAttribute(
"id", Tag);
897 if (!
string.IsNullOrEmpty(this.Tag) && this.linkDestinations.TryGetValue(
this.Tag, out
string destination) && !
string.IsNullOrEmpty(destination))
899 XmlElement aElement =
Document.CreateElement(
"a", SVGNamespace);
900 aElement.SetAttribute(
"href", destination);
901 currentElement.AppendChild(aElement);
902 currentElement = aElement;
905 currentElement.AppendChild(textElement);
907 currentElement = currElement;
911 PathText(text, x, y);
916 public void Transform(
double a,
double b,
double c,
double d,
double e,
double f)
918 double[,] transfMatrix =
new double[3, 3] { { a, c, e }, { b, d, f }, { 0, 0, 1 } };
919 _transform = MatrixUtils.Multiply(_transform, transfMatrix);
921 currentPath =
new SVGPathObject();
922 currentFigure =
new SVGFigure();
925 public void Translate(
double x,
double y)
927 _transform = MatrixUtils.Translate(_transform, x, y);
929 currentPath =
new SVGPathObject();
930 currentFigure =
new SVGFigure();
933 public void SetClippingPath()
935 if (currentFigure.PointCount > 0)
937 currentPath.Figures.Add(currentFigure);
940 XmlElement clipPath =
Document.CreateElement(
"clipPath", SVGNamespace);
941 string id = Guid.NewGuid().ToString(
"N");
942 clipPath.SetAttribute(
"id",
id);
944 if (!
string.IsNullOrEmpty(_currClipPath))
946 clipPath.SetAttribute(
"clip-path", _currClipPath);
949 XmlElement path =
Document.CreateElement(
"path", SVGNamespace);
950 path.SetAttribute(
"d", currentPath.Figures.Aggregate(
"", (a, b) => a + b.Data));
954 path.SetAttribute(
"stroke", solid.Colour.ToCSSString(
false));
955 path.SetAttribute(
"stroke-opacity", solid.A.ToString(System.Globalization.CultureInfo.InvariantCulture));
961 if (!gradients.TryGetValue(linearGradient, out gradientName))
963 gradientName =
"gradient" + (gradients.Count + 1).ToString(System.Globalization.CultureInfo.InvariantCulture);
965 XmlElement gradientElement = linearGradient.ToLinearGradient(
Document, gradientName);
966 this.definitions.AppendChild(gradientElement);
968 gradients.Add(linearGradient, gradientName);
971 path.SetAttribute(
"stroke",
"url(#" + gradientName +
")");
977 if (!gradients.TryGetValue(radialGradient, out gradientName))
979 gradientName =
"gradient" + (gradients.Count + 1).ToString(System.Globalization.CultureInfo.InvariantCulture);
981 XmlElement gradientElement = radialGradient.ToRadialGradient(
Document, gradientName);
982 this.definitions.AppendChild(gradientElement);
984 gradients.Add(radialGradient, gradientName);
987 path.SetAttribute(
"stroke",
"url(#" + gradientName +
")");
990 path.SetAttribute(
"stroke-width", LineWidth.ToString(System.Globalization.CultureInfo.InvariantCulture));
995 path.SetAttribute(
"stroke-linecap",
"butt");
998 path.SetAttribute(
"stroke-linecap",
"round");
1001 path.SetAttribute(
"stroke-linecap",
"square");
1008 path.SetAttribute(
"stroke-linejoin",
"bevel");
1011 path.SetAttribute(
"stroke-linejoin",
"round");
1014 path.SetAttribute(
"stroke-linejoin",
"miter");
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));
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) +
")");
1029 clipPath.AppendChild(path);
1031 currentElement.AppendChild(clipPath);
1033 _currClipPath =
"url(#" +
id +
")";
1035 currentPath =
new SVGPathObject();
1036 currentFigure =
new SVGFigure();
1039 public void DrawRasterImage(
int sourceX,
int sourceY,
int sourceWidth,
int sourceHeight,
double destinationX,
double destinationY,
double destinationWidth,
double destinationHeight,
RasterImage image)
1043 MoveTo(destinationX, destinationY);
1044 LineTo(destinationX + destinationWidth, destinationY);
1045 LineTo(destinationX + destinationWidth, destinationY + destinationHeight);
1046 LineTo(destinationX, destinationY + destinationHeight);
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;
1055 double scaleX = destinationWidth / sourceRectWidth;
1056 double scaleY = destinationHeight / sourceRectHeight;
1058 double translationX = destinationX / scaleX - sourceRectX;
1059 double translationY = destinationY / scaleY - sourceRectY;
1061 Scale(scaleX, scaleY);
1062 Translate(translationX, translationY);
1064 XmlElement currElement = currentElement;
1066 if (!
string.IsNullOrEmpty(_currClipPath))
1068 currentElement =
Document.CreateElement(
"g", SVGNamespace);
1069 currentElement.SetAttribute(
"clip-path", _currClipPath);
1070 currElement.AppendChild(currentElement);
1073 XmlElement img =
Document.CreateElement(
"image", SVGNamespace);
1074 img.SetAttribute(
"x",
"0");
1075 img.SetAttribute(
"y",
"0");
1077 img.SetAttribute(
"width",
"1");
1078 img.SetAttribute(
"height",
"1");
1080 img.SetAttribute(
"preserveAspectRatio",
"none");
1084 img.SetAttribute(
"image-rendering",
"optimizeQuality");
1088 img.SetAttribute(
"image-rendering",
"pixelated");
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) +
")");
1095 if (!
string.IsNullOrEmpty(Tag))
1097 img.SetAttribute(
"id", Tag);
1100 img.SetAttribute(
"href",
"http://www.w3.org/1999/xlink",
"data:image/png;base64," + Convert.ToBase64String(image.
PNGStream.ToArray()));
1102 if (!
string.IsNullOrEmpty(this.Tag) && this.linkDestinations.TryGetValue(
this.Tag, out
string destination) && !
string.IsNullOrEmpty(destination))
1104 XmlElement aElement =
Document.CreateElement(
"a", SVGNamespace);
1105 aElement.SetAttribute(
"href", destination);
1106 currentElement.AppendChild(aElement);
1107 currentElement = aElement;
1110 currentElement.AppendChild(img);
1112 currentElement = currElement;
1117 private void ProcessText(
string text, XmlNode parent)
1121 List<(string,
Point)> tSpans =
new List<(
string,
Point)>();
1123 StringBuilder currentRun =
new StringBuilder();
1126 Point currentGlyphPlacementDelta =
new Point();
1127 Point currentGlyphAdvanceDelta =
new Point();
1131 for (
int i = 0; i < text.Length; i++)
1133 if (i < text.Length - 1)
1135 currentGlyphPlacementDelta = nextGlyphPlacementDelta;
1136 currentGlyphAdvanceDelta = nextGlyphAdvanceDelta;
1137 nextGlyphAdvanceDelta =
new Point();
1138 nextGlyphPlacementDelta =
new Point();
1142 if (kerning !=
null)
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);
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);
1152 if (currentGlyphPlacementDelta.
X != 0 || currentGlyphPlacementDelta.
Y != 0 || currentGlyphAdvanceDelta.
X != 0 || currentGlyphAdvanceDelta.
Y != 0)
1154 if (currentRun.Length > 0)
1156 tSpans.Add((currentRun.ToString(), currentKerning));
1158 tSpans.Add((text[i].ToString(),
new Point(currentGlyphPlacementDelta.
X *
Font.
FontSize / 1000, currentGlyphPlacementDelta.
Y *
Font.
FontSize / 1000)));
1161 currentKerning =
new Point((currentGlyphAdvanceDelta.
X - currentGlyphPlacementDelta.
X) *
Font.
FontSize / 1000, (currentGlyphAdvanceDelta.
Y - currentGlyphPlacementDelta.
Y) *
Font.
FontSize / 1000);
1165 tSpans.Add((text[i].ToString(),
new Point(currentGlyphPlacementDelta.
X *
Font.
FontSize / 1000 + currentKerning.
X, currentGlyphPlacementDelta.
Y *
Font.
FontSize / 1000 + currentKerning.
Y)));
1168 currentKerning =
new Point((currentGlyphAdvanceDelta.
X - currentGlyphPlacementDelta.
X) *
Font.
FontSize / 1000, (currentGlyphAdvanceDelta.
Y - currentGlyphPlacementDelta.
Y) *
Font.
FontSize / 1000);
1173 currentRun.Append(text[i]);
1177 if (currentRun.Length > 0)
1179 tSpans.Add((currentRun.ToString(), currentKerning));
1182 for (
int i = 0; i < tSpans.Count; i++)
1184 XmlElement tspanElement =
Document.CreateElement(
"tspan", SVGNamespace);
1185 tspanElement.InnerText = tSpans[i].Item1.Replace(
" ",
"\u00A0");
1187 if (tSpans[i].Item2.X != 0)
1189 tspanElement.SetAttribute(
"dx", tSpans[i].Item2.X.ToString(System.Globalization.CultureInfo.InvariantCulture));
1192 if (tSpans[i].Item2.Y != 0)
1194 tspanElement.SetAttribute(
"dy", tSpans[i].Item2.Y.ToString(System.Globalization.CultureInfo.InvariantCulture));
1197 parent.AppendChild(tspanElement);
1203 parent.InnerText = text.Replace(
" ",
"\u00A0");
1219 bool rasterisationNeeded =
false;
1220 bool justDraw =
false;
1224 rasterisationNeeded =
true;
1232 rasterisationNeeded =
true;
1236 rasterisationNeeded =
false;
1241 rasterisationNeeded =
false;
1247 rasterisationNeeded =
false;
1250 XmlElement currElement = currentElement;
1252 if (!
string.IsNullOrEmpty(_currClipPath))
1254 currentElement =
Document.CreateElement(
"g", SVGNamespace);
1255 currentElement.SetAttribute(
"clip-path", _currClipPath);
1256 currElement.AppendChild(currentElement);
1259 XmlElement currentElement2 = currentElement;
1261 string filterGuid = Guid.NewGuid().ToString(
"N");
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);
1286 currentElement = maskElement;
1288 double[,] currTransform = _transform;
1289 _transform = MatrixUtils.Identity;
1291 mask.Mask.CopyToIGraphicsContext(
this);
1293 _transform = currTransform;
1295 currentElement =
Document.CreateElement(
"g", SVGNamespace);
1296 currentElement.SetAttribute(
"mask",
"url(#" + filterGuid +
")");
1297 currentElement2.AppendChild(currentElement);
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;
1307 _transform = currTransform;
1309 currentElement = currElement;
1313 rasterisationNeeded =
false;
1315 XmlElement currElement = currentElement;
1317 if (!
string.IsNullOrEmpty(_currClipPath))
1319 currentElement =
Document.CreateElement(
"g", SVGNamespace);
1320 currentElement.SetAttribute(
"clip-path", _currClipPath);
1321 currElement.AppendChild(currentElement);
1338 string filterGuid = Guid.NewGuid().ToString(
"N");
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);
1350 XmlElement feElement =
Document.CreateElement(
"feGaussianBlur", SVGNamespace);
1351 feElement.SetAttribute(
"stdDeviation", gauss.StandardDeviation.ToString(System.Globalization.CultureInfo.InvariantCulture));
1352 filterElement.AppendChild(feElement);
1354 XmlElement currentElement2 = currentElement;
1356 currentElement =
Document.CreateElement(
"g", SVGNamespace);
1357 currentElement.SetAttribute(
"filter",
"url(#" + filterGuid +
")");
1358 currentElement2.AppendChild(currentElement);
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;
1368 currentElement = currElement;
1372 rasterisationNeeded =
false;
1374 XmlElement currElement = currentElement;
1376 if (!
string.IsNullOrEmpty(_currClipPath))
1378 currentElement =
Document.CreateElement(
"g", SVGNamespace);
1379 currentElement.SetAttribute(
"clip-path", _currClipPath);
1380 currElement.AppendChild(currentElement);
1397 string filterGuid = Guid.NewGuid().ToString(
"N");
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);
1409 XmlElement feElement =
Document.CreateElement(
"feColorMatrix", SVGNamespace);
1410 feElement.SetAttribute(
"type",
"matrix");
1412 StringBuilder matrix =
new StringBuilder();
1414 for (
int i = 0; i < 4; i++)
1416 for (
int j = 0; j < 5; j++)
1418 matrix.Append(cmf.ColourMatrix[i, j].ToString(System.Globalization.CultureInfo.InvariantCulture));
1419 if (i != 3 || j != 4)
1426 feElement.SetAttribute(
"values", matrix.ToString());
1427 filterElement.AppendChild(feElement);
1429 XmlElement currentElement2 = currentElement;
1431 currentElement =
Document.CreateElement(
"g", SVGNamespace);
1432 currentElement.SetAttribute(
"filter",
"url(#" + filterGuid +
")");
1433 currentElement2.AppendChild(currentElement);
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;
1443 currentElement = currElement;
1447 bool allSupported =
true;
1449 foreach (
IFilter filter2
in comp.Filters)
1453 allSupported =
false;
1460 rasterisationNeeded =
false;
1463 XmlElement currElement = currentElement;
1465 if (!
string.IsNullOrEmpty(_currClipPath))
1467 currentElement =
Document.CreateElement(
"g", SVGNamespace);
1468 currentElement.SetAttribute(
"clip-path", _currClipPath);
1469 currElement.AppendChild(currentElement);
1486 string filterGuid = Guid.NewGuid().ToString(
"N");
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);
1498 foreach (
IFilter filter2
in comp.Filters)
1502 XmlElement feElement =
Document.CreateElement(
"feGaussianBlur", SVGNamespace);
1503 feElement.SetAttribute(
"stdDeviation", gauss2.StandardDeviation.ToString(System.Globalization.CultureInfo.InvariantCulture));
1504 filterElement.AppendChild(feElement);
1508 XmlElement feElement =
Document.CreateElement(
"feColorMatrix", SVGNamespace);
1509 feElement.SetAttribute(
"type",
"matrix");
1511 StringBuilder matrix =
new StringBuilder();
1513 for (
int i = 0; i < 4; i++)
1515 for (
int j = 0; j < 5; j++)
1517 matrix.Append(cmf2.ColourMatrix[i, j].ToString(System.Globalization.CultureInfo.InvariantCulture));
1518 if (i != 3 || j != 4)
1525 feElement.SetAttribute(
"values", matrix.ToString());
1526 filterElement.AppendChild(feElement);
1530 XmlElement currentElement2 = currentElement;
1532 currentElement =
Document.CreateElement(
"g", SVGNamespace);
1533 currentElement.SetAttribute(
"filter",
"url(#" + filterGuid +
")");
1534 currentElement2.AppendChild(currentElement);
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;
1544 currentElement = currElement;
1551 if (rasterisationNeeded)
1553 double scale = FilterOption.RasterisationResolution;
1561 if (!FilterOption.RasterisationResolutionRelative)
1572 filtered = locInvFilter.Filter(rasterised, scale);
1576 filtered = filterWithLoc.Filter(rasterised, bounds, scale);
1579 if (filtered !=
null)
1581 rasterised.Dispose();
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"".");
1621 using (FileStream sr =
new FileStream(fileName, FileMode.Create))
1623 page.SaveAsSVG(sr, textOption, linkDestinations, filterOption);
1671 RasteriseIfNecessary,
1676 NeverRasteriseAndIgnore,
1681 NeverRasteriseAndSkip,
1738 if (linkDestinations ==
null)
1740 linkDestinations =
new Dictionary<string, string>();
1743 if (filterOption ==
null)
1748 bool textToPaths = textOption ==
TextOptions.ConvertIntoPaths;
1750 SVGContext ctx =
new SVGContext(page.
Width, page.
Height, textToPaths, textOption, linkDestinations, filterOption);
1755 ctx.SetFillStyle((0, 0, 0, 1));
1759 if (!textToPaths && textOption !=
TextOptions.DoNotEmbed)
1761 bool subsetFonts = textOption ==
TextOptions.SubsetFonts;
1763 StringBuilder cssFonts =
new StringBuilder();
1765 Dictionary<string, string> newFontFamilies =
new Dictionary<string, string>();
1767 foreach (KeyValuePair<string, FontFamily> kvp
in ctx.UsedFontFamilies)
1773 newFontFamilies[kvp.Key] = kvp.Value.TrueTypeFile.GetFontFamilyName() +
"-" + Guid.NewGuid().ToString();
1774 subsettedFont = kvp.Value.TrueTypeFile.SubsetFont(
new string(ctx.UsedChars[kvp.Key].ToArray()));
1778 newFontFamilies[kvp.Key] = kvp.Value.TrueTypeFile.GetFontName();
1779 subsettedFont = kvp.Value.TrueTypeFile;
1784 using (MemoryStream fontStream =
new MemoryStream((
int)subsettedFont.
FontStream.Length))
1786 subsettedFont.
FontStream.Seek(0, SeekOrigin.Begin);
1789 fontBytes = fontStream.ToArray();
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");
1798 XmlElement style = ctx.Document.CreateElement(
"style", SVGContext.SVGNamespace);
1799 style.InnerText = cssFonts.ToString();
1801 XmlNode svgElement = ctx.Document.GetElementsByTagName(
"svg")[0];
1803 svgElement.InsertBefore(style, svgElement.FirstChild);
1805 foreach (XmlNode text
in ctx.Document.GetElementsByTagName(
"text"))
1807 string fontFamily = text.Attributes[
"font-family"].Value;
1809 string fallbackFontFamily =
"";
1814 case "Helvetica-Bold":
1815 case "Helvetica-Oblique":
1816 case "Helvetica-BoldOblique":
1817 fallbackFontFamily =
"sans-serif";
1822 case "Times-Italic":
1823 case "Times-BoldItalic":
1824 fallbackFontFamily =
"serif";
1828 case "Courier-Bold":
1829 case "Courier-Oblique":
1830 case "Courier-BoldOblique":
1831 fallbackFontFamily =
"monospace";
1835 if (ctx.UsedFontFamilies[fontFamily].TrueTypeFile.IsFixedPitch())
1837 fallbackFontFamily =
"monospace";
1839 else if (ctx.UsedFontFamilies[fontFamily].TrueTypeFile.IsScript() || ctx.UsedFontFamilies[fontFamily].TrueTypeFile.IsSerif())
1841 fallbackFontFamily =
"serif";
1845 fallbackFontFamily =
"sans-serif";
1851 if (!
string.IsNullOrEmpty(fallbackFontFamily))
1853 ((XmlElement)text).SetAttribute(
"font-family", newFontFamilies[fontFamily] +
", " + fallbackFontFamily);
1857 ((XmlElement)text).SetAttribute(
"font-family", newFontFamilies[fontFamily]);
1862 else if (!textToPaths && textOption ==
TextOptions.DoNotEmbed)
1864 foreach (XmlNode text
in ctx.Document.GetElementsByTagName(
"text"))
1866 string fontFamily = text.Attributes[
"font-family"].Value;
1868 string newFontFamily = ctx.UsedFontFamilies[fontFamily].TrueTypeFile.GetFontFamilyName();
1873 case "Helvetica-Bold":
1874 case "Helvetica-Oblique":
1875 case "Helvetica-BoldOblique":
1876 newFontFamily = newFontFamily +
", sans-serif";
1881 case "Times-Italic":
1882 case "Times-BoldItalic":
1883 newFontFamily = newFontFamily +
", serif";
1887 case "Courier-Bold":
1888 case "Courier-Oblique":
1889 case "Courier-BoldOblique":
1890 newFontFamily = newFontFamily +
", monospace";
1894 if (ctx.UsedFontFamilies[fontFamily].TrueTypeFile.IsFixedPitch())
1896 newFontFamily = newFontFamily +
", monospace";
1898 else if (ctx.UsedFontFamilies[fontFamily].TrueTypeFile.IsScript())
1900 newFontFamily = newFontFamily +
", cursive";
1902 else if (ctx.UsedFontFamilies[fontFamily].TrueTypeFile.IsSerif())
1904 newFontFamily = newFontFamily +
", serif";
1908 newFontFamily = newFontFamily +
", sans-serif";
1913 ((XmlElement)text).SetAttribute(
"font-family", newFontFamily);
1917 ctx.Document.DocumentElement.SetAttribute(
"style",
"font-synthesis: none;");
1919 WriteXMLToStream(ctx.Document.DocumentElement, stream);
1924 internal static XmlElement ToLinearGradient(
this LinearGradientBrush brush, XmlDocument document,
string gradientId)
1926 XmlElement gradient = document.CreateElement(
"linearGradient", SVGContext.SVGNamespace);
1928 gradient.SetAttribute(
"id", gradientId);
1930 gradient.SetAttribute(
"gradientUnits",
"userSpaceOnUse");
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));
1939 XmlElement gradientStop = document.CreateElement(
"stop", SVGContext.SVGNamespace);
1941 gradientStop.SetAttribute(
"offset", stop.
Offset.ToString(System.Globalization.CultureInfo.InvariantCulture));
1943 gradientStop.SetAttribute(
"stop-opacity", stop.
Colour.
A.ToString(System.Globalization.CultureInfo.InvariantCulture));
1945 gradient.AppendChild(gradientStop);
1951 internal static XmlElement ToRadialGradient(
this RadialGradientBrush brush, XmlDocument document,
string gradientId)
1953 XmlElement gradient = document.CreateElement(
"radialGradient", SVGContext.SVGNamespace);
1955 gradient.SetAttribute(
"id", gradientId);
1957 gradient.SetAttribute(
"gradientUnits",
"userSpaceOnUse");
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));
1965 foreach (GradientStop stop
in brush.GradientStops)
1967 XmlElement gradientStop = document.CreateElement(
"stop", SVGContext.SVGNamespace);
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));
1973 gradient.AppendChild(gradientStop);
1980 private static void WriteStartElement(XmlWriter writer, XmlElement e)
1982 writer.WriteStartElement(e.Prefix, e.LocalName, e.NamespaceURI);
1984 foreach (XmlAttribute a
in e.Attributes)
1986 writer.WriteAttributeString(a.Prefix, a.LocalName, a.NamespaceURI, a.Value);
1990 private static void WriteElement(XmlWriter writer, XmlElement e)
1992 if (e.Name ==
"text")
1994 XmlWriterSettings settings =
new XmlWriterSettings();
1995 settings.Indent =
false;
1996 settings.OmitXmlDeclaration =
true;
1997 settings.ConformanceLevel = ConformanceLevel.Fragment;
1998 settings.NamespaceHandling = NamespaceHandling.OmitDuplicates;
2000 WriteStartElement(writer, e);
2002 StringBuilder sb =
new StringBuilder();
2004 using (XmlWriter newWriter = XmlWriter.Create(sb, settings))
2006 foreach (XmlNode n
in e.ChildNodes)
2008 n.WriteTo(newWriter);
2012 writer.WriteRaw(sb.ToString().Replace(
" xmlns=\"http://www.w3.org/2000/svg\">",
">"));
2014 writer.WriteEndElement();
2018 WriteStartElement(writer, e);
2019 foreach (XmlNode n
in e.ChildNodes)
2021 if (n is XmlElement element)
2023 WriteElement(writer, element);
2030 writer.WriteEndElement();
2034 private static void WriteXMLToStream(XmlElement element, Stream output)
2036 XmlWriterSettings settings =
new XmlWriterSettings();
2037 settings.Indent =
true;
2039 using (XmlWriter writer = XmlWriter.Create(output, settings))
2041 WriteElement(writer, element);