/// <summary> /// Create the xml export structure /// </summary> private static XElement CreateXmlExport(GraphicVisual visual, int width) { XNamespace ns = "http://www.w3.org/2000/svg"; XElement root = new XElement(ns + "svg"); root.Add(new XAttribute("Version", "1.1")); var normalizer = new NormalizeVisual(); var normalizedVisual = normalizer.Normalize(visual, NormalizeAspect.Width, width); double height = normalizer.AspectRatio * width; root.Add(new XAttribute("viewBox", $"0 0 {width} {height}")); XElement definitions = new XElement(ns + "defs"); int definitionsCount = 0; var element = GenerateXmlTree(normalizedVisual, ns, definitions, ref definitionsCount); if (definitions.HasElements) { root.Add(definitions); } root.Add(element); return(root); }
/// <summary> /// Export to an image format /// </summary> public static void ExportSvg(GraphicVisual visual, int width, string filename) { XNamespace ns = "http://www.w3.org/2000/svg"; XElement root = new XElement(ns + "svg"); root.Add(new XAttribute("Version", "1.1")); var normalizer = new NormalizeVisual(); var normalizedVisual = normalizer.Normalize(visual, NormalizeAspect.Width, width); double height = normalizer.AspectRatio * width; root.Add(new XAttribute("viewBox", $"0 0 {width} {height}")); XElement definitions = new XElement(ns + "defs"); int definitionsCount = 0; var element = Generate(normalizedVisual, ns, definitions, ref definitionsCount); if (definitions.HasElements) { root.Add(definitions); } root.Add(element); using (var writer = XmlWriter.Create(filename, new XmlWriterSettings { OmitXmlDeclaration = true, Indent = true })) { root.Save(writer); } }
/// <summary> /// Parse the given file /// </summary> GraphicVisual IFileParser.Parse(string filename) { GraphicVisual visual = null; try { using (var file = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.Read)) { using (GZipStream decompressionStream = new GZipStream(file, CompressionMode.Decompress)) { var root = XElement.Load(decompressionStream); var svgParser = new SvgParser(); visual = svgParser.ParseRoot(root); } } } #pragma warning disable CS0168 // Variable is declared but never used catch (Exception e) #pragma warning restore CS0168 // Variable is declared but never used { } return(visual); }
/// <summary> /// Export to EPS /// </summary> public void Export(GraphicVisual visual, int width, string filename, out string message) { outputStream = new StreamWriter(filename, false, Encoding.ASCII); var normalizer = new NormalizeVisual(); var normalizedVisual = normalizer.Normalize(visual, NormalizeAspect.Width, width); double height = normalizer.AspectRatio * width; InitEpsFile(width, height); Generate(normalizedVisual); FinalizeEpsFile(); outputStream.Close(); outputStream.Dispose(); if (nonSupportedOpacityFound) { message = "EPS does not support opacity, opacity ignored"; } else { message = string.Empty; } }
/// <summary> /// Determines the scale factor of the specified geometry tree /// </summary> private Rect DetermineBounds(GraphicVisual visual) { var left = double.PositiveInfinity; var top = double.PositiveInfinity; var right = double.NegativeInfinity; var bottom = double.NegativeInfinity; void DetermineBoundsRecursive(GraphicVisual visualRecursive) { switch (visualRecursive) { case GraphicGroup group: { foreach (var childVisual in group.Children) { DetermineBoundsRecursive(childVisual); } break; } case GraphicPath graphicPath: { var bounds = graphicPath.Geometry.Bounds; if (bounds.Left < left) { left = bounds.Left; } if (bounds.Top < top) { top = bounds.Top; } if (bounds.Right > right) { right = bounds.Right; } if (bounds.Bottom > bottom) { bottom = bounds.Bottom; } break; } } } DetermineBoundsRecursive(visual); if (double.IsPositiveInfinity(left)) { return(new Rect()); } return(new Rect(left, top, right - left, bottom - top)); }
/// <summary> /// Generate a list of raw (pure) geometry streams without any code around /// </summary> public static List <string> GenerateStreamGeometries(GraphicVisual visual) { List <string> list = new List <string>(); GenerateStreamGeometries(visual, list); return(list); }
/// <summary> /// Remove all unneeded groups with clipping set that doesn't clip anything /// In sum this is a very, very expensive operation. But it helps to /// create cleaner and faster code without unnecessary clipping /// </summary> private static Geometry RemoveClipping(GraphicVisual visual) { Geometry geometry = null; switch (visual) { case GraphicGroup group: { var childrenGeometry = new PathGeometry(); geometry = childrenGeometry; foreach (var childVisual in group.Children) { var childgeometry = RemoveClipping(childVisual); childrenGeometry.AddGeometry(childgeometry); } if (group.Clip != null) { var groupClipGeometry = GeometryBinaryGenerator.GenerateGeometry(group.Clip); var intersection = groupClipGeometry.FillContainsWithDetail(childrenGeometry, 0.0001, ToleranceType.Absolute); if (intersection == IntersectionDetail.FullyContains) { group.Clip = null; } else if (intersection == IntersectionDetail.Intersects) { var pen1 = new Pen(Brushes.Black, 2); groupClipGeometry = groupClipGeometry.GetWidenedPathGeometry(pen1); groupClipGeometry = groupClipGeometry.GetOutlinedPathGeometry(); var pen2 = new Pen(Brushes.Black, 1); childrenGeometry = childrenGeometry.GetWidenedPathGeometry(pen2); childrenGeometry = childrenGeometry.GetOutlinedPathGeometry(); intersection = groupClipGeometry.FillContainsWithDetail(childrenGeometry, 0.0001, ToleranceType.Absolute); if (intersection == IntersectionDetail.FullyContains) { group.Clip = null; } } } break; } case GraphicPath graphicPath: { geometry = GeometryBinaryGenerator.GenerateGeometry(graphicPath.Geometry); break; } } return(geometry); }
/// <summary> /// Initialize a stream generation /// </summary> protected override void Init(GraphicVisual visual) { var code = Code; var indent = MethodBodyIndent; code.AppendLine($"{indent}PathGeometry pathGeometry = new PathGeometry();"); code.AppendLine($"{indent}PathFigureCollection pathFigureCollection = new PathFigureCollection();"); code.AppendLine($"{indent}pathGeometry.Figures = pathFigureCollection;"); }
/// <summary> /// Generate the XAML Path source code for a visual /// </summary> private static void GeneratePathGroup(GraphicVisual visual, StringBuilder result, int level, GeometryGeneratorType geometryGeneratorType) { switch (visual) { case GraphicGroup group: { var tag = "Grid"; var indentTag = SourceFormatterHelper.GetTagIndent(level); result.Append($"{indentTag}<{tag}"); bool tagIndent = false; if (!DoubleUtilities.IsEqual(group.Opacity, 1.0)) { tagIndent = true; string opac = string.Format(CultureInfo.InvariantCulture, " Opacity=\"{0}\"", DoubleUtilities.FormatString(group.Opacity)); result.Append(opac); } if (group.Clip != null) { if (tagIndent) { var indentProperty = SourceFormatterHelper.GetPropertyIndent(level, tag); result.AppendLine(); result.Append(indentProperty); } else { result.Append(" "); } result.Append(string.Format("Clip=\"")); var stream = StreamSourceGenerator.GenerateStreamGeometry(group.Clip); result.Append(stream); result.Append("\""); } result.AppendLine(">"); foreach (var childVisual in group.Children) { GeneratePathGroup(childVisual, result, level + 1, geometryGeneratorType); } result.AppendLine($"{indentTag}</{tag}>"); break; } case GraphicPath graphicPath: { result.AppendLine(GeneratePath(graphicPath, false, level, geometryGeneratorType)); break; } } }
/// <summary> /// Initialize a stream generation /// </summary> protected override void Init(GraphicVisual visual) { var code = Code; var indent = MethodBodyIndent; code.AppendLine($"{indent}StreamGeometry geometry = new StreamGeometry();"); code.AppendLine($"{indent}geometry.FillRule = FillRule.EvenOdd;"); code.AppendLine($"{indent}StreamGeometryContext ctx = geometry.Open();"); }
/// <summary> /// Initialize a stream generation /// </summary> protected override void Init(GraphicVisual visual) { InitCode(visual); var indent2 = Indent2; code.AppendLine($"{indent2}StreamGeometry geometry = new StreamGeometry();"); code.AppendLine($"{indent2}geometry.FillRule = FillRule.EvenOdd;"); code.AppendLine($"{indent2}StreamGeometryContext ctx = geometry.Open();"); }
/// <summary> /// Generate the XAML source code for a PathGeometry /// </summary> public static string GeneratePathGeometry(GraphicVisual visual) { StringBuilder result = new StringBuilder(); int xKey = 1; GeneratePathGeometry(result, visual, ref xKey); return(result.ToString()); }
/// <summary> /// Generates a WPF brush from the specified drawing. /// </summary> public static Brush Generate(GraphicVisual visual) { var drawingBrush = new DrawingBrush(); drawingBrush.Stretch = Stretch.Uniform; drawingBrush.Drawing = GenerateDrawing(visual); return(drawingBrush); }
/// <summary> /// Remove all unneeded groups (neutral groups with only one child or /// with clipping set that doesn't clip anything) /// Attention: Modifies the original visual /// </summary> public static GraphicVisual Optimize(GraphicVisual visual) { if (visual == null) { return(null); } RemoveClipping(visual); return(Optimize(visual, 0)); }
/// <summary> /// Initialize a stream generation /// </summary> protected override void Init(GraphicVisual visual) { InitCode(visual); var indent2 = Indent2; code.AppendLine($"{indent2}PathGeometry pathGeometry = new PathGeometry();"); code.AppendLine($"{indent2}PathFigureCollection pathFigureCollection = new PathFigureCollection();"); code.AppendLine($"{indent2}pathGeometry.Figures = pathFigureCollection;"); }
/// <summary> /// Export to SVG /// </summary> public static void ExportSvg(GraphicVisual visual, int width, string filename) { XElement root = CreateXmlExport(visual, width); using (var writer = XmlWriter.Create(filename, new XmlWriterSettings { OmitXmlDeclaration = true, Indent = true })) { root.Save(writer); } }
/// <summary> /// Set a new visual /// </summary> public void SetNewGraphicVisual(GraphicVisual visual) { selectedVisual = visual; if (visual == null) { SourceCode = null; } UpdateSourceCode(); }
/// <summary> /// Set a new visual /// </summary> public void SetNewGraphicVisual(GraphicVisual visual, GraphicColorPrecision?colorPrecision = null) { if (visual == null) { ColorPrecisionMessage = null; Preview = null; return; } ColorPrecisionMessage = CommonViews.Helper.GetColorPrecisionText(colorPrecision); Preview = DrawingBrushBinaryGenerator.Generate(visual); }
/// <summary> /// Generate a visual recursively to xml /// </summary> private static XElement Generate(GraphicVisual visual, XNamespace ns, XElement definitions, ref int definitionsCount) { XElement element = null; switch (visual) { case GraphicGroup group: { element = new XElement(ns + "g"); if (!DoubleUtilities.IsEqual(group.Opacity, 1)) { element.Add(new XAttribute("opacity", DoubleUtilities.FormatString(group.Opacity))); } if (group.Clip != null) { var clipElement = new XElement(ns + "clipPath"); definitions.Add(clipElement); definitionsCount++; string defId = $"clip{definitionsCount}"; element.Add(new XAttribute("clip-path", $"url(#{defId})")); clipElement.Add(new XAttribute("id", defId)); var pathElement = new XElement(ns + "path"); clipElement.Add(pathElement); var pathStr = StreamSourceGenerator.GenerateStreamGeometry(group.Clip, false); pathElement.Add(new XAttribute("d", pathStr)); } foreach (var childVisual in group.Childreen) { var path = Generate(childVisual, ns, definitions, ref definitionsCount); element.Add(path); } break; } case GraphicPath graphicPath: { element = GeneratePath(graphicPath, ns, definitions, ref definitionsCount); break; } } return(element); }
/// <summary> /// Set a new visual /// </summary> public void SetNewGraphicVisual(GraphicVisual visual, GraphicColorPrecision?colorPrecision = null) { selectedVisual = visual; if (visual == null) { ColorPrecisionMessage = null; SourceCode = null; } ColorPrecisionMessage = Helper.GetColorPrecisionText(colorPrecision); UpdateSourceCode(); }
/// <summary> /// Builds the selected visual. /// </summary> private GraphicVisual BuildSelectedDrawing(GraphicVisual visual) { var selectedShapes = GetSelectedPaths(); GraphicVisual selectedVisual = null; if (visual != null) { selectedVisual = BuildSelectedGeometry(visual, selectedShapes); selectedVisual = OptimizeVisual.Optimize(selectedVisual); } return(selectedVisual); }
/// <summary> /// Generates the drawing brush source code for a given graphic drawing. /// </summary> public static string Generate(GraphicVisual visual, GeometryGeneratorType geometryGeneratorType) { StringBuilder result = new StringBuilder(); var indentTag = SourceFormatterHelper.GetTagIndent(1); result.AppendLine("<DrawingBrush Stretch=\"Uniform\">"); result.AppendLine($"{indentTag}<DrawingBrush.Drawing>"); Generate(visual, result, 2, geometryGeneratorType); result.AppendLine($"{indentTag}</DrawingBrush.Drawing>"); result.AppendLine("</DrawingBrush>"); return(result.ToString()); }
/// <summary> /// Parse a single SVG shape /// </summary> public GraphicVisual Parse(XElement shape, XNamespace svgNamespace, Matrix currentTransformationMatrix, CssStyleCascade cssStyleCascade, Dictionary <string, XElement> globalDefinitions) { GraphicVisual graphicVisual = null; cssStyleCascade.PushStyles(shape); var transform = cssStyleCascade.GetPropertyFromTop("transform"); if (!string.IsNullOrEmpty(transform)) { var transformMatrix = TransformMatrixParser.GetTransformMatrix(transform); currentTransformationMatrix = transformMatrix * currentTransformationMatrix; } var geometry = GeometryParser.Parse(shape, currentTransformationMatrix); if (geometry != null) { var graphicPath = new GraphicPath(); graphicPath.Geometry = geometry; graphicVisual = graphicPath; this.svgNamespace = svgNamespace; this.globalDefinitions = globalDefinitions; this.currentTransformationMatrix = currentTransformationMatrix; this.cssStyleCascade = cssStyleCascade; SetFillAndStroke(shape, graphicPath); if (Clipping.IsClipPathSet(cssStyleCascade)) { // shapes don't support clipping, create a group around it var group = new GraphicGroup(); graphicVisual = group; group.Childreen.Add(graphicPath); Clipping.SetClipPath(group, currentTransformationMatrix, cssStyleCascade, globalDefinitions); } cssStyleCascade.Pop(); } return(graphicVisual); }
/// <summary> /// Generates geometry source code /// </summary> public string GenerateSource(GraphicVisual visual, NormalizeGeometrySourceAspect normalizeGeometrySourceAspect, bool includeOffset, string filename) { this.normalizeAspect = normalizeGeometrySourceAspect; this.includeOffset = includeOffset; this.filename = filename; Code = new StringBuilder(); var normalizer = new NormalizeVisual(); NormalizeAspect normalizeType; switch (normalizeAspect) { case NormalizeGeometrySourceAspect.Width: normalizeType = NormalizeAspect.Width; scaleWidthVariableName = "scale"; scaleHeightVariableName = "scale"; break; case NormalizeGeometrySourceAspect.Height: normalizeType = NormalizeAspect.Height; scaleWidthVariableName = "scale"; scaleHeightVariableName = "scale"; break; default: normalizeType = NormalizeAspect.Individual; scaleWidthVariableName = "width"; scaleHeightVariableName = "height"; break; } var normalizedVisual = normalizer.Normalize(visual, normalizeType, 1.0); aspectRatio = normalizer.AspectRatio; InitCode(visual); Init(visual); GenerateSourceRecursive(normalizedVisual); Terminate(); FinalizeCode(); return Code.ToString(); }
/// <summary> /// Generate the XAML Path source code for all given graphic paths /// </summary> public static string GeneratePath(GraphicVisual visual, GeometryGeneratorType geometryGeneratorType) { StringBuilder result = new StringBuilder(); if (visual is GraphicPath graphicPath) { result.AppendLine(GeneratePath(graphicPath, true, 0, geometryGeneratorType)); } else { result.AppendLine("<Viewbox>"); GeneratePathGroup(visual, result, 1, geometryGeneratorType); result.AppendLine("</Viewbox>"); } return(result.ToString()); }
/// <summary> /// Parse an SVG given as XElement root /// </summary> public GraphicVisual ParseRoot(XElement root) { var nameSpaceAttributes = root.Attributes().Where(a => a.IsNamespaceDeclaration); var defaultNamespaceAttribute = root.Attributes().Where(a => a.IsNamespaceDeclaration && a.Name.Namespace == XNamespace.None).FirstOrDefault(); XNamespace defaultNamespace = defaultNamespaceAttribute.Value; Matrix currentTransformationMatrix = Matrix.Identity; cssStyleCascade = new CssStyleCascade(root); ReadGlobalDefinitions(root); GraphicVisual visual = ParseGroup(defaultNamespace, root, currentTransformationMatrix); visual = OptimizeVisual.Optimize(visual); return(visual); }
/// <summary> /// Parse the given file /// </summary> GraphicVisual IFileParser.Parse(string filename) { GraphicVisual visual = null; try { var root = XElement.Load(new Uri(filename).ToString()); visual = ParseRoot(root); } #pragma warning disable CS0168 // Variable is declared but never used catch (Exception e) #pragma warning restore CS0168 // Variable is declared but never used { } return(visual); }
private GraphicVisual ParseElement(XElement element, Matrix matrix) { GraphicVisual graphicVisual = null; switch (element.Name.LocalName) { case "defs": case "style": { // already read, ignore break; } case "svg": { graphicVisual = ParseSVG(element, matrix, false); break; } case "g": { graphicVisual = ParseGContainer(element, matrix); break; } case "text": { graphicVisual = textParser.Parse(element, matrix); break; } case "use": { graphicVisual = ParseUseElement(element, matrix); break; } default: { graphicVisual = shapeParser.Parse(element, matrix); break; } } return(graphicVisual); }
/// <summary> /// Export to SVGZ /// </summary> public static void ExportSvgz(GraphicVisual visual, int width, string filename) { XElement root = CreateXmlExport(visual, width); using (var fs = File.Create(filename)) { using (var gz = new GZipStream(fs, CompressionMode.Compress)) { using (var writer = XmlWriter.Create(gz, new XmlWriterSettings { OmitXmlDeclaration = true, Indent = true })) { root.Save(writer); } } } }
/// <summary> /// Generate the XAML source code (a <Path/> for a single graphic path /// </summary> public static string GenerateGeometry(GraphicVisual visual) { var tag = "Geometry"; StringBuilder result = new StringBuilder(); var geometries = StreamSourceGenerator.GenerateStreamGeometries(visual); int i = 1; foreach (var geometry in geometries) { result.Append($"<{tag} x:Key=\"shape{i}\">"); result.Append(geometry); result.AppendLine($"</{tag}>"); i++; } return(result.ToString()); }