20 using System.Collections.Generic;
24 using System.Text.RegularExpressions;
56 if (uri.StartsWith(
"data:"))
58 string mimeType = uri.Substring(uri.IndexOf(
":") + 1, uri.IndexOf(
";") - uri.IndexOf(
":") - 1);
60 string type = uri.Substring(uri.IndexOf(
";") + 1, uri.IndexOf(
",") - uri.IndexOf(
";") - 1);
62 if (mimeType ==
"image/svg+xml")
64 int offset = uri.IndexOf(
",") + 1;
71 data = Encoding.UTF8.GetString(Convert.FromBase64String(uri.Substring(offset)));
77 data = System.Web.HttpUtility.UrlDecode(uri.Substring(offset));
81 data = System.Web.HttpUtility.UrlDecode(uri.Substring(offset));
84 throw new InvalidDataException(
"Unknown data stream type!");
105 XmlDocument svgDoc =
new XmlDocument();
106 svgDoc.LoadXml(svgSource);
108 Dictionary<string, FontFamily> embeddedFonts =
new Dictionary<string, FontFamily>();
110 StylesheetParser parser =
new StylesheetParser();
112 List<Stylesheet> styleSheets =
new List<Stylesheet>();
114 foreach (XmlNode styleNode
in svgDoc.GetElementsByTagName(
"style"))
116 foreach (KeyValuePair<string, FontFamily> fnt
in GetEmbeddedFonts(styleNode.InnerText))
118 embeddedFonts.Add(fnt.Key, fnt.Value);
123 Stylesheet sheet = parser.Parse(styleNode.InnerText);
124 styleSheets.Add(sheet);
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>();
133 foreach (XmlNode definitionsNode
in svgDoc.GetElementsByTagName(
"defs"))
135 foreach (KeyValuePair<string, Brush> fnt
in GetGradients(definitionsNode, styleSheets))
137 gradients.Add(fnt.Key, fnt.Value);
140 foreach (KeyValuePair<string, IFilter> filt
in GetFilters(definitionsNode, styleSheets))
142 filters.Add(filt.Key, filt.Value);
145 foreach (KeyValuePair<string, XmlNode> mask
in GetMasks(definitionsNode, styleSheets))
147 masks.Add(mask.Key, mask.Value);
153 Size pageSize = InterpretSVGObject(svgDoc.GetElementsByTagName(
"svg")[0], gpr,
new PresentationAttributes() { EmbeddedFonts = embeddedFonts }, styleSheets, gradients, filters, masks);
169 return FromString(File.ReadAllText(fileName));
179 using (StreamReader sr =
new StreamReader(svgSourceStream))
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)
187 double[] viewBox = ParseListOfDoubles(svgObject.Attributes?[
"viewBox"]?.Value);
189 double width, height, x, y;
191 string widthAttribute = svgObject.Attributes?[
"width"]?.Value?.Replace(
"px",
"");
193 if (!
double.TryParse(widthAttribute, System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out width)) { width =
double.NaN; }
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; }
198 string xAttribute = svgObject.Attributes?[
"x"]?.Value;
199 double.TryParse(xAttribute, System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out x);
201 string yAttribute = svgObject.Attributes?[
"y"]?.Value;
202 double.TryParse(yAttribute, System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out y);
207 double postTranslateX = 0;
208 double postTranslateY = 0;
212 if (!
double.IsNaN(width) && !
double.IsNaN(height))
214 scaleX = width / viewBox[2];
215 scaleY = height / viewBox[3];
217 else if (!
double.IsNaN(width) &&
double.IsNaN(height))
219 scaleX = width / viewBox[2];
221 height = scaleY * viewBox[3];
223 else if (
double.IsNaN(width) && !
double.IsNaN(height))
225 scaleY = height / viewBox[3];
227 width = scaleX * viewBox[2];
229 else if (
double.IsNaN(width) &&
double.IsNaN(height))
235 postTranslateX = -viewBox[0];
236 postTranslateY = -viewBox[1];
240 viewBox =
new double[4];
242 if (!
double.IsNaN(width))
247 if (!
double.IsNaN(height))
253 double diagonal = Math.Sqrt(viewBox[2] * viewBox[2] + viewBox[3] * viewBox[3]) / Math.Sqrt(2);
255 Size tbrSize =
new Size(width, height);
259 gpr.
Scale(scaleX, scaleY);
260 gpr.
Translate(postTranslateX, postTranslateY);
262 attributes = InterpretPresentationAttributes(svgObject, attributes, viewBox[2], viewBox[3], diagonal, gpr, styleSheets, gradients);
264 foreach (KeyValuePair<string, XmlNode> mask
in masks)
266 Graphics maskGpr =
new Graphics();
267 InterpretGObject(mask.Value, maskGpr, viewBox[2], viewBox[3], diagonal, attributes, styleSheets, gradients, filters);
269 filters.Add(mask.Key,
new MaskFilter(maskGpr));
272 InterpretSVGChildren(svgObject, gpr, attributes, viewBox[2], viewBox[3], diagonal, styleSheets, gradients, filters);
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)
281 foreach (XmlNode child
in svgObject.ChildNodes)
283 InterpretSVGElement(child, gpr, attributes, width, height, diagonal, styleSheets, gradients, filters);
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)
289 if (currObject.NodeType == XmlNodeType.EntityReference)
291 InterpretSVGChildren(currObject, gpr, attributes, width, height, diagonal, styleSheets, gradients, filters);
293 else if (currObject.Name.Equals(
"svg", StringComparison.OrdinalIgnoreCase))
295 InterpretSVGObject(currObject, gpr, attributes, styleSheets, gradients, filters,
new Dictionary<string, XmlNode>());
297 else if (currObject.Name.Equals(
"line", StringComparison.OrdinalIgnoreCase))
299 InterpretLineObject(currObject, gpr, width, height, diagonal, attributes, styleSheets, gradients);
301 else if (currObject.Name.Equals(
"circle", StringComparison.OrdinalIgnoreCase))
303 InterpretCircleObject(currObject, gpr, width, height, diagonal, attributes, styleSheets, gradients);
305 else if (currObject.Name.Equals(
"ellipse", StringComparison.OrdinalIgnoreCase))
307 InterpretEllipseObject(currObject, gpr, width, height, diagonal, attributes, styleSheets, gradients);
309 else if (currObject.Name.Equals(
"path", StringComparison.OrdinalIgnoreCase))
311 InterpretPathObject(currObject, gpr, width, height, diagonal, attributes, styleSheets, gradients);
313 else if (currObject.Name.Equals(
"polyline", StringComparison.OrdinalIgnoreCase))
315 InterpretPolyLineObject(currObject,
false, gpr, width, height, diagonal, attributes, styleSheets, gradients);
317 else if (currObject.Name.Equals(
"polygon", StringComparison.OrdinalIgnoreCase))
319 InterpretPolyLineObject(currObject,
true, gpr, width, height, diagonal, attributes, styleSheets, gradients);
321 else if (currObject.Name.Equals(
"rect", StringComparison.OrdinalIgnoreCase))
323 InterpretRectObject(currObject, gpr, width, height, diagonal, attributes, styleSheets, gradients);
325 else if (currObject.Name.Equals(
"use", StringComparison.OrdinalIgnoreCase))
327 InterpretUseObject(currObject, gpr, width, height, diagonal, attributes, styleSheets, gradients, filters);
329 else if (currObject.Name.Equals(
"g", StringComparison.OrdinalIgnoreCase) || currObject.Name.Equals(
"symbol", StringComparison.OrdinalIgnoreCase))
331 InterpretGObject(currObject, gpr, width, height, diagonal, attributes, styleSheets, gradients, filters);
333 else if (currObject.Name.Equals(
"text", StringComparison.OrdinalIgnoreCase))
338 InterpretTextObject(currObject, gpr, width, height, diagonal, attributes, styleSheets, gradients, ref x, ref y);
340 else if (currObject.Name.Equals(
"image", StringComparison.OrdinalIgnoreCase))
342 InterpretImageObject(currObject, gpr, width, height, diagonal, attributes, styleSheets, gradients);
346 private static void InterpretImageObject(XmlNode currObject, Graphics gpr,
double width,
double height,
double diagonal, PresentationAttributes attributes, IEnumerable<Stylesheet> styleSheets, Dictionary<string, Brush> gradients)
348 PresentationAttributes currAttributes = InterpretPresentationAttributes(currObject, attributes, width, height, diagonal, gpr, styleSheets, gradients);
350 double x = ParseLengthOrPercentage(currObject.Attributes?[
"x"]?.Value, width, currAttributes.X);
351 double y = ParseLengthOrPercentage(currObject.Attributes?[
"y"]?.Value, height, currAttributes.Y);
353 double w = ParseLengthOrPercentage(currObject.Attributes?[
"width"]?.Value, width, currAttributes.Width);
354 double h = ParseLengthOrPercentage(currObject.Attributes?[
"height"]?.Value, height, currAttributes.Height);
356 bool interpolate = !(currObject.Attributes?[
"image-rendering"]?.Value ==
"pixelated" || currObject.Attributes?[
"image-rendering"]?.Value ==
"optimizeSpeed");
358 string href = currObject.Attributes?[
"href"]?.Value;
360 if (
string.IsNullOrEmpty(href))
362 href = currObject.Attributes?[
"xlink:href"]?.Value;
365 bool hadClippingPath = ApplyClipPath(currObject, gpr, width, height, diagonal, attributes, styleSheets, gradients);
367 if (!
string.IsNullOrEmpty(href) && w > 0 && h > 0)
375 double scaleX = w / image.Width;
376 double scaleY = h / image.Height;
378 gpr.Scale(scaleX, scaleY);
380 gpr.DrawGraphics(x / scaleX, y / scaleY, image.Graphics);
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);
396 if (currAttributes.NeedsRestore)
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)
404 PresentationAttributes currAttributes = InterpretPresentationAttributes(currObject, attributes, width, height, diagonal, gpr, styleSheets, gradients);
406 x = ParseLengthOrPercentage(currObject.Attributes?[
"x"]?.Value, width, x);
407 y = ParseLengthOrPercentage(currObject.Attributes?[
"y"]?.Value, height, y);
409 double dx = ParseLengthOrPercentage(currObject.Attributes?[
"dx"]?.Value, width, 0);
410 double dy = ParseLengthOrPercentage(currObject.Attributes?[
"dy"]?.Value, height, 0);
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;
419 bool hadClippingPath = ApplyClipPath(currObject, gpr, width, height, diagonal, attributes, styleSheets, gradients);
421 if (currObject.ChildNodes.OfType<XmlNode>().Any(a => a.NodeType != XmlNodeType.Text))
423 foreach (XmlNode child
in currObject.ChildNodes)
425 InterpretTextObject(child, gpr, width, height, diagonal, currAttributes, styleSheets, gradients, ref x, ref y, fontSize, fontFamily, textAlign);
433 if (currAttributes.NeedsRestore)
440 string text = currObject.InnerText;
442 if (!
double.IsNaN(fontSize) && !
string.IsNullOrEmpty(text))
444 FontFamily parsedFontFamily = ParseFontFamily(fontFamily, currAttributes.EmbeddedFonts);
445 string fontWeight = currObject.Attributes?[
"font-weight"]?.Value;
446 string fontStyle = currObject.Attributes?[
"font-style"]?.Value;
448 if (fontWeight !=
null && (fontWeight.Equals(
"bold", StringComparison.OrdinalIgnoreCase) || fontWeight.Equals(
"bolder", StringComparison.OrdinalIgnoreCase) || (
int.TryParse(fontWeight, out
int weight) && weight >= 500)))
450 parsedFontFamily = GetBoldFontFamily(parsedFontFamily);
453 if (fontStyle !=
null && (fontStyle.Equals(
"italic", StringComparison.OrdinalIgnoreCase) || fontStyle.Equals(
"oblique", StringComparison.OrdinalIgnoreCase)))
455 parsedFontFamily = GetItalicFontFamily(parsedFontFamily);
458 Font fnt =
new Font(parsedFontFamily, fontSize);
462 if (fnt.FontFamily.TrueTypeFile !=
null)
464 Font.DetailedFontMetrics metrics = fnt.MeasureTextAdvanced(text);
465 x += metrics.LeftSideBearing;
467 if (!
string.IsNullOrEmpty(textAlign) && (textAlign.Equals(
"right", StringComparison.OrdinalIgnoreCase) || textAlign.Equals(
"end", StringComparison.OrdinalIgnoreCase)))
469 x -= metrics.Width + metrics.LeftSideBearing;
471 else if (!
string.IsNullOrEmpty(textAlign) && textAlign.Equals(
"center", StringComparison.OrdinalIgnoreCase))
473 x -= metrics.Width * 0.5;
476 endX += metrics.AdvanceWidth;
481 string textBaseline = currObject.Attributes?[
"alignment-baseline"]?.Value;
483 if (textBaseline !=
null)
485 if (textBaseline.Equals(
"text-bottom", StringComparison.OrdinalIgnoreCase) || textBaseline.Equals(
"bottom", StringComparison.OrdinalIgnoreCase))
489 if (textBaseline.Equals(
"middle", StringComparison.OrdinalIgnoreCase) || textBaseline.Equals(
"central", StringComparison.OrdinalIgnoreCase) || textBaseline.Equals(
"center", StringComparison.OrdinalIgnoreCase))
493 if (textBaseline.Equals(
"text-top", StringComparison.OrdinalIgnoreCase) || textBaseline.Equals(
"top", StringComparison.OrdinalIgnoreCase) || textBaseline.Equals(
"hanging", StringComparison.OrdinalIgnoreCase))
499 if (currAttributes.StrokeFirst)
501 if (currAttributes.Stroke !=
null)
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);
507 if (currAttributes.Fill !=
null)
509 Brush fillColour = currAttributes.Fill.MultiplyOpacity(currAttributes.Opacity * currAttributes.FillOpacity);
510 gpr.FillText(x, y, text, fnt, fillColour, baseline);
515 if (currAttributes.Fill !=
null)
517 Brush fillColour = currAttributes.Fill.MultiplyOpacity(currAttributes.Opacity * currAttributes.FillOpacity);
518 gpr.FillText(x, y, text, fnt, fillColour, baseline);
521 if (currAttributes.Stroke !=
null)
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);
536 if (currAttributes.NeedsRestore)
543 private static FontFamily GetBoldFontFamily(FontFamily fontFamily)
545 switch (fontFamily.FileName)
549 return FontFamily.ResolveFontFamily(FontFamily.StandardFontFamilies.TimesBold);
551 case "Times-BoldItalic":
552 return FontFamily.ResolveFontFamily(FontFamily.StandardFontFamilies.TimesBoldItalic);
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);
561 return FontFamily.ResolveFontFamily(FontFamily.StandardFontFamilies.CourierBold);
562 case "Courier-Oblique":
563 case "Courier-BoldOblique":
564 return FontFamily.ResolveFontFamily(FontFamily.StandardFontFamilies.CourierBoldOblique);
570 private static FontFamily GetItalicFontFamily(FontFamily fontFamily)
572 switch (fontFamily.FileName)
576 return FontFamily.ResolveFontFamily(FontFamily.StandardFontFamilies.TimesItalic);
578 case "Times-BoldItalic":
579 return FontFamily.ResolveFontFamily(FontFamily.StandardFontFamilies.TimesBoldItalic);
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);
587 case "Courier-Oblique":
588 return FontFamily.ResolveFontFamily(FontFamily.StandardFontFamilies.CourierOblique);
590 case "Courier-BoldOblique":
591 return FontFamily.ResolveFontFamily(FontFamily.StandardFontFamilies.CourierBoldOblique);
597 private static FontFamily ParseFontFamily(
string fontFamily, Dictionary<string, FontFamily> embeddedFonts)
599 string[] fontFamilies = Regexes.FontFamilySeparator.Split(fontFamily);
601 foreach (
string fam
in fontFamilies)
603 string family = fam.Trim().Trim(
',',
'"').Trim();
605 if (embeddedFonts.TryGetValue(family, out FontFamily tbr))
610 List<(string, int)> matchedFamilies =
new List<(
string,
int)>();
612 for (
int i = 0; i < FontFamily.StandardFamilies.Length; i++)
614 if (family.StartsWith(FontFamily.StandardFamilies[i]))
616 matchedFamilies.Add((FontFamily.StandardFamilies[i], FontFamily.StandardFamilies[i].Length));
620 if (matchedFamilies.Count > 0)
622 return FontFamily.ResolveFontFamily((from el in matchedFamilies orderby el.Item2 descending select el.Item1).First());
626 if (family.Equals(
"serif", StringComparison.OrdinalIgnoreCase))
628 return FontFamily.ResolveFontFamily(FontFamily.StandardFontFamilies.TimesRoman);
630 else if (family.Equals(
"sans-serif", StringComparison.OrdinalIgnoreCase))
632 return FontFamily.ResolveFontFamily(FontFamily.StandardFontFamilies.Helvetica);
634 else if (family.Equals(
"monospace", StringComparison.OrdinalIgnoreCase))
636 return FontFamily.ResolveFontFamily(FontFamily.StandardFontFamilies.Courier);
638 else if (family.Equals(
"cursive", StringComparison.OrdinalIgnoreCase))
640 return FontFamily.ResolveFontFamily(FontFamily.StandardFontFamilies.TimesItalic);
642 else if (family.Equals(
"system-ui", StringComparison.OrdinalIgnoreCase))
644 return FontFamily.ResolveFontFamily(FontFamily.StandardFontFamilies.Helvetica);
646 else if (family.Equals(
"ui-serif", StringComparison.OrdinalIgnoreCase))
648 return FontFamily.ResolveFontFamily(FontFamily.StandardFontFamilies.TimesRoman);
650 else if (family.Equals(
"ui-sans-serif", StringComparison.OrdinalIgnoreCase))
652 return FontFamily.ResolveFontFamily(FontFamily.StandardFontFamilies.Helvetica);
654 else if (family.Equals(
"ui-monospace", StringComparison.OrdinalIgnoreCase))
656 return FontFamily.ResolveFontFamily(FontFamily.StandardFontFamilies.Courier);
658 else if (family.Equals(
"StandardSymbolsPS", StringComparison.OrdinalIgnoreCase))
660 return FontFamily.ResolveFontFamily(FontFamily.StandardFontFamilies.Symbol);
662 else if (family.Equals(
"D050000L", StringComparison.OrdinalIgnoreCase))
664 return FontFamily.ResolveFontFamily(FontFamily.StandardFontFamilies.ZapfDingbats);
669 return FontFamily.ResolveFontFamily(FontFamily.StandardFontFamilies.Helvetica);
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)
674 PresentationAttributes currAttributes = InterpretPresentationAttributes(currObject, attributes, width, height, diagonal, gpr, styleSheets, gradients);
676 bool hadClippingPath = ApplyClipPath(currObject, gpr, width, height, diagonal, attributes, styleSheets, gradients);
678 string filter = currObject.Attributes?[
"filter"]?.Value ?? currObject.Attributes?[
"mask"]?.Value;
680 if (!
string.IsNullOrEmpty(filter) && filter.StartsWith(
"url(#"))
682 filter = filter.Substring(5, filter.Length - 6);
685 if (!
string.IsNullOrEmpty(filter) && filters.ContainsKey(filter))
687 Graphics filteredGraphics =
new Graphics();
689 InterpretSVGChildren(currObject, filteredGraphics, currAttributes, width, height, diagonal, styleSheets, gradients, filters);
690 gpr.DrawGraphics(0, 0, filteredGraphics, filters[filter]);
694 InterpretSVGChildren(currObject, gpr, currAttributes, width, height, diagonal, styleSheets, gradients, filters);
702 if (currAttributes.NeedsRestore)
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)
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);
717 string id = currObject.Attributes?[
"href"]?.Value ?? currObject.Attributes?[
"xlink:href"]?.Value;
719 if (
id !=
null &&
id.StartsWith(
"#"))
721 id =
id.Substring(1);
723 XmlNode element = currObject.OwnerDocument.SelectSingleNode(
string.Format(
"//*[@id='{0}']",
id));
727 XmlNode clone = element.Clone();
729 currObject.AppendChild(clone);
732 PresentationAttributes currAttributes = InterpretPresentationAttributes(currObject, attributes, width, height, diagonal, gpr, styleSheets, gradients);
738 ((XmlElement)clone).SetAttribute(
"x",
"0");
739 ((XmlElement)clone).SetAttribute(
"y",
"0");
741 if (clone.Attributes?[
"viewBox"] !=
null)
743 ((XmlElement)clone).SetAttribute(
"width", w.ToString(System.Globalization.CultureInfo.InvariantCulture));
744 ((XmlElement)clone).SetAttribute(
"height", h.ToString(System.Globalization.CultureInfo.InvariantCulture));
747 InterpretSVGElement(clone, gpr, currAttributes, width, height, diagonal, styleSheets, gradients, filters);
751 if (currAttributes.NeedsRestore)
759 private static bool ApplyClipPath(XmlNode currObject, Graphics gpr,
double width,
double height,
double diagonal, PresentationAttributes attributes, IEnumerable<Stylesheet> styleSheets, Dictionary<string, Brush> gradients)
761 string id = currObject.Attributes?[
"clip-path"]?.Value;
763 if (
id !=
null &&
id.StartsWith(
"url(#"))
765 id =
id.Substring(5);
766 id =
id.Substring(0,
id.Length - 1);
768 XmlNode element = currObject.OwnerDocument.SelectSingleNode(
string.Format(
"//*[@id='{0}']",
id));
770 if (element !=
null && element.ChildNodes.Count == 1 && element.ChildNodes[0].Name.Equals(
"path", StringComparison.OrdinalIgnoreCase))
772 bool hasParentClipPath = ApplyClipPath(element, gpr, width, height, diagonal, attributes, styleSheets, gradients);
774 Graphics pathGraphics =
new Graphics();
775 InterpretPathObject(element.ChildNodes[0], pathGraphics, width, height, diagonal, attributes, styleSheets, gradients);
777 PathTransformerGraphicsContext ptgc =
new PathTransformerGraphicsContext();
778 pathGraphics.CopyToIGraphicsContext(ptgc);
780 if (!hasParentClipPath)
785 gpr.SetClippingPath(ptgc.CurrentPath);
798 private static void InterpretRectObject(XmlNode currObject, Graphics gpr,
double width,
double height,
double diagonal, PresentationAttributes attributes, IEnumerable<Stylesheet> styleSheets, Dictionary<string, Brush> gradients)
800 double x, y, w, h, rx, ry;
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);
811 if (
double.IsNaN(rx) && !
double.IsNaN(ry))
815 else if (!
double.IsNaN(rx) &&
double.IsNaN(ry))
820 if (
double.IsNaN(rx))
825 if (
double.IsNaN(ry))
830 rx = Math.Min(rx, w / 2);
831 ry = Math.Min(ry, h / 2);
833 GraphicsPath path =
new GraphicsPath();
835 path.MoveTo(x + rx, y);
836 path.LineTo(x + w - rx, y);
838 if (rx > 0 && ry > 0)
840 path.EllipticalArc(rx, ry, 0,
false,
true,
new Point(x + w, y + ry));
843 path.LineTo(x + w, y + h - ry);
845 if (rx > 0 && ry > 0)
847 path.EllipticalArc(rx, ry, 0,
false,
true,
new Point(x + w - rx, y + h));
850 path.LineTo(x + rx, y + h);
852 if (rx > 0 && ry > 0)
854 path.EllipticalArc(rx, ry, 0,
false,
true,
new Point(x, y + h - ry));
857 path.LineTo(x, y + ry);
859 if (rx > 0 && ry > 0)
861 path.EllipticalArc(rx, ry, 0,
false,
true,
new Point(x + rx, y));
866 PresentationAttributes currAttributes = InterpretPresentationAttributes(currObject, attributes, width, height, diagonal, gpr, styleSheets, gradients);
868 bool hadClippingPath = ApplyClipPath(currObject, gpr, width, height, diagonal, attributes, styleSheets, gradients);
870 if (currAttributes.StrokeFirst)
872 if (currAttributes.Stroke !=
null)
874 Brush strokeColour = currAttributes.Stroke.MultiplyOpacity(currAttributes.Opacity * currAttributes.StrokeOpacity);
875 gpr.StrokePath(path, strokeColour, currAttributes.StrokeThickness, currAttributes.LineCap, currAttributes.LineJoin, currAttributes.LineDash);
878 if (currAttributes.Fill !=
null)
880 Brush fillColour = currAttributes.Fill.MultiplyOpacity(currAttributes.Opacity * currAttributes.FillOpacity);
881 gpr.FillPath(path, fillColour);
886 if (currAttributes.Fill !=
null)
888 Brush fillColour = currAttributes.Fill.MultiplyOpacity(currAttributes.Opacity * currAttributes.FillOpacity);
889 gpr.FillPath(path, fillColour);
892 if (currAttributes.Stroke !=
null)
894 Brush strokeColour = currAttributes.Stroke.MultiplyOpacity(currAttributes.Opacity * currAttributes.StrokeOpacity);
895 gpr.StrokePath(path, strokeColour, currAttributes.StrokeThickness, currAttributes.LineCap, currAttributes.LineJoin, currAttributes.LineDash);
904 if (currAttributes.NeedsRestore)
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)
913 string points = currObject.Attributes?[
"points"]?.Value;
917 double[] coordinates = ParseListOfDoubles(points);
919 GraphicsPath path =
new GraphicsPath();
921 for (
int i = 0; i < coordinates.Length; i += 2)
923 path.LineTo(coordinates[i], coordinates[i + 1]);
931 PresentationAttributes currAttributes = InterpretPresentationAttributes(currObject, attributes, width, height, diagonal, gpr, styleSheets, gradients);
933 bool hadClippingPath = ApplyClipPath(currObject, gpr, width, height, diagonal, attributes, styleSheets, gradients);
935 if (currAttributes.StrokeFirst)
937 if (currAttributes.Stroke !=
null)
939 Brush strokeColour = currAttributes.Stroke.MultiplyOpacity(currAttributes.Opacity * currAttributes.StrokeOpacity);
940 gpr.StrokePath(path, strokeColour, currAttributes.StrokeThickness, currAttributes.LineCap, currAttributes.LineJoin, currAttributes.LineDash);
943 if (currAttributes.Fill !=
null)
945 Brush fillColour = currAttributes.Fill.MultiplyOpacity(currAttributes.Opacity * currAttributes.FillOpacity);
946 gpr.FillPath(path, fillColour);
951 if (currAttributes.Fill !=
null)
953 Brush fillColour = currAttributes.Fill.MultiplyOpacity(currAttributes.Opacity * currAttributes.FillOpacity);
954 gpr.FillPath(path, fillColour);
957 if (currAttributes.Stroke !=
null)
959 Brush strokeColour = currAttributes.Stroke.MultiplyOpacity(currAttributes.Opacity * currAttributes.StrokeOpacity);
960 gpr.StrokePath(path, strokeColour, currAttributes.StrokeThickness, currAttributes.LineCap, currAttributes.LineJoin, currAttributes.LineDash);
969 if (currAttributes.NeedsRestore)
976 private static void InterpretPathObject(XmlNode currObject, Graphics gpr,
double width,
double height,
double diagonal, PresentationAttributes attributes, IEnumerable<Stylesheet> styleSheets, Dictionary<string, Brush> gradients)
978 string d = currObject.Attributes?[
"d"]?.Value;
982 List<string> pathData = TokenisePathData(d);
984 GraphicsPath path =
new GraphicsPath();
986 Point lastPoint =
new Point();
987 Point? figureStartPoint =
null;
989 char lastCommand =
'\0';
990 Point lastCtrlPoint =
new Point();
992 for (
int i = 0; i < pathData.Count; i++)
994 Point delta =
new Point();
996 bool isAbsolute =
char.IsUpper(pathData[i][0]);
1003 switch (pathData[i][0])
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;
1012 while (i < pathData.Count - 1 && !
char.IsLetter(pathData[i + 1][0]))
1020 delta =
new Point();
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);
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)
1036 figureStartPoint = lastPoint;
1040 while (i < pathData.Count - 1 && !
char.IsLetter(pathData[i + 1][0]))
1048 delta =
new Point();
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);
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)
1063 figureStartPoint = lastPoint;
1067 while (i < pathData.Count - 1 && !
char.IsLetter(pathData[i + 1][0]))
1075 delta =
new Point();
1078 lastPoint =
new Point(delta.X +
double.Parse(pathData[i + 1], System.Globalization.CultureInfo.InvariantCulture), lastPoint.Y);
1079 path.LineTo(lastPoint);
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)
1090 figureStartPoint = lastPoint;
1094 while (i < pathData.Count - 1 && !
char.IsLetter(pathData[i + 1][0]))
1102 delta =
new Point();
1105 lastPoint =
new Point(lastPoint.X, delta.Y +
double.Parse(pathData[i + 1], System.Globalization.CultureInfo.InvariantCulture));
1106 path.LineTo(lastPoint);
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));
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));
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));
1123 if (figureStartPoint ==
null)
1125 figureStartPoint = lastPoint;
1128 path.CubicBezierTo(ctrlPoint1, ctrlPoint2, lastPoint);
1130 lastCtrlPoint = ctrlPoint2;
1133 while (i < pathData.Count - 1 && !
char.IsLetter(pathData[i + 1][0]))
1141 delta =
new Point();
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));
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));
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));
1153 path.CubicBezierTo(ctrlPoint1, ctrlPoint2, lastPoint);
1162 if (lastCommand ==
'C')
1164 ctrlPoint1 =
new Point(2 * lastPoint.X - lastCtrlPoint.X, 2 * lastPoint.Y - lastCtrlPoint.Y);
1168 ctrlPoint1 = lastPoint;
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));
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));
1177 if (figureStartPoint ==
null)
1179 figureStartPoint = lastPoint;
1182 path.CubicBezierTo(ctrlPoint1, ctrlPoint2, lastPoint);
1184 lastCtrlPoint = ctrlPoint2;
1187 while (i < pathData.Count - 1 && !
char.IsLetter(pathData[i + 1][0]))
1195 delta =
new Point();
1198 ctrlPoint1 =
new Point(2 * lastPoint.X - lastCtrlPoint.X, 2 * lastPoint.Y - lastCtrlPoint.Y);
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));
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));
1206 path.CubicBezierTo(ctrlPoint1, ctrlPoint2, lastPoint);
1208 lastCtrlPoint = ctrlPoint2;
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));
1218 Point actualCP1 =
new Point(lastPoint.X + 2 * (ctrlPoint.X - lastPoint.X) / 3, lastPoint.Y + 2 * (ctrlPoint.Y - lastPoint.Y) / 3);
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));
1223 Point actualCP2 =
new Point(lastPoint.X + 2 * (ctrlPoint.X - lastPoint.X) / 3, lastPoint.Y + 2 * (ctrlPoint.Y - lastPoint.Y) / 3);
1225 if (figureStartPoint ==
null)
1227 figureStartPoint = lastPoint;
1230 path.CubicBezierTo(actualCP1, actualCP2, lastPoint);
1232 lastCtrlPoint = ctrlPoint;
1235 while (i < pathData.Count - 1 && !
char.IsLetter(pathData[i + 1][0]))
1243 delta =
new Point();
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));
1249 actualCP1 =
new Point(lastPoint.X + 2 * (ctrlPoint.X - lastPoint.X) / 3, lastPoint.Y + 2 * (ctrlPoint.Y - lastPoint.Y) / 3);
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));
1254 actualCP2 =
new Point(lastPoint.X + 2 * (ctrlPoint.X - lastPoint.X) / 3, lastPoint.Y + 2 * (ctrlPoint.Y - lastPoint.Y) / 3);
1256 path.CubicBezierTo(actualCP1, actualCP2, lastPoint);
1257 lastCtrlPoint = ctrlPoint;
1268 if (lastCommand ==
'Q')
1270 ctrlPoint =
new Point(2 * lastPoint.X - lastCtrlPoint.X, 2 * lastPoint.Y - lastCtrlPoint.Y);
1274 ctrlPoint = lastPoint;
1277 Point actualCP1 =
new Point(lastPoint.X + 2 * (ctrlPoint.X - lastPoint.X) / 3, lastPoint.Y + 2 * (ctrlPoint.Y - lastPoint.Y) / 3);
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));
1282 Point actualCP2 =
new Point(lastPoint.X + 2 * (ctrlPoint.X - lastPoint.X) / 3, lastPoint.Y + 2 * (ctrlPoint.Y - lastPoint.Y) / 3);
1284 if (figureStartPoint ==
null)
1286 figureStartPoint = lastPoint;
1289 path.CubicBezierTo(actualCP1, actualCP2, lastPoint);
1290 lastCtrlPoint = ctrlPoint;
1293 while (i < pathData.Count - 1 && !
char.IsLetter(pathData[i + 1][0]))
1301 delta =
new Point();
1304 ctrlPoint =
new Point(2 * lastPoint.X - lastCtrlPoint.X, 2 * lastPoint.Y - lastCtrlPoint.Y);
1306 actualCP1 =
new Point(lastPoint.X + 2 * (ctrlPoint.X - lastPoint.X) / 3, lastPoint.Y + 2 * (ctrlPoint.Y - lastPoint.Y) / 3);
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));
1311 actualCP2 =
new Point(lastPoint.X + 2 * (ctrlPoint.X - lastPoint.X) / 3, lastPoint.Y + 2 * (ctrlPoint.Y - lastPoint.Y) / 3);
1313 path.CubicBezierTo(actualCP1, actualCP2, lastPoint);
1315 lastCtrlPoint = ctrlPoint;
1322 Point startPoint = lastPoint;
1324 if (figureStartPoint ==
null)
1326 figureStartPoint = lastPoint;
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';
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));
1337 path.EllipticalArc(radii.X, radii.Y, angle, largeArcFlag, sweepFlag, lastPoint);
1339 while (i < pathData.Count - 1 && !
char.IsLetter(pathData[i + 1][0]))
1347 delta =
new Point();
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';
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));
1359 path.EllipticalArc(radii.X, radii.Y, angle, largeArcFlag, sweepFlag, lastPoint);
1367 lastPoint = figureStartPoint.Value;
1368 figureStartPoint =
null;
1374 PresentationAttributes currAttributes = InterpretPresentationAttributes(currObject, attributes, width, height, diagonal, gpr, styleSheets, gradients);
1376 bool hadClippingPath = ApplyClipPath(currObject, gpr, width, height, diagonal, attributes, styleSheets, gradients);
1378 if (currAttributes.StrokeFirst)
1380 if (currAttributes.Stroke !=
null)
1382 Brush strokeColour = currAttributes.Stroke.MultiplyOpacity(currAttributes.Opacity * currAttributes.StrokeOpacity);
1383 gpr.StrokePath(path, strokeColour, currAttributes.StrokeThickness, currAttributes.LineCap, currAttributes.LineJoin, currAttributes.LineDash);
1386 if (currAttributes.Fill !=
null)
1388 Brush fillColour = currAttributes.Fill.MultiplyOpacity(currAttributes.Opacity * currAttributes.FillOpacity);
1389 gpr.FillPath(path, fillColour);
1394 if (currAttributes.Fill !=
null)
1396 Brush fillColour = currAttributes.Fill.MultiplyOpacity(currAttributes.Opacity * currAttributes.FillOpacity);
1397 gpr.FillPath(path, fillColour);
1400 if (currAttributes.Stroke !=
null)
1402 Brush strokeColour = currAttributes.Stroke.MultiplyOpacity(currAttributes.Opacity * currAttributes.StrokeOpacity);
1403 gpr.StrokePath(path, strokeColour, currAttributes.StrokeThickness, currAttributes.LineCap, currAttributes.LineJoin, currAttributes.LineDash);
1407 if (hadClippingPath)
1412 if (currAttributes.NeedsRestore)
1420 private static List<string> TokenisePathData(
string d)
1422 List<string> tbr =
new List<string>();
1424 string currToken =
"";
1426 for (
int i = 0; i < d.Length; i++)
1430 if (c >=
'0' && c <=
'9' || c ==
'.' || c ==
'e' || c ==
'E')
1434 else if (c ==
'-' || c ==
'+')
1436 if (i > 0 && (d[i - 1] ==
'e' || d[i - 1] ==
'E'))
1442 if (!
string.IsNullOrEmpty(currToken))
1449 else if (
char.IsWhiteSpace(c) || c ==
',')
1451 if (!
string.IsNullOrEmpty(currToken))
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'))
1459 if (!
string.IsNullOrEmpty(currToken))
1467 else if (
"MmLlHhVvCcSsQqTtAaZz".Contains(c))
1469 if (!
string.IsNullOrEmpty(currToken))
1473 tbr.Add(c.ToString());
1478 if (!
string.IsNullOrEmpty(currToken))
1486 private static void InterpretCircleObject(XmlNode circleObject, Graphics gpr,
double width,
double height,
double diagonal, PresentationAttributes attributes, IEnumerable<Stylesheet> styleSheets, Dictionary<string, Brush> gradients)
1490 cx = ParseLengthOrPercentage(circleObject.Attributes?[
"cx"]?.Value, width);
1491 cy = ParseLengthOrPercentage(circleObject.Attributes?[
"cy"]?.Value, height);
1492 r = ParseLengthOrPercentage(circleObject.Attributes?[
"r"]?.Value, diagonal);
1494 PresentationAttributes circleAttributes = InterpretPresentationAttributes(circleObject, attributes, width, height, diagonal, gpr, styleSheets, gradients);
1496 bool hadClippingPath = ApplyClipPath(circleObject, gpr, width, height, diagonal, attributes, styleSheets, gradients);
1498 if (circleAttributes.StrokeFirst)
1500 if (circleAttributes.Stroke !=
null)
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);
1506 if (circleAttributes.Fill !=
null)
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);
1514 if (circleAttributes.Fill !=
null)
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);
1520 if (circleAttributes.Stroke !=
null)
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);
1527 if (hadClippingPath)
1532 if (circleAttributes.NeedsRestore)
1538 private static void InterpretEllipseObject(XmlNode currObject, Graphics gpr,
double width,
double height,
double diagonal, PresentationAttributes attributes, IEnumerable<Stylesheet> styleSheets, Dictionary<string, Brush> gradients)
1540 double cx, cy, rx, ry;
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);
1547 if (
double.IsNaN(rx) && !
double.IsNaN(ry))
1551 else if (!
double.IsNaN(rx) &&
double.IsNaN(ry))
1556 if (rx > 0 && ry > 0)
1559 PresentationAttributes currAttributes = InterpretPresentationAttributes(currObject, attributes, width, height, diagonal, gpr, styleSheets, gradients);
1561 bool hadClippingPath = ApplyClipPath(currObject, gpr, width, height, diagonal, attributes, styleSheets, gradients);
1563 double r = Math.Min(rx, ry);
1566 gpr.Translate(cx, cy);
1567 gpr.Scale(rx / r, ry / r);
1569 if (currAttributes.StrokeFirst)
1571 if (currAttributes.Stroke !=
null)
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);
1577 if (currAttributes.Fill !=
null)
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);
1585 if (currAttributes.Fill !=
null)
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);
1591 if (currAttributes.Stroke !=
null)
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);
1600 if (hadClippingPath)
1605 if (currAttributes.NeedsRestore)
1612 private static void InterpretLineObject(XmlNode lineObject, Graphics gpr,
double width,
double height,
double diagonal, PresentationAttributes attributes, IEnumerable<Stylesheet> styleSheets, Dictionary<string, Brush> gradients)
1614 double x1, x2, y1, y2;
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);
1621 PresentationAttributes lineAttributes = InterpretPresentationAttributes(lineObject, attributes, width, height, diagonal, gpr, styleSheets, gradients);
1623 bool hadClippingPath = ApplyClipPath(lineObject, gpr, width, height, diagonal, attributes, styleSheets, gradients);
1625 if (lineAttributes.Stroke !=
null)
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);
1631 if (hadClippingPath)
1636 if (lineAttributes.NeedsRestore)
1642 private static void SetStyleAttributes(XmlNode obj, IEnumerable<Stylesheet> styleSheets)
1644 string style = obj.Attributes?[
"style"]?.Value;
1646 string classes = obj.Attributes?[
"class"]?.Value;
1648 if (!
string.IsNullOrEmpty(classes))
1650 string[] splitClasses = classes.Split(
' ');
1652 foreach (
string className
in splitClasses)
1654 if (!
string.IsNullOrEmpty(className.Trim()))
1656 foreach (Stylesheet sheet
in styleSheets)
1658 foreach (StyleRule rule
in sheet.StyleRules)
1660 if (rule.SelectorText.Contains(
"." + className))
1662 style = rule.Style.CssText +
"; " + style;
1670 if (!
string.IsNullOrEmpty(style))
1672 string[] splitStyle = style.Split(
';');
1674 for (
int i = 0; i < splitStyle.Length; i++)
1676 string[] styleCouple = splitStyle[i].Split(
':');
1678 if (styleCouple.Length == 2)
1680 string styleName = styleCouple[0].Trim();
1681 string styleValue = styleCouple[1].Trim();
1683 ((XmlElement)obj).SetAttribute(styleName, styleValue);
1685 else if (!
string.IsNullOrWhiteSpace(splitStyle[i]))
1687 throw new InvalidOperationException(
"The style specification is not valid: " + splitStyle[i]);
1693 internal static PresentationAttributes InterpretPresentationAttributes(XmlNode obj, PresentationAttributes parentPresentationAttributes,
double width,
double height,
double diagonal, Graphics gpr, IEnumerable<Stylesheet> styleSheets, Dictionary<string, Brush> gradients)
1695 SetStyleAttributes(obj, styleSheets);
1697 PresentationAttributes tbr = parentPresentationAttributes.Clone();
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;
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;
1717 string transform = obj.Attributes?[
"transform"]?.Value;
1721 tbr.X = ParseLengthOrPercentage(xA, width);
1726 tbr.Y = ParseLengthOrPercentage(yA, height);
1731 tbr.Width = ParseLengthOrPercentage(wA, width);
1736 tbr.Height = ParseLengthOrPercentage(hA, height);
1741 if (stroke.Trim().StartsWith(
"url("))
1743 string url = stroke.Trim().Substring(4);
1744 if (url.EndsWith(
")"))
1746 url = url.Substring(0, url.Length - 1);
1751 if (url.StartsWith(
"#"))
1753 url = url.Substring(1);
1754 if (gradients.TryGetValue(url, out Brush brush))
1766 tbr.Stroke = Colour.FromCSSString(stroke);
1770 if (strokeOpacity !=
null)
1772 tbr.StrokeOpacity = ParseLengthOrPercentage(strokeOpacity, 1);
1777 if (fill.Trim().StartsWith(
"url("))
1779 string url = fill.Trim().Substring(4);
1780 if (url.EndsWith(
")"))
1782 url = url.Substring(0, url.Length - 1);
1787 if (url.StartsWith(
"#"))
1789 url = url.Substring(1);
1790 if (gradients.TryGetValue(url, out Brush brush))
1802 tbr.Fill = Colour.FromCSSString(fill);
1806 if (fillOpacity !=
null)
1808 tbr.FillOpacity = ParseLengthOrPercentage(fillOpacity, 1);
1811 if (currentColour !=
null)
1813 tbr.CurrentColour = Colour.FromCSSString(currentColour);
1816 if (strokeThickness !=
null)
1818 tbr.StrokeThickness = ParseLengthOrPercentage(strokeThickness, diagonal);
1821 if (lineCap !=
null)
1823 if (lineCap.Equals(
"butt", StringComparison.OrdinalIgnoreCase))
1827 else if (lineCap.Equals(
"round", StringComparison.OrdinalIgnoreCase))
1831 else if (lineCap.Equals(
"square", StringComparison.OrdinalIgnoreCase))
1837 if (lineJoin !=
null)
1839 if (lineJoin.Equals(
"bevel", StringComparison.OrdinalIgnoreCase))
1843 else if (lineJoin.Equals(
"miter", StringComparison.OrdinalIgnoreCase) || lineJoin.Equals(
"miter-clip", StringComparison.OrdinalIgnoreCase))
1847 else if (lineJoin.Equals(
"round", StringComparison.OrdinalIgnoreCase))
1853 if (opacity !=
null)
1855 tbr.Opacity = ParseLengthOrPercentage(opacity, 1);
1858 if (strokeDashArray !=
null)
1860 if (strokeDashArray !=
"none")
1862 double[] parsedArray = ParseListOfDoubles(strokeDashArray);
1864 tbr.LineDash =
new LineDash(parsedArray[0], parsedArray.Length > 1 ? parsedArray[1] : parsedArray[0], tbr.LineDash.Phase);
1868 tbr.LineDash = LineDash.SolidLine;
1872 if (strokeDashOffset !=
null)
1874 tbr.LineDash =
new LineDash(tbr.LineDash.UnitsOn, tbr.LineDash.UnitsOff, ParseLengthOrPercentage(strokeDashOffset, diagonal));
1877 if (paintOrder !=
null)
1879 if (paintOrder.Equals(
"normal", StringComparison.OrdinalIgnoreCase))
1881 tbr.StrokeFirst =
false;
1885 if (paintOrder.IndexOf(
"stroke", StringComparison.OrdinalIgnoreCase) >= 0 && (paintOrder.IndexOf(
"fill", StringComparison.OrdinalIgnoreCase) < 0 || paintOrder.IndexOf(
"fill", StringComparison.OrdinalIgnoreCase) > paintOrder.IndexOf(
"stroke", StringComparison.OrdinalIgnoreCase)))
1887 tbr.StrokeFirst =
true;
1891 tbr.StrokeFirst =
false;
1896 if (transform !=
null)
1899 tbr.NeedsRestore =
true;
1901 string[] transforms = ParseListOfTransforms(transform);
1903 for (
int i = 0; i < transforms.Length; i++)
1905 if (transforms[i].Equals(
"matrix", StringComparison.OrdinalIgnoreCase))
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);
1914 gpr.Transform(a, b, c, d, e, f);
1917 else if (transforms[i].Equals(
"translate", StringComparison.OrdinalIgnoreCase))
1919 double x = ParseLengthOrPercentage(transforms[i + 1], 1);
1923 if (i < transforms.Length - 2 && !
double.IsNaN(y = ParseLengthOrPercentage(transforms[i + 2], 1)))
1925 gpr.Translate(x, y);
1930 gpr.Translate(x, 0);
1934 else if (transforms[i].Equals(
"scale", StringComparison.OrdinalIgnoreCase))
1936 double x = ParseLengthOrPercentage(transforms[i + 1], 1);
1940 if (i < transforms.Length - 2 && !
double.IsNaN(y = ParseLengthOrPercentage(transforms[i + 2], 1)))
1951 else if (transforms[i].Equals(
"rotate", StringComparison.OrdinalIgnoreCase))
1953 double a = ParseLengthOrPercentage(transforms[i + 1], 1) * Math.PI / 180;
1957 if (i < transforms.Length - 3 && !
double.IsNaN(x = ParseLengthOrPercentage(transforms[i + 2], 1)) && !
double.IsNaN(y = ParseLengthOrPercentage(transforms[i + 3], 1)))
1959 gpr.RotateAt(a,
new Point(x, y));
1968 else if (transforms[i].Equals(
"skewX", StringComparison.OrdinalIgnoreCase))
1970 double psi = ParseLengthOrPercentage(transforms[i + 1], 1) * Math.PI / 180;
1972 gpr.Transform(1, 0, Math.Tan(psi), 1, 0, 0);
1976 else if (transforms[i].Equals(
"skewY", StringComparison.OrdinalIgnoreCase))
1978 double psi = ParseLengthOrPercentage(transforms[i + 1], 1) * Math.PI / 180;
1980 gpr.Transform(1, Math.Tan(psi), 0, 1, 0, 0);
1990 private static double ParseLengthOrPercentage(
string value,
double total,
double defaultValue = 0)
1994 if (value.Contains(
"%"))
1996 value = value.Replace(
"%",
"");
1997 return double.Parse(value, System.Globalization.CultureInfo.InvariantCulture) * total / 100;
1999 else if (
double.TryParse(value.Replace(
"px",
"").Replace(
"pt",
""), System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out
double result))
2005 string cleanedNumber = Regexes.NumberRegex.Match(value).Value;
2006 return double.Parse(cleanedNumber, System.Globalization.CultureInfo.InvariantCulture);
2011 return defaultValue;
2015 internal class PresentationAttributes
2017 public Dictionary<string, FontFamily> EmbeddedFonts;
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;
2027 public double Opacity = 1;
2028 public LineDash LineDash =
new LineDash(0, 0, 0);
2029 public bool NeedsRestore =
false;
2030 public bool StrokeFirst =
false;
2033 public double Width;
2034 public double Height;
2036 public PresentationAttributes Clone()
2038 return new PresentationAttributes()
2040 EmbeddedFonts = this.EmbeddedFonts,
2042 Stroke = this.Stroke,
2043 StrokeOpacity = this.StrokeOpacity,
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,
2056 Height = this.Height
2061 private static class Regexes
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);
2068 private static double[] ParseListOfDoubles(
string value)
2075 string[] splitValue = Regexes.ListSeparator.Split(value);
2076 double[] tbr =
new double[splitValue.Length];
2078 for (
int i = 0; i < splitValue.Length; i++)
2080 tbr[i] =
double.Parse(splitValue[i], System.Globalization.CultureInfo.InvariantCulture);
2086 private static string[] ParseListOfTransforms(
string value)
2093 string[] splitValue = Regexes.ListSeparator.Split(value.Replace(
"(",
" ").Replace(
")",
" ").Trim());
2098 private static List<KeyValuePair<string, FontFamily>> GetEmbeddedFonts(
string styleBlock)
2100 StringReader sr =
new StringReader(styleBlock);
2102 List<KeyValuePair<string, FontFamily>> tbr =
new List<KeyValuePair<string, FontFamily>>();
2104 while (sr.Peek() >= 0)
2106 string token = ReadCSSToken(sr);
2108 if (token.Equals(
"@font-face", StringComparison.OrdinalIgnoreCase))
2110 List<string> tokens =
new List<string>();
2112 while (!token.Equals(
"}", StringComparison.OrdinalIgnoreCase))
2114 token = ReadCSSToken(sr);
2118 KeyValuePair<string, FontFamily>? fontFace = ParseFontFaceBlock(tokens);
2120 if (fontFace !=
null)
2122 tbr.Add(fontFace.Value);
2130 private static IEnumerable<KeyValuePair<string, IFilter>> GetFilters(XmlNode definitionsNode, List<Stylesheet> styleSheets)
2132 Dictionary<string, IFilter> tbr =
new Dictionary<string, IFilter>();
2134 foreach (XmlNode definition
in definitionsNode.ChildNodes)
2136 if (definition.Name.Equals(
"filter", StringComparison.OrdinalIgnoreCase))
2138 XmlElement filter = (XmlElement)definition;
2140 string id = filter.GetAttribute(
"id");
2142 List<ILocationInvariantFilter> filterElements =
new List<ILocationInvariantFilter>();
2144 foreach (XmlNode filterDefinition
in definition.ChildNodes)
2146 if (filterDefinition.Name.Equals(
"feGaussianBlur", StringComparison.OrdinalIgnoreCase))
2148 XmlElement actualFilter = (XmlElement)filterDefinition;
2150 string stdDeviation = actualFilter.GetAttribute(
"stdDeviation");
2152 filterElements.Add(
new GaussianBlurFilter(
double.Parse(stdDeviation, System.Globalization.CultureInfo.InvariantCulture)));
2154 else if (filterDefinition.Name.Equals(
"feColorMatrix", StringComparison.OrdinalIgnoreCase))
2156 XmlElement actualFilter = (XmlElement)filterDefinition;
2158 string type = actualFilter.GetAttribute(
"type");
2160 if (type.Equals(
"matrix", StringComparison.OrdinalIgnoreCase))
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();
2165 if (parsedValues.Length == 20)
2167 double[,] matrix =
new double[5, 5];
2170 for (
int i = 0; i < 20; i++)
2175 matrix[y, x] = parsedValues[i];
2184 if (filterElements.Count > 0)
2186 if (filterElements.Count == 1)
2188 tbr.Add(
id, filterElements[0]);
2201 private static IEnumerable<KeyValuePair<string, XmlNode>> GetMasks(XmlNode definitionsNode, List<Stylesheet> styleSheets)
2203 Dictionary<string, XmlNode> tbr =
new Dictionary<string, XmlNode>();
2205 foreach (XmlNode definition
in definitionsNode.ChildNodes)
2207 if (definition.Name.Equals(
"mask", StringComparison.OrdinalIgnoreCase))
2209 XmlElement mask = (XmlElement)definition;
2211 string id = mask.GetAttribute(
"id");
2213 tbr.Add(
id, definition);
2220 private static IEnumerable<KeyValuePair<string, Brush>> GetGradients(XmlNode definitionsNode, List<Stylesheet> styleSheets)
2222 Dictionary<string, Brush> tbr =
new Dictionary<string, Brush>();
2223 Dictionary<string, XmlNodeList> stopLists =
new Dictionary<string, XmlNodeList>();
2225 foreach (XmlNode definition
in definitionsNode.ChildNodes)
2227 if (definition.Name.Equals(
"linearGradient", StringComparison.OrdinalIgnoreCase))
2229 XmlElement gradient = (XmlElement)definition;
2231 string id = gradient.GetAttribute(
"id");
2238 if (!(gradient.HasAttribute(
"x1") &&
double.TryParse(gradient.GetAttribute(
"x1"), out x1)))
2243 if (!(gradient.HasAttribute(
"y1") &&
double.TryParse(gradient.GetAttribute(
"y1"), out y1)))
2248 if (!(gradient.HasAttribute(
"x2") &&
double.TryParse(gradient.GetAttribute(
"x2"), out x2)))
2253 if (!(gradient.HasAttribute(
"y2") &&
double.TryParse(gradient.GetAttribute(
"y2"), out y2)))
2258 List<GradientStop> gradientStops =
new List<GradientStop>();
2260 XmlNodeList childNodes;
2262 if (gradient.HasAttribute(
"xlink:href"))
2264 string refId = gradient.GetAttribute(
"xlink:href").Trim();
2266 if (refId.StartsWith(
"#"))
2268 refId = refId.Substring(1);
2270 if (!stopLists.TryGetValue(refId, out childNodes))
2272 childNodes = gradient.ChildNodes;
2277 childNodes = gradient.ChildNodes;
2282 childNodes = gradient.ChildNodes;
2285 stopLists[id] = childNodes;
2287 foreach (XmlNode stopNode
in childNodes)
2289 if (stopNode.Name.Equals(
"stop", StringComparison.OrdinalIgnoreCase))
2291 SetStyleAttributes(stopNode, styleSheets);
2293 XmlElement stop = (XmlElement)stopNode;
2298 if (stop.HasAttribute(
"offset"))
2300 offset = ParseLengthOrPercentage(stop.GetAttribute(
"offset"), 1);
2303 if (stop.HasAttribute(
"stop-opacity"))
2305 opacity = ParseLengthOrPercentage(stop.GetAttribute(
"stop-opacity"), 1);
2308 Colour stopColour = Colour.FromRgba(0, 0, 0, 0);
2310 if (stop.HasAttribute(
"stop-color"))
2312 stopColour = (Colour.FromCSSString(stop.GetAttribute(
"stop-color")) ?? stopColour).WithAlpha(opacity);
2315 gradientStops.Add(
new GradientStop(stopColour, offset));
2319 if (gradient.HasAttribute(
"gradientTransform"))
2321 string transform = gradient.GetAttribute(
"gradientTransform");
2322 string[] transforms = ParseListOfTransforms(transform);
2324 double[,] transformMatrix = MatrixUtils.Identity;
2326 for (
int i = 0; i < transforms.Length; i++)
2328 if (transforms[i].Equals(
"matrix", StringComparison.OrdinalIgnoreCase))
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);
2337 transformMatrix = MatrixUtils.Multiply(transformMatrix,
new double[,] { { a, c, e }, { b, d, f }, { 0, 0, 1 } });
2341 else if (transforms[i].Equals(
"translate", StringComparison.OrdinalIgnoreCase))
2343 double x = ParseLengthOrPercentage(transforms[i + 1], 1);
2347 if (i < transforms.Length - 2 && !
double.IsNaN(y = ParseLengthOrPercentage(transforms[i + 2], 1)))
2349 transformMatrix = MatrixUtils.Translate(transformMatrix, x, y);
2354 transformMatrix = MatrixUtils.Translate(transformMatrix, x, 0);
2358 else if (transforms[i].Equals(
"scale", StringComparison.OrdinalIgnoreCase))
2360 double x = ParseLengthOrPercentage(transforms[i + 1], 1);
2364 if (i < transforms.Length - 2 && !
double.IsNaN(y = ParseLengthOrPercentage(transforms[i + 2], 1)))
2366 transformMatrix = MatrixUtils.Scale(transformMatrix, x, y);
2371 transformMatrix = MatrixUtils.Scale(transformMatrix, x, x);
2375 else if (transforms[i].Equals(
"rotate", StringComparison.OrdinalIgnoreCase))
2377 double a = ParseLengthOrPercentage(transforms[i + 1], 1) * Math.PI / 180;
2381 if (i < transforms.Length - 3 && !
double.IsNaN(x = ParseLengthOrPercentage(transforms[i + 2], 1)) && !
double.IsNaN(y = ParseLengthOrPercentage(transforms[i + 3], 1)))
2383 transformMatrix = MatrixUtils.Translate(transformMatrix, x, y);
2384 transformMatrix = MatrixUtils.Rotate(transformMatrix, a);
2385 transformMatrix = MatrixUtils.Translate(transformMatrix, -x, -y);
2390 transformMatrix = MatrixUtils.Rotate(transformMatrix, a);
2394 else if (transforms[i].Equals(
"skewX", StringComparison.OrdinalIgnoreCase))
2396 double psi = ParseLengthOrPercentage(transforms[i + 1], 1) * Math.PI / 180;
2398 transformMatrix = MatrixUtils.Multiply(transformMatrix,
new double[,] { { 1, Math.Tan(psi), 0 }, { 0, 1, 0 }, { 0, 0, 1 } });
2401 else if (transforms[i].Equals(
"skewY", StringComparison.OrdinalIgnoreCase))
2403 double psi = ParseLengthOrPercentage(transforms[i + 1], 1) * Math.PI / 180;
2405 transformMatrix = MatrixUtils.Multiply(transformMatrix,
new double[,] { { 1, 0, 0 }, { Math.Tan(psi), 1, 0 }, { 0, 0, 1 } });
2410 double[] start = MatrixUtils.Multiply(transformMatrix,
new double[] { x1, y1 });
2411 double[] end = MatrixUtils.Multiply(transformMatrix,
new double[] { x2, y2 });
2419 tbr.Add(
id,
new LinearGradientBrush(
new Point(x1, y1),
new Point(x2, y2), gradientStops));
2421 else if (definition.Name.Equals(
"radialGradient", StringComparison.OrdinalIgnoreCase))
2423 XmlElement gradient = (XmlElement)definition;
2425 string id = gradient.GetAttribute(
"id");
2431 if (!(gradient.HasAttribute(
"cx") &&
double.TryParse(gradient.GetAttribute(
"cx"), out cx)))
2436 if (!(gradient.HasAttribute(
"cy") &&
double.TryParse(gradient.GetAttribute(
"cy"), out cy)))
2441 if (!(gradient.HasAttribute(
"r") &&
double.TryParse(gradient.GetAttribute(
"r"), out r)))
2449 if (!(gradient.HasAttribute(
"fx") &&
double.TryParse(gradient.GetAttribute(
"fx"), out fx)))
2454 if (!(gradient.HasAttribute(
"fy") &&
double.TryParse(gradient.GetAttribute(
"fy"), out fy)))
2459 List<GradientStop> gradientStops =
new List<GradientStop>();
2461 XmlNodeList childNodes;
2463 if (gradient.HasAttribute(
"xlink:href"))
2465 string refId = gradient.GetAttribute(
"xlink:href").Trim();
2467 if (refId.StartsWith(
"#"))
2469 refId = refId.Substring(1);
2471 if (!stopLists.TryGetValue(refId, out childNodes))
2473 childNodes = gradient.ChildNodes;
2478 childNodes = gradient.ChildNodes;
2483 childNodes = gradient.ChildNodes;
2486 stopLists[id] = childNodes;
2488 foreach (XmlNode stopNode
in childNodes)
2490 if (stopNode.Name.Equals(
"stop", StringComparison.OrdinalIgnoreCase))
2492 SetStyleAttributes(stopNode, styleSheets);
2494 XmlElement stop = (XmlElement)stopNode;
2499 if (stop.HasAttribute(
"offset"))
2501 offset = ParseLengthOrPercentage(stop.GetAttribute(
"offset"), 1);
2504 if (stop.HasAttribute(
"stop-opacity"))
2506 opacity = ParseLengthOrPercentage(stop.GetAttribute(
"stop-opacity"), 1);
2509 Colour stopColour = Colour.FromRgba(0, 0, 0, 0);
2511 if (stop.HasAttribute(
"stop-color"))
2513 stopColour = (Colour.FromCSSString(stop.GetAttribute(
"stop-color")) ?? stopColour).WithAlpha(opacity);
2516 gradientStops.Add(
new GradientStop(stopColour, offset));
2520 if (gradient.HasAttribute(
"gradientTransform"))
2522 string transform = gradient.GetAttribute(
"gradientTransform");
2523 string[] transforms = ParseListOfTransforms(transform);
2525 double[,] transformMatrix = MatrixUtils.Identity;
2527 for (
int i = 0; i < transforms.Length; i++)
2529 if (transforms[i].Equals(
"matrix", StringComparison.OrdinalIgnoreCase))
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);
2538 transformMatrix = MatrixUtils.Multiply(transformMatrix,
new double[,] { { a, c, e }, { b, d, f }, { 0, 0, 1 } });
2542 else if (transforms[i].Equals(
"translate", StringComparison.OrdinalIgnoreCase))
2544 double x = ParseLengthOrPercentage(transforms[i + 1], 1);
2548 if (i < transforms.Length - 2 && !
double.IsNaN(y = ParseLengthOrPercentage(transforms[i + 2], 1)))
2550 transformMatrix = MatrixUtils.Translate(transformMatrix, x, y);
2555 transformMatrix = MatrixUtils.Translate(transformMatrix, x, 0);
2559 else if (transforms[i].Equals(
"scale", StringComparison.OrdinalIgnoreCase))
2561 double x = ParseLengthOrPercentage(transforms[i + 1], 1);
2565 if (i < transforms.Length - 2 && !
double.IsNaN(y = ParseLengthOrPercentage(transforms[i + 2], 1)))
2567 transformMatrix = MatrixUtils.Scale(transformMatrix, x, y);
2572 transformMatrix = MatrixUtils.Scale(transformMatrix, x, x);
2576 else if (transforms[i].Equals(
"rotate", StringComparison.OrdinalIgnoreCase))
2578 double a = ParseLengthOrPercentage(transforms[i + 1], 1) * Math.PI / 180;
2582 if (i < transforms.Length - 3 && !
double.IsNaN(x = ParseLengthOrPercentage(transforms[i + 2], 1)) && !
double.IsNaN(y = ParseLengthOrPercentage(transforms[i + 3], 1)))
2584 transformMatrix = MatrixUtils.Translate(transformMatrix, x, y);
2585 transformMatrix = MatrixUtils.Rotate(transformMatrix, a);
2586 transformMatrix = MatrixUtils.Translate(transformMatrix, -x, -y);
2591 transformMatrix = MatrixUtils.Rotate(transformMatrix, a);
2595 else if (transforms[i].Equals(
"skewX", StringComparison.OrdinalIgnoreCase))
2597 double psi = ParseLengthOrPercentage(transforms[i + 1], 1) * Math.PI / 180;
2599 transformMatrix = MatrixUtils.Multiply(transformMatrix,
new double[,] { { 1, Math.Tan(psi), 0 }, { 0, 1, 0 }, { 0, 0, 1 } });
2602 else if (transforms[i].Equals(
"skewY", StringComparison.OrdinalIgnoreCase))
2604 double psi = ParseLengthOrPercentage(transforms[i + 1], 1) * Math.PI / 180;
2606 transformMatrix = MatrixUtils.Multiply(transformMatrix,
new double[,] { { 1, 0, 0 }, { Math.Tan(psi), 1, 0 }, { 0, 0, 1 } });
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]);
2615 double[] focus = MatrixUtils.Multiply(transformMatrix,
new double[] { fx, fy });
2616 double[] centre = MatrixUtils.Multiply(transformMatrix,
new double[] { cx, cy });
2622 r = r * Math.Sqrt(determinant);
2625 tbr.Add(
id,
new RadialGradientBrush(
new Point(fx, fy),
new Point(cx, cy), r, gradientStops));
2633 private static KeyValuePair<string, FontFamily>? ParseFontFaceBlock(List<string> tokens)
2635 int fontFamilyInd = tokens.IndexOf(
"font-family");
2636 string fontFamilyName = tokens[fontFamilyInd + 2].Trim().Trim(
'"').Trim();
2638 int srcInd = tokens.IndexOf(
"src");
2639 string src = tokens[srcInd + 2];
2641 string mimeType = src.Substring(src.IndexOf(
"data:") + 5);
2642 mimeType = mimeType.Substring(0, mimeType.IndexOf(
";"));
2644 if (mimeType.Equals(
"font/ttf", StringComparison.OrdinalIgnoreCase) || mimeType.Equals(
"font/truetype", StringComparison.OrdinalIgnoreCase) || mimeType.Equals(
"application/x-font-ttf", StringComparison.OrdinalIgnoreCase))
2646 src = src.Substring(src.IndexOf(
"base64,") + 7);
2647 src = src.TrimEnd(
')').TrimEnd(
'\"').TrimEnd(
')');
2648 byte[] fontBytes = Convert.FromBase64String(src);
2650 string tempFile = Path.GetTempFileName();
2652 File.WriteAllBytes(tempFile, fontBytes);
2654 FontFamily family = FontFamily.ResolveFontFamily(tempFile);
2655 return new KeyValuePair<string, FontFamily>(fontFamilyName, family);
2661 private const string CSSDelimiters =
":;,{}";
2663 private static string ReadCSSToken(StringReader reader)
2665 StringBuilder tbr =
new StringBuilder();
2667 bool openQuotes =
false;
2668 int openParentheses = 0;
2670 int c = reader.Read();
2673 tbr.Append((
char)c);
2677 openQuotes = !openQuotes;
2690 while (c >= 0 && (!CSSDelimiters.Contains((
char)c) || openQuotes || openParentheses > 0))
2693 tbr.Append((
char)c);
2696 openQuotes = !openQuotes;
2710 string val = tbr.ToString().Trim();
2712 return (
string.IsNullOrEmpty(val) && c >= 0) ? ReadCSSToken(reader) : val;
2716 internal class PathTransformerGraphicsContext : IGraphicsContext
2718 public GraphicsPath CurrentPath =
new GraphicsPath();
2719 public double[,] TransformMatrix = MatrixUtils.Identity;
2721 private Stack<double[,]> transformMatrices;
2722 public PathTransformerGraphicsContext()
2724 transformMatrices =
new Stack<double[,]>();
2725 transformMatrices.Push((
double[,])TransformMatrix.Clone());
2728 public void CubicBezierTo(
double p1X,
double p1Y,
double p2X,
double p2Y,
double p3X,
double p3Y)
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 });
2734 CurrentPath.CubicBezierTo(p1[0], p1[1], p2[0], p2[1], p3[0], p3[1]);
2737 public void LineTo(
double x,
double y)
2739 double[] p = MatrixUtils.Multiply(TransformMatrix,
new double[] { x, y });
2740 CurrentPath.LineTo(p[0], p[1]);
2743 public void MoveTo(
double x,
double y)
2745 double[] p = MatrixUtils.Multiply(TransformMatrix,
new double[] { x, y });
2746 CurrentPath.MoveTo(p[0], p[1]);
2751 CurrentPath.Close();
2754 public void Restore()
2756 TransformMatrix = transformMatrices.Pop();
2759 public void Rotate(
double angle)
2761 TransformMatrix = MatrixUtils.Rotate(TransformMatrix, angle);
2766 transformMatrices.Push((
double[,])TransformMatrix.Clone());
2769 public void Scale(
double scaleX,
double scaleY)
2771 TransformMatrix = MatrixUtils.Scale(TransformMatrix, scaleX, scaleY);
2774 public void Transform(
double a,
double b,
double c,
double d,
double e,
double f)
2776 double[,] transfMatrix =
new double[3, 3] { { a, c, e }, { b, d, f }, { 0, 0, 1 } };
2777 TransformMatrix = MatrixUtils.Multiply(TransformMatrix, transfMatrix);
2780 public void Translate(
double x,
double y)
2782 TransformMatrix = MatrixUtils.Translate(TransformMatrix, x, y);
2787 public double Width =>
throw new NotImplementedException();
2789 public double Height =>
throw new NotImplementedException();
2791 public Font Font {
get =>
throw new NotImplementedException();
set =>
throw new NotImplementedException(); }
2792 public TextBaselines TextBaseline {
get =>
throw new NotImplementedException();
set =>
throw new NotImplementedException(); }
2794 public Brush FillStyle => Colours.Black;
2796 public Brush StrokeStyle => Colours.Black;
2798 public double LineWidth {
get => 1;
set { } }
2799 public LineCaps LineCap {
set { } }
2801 public string Tag {
get =>
null;
set { } }
2803 public void DrawRasterImage(
int sourceX,
int sourceY,
int sourceWidth,
int sourceHeight,
double destinationX,
double destinationY,
double destinationWidth,
double destinationHeight, RasterImage image)
2805 throw new NotImplementedException();
2813 public void FillText(
string text,
double x,
double y)
2815 throw new NotImplementedException();
2818 public void Rectangle(
double x0,
double y0,
double width,
double height)
2821 LineTo(x0 + width, y0);
2822 LineTo(x0 + width, y0 + height);
2823 LineTo(x0, y0 + height);
2827 public void SetClippingPath()
2829 throw new NotImplementedException();
2832 public void SetFillStyle((
int r,
int g,
int b,
double a) style)
2837 public void SetFillStyle(Brush style)
2842 public void SetLineDash(LineDash dash)
2847 public void SetStrokeStyle((
int r,
int g,
int b,
double a) style)
2852 public void SetStrokeStyle(Brush style)
2857 public void Stroke()
2862 public void StrokeText(
string text,
double x,
double y)
2864 throw new NotImplementedException();
2867 public void DrawFilteredGraphics(Graphics graphics,
IFilter filter)
2869 graphics.CopyToIGraphicsContext(
this);