private static StyleProperties CreateStyleProperties(XElement element, SVGStyle style) { var attributes = element.Attributes(); XAttribute attribute; if (TryGetClassAttribute(attributes, out attribute)) { return(style[attribute.Value]); } if (TryGetStyleAttribute(attributes, out attribute)) { return(attribute.Value.StartsWith("&") ? style[attribute.Value] : SVGUtils.ParseStyleContent(attribute.Value)); } var styleProps = new StyleProperties(); foreach (var attr in attributes .Where(IsNotPathAttribute)) { styleProps[attr.Name.LocalName] = attr.Value; } return(styleProps); }
/***************************************************/ /**** Public Methods - Graphics ****/ /***************************************************/ public static string ToSVGString(this SVGStyle svgStyle) { string styleString = "stroke-width=\"" + svgStyle.StrokeWidth.ToString() + "\" stroke=\"__stroke-color__\" fill=\"__fill-color__\" stroke-opacity=\"" + svgStyle.StrokeOpacity.ToString() + "\" fill-opacity=\"" + svgStyle.FillOpacity.ToString() + "\" stroke-dasharray=\"__stroke-dasharray__\""; string strokeColor = svgStyle.StrokeColor.ToString(); Regex regex = new Regex(@"(\d{1,3}),(\d{1,3}),(\d{1,3})"); Match matchStroke = regex.Match(strokeColor); if (matchStroke.Success) { string rgb1 = "rgb(" + svgStyle.StrokeColor.ToString() + ")"; styleString = styleString.Replace("__stroke-color__", rgb1); } else { styleString = styleString.Replace("__stroke-color__", svgStyle.StrokeColor.ToString()); } string fillColor = svgStyle.FillColor.ToString(); Match matchFill = regex.Match(fillColor); if (matchFill.Success) { string rgb2 = "rgb(" + svgStyle.FillColor.ToString() + ")"; styleString = styleString.Replace("__fill-color__", rgb2); } else { styleString = styleString.Replace("__fill-color__", svgStyle.FillColor.ToString()); } string DashArray = ""; for (int i = 0; i < svgStyle.StrokeDash.Count; i++) { string a = svgStyle.StrokeDash[i].ToString(); if (i == 0) { DashArray += a; } else { DashArray += " " + a; } } styleString = styleString.Replace("__stroke-dasharray__", DashArray); return(styleString); }
public SVGStyle Parse(T documentType) { var rawValue = _valueExtractor(documentType); var style = new SVGStyle(); foreach (var tuple in SelectProperties(rawValue)) { style.Add(tuple.Item1, tuple.Item2); } return(style); }
private static SVGElement CreateSvgDta(XElement element, SVGStyle style) { if (MissingPathAttribute(element)) { return(null); } var d = element.Attributes().First(x => x.Name.LocalName == "d").Value; var styleProps = CreateStyleProperties(element, style); return(new SVGElement { StyleProperties = styleProps, Path = d }); }
/***************************************************/ /**** Public Methods ****/ /***************************************************/ public static SVGObject SVGObject(List <IGeometry> shapes, SVGStyle style = null) { return(new SVGObject { Shapes = shapes, Style = style == null ? new SVGStyle() : style }); }
/// <summary> /// Exports the given manifold to SVG format with the given style, flattening it onto a plane if necessary. /// </summary> /// <param name="writer">The writer to which the SVG-formatted manifold will be written.</param> /// <param name="surface">The surface describing the manifold.</param> /// <param name="topology">The topology of the manifold.</param> /// <param name="orientation">The orientation of the plane to which the manifold will be flattened.</param> /// <param name="scale">The scale which will be applied to the manifold while writing out SVG positions.</param> /// <param name="vertexPositions">The vertex positions of the manifold.</param> /// <param name="numericFormat">The format specifier to use for each number written to <paramref name="writer"/>.</param> /// <param name="style">The style details to apply to the SVG content.</param> public static void ExportToSVG(System.IO.TextWriter writer, ISurface surface, Topology topology, Quaternion orientation, Vector3 scale, IVertexAttribute <Vector3> vertexPositions, string numericFormat, SVGStyle style) { var inverseOrientation = Quaternion.Inverse(orientation); var plane = new Plane(orientation * Vector3.back, 0f); var transform = Matrix4x4.TRS(Vector3.zero, inverseOrientation, scale); Vector2 transformedVertexDotRadius = new Vector2(style.vertexCircleRadius * transform.m00, style.vertexCircleRadius * transform.m11); Func <Vector3, Vector2> flatten = (Vector3 point) => transform *plane.ClosestPoint(point); Vector2 min = new Vector2(float.PositiveInfinity, float.PositiveInfinity); Vector2 max = new Vector2(float.NegativeInfinity, float.NegativeInfinity); foreach (var face in topology.faces) { foreach (var edge in face.edges) { var p = flatten(vertexPositions[edge]); min = Geometry.AxisAlignedMin(min, p); max = Geometry.AxisAlignedMax(max, p); } } var range = max - min; flatten = (Vector3 point) => { Vector2 p = transform * plane.ClosestPoint(point); p.y = max.y - p.y + min.y; return(p); }; writer.WriteLine("<svg viewBox=\"{0} {1} {2} {3}\" xmlns=\"http://www.w3.org/2000/svg\">", (min.x - style.padding.x).ToString(numericFormat), (min.y - style.padding.y).ToString(numericFormat), (range.x + style.padding.x * 2f).ToString(numericFormat), (range.y + style.padding.y * 2f).ToString(numericFormat)); writer.WriteLine("\t<style>"); writer.WriteLine("\t\tsvg"); writer.WriteLine("\t\t{"); if (!style.svg.ContainsKey("width")) { writer.WriteLine("\t\t\twidth: 100%;"); } if (!style.svg.ContainsKey("height")) { writer.WriteLine("\t\t\theight: 100%;"); } foreach (var keyValue in style.svg) { writer.WriteLine("\t\t\t{0}: {1};", keyValue.Key, keyValue.Value); } writer.WriteLine("\t\t}"); writer.WriteLine("\t\ttext.index"); writer.WriteLine("\t\t{"); foreach (var keyValue in style.index) { writer.WriteLine("\t\t\t{0}: {1};", keyValue.Key, keyValue.Value); } writer.WriteLine("\t\t}"); writer.WriteLine("\t\tellipse.vertex"); writer.WriteLine("\t\t{"); if (!style.vertexCircle.ContainsKey("stroke")) { writer.WriteLine("\t\t\tstroke: black;"); } if (!style.vertexCircle.ContainsKey("stroke-width")) { writer.WriteLine("\t\t\tstroke-width: 1;"); } if (!style.vertexCircle.ContainsKey("fill")) { writer.WriteLine("\t\t\tfill: none;"); } foreach (var keyValue in style.vertexCircle) { writer.WriteLine("\t\t\t{0}: {1};", keyValue.Key, keyValue.Value); } writer.WriteLine("\t\t}"); writer.WriteLine("\t\ttext.vertex.index"); writer.WriteLine("\t\t{"); if (!style.vertexIndex.ContainsKey("fill")) { writer.WriteLine("\t\t\tfill: black;"); } if (!style.vertexIndex.ContainsKey("font-size")) { writer.WriteLine("\t\t\tfont-size: {0:F0}px;", transformedVertexDotRadius.x); } if (!style.vertexIndex.ContainsKey("text-anchor")) { writer.WriteLine("\t\t\ttext-anchor: middle;"); } if (!style.vertexIndex.ContainsKey("dominant-baseline")) { writer.WriteLine("\t\t\tdominant-baseline: central;"); } foreach (var keyValue in style.vertexIndex) { writer.WriteLine("\t\t\t{0}: {1};", keyValue.Key, keyValue.Value); } writer.WriteLine("\t\t}"); writer.WriteLine("\t\tpolygon.face"); writer.WriteLine("\t\t{"); if (!style.facePolygon.ContainsKey("fill")) { writer.WriteLine("\t\t\tfill: #CCC;"); } if (!style.facePolygon.ContainsKey("stroke")) { writer.WriteLine("\t\t\tstroke: none;"); } foreach (var keyValue in style.facePolygon) { writer.WriteLine("\t\t\t{0}: {1};", keyValue.Key, keyValue.Value); } writer.WriteLine("\t\t}"); writer.WriteLine("\t\ttext.face.index"); writer.WriteLine("\t\t{"); if (!style.faceIndex.ContainsKey("fill")) { writer.WriteLine("\t\t\tfill: black;"); } if (!style.faceIndex.ContainsKey("font-size")) { writer.WriteLine("\t\t\tfont-size: {0:F0}px;", transformedVertexDotRadius.x * 2f); } if (!style.faceIndex.ContainsKey("text-anchor")) { writer.WriteLine("\t\t\ttext-anchor: middle;"); } if (!style.faceIndex.ContainsKey("dominant-baseline")) { writer.WriteLine("\t\t\tdominant-baseline: central;"); } foreach (var keyValue in style.faceIndex) { writer.WriteLine("\t\t\t{0}: {1};", keyValue.Key, keyValue.Value); } writer.WriteLine("\t\t}"); writer.WriteLine("\t\tpolyline.edge"); writer.WriteLine("\t\t{"); if (!style.edgePath.ContainsKey("stroke")) { writer.WriteLine("\t\t\tstroke: black;"); } if (!style.edgePath.ContainsKey("stroke-width")) { writer.WriteLine("\t\t\tstroke-width: 1;"); } if (!style.edgePath.ContainsKey("stroke-linecap")) { writer.WriteLine("\t\t\tstroke-linecap: square;"); } if (!style.edgePath.ContainsKey("stroke-linejoin")) { writer.WriteLine("\t\t\tstroke-linejoin: miter;"); } if (!style.edgePath.ContainsKey("fill")) { writer.WriteLine("\t\t\tfill: none;"); } foreach (var keyValue in style.edgePath) { writer.WriteLine("\t\t\t{0}: {1};", keyValue.Key, keyValue.Value); } writer.WriteLine("\t\t}"); writer.WriteLine("\t\ttext.edge.index"); writer.WriteLine("\t\t{"); if (!style.edgeIndex.ContainsKey("fill")) { writer.WriteLine("\t\t\tfill: black;"); } if (!style.edgeIndex.ContainsKey("font-size")) { writer.WriteLine("\t\t\tfont-size: {0:F0}px;", style.edgeIndexOffset * Mathf.Sqrt(transform.m00 * transform.m11)); } if (!style.edgeIndex.ContainsKey("text-anchor")) { writer.WriteLine("\t\t\ttext-anchor: middle;"); } if (!style.edgeIndex.ContainsKey("dominant-baseline")) { writer.WriteLine("\t\t\tdominant-baseline: central;"); } foreach (var keyValue in style.edgeIndex) { writer.WriteLine("\t\t\t{0}: {1};", keyValue.Key, keyValue.Value); } writer.WriteLine("\t\t}"); writer.WriteLine("\t</style>"); var facePointFormat = string.Format("{{0:{0}}},{{1:{0}}}", numericFormat); var faceIndexFormat = string.Format("\t<text class=\"face index\" x=\"{{0:{0}}}\" y=\"{{1:{0}}}\">{{2}}</text>", numericFormat); var arrowFormat = string.Format("\t<polyline class=\"edge{{6}}\" points=\"{{0:{0}}},{{1:{0}}} {{2:{0}}},{{3:{0}}} {{4:{0}}},{{5:{0}}}\" />", numericFormat); var edgeIndexFormat = string.Format("\t<text class=\"edge index{{3}}\" x=\"{{0:{0}}}\" y=\"{{1:{0}}}\">{{2}}</text>", numericFormat); var vertexFormat = string.Format("\t<ellipse class=\"vertex{{4}}\" cx=\"{{0:{0}}}\" cy=\"{{1:{0}}}\" rx=\"{{2:{0}}}\" ry=\"{{3:{0}}}\" />", numericFormat); var vertexIndexFormat = string.Format("\t<text class=\"vertex index{{3}}\" x=\"{{0:{0}}}\" y=\"{{1:{0}}}\">{{2}}</text>", numericFormat); var centroids = FaceAttributeUtility.CalculateFaceCentroidsFromVertexPositions(topology.internalFaces, vertexPositions); var bisectors = EdgeAttributeUtility.CalculateFaceEdgeBisectorsFromVertexPositions(topology.internalFaces, surface, vertexPositions, centroids); writer.WriteLine(); writer.WriteLine("\t<!-- Faces -->"); foreach (var face in topology.internalFaces) { writer.Write("\t<polygon class=\"face\" points=\""); foreach (var edge in face.edges) { if (edge != face.firstEdge) { writer.Write(" "); } var p = flatten(vertexPositions[edge] + bisectors[edge] * style.faceInset); writer.Write(facePointFormat, p.x, p.y); } writer.WriteLine("\" />"); if (style.showFaceIndices) { var p = flatten(centroids[face]); writer.WriteLine(faceIndexFormat, p.x, p.y, face.index); } } writer.WriteLine(); writer.WriteLine("\t<!-- Edges -->"); foreach (var face in topology.faces) { foreach (var edge in face.edges) { ExportEdgeToSVG(writer, surface, edge, edge.prev, edge, flatten, vertexPositions, arrowFormat, edgeIndexFormat, style, " not-wrapped"); if ((edge.wrap & EdgeWrap.FaceToFace) != EdgeWrap.None) { ExportEdgeToSVG(writer, surface, edge, edge.twin, edge.twin.vertexEdge.next.twin.faceEdge, flatten, vertexPositions, arrowFormat, edgeIndexFormat, style, " wrapped"); } } } writer.WriteLine(); writer.WriteLine("\t<!-- Vertices -->"); foreach (var vertex in topology.vertices) { bool neitherAxis = false; bool axis0 = false; bool axis1 = false; bool bothAxes = false; foreach (var edge in vertex.edges) { var faceEdge = edge.twin.faceEdge; var wrap = faceEdge.wrap & EdgeWrap.FaceToVert; if (EdgeWrapUtility.WrapsOnNeitherAxis(wrap)) { if (!neitherAxis) { neitherAxis = true; ExportVertexToSVG(writer, vertex, faceEdge, flatten, vertexPositions, transformedVertexDotRadius, vertexFormat, vertexIndexFormat, style, " wrapped-neither"); } } else if (EdgeWrapUtility.WrapsOnOnlyAxis0(wrap)) { if (!axis0) { axis0 = true; ExportVertexToSVG(writer, vertex, faceEdge, flatten, vertexPositions, transformedVertexDotRadius, vertexFormat, vertexIndexFormat, style, " wrapped-axis-0"); } } else if (EdgeWrapUtility.WrapsOnOnlyAxis1(wrap)) { if (!axis1) { axis1 = true; ExportVertexToSVG(writer, vertex, faceEdge, flatten, vertexPositions, transformedVertexDotRadius, vertexFormat, vertexIndexFormat, style, " wrapped-axis-1"); } } else { if (!bothAxes) { bothAxes = true; ExportVertexToSVG(writer, vertex, faceEdge, flatten, vertexPositions, transformedVertexDotRadius, vertexFormat, vertexIndexFormat, style, " wrapped-both"); } } } } writer.WriteLine("</svg>"); }
private static void ExportVertexToSVG(System.IO.TextWriter writer, Topology.Vertex vertex, Topology.FaceEdge edge, Func <Vector3, Vector2> flatten, IVertexAttribute <Vector3> vertexPositions, Vector2 transformedVertexDotRadius, string vertexFormat, string vertexIndexFormat, SVGStyle style, string additionalClasses) { var p = flatten(vertexPositions[edge]); writer.WriteLine(vertexFormat, p.x, p.y, transformedVertexDotRadius.x, transformedVertexDotRadius.y, additionalClasses); if (style.showVertexIndices) { writer.WriteLine(vertexIndexFormat, p.x, p.y, vertex.index, additionalClasses); } }
private static void ExportEdgeToSVG(System.IO.TextWriter writer, ISurface surface, Topology.FaceEdge edge, Topology.FaceEdge nearVertexFaceEdge, Topology.FaceEdge farVertexFaceEdge, Func <Vector3, Vector2> flatten, IVertexAttribute <Vector3> vertexPositions, string arrowFormat, string edgeIndexFormat, SVGStyle style, string additionalClasses) { var p0 = vertexPositions[nearVertexFaceEdge]; var p1 = vertexPositions[farVertexFaceEdge]; var n0 = surface.GetNormal(p0); var n1 = surface.GetNormal(p1); var edgeDirection = (p1 - p0).normalized; var edgeNormal0 = (n0 + n1).normalized; var edgeNormal1 = Vector3.Cross(edgeDirection, edgeNormal0); var offset0 = edgeDirection * (style.vertexCircleRadius + style.edgeSeparation); var offset1 = edgeNormal1 * style.edgeSeparation * 0.5f; var offset2 = -edgeDirection * style.edgeArrowOffset.x + edgeNormal1 * style.edgeArrowOffset.y; Vector2 arrow0 = flatten(p0 + offset0 + offset1); Vector2 arrow1 = flatten(p1 - offset0 + offset1); Vector2 arrow2 = flatten(p1 - offset0 + offset1 + offset2); writer.WriteLine(arrowFormat, arrow0.x, arrow0.y, arrow1.x, arrow1.y, arrow2.x, arrow2.y, additionalClasses); if (style.showEdgeIndices) { var edgeCenter = flatten((p0 + p1) * 0.5f + edgeNormal1 * (style.edgeSeparation * 0.5f + style.edgeIndexOffset)); writer.WriteLine(edgeIndexFormat, edgeCenter.x, edgeCenter.y, edge.index, additionalClasses); } }