VectSharp  2.2.1
A light library for C# vector graphics
SVGParser.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 ExCSS;
19 using System;
20 using System.Collections.Generic;
21 using System.IO;
22 using System.Linq;
23 using System.Text;
24 using System.Text.RegularExpressions;
25 using System.Xml;
26 using VectSharp.Filters;
27 
28 namespace VectSharp.SVG
29 {
30  /// <summary>
31  /// Contains methods to read an SVG image file.
32  /// </summary>
33  public static class Parser
34  {
35  static Parser()
36  {
38  }
39 
40  /// <summary>
41  /// A function that takes as input an image URI and a boolean value indicating whether the image should be interpolated, and returns a <see cref="Page"/> object containing the image.
42  /// By default, this is equal to <see cref="ParseSVGURI"/>, i.e. it is only able to parse SVG images. If you wish to enable the parsing of other formats, you should install the "VectSharp.MuPDFUtils" NuGet package
43  /// and enable the parser in your program by doing something like:
44  /// <code>VectSharp.SVG.Parser.ParseImageURI = VectSharp.MuPDFUtils.ImageURIParser.Parser(VectSharp.SVG.Parser.ParseSVGURI);</code>
45  /// </summary>
46  public static Func<string, bool, Page> ParseImageURI;
47 
48  /// <summary>
49  /// Parses an SVG image URI.
50  /// </summary>
51  /// <param name="uri">The image URI to parse.</param>
52  /// <param name="ignored">This value is ignored and is only needed for compatibility.</param>
53  /// <returns>A <see cref="Page"/> containing the parsed SVG image, or null.</returns>
54  public static Page ParseSVGURI(string uri, bool ignored = false)
55  {
56  if (uri.StartsWith("data:"))
57  {
58  string mimeType = uri.Substring(uri.IndexOf(":") + 1, uri.IndexOf(";") - uri.IndexOf(":") - 1);
59 
60  string type = uri.Substring(uri.IndexOf(";") + 1, uri.IndexOf(",") - uri.IndexOf(";") - 1);
61 
62  if (mimeType == "image/svg+xml")
63  {
64  int offset = uri.IndexOf(",") + 1;
65 
66  string data;
67 
68  switch (type)
69  {
70  case "base64":
71  data = Encoding.UTF8.GetString(Convert.FromBase64String(uri.Substring(offset)));
72  break;
73  case "":
74  case "charset=utf-8":
75  case "utf-8":
76  case "utf8":
77  data = System.Web.HttpUtility.UrlDecode(uri.Substring(offset));
78  break;
79  case "charset=ascii":
80  case "ascii":
81  data = System.Web.HttpUtility.UrlDecode(uri.Substring(offset));
82  break;
83  default:
84  throw new InvalidDataException("Unknown data stream type!");
85  }
86 
87  return FromString(data);
88  }
89  else
90  {
91  return null;
92  }
93  }
94 
95  return null;
96  }
97 
98  /// <summary>
99  /// Parses SVG source into a <see cref="Page"/> containing the image represented by the code.
100  /// </summary>
101  /// <param name="svgSource">The SVG source code.</param>
102  /// <returns>A <see cref="Page"/> containing the image represented by the <paramref name="svgSource"/>.</returns>
103  public static Page FromString(string svgSource)
104  {
105  XmlDocument svgDoc = new XmlDocument();
106  svgDoc.LoadXml(svgSource);
107 
108  Dictionary<string, FontFamily> embeddedFonts = new Dictionary<string, FontFamily>();
109 
110  StylesheetParser parser = new StylesheetParser();
111 
112  List<Stylesheet> styleSheets = new List<Stylesheet>();
113 
114  foreach (XmlNode styleNode in svgDoc.GetElementsByTagName("style"))
115  {
116  foreach (KeyValuePair<string, FontFamily> fnt in GetEmbeddedFonts(styleNode.InnerText))
117  {
118  embeddedFonts.Add(fnt.Key, fnt.Value);
119  }
120 
121  try
122  {
123  Stylesheet sheet = parser.Parse(styleNode.InnerText);
124  styleSheets.Add(sheet);
125  }
126  catch { }
127  }
128 
129  Dictionary<string, Brush> gradients = new Dictionary<string, Brush>();
130  Dictionary<string, IFilter> filters = new Dictionary<string, IFilter>();
131  Dictionary<string, XmlNode> masks = new Dictionary<string, XmlNode>();
132 
133  foreach (XmlNode definitionsNode in svgDoc.GetElementsByTagName("defs"))
134  {
135  foreach (KeyValuePair<string, Brush> fnt in GetGradients(definitionsNode, styleSheets))
136  {
137  gradients.Add(fnt.Key, fnt.Value);
138  }
139 
140  foreach (KeyValuePair<string, IFilter> filt in GetFilters(definitionsNode, styleSheets))
141  {
142  filters.Add(filt.Key, filt.Value);
143  }
144 
145  foreach (KeyValuePair<string, XmlNode> mask in GetMasks(definitionsNode, styleSheets))
146  {
147  masks.Add(mask.Key, mask.Value);
148  }
149  }
150 
151  Graphics gpr = new Graphics();
152 
153  Size pageSize = InterpretSVGObject(svgDoc.GetElementsByTagName("svg")[0], gpr, new PresentationAttributes() { EmbeddedFonts = embeddedFonts }, styleSheets, gradients, filters, masks);
154 
155  Page pg = new Page(pageSize.Width, pageSize.Height);
156 
157  pg.Graphics = gpr;
158 
159  return pg;
160  }
161 
162  /// <summary>
163  /// Parses an SVG image file into a <see cref="Page"/> containing the image.
164  /// </summary>
165  /// <param name="fileName">The path to the SVG image file.</param>
166  /// <returns>A <see cref="Page"/> containing the image represented by the file.</returns>
167  public static Page FromFile(string fileName)
168  {
169  return FromString(File.ReadAllText(fileName));
170  }
171 
172  /// <summary>
173  /// Parses an stream containing SVG source code into a <see cref="Page"/> containing the image represented by the code.
174  /// </summary>
175  /// <param name="svgSourceStream">The stream containing SVG source code.</param>
176  /// <returns>A <see cref="Page"/> containing the image represented by the <paramref name="svgSourceStream"/>.</returns>
177  public static Page FromStream(Stream svgSourceStream)
178  {
179  using (StreamReader sr = new StreamReader(svgSourceStream))
180  {
181  return FromString(sr.ReadToEnd());
182  }
183  }
184 
185  private static Size InterpretSVGObject(XmlNode svgObject, Graphics gpr, PresentationAttributes attributes, IEnumerable<Stylesheet> styleSheets, Dictionary<string, Brush> gradients, Dictionary<string, IFilter> filters, Dictionary<string, XmlNode> masks)
186  {
187  double[] viewBox = ParseListOfDoubles(svgObject.Attributes?["viewBox"]?.Value);
188 
189  double width, height, x, y;
190 
191  string widthAttribute = svgObject.Attributes?["width"]?.Value?.Replace("px", "");
192 
193  if (!double.TryParse(widthAttribute, System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out width)) { width = double.NaN; }
194 
195  string heightAttribute = svgObject.Attributes?["height"]?.Value?.Replace("px", "");
196  if (!double.TryParse(heightAttribute, System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out height)) { height = double.NaN; }
197 
198  string xAttribute = svgObject.Attributes?["x"]?.Value;
199  double.TryParse(xAttribute, System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out x);
200 
201  string yAttribute = svgObject.Attributes?["y"]?.Value;
202  double.TryParse(yAttribute, System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out y);
203 
204  double scaleX = 1;
205  double scaleY = 1;
206 
207  double postTranslateX = 0;
208  double postTranslateY = 0;
209 
210  if (viewBox != null)
211  {
212  if (!double.IsNaN(width) && !double.IsNaN(height))
213  {
214  scaleX = width / viewBox[2];
215  scaleY = height / viewBox[3];
216  }
217  else if (!double.IsNaN(width) && double.IsNaN(height))
218  {
219  scaleX = width / viewBox[2];
220  scaleY = scaleX;
221  height = scaleY * viewBox[3];
222  }
223  else if (double.IsNaN(width) && !double.IsNaN(height))
224  {
225  scaleY = height / viewBox[3];
226  scaleX = scaleY;
227  width = scaleX * viewBox[2];
228  }
229  else if (double.IsNaN(width) && double.IsNaN(height))
230  {
231  width = viewBox[2];
232  height = viewBox[3];
233  }
234 
235  postTranslateX = -viewBox[0];
236  postTranslateY = -viewBox[1];
237  }
238  else
239  {
240  viewBox = new double[4];
241 
242  if (!double.IsNaN(width))
243  {
244  viewBox[2] = width;
245  }
246 
247  if (!double.IsNaN(height))
248  {
249  viewBox[3] = height;
250  }
251  }
252 
253  double diagonal = Math.Sqrt(viewBox[2] * viewBox[2] + viewBox[3] * viewBox[3]) / Math.Sqrt(2);
254 
255  Size tbrSize = new Size(width, height);
256 
257  gpr.Save();
258  gpr.Translate(x, y);
259  gpr.Scale(scaleX, scaleY);
260  gpr.Translate(postTranslateX, postTranslateY);
261 
262  attributes = InterpretPresentationAttributes(svgObject, attributes, viewBox[2], viewBox[3], diagonal, gpr, styleSheets, gradients);
263 
264  foreach (KeyValuePair<string, XmlNode> mask in masks)
265  {
266  Graphics maskGpr = new Graphics();
267  InterpretGObject(mask.Value, maskGpr, viewBox[2], viewBox[3], diagonal, attributes, styleSheets, gradients, filters);
268 
269  filters.Add(mask.Key, new MaskFilter(maskGpr));
270  }
271 
272  InterpretSVGChildren(svgObject, gpr, attributes, viewBox[2], viewBox[3], diagonal, styleSheets, gradients, filters);
273 
274  gpr.Restore();
275 
276  return tbrSize;
277  }
278 
279  private static void InterpretSVGChildren(XmlNode svgObject, Graphics gpr, PresentationAttributes attributes, double width, double height, double diagonal, IEnumerable<Stylesheet> styleSheets, Dictionary<string, Brush> gradients, Dictionary<string, IFilter> filters)
280  {
281  foreach (XmlNode child in svgObject.ChildNodes)
282  {
283  InterpretSVGElement(child, gpr, attributes, width, height, diagonal, styleSheets, gradients, filters);
284  }
285  }
286 
287  private static void InterpretSVGElement(XmlNode currObject, Graphics gpr, PresentationAttributes attributes, double width, double height, double diagonal, IEnumerable<Stylesheet> styleSheets, Dictionary<string, Brush> gradients, Dictionary<string, IFilter> filters)
288  {
289  if (currObject.NodeType == XmlNodeType.EntityReference)
290  {
291  InterpretSVGChildren(currObject, gpr, attributes, width, height, diagonal, styleSheets, gradients, filters);
292  }
293  else if (currObject.Name.Equals("svg", StringComparison.OrdinalIgnoreCase))
294  {
295  InterpretSVGObject(currObject, gpr, attributes, styleSheets, gradients, filters, new Dictionary<string, XmlNode>());
296  }
297  else if (currObject.Name.Equals("line", StringComparison.OrdinalIgnoreCase))
298  {
299  InterpretLineObject(currObject, gpr, width, height, diagonal, attributes, styleSheets, gradients);
300  }
301  else if (currObject.Name.Equals("circle", StringComparison.OrdinalIgnoreCase))
302  {
303  InterpretCircleObject(currObject, gpr, width, height, diagonal, attributes, styleSheets, gradients);
304  }
305  else if (currObject.Name.Equals("ellipse", StringComparison.OrdinalIgnoreCase))
306  {
307  InterpretEllipseObject(currObject, gpr, width, height, diagonal, attributes, styleSheets, gradients);
308  }
309  else if (currObject.Name.Equals("path", StringComparison.OrdinalIgnoreCase))
310  {
311  InterpretPathObject(currObject, gpr, width, height, diagonal, attributes, styleSheets, gradients);
312  }
313  else if (currObject.Name.Equals("polyline", StringComparison.OrdinalIgnoreCase))
314  {
315  InterpretPolyLineObject(currObject, false, gpr, width, height, diagonal, attributes, styleSheets, gradients);
316  }
317  else if (currObject.Name.Equals("polygon", StringComparison.OrdinalIgnoreCase))
318  {
319  InterpretPolyLineObject(currObject, true, gpr, width, height, diagonal, attributes, styleSheets, gradients);
320  }
321  else if (currObject.Name.Equals("rect", StringComparison.OrdinalIgnoreCase))
322  {
323  InterpretRectObject(currObject, gpr, width, height, diagonal, attributes, styleSheets, gradients);
324  }
325  else if (currObject.Name.Equals("use", StringComparison.OrdinalIgnoreCase))
326  {
327  InterpretUseObject(currObject, gpr, width, height, diagonal, attributes, styleSheets, gradients, filters);
328  }
329  else if (currObject.Name.Equals("g", StringComparison.OrdinalIgnoreCase) || currObject.Name.Equals("symbol", StringComparison.OrdinalIgnoreCase))
330  {
331  InterpretGObject(currObject, gpr, width, height, diagonal, attributes, styleSheets, gradients, filters);
332  }
333  else if (currObject.Name.Equals("text", StringComparison.OrdinalIgnoreCase))
334  {
335  double x = 0;
336  double y = 0;
337 
338  InterpretTextObject(currObject, gpr, width, height, diagonal, attributes, styleSheets, gradients, ref x, ref y);
339  }
340  else if (currObject.Name.Equals("image", StringComparison.OrdinalIgnoreCase))
341  {
342  InterpretImageObject(currObject, gpr, width, height, diagonal, attributes, styleSheets, gradients);
343  }
344  }
345 
346  private static void InterpretImageObject(XmlNode currObject, Graphics gpr, double width, double height, double diagonal, PresentationAttributes attributes, IEnumerable<Stylesheet> styleSheets, Dictionary<string, Brush> gradients)
347  {
348  PresentationAttributes currAttributes = InterpretPresentationAttributes(currObject, attributes, width, height, diagonal, gpr, styleSheets, gradients);
349 
350  double x = ParseLengthOrPercentage(currObject.Attributes?["x"]?.Value, width, currAttributes.X);
351  double y = ParseLengthOrPercentage(currObject.Attributes?["y"]?.Value, height, currAttributes.Y);
352 
353  double w = ParseLengthOrPercentage(currObject.Attributes?["width"]?.Value, width, currAttributes.Width);
354  double h = ParseLengthOrPercentage(currObject.Attributes?["height"]?.Value, height, currAttributes.Height);
355 
356  bool interpolate = !(currObject.Attributes?["image-rendering"]?.Value == "pixelated" || currObject.Attributes?["image-rendering"]?.Value == "optimizeSpeed");
357 
358  string href = currObject.Attributes?["href"]?.Value;
359 
360  if (string.IsNullOrEmpty(href))
361  {
362  href = currObject.Attributes?["xlink:href"]?.Value;
363  }
364 
365  bool hadClippingPath = ApplyClipPath(currObject, gpr, width, height, diagonal, attributes, styleSheets, gradients);
366 
367  if (!string.IsNullOrEmpty(href) && w > 0 && h > 0)
368  {
369  Page image = ParseImageURI(href, interpolate);
370 
371  if (image != null)
372  {
373  gpr.Save();
374 
375  double scaleX = w / image.Width;
376  double scaleY = h / image.Height;
377 
378  gpr.Scale(scaleX, scaleY);
379 
380  gpr.DrawGraphics(x / scaleX, y / scaleY, image.Graphics);
381 
382  gpr.Restore();
383  }
384  else
385  {
386  gpr.StrokeRectangle(x, y, w, h, Colours.Red, 0.1);
387  gpr.StrokePath(new GraphicsPath().MoveTo(x, y).LineTo(x + w, y + h).MoveTo(x + w, y).LineTo(x, y + h), Colours.Red, 0.1);
388  }
389  }
390 
391  if (hadClippingPath)
392  {
393  gpr.Restore();
394  }
395 
396  if (currAttributes.NeedsRestore)
397  {
398  gpr.Restore();
399  }
400  }
401 
402  private static void InterpretTextObject(XmlNode currObject, Graphics gpr, double width, double height, double diagonal, PresentationAttributes attributes, IEnumerable<Stylesheet> styleSheets, Dictionary<string, Brush> gradients, ref double x, ref double y, double fontSize = double.NaN, string fontFamily = null, string textAlign = null)
403  {
404  PresentationAttributes currAttributes = InterpretPresentationAttributes(currObject, attributes, width, height, diagonal, gpr, styleSheets, gradients);
405 
406  x = ParseLengthOrPercentage(currObject.Attributes?["x"]?.Value, width, x);
407  y = ParseLengthOrPercentage(currObject.Attributes?["y"]?.Value, height, y);
408 
409  double dx = ParseLengthOrPercentage(currObject.Attributes?["dx"]?.Value, width, 0);
410  double dy = ParseLengthOrPercentage(currObject.Attributes?["dy"]?.Value, height, 0);
411 
412  x += dx;
413  y += dy;
414 
415  fontFamily = currObject.Attributes?["font-family"]?.Value ?? fontFamily;
416  fontSize = ParseLengthOrPercentage(currObject.Attributes?["font-size"]?.Value, width, fontSize);
417  textAlign = currObject.Attributes?["text-align"]?.Value ?? textAlign;
418 
419  bool hadClippingPath = ApplyClipPath(currObject, gpr, width, height, diagonal, attributes, styleSheets, gradients);
420 
421  if (currObject.ChildNodes.OfType<XmlNode>().Any(a => a.NodeType != XmlNodeType.Text))
422  {
423  foreach (XmlNode child in currObject.ChildNodes)
424  {
425  InterpretTextObject(child, gpr, width, height, diagonal, currAttributes, styleSheets, gradients, ref x, ref y, fontSize, fontFamily, textAlign);
426  }
427 
428  if (hadClippingPath)
429  {
430  gpr.Restore();
431  }
432 
433  if (currAttributes.NeedsRestore)
434  {
435  gpr.Restore();
436  }
437  }
438  else
439  {
440  string text = currObject.InnerText;
441 
442  if (!double.IsNaN(fontSize) && !string.IsNullOrEmpty(text))
443  {
444  FontFamily parsedFontFamily = ParseFontFamily(fontFamily, currAttributes.EmbeddedFonts);
445  string fontWeight = currObject.Attributes?["font-weight"]?.Value;
446  string fontStyle = currObject.Attributes?["font-style"]?.Value;
447 
448  if (fontWeight != null && (fontWeight.Equals("bold", StringComparison.OrdinalIgnoreCase) || fontWeight.Equals("bolder", StringComparison.OrdinalIgnoreCase) || (int.TryParse(fontWeight, out int weight) && weight >= 500)))
449  {
450  parsedFontFamily = GetBoldFontFamily(parsedFontFamily);
451  }
452 
453  if (fontStyle != null && (fontStyle.Equals("italic", StringComparison.OrdinalIgnoreCase) || fontStyle.Equals("oblique", StringComparison.OrdinalIgnoreCase)))
454  {
455  parsedFontFamily = GetItalicFontFamily(parsedFontFamily);
456  }
457 
458  Font fnt = new Font(parsedFontFamily, fontSize);
459 
460  double endX = x;
461 
462  if (fnt.FontFamily.TrueTypeFile != null)
463  {
464  Font.DetailedFontMetrics metrics = fnt.MeasureTextAdvanced(text);
465  x += metrics.LeftSideBearing;
466 
467  if (!string.IsNullOrEmpty(textAlign) && (textAlign.Equals("right", StringComparison.OrdinalIgnoreCase) || textAlign.Equals("end", StringComparison.OrdinalIgnoreCase)))
468  {
469  x -= metrics.Width + metrics.LeftSideBearing;
470  }
471  else if (!string.IsNullOrEmpty(textAlign) && textAlign.Equals("center", StringComparison.OrdinalIgnoreCase))
472  {
473  x -= metrics.Width * 0.5;
474  }
475 
476  endX += metrics.AdvanceWidth;
477  }
478 
479  TextBaselines baseline = TextBaselines.Baseline;
480 
481  string textBaseline = currObject.Attributes?["alignment-baseline"]?.Value;
482 
483  if (textBaseline != null)
484  {
485  if (textBaseline.Equals("text-bottom", StringComparison.OrdinalIgnoreCase) || textBaseline.Equals("bottom", StringComparison.OrdinalIgnoreCase))
486  {
487  baseline = TextBaselines.Bottom;
488  }
489  if (textBaseline.Equals("middle", StringComparison.OrdinalIgnoreCase) || textBaseline.Equals("central", StringComparison.OrdinalIgnoreCase) || textBaseline.Equals("center", StringComparison.OrdinalIgnoreCase))
490  {
491  baseline = TextBaselines.Middle;
492  }
493  if (textBaseline.Equals("text-top", StringComparison.OrdinalIgnoreCase) || textBaseline.Equals("top", StringComparison.OrdinalIgnoreCase) || textBaseline.Equals("hanging", StringComparison.OrdinalIgnoreCase))
494  {
495  baseline = TextBaselines.Top;
496  }
497  }
498 
499  if (currAttributes.StrokeFirst)
500  {
501  if (currAttributes.Stroke != null)
502  {
503  Brush strokeColour = currAttributes.Stroke.MultiplyOpacity(currAttributes.Opacity * currAttributes.StrokeOpacity);
504  gpr.StrokeText(x, y, text, fnt, strokeColour, baseline, currAttributes.StrokeThickness, currAttributes.LineCap, currAttributes.LineJoin, currAttributes.LineDash);
505  }
506 
507  if (currAttributes.Fill != null)
508  {
509  Brush fillColour = currAttributes.Fill.MultiplyOpacity(currAttributes.Opacity * currAttributes.FillOpacity);
510  gpr.FillText(x, y, text, fnt, fillColour, baseline);
511  }
512  }
513  else
514  {
515  if (currAttributes.Fill != null)
516  {
517  Brush fillColour = currAttributes.Fill.MultiplyOpacity(currAttributes.Opacity * currAttributes.FillOpacity);
518  gpr.FillText(x, y, text, fnt, fillColour, baseline);
519  }
520 
521  if (currAttributes.Stroke != null)
522  {
523  Brush strokeColour = currAttributes.Stroke.MultiplyOpacity(currAttributes.Opacity * currAttributes.StrokeOpacity);
524  gpr.StrokeText(x, y, text, fnt, strokeColour, baseline, currAttributes.StrokeThickness, currAttributes.LineCap, currAttributes.LineJoin, currAttributes.LineDash);
525  }
526  }
527 
528  x = endX;
529  }
530 
531  if (hadClippingPath)
532  {
533  gpr.Restore();
534  }
535 
536  if (currAttributes.NeedsRestore)
537  {
538  gpr.Restore();
539  }
540  }
541  }
542 
543  private static FontFamily GetBoldFontFamily(FontFamily fontFamily)
544  {
545  switch (fontFamily.FileName)
546  {
547  case "Times-Roman":
548  case "Times-Bold":
549  return FontFamily.ResolveFontFamily(FontFamily.StandardFontFamilies.TimesBold);
550  case "Times-Italic":
551  case "Times-BoldItalic":
552  return FontFamily.ResolveFontFamily(FontFamily.StandardFontFamilies.TimesBoldItalic);
553  case "Helvetica":
554  case "Helvetica-Bold":
555  return FontFamily.ResolveFontFamily(FontFamily.StandardFontFamilies.HelveticaBold);
556  case "Helvetica-Oblique":
557  case "Helvetica-BoldOblique":
558  return FontFamily.ResolveFontFamily(FontFamily.StandardFontFamilies.HelveticaBoldOblique);
559  case "Courier":
560  case "Courier-Bold":
561  return FontFamily.ResolveFontFamily(FontFamily.StandardFontFamilies.CourierBold);
562  case "Courier-Oblique":
563  case "Courier-BoldOblique":
564  return FontFamily.ResolveFontFamily(FontFamily.StandardFontFamilies.CourierBoldOblique);
565  default:
566  return fontFamily;
567  }
568  }
569 
570  private static FontFamily GetItalicFontFamily(FontFamily fontFamily)
571  {
572  switch (fontFamily.FileName)
573  {
574  case "Times-Roman":
575  case "Times-Italic":
576  return FontFamily.ResolveFontFamily(FontFamily.StandardFontFamilies.TimesItalic);
577  case "Times-Bold":
578  case "Times-BoldItalic":
579  return FontFamily.ResolveFontFamily(FontFamily.StandardFontFamilies.TimesBoldItalic);
580  case "Helvetica":
581  case "Helvetica-Oblique":
582  return FontFamily.ResolveFontFamily(FontFamily.StandardFontFamilies.HelveticaOblique);
583  case "Helvetica-Bold":
584  case "Helvetica-BoldOblique":
585  return FontFamily.ResolveFontFamily(FontFamily.StandardFontFamilies.HelveticaBoldOblique);
586  case "Courier":
587  case "Courier-Oblique":
588  return FontFamily.ResolveFontFamily(FontFamily.StandardFontFamilies.CourierOblique);
589  case "Courier-Bold":
590  case "Courier-BoldOblique":
591  return FontFamily.ResolveFontFamily(FontFamily.StandardFontFamilies.CourierBoldOblique);
592  default:
593  return fontFamily;
594  }
595  }
596 
597  private static FontFamily ParseFontFamily(string fontFamily, Dictionary<string, FontFamily> embeddedFonts)
598  {
599  string[] fontFamilies = Regexes.FontFamilySeparator.Split(fontFamily);
600 
601  foreach (string fam in fontFamilies)
602  {
603  string family = fam.Trim().Trim(',', '"').Trim();
604 
605  if (embeddedFonts.TryGetValue(family, out FontFamily tbr))
606  {
607  return tbr;
608  }
609 
610  List<(string, int)> matchedFamilies = new List<(string, int)>();
611 
612  for (int i = 0; i < FontFamily.StandardFamilies.Length; i++)
613  {
614  if (family.StartsWith(FontFamily.StandardFamilies[i]))
615  {
616  matchedFamilies.Add((FontFamily.StandardFamilies[i], FontFamily.StandardFamilies[i].Length));
617  }
618  }
619 
620  if (matchedFamilies.Count > 0)
621  {
622  return FontFamily.ResolveFontFamily((from el in matchedFamilies orderby el.Item2 descending select el.Item1).First());
623  }
624  else
625  {
626  if (family.Equals("serif", StringComparison.OrdinalIgnoreCase))
627  {
628  return FontFamily.ResolveFontFamily(FontFamily.StandardFontFamilies.TimesRoman);
629  }
630  else if (family.Equals("sans-serif", StringComparison.OrdinalIgnoreCase))
631  {
632  return FontFamily.ResolveFontFamily(FontFamily.StandardFontFamilies.Helvetica);
633  }
634  else if (family.Equals("monospace", StringComparison.OrdinalIgnoreCase))
635  {
636  return FontFamily.ResolveFontFamily(FontFamily.StandardFontFamilies.Courier);
637  }
638  else if (family.Equals("cursive", StringComparison.OrdinalIgnoreCase))
639  {
640  return FontFamily.ResolveFontFamily(FontFamily.StandardFontFamilies.TimesItalic);
641  }
642  else if (family.Equals("system-ui", StringComparison.OrdinalIgnoreCase))
643  {
644  return FontFamily.ResolveFontFamily(FontFamily.StandardFontFamilies.Helvetica);
645  }
646  else if (family.Equals("ui-serif", StringComparison.OrdinalIgnoreCase))
647  {
648  return FontFamily.ResolveFontFamily(FontFamily.StandardFontFamilies.TimesRoman);
649  }
650  else if (family.Equals("ui-sans-serif", StringComparison.OrdinalIgnoreCase))
651  {
652  return FontFamily.ResolveFontFamily(FontFamily.StandardFontFamilies.Helvetica);
653  }
654  else if (family.Equals("ui-monospace", StringComparison.OrdinalIgnoreCase))
655  {
656  return FontFamily.ResolveFontFamily(FontFamily.StandardFontFamilies.Courier);
657  }
658  else if (family.Equals("StandardSymbolsPS", StringComparison.OrdinalIgnoreCase))
659  {
660  return FontFamily.ResolveFontFamily(FontFamily.StandardFontFamilies.Symbol);
661  }
662  else if (family.Equals("D050000L", StringComparison.OrdinalIgnoreCase))
663  {
664  return FontFamily.ResolveFontFamily(FontFamily.StandardFontFamilies.ZapfDingbats);
665  }
666  }
667  }
668 
669  return FontFamily.ResolveFontFamily(FontFamily.StandardFontFamilies.Helvetica);
670  }
671 
672  private static void InterpretGObject(XmlNode currObject, Graphics gpr, double width, double height, double diagonal, PresentationAttributes attributes, IEnumerable<Stylesheet> styleSheets, Dictionary<string, Brush> gradients, Dictionary<string, IFilter> filters)
673  {
674  PresentationAttributes currAttributes = InterpretPresentationAttributes(currObject, attributes, width, height, diagonal, gpr, styleSheets, gradients);
675 
676  bool hadClippingPath = ApplyClipPath(currObject, gpr, width, height, diagonal, attributes, styleSheets, gradients);
677 
678  string filter = currObject.Attributes?["filter"]?.Value ?? currObject.Attributes?["mask"]?.Value;
679 
680  if (!string.IsNullOrEmpty(filter) && filter.StartsWith("url(#"))
681  {
682  filter = filter.Substring(5, filter.Length - 6);
683  }
684 
685  if (!string.IsNullOrEmpty(filter) && filters.ContainsKey(filter))
686  {
687  Graphics filteredGraphics = new Graphics();
688 
689  InterpretSVGChildren(currObject, filteredGraphics, currAttributes, width, height, diagonal, styleSheets, gradients, filters);
690  gpr.DrawGraphics(0, 0, filteredGraphics, filters[filter]);
691  }
692  else
693  {
694  InterpretSVGChildren(currObject, gpr, currAttributes, width, height, diagonal, styleSheets, gradients, filters);
695  }
696 
697  if (hadClippingPath)
698  {
699  gpr.Restore();
700  }
701 
702  if (currAttributes.NeedsRestore)
703  {
704  gpr.Restore();
705  }
706  }
707 
708  private static void InterpretUseObject(XmlNode currObject, Graphics gpr, double width, double height, double diagonal, PresentationAttributes attributes, IEnumerable<Stylesheet> styleSheets, Dictionary<string, Brush> gradients, Dictionary<string, IFilter> filters)
709  {
710  double x, y, w, h;
711 
712  x = ParseLengthOrPercentage(currObject.Attributes?["x"]?.Value, width);
713  y = ParseLengthOrPercentage(currObject.Attributes?["y"]?.Value, height);
714  w = ParseLengthOrPercentage(currObject.Attributes?["width"]?.Value, width, double.NaN);
715  h = ParseLengthOrPercentage(currObject.Attributes?["height"]?.Value, height, double.NaN);
716 
717  string id = currObject.Attributes?["href"]?.Value ?? currObject.Attributes?["xlink:href"]?.Value;
718 
719  if (id != null && id.StartsWith("#"))
720  {
721  id = id.Substring(1);
722 
723  XmlNode element = currObject.OwnerDocument.SelectSingleNode(string.Format("//*[@id='{0}']", id));
724 
725  if (element != null)
726  {
727  XmlNode clone = element.Clone();
728 
729  currObject.AppendChild(clone);
730 
731 
732  PresentationAttributes currAttributes = InterpretPresentationAttributes(currObject, attributes, width, height, diagonal, gpr, styleSheets, gradients);
733 
734 
735  gpr.Save();
736  gpr.Translate(x, y);
737 
738  ((XmlElement)clone).SetAttribute("x", "0");
739  ((XmlElement)clone).SetAttribute("y", "0");
740 
741  if (clone.Attributes?["viewBox"] != null)
742  {
743  ((XmlElement)clone).SetAttribute("width", w.ToString(System.Globalization.CultureInfo.InvariantCulture));
744  ((XmlElement)clone).SetAttribute("height", h.ToString(System.Globalization.CultureInfo.InvariantCulture));
745  }
746 
747  InterpretSVGElement(clone, gpr, currAttributes, width, height, diagonal, styleSheets, gradients, filters);
748 
749  gpr.Restore();
750 
751  if (currAttributes.NeedsRestore)
752  {
753  gpr.Restore();
754  }
755  }
756  }
757  }
758 
759  private static bool ApplyClipPath(XmlNode currObject, Graphics gpr, double width, double height, double diagonal, PresentationAttributes attributes, IEnumerable<Stylesheet> styleSheets, Dictionary<string, Brush> gradients)
760  {
761  string id = currObject.Attributes?["clip-path"]?.Value;
762 
763  if (id != null && id.StartsWith("url(#"))
764  {
765  id = id.Substring(5);
766  id = id.Substring(0, id.Length - 1);
767 
768  XmlNode element = currObject.OwnerDocument.SelectSingleNode(string.Format("//*[@id='{0}']", id));
769 
770  if (element != null && element.ChildNodes.Count == 1 && element.ChildNodes[0].Name.Equals("path", StringComparison.OrdinalIgnoreCase))
771  {
772  bool hasParentClipPath = ApplyClipPath(element, gpr, width, height, diagonal, attributes, styleSheets, gradients);
773 
774  Graphics pathGraphics = new Graphics();
775  InterpretPathObject(element.ChildNodes[0], pathGraphics, width, height, diagonal, attributes, styleSheets, gradients);
776 
777  PathTransformerGraphicsContext ptgc = new PathTransformerGraphicsContext();
778  pathGraphics.CopyToIGraphicsContext(ptgc);
779 
780  if (!hasParentClipPath)
781  {
782  gpr.Save();
783  }
784 
785  gpr.SetClippingPath(ptgc.CurrentPath);
786 
787  return true;
788  }
789 
790  return false;
791  }
792  else
793  {
794  return false;
795  }
796  }
797 
798  private static void InterpretRectObject(XmlNode currObject, Graphics gpr, double width, double height, double diagonal, PresentationAttributes attributes, IEnumerable<Stylesheet> styleSheets, Dictionary<string, Brush> gradients)
799  {
800  double x, y, w, h, rx, ry;
801 
802  x = ParseLengthOrPercentage(currObject.Attributes?["x"]?.Value, width);
803  y = ParseLengthOrPercentage(currObject.Attributes?["y"]?.Value, height);
804  w = ParseLengthOrPercentage(currObject.Attributes?["width"]?.Value, width);
805  h = ParseLengthOrPercentage(currObject.Attributes?["height"]?.Value, height);
806  rx = ParseLengthOrPercentage(currObject.Attributes?["rx"]?.Value, width, double.NaN);
807  ry = ParseLengthOrPercentage(currObject.Attributes?["ry"]?.Value, height, double.NaN);
808 
809  if (w > 0 && h > 0)
810  {
811  if (double.IsNaN(rx) && !double.IsNaN(ry))
812  {
813  rx = ry;
814  }
815  else if (!double.IsNaN(rx) && double.IsNaN(ry))
816  {
817  ry = rx;
818  }
819 
820  if (double.IsNaN(rx))
821  {
822  rx = 0;
823  }
824 
825  if (double.IsNaN(ry))
826  {
827  ry = 0;
828  }
829 
830  rx = Math.Min(rx, w / 2);
831  ry = Math.Min(ry, h / 2);
832 
833  GraphicsPath path = new GraphicsPath();
834 
835  path.MoveTo(x + rx, y);
836  path.LineTo(x + w - rx, y);
837 
838  if (rx > 0 && ry > 0)
839  {
840  path.EllipticalArc(rx, ry, 0, false, true, new Point(x + w, y + ry));
841  }
842 
843  path.LineTo(x + w, y + h - ry);
844 
845  if (rx > 0 && ry > 0)
846  {
847  path.EllipticalArc(rx, ry, 0, false, true, new Point(x + w - rx, y + h));
848  }
849 
850  path.LineTo(x + rx, y + h);
851 
852  if (rx > 0 && ry > 0)
853  {
854  path.EllipticalArc(rx, ry, 0, false, true, new Point(x, y + h - ry));
855  }
856 
857  path.LineTo(x, y + ry);
858 
859  if (rx > 0 && ry > 0)
860  {
861  path.EllipticalArc(rx, ry, 0, false, true, new Point(x + rx, y));
862  }
863 
864  path.Close();
865 
866  PresentationAttributes currAttributes = InterpretPresentationAttributes(currObject, attributes, width, height, diagonal, gpr, styleSheets, gradients);
867 
868  bool hadClippingPath = ApplyClipPath(currObject, gpr, width, height, diagonal, attributes, styleSheets, gradients);
869 
870  if (currAttributes.StrokeFirst)
871  {
872  if (currAttributes.Stroke != null)
873  {
874  Brush strokeColour = currAttributes.Stroke.MultiplyOpacity(currAttributes.Opacity * currAttributes.StrokeOpacity);
875  gpr.StrokePath(path, strokeColour, currAttributes.StrokeThickness, currAttributes.LineCap, currAttributes.LineJoin, currAttributes.LineDash);
876  }
877 
878  if (currAttributes.Fill != null)
879  {
880  Brush fillColour = currAttributes.Fill.MultiplyOpacity(currAttributes.Opacity * currAttributes.FillOpacity);
881  gpr.FillPath(path, fillColour);
882  }
883  }
884  else
885  {
886  if (currAttributes.Fill != null)
887  {
888  Brush fillColour = currAttributes.Fill.MultiplyOpacity(currAttributes.Opacity * currAttributes.FillOpacity);
889  gpr.FillPath(path, fillColour);
890  }
891 
892  if (currAttributes.Stroke != null)
893  {
894  Brush strokeColour = currAttributes.Stroke.MultiplyOpacity(currAttributes.Opacity * currAttributes.StrokeOpacity);
895  gpr.StrokePath(path, strokeColour, currAttributes.StrokeThickness, currAttributes.LineCap, currAttributes.LineJoin, currAttributes.LineDash);
896  }
897  }
898 
899  if (hadClippingPath)
900  {
901  gpr.Restore();
902  }
903 
904  if (currAttributes.NeedsRestore)
905  {
906  gpr.Restore();
907  }
908  }
909  }
910 
911  private static void InterpretPolyLineObject(XmlNode currObject, bool isPolygon, Graphics gpr, double width, double height, double diagonal, PresentationAttributes attributes, IEnumerable<Stylesheet> styleSheets, Dictionary<string, Brush> gradients)
912  {
913  string points = currObject.Attributes?["points"]?.Value;
914 
915  if (points != null)
916  {
917  double[] coordinates = ParseListOfDoubles(points);
918 
919  GraphicsPath path = new GraphicsPath();
920 
921  for (int i = 0; i < coordinates.Length; i += 2)
922  {
923  path.LineTo(coordinates[i], coordinates[i + 1]);
924  }
925 
926  if (isPolygon)
927  {
928  path.Close();
929  }
930 
931  PresentationAttributes currAttributes = InterpretPresentationAttributes(currObject, attributes, width, height, diagonal, gpr, styleSheets, gradients);
932 
933  bool hadClippingPath = ApplyClipPath(currObject, gpr, width, height, diagonal, attributes, styleSheets, gradients);
934 
935  if (currAttributes.StrokeFirst)
936  {
937  if (currAttributes.Stroke != null)
938  {
939  Brush strokeColour = currAttributes.Stroke.MultiplyOpacity(currAttributes.Opacity * currAttributes.StrokeOpacity);
940  gpr.StrokePath(path, strokeColour, currAttributes.StrokeThickness, currAttributes.LineCap, currAttributes.LineJoin, currAttributes.LineDash);
941  }
942 
943  if (currAttributes.Fill != null)
944  {
945  Brush fillColour = currAttributes.Fill.MultiplyOpacity(currAttributes.Opacity * currAttributes.FillOpacity);
946  gpr.FillPath(path, fillColour);
947  }
948  }
949  else
950  {
951  if (currAttributes.Fill != null)
952  {
953  Brush fillColour = currAttributes.Fill.MultiplyOpacity(currAttributes.Opacity * currAttributes.FillOpacity);
954  gpr.FillPath(path, fillColour);
955  }
956 
957  if (currAttributes.Stroke != null)
958  {
959  Brush strokeColour = currAttributes.Stroke.MultiplyOpacity(currAttributes.Opacity * currAttributes.StrokeOpacity);
960  gpr.StrokePath(path, strokeColour, currAttributes.StrokeThickness, currAttributes.LineCap, currAttributes.LineJoin, currAttributes.LineDash);
961  }
962  }
963 
964  if (hadClippingPath)
965  {
966  gpr.Restore();
967  }
968 
969  if (currAttributes.NeedsRestore)
970  {
971  gpr.Restore();
972  }
973  }
974  }
975 
976  private static void InterpretPathObject(XmlNode currObject, Graphics gpr, double width, double height, double diagonal, PresentationAttributes attributes, IEnumerable<Stylesheet> styleSheets, Dictionary<string, Brush> gradients)
977  {
978  string d = currObject.Attributes?["d"]?.Value;
979 
980  if (d != null)
981  {
982  List<string> pathData = TokenisePathData(d);
983 
984  GraphicsPath path = new GraphicsPath();
985 
986  Point lastPoint = new Point();
987  Point? figureStartPoint = null;
988 
989  char lastCommand = '\0';
990  Point lastCtrlPoint = new Point();
991 
992  for (int i = 0; i < pathData.Count; i++)
993  {
994  Point delta = new Point();
995 
996  bool isAbsolute = char.IsUpper(pathData[i][0]);
997 
998  if (!isAbsolute)
999  {
1000  delta = lastPoint;
1001  }
1002 
1003  switch (pathData[i][0])
1004  {
1005  case 'M':
1006  case 'm':
1007  lastPoint = new Point(delta.X + double.Parse(pathData[i + 1], System.Globalization.CultureInfo.InvariantCulture), delta.Y + double.Parse(pathData[i + 2], System.Globalization.CultureInfo.InvariantCulture));
1008  path.MoveTo(lastPoint);
1009  figureStartPoint = lastPoint;
1010  i += 2;
1011  lastCommand = 'M';
1012  while (i < pathData.Count - 1 && !char.IsLetter(pathData[i + 1][0]))
1013  {
1014  if (!isAbsolute)
1015  {
1016  delta = lastPoint;
1017  }
1018  else
1019  {
1020  delta = new Point();
1021  }
1022 
1023  lastPoint = new Point(delta.X + double.Parse(pathData[i + 1], System.Globalization.CultureInfo.InvariantCulture), delta.Y + double.Parse(pathData[i + 2], System.Globalization.CultureInfo.InvariantCulture));
1024  path.LineTo(lastPoint);
1025 
1026  i += 2;
1027  lastCommand = 'L';
1028  }
1029  break;
1030  case 'L':
1031  case 'l':
1032  lastPoint = new Point(delta.X + double.Parse(pathData[i + 1], System.Globalization.CultureInfo.InvariantCulture), delta.Y + double.Parse(pathData[i + 2], System.Globalization.CultureInfo.InvariantCulture));
1033  path.LineTo(lastPoint);
1034  if (figureStartPoint == null)
1035  {
1036  figureStartPoint = lastPoint;
1037  }
1038  i += 2;
1039  lastCommand = 'L';
1040  while (i < pathData.Count - 1 && !char.IsLetter(pathData[i + 1][0]))
1041  {
1042  if (!isAbsolute)
1043  {
1044  delta = lastPoint;
1045  }
1046  else
1047  {
1048  delta = new Point();
1049  }
1050 
1051  lastPoint = new Point(delta.X + double.Parse(pathData[i + 1], System.Globalization.CultureInfo.InvariantCulture), delta.Y + double.Parse(pathData[i + 2], System.Globalization.CultureInfo.InvariantCulture));
1052  path.LineTo(lastPoint);
1053 
1054  i += 2;
1055  }
1056  break;
1057  case 'H':
1058  case 'h':
1059  lastPoint = new Point(delta.X + double.Parse(pathData[i + 1], System.Globalization.CultureInfo.InvariantCulture), lastPoint.Y);
1060  path.LineTo(lastPoint);
1061  if (figureStartPoint == null)
1062  {
1063  figureStartPoint = lastPoint;
1064  }
1065  i++;
1066  lastCommand = 'L';
1067  while (i < pathData.Count - 1 && !char.IsLetter(pathData[i + 1][0]))
1068  {
1069  if (!isAbsolute)
1070  {
1071  delta = lastPoint;
1072  }
1073  else
1074  {
1075  delta = new Point();
1076  }
1077 
1078  lastPoint = new Point(delta.X + double.Parse(pathData[i + 1], System.Globalization.CultureInfo.InvariantCulture), lastPoint.Y);
1079  path.LineTo(lastPoint);
1080 
1081  i++;
1082  }
1083  break;
1084  case 'V':
1085  case 'v':
1086  lastPoint = new Point(lastPoint.X, delta.Y + double.Parse(pathData[i + 1], System.Globalization.CultureInfo.InvariantCulture));
1087  path.LineTo(lastPoint);
1088  if (figureStartPoint == null)
1089  {
1090  figureStartPoint = lastPoint;
1091  }
1092  i++;
1093  lastCommand = 'L';
1094  while (i < pathData.Count - 1 && !char.IsLetter(pathData[i + 1][0]))
1095  {
1096  if (!isAbsolute)
1097  {
1098  delta = lastPoint;
1099  }
1100  else
1101  {
1102  delta = new Point();
1103  }
1104 
1105  lastPoint = new Point(lastPoint.X, delta.Y + double.Parse(pathData[i + 1], System.Globalization.CultureInfo.InvariantCulture));
1106  path.LineTo(lastPoint);
1107 
1108  i++;
1109  }
1110  break;
1111  case 'C':
1112  case 'c':
1113  {
1114  Point ctrlPoint1 = new Point(delta.X + double.Parse(pathData[i + 1], System.Globalization.CultureInfo.InvariantCulture), delta.Y + double.Parse(pathData[i + 2], System.Globalization.CultureInfo.InvariantCulture));
1115  i += 2;
1116 
1117  Point ctrlPoint2 = new Point(delta.X + double.Parse(pathData[i + 1], System.Globalization.CultureInfo.InvariantCulture), delta.Y + double.Parse(pathData[i + 2], System.Globalization.CultureInfo.InvariantCulture));
1118  i += 2;
1119 
1120  lastPoint = new Point(delta.X + double.Parse(pathData[i + 1], System.Globalization.CultureInfo.InvariantCulture), delta.Y + double.Parse(pathData[i + 2], System.Globalization.CultureInfo.InvariantCulture));
1121  i += 2;
1122 
1123  if (figureStartPoint == null)
1124  {
1125  figureStartPoint = lastPoint;
1126  }
1127 
1128  path.CubicBezierTo(ctrlPoint1, ctrlPoint2, lastPoint);
1129 
1130  lastCtrlPoint = ctrlPoint2;
1131  lastCommand = 'C';
1132 
1133  while (i < pathData.Count - 1 && !char.IsLetter(pathData[i + 1][0]))
1134  {
1135  if (!isAbsolute)
1136  {
1137  delta = lastPoint;
1138  }
1139  else
1140  {
1141  delta = new Point();
1142  }
1143 
1144  ctrlPoint1 = new Point(delta.X + double.Parse(pathData[i + 1], System.Globalization.CultureInfo.InvariantCulture), delta.Y + double.Parse(pathData[i + 2], System.Globalization.CultureInfo.InvariantCulture));
1145  i += 2;
1146 
1147  ctrlPoint2 = new Point(delta.X + double.Parse(pathData[i + 1], System.Globalization.CultureInfo.InvariantCulture), delta.Y + double.Parse(pathData[i + 2], System.Globalization.CultureInfo.InvariantCulture));
1148  i += 2;
1149 
1150  lastPoint = new Point(delta.X + double.Parse(pathData[i + 1], System.Globalization.CultureInfo.InvariantCulture), delta.Y + double.Parse(pathData[i + 2], System.Globalization.CultureInfo.InvariantCulture));
1151  i += 2;
1152 
1153  path.CubicBezierTo(ctrlPoint1, ctrlPoint2, lastPoint);
1154  }
1155  }
1156  break;
1157  case 'S':
1158  case 's':
1159  {
1160  Point ctrlPoint1;
1161 
1162  if (lastCommand == 'C')
1163  {
1164  ctrlPoint1 = new Point(2 * lastPoint.X - lastCtrlPoint.X, 2 * lastPoint.Y - lastCtrlPoint.Y);
1165  }
1166  else
1167  {
1168  ctrlPoint1 = lastPoint;
1169  }
1170 
1171  Point ctrlPoint2 = new Point(delta.X + double.Parse(pathData[i + 1], System.Globalization.CultureInfo.InvariantCulture), delta.Y + double.Parse(pathData[i + 2], System.Globalization.CultureInfo.InvariantCulture));
1172  i += 2;
1173 
1174  lastPoint = new Point(delta.X + double.Parse(pathData[i + 1], System.Globalization.CultureInfo.InvariantCulture), delta.Y + double.Parse(pathData[i + 2], System.Globalization.CultureInfo.InvariantCulture));
1175  i += 2;
1176 
1177  if (figureStartPoint == null)
1178  {
1179  figureStartPoint = lastPoint;
1180  }
1181 
1182  path.CubicBezierTo(ctrlPoint1, ctrlPoint2, lastPoint);
1183 
1184  lastCtrlPoint = ctrlPoint2;
1185  lastCommand = 'C';
1186 
1187  while (i < pathData.Count - 1 && !char.IsLetter(pathData[i + 1][0]))
1188  {
1189  if (!isAbsolute)
1190  {
1191  delta = lastPoint;
1192  }
1193  else
1194  {
1195  delta = new Point();
1196  }
1197 
1198  ctrlPoint1 = new Point(2 * lastPoint.X - lastCtrlPoint.X, 2 * lastPoint.Y - lastCtrlPoint.Y);
1199 
1200  ctrlPoint2 = new Point(delta.X + double.Parse(pathData[i + 1], System.Globalization.CultureInfo.InvariantCulture), delta.Y + double.Parse(pathData[i + 2], System.Globalization.CultureInfo.InvariantCulture));
1201  i += 2;
1202 
1203  lastPoint = new Point(delta.X + double.Parse(pathData[i + 1], System.Globalization.CultureInfo.InvariantCulture), delta.Y + double.Parse(pathData[i + 2], System.Globalization.CultureInfo.InvariantCulture));
1204  i += 2;
1205 
1206  path.CubicBezierTo(ctrlPoint1, ctrlPoint2, lastPoint);
1207 
1208  lastCtrlPoint = ctrlPoint2;
1209  }
1210  }
1211  break;
1212  case 'Q':
1213  case 'q':
1214  {
1215  Point ctrlPoint = new Point(delta.X + double.Parse(pathData[i + 1], System.Globalization.CultureInfo.InvariantCulture), delta.Y + double.Parse(pathData[i + 2], System.Globalization.CultureInfo.InvariantCulture));
1216  i += 2;
1217 
1218  Point actualCP1 = new Point(lastPoint.X + 2 * (ctrlPoint.X - lastPoint.X) / 3, lastPoint.Y + 2 * (ctrlPoint.Y - lastPoint.Y) / 3);
1219 
1220  lastPoint = new Point(delta.X + double.Parse(pathData[i + 1], System.Globalization.CultureInfo.InvariantCulture), delta.Y + double.Parse(pathData[i + 2], System.Globalization.CultureInfo.InvariantCulture));
1221  i += 2;
1222 
1223  Point actualCP2 = new Point(lastPoint.X + 2 * (ctrlPoint.X - lastPoint.X) / 3, lastPoint.Y + 2 * (ctrlPoint.Y - lastPoint.Y) / 3);
1224 
1225  if (figureStartPoint == null)
1226  {
1227  figureStartPoint = lastPoint;
1228  }
1229 
1230  path.CubicBezierTo(actualCP1, actualCP2, lastPoint);
1231 
1232  lastCtrlPoint = ctrlPoint;
1233  lastCommand = 'Q';
1234 
1235  while (i < pathData.Count - 1 && !char.IsLetter(pathData[i + 1][0]))
1236  {
1237  if (!isAbsolute)
1238  {
1239  delta = lastPoint;
1240  }
1241  else
1242  {
1243  delta = new Point();
1244  }
1245 
1246  ctrlPoint = new Point(delta.X + double.Parse(pathData[i + 1], System.Globalization.CultureInfo.InvariantCulture), delta.Y + double.Parse(pathData[i + 2], System.Globalization.CultureInfo.InvariantCulture));
1247  i += 2;
1248 
1249  actualCP1 = new Point(lastPoint.X + 2 * (ctrlPoint.X - lastPoint.X) / 3, lastPoint.Y + 2 * (ctrlPoint.Y - lastPoint.Y) / 3);
1250 
1251  lastPoint = new Point(delta.X + double.Parse(pathData[i + 1], System.Globalization.CultureInfo.InvariantCulture), delta.Y + double.Parse(pathData[i + 2], System.Globalization.CultureInfo.InvariantCulture));
1252  i += 2;
1253 
1254  actualCP2 = new Point(lastPoint.X + 2 * (ctrlPoint.X - lastPoint.X) / 3, lastPoint.Y + 2 * (ctrlPoint.Y - lastPoint.Y) / 3);
1255 
1256  path.CubicBezierTo(actualCP1, actualCP2, lastPoint);
1257  lastCtrlPoint = ctrlPoint;
1258  }
1259 
1260 
1261  }
1262  break;
1263  case 'T':
1264  case 't':
1265  {
1266  Point ctrlPoint;
1267 
1268  if (lastCommand == 'Q')
1269  {
1270  ctrlPoint = new Point(2 * lastPoint.X - lastCtrlPoint.X, 2 * lastPoint.Y - lastCtrlPoint.Y);
1271  }
1272  else
1273  {
1274  ctrlPoint = lastPoint;
1275  }
1276 
1277  Point actualCP1 = new Point(lastPoint.X + 2 * (ctrlPoint.X - lastPoint.X) / 3, lastPoint.Y + 2 * (ctrlPoint.Y - lastPoint.Y) / 3);
1278 
1279  lastPoint = new Point(delta.X + double.Parse(pathData[i + 1], System.Globalization.CultureInfo.InvariantCulture), delta.Y + double.Parse(pathData[i + 2], System.Globalization.CultureInfo.InvariantCulture));
1280  i += 2;
1281 
1282  Point actualCP2 = new Point(lastPoint.X + 2 * (ctrlPoint.X - lastPoint.X) / 3, lastPoint.Y + 2 * (ctrlPoint.Y - lastPoint.Y) / 3);
1283 
1284  if (figureStartPoint == null)
1285  {
1286  figureStartPoint = lastPoint;
1287  }
1288 
1289  path.CubicBezierTo(actualCP1, actualCP2, lastPoint);
1290  lastCtrlPoint = ctrlPoint;
1291  lastCommand = 'Q';
1292 
1293  while (i < pathData.Count - 1 && !char.IsLetter(pathData[i + 1][0]))
1294  {
1295  if (!isAbsolute)
1296  {
1297  delta = lastPoint;
1298  }
1299  else
1300  {
1301  delta = new Point();
1302  }
1303 
1304  ctrlPoint = new Point(2 * lastPoint.X - lastCtrlPoint.X, 2 * lastPoint.Y - lastCtrlPoint.Y);
1305 
1306  actualCP1 = new Point(lastPoint.X + 2 * (ctrlPoint.X - lastPoint.X) / 3, lastPoint.Y + 2 * (ctrlPoint.Y - lastPoint.Y) / 3);
1307 
1308  lastPoint = new Point(delta.X + double.Parse(pathData[i + 1], System.Globalization.CultureInfo.InvariantCulture), delta.Y + double.Parse(pathData[i + 2], System.Globalization.CultureInfo.InvariantCulture));
1309  i += 2;
1310 
1311  actualCP2 = new Point(lastPoint.X + 2 * (ctrlPoint.X - lastPoint.X) / 3, lastPoint.Y + 2 * (ctrlPoint.Y - lastPoint.Y) / 3);
1312 
1313  path.CubicBezierTo(actualCP1, actualCP2, lastPoint);
1314 
1315  lastCtrlPoint = ctrlPoint;
1316  }
1317  }
1318  break;
1319  case 'A':
1320  case 'a':
1321  {
1322  Point startPoint = lastPoint;
1323 
1324  if (figureStartPoint == null)
1325  {
1326  figureStartPoint = lastPoint;
1327  }
1328 
1329  Point radii = new Point(double.Parse(pathData[i + 1], System.Globalization.CultureInfo.InvariantCulture), double.Parse(pathData[i + 2], System.Globalization.CultureInfo.InvariantCulture));
1330  double angle = double.Parse(pathData[i + 3], System.Globalization.CultureInfo.InvariantCulture) * Math.PI / 180;
1331  bool largeArcFlag = pathData[i + 4][0] == '1';
1332  bool sweepFlag = pathData[i + 5][0] == '1';
1333 
1334  lastPoint = new Point(delta.X + double.Parse(pathData[i + 6], System.Globalization.CultureInfo.InvariantCulture), delta.Y + double.Parse(pathData[i + 7], System.Globalization.CultureInfo.InvariantCulture));
1335  i += 7;
1336 
1337  path.EllipticalArc(radii.X, radii.Y, angle, largeArcFlag, sweepFlag, lastPoint);
1338 
1339  while (i < pathData.Count - 1 && !char.IsLetter(pathData[i + 1][0]))
1340  {
1341  if (!isAbsolute)
1342  {
1343  delta = lastPoint;
1344  }
1345  else
1346  {
1347  delta = new Point();
1348  }
1349 
1350  startPoint = lastPoint;
1351  radii = new Point(double.Parse(pathData[i + 1], System.Globalization.CultureInfo.InvariantCulture), double.Parse(pathData[i + 2], System.Globalization.CultureInfo.InvariantCulture));
1352  angle = double.Parse(pathData[i + 3], System.Globalization.CultureInfo.InvariantCulture) * Math.PI / 180;
1353  largeArcFlag = pathData[i + 4][0] == '1';
1354  sweepFlag = pathData[i + 5][0] == '1';
1355 
1356  lastPoint = new Point(delta.X + double.Parse(pathData[i + 6], System.Globalization.CultureInfo.InvariantCulture), delta.Y + double.Parse(pathData[i + 7], System.Globalization.CultureInfo.InvariantCulture));
1357  i += 7;
1358 
1359  path.EllipticalArc(radii.X, radii.Y, angle, largeArcFlag, sweepFlag, lastPoint);
1360  }
1361  }
1362 
1363  break;
1364  case 'Z':
1365  case 'z':
1366  path.Close();
1367  lastPoint = figureStartPoint.Value;
1368  figureStartPoint = null;
1369  lastCommand = 'Z';
1370  break;
1371  }
1372  }
1373 
1374  PresentationAttributes currAttributes = InterpretPresentationAttributes(currObject, attributes, width, height, diagonal, gpr, styleSheets, gradients);
1375 
1376  bool hadClippingPath = ApplyClipPath(currObject, gpr, width, height, diagonal, attributes, styleSheets, gradients);
1377 
1378  if (currAttributes.StrokeFirst)
1379  {
1380  if (currAttributes.Stroke != null)
1381  {
1382  Brush strokeColour = currAttributes.Stroke.MultiplyOpacity(currAttributes.Opacity * currAttributes.StrokeOpacity);
1383  gpr.StrokePath(path, strokeColour, currAttributes.StrokeThickness, currAttributes.LineCap, currAttributes.LineJoin, currAttributes.LineDash);
1384  }
1385 
1386  if (currAttributes.Fill != null)
1387  {
1388  Brush fillColour = currAttributes.Fill.MultiplyOpacity(currAttributes.Opacity * currAttributes.FillOpacity);
1389  gpr.FillPath(path, fillColour);
1390  }
1391  }
1392  else
1393  {
1394  if (currAttributes.Fill != null)
1395  {
1396  Brush fillColour = currAttributes.Fill.MultiplyOpacity(currAttributes.Opacity * currAttributes.FillOpacity);
1397  gpr.FillPath(path, fillColour);
1398  }
1399 
1400  if (currAttributes.Stroke != null)
1401  {
1402  Brush strokeColour = currAttributes.Stroke.MultiplyOpacity(currAttributes.Opacity * currAttributes.StrokeOpacity);
1403  gpr.StrokePath(path, strokeColour, currAttributes.StrokeThickness, currAttributes.LineCap, currAttributes.LineJoin, currAttributes.LineDash);
1404  }
1405  }
1406 
1407  if (hadClippingPath)
1408  {
1409  gpr.Restore();
1410  }
1411 
1412  if (currAttributes.NeedsRestore)
1413  {
1414  gpr.Restore();
1415  }
1416  }
1417 
1418  }
1419 
1420  private static List<string> TokenisePathData(string d)
1421  {
1422  List<string> tbr = new List<string>();
1423 
1424  string currToken = "";
1425 
1426  for (int i = 0; i < d.Length; i++)
1427  {
1428  char c = d[i];
1429 
1430  if (c >= '0' && c <= '9' || c == '.' || c == 'e' || c == 'E')
1431  {
1432  currToken += c;
1433  }
1434  else if (c == '-' || c == '+')
1435  {
1436  if (i > 0 && (d[i - 1] == 'e' || d[i - 1] == 'E'))
1437  {
1438  currToken += c;
1439  }
1440  else
1441  {
1442  if (!string.IsNullOrEmpty(currToken))
1443  {
1444  tbr.Add(currToken);
1445  }
1446  currToken = "" + c;
1447  }
1448  }
1449  else if (char.IsWhiteSpace(c) || c == ',')
1450  {
1451  if (!string.IsNullOrEmpty(currToken))
1452  {
1453  tbr.Add(currToken);
1454  }
1455  currToken = "";
1456  }
1457  else if (i < d.Length - 2 && (c == 'N' || c == 'n') && (d[i + 1] == 'a' || d[i + 1] == 'A') && (d[i + 2] == 'N' || d[i + 2] == 'n'))
1458  {
1459  if (!string.IsNullOrEmpty(currToken))
1460  {
1461  tbr.Add(currToken);
1462  }
1463  tbr.Add("NaN");
1464  currToken = "";
1465  i += 2;
1466  }
1467  else if ("MmLlHhVvCcSsQqTtAaZz".Contains(c))
1468  {
1469  if (!string.IsNullOrEmpty(currToken))
1470  {
1471  tbr.Add(currToken);
1472  }
1473  tbr.Add(c.ToString());
1474  currToken = "";
1475  }
1476  }
1477 
1478  if (!string.IsNullOrEmpty(currToken))
1479  {
1480  tbr.Add(currToken);
1481  }
1482 
1483  return tbr;
1484  }
1485 
1486  private static void InterpretCircleObject(XmlNode circleObject, Graphics gpr, double width, double height, double diagonal, PresentationAttributes attributes, IEnumerable<Stylesheet> styleSheets, Dictionary<string, Brush> gradients)
1487  {
1488  double cx, cy, r;
1489 
1490  cx = ParseLengthOrPercentage(circleObject.Attributes?["cx"]?.Value, width);
1491  cy = ParseLengthOrPercentage(circleObject.Attributes?["cy"]?.Value, height);
1492  r = ParseLengthOrPercentage(circleObject.Attributes?["r"]?.Value, diagonal);
1493 
1494  PresentationAttributes circleAttributes = InterpretPresentationAttributes(circleObject, attributes, width, height, diagonal, gpr, styleSheets, gradients);
1495 
1496  bool hadClippingPath = ApplyClipPath(circleObject, gpr, width, height, diagonal, attributes, styleSheets, gradients);
1497 
1498  if (circleAttributes.StrokeFirst)
1499  {
1500  if (circleAttributes.Stroke != null)
1501  {
1502  Brush strokeColour = circleAttributes.Stroke.MultiplyOpacity(circleAttributes.Opacity * circleAttributes.StrokeOpacity);
1503  gpr.StrokePath(new GraphicsPath().Arc(cx, cy, r, 0, 2 * Math.PI).Close(), strokeColour, circleAttributes.StrokeThickness, circleAttributes.LineCap, circleAttributes.LineJoin, circleAttributes.LineDash);
1504  }
1505 
1506  if (circleAttributes.Fill != null)
1507  {
1508  Brush fillColour = circleAttributes.Fill.MultiplyOpacity(circleAttributes.Opacity * circleAttributes.FillOpacity);
1509  gpr.FillPath(new GraphicsPath().Arc(cx, cy, r, 0, 2 * Math.PI).Close(), fillColour);
1510  }
1511  }
1512  else
1513  {
1514  if (circleAttributes.Fill != null)
1515  {
1516  Brush fillColour = circleAttributes.Fill.MultiplyOpacity(circleAttributes.Opacity * circleAttributes.FillOpacity);
1517  gpr.FillPath(new GraphicsPath().Arc(cx, cy, r, 0, 2 * Math.PI).Close(), fillColour);
1518  }
1519 
1520  if (circleAttributes.Stroke != null)
1521  {
1522  Brush strokeColour = circleAttributes.Stroke.MultiplyOpacity(circleAttributes.Opacity * circleAttributes.StrokeOpacity);
1523  gpr.StrokePath(new GraphicsPath().Arc(cx, cy, r, 0, 2 * Math.PI).Close(), strokeColour, circleAttributes.StrokeThickness, circleAttributes.LineCap, circleAttributes.LineJoin, circleAttributes.LineDash);
1524  }
1525  }
1526 
1527  if (hadClippingPath)
1528  {
1529  gpr.Restore();
1530  }
1531 
1532  if (circleAttributes.NeedsRestore)
1533  {
1534  gpr.Restore();
1535  }
1536  }
1537 
1538  private static void InterpretEllipseObject(XmlNode currObject, Graphics gpr, double width, double height, double diagonal, PresentationAttributes attributes, IEnumerable<Stylesheet> styleSheets, Dictionary<string, Brush> gradients)
1539  {
1540  double cx, cy, rx, ry;
1541 
1542  cx = ParseLengthOrPercentage(currObject.Attributes?["cx"]?.Value, width);
1543  cy = ParseLengthOrPercentage(currObject.Attributes?["cy"]?.Value, height);
1544  rx = ParseLengthOrPercentage(currObject.Attributes?["rx"]?.Value, width, double.NaN);
1545  ry = ParseLengthOrPercentage(currObject.Attributes?["ry"]?.Value, height, double.NaN);
1546 
1547  if (double.IsNaN(rx) && !double.IsNaN(ry))
1548  {
1549  rx = ry;
1550  }
1551  else if (!double.IsNaN(rx) && double.IsNaN(ry))
1552  {
1553  ry = rx;
1554  }
1555 
1556  if (rx > 0 && ry > 0)
1557  {
1558 
1559  PresentationAttributes currAttributes = InterpretPresentationAttributes(currObject, attributes, width, height, diagonal, gpr, styleSheets, gradients);
1560 
1561  bool hadClippingPath = ApplyClipPath(currObject, gpr, width, height, diagonal, attributes, styleSheets, gradients);
1562 
1563  double r = Math.Min(rx, ry);
1564 
1565  gpr.Save();
1566  gpr.Translate(cx, cy);
1567  gpr.Scale(rx / r, ry / r);
1568 
1569  if (currAttributes.StrokeFirst)
1570  {
1571  if (currAttributes.Stroke != null)
1572  {
1573  Brush strokeColour = currAttributes.Stroke.MultiplyOpacity(currAttributes.Opacity * currAttributes.StrokeOpacity);
1574  gpr.StrokePath(new GraphicsPath().Arc(0, 0, r, 0, 2 * Math.PI).Close(), strokeColour, currAttributes.StrokeThickness, currAttributes.LineCap, currAttributes.LineJoin, currAttributes.LineDash);
1575  }
1576 
1577  if (currAttributes.Fill != null)
1578  {
1579  Brush fillColour = currAttributes.Fill.MultiplyOpacity(currAttributes.Opacity * currAttributes.FillOpacity);
1580  gpr.FillPath(new GraphicsPath().Arc(0, 0, r, 0, 2 * Math.PI).Close(), fillColour);
1581  }
1582  }
1583  else
1584  {
1585  if (currAttributes.Fill != null)
1586  {
1587  Brush fillColour = currAttributes.Fill.MultiplyOpacity(currAttributes.Opacity * currAttributes.FillOpacity);
1588  gpr.FillPath(new GraphicsPath().Arc(0, 0, r, 0, 2 * Math.PI).Close(), fillColour);
1589  }
1590 
1591  if (currAttributes.Stroke != null)
1592  {
1593  Brush strokeColour = currAttributes.Stroke.MultiplyOpacity(currAttributes.Opacity * currAttributes.StrokeOpacity);
1594  gpr.StrokePath(new GraphicsPath().Arc(0, 0, r, 0, 2 * Math.PI).Close(), strokeColour, currAttributes.StrokeThickness, currAttributes.LineCap, currAttributes.LineJoin, currAttributes.LineDash);
1595  }
1596  }
1597 
1598  gpr.Restore();
1599 
1600  if (hadClippingPath)
1601  {
1602  gpr.Restore();
1603  }
1604 
1605  if (currAttributes.NeedsRestore)
1606  {
1607  gpr.Restore();
1608  }
1609  }
1610  }
1611 
1612  private static void InterpretLineObject(XmlNode lineObject, Graphics gpr, double width, double height, double diagonal, PresentationAttributes attributes, IEnumerable<Stylesheet> styleSheets, Dictionary<string, Brush> gradients)
1613  {
1614  double x1, x2, y1, y2;
1615 
1616  x1 = ParseLengthOrPercentage(lineObject.Attributes?["x1"]?.Value, width);
1617  y1 = ParseLengthOrPercentage(lineObject.Attributes?["y1"]?.Value, height);
1618  x2 = ParseLengthOrPercentage(lineObject.Attributes?["x2"]?.Value, width);
1619  y2 = ParseLengthOrPercentage(lineObject.Attributes?["y2"]?.Value, height);
1620 
1621  PresentationAttributes lineAttributes = InterpretPresentationAttributes(lineObject, attributes, width, height, diagonal, gpr, styleSheets, gradients);
1622 
1623  bool hadClippingPath = ApplyClipPath(lineObject, gpr, width, height, diagonal, attributes, styleSheets, gradients);
1624 
1625  if (lineAttributes.Stroke != null)
1626  {
1627  Brush strokeColour = lineAttributes.Stroke.MultiplyOpacity(lineAttributes.Opacity * lineAttributes.StrokeOpacity);
1628  gpr.StrokePath(new GraphicsPath().MoveTo(x1, y1).LineTo(x2, y2), strokeColour, lineAttributes.StrokeThickness, lineAttributes.LineCap, lineAttributes.LineJoin, lineAttributes.LineDash);
1629  }
1630 
1631  if (hadClippingPath)
1632  {
1633  gpr.Restore();
1634  }
1635 
1636  if (lineAttributes.NeedsRestore)
1637  {
1638  gpr.Restore();
1639  }
1640  }
1641 
1642  private static void SetStyleAttributes(XmlNode obj, IEnumerable<Stylesheet> styleSheets)
1643  {
1644  string style = obj.Attributes?["style"]?.Value;
1645 
1646  string classes = obj.Attributes?["class"]?.Value;
1647 
1648  if (!string.IsNullOrEmpty(classes))
1649  {
1650  string[] splitClasses = classes.Split(' ');
1651 
1652  foreach (string className in splitClasses)
1653  {
1654  if (!string.IsNullOrEmpty(className.Trim()))
1655  {
1656  foreach (Stylesheet sheet in styleSheets)
1657  {
1658  foreach (StyleRule rule in sheet.StyleRules)
1659  {
1660  if (rule.SelectorText.Contains("." + className))
1661  {
1662  style = rule.Style.CssText + "; " + style;
1663  }
1664  }
1665  }
1666  }
1667  }
1668  }
1669 
1670  if (!string.IsNullOrEmpty(style))
1671  {
1672  string[] splitStyle = style.Split(';');
1673 
1674  for (int i = 0; i < splitStyle.Length; i++)
1675  {
1676  string[] styleCouple = splitStyle[i].Split(':');
1677 
1678  if (styleCouple.Length == 2)
1679  {
1680  string styleName = styleCouple[0].Trim();
1681  string styleValue = styleCouple[1].Trim();
1682 
1683  ((XmlElement)obj).SetAttribute(styleName, styleValue);
1684  }
1685  else if (!string.IsNullOrWhiteSpace(splitStyle[i]))
1686  {
1687  throw new InvalidOperationException("The style specification is not valid: " + splitStyle[i]);
1688  }
1689  }
1690  }
1691  }
1692 
1693  internal static PresentationAttributes InterpretPresentationAttributes(XmlNode obj, PresentationAttributes parentPresentationAttributes, double width, double height, double diagonal, Graphics gpr, IEnumerable<Stylesheet> styleSheets, Dictionary<string, Brush> gradients)
1694  {
1695  SetStyleAttributes(obj, styleSheets);
1696 
1697  PresentationAttributes tbr = parentPresentationAttributes.Clone();
1698 
1699  string stroke = obj.Attributes?["stroke"]?.Value;
1700  string strokeOpacity = obj.Attributes?["stroke-opacity"]?.Value;
1701  string fill = obj.Attributes?["fill"]?.Value;
1702  string fillOpacity = obj.Attributes?["fill-opacity"]?.Value;
1703  string currentColour = obj.Attributes?["colour"]?.Value;
1704  string strokeThickness = obj.Attributes?["stroke-width"]?.Value;
1705  string lineCap = obj.Attributes?["stroke-linecap"]?.Value;
1706  string lineJoin = obj.Attributes?["stroke-linejoin"]?.Value;
1707  string opacity = obj.Attributes?["opacity"]?.Value;
1708  string strokeDashArray = obj.Attributes?["stroke-dasharray"]?.Value;
1709  string strokeDashOffset = obj.Attributes?["stroke-dashoffset"]?.Value;
1710  string paintOrder = obj.Attributes?["paint-order"]?.Value;
1711 
1712  string xA = obj.Attributes?["x"]?.Value;
1713  string yA = obj.Attributes?["y"]?.Value;
1714  string wA = obj.Attributes?["width"]?.Value;
1715  string hA = obj.Attributes?["height"]?.Value;
1716 
1717  string transform = obj.Attributes?["transform"]?.Value;
1718 
1719  if (xA != null)
1720  {
1721  tbr.X = ParseLengthOrPercentage(xA, width);
1722  }
1723 
1724  if (yA != null)
1725  {
1726  tbr.Y = ParseLengthOrPercentage(yA, height);
1727  }
1728 
1729  if (wA != null)
1730  {
1731  tbr.Width = ParseLengthOrPercentage(wA, width);
1732  }
1733 
1734  if (hA != null)
1735  {
1736  tbr.Height = ParseLengthOrPercentage(hA, height);
1737  }
1738 
1739  if (stroke != null)
1740  {
1741  if (stroke.Trim().StartsWith("url("))
1742  {
1743  string url = stroke.Trim().Substring(4);
1744  if (url.EndsWith(")"))
1745  {
1746  url = url.Substring(0, url.Length - 1);
1747  }
1748 
1749  url = url.Trim();
1750 
1751  if (url.StartsWith("#"))
1752  {
1753  url = url.Substring(1);
1754  if (gradients.TryGetValue(url, out Brush brush))
1755  {
1756  tbr.Stroke = brush;
1757  }
1758  }
1759  else
1760  {
1761  tbr.Stroke = null;
1762  }
1763  }
1764  else
1765  {
1766  tbr.Stroke = Colour.FromCSSString(stroke);
1767  }
1768  }
1769 
1770  if (strokeOpacity != null)
1771  {
1772  tbr.StrokeOpacity = ParseLengthOrPercentage(strokeOpacity, 1);
1773  }
1774 
1775  if (fill != null)
1776  {
1777  if (fill.Trim().StartsWith("url("))
1778  {
1779  string url = fill.Trim().Substring(4);
1780  if (url.EndsWith(")"))
1781  {
1782  url = url.Substring(0, url.Length - 1);
1783  }
1784 
1785  url = url.Trim();
1786 
1787  if (url.StartsWith("#"))
1788  {
1789  url = url.Substring(1);
1790  if (gradients.TryGetValue(url, out Brush brush))
1791  {
1792  tbr.Fill = brush;
1793  }
1794  }
1795  else
1796  {
1797  tbr.Fill = null;
1798  }
1799  }
1800  else
1801  {
1802  tbr.Fill = Colour.FromCSSString(fill);
1803  }
1804  }
1805 
1806  if (fillOpacity != null)
1807  {
1808  tbr.FillOpacity = ParseLengthOrPercentage(fillOpacity, 1);
1809  }
1810 
1811  if (currentColour != null)
1812  {
1813  tbr.CurrentColour = Colour.FromCSSString(currentColour);
1814  }
1815 
1816  if (strokeThickness != null)
1817  {
1818  tbr.StrokeThickness = ParseLengthOrPercentage(strokeThickness, diagonal);
1819  }
1820 
1821  if (lineCap != null)
1822  {
1823  if (lineCap.Equals("butt", StringComparison.OrdinalIgnoreCase))
1824  {
1825  tbr.LineCap = LineCaps.Butt;
1826  }
1827  else if (lineCap.Equals("round", StringComparison.OrdinalIgnoreCase))
1828  {
1829  tbr.LineCap = LineCaps.Round;
1830  }
1831  else if (lineCap.Equals("square", StringComparison.OrdinalIgnoreCase))
1832  {
1833  tbr.LineCap = LineCaps.Square;
1834  }
1835  }
1836 
1837  if (lineJoin != null)
1838  {
1839  if (lineJoin.Equals("bevel", StringComparison.OrdinalIgnoreCase))
1840  {
1841  tbr.LineJoin = LineJoins.Bevel;
1842  }
1843  else if (lineJoin.Equals("miter", StringComparison.OrdinalIgnoreCase) || lineJoin.Equals("miter-clip", StringComparison.OrdinalIgnoreCase))
1844  {
1845  tbr.LineJoin = LineJoins.Miter;
1846  }
1847  else if (lineJoin.Equals("round", StringComparison.OrdinalIgnoreCase))
1848  {
1849  tbr.LineJoin = LineJoins.Round;
1850  }
1851  }
1852 
1853  if (opacity != null)
1854  {
1855  tbr.Opacity = ParseLengthOrPercentage(opacity, 1);
1856  }
1857 
1858  if (strokeDashArray != null)
1859  {
1860  if (strokeDashArray != "none")
1861  {
1862  double[] parsedArray = ParseListOfDoubles(strokeDashArray);
1863 
1864  tbr.LineDash = new LineDash(parsedArray[0], parsedArray.Length > 1 ? parsedArray[1] : parsedArray[0], tbr.LineDash.Phase);
1865  }
1866  else
1867  {
1868  tbr.LineDash = LineDash.SolidLine;
1869  }
1870  }
1871 
1872  if (strokeDashOffset != null)
1873  {
1874  tbr.LineDash = new LineDash(tbr.LineDash.UnitsOn, tbr.LineDash.UnitsOff, ParseLengthOrPercentage(strokeDashOffset, diagonal));
1875  }
1876 
1877  if (paintOrder != null)
1878  {
1879  if (paintOrder.Equals("normal", StringComparison.OrdinalIgnoreCase))
1880  {
1881  tbr.StrokeFirst = false;
1882  }
1883  else
1884  {
1885  if (paintOrder.IndexOf("stroke", StringComparison.OrdinalIgnoreCase) >= 0 && (paintOrder.IndexOf("fill", StringComparison.OrdinalIgnoreCase) < 0 || paintOrder.IndexOf("fill", StringComparison.OrdinalIgnoreCase) > paintOrder.IndexOf("stroke", StringComparison.OrdinalIgnoreCase)))
1886  {
1887  tbr.StrokeFirst = true;
1888  }
1889  else
1890  {
1891  tbr.StrokeFirst = false;
1892  }
1893  }
1894  }
1895 
1896  if (transform != null)
1897  {
1898  gpr.Save();
1899  tbr.NeedsRestore = true;
1900 
1901  string[] transforms = ParseListOfTransforms(transform);
1902 
1903  for (int i = 0; i < transforms.Length; i++)
1904  {
1905  if (transforms[i].Equals("matrix", StringComparison.OrdinalIgnoreCase))
1906  {
1907  double a = ParseLengthOrPercentage(transforms[i + 1], 1);
1908  double b = ParseLengthOrPercentage(transforms[i + 2], 1);
1909  double c = ParseLengthOrPercentage(transforms[i + 3], 1);
1910  double d = ParseLengthOrPercentage(transforms[i + 4], 1);
1911  double e = ParseLengthOrPercentage(transforms[i + 5], 1);
1912  double f = ParseLengthOrPercentage(transforms[i + 6], 1);
1913 
1914  gpr.Transform(a, b, c, d, e, f);
1915  i += 6;
1916  }
1917  else if (transforms[i].Equals("translate", StringComparison.OrdinalIgnoreCase))
1918  {
1919  double x = ParseLengthOrPercentage(transforms[i + 1], 1);
1920 
1921  double y;
1922 
1923  if (i < transforms.Length - 2 && !double.IsNaN(y = ParseLengthOrPercentage(transforms[i + 2], 1)))
1924  {
1925  gpr.Translate(x, y);
1926  i += 2;
1927  }
1928  else
1929  {
1930  gpr.Translate(x, 0);
1931  i++;
1932  }
1933  }
1934  else if (transforms[i].Equals("scale", StringComparison.OrdinalIgnoreCase))
1935  {
1936  double x = ParseLengthOrPercentage(transforms[i + 1], 1);
1937 
1938  double y;
1939 
1940  if (i < transforms.Length - 2 && !double.IsNaN(y = ParseLengthOrPercentage(transforms[i + 2], 1)))
1941  {
1942  gpr.Scale(x, y);
1943  i += 2;
1944  }
1945  else
1946  {
1947  gpr.Scale(x, x);
1948  i++;
1949  }
1950  }
1951  else if (transforms[i].Equals("rotate", StringComparison.OrdinalIgnoreCase))
1952  {
1953  double a = ParseLengthOrPercentage(transforms[i + 1], 1) * Math.PI / 180;
1954 
1955  double x, y;
1956 
1957  if (i < transforms.Length - 3 && !double.IsNaN(x = ParseLengthOrPercentage(transforms[i + 2], 1)) && !double.IsNaN(y = ParseLengthOrPercentage(transforms[i + 3], 1)))
1958  {
1959  gpr.RotateAt(a, new Point(x, y));
1960  i += 2;
1961  }
1962  else
1963  {
1964  gpr.Rotate(a);
1965  i++;
1966  }
1967  }
1968  else if (transforms[i].Equals("skewX", StringComparison.OrdinalIgnoreCase))
1969  {
1970  double psi = ParseLengthOrPercentage(transforms[i + 1], 1) * Math.PI / 180;
1971 
1972  gpr.Transform(1, 0, Math.Tan(psi), 1, 0, 0);
1973 
1974  i++;
1975  }
1976  else if (transforms[i].Equals("skewY", StringComparison.OrdinalIgnoreCase))
1977  {
1978  double psi = ParseLengthOrPercentage(transforms[i + 1], 1) * Math.PI / 180;
1979 
1980  gpr.Transform(1, Math.Tan(psi), 0, 1, 0, 0);
1981 
1982  i++;
1983  }
1984  }
1985  }
1986 
1987  return tbr;
1988  }
1989 
1990  private static double ParseLengthOrPercentage(string value, double total, double defaultValue = 0)
1991  {
1992  if (value != null)
1993  {
1994  if (value.Contains("%"))
1995  {
1996  value = value.Replace("%", "");
1997  return double.Parse(value, System.Globalization.CultureInfo.InvariantCulture) * total / 100;
1998  }
1999  else if (double.TryParse(value.Replace("px", "").Replace("pt", ""), System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out double result))
2000  {
2001  return result;
2002  }
2003  else
2004  {
2005  string cleanedNumber = Regexes.NumberRegex.Match(value).Value;
2006  return double.Parse(cleanedNumber, System.Globalization.CultureInfo.InvariantCulture);
2007  }
2008  }
2009  else
2010  {
2011  return defaultValue;
2012  }
2013  }
2014 
2015  internal class PresentationAttributes
2016  {
2017  public Dictionary<string, FontFamily> EmbeddedFonts;
2018 
2019  public Brush Stroke = null;
2020  public double StrokeOpacity = 1;
2021  public Brush Fill = Colour.FromRgb(0, 0, 0);
2022  public double FillOpacity = 1;
2023  public Brush CurrentColour = null;
2024  public double StrokeThickness = 1;
2025  public LineCaps LineCap = LineCaps.Butt;
2026  public LineJoins LineJoin = LineJoins.Miter;
2027  public double Opacity = 1;
2028  public LineDash LineDash = new LineDash(0, 0, 0);
2029  public bool NeedsRestore = false;
2030  public bool StrokeFirst = false;
2031  public double X;
2032  public double Y;
2033  public double Width;
2034  public double Height;
2035 
2036  public PresentationAttributes Clone()
2037  {
2038  return new PresentationAttributes()
2039  {
2040  EmbeddedFonts = this.EmbeddedFonts,
2041 
2042  Stroke = this.Stroke,
2043  StrokeOpacity = this.StrokeOpacity,
2044  Fill = this.Fill,
2045  FillOpacity = this.FillOpacity,
2046  CurrentColour = this.CurrentColour,
2047  StrokeThickness = this.StrokeThickness,
2048  LineCap = this.LineCap,
2049  LineJoin = this.LineJoin,
2050  Opacity = this.Opacity,
2051  LineDash = this.LineDash,
2052  StrokeFirst = this.StrokeFirst,
2053  X = this.X,
2054  Y = this.Y,
2055  Width = this.Width,
2056  Height = this.Height
2057  };
2058  }
2059  }
2060 
2061  private static class Regexes
2062  {
2063  public static Regex ListSeparator = new Regex("[ \\t\\n\\r\\f]*,[ \\t\\n\\r\\f]*|[ \\t\\n\\r\\f]+", RegexOptions.Compiled);
2064  public static Regex FontFamilySeparator = new Regex("(?:^|,)(\"(?:[^\"])*\"|[^,]*)", RegexOptions.Compiled);
2065  public static Regex NumberRegex = new Regex(@"^[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?", RegexOptions.Compiled);
2066  }
2067 
2068  private static double[] ParseListOfDoubles(string value)
2069  {
2070  if (value == null)
2071  {
2072  return null;
2073  }
2074 
2075  string[] splitValue = Regexes.ListSeparator.Split(value);
2076  double[] tbr = new double[splitValue.Length];
2077 
2078  for (int i = 0; i < splitValue.Length; i++)
2079  {
2080  tbr[i] = double.Parse(splitValue[i], System.Globalization.CultureInfo.InvariantCulture);
2081  }
2082 
2083  return tbr;
2084  }
2085 
2086  private static string[] ParseListOfTransforms(string value)
2087  {
2088  if (value == null)
2089  {
2090  return null;
2091  }
2092 
2093  string[] splitValue = Regexes.ListSeparator.Split(value.Replace("(", " ").Replace(")", " ").Trim());
2094 
2095  return splitValue;
2096  }
2097 
2098  private static List<KeyValuePair<string, FontFamily>> GetEmbeddedFonts(string styleBlock)
2099  {
2100  StringReader sr = new StringReader(styleBlock);
2101 
2102  List<KeyValuePair<string, FontFamily>> tbr = new List<KeyValuePair<string, FontFamily>>();
2103 
2104  while (sr.Peek() >= 0)
2105  {
2106  string token = ReadCSSToken(sr);
2107 
2108  if (token.Equals("@font-face", StringComparison.OrdinalIgnoreCase))
2109  {
2110  List<string> tokens = new List<string>();
2111 
2112  while (!token.Equals("}", StringComparison.OrdinalIgnoreCase))
2113  {
2114  token = ReadCSSToken(sr);
2115  tokens.Add(token);
2116  }
2117 
2118  KeyValuePair<string, FontFamily>? fontFace = ParseFontFaceBlock(tokens);
2119 
2120  if (fontFace != null)
2121  {
2122  tbr.Add(fontFace.Value);
2123  }
2124  }
2125  }
2126 
2127  return tbr;
2128  }
2129 
2130  private static IEnumerable<KeyValuePair<string, IFilter>> GetFilters(XmlNode definitionsNode, List<Stylesheet> styleSheets)
2131  {
2132  Dictionary<string, IFilter> tbr = new Dictionary<string, IFilter>();
2133 
2134  foreach (XmlNode definition in definitionsNode.ChildNodes)
2135  {
2136  if (definition.Name.Equals("filter", StringComparison.OrdinalIgnoreCase))
2137  {
2138  XmlElement filter = (XmlElement)definition;
2139 
2140  string id = filter.GetAttribute("id");
2141 
2142  List<ILocationInvariantFilter> filterElements = new List<ILocationInvariantFilter>();
2143 
2144  foreach (XmlNode filterDefinition in definition.ChildNodes)
2145  {
2146  if (filterDefinition.Name.Equals("feGaussianBlur", StringComparison.OrdinalIgnoreCase))
2147  {
2148  XmlElement actualFilter = (XmlElement)filterDefinition;
2149 
2150  string stdDeviation = actualFilter.GetAttribute("stdDeviation");
2151 
2152  filterElements.Add(new GaussianBlurFilter(double.Parse(stdDeviation, System.Globalization.CultureInfo.InvariantCulture)));
2153  }
2154  else if (filterDefinition.Name.Equals("feColorMatrix", StringComparison.OrdinalIgnoreCase))
2155  {
2156  XmlElement actualFilter = (XmlElement)filterDefinition;
2157 
2158  string type = actualFilter.GetAttribute("type");
2159 
2160  if (type.Equals("matrix", StringComparison.OrdinalIgnoreCase))
2161  {
2162  string values = actualFilter.GetAttribute("values");
2163  double[] parsedValues = (from el in System.Text.RegularExpressions.Regex.Split(values, "\\s") select double.Parse(el.Trim())).ToArray();
2164 
2165  if (parsedValues.Length == 20)
2166  {
2167  double[,] matrix = new double[5, 5];
2168  matrix[4, 4] = 1;
2169 
2170  for (int i = 0; i < 20; i++)
2171  {
2172  int y = i / 5;
2173  int x = i % 5;
2174 
2175  matrix[y, x] = parsedValues[i];
2176  }
2177 
2178  filterElements.Add(new ColourMatrixFilter(new ColourMatrix(matrix)));
2179  }
2180  }
2181  }
2182  }
2183 
2184  if (filterElements.Count > 0)
2185  {
2186  if (filterElements.Count == 1)
2187  {
2188  tbr.Add(id, filterElements[0]);
2189  }
2190  else
2191  {
2192  tbr.Add(id, new CompositeLocationInvariantFilter(filterElements));
2193  }
2194  }
2195  }
2196  }
2197 
2198  return tbr;
2199  }
2200 
2201  private static IEnumerable<KeyValuePair<string, XmlNode>> GetMasks(XmlNode definitionsNode, List<Stylesheet> styleSheets)
2202  {
2203  Dictionary<string, XmlNode> tbr = new Dictionary<string, XmlNode>();
2204 
2205  foreach (XmlNode definition in definitionsNode.ChildNodes)
2206  {
2207  if (definition.Name.Equals("mask", StringComparison.OrdinalIgnoreCase))
2208  {
2209  XmlElement mask = (XmlElement)definition;
2210 
2211  string id = mask.GetAttribute("id");
2212 
2213  tbr.Add(id, definition);
2214  }
2215  }
2216 
2217  return tbr;
2218  }
2219 
2220  private static IEnumerable<KeyValuePair<string, Brush>> GetGradients(XmlNode definitionsNode, List<Stylesheet> styleSheets)
2221  {
2222  Dictionary<string, Brush> tbr = new Dictionary<string, Brush>();
2223  Dictionary<string, XmlNodeList> stopLists = new Dictionary<string, XmlNodeList>();
2224 
2225  foreach (XmlNode definition in definitionsNode.ChildNodes)
2226  {
2227  if (definition.Name.Equals("linearGradient", StringComparison.OrdinalIgnoreCase))
2228  {
2229  XmlElement gradient = (XmlElement)definition;
2230 
2231  string id = gradient.GetAttribute("id");
2232 
2233  double x1;
2234  double y1;
2235  double x2;
2236  double y2;
2237 
2238  if (!(gradient.HasAttribute("x1") && double.TryParse(gradient.GetAttribute("x1"), out x1)))
2239  {
2240  x1 = 0;
2241  }
2242 
2243  if (!(gradient.HasAttribute("y1") && double.TryParse(gradient.GetAttribute("y1"), out y1)))
2244  {
2245  y1 = 0;
2246  }
2247 
2248  if (!(gradient.HasAttribute("x2") && double.TryParse(gradient.GetAttribute("x2"), out x2)))
2249  {
2250  x2 = 0;
2251  }
2252 
2253  if (!(gradient.HasAttribute("y2") && double.TryParse(gradient.GetAttribute("y2"), out y2)))
2254  {
2255  y2 = 0;
2256  }
2257 
2258  List<GradientStop> gradientStops = new List<GradientStop>();
2259 
2260  XmlNodeList childNodes;
2261 
2262  if (gradient.HasAttribute("xlink:href"))
2263  {
2264  string refId = gradient.GetAttribute("xlink:href").Trim();
2265 
2266  if (refId.StartsWith("#"))
2267  {
2268  refId = refId.Substring(1);
2269 
2270  if (!stopLists.TryGetValue(refId, out childNodes))
2271  {
2272  childNodes = gradient.ChildNodes;
2273  }
2274  }
2275  else
2276  {
2277  childNodes = gradient.ChildNodes;
2278  }
2279  }
2280  else
2281  {
2282  childNodes = gradient.ChildNodes;
2283  }
2284 
2285  stopLists[id] = childNodes;
2286 
2287  foreach (XmlNode stopNode in childNodes)
2288  {
2289  if (stopNode.Name.Equals("stop", StringComparison.OrdinalIgnoreCase))
2290  {
2291  SetStyleAttributes(stopNode, styleSheets);
2292 
2293  XmlElement stop = (XmlElement)stopNode;
2294 
2295  double offset = 0;
2296  double opacity = 1;
2297 
2298  if (stop.HasAttribute("offset"))
2299  {
2300  offset = ParseLengthOrPercentage(stop.GetAttribute("offset"), 1);
2301  }
2302 
2303  if (stop.HasAttribute("stop-opacity"))
2304  {
2305  opacity = ParseLengthOrPercentage(stop.GetAttribute("stop-opacity"), 1);
2306  }
2307 
2308  Colour stopColour = Colour.FromRgba(0, 0, 0, 0);
2309 
2310  if (stop.HasAttribute("stop-color"))
2311  {
2312  stopColour = (Colour.FromCSSString(stop.GetAttribute("stop-color")) ?? stopColour).WithAlpha(opacity);
2313  }
2314 
2315  gradientStops.Add(new GradientStop(stopColour, offset));
2316  }
2317  }
2318 
2319  if (gradient.HasAttribute("gradientTransform"))
2320  {
2321  string transform = gradient.GetAttribute("gradientTransform");
2322  string[] transforms = ParseListOfTransforms(transform);
2323 
2324  double[,] transformMatrix = MatrixUtils.Identity;
2325 
2326  for (int i = 0; i < transforms.Length; i++)
2327  {
2328  if (transforms[i].Equals("matrix", StringComparison.OrdinalIgnoreCase))
2329  {
2330  double a = ParseLengthOrPercentage(transforms[i + 1], 1);
2331  double b = ParseLengthOrPercentage(transforms[i + 2], 1);
2332  double c = ParseLengthOrPercentage(transforms[i + 3], 1);
2333  double d = ParseLengthOrPercentage(transforms[i + 4], 1);
2334  double e = ParseLengthOrPercentage(transforms[i + 5], 1);
2335  double f = ParseLengthOrPercentage(transforms[i + 6], 1);
2336 
2337  transformMatrix = MatrixUtils.Multiply(transformMatrix, new double[,] { { a, c, e }, { b, d, f }, { 0, 0, 1 } });
2338 
2339  i += 6;
2340  }
2341  else if (transforms[i].Equals("translate", StringComparison.OrdinalIgnoreCase))
2342  {
2343  double x = ParseLengthOrPercentage(transforms[i + 1], 1);
2344 
2345  double y;
2346 
2347  if (i < transforms.Length - 2 && !double.IsNaN(y = ParseLengthOrPercentage(transforms[i + 2], 1)))
2348  {
2349  transformMatrix = MatrixUtils.Translate(transformMatrix, x, y);
2350  i += 2;
2351  }
2352  else
2353  {
2354  transformMatrix = MatrixUtils.Translate(transformMatrix, x, 0);
2355  i++;
2356  }
2357  }
2358  else if (transforms[i].Equals("scale", StringComparison.OrdinalIgnoreCase))
2359  {
2360  double x = ParseLengthOrPercentage(transforms[i + 1], 1);
2361 
2362  double y;
2363 
2364  if (i < transforms.Length - 2 && !double.IsNaN(y = ParseLengthOrPercentage(transforms[i + 2], 1)))
2365  {
2366  transformMatrix = MatrixUtils.Scale(transformMatrix, x, y);
2367  i += 2;
2368  }
2369  else
2370  {
2371  transformMatrix = MatrixUtils.Scale(transformMatrix, x, x);
2372  i++;
2373  }
2374  }
2375  else if (transforms[i].Equals("rotate", StringComparison.OrdinalIgnoreCase))
2376  {
2377  double a = ParseLengthOrPercentage(transforms[i + 1], 1) * Math.PI / 180;
2378 
2379  double x, y;
2380 
2381  if (i < transforms.Length - 3 && !double.IsNaN(x = ParseLengthOrPercentage(transforms[i + 2], 1)) && !double.IsNaN(y = ParseLengthOrPercentage(transforms[i + 3], 1)))
2382  {
2383  transformMatrix = MatrixUtils.Translate(transformMatrix, x, y);
2384  transformMatrix = MatrixUtils.Rotate(transformMatrix, a);
2385  transformMatrix = MatrixUtils.Translate(transformMatrix, -x, -y);
2386  i += 2;
2387  }
2388  else
2389  {
2390  transformMatrix = MatrixUtils.Rotate(transformMatrix, a);
2391  i++;
2392  }
2393  }
2394  else if (transforms[i].Equals("skewX", StringComparison.OrdinalIgnoreCase))
2395  {
2396  double psi = ParseLengthOrPercentage(transforms[i + 1], 1) * Math.PI / 180;
2397 
2398  transformMatrix = MatrixUtils.Multiply(transformMatrix, new double[,] { { 1, Math.Tan(psi), 0 }, { 0, 1, 0 }, { 0, 0, 1 } });
2399  i++;
2400  }
2401  else if (transforms[i].Equals("skewY", StringComparison.OrdinalIgnoreCase))
2402  {
2403  double psi = ParseLengthOrPercentage(transforms[i + 1], 1) * Math.PI / 180;
2404 
2405  transformMatrix = MatrixUtils.Multiply(transformMatrix, new double[,] { { 1, 0, 0 }, { Math.Tan(psi), 1, 0 }, { 0, 0, 1 } });
2406  i++;
2407  }
2408  }
2409 
2410  double[] start = MatrixUtils.Multiply(transformMatrix, new double[] { x1, y1 });
2411  double[] end = MatrixUtils.Multiply(transformMatrix, new double[] { x2, y2 });
2412 
2413  x1 = start[0];
2414  y1 = start[1];
2415  x2 = end[0];
2416  y2 = end[1];
2417  }
2418 
2419  tbr.Add(id, new LinearGradientBrush(new Point(x1, y1), new Point(x2, y2), gradientStops));
2420  }
2421  else if (definition.Name.Equals("radialGradient", StringComparison.OrdinalIgnoreCase))
2422  {
2423  XmlElement gradient = (XmlElement)definition;
2424 
2425  string id = gradient.GetAttribute("id");
2426 
2427  double cx;
2428  double cy;
2429  double r;
2430 
2431  if (!(gradient.HasAttribute("cx") && double.TryParse(gradient.GetAttribute("cx"), out cx)))
2432  {
2433  cx = 0;
2434  }
2435 
2436  if (!(gradient.HasAttribute("cy") && double.TryParse(gradient.GetAttribute("cy"), out cy)))
2437  {
2438  cy = 0;
2439  }
2440 
2441  if (!(gradient.HasAttribute("r") && double.TryParse(gradient.GetAttribute("r"), out r)))
2442  {
2443  r = 0;
2444  }
2445 
2446  double fx;
2447  double fy;
2448 
2449  if (!(gradient.HasAttribute("fx") && double.TryParse(gradient.GetAttribute("fx"), out fx)))
2450  {
2451  fx = cx;
2452  }
2453 
2454  if (!(gradient.HasAttribute("fy") && double.TryParse(gradient.GetAttribute("fy"), out fy)))
2455  {
2456  fy = cy;
2457  }
2458 
2459  List<GradientStop> gradientStops = new List<GradientStop>();
2460 
2461  XmlNodeList childNodes;
2462 
2463  if (gradient.HasAttribute("xlink:href"))
2464  {
2465  string refId = gradient.GetAttribute("xlink:href").Trim();
2466 
2467  if (refId.StartsWith("#"))
2468  {
2469  refId = refId.Substring(1);
2470 
2471  if (!stopLists.TryGetValue(refId, out childNodes))
2472  {
2473  childNodes = gradient.ChildNodes;
2474  }
2475  }
2476  else
2477  {
2478  childNodes = gradient.ChildNodes;
2479  }
2480  }
2481  else
2482  {
2483  childNodes = gradient.ChildNodes;
2484  }
2485 
2486  stopLists[id] = childNodes;
2487 
2488  foreach (XmlNode stopNode in childNodes)
2489  {
2490  if (stopNode.Name.Equals("stop", StringComparison.OrdinalIgnoreCase))
2491  {
2492  SetStyleAttributes(stopNode, styleSheets);
2493 
2494  XmlElement stop = (XmlElement)stopNode;
2495 
2496  double offset = 0;
2497  double opacity = 1;
2498 
2499  if (stop.HasAttribute("offset"))
2500  {
2501  offset = ParseLengthOrPercentage(stop.GetAttribute("offset"), 1);
2502  }
2503 
2504  if (stop.HasAttribute("stop-opacity"))
2505  {
2506  opacity = ParseLengthOrPercentage(stop.GetAttribute("stop-opacity"), 1);
2507  }
2508 
2509  Colour stopColour = Colour.FromRgba(0, 0, 0, 0);
2510 
2511  if (stop.HasAttribute("stop-color"))
2512  {
2513  stopColour = (Colour.FromCSSString(stop.GetAttribute("stop-color")) ?? stopColour).WithAlpha(opacity);
2514  }
2515 
2516  gradientStops.Add(new GradientStop(stopColour, offset));
2517  }
2518  }
2519 
2520  if (gradient.HasAttribute("gradientTransform"))
2521  {
2522  string transform = gradient.GetAttribute("gradientTransform");
2523  string[] transforms = ParseListOfTransforms(transform);
2524 
2525  double[,] transformMatrix = MatrixUtils.Identity;
2526 
2527  for (int i = 0; i < transforms.Length; i++)
2528  {
2529  if (transforms[i].Equals("matrix", StringComparison.OrdinalIgnoreCase))
2530  {
2531  double a = ParseLengthOrPercentage(transforms[i + 1], 1);
2532  double b = ParseLengthOrPercentage(transforms[i + 2], 1);
2533  double c = ParseLengthOrPercentage(transforms[i + 3], 1);
2534  double d = ParseLengthOrPercentage(transforms[i + 4], 1);
2535  double e = ParseLengthOrPercentage(transforms[i + 5], 1);
2536  double f = ParseLengthOrPercentage(transforms[i + 6], 1);
2537 
2538  transformMatrix = MatrixUtils.Multiply(transformMatrix, new double[,] { { a, c, e }, { b, d, f }, { 0, 0, 1 } });
2539 
2540  i += 6;
2541  }
2542  else if (transforms[i].Equals("translate", StringComparison.OrdinalIgnoreCase))
2543  {
2544  double x = ParseLengthOrPercentage(transforms[i + 1], 1);
2545 
2546  double y;
2547 
2548  if (i < transforms.Length - 2 && !double.IsNaN(y = ParseLengthOrPercentage(transforms[i + 2], 1)))
2549  {
2550  transformMatrix = MatrixUtils.Translate(transformMatrix, x, y);
2551  i += 2;
2552  }
2553  else
2554  {
2555  transformMatrix = MatrixUtils.Translate(transformMatrix, x, 0);
2556  i++;
2557  }
2558  }
2559  else if (transforms[i].Equals("scale", StringComparison.OrdinalIgnoreCase))
2560  {
2561  double x = ParseLengthOrPercentage(transforms[i + 1], 1);
2562 
2563  double y;
2564 
2565  if (i < transforms.Length - 2 && !double.IsNaN(y = ParseLengthOrPercentage(transforms[i + 2], 1)))
2566  {
2567  transformMatrix = MatrixUtils.Scale(transformMatrix, x, y);
2568  i += 2;
2569  }
2570  else
2571  {
2572  transformMatrix = MatrixUtils.Scale(transformMatrix, x, x);
2573  i++;
2574  }
2575  }
2576  else if (transforms[i].Equals("rotate", StringComparison.OrdinalIgnoreCase))
2577  {
2578  double a = ParseLengthOrPercentage(transforms[i + 1], 1) * Math.PI / 180;
2579 
2580  double x, y;
2581 
2582  if (i < transforms.Length - 3 && !double.IsNaN(x = ParseLengthOrPercentage(transforms[i + 2], 1)) && !double.IsNaN(y = ParseLengthOrPercentage(transforms[i + 3], 1)))
2583  {
2584  transformMatrix = MatrixUtils.Translate(transformMatrix, x, y);
2585  transformMatrix = MatrixUtils.Rotate(transformMatrix, a);
2586  transformMatrix = MatrixUtils.Translate(transformMatrix, -x, -y);
2587  i += 2;
2588  }
2589  else
2590  {
2591  transformMatrix = MatrixUtils.Rotate(transformMatrix, a);
2592  i++;
2593  }
2594  }
2595  else if (transforms[i].Equals("skewX", StringComparison.OrdinalIgnoreCase))
2596  {
2597  double psi = ParseLengthOrPercentage(transforms[i + 1], 1) * Math.PI / 180;
2598 
2599  transformMatrix = MatrixUtils.Multiply(transformMatrix, new double[,] { { 1, Math.Tan(psi), 0 }, { 0, 1, 0 }, { 0, 0, 1 } });
2600  i++;
2601  }
2602  else if (transforms[i].Equals("skewY", StringComparison.OrdinalIgnoreCase))
2603  {
2604  double psi = ParseLengthOrPercentage(transforms[i + 1], 1) * Math.PI / 180;
2605 
2606  transformMatrix = MatrixUtils.Multiply(transformMatrix, new double[,] { { 1, 0, 0 }, { Math.Tan(psi), 1, 0 }, { 0, 0, 1 } });
2607  i++;
2608  }
2609  }
2610 
2611  double determinant = transformMatrix[0, 0] * (transformMatrix[1, 1] * transformMatrix[2, 2] - transformMatrix[1, 2] * transformMatrix[2, 1]) -
2612  transformMatrix[0, 1] * (transformMatrix[1, 0] * transformMatrix[2, 2] - transformMatrix[1, 2] * transformMatrix[2, 0]) +
2613  transformMatrix[0, 2] * (transformMatrix[1, 0] * transformMatrix[2, 1] - transformMatrix[1, 1] * transformMatrix[2, 0]);
2614 
2615  double[] focus = MatrixUtils.Multiply(transformMatrix, new double[] { fx, fy });
2616  double[] centre = MatrixUtils.Multiply(transformMatrix, new double[] { cx, cy });
2617 
2618  fx = focus[0];
2619  fy = focus[1];
2620  cx = centre[0];
2621  cy = centre[1];
2622  r = r * Math.Sqrt(determinant);
2623  }
2624 
2625  tbr.Add(id, new RadialGradientBrush(new Point(fx, fy), new Point(cx, cy), r, gradientStops));
2626  }
2627  }
2628 
2629 
2630  return tbr;
2631  }
2632 
2633  private static KeyValuePair<string, FontFamily>? ParseFontFaceBlock(List<string> tokens)
2634  {
2635  int fontFamilyInd = tokens.IndexOf("font-family");
2636  string fontFamilyName = tokens[fontFamilyInd + 2].Trim().Trim('"').Trim();
2637 
2638  int srcInd = tokens.IndexOf("src");
2639  string src = tokens[srcInd + 2];
2640 
2641  string mimeType = src.Substring(src.IndexOf("data:") + 5);
2642  mimeType = mimeType.Substring(0, mimeType.IndexOf(";"));
2643 
2644  if (mimeType.Equals("font/ttf", StringComparison.OrdinalIgnoreCase) || mimeType.Equals("font/truetype", StringComparison.OrdinalIgnoreCase) || mimeType.Equals("application/x-font-ttf", StringComparison.OrdinalIgnoreCase))
2645  {
2646  src = src.Substring(src.IndexOf("base64,") + 7);
2647  src = src.TrimEnd(')').TrimEnd('\"').TrimEnd(')');
2648  byte[] fontBytes = Convert.FromBase64String(src);
2649 
2650  string tempFile = Path.GetTempFileName();
2651 
2652  File.WriteAllBytes(tempFile, fontBytes);
2653 
2654  FontFamily family = FontFamily.ResolveFontFamily(tempFile);
2655  return new KeyValuePair<string, FontFamily>(fontFamilyName, family);
2656  }
2657 
2658  return null;
2659  }
2660 
2661  private const string CSSDelimiters = ":;,{}";
2662 
2663  private static string ReadCSSToken(StringReader reader)
2664  {
2665  StringBuilder tbr = new StringBuilder();
2666 
2667  bool openQuotes = false;
2668  int openParentheses = 0;
2669 
2670  int c = reader.Read();
2671  if (c >= 0)
2672  {
2673  tbr.Append((char)c);
2674 
2675  if ((char)c == '"')
2676  {
2677  openQuotes = !openQuotes;
2678  }
2679 
2680  if ((char)c == '(')
2681  {
2682  openParentheses++;
2683  }
2684  if ((char)c == ')')
2685  {
2686  openParentheses--;
2687  }
2688 
2689 
2690  while (c >= 0 && (!CSSDelimiters.Contains((char)c) || openQuotes || openParentheses > 0))
2691  {
2692  c = reader.Read();
2693  tbr.Append((char)c);
2694  if ((char)c == '"')
2695  {
2696  openQuotes = !openQuotes;
2697  }
2698  if ((char)c == '(')
2699  {
2700  openParentheses++;
2701  }
2702  if ((char)c == ')')
2703  {
2704  openParentheses--;
2705  }
2706  c = reader.Peek();
2707  }
2708  }
2709 
2710  string val = tbr.ToString().Trim();
2711 
2712  return (string.IsNullOrEmpty(val) && c >= 0) ? ReadCSSToken(reader) : val;
2713  }
2714  }
2715 
2716  internal class PathTransformerGraphicsContext : IGraphicsContext
2717  {
2718  public GraphicsPath CurrentPath = new GraphicsPath();
2719  public double[,] TransformMatrix = MatrixUtils.Identity;
2720 
2721  private Stack<double[,]> transformMatrices;
2722  public PathTransformerGraphicsContext()
2723  {
2724  transformMatrices = new Stack<double[,]>();
2725  transformMatrices.Push((double[,])TransformMatrix.Clone());
2726  }
2727 
2728  public void CubicBezierTo(double p1X, double p1Y, double p2X, double p2Y, double p3X, double p3Y)
2729  {
2730  double[] p1 = MatrixUtils.Multiply(TransformMatrix, new double[] { p1X, p1Y });
2731  double[] p2 = MatrixUtils.Multiply(TransformMatrix, new double[] { p2X, p2Y });
2732  double[] p3 = MatrixUtils.Multiply(TransformMatrix, new double[] { p3X, p3Y });
2733 
2734  CurrentPath.CubicBezierTo(p1[0], p1[1], p2[0], p2[1], p3[0], p3[1]);
2735  }
2736 
2737  public void LineTo(double x, double y)
2738  {
2739  double[] p = MatrixUtils.Multiply(TransformMatrix, new double[] { x, y });
2740  CurrentPath.LineTo(p[0], p[1]);
2741  }
2742 
2743  public void MoveTo(double x, double y)
2744  {
2745  double[] p = MatrixUtils.Multiply(TransformMatrix, new double[] { x, y });
2746  CurrentPath.MoveTo(p[0], p[1]);
2747  }
2748 
2749  public void Close()
2750  {
2751  CurrentPath.Close();
2752  }
2753 
2754  public void Restore()
2755  {
2756  TransformMatrix = transformMatrices.Pop();
2757  }
2758 
2759  public void Rotate(double angle)
2760  {
2761  TransformMatrix = MatrixUtils.Rotate(TransformMatrix, angle);
2762  }
2763 
2764  public void Save()
2765  {
2766  transformMatrices.Push((double[,])TransformMatrix.Clone());
2767  }
2768 
2769  public void Scale(double scaleX, double scaleY)
2770  {
2771  TransformMatrix = MatrixUtils.Scale(TransformMatrix, scaleX, scaleY);
2772  }
2773 
2774  public void Transform(double a, double b, double c, double d, double e, double f)
2775  {
2776  double[,] transfMatrix = new double[3, 3] { { a, c, e }, { b, d, f }, { 0, 0, 1 } };
2777  TransformMatrix = MatrixUtils.Multiply(TransformMatrix, transfMatrix);
2778  }
2779 
2780  public void Translate(double x, double y)
2781  {
2782  TransformMatrix = MatrixUtils.Translate(TransformMatrix, x, y);
2783  }
2784 
2785 
2786 
2787  public double Width => throw new NotImplementedException();
2788 
2789  public double Height => throw new NotImplementedException();
2790 
2791  public Font Font { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
2792  public TextBaselines TextBaseline { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
2793 
2794  public Brush FillStyle => Colours.Black;
2795 
2796  public Brush StrokeStyle => Colours.Black;
2797 
2798  public double LineWidth { get => 1; set { } }
2799  public LineCaps LineCap { set { } }
2800  public LineJoins LineJoin { set { } }
2801  public string Tag { get => null; set { } }
2802 
2803  public void DrawRasterImage(int sourceX, int sourceY, int sourceWidth, int sourceHeight, double destinationX, double destinationY, double destinationWidth, double destinationHeight, RasterImage image)
2804  {
2805  throw new NotImplementedException();
2806  }
2807 
2808  public void Fill()
2809  {
2810 
2811  }
2812 
2813  public void FillText(string text, double x, double y)
2814  {
2815  throw new NotImplementedException();
2816  }
2817 
2818  public void Rectangle(double x0, double y0, double width, double height)
2819  {
2820  MoveTo(x0, y0);
2821  LineTo(x0 + width, y0);
2822  LineTo(x0 + width, y0 + height);
2823  LineTo(x0, y0 + height);
2824  Close();
2825  }
2826 
2827  public void SetClippingPath()
2828  {
2829  throw new NotImplementedException();
2830  }
2831 
2832  public void SetFillStyle((int r, int g, int b, double a) style)
2833  {
2834 
2835  }
2836 
2837  public void SetFillStyle(Brush style)
2838  {
2839 
2840  }
2841 
2842  public void SetLineDash(LineDash dash)
2843  {
2844 
2845  }
2846 
2847  public void SetStrokeStyle((int r, int g, int b, double a) style)
2848  {
2849 
2850  }
2851 
2852  public void SetStrokeStyle(Brush style)
2853  {
2854 
2855  }
2856 
2857  public void Stroke()
2858  {
2859 
2860  }
2861 
2862  public void StrokeText(string text, double x, double y)
2863  {
2864  throw new NotImplementedException();
2865  }
2866 
2867  public void DrawFilteredGraphics(Graphics graphics, IFilter filter)
2868  {
2869  graphics.CopyToIGraphicsContext(this);
2870  }
2871  }
2872 }
VectSharp.Filters.ColourMatrix
Represents a colour transformation matrix.
Definition: ColourMatrixFilter.cs:27
VectSharp.Graphics.Translate
void Translate(double x, double y)
Translate the coordinate system origin.
Definition: Graphics.cs:370
VectSharp.SVG.Parser.FromFile
static Page FromFile(string fileName)
Parses an SVG image file into a Page containing the image.
Definition: SVGParser.cs:167
VectSharp.SVG.Parser
Contains methods to read an SVG image file.
Definition: SVGParser.cs:34
VectSharp.SVG
Definition: SVGContext.cs:28
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.Filters.ColourMatrixFilter
Represents a filter that applies a Filters.ColourMatrix to the colours of the image.
Definition: ColourMatrixFilter.cs:568
VectSharp.Page
Represents a Graphics object with a width and height.
Definition: Document.cs:48
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.Graphics.Save
void Save()
Save the current transform state (rotation, translation, scale).
Definition: Graphics.cs:524
VectSharp.TextBaselines
TextBaselines
Represent text baselines.
Definition: Enums.cs:24
VectSharp.Graphics
Represents an abstract drawing surface.
Definition: Graphics.cs:262
VectSharp.LineJoins
LineJoins
Represents line joining options.
Definition: Enums.cs:92
VectSharp.SVG.Parser.FromString
static Page FromString(string svgSource)
Parses SVG source into a Page containing the image represented by the code.
Definition: SVGParser.cs:103
VectSharp.Graphics.Scale
void Scale(double scaleX, double scaleY)
Scale the coordinate system with respect to the origin.
Definition: Graphics.cs:389
VectSharp.SVG.Parser.ParseImageURI
static Func< string, bool, Page > ParseImageURI
A function that takes as input an image URI and a boolean value indicating whether the image should b...
Definition: SVGParser.cs:46
VectSharp.SVG.Parser.FromStream
static Page FromStream(Stream svgSourceStream)
Parses an stream containing SVG source code into a Page containing the image represented by the code.
Definition: SVGParser.cs:177
VectSharp.Filters
Definition: BoxBlurFilter.cs:22
VectSharp.Size
Represents the size of an object.
Definition: Point.cs:146
VectSharp.SVG.Parser.ParseSVGURI
static Page ParseSVGURI(string uri, bool ignored=false)
Parses an SVG image URI.
Definition: SVGParser.cs:54
VectSharp.Filters.MaskFilter
Represents a filter that uses the luminance of an image to mask another image.
Definition: MaskFilter.cs:27
VectSharp.Size.Width
double Width
Width of the object.
Definition: Point.cs:150
VectSharp.Graphics.Restore
void Restore()
Restore the previous transform state (rotation, translation scale).
Definition: Graphics.cs:532
VectSharp.Filters.CompositeLocationInvariantFilter
Represents a filter that corresponds to applying multiple ILocationInvariantFilters one after the oth...
Definition: CompositeFilter.cs:27
VectSharp.SegmentType.Arc
@ Arc
The segment represents a circular arc from the current point to a new point.
VectSharp.Filters.GaussianBlurFilter
Represents a filter that applies a Gaussian blur effect.
Definition: GaussianBlurFilter.cs:27