using Microsoft.Msagl.Layout.LargeGraphLayout; #if PERSISTENCE using System; using System.Linq; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.Threading; using System.Xml; using Microsoft.Msagl.Core.DataStructures; using Microsoft.Msagl.Core.Geometry; using Microsoft.Msagl.Core.Geometry.Curves; using Microsoft.Msagl.Core.Layout; using Microsoft.Msagl.Core.Routing; using Microsoft.Msagl.Layout.Layered; using Microsoft.Msagl.Layout.MDS; namespace Microsoft.Msagl.DebugHelpers.Persistence { /// /// writes a GeometryGraph to a stream /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "GraphWriter")] public class GeometryGraphWriter { const string FileExtension = ".msagl.geom"; Dictionary nodeIds = new Dictionary(); Dictionary edgeIds = new Dictionary(); /// /// /// GeometryGraph graph; bool needToCloseXmlWriter = true; Stream stream; /// /// Constructor /// /// the stream to write the graph into /// the graph /// The settings to be written. public GeometryGraphWriter(Stream streamPar, GeometryGraph graphP, LayoutAlgorithmSettings settings) { stream = streamPar; Graph = graphP; Settings = settings; var xmlWriterSettings = new XmlWriterSettings { Indent = true }; XmlWriter = XmlWriter.Create(stream, xmlWriterSettings); EdgeEnumeration = graphP.Edges; } /// /// an empty constructor /// public GeometryGraphWriter() { } /// /// if set to true then the XmlWriter will be closed after the graph writing /// [SuppressMessage("Microsoft.Design", "CA1044:PropertiesShouldNotBeWriteOnly")] public bool NeedToCloseXmlWriter { get { return needToCloseXmlWriter; } set { needToCloseXmlWriter = value; } } /// /// the stream to write the graph into /// public Stream Stream { get { return stream; } set { stream = value; } } /// /// /// [SuppressMessage("Microsoft.Design", "CA1044:PropertiesShouldNotBeWriteOnly")] public XmlWriter XmlWriter { get; set; } /// /// the graph /// public GeometryGraph Graph { get { return graph; } set { graph = value; } } /// /// The settings /// public LayoutAlgorithmSettings Settings { get; set; } /// /// saves the graph to a file /// public static void Write(GeometryGraph graph, string fileName) { Write(graph, null, fileName); } /// /// saves the graph and settings to a file /// [SuppressMessage("Microsoft.Globalization", "CA1309:UseOrdinalStringComparison", MessageId = "System.String.EndsWith(System.String,System.StringComparison)"), SuppressMessage("Microsoft.Globalization", "CA1309:UseOrdinalStringComparison", MessageId = "System.String.EndsWith(System.String,System.Boolean,System.Globalization.CultureInfo)")] public static void Write(GeometryGraph graph, LayoutAlgorithmSettings settings, string fileName) { if (fileName == null) return; if (!fileName.EndsWith(FileExtension, StringComparison.InvariantCultureIgnoreCase)) fileName += FileExtension; using (Stream stream = File.Open(fileName, FileMode.Create)) { var graphWriter = new GeometryGraphWriter(stream, graph, settings); graphWriter.Write(); } } /// /// Writes the graph to a file /// public void Write() { var currentCulture = Thread.CurrentThread.CurrentCulture; Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture; try { Open(); WriteLayoutSettings(); InitEdgeIds(); WriteNodes(); WriteClusters(); WriteEdges(); WriteLayers(); Close(); } finally { Thread.CurrentThread.CurrentCulture = currentCulture; } } void WriteLayers() { if (graph.LgData == null) return; WriteStartElement(GeometryToken.LgLevels); WriteLgEdgeInfos(); WriteSortedLgInfos(); Dictionary railIds = CreateRailIds(); for (int i = 0; i < graph.LgData.Levels.Count; i++) { WriteLevel(graph.LgData.Levels[i], railIds, graph.LgData.LevelNodeCounts[i]); } WriteEndElement(); WriteStartElement(GeometryToken.LgSkeletonLevels); for (int i = 0; i < graph.LgData.SkeletonLevels.Count; i++) { WriteSkeletonLevel(graph.LgData.SkeletonLevels[i], railIds); } WriteEndElement(); } void WriteLgEdgeInfos() { WriteStartElement(GeometryToken.LgEdgeInfos); foreach (var t in graph.LgData.GeometryEdgesToLgEdgeInfos) { var edge = t.Key; var ei = t.Value; WriteLgEdgeInfo(edge, ei); } WriteEndElement(); } void WriteLgEdgeInfo(Edge edge, LgEdgeInfo ei) { WriteStartElement(GeometryToken.LgEdgeInfo); WriteAttribute(GeometryToken.EdgeId, edgeIds[edge]); WriteAttribute(GeometryToken.Rank, ei.Rank); WriteAttribute(GeometryToken.Zoomlevel, ei.ZoomLevel); WriteEndElement(); } void WriteLevel(LgLevel level, Dictionary railsToIds, int nodeCountOnLevel) { WriteStartElement(GeometryToken.Level); WriteAttribute(GeometryToken.NodeCountOnLevel, nodeCountOnLevel); WriteAttribute(GeometryToken.Zoomlevel, level.ZoomLevel); WriteLevelRails(level, railsToIds); WriteEndElement(); } void WriteSkeletonLevel(LgSkeletonLevel level, Dictionary railsToIds) { WriteStartElement(GeometryToken.SkeletonLevel); //WriteAttribute(GeometryToken.NodeCountOnLevel, nodeCountOnLevel); WriteAttribute(GeometryToken.Zoomlevel, level.ZoomLevel); WriteEndElement(); } void WriteLevelRails(LgLevel level, Dictionary railIds) { WriteStartElement(GeometryToken.RailsPerEdge); foreach (var t in level._railsOfEdges) { WriteEdgeRails(t.Key, t.Value, railIds); } WriteEndElement(); WriteRailsGeometry(level, railIds); } void WriteRailsGeometry(LgLevel level, Dictionary railIds) { WriteStartElement(GeometryToken.Rails); foreach (var rail in level._railDictionary.Values) WriteRail(rail, railIds[rail]); WriteEndElement(); } void WriteRail(Rail rail, int railId) { WriteStartElement(GeometryToken.Rail); WriteAttribute(GeometryToken.Id, railId); WriteAttribute(GeometryToken.Zoomlevel, rail.ZoomLevel); if (rail.MinPassingEdgeZoomLevel != Double.MaxValue) WriteAttribute(GeometryToken.MinPassingEdgeZoomLevel, rail.MinPassingEdgeZoomLevel); Arrowhead ah = rail.Geometry as Arrowhead; if (ah != null) { WriteStartElement(GeometryToken.Arrowhead); WriteAttribute(GeometryToken.ArrowheadPosition, ah.TipPosition); WriteAttribute(GeometryToken.CurveAttachmentPoint, rail.CurveAttachmentPoint); WriteEndElement(); } else { ICurve curve = rail.Geometry as ICurve; if (curve != null) WriteICurve(curve); else throw new InvalidOperationException(); } WriteEndElement(); } void WriteEdgeRails(Edge edge, Set rails, Dictionary railIds) { WriteStartElement(GeometryToken.EdgeRails); WriteAttribute(GeometryToken.EdgeId, edgeIds[edge]); List railIdStrings = new List(); foreach (var rail in rails) { railIdStrings.Add(railIds[rail].ToString()); } WriteAttribute(GeometryToken.EdgeRails, String.Join(" ", railIdStrings)); WriteEndElement(); } Dictionary CreateRailIds() { var ret = new Dictionary(); int id = 0; foreach (var level in graph.LgData.Levels) { foreach (var rail in level._railDictionary.Values) { if (ret.ContainsKey(rail)) { continue; } ret[rail] = id++; } } return ret; } void WriteSortedLgInfos() { WriteStartElement(GeometryToken.LgNodeInfos); foreach (var lgNodeInfo in graph.LgData.SortedLgNodeInfos) { WriteLgNodeInfo(lgNodeInfo); } WriteEndElement(); } void WriteLgNodeInfo(LgNodeInfo lgNodeInfo) { WriteStartElement(GeometryToken.LgNodeInfo); WriteAttribute(GeometryToken.Id, nodeIds[lgNodeInfo.GeometryNode]); WriteAttribute(GeometryToken.Rank, lgNodeInfo.Rank); WriteAttribute(GeometryToken.Zoomlevel, lgNodeInfo.ZoomLevel); WriteAttribute(GeometryToken.LabelVisibleFromScale, lgNodeInfo.LabelVisibleFromScale); WriteAttribute(GeometryToken.LabelOffset, lgNodeInfo.LabelOffset); WriteAttribute(GeometryToken.LabelWidthToHeightRatio, lgNodeInfo.LabelWidthToHeightRatio); WriteEndElement(); } void InitEdgeIds() { int id = 0; foreach (var e in graph.Edges) edgeIds[e] = id++; } /// /// roman: Writes the graph to an Ipe file /// todo /// public void WriteIpe() { var currentCulture = Thread.CurrentThread.CurrentCulture; Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture; try { Open(); WriteNodesIpe(); Close(); } finally { Thread.CurrentThread.CurrentCulture = currentCulture; } } void WriteClusters() { if (graph.RootCluster == null) return; WriteStartElement(GeometryToken.Clusters); MapClustersToIds(graph.RootCluster); foreach (var cluster in graph.RootCluster.AllClustersDepthFirstExcludingSelf()) WriteCluster(cluster, nodeIds[cluster]); WriteEndElement(); } void WriteCluster(Cluster cluster, string clusterId) { WriteStartElement(GeometryToken.Cluster); WriteAttribute(GeometryToken.Id, clusterId); WriteAttribute(GeometryToken.Barycenter, cluster.Barycenter); WriteChildClusters(cluster); WriteChildNodes(cluster); if (cluster.BoundaryCurve != null) WriteICurve(cluster.BoundaryCurve); WriteClusterRectBoundary(cluster.RectangularBoundary); WriteEndElement(); } void WriteClusterRectBoundary(RectangularClusterBoundary recClBnd) { if (recClBnd == null) return; WriteStartElement(GeometryToken.RectangularClusterBoundary); WriteAttribute(GeometryToken.LeftMargin, recClBnd.LeftMargin); WriteAttribute(GeometryToken.RightMargin, recClBnd.RightMargin); WriteAttribute(GeometryToken.TopMargin, recClBnd.TopMargin); WriteAttribute(GeometryToken.BottomMargin, recClBnd.BottomMargin); if (recClBnd.DefaultMarginIsSet) { WriteAttribute(GeometryToken.DefaultLeftMargin, recClBnd.DefaultLeftMargin); WriteAttribute(GeometryToken.DefaultRightMargin, recClBnd.DefaultRightMargin); WriteAttribute(GeometryToken.DefaultTopMargin, recClBnd.DefaultTopMargin); WriteAttribute(GeometryToken.DefaultBottomMargin, recClBnd.DefaultBottomMargin); } WriteAttribute(GeometryToken.GenerateFixedConstraints, recClBnd.GenerateFixedConstraints); WriteAttribute(GeometryToken.GenerateFixedConstraintsDefault, recClBnd.GenerateFixedConstraintsDefault); WriteAttribute(GeometryToken.MinNodeHeight, recClBnd.MinHeight); WriteAttribute(GeometryToken.MinNodeWidth, recClBnd.MinWidth); WriteRect(recClBnd.Rect.Left, recClBnd.Rect.Bottom, recClBnd.Rect.Width, recClBnd.Rect.Height, recClBnd.RadiusX, recClBnd.RadiusY); WriteBorderInfo(GeometryToken.RightBorderInfo, recClBnd.RightBorderInfo); WriteBorderInfo(GeometryToken.LeftBorderInfo, recClBnd.LeftBorderInfo); WriteBorderInfo(GeometryToken.TopBorderInfo, recClBnd.TopBorderInfo); WriteBorderInfo(GeometryToken.BottomBorderInfo, recClBnd.BottomBorderInfo); WriteEndElement(); } void WriteBorderInfo(GeometryToken token, BorderInfo borderInfo) { WriteStartElement(token); WriteAttribute(GeometryToken.InnerMargin, borderInfo.InnerMargin); WriteAttribute(GeometryToken.FixedPosition, borderInfo.FixedPosition); WriteAttribute(GeometryToken.Weight, borderInfo.Weight); WriteEndElement(); } void WriteChildNodes(Cluster cluster) { WriteAttribute(GeometryToken.ChildNodes, string.Join(" ", cluster.nodes.Select(child => nodeIds[child].ToString(CultureInfo.InvariantCulture)))); } void WriteChildClusters(Cluster cluster) { WriteAttribute(GeometryToken.ChildClusters, String.Join(" ", cluster.Clusters.Select(child => NodeToIds[child]))); } [SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider", MessageId = "System.Int32.ToString" )] void MapClustersToIds(Cluster cluster) { string id; var setOfIds = new Set(nodeIds.Values); foreach (Cluster child in cluster.AllClustersDepthFirst()) { if (!nodeIds.TryGetValue(child, out id)) { id = FindNewId(setOfIds); nodeIds[child] = id; setOfIds.Insert(id); } } } string FindNewId(Set setOfIds) { int i = nodeIds.Count; do { var s = i.ToString(); if (!setOfIds.Contains(s)) return s; i++; } while (true); } [SuppressMessage("Microsoft.Globalization", "CA1304:SpecifyCultureInfo", MessageId = "System.String.ToLower")] void Open() { XmlWriter.WriteStartElement(GeometryToken.Graph.ToString().ToLower()); WriteAttribute(GeometryToken.Margins, this.graph.Margins); } void Close() { XmlWriter.WriteEndElement(); if (NeedToCloseXmlWriter) { XmlWriter.WriteEndDocument(); XmlWriter.Flush(); XmlWriter.Close(); } } void WriteEdges() { WriteStartElement(GeometryToken.Edges); foreach (Edge edge in EdgeEnumeration) WriteEdge(edge); WriteEndElement(); } string NodeOrClusterId(Node node) { return nodeIds[node]; } void WriteEdge(Edge edge) { WriteStartElement(GeometryToken.Edge); WriteAttribute(GeometryToken.Id, edgeIds[edge]); WriteAttribute(GeometryToken.S, NodeOrClusterId(edge.Source).ToString(CultureInfo.InvariantCulture)); WriteAttribute(GeometryToken.T, NodeOrClusterId(edge.Target).ToString(CultureInfo.InvariantCulture)); if (edge.LineWidth != 1) WriteAttribute(GeometryToken.LineWidth, edge.LineWidth); if (edge.ArrowheadAtSource) { WriteAttribute(GeometryToken.As, edge.EdgeGeometry.SourceArrowhead.TipPosition); WriteDefaultDouble(GeometryToken.Asl, edge.EdgeGeometry.SourceArrowhead.Length, Arrowhead.DefaultArrowheadLength); } if (edge.ArrowheadAtTarget) { WriteAttribute(GeometryToken.At, edge.EdgeGeometry.TargetArrowhead.TipPosition); WriteDefaultDouble(GeometryToken.Atl, edge.EdgeGeometry.TargetArrowhead.Length, Arrowhead.DefaultArrowheadLength); } if (edge.Weight != 1) WriteAttribute(GeometryToken.Weight, edge.Weight); if (edge.Separation != 1) WriteAttribute(GeometryToken.Separation, edge.Separation); if (edge.Label != null) WriteLabel(edge.Label); WriteICurve(edge.Curve); WriteEndElement(); } void WriteDefaultDouble(GeometryToken geometryToken, double val, double defaultValue) { if (val != defaultValue) WriteAttribute(geometryToken, DoubleToString(val)); } void WriteAttribute(GeometryToken attrKind, object val) { var attrString = FirstCharToLower(attrKind); if (val is Point) XmlWriter.WriteAttributeString(attrString, PointToString((Point)val)); else if (val is Double) XmlWriter.WriteAttributeString(attrString, DoubleToString((double)val)); else XmlWriter.WriteAttributeString(attrString, val.ToString()); } [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase")] static internal string FirstCharToLower(GeometryToken attrKind) { var attrString = attrKind.ToString(); attrString = attrString.Substring(0, 1).ToLower(CultureInfo.InvariantCulture) + attrString.Substring(1, attrString.Length - 1); return attrString; } string PointToString(Point start) { return DoubleToString(start.X) + " " + DoubleToString(start.Y); } string formatForDoubleString = "#.###########"; int precision = 11; IEnumerable edgeEnumeration; /// /// public int Precision { get { return precision; } set { precision = Math.Max(1, value); var s = new char[precision + 2]; s[0] = '#'; s[1] = '.'; for (int i = 0; i < precision; i++) s[2 + i] = '#'; formatForDoubleString = new string(s); } } /// /// a mapping from nodes to their ids /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] public Dictionary NodeToIds { get { return nodeIds; } set { nodeIds = value; } } /// /// this enumeration is used in a combination with GraphWriter, to dictate the order of edges /// public IEnumerable EdgeEnumeration { get { return edgeEnumeration; } set { edgeEnumeration = value; } } string DoubleToString(double d) { return (Math.Abs(d) < 1e-11) ? "0" : d.ToString(formatForDoubleString, CultureInfo.InvariantCulture); } void WriteLabel(Label label) { WriteAttribute(GeometryToken.Label, LabelToString(label)); } [SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider", MessageId = "System.String.Format(System.String,System.Object,System.Object,System.Object)")] string LabelToString(Label label) { return String.Format("{0} {1} {2}", PointToString(label.Center), DoubleToString(label.Width), DoubleToString(label.Height)); } /* void WriteUnderlyingPolyline(SmoothedPolyline polylinePointsCollection) { WriteStartElement(GeometryToken.UnderlyingPolyline); if (polylinePointsCollection == null) WriteStringElement(GeometryToken.UnderlyingPolylineIsNull, true); else { WriteStringElement(GeometryToken.UnderlyingPolylineIsNull, false); WritePolylineSites(polylinePointsCollection); } WriteEndElement(); } */ /* void WritePolylineSites(SmoothedPolyline polylinePointsCollection) { Site site = polylinePointsCollection.HeadSite; do { WritePolylineSite(site); site = site.Next; } while (site != null); } */ /* void WritePolylineSite(Site site) { WriteStartElement(GeometryToken.PolylineSite); WriteStringElement(GeometryToken.SiteK, site.PreviousBezierSegmentFitCoefficient); WriteStringElement(GeometryToken.SiteK, site.NextBezierSegmentFitCoefficient); WritePointElement(GeometryToken.SiteV, site.Point); WriteEndElement(); } */ void WriteNodes() { WriteStartElement(GeometryToken.Nodes); foreach (Node node in Graph.Nodes) WriteNode(node); WriteEndElement(); } void WriteNodesIpe() { foreach (Node node in Graph.Nodes) WriteNodeIpe(node); } [SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider", MessageId = "System.Int32.ToString")] void WriteNode(Node node) { string id; if (!nodeIds.TryGetValue(node, out id)) nodeIds[node] = id = nodeIds.Count.ToString(); WriteStartElement(GeometryToken.Node); WriteAttribute(GeometryToken.Id, id); if (node.Padding != GeometryGraphReader.NodeDefaultPadding) WriteAttribute(GeometryToken.Padding, node.Padding); WriteICurve(node.BoundaryCurve); WriteEndElement(); } void WriteNodeIpe(Node node) { string id; if (!nodeIds.TryGetValue(node, out id)) nodeIds[node] = id = nodeIds.Count.ToString(); // todo: add Id as text if (node.BoundaryCurve != null) { WriteICurveIpe(node.BoundaryCurve); WriteLabelIpe(node.BoundaryCurve.BoundingBox.Center, "" + id); } } void WriteLabelIpe(Point p, string label) { XmlWriter.WriteStartElement("text"); XmlWriter.WriteAttributeString("pos", p.X + " " + p.Y); XmlWriter.WriteAttributeString("halign", "center"); XmlWriter.WriteAttributeString("valign", "center"); XmlWriter.WriteString(label); XmlWriter.WriteEndElement(); } void WriteICurve(ICurve iCurve) { if (iCurve == null) return; var rect = iCurve as RoundedRect; if (rect != null) WriteRect(rect.BoundingBox.Left, rect.BoundingBox.Bottom, rect.BoundingBox.Width, rect.BoundingBox.Height, rect.RadiusX, rect.RadiusY); else { var c = iCurve as Curve; if (c != null) WriteCurveInSvgStyle(c); else { var ellipse = iCurve as Ellipse; if (ellipse != null) WriteEllipseInSvgStyle(ellipse); else { var poly = iCurve as Polyline; if (poly != null) WritePolylineInSvgStyle(poly); else { var ls = iCurve as LineSegment; if (ls != null) WriteLineSeg(ls); else { var bs = iCurve as CubicBezierSegment; if (bs != null) { WriteBezierSegment(bs); } else throw new InvalidOperationException(); } } } } } } void WriteBezierSegment(CubicBezierSegment bs) { WriteStartElement(GeometryToken.CubicBezierSegment); WriteAttribute(GeometryToken.Points, PointsToString(bs.B(0), bs.B(1), bs.B(2), bs.B(3))); WriteEndElement(); } void WriteICurveIpe(ICurve iCurve) { if (iCurve == null) return; var rect = iCurve as RoundedRect; if (rect != null) WriteRectIpe(rect.BoundingBox.Left, rect.BoundingBox.Bottom, rect.BoundingBox.Width, rect.BoundingBox.Height, rect.RadiusX, rect.RadiusY); return; } void WriteRect(double x, double y, double width, double height, double rx, double ry) { WriteStartElement(GeometryToken.Rect); WriteAttribute(GeometryToken.X, x); WriteAttribute(GeometryToken.Y, y); WriteAttribute(GeometryToken.Width, width); WriteAttribute(GeometryToken.Height, height); if (rx > 0) WriteAttribute(GeometryToken.Rx, rx); if (ry > 0) WriteAttribute(GeometryToken.Ry, ry); WriteEndElement(); } void WriteRectIpe(double x, double y, double width, double height, double rx, double ry) { XmlWriter.WriteStartElement("path"); XmlWriter.WriteString("\n" + x + " " + y + " m\n"); XmlWriter.WriteString((x + width) + " " + y + " l\n"); XmlWriter.WriteString((x + width) + " " + (y + height) + " l\n"); XmlWriter.WriteString(x + " " + (y + height) + " l\nh\n"); XmlWriter.WriteEndElement(); //48 832 m //48 752 l //144 752 l //144 832 l //h } void WritePolylineInSvgStyle(Polyline poly) { WriteStartElement(poly.Closed ? GeometryToken.Polygon : GeometryToken.Polyline); WriteAttribute(GeometryToken.Points, PointsToString(poly.ToArray())); WriteEndElement(); } void WriteEllipseInSvgStyle(Ellipse ellipse) { if (ApproximateComparer.Close(ellipse.ParStart, 0) && ApproximateComparer.Close(ellipse.ParEnd, 2 * Math.PI)) { WriteFullEllipse(ellipse); } else { WriteEllepticalArc(ellipse); } } void WriteFullEllipse(Ellipse ellipse) { WriteStartElement(GeometryToken.Ellipse); WriteAttribute(GeometryToken.Cx, ellipse.Center.X); WriteAttribute(GeometryToken.Cy, ellipse.Center.Y); WriteAttribute(GeometryToken.Rx, ellipse.AxisA.Length); WriteAttribute(GeometryToken.Ry, ellipse.AxisB.Length); WriteEndElement(); } // ReSharper disable UnusedParameter.Local [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "ellipse")] static void WriteEllepticalArc(Ellipse ellipse) { // ReSharper restore UnusedParameter.Local throw new NotImplementedException(); } void WriteCurveInSvgStyle(Curve curve) { WriteStartElement(GeometryToken.Curve); WriteAttribute(GeometryToken.CurveData, CurveString(curve)); WriteEndElement(); } string CurveString(ICurve iCurve) { return String.Join(" ", CurveStringTokens(iCurve)); } IEnumerable CurveStringTokens(ICurve iCurve) { yield return "M"; yield return PointToString(iCurve.Start); var curve = iCurve as Curve; var previousInstruction = 'w'; //a character that is not used by the SVG curve if (curve != null) for (int i = 0; i < curve.Segments.Count; i++) { var segment = curve.Segments[i]; if (i != curve.Segments.Count - 1) yield return SegmentString(segment, ref previousInstruction); else { //it is the last seg if (segment is LineSegment && ApproximateComparer.Close(segment.End, iCurve.Start)) yield return "Z"; else yield return SegmentString(segment, ref previousInstruction); } } } string SegmentString(ICurve segment, ref char previousInstruction) { var ls = segment as LineSegment; if (ls != null) { var str = LineSegmentString(ls, previousInstruction); previousInstruction = 'L'; return str; } var cubic = segment as CubicBezierSegment; if (cubic != null) { var str = CubicBezierSegmentToString(cubic, previousInstruction); previousInstruction = 'C'; return str; } var ellipseArc = segment as Ellipse; if (ellipseArc != null) { previousInstruction = 'A'; return EllipticalArcToString(ellipseArc); } throw new NotImplementedException(); } [SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider", MessageId = "System.Int32.ToString")] string EllipticalArcToString(Ellipse ellipse) { /* * rx ry x-axis-rotation large-arc-flag sweep-flag x y * */ //In general in an Msagl ellipse the axes don't have to be orthogonal: we have a possible bug here var rx = "A" + DoubleToString(ellipse.AxisA.Length); var ry = DoubleToString(ellipse.AxisB.Length); var xAxisRotation = DoubleToString(180 * Point.Angle(new Point(1, 0), ellipse.AxisA) / Math.PI); var largeArcFlag = Math.Abs(ellipse.ParEnd - ellipse.ParStart) >= Math.PI ? "1" : "0"; var sweepFlagInt = ellipse.ParEnd > ellipse.ParStart ? 1 : 0; //it happens because of the y-axis orientation down in SVG if (AxesSwapped(ellipse.AxisA, ellipse.AxisB)) { sweepFlagInt = sweepFlagInt == 1 ? 0 : 1; } var endPoint = PointToString(ellipse.End); return string.Join(" ", new[] { rx, ry, xAxisRotation, largeArcFlag, sweepFlagInt.ToString(), endPoint }); } static bool AxesSwapped(Point axisA, Point axisB) { return axisA.X * axisB.Y - axisB.X * axisA.Y < 0; } string CubicBezierSegmentToString(CubicBezierSegment cubic, char previousInstruction) { var str = PointsToString(cubic.B(1), cubic.B(2), cubic.B(3)); return previousInstruction == 'C' ? str : "C" + str; } string PointsToString(params Point[] points) { return String.Join(" ", points.Select(PointToString)); } string LineSegmentString(LineSegment ls, char previousInstruction) { var str = PointToString(ls.End); return previousInstruction == 'L' ? str : "L" + str; } void WriteLineSeg(LineSegment ls) { WriteStartElement(GeometryToken.LineSegment); WriteAttribute(GeometryToken.Points, PointsToString(ls.Start, ls.End)); WriteEndElement(); } [SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters", MessageId = "System.Xml.XmlWriter.WriteComment(System.String)")] void WriteTransformation(PlaneTransformation transformation) { WriteStartElement(GeometryToken.Transform); XmlWriter.WriteComment("the order of elements is [0,0],[0,1],[0,2],[1,0],[1,1],[1,2]"); for (int i = 0; i < 2; i++) for (int j = 0; j < 3; j++) WriteTransformationElement(transformation[i, j]); WriteEndElement(); } void WriteTransformationElement(double t) { WriteStringElement(GeometryToken.TransformElement, t); } /// /// writes the starte element with the token /// /// void WriteStartElement(GeometryToken token) { XmlWriter.WriteStartElement(FirstCharToLower(token)); } //static void WriteStartElement(XmlWriter writer, Tokens token) { // writer.WriteStartElement(token.ToString()); //} /// /// WriteStringElement with double /// /// /// void WriteStringElement(GeometryToken tokens, double element) { XmlWriter.WriteElementString(tokens.ToString(), XmlConvert.ToString(element)); } /// /// writes the end element /// void WriteEndElement() { XmlWriter.WriteEndElement(); } /// /// /// void WriteLayoutSettings() { if (Settings != null) { LayoutAlgorithmSettings settings = Settings; EdgeRoutingSettings routingSettings = settings.EdgeRoutingSettings; WriteStartElement(GeometryToken.LayoutAlgorithmSettings); WriteAttribute(GeometryToken.EdgeRoutingMode, (int)routingSettings.EdgeRoutingMode); var sugiyama = settings as SugiyamaLayoutSettings; if (sugiyama != null) WriteSugiyamaSettings(sugiyama); else { var mds = settings as MdsLayoutSettings; if (mds != null) { WriteAttribute(GeometryToken.LayoutAlgorithmType, GeometryToken.MdsLayoutSettings); #if REPORTING WriteAttribute(GeometryToken.Reporting, mds.Reporting); #endif WriteAttribute(GeometryToken.Exponent, mds.Exponent); WriteAttribute(GeometryToken.IterationsWithMajorization, mds.IterationsWithMajorization); WriteAttribute(GeometryToken.PivotNumber, mds.PivotNumber); WriteAttribute(GeometryToken.RotationAngle, mds.RotationAngle); WriteAttribute(GeometryToken.ScaleX, mds.ScaleX); WriteAttribute(GeometryToken.ScaleY, mds.ScaleY); } } XmlWriter.WriteEndElement(); } } void WriteSugiyamaSettings(SugiyamaLayoutSettings sugiyama) { WriteAttribute(GeometryToken.LayoutAlgorithmType, GeometryToken.SugiyamaLayoutSettings); WriteAttribute(GeometryToken.MinNodeWidth, sugiyama.MinNodeWidth); WriteAttribute(GeometryToken.MinNodeHeight, sugiyama.MinNodeHeight); WriteAttribute(GeometryToken.AspectRatio, sugiyama.AspectRatio); WriteAttribute(GeometryToken.NodeSeparation, sugiyama.NodeSeparation); #if REPORTING WriteAttribute(GeometryToken.Reporting, sugiyama.Reporting); #endif WriteAttribute(GeometryToken.RandomSeedForOrdering, sugiyama.RandomSeedForOrdering); WriteAttribute(GeometryToken.NoGainStepsBound, sugiyama.NoGainAdjacentSwapStepsBound); WriteAttribute(GeometryToken.MaxNumberOfPassesInOrdering, sugiyama.MaxNumberOfPassesInOrdering); WriteAttribute(GeometryToken.RepetitionCoefficientForOrdering, sugiyama.RepetitionCoefficientForOrdering); WriteAttribute(GeometryToken.GroupSplit, sugiyama.GroupSplit); WriteAttribute(GeometryToken.LabelCornersPreserveCoefficient, sugiyama.LabelCornersPreserveCoefficient); WriteAttribute(GeometryToken.BrandesThreshold, sugiyama.BrandesThreshold); WriteAttribute(GeometryToken.LayerSeparation, sugiyama.LayerSeparation); WriteTransformation(sugiyama.Transformation); } } } #endif