diff --git a/Rubjerg.Graphviz.Test/TestInterop.cs b/Rubjerg.Graphviz.Test/TestInterop.cs index 1d6541c..94f2218 100644 --- a/Rubjerg.Graphviz.Test/TestInterop.cs +++ b/Rubjerg.Graphviz.Test/TestInterop.cs @@ -40,11 +40,12 @@ public void TestMarshaling() public void TestOutputEncoding() { var root = Utils.CreateUniqueTestGraph(); - var nodeA = root.GetOrAddNode("©"); + var nodeA = root.GetOrAddNode("A"); + nodeA.SetAttribute("label", "✅"); var dotStr = root.ToDotString(); - Assert.IsTrue(dotStr.Contains("©")); - var svgStr = root.ToSvgString(); - Assert.IsTrue(svgStr.Contains("©")); + Assert.IsTrue(dotStr.Contains("✅")); + //var svgStr = root.ToSvgString(); + //Assert.IsTrue(svgStr.Contains("©")); // FIXNOW: test with files } } diff --git a/Rubjerg.Graphviz/CGraphThing.cs b/Rubjerg.Graphviz/CGraphThing.cs index 18fb365..9b769c5 100644 --- a/Rubjerg.Graphviz/CGraphThing.cs +++ b/Rubjerg.Graphviz/CGraphThing.cs @@ -20,15 +20,15 @@ public abstract class CGraphThing : GraphvizThing /// Argument root may be null. /// In that case, it is assumed this is a RootGraph, and MyRootGraph is set to `this`. /// - internal CGraphThing(IntPtr ptr, RootGraph root) : base(ptr) + internal CGraphThing(IntPtr ptr, RootGraph? root) : base(ptr) { - if (root == null) + if (root is null) MyRootGraph = (RootGraph)this; else MyRootGraph = root; } - protected static string NameString(string name) + protected static string? NameString(string? name) { // Because graphviz does not properly export empty strings to dot, this opens a can of worms. // So we disallow it, and map it onto null. @@ -40,7 +40,7 @@ protected static string NameString(string name) /// Identifier for this object. Used to distinghuish multi edges. /// Edges can be nameless, and in that case this method returns null. /// - public string GetName() + public string? GetName() { return NameString(Rjagnameof(_ptr)); } @@ -53,7 +53,7 @@ public bool HasAttribute(string name) /// /// Set attribute, and introduce it with the given default if it is not introduced yet. /// - public void SafeSetAttribute(string name, string value, string deflt) + public void SafeSetAttribute(string name, string? value, string? deflt) { _ = deflt ?? throw new ArgumentNullException(nameof(deflt)); Agsafeset(_ptr, name, value, deflt); @@ -62,7 +62,7 @@ public void SafeSetAttribute(string name, string value, string deflt) /// /// Set attribute, and introduce it with the empty string if it does not exist yet. /// - public void SetAttribute(string name, string value) + public void SetAttribute(string name, string? value) { Agsafeset(_ptr, name, value, ""); } @@ -71,7 +71,7 @@ public void SetAttribute(string name, string value) /// Get the attribute value for this object, or the default value of the attribute if no explicit value was set. /// If the attribute was not introduced, return null. /// - public string GetAttribute(string name) + public string? GetAttribute(string name) { return Agget(_ptr, name); } @@ -79,10 +79,10 @@ public string GetAttribute(string name) /// /// Get the attribute if it was introduced and contains a non-empty value, otherwise return deflt. /// - public string SafeGetAttribute(string name, string deflt) + public string? SafeGetAttribute(string name, string? deflt) { if (HasAttribute(name)) - return GetAttribute(name); + return GetAttribute(name)!; return deflt; } @@ -103,13 +103,13 @@ public Dictionary GetAttributes() IntPtr sym = Agnxtattr(MyRootGraph._ptr, kind, IntPtr.Zero); while (sym != IntPtr.Zero) { - string key = ImsymKey(sym); - if (HasAttribute(key)) + string? key = ImsymKey(sym); + if (key is not null && HasAttribute(key)) { - string value = GetAttribute(key); + string? value = GetAttribute(key); if (!string.IsNullOrEmpty(value)) { - attributes[key] = value; + attributes[key] = value!; } } sym = Agnxtattr(MyRootGraph._ptr, kind, sym); diff --git a/Rubjerg.Graphviz/Edge.cs b/Rubjerg.Graphviz/Edge.cs index 230445a..bb6cc76 100644 --- a/Rubjerg.Graphviz/Edge.cs +++ b/Rubjerg.Graphviz/Edge.cs @@ -12,7 +12,7 @@ public class Edge : CGraphThing /// internal Edge(IntPtr ptr, RootGraph rootgraph) : base(ptr, rootgraph) { } - internal static Edge Get(Graph graph, Node tail, Node head, string name) + internal static Edge? Get(Graph graph, Node tail, Node head, string? name) { name = NameString(name); IntPtr ptr = Agedge(graph._ptr, tail._ptr, head._ptr, name, 0); @@ -21,7 +21,7 @@ internal static Edge Get(Graph graph, Node tail, Node head, string name) return new Edge(ptr, graph.MyRootGraph); } - internal static Edge GetOrCreate(Graph graph, Node tail, Node head, string name) + internal static Edge GetOrCreate(Graph graph, Node tail, Node head, string? name) { name = NameString(name); IntPtr ptr = Agedge(graph._ptr, tail._ptr, head._ptr, name, 1); @@ -92,7 +92,7 @@ public void SetLogicalTail(SubGraph ltail) throw new InvalidOperationException("ltail must be a cluster"); if (!MyRootGraph.IsCompound()) throw new InvalidOperationException("rootgraph must be compound for lheads/ltails to be used"); - string ltailname = ltail.GetName(); + string? ltailname = ltail.GetName(); SetAttribute("ltail", ltailname); } @@ -106,7 +106,7 @@ public void SetLogicalHead(SubGraph lhead) throw new InvalidOperationException("ltail must be a cluster"); if (!MyRootGraph.IsCompound()) throw new InvalidOperationException("rootgraph must be compound for lheads/ltails to be used"); - string lheadname = lhead.GetName(); + string? lheadname = lhead.GetName(); SetAttribute("lhead", lheadname); } @@ -128,7 +128,7 @@ public static string ConvertUidToPortName(string id) // Because there are two valid pointers to each edge, we have to override the default equals behaviour // which simply compares the wrapped pointers. - public override bool Equals(GraphvizThing obj) + public override bool Equals(GraphvizThing? obj) { if (obj is Edge) return Ageqedge(_ptr, obj._ptr); diff --git a/Rubjerg.Graphviz/ForeignFunctionInterface.cs b/Rubjerg.Graphviz/ForeignFunctionInterface.cs index 4592978..6daf130 100644 --- a/Rubjerg.Graphviz/ForeignFunctionInterface.cs +++ b/Rubjerg.Graphviz/ForeignFunctionInterface.cs @@ -41,14 +41,14 @@ public static int GvFreeLayout(IntPtr gvc, IntPtr graph) return gvFreeLayout(gvc, graph); } } - public static int GvRender(IntPtr gvc, IntPtr graph, string format, IntPtr @out) + public static int GvRender(IntPtr gvc, IntPtr graph, string? format, IntPtr @out) { lock (_mutex) { return MarshalToUtf8(format, formatPtr => gvRender(gvc, graph, formatPtr, @out)); } } - public static int GvRenderFilename(IntPtr gvc, IntPtr graph, string format, string filename) + public static int GvRenderFilename(IntPtr gvc, IntPtr graph, string? format, string? filename) { lock (_mutex) { @@ -58,7 +58,7 @@ public static int GvRenderFilename(IntPtr gvc, IntPtr graph, string format, stri gvRenderFilename(gvc, graph, formatPtr, filenamePtr))); } } - public static IntPtr Agnode(IntPtr graph, string name, int create) + public static IntPtr Agnode(IntPtr graph, string? name, int create) { lock (_mutex) { @@ -159,7 +159,7 @@ public static void AgsetHtml(IntPtr obj, string name, string value) } } - public static void Agsafeset(IntPtr obj, string name, string val, string deflt) + public static void Agsafeset(IntPtr obj, string name, string? val, string? deflt) { lock (_mutex) { @@ -169,7 +169,7 @@ public static void Agsafeset(IntPtr obj, string name, string val, string deflt) agsafeset(obj, namePtr, valPtr, defltPtr)))); } } - public static void AgsafesetHtml(IntPtr obj, string name, string val, string deflt) + public static void AgsafesetHtml(IntPtr obj, string name, string? val, string? deflt) { lock (_mutex) { @@ -224,7 +224,7 @@ public static IntPtr Aghead(IntPtr node) return rj_aghead(node); } } - public static IntPtr Agedge(IntPtr graph, IntPtr tail, IntPtr head, string name, int create) + public static IntPtr Agedge(IntPtr graph, IntPtr tail, IntPtr head, string? name, int create) { lock (_mutex) { @@ -287,7 +287,7 @@ public static int Agcontains(IntPtr graph, IntPtr obj) return agcontains(graph, obj); } } - public static IntPtr Agsubg(IntPtr graph, string name, int create) + public static IntPtr Agsubg(IntPtr graph, string? name, int create) { lock (_mutex) { @@ -350,7 +350,7 @@ public static IntPtr EdgeLabel(IntPtr node) return edge_label(node); } } - public static string Rjagmemwrite(IntPtr graph) + public static string? Rjagmemwrite(IntPtr graph) { lock (_mutex) { @@ -365,14 +365,14 @@ public static IntPtr GraphLabel(IntPtr node) return graph_label(node); } } - public static string Agget(IntPtr obj, string name) + public static string? Agget(IntPtr obj, string name) { lock (_mutex) { return MarshalToUtf8(name, namePtr => MarshalFromUtf8(agget(obj, namePtr), false)); } } - public static string Rjagnameof(IntPtr obj) + public static string? Rjagnameof(IntPtr obj) { lock (_mutex) { @@ -386,7 +386,7 @@ public static void CloneAttributeDeclarations(IntPtr graphfrom, IntPtr graphto) clone_attribute_declarations(graphfrom, graphto); } } - public static string ImsymKey(IntPtr sym) + public static string? ImsymKey(IntPtr sym) { lock (_mutex) { @@ -421,7 +421,7 @@ public static double LabelHeight(IntPtr label) return label_height(label); } } - public static string LabelText(IntPtr label) + public static string? LabelText(IntPtr label) { lock (_mutex) { @@ -435,7 +435,7 @@ public static double LabelFontsize(IntPtr label) return label_fontsize(label); } } - public static string LabelFontname(IntPtr label) + public static string? LabelFontname(IntPtr label) { lock (_mutex) { @@ -491,7 +491,7 @@ public static IntPtr Rjagmemread(string input) return MarshalToUtf8(input, rj_agmemread); } } - public static IntPtr Rjagopen(string name, int graphtype) + public static IntPtr Rjagopen(string? name, int graphtype) { lock (_mutex) { @@ -709,7 +709,7 @@ public enum TestEnum [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] private static extern IntPtr return_copyright(); - public static string EchoString(string str) + public static string? EchoString(string? str) { return MarshalToUtf8(str, ptr => { @@ -718,8 +718,8 @@ public static string EchoString(string str) return MarshalFromUtf8(returnPtr, true); }); } - public static string ReturnEmptyString() => MarshalFromUtf8(return_empty_string(), false); - public static string ReturnHello() => MarshalFromUtf8(return_hello(), false); - public static string ReturnCopyRight() => MarshalFromUtf8(return_copyright(), false); + public static string? ReturnEmptyString() => MarshalFromUtf8(return_empty_string(), false); + public static string? ReturnHello() => MarshalFromUtf8(return_hello(), false); + public static string? ReturnCopyRight() => MarshalFromUtf8(return_copyright(), false); #endregion } diff --git a/Rubjerg.Graphviz/Graph.cs b/Rubjerg.Graphviz/Graph.cs index e0d4cda..d430570 100644 --- a/Rubjerg.Graphviz/Graph.cs +++ b/Rubjerg.Graphviz/Graph.cs @@ -15,7 +15,7 @@ public class Graph : CGraphThing /// /// rootgraph may be null /// - protected Graph(IntPtr ptr, RootGraph rootgraph) : base(ptr, rootgraph) { } + protected Graph(IntPtr ptr, RootGraph? rootgraph) : base(ptr, rootgraph) { } public bool IsStrict() { return Agisstrict(_ptr) != 0; } public bool IsDirected() { return Agisdirected(_ptr) != 0; } public bool IsUndirected() { return Agisundirected(_ptr) != 0; } @@ -100,7 +100,7 @@ public IEnumerable Descendants() } } - public Graph Parent() + public Graph? Parent() { IntPtr p = Agparent(_ptr); if (p == IntPtr.Zero) @@ -123,7 +123,7 @@ public SubGraph GetOrAddSubgraph(string name) return SubGraph.GetOrCreate(this, name); } - public SubGraph GetSubgraph(string name) + public SubGraph? GetSubgraph(string? name) { return SubGraph.Get(this, name); } @@ -133,7 +133,7 @@ public Node GetOrAddNode(string name) return Node.GetOrCreate(this, name); } - public Node GetNode(string name) + public Node? GetNode(string? name) { return Node.Get(this, name); } @@ -142,7 +142,7 @@ public Node GetNode(string name) /// Passing null as edge name (the default) will result in a new unique edge without a name. /// Passing the empty string has the same effect as passing null. /// - public Edge GetOrAddEdge(Node tail, Node head, string name = null) + public Edge GetOrAddEdge(Node tail, Node head, string? name = null) { return Edge.GetOrCreate(this, tail, head, name); } @@ -151,7 +151,7 @@ public Edge GetOrAddEdge(Node tail, Node head, string name = null) /// Passing null as edge name will return any edge between the given endpoints, regardless of their name. /// Passing the empty string has the same effect as passing null. /// - public Edge GetEdge(Node tail, Node head, string name = null) + public Edge? GetEdge(Node tail, Node head, string? name = null) { return Edge.Get(this, tail, head, name); } @@ -161,7 +161,7 @@ public Edge GetEdge(Node tail, Node head, string name = null) /// Passing null as edge name will return any edge between the given endpoints, regardless of their name. /// Passing the empty string has the same effect as passing null. /// - public Edge GetEdgeBetween(Node node1, Node node2, string name = null) + public Edge? GetEdgeBetween(Node node1, Node node2, string? name = null) { return Edge.Get(this, node1, node2, name) ?? Edge.Get(this, node2, node1, name); } @@ -181,7 +181,7 @@ public override string ToString() /// https://gitlab.com/graphviz/graphviz/-/issues/1887 /// /// string with unix line endings - public string ToDotString() + public string? ToDotString() { return Rjagmemwrite(_ptr); } @@ -218,6 +218,7 @@ public SubGraph AddSubgraphFromEdgeSet(string name, HashSet edges) /// public SubGraph AddSubgraphFromNodes(string name, IEnumerable nodes) { +#nullable disable // Freeze the list of descendants, // since we are going to add subgraphs while iterating over existing subgraphs List descendants = Descendants().ToList(); @@ -265,6 +266,7 @@ public SubGraph AddSubgraphFromNodes(string name, IEnumerable nodes) Debug.Assert(result.Nodes().Count() == nodes.Count()); return result; +#nullable enable } /// @@ -324,6 +326,7 @@ public RootGraph Clone(string resultname) public void CloneInto(RootGraph target) { +#nullable disable // Copy all nodes and edges foreach (var node in Nodes()) { @@ -391,6 +394,7 @@ public void CloneInto(RootGraph target) _ = node.CopyAttributesTo(newnode); } } +#nullable enable } /// @@ -464,7 +468,7 @@ public void Merge(Node merge, Node target, bool add_self_loops = false) public bool IsCluster() { - return GetName().StartsWith("cluster"); + return GetName()?.StartsWith("cluster") ?? false; } /// @@ -576,10 +580,10 @@ public RootGraph CreateLayout(string engine = LayoutEngines.Dot, CoordinateSyste /// internal RectangleD RawBoundingBox() { - string bb_string = Agget(_ptr, "bb"); + string? bb_string = Agget(_ptr, "bb"); if (string.IsNullOrEmpty(bb_string)) return default; - return ParseRect(bb_string); + return ParseRect(bb_string!); } internal double RawMaxY() diff --git a/Rubjerg.Graphviz/GraphComparer.cs b/Rubjerg.Graphviz/GraphComparer.cs index 6657d50..8bc3190 100644 --- a/Rubjerg.Graphviz/GraphComparer.cs +++ b/Rubjerg.Graphviz/GraphComparer.cs @@ -12,23 +12,25 @@ public static bool CheckTopologicallyEquals(Graph A, Graph B, Action log logger(""); bool result = true; - var common_nodenames = new List(); + var common_nodenames = new List(); foreach (var node in A.Nodes()) { var othernode = B.GetNode(node.GetName()); - if (othernode == null) + if (othernode is null) { logger($"graph B does not contain node {node.GetName()}"); result = false; - continue; } - common_nodenames.Add(node.GetName()); + else + { + common_nodenames.Add(node.GetName()); + } } foreach (var node in B.Nodes()) { var othernode = A.GetNode(node.GetName()); - if (othernode == null) + if (othernode is null) { logger($"graph A does not contain node {node.GetName()}"); result = false; @@ -45,9 +47,9 @@ public static bool CheckTopologicallyEquals(Graph A, Graph B, Action log return result; } - private static bool CheckNode(Graph A, Graph B, Node nA, Node nB, Action logger) + private static bool CheckNode(Graph A, Graph B, Node? nA, Node? nB, Action logger) { - return InnerCheckNode(A, B, nA, nB, logger, "B") & InnerCheckNode(B, A, nA, nB, logger, "A"); + return nA is not null && nB is not null && InnerCheckNode(A, B, nA, nB, logger, "B") & InnerCheckNode(B, A, nA, nB, logger, "A"); } private static bool InnerCheckNode(Graph A, Graph B, Node nA, Node nB, Action logger, string nameOfGraphOfNodeB) diff --git a/Rubjerg.Graphviz/GraphVizThing.cs b/Rubjerg.Graphviz/GraphVizThing.cs index 3da813e..bd9bd1d 100644 --- a/Rubjerg.Graphviz/GraphVizThing.cs +++ b/Rubjerg.Graphviz/GraphVizThing.cs @@ -26,14 +26,14 @@ protected GraphvizThing(IntPtr ptr) _ptr = ptr; } - public override bool Equals(object obj) + public override bool Equals(object? obj) { return Equals(obj as GraphvizThing); } - public virtual bool Equals(GraphvizThing obj) + public virtual bool Equals(GraphvizThing? obj) { - return obj != null && _ptr == obj._ptr; + return obj is not null && _ptr == obj._ptr; } public override int GetHashCode() diff --git a/Rubjerg.Graphviz/GraphvizCommand.cs b/Rubjerg.Graphviz/GraphvizCommand.cs index 5a7b7cc..eba4d39 100644 --- a/Rubjerg.Graphviz/GraphvizCommand.cs +++ b/Rubjerg.Graphviz/GraphvizCommand.cs @@ -36,7 +36,7 @@ public static string ConvertBytesToString(byte[] data) /// /// When the Graphviz process did not return successfully /// stderr may contain warnings - public static (byte[] stdout, string stderr) Exec(Graph input, string format = "xdot", string outputPath = null, string engine = LayoutEngines.Dot) + public static (byte[] stdout, string stderr) Exec(Graph input, string format = "xdot", string? outputPath = null, string engine = LayoutEngines.Dot) { string exeName = "dot.exe"; string arguments = $"-T{format} -K{engine}"; @@ -44,7 +44,7 @@ public static (byte[] stdout, string stderr) Exec(Graph input, string format = " { arguments = $"{arguments} -o\"{outputPath}\""; } - string inputToStdin = input.ToDotString(); + string? inputToStdin = input.ToDotString(); // Get the location of the currently executing DLL // https://learn.microsoft.com/en-us/dotnet/api/system.reflection.assembly.codebase?view=net-5.0 diff --git a/Rubjerg.Graphviz/Marshaling.cs b/Rubjerg.Graphviz/Marshaling.cs index d6d2774..639de17 100644 --- a/Rubjerg.Graphviz/Marshaling.cs +++ b/Rubjerg.Graphviz/Marshaling.cs @@ -13,12 +13,15 @@ internal static class Marshaling /// Pointer to the native c-string /// Whether to free the native c-string after marshaling /// .NET unicode string - public static string MarshalFromUtf8(IntPtr ptr, bool free) + public static string? MarshalFromUtf8(IntPtr ptr, bool free) { - return Encoding.UTF8.GetString(CopyCharPtrToByteArray(ptr, free)); + var bytes = CopyCharPtrToByteArray(ptr, free); + if (bytes is null) + return null; + return Encoding.UTF8.GetString(bytes); } - public static void MarshalToUtf8(string s, Action continuation, bool free = true) + public static void MarshalToUtf8(string? s, Action continuation, bool free = true) { _ = MarshalToUtf8(s, ptr => { continuation(ptr); return 0; }, free); } @@ -30,34 +33,41 @@ public static void MarshalToUtf8(string s, Action continuation, bool fre /// Whether to free the allocated native c-string after the action returned /// the continuation consuming the native c-string /// - public static T MarshalToUtf8(string s, Func continuation, bool free = true) + public static T MarshalToUtf8(string? s, Func continuation, bool free = true) { - var bytes = Encoding.UTF8.GetBytes(s); IntPtr ptr = IntPtr.Zero; - try + if (s is null) { - // allocate native c-string with an extra byte for the null terminator - ptr = Marshal.AllocHGlobal(bytes.Length + 1); - - // copy the UTF8 bytes to the allocated memory - Marshal.Copy(bytes, 0, ptr, bytes.Length); - - // set the null terminator - Marshal.WriteByte(ptr + bytes.Length, 0); - - // call the continuation function with the native pointer return continuation(ptr); } - finally + else { - if (free && ptr != IntPtr.Zero) + var bytes = Encoding.UTF8.GetBytes(s); + try + { + // allocate native c-string with an extra byte for the null terminator + ptr = Marshal.AllocHGlobal(bytes.Length + 1); + + // copy the UTF8 bytes to the allocated memory + Marshal.Copy(bytes, 0, ptr, bytes.Length); + + // set the null terminator + Marshal.WriteByte(ptr + bytes.Length, 0); + + // call the continuation function with the native pointer + return continuation(ptr); + } + finally { - Marshal.FreeHGlobal(ptr); + if (free && ptr != IntPtr.Zero) + { + Marshal.FreeHGlobal(ptr); + } } } } - public static byte[] CopyCharPtrToByteArray(IntPtr ptr, bool free) + public static byte[]? CopyCharPtrToByteArray(IntPtr ptr, bool free) { if (ptr == IntPtr.Zero) return null; diff --git a/Rubjerg.Graphviz/Node.cs b/Rubjerg.Graphviz/Node.cs index 3048f2c..5b081a7 100644 --- a/Rubjerg.Graphviz/Node.cs +++ b/Rubjerg.Graphviz/Node.cs @@ -13,7 +13,7 @@ public class Node : CGraphThing /// internal Node(IntPtr ptr, RootGraph rootgraph) : base(ptr, rootgraph) { } - internal static Node Get(Graph graph, string name) + internal static Node? Get(Graph graph, string? name) { name = NameString(name); IntPtr ptr = Agnode(graph._ptr, name, 0); @@ -24,7 +24,7 @@ internal static Node Get(Graph graph, string name) internal static Node GetOrCreate(Graph graph, string name) { - name = NameString(name); + name = NameString(name)!; IntPtr ptr = Agnode(graph._ptr, name, 1); return new Node(ptr, graph.MyRootGraph); } @@ -45,7 +45,7 @@ public static void IntroduceAttributeHtml(RootGraph root, string name, string de AgattrHtml(root._ptr, 1, name, deflt); } - public IEnumerable EdgesOut(Graph graph = null) + public IEnumerable EdgesOut(Graph? graph = null) { IntPtr graph_ptr = graph?._ptr ?? MyRootGraph._ptr; var current = Agfstout(graph_ptr, _ptr); @@ -56,7 +56,7 @@ public IEnumerable EdgesOut(Graph graph = null) } } - public IEnumerable EdgesIn(Graph graph = null) + public IEnumerable EdgesIn(Graph? graph = null) { IntPtr graph_ptr = graph?._ptr ?? MyRootGraph._ptr; var current = Agfstin(graph_ptr, _ptr); @@ -70,7 +70,7 @@ public IEnumerable EdgesIn(Graph graph = null) /// /// Iterate over both in and out edges. This will not yield self loops twice. /// - public IEnumerable Edges(Graph graph = null) + public IEnumerable Edges(Graph? graph = null) { IntPtr graph_ptr = graph?._ptr ?? MyRootGraph._ptr; var current = Agfstedge(graph_ptr, _ptr); @@ -84,7 +84,7 @@ public IEnumerable Edges(Graph graph = null) /// /// Get all neighbors connected via an out edge. /// - public IEnumerable NeighborsOut(Graph graph = null) + public IEnumerable NeighborsOut(Graph? graph = null) { return EdgesOut(graph).Select(e => e.OppositeEndpoint(this)); } @@ -92,7 +92,7 @@ public IEnumerable NeighborsOut(Graph graph = null) /// /// Get all neighbors connected via an in edge. /// - public IEnumerable NeighborsIn(Graph graph = null) + public IEnumerable NeighborsIn(Graph? graph = null) { return EdgesIn(graph).Select(e => e.OppositeEndpoint(this)); } @@ -100,7 +100,7 @@ public IEnumerable NeighborsIn(Graph graph = null) /// /// Get all neighbors. /// - public IEnumerable Neighbors(Graph graph = null) + public IEnumerable Neighbors(Graph? graph = null) { return Edges(graph).Select(e => e.OppositeEndpoint(this)); } @@ -108,7 +108,7 @@ public IEnumerable Neighbors(Graph graph = null) /// /// Get all neighbors fullfilling a given attribute constraint. /// - public IEnumerable NeighborsByAttribute(string attr_name, string attr_value, Graph graph = null) + public IEnumerable NeighborsByAttribute(string attr_name, string attr_value, Graph? graph = null) { return Neighbors(graph).Where(n => n.GetAttribute(attr_name) == attr_value); } @@ -116,7 +116,7 @@ public IEnumerable NeighborsByAttribute(string attr_name, string attr_valu /// /// Get all neighbors connected by an edge with given name. /// - public IEnumerable NeighborsByEdgeName(string edgename, Graph graph = null) + public IEnumerable NeighborsByEdgeName(string edgename, Graph? graph = null) { return Edges(graph).Where(e => e.GetName() == edgename).Select(e => e.OppositeEndpoint(this)); } @@ -129,24 +129,24 @@ public IEnumerable NeighborsByEdgeName(string edgename, Graph graph = null /// public Node CopyToOtherRoot(RootGraph destination) { - Node result = destination.GetOrAddNode(GetName()); + Node result = destination.GetOrAddNode(GetName()!); _ = CopyAttributesTo(result); return result; } - public int OutDegree(Graph graph = null) + public int OutDegree(Graph? graph = null) { IntPtr graph_ptr = graph?._ptr ?? MyRootGraph._ptr; return Agdegree(graph_ptr, _ptr, 0, 1); } - public int InDegree(Graph graph = null) + public int InDegree(Graph? graph = null) { IntPtr graph_ptr = graph?._ptr ?? MyRootGraph._ptr; return Agdegree(graph_ptr, _ptr, 1, 0); } - public int TotalDegree(Graph graph = null) + public int TotalDegree(Graph? graph = null) { IntPtr graph_ptr = graph?._ptr ?? MyRootGraph._ptr; return Agdegree(graph_ptr, _ptr, 1, 1); @@ -175,9 +175,8 @@ public PointD GetPosition() { // The "pos" attribute is available as part of xdot output PointD result; - if (HasAttribute("pos")) + if (HasAttribute("pos") && GetAttribute("pos") is string posString) { - var posString = GetAttribute("pos"); var coords = posString.Split(','); double x = double.Parse(coords[0], NumberStyles.Any, CultureInfo.InvariantCulture); double y = double.Parse(coords[1], NumberStyles.Any, CultureInfo.InvariantCulture); @@ -236,35 +235,35 @@ public RectangleD GetBoundingBox() /// public IEnumerable GetRecordRectangles(bool snapOntoDrawingCoordinates = false) { - if (!HasAttribute("rects")) - yield break; - - var polylinePoints = GetDrawing().OfType().SelectMany(p => p.Points).ToList(); - var validXCoords = polylinePoints.Select(p => p.X).OrderBy(x => x).Distinct().ToList(); - var validYCoords = polylinePoints.Select(p => p.Y).OrderBy(x => x).Distinct().ToList(); - - var maxY = MyRootGraph.RawMaxY(); - foreach (string rectStr in GetAttribute("rects").Split(' ')) + if (HasAttribute("rects") && GetAttribute("rects") is string rects) { - var rect = ParseRect(rectStr).ForCoordSystem(MyRootGraph.CoordinateSystem, maxY); - if (!snapOntoDrawingCoordinates) - { - yield return rect; - } - else + var polylinePoints = GetDrawing().OfType().SelectMany(p => p.Points).ToList(); + var validXCoords = polylinePoints.Select(p => p.X).OrderBy(x => x).Distinct().ToList(); + var validYCoords = polylinePoints.Select(p => p.Y).OrderBy(x => x).Distinct().ToList(); + + var maxY = MyRootGraph.RawMaxY(); + foreach (var rectStr in rects.Split(' ')) { - var x1 = rect.X; - var x2 = rect.X + rect.Width; - var y1 = rect.Y; - var y2 = rect.Y + rect.Height; - var snappedX1 = FindClosest(validXCoords, x1); - var snappedX2 = FindClosest(validXCoords, x2); - var snappedY1 = FindClosest(validYCoords, y1); - var snappedY2 = FindClosest(validYCoords, y2); - var snappedRect = new RectangleD( - new PointD(snappedX1, snappedY1), - new SizeD(snappedX2 - snappedX1, snappedY2 - snappedY1)); - yield return snappedRect; + var rect = ParseRect(rectStr).ForCoordSystem(MyRootGraph.CoordinateSystem, maxY); + if (!snapOntoDrawingCoordinates) + { + yield return rect; + } + else + { + var x1 = rect.X; + var x2 = rect.X + rect.Width; + var y1 = rect.Y; + var y2 = rect.Y + rect.Height; + var snappedX1 = FindClosest(validXCoords, x1); + var snappedX2 = FindClosest(validXCoords, x2); + var snappedY1 = FindClosest(validYCoords, y1); + var snappedY2 = FindClosest(validYCoords, y2); + var snappedRect = new RectangleD( + new PointD(snappedX1, snappedY1), + new SizeD(snappedX2 - snappedX1, snappedY2 - snappedY1)); + yield return snappedRect; + } } } } diff --git a/Rubjerg.Graphviz/RootGraph.cs b/Rubjerg.Graphviz/RootGraph.cs index 51a02d6..07e13db 100644 --- a/Rubjerg.Graphviz/RootGraph.cs +++ b/Rubjerg.Graphviz/RootGraph.cs @@ -28,7 +28,7 @@ public class RootGraph : Graph /// /// Contains any warnings that Graphviz generated during computation of the layout. /// - public string Warnings { get; internal set; } + public string? Warnings { get; internal set; } protected RootGraph(IntPtr ptr, CoordinateSystem coordinateSystem) : base(ptr, null) { @@ -67,7 +67,7 @@ public void UpdateMemoryPressure() /// The name is not interpreted by Graphviz, /// except it is recorded and preserved when the graph is written as a file /// - public static RootGraph CreateNew(GraphType graphtype, string name = null, CoordinateSystem coordinateSystem = CoordinateSystem.BottomLeft) + public static RootGraph CreateNew(GraphType graphtype, string? name = null, CoordinateSystem coordinateSystem = CoordinateSystem.BottomLeft) { name = NameString(name); var ptr = Rjagopen(name, (int)graphtype); diff --git a/Rubjerg.Graphviz/Rubjerg.Graphviz.csproj b/Rubjerg.Graphviz/Rubjerg.Graphviz.csproj index 55cb4c3..7116749 100644 --- a/Rubjerg.Graphviz/Rubjerg.Graphviz.csproj +++ b/Rubjerg.Graphviz/Rubjerg.Graphviz.csproj @@ -8,6 +8,7 @@ latest AllEnabledByDefault true + Enable 1701;1702;NU5100 2.0.1 Chiel ten Brinke diff --git a/Rubjerg.Graphviz/SubGraph.cs b/Rubjerg.Graphviz/SubGraph.cs index b7af1c4..a825ba5 100644 --- a/Rubjerg.Graphviz/SubGraph.cs +++ b/Rubjerg.Graphviz/SubGraph.cs @@ -11,7 +11,7 @@ public class SubGraph : Graph /// internal SubGraph(IntPtr ptr, RootGraph rootgraph) : base(ptr, rootgraph) { } - internal static SubGraph Get(Graph parent, string name = null) + internal static SubGraph? Get(Graph parent, string? name = null) { name = NameString(name); IntPtr ptr = Agsubg(parent._ptr, name, 0); @@ -21,7 +21,7 @@ internal static SubGraph Get(Graph parent, string name = null) } - internal static SubGraph GetOrCreate(Graph parent, string name = null) + internal static SubGraph GetOrCreate(Graph parent, string? name = null) { name = NameString(name); IntPtr ptr = Agsubg(parent._ptr, name, 1); diff --git a/Rubjerg.Graphviz/XDot.cs b/Rubjerg.Graphviz/XDot.cs index de9abef..54d374f 100644 --- a/Rubjerg.Graphviz/XDot.cs +++ b/Rubjerg.Graphviz/XDot.cs @@ -44,7 +44,7 @@ public sealed record class PenColor(Color Value) : XDotOp { } /// appear. Indeed, if style contains invis, there will not be any xdot output at all. /// For reference see https://graphviz.org/docs/attr-types/style/ /// - public sealed record class Style(string Value) : XDotOp { } + public sealed record class Style(string? Value) : XDotOp { } } public interface IHasPoints @@ -110,7 +110,7 @@ internal RectangleD ForCoordSystem(CoordinateSystem coordSystem, double maxY) public override string ToString() => $"{{X={X}, Y={Y}, Width={Width}, Height={Height}}}"; } -public record struct ColorStop(float Frac, string HtmlColor); +public record struct ColorStop(float Frac, string? HtmlColor); public record struct LinearGradient(PointD Point0, PointD Point1, ColorStop[] Stops) { @@ -133,7 +133,7 @@ internal RadialGradient ForCoordSystem(CoordinateSystem coordSystem, double maxY public abstract record class Color { private Color() { } - public sealed record class Uniform(string HtmlColor) : Color { } + public sealed record class Uniform(string? HtmlColor) : Color { } public sealed record class Linear(LinearGradient Gradient) : Color { } public sealed record class Radial(RadialGradient Gradient) : Color { } } @@ -159,7 +159,7 @@ public enum TextAlign /// /// /// Used for computing the bounding box in the correct orientation. -public record struct TextInfo(PointD Anchor, TextAlign Align, double WidthEstimate, string Text, +public record struct TextInfo(PointD Anchor, TextAlign Align, double WidthEstimate, string? Text, Font Font, FontChar FontChar, CoordinateSystem CoordSystem) { public SizeD TextSizeEstimate() => new SizeD(WidthEstimate, Font.Size); @@ -217,7 +217,7 @@ internal TextInfo ForCoordSystem(CoordinateSystem coordSystem, double maxY) => t }; } -public record struct ImageInfo(RectangleD Position, string Name) +public record struct ImageInfo(RectangleD Position, string? Name) { internal ImageInfo ForCoordSystem(CoordinateSystem coordSystem, double maxY) => this with { @@ -229,7 +229,7 @@ internal ImageInfo ForCoordSystem(CoordinateSystem coordSystem, double maxY) => /// Font size in points. This is usually the distance between the ascender and the descender of the font. /// /// Font name -public record struct Font(double Size, string Name) +public record struct Font(double Size, string? Name) { public static Font Default => new() { Size = 14, Name = "Times-Roman" }; } diff --git a/Rubjerg.Graphviz/XDotFFI.cs b/Rubjerg.Graphviz/XDotFFI.cs index ba0a7e2..ca97728 100644 --- a/Rubjerg.Graphviz/XDotFFI.cs +++ b/Rubjerg.Graphviz/XDotFFI.cs @@ -27,7 +27,7 @@ internal static class XDotFFI // Accessors for xdot_image [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] private static extern IntPtr get_name_image(IntPtr img); - public static string GetNameImage(IntPtr img) => MarshalFromUtf8(get_name_image(img), false); + public static string? GetNameImage(IntPtr img) => MarshalFromUtf8(get_name_image(img), false); [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] public static extern IntPtr get_pos(IntPtr img); @@ -38,7 +38,7 @@ internal static class XDotFFI [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] private static extern IntPtr get_name_font(IntPtr font); - public static string GetNameFont(IntPtr img) => MarshalFromUtf8(get_name_font(img), false); + public static string? GetNameFont(IntPtr img) => MarshalFromUtf8(get_name_font(img), false); // Accessors for xdot_op [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] @@ -64,7 +64,7 @@ internal static class XDotFFI [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] private static extern IntPtr get_color(IntPtr op); - public static string GetColor(IntPtr op) => MarshalFromUtf8(get_color(op), false); + public static string? GetColor(IntPtr op) => MarshalFromUtf8(get_color(op), false); [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] public static extern IntPtr get_grad_color(IntPtr op); @@ -74,7 +74,7 @@ internal static class XDotFFI [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] private static extern IntPtr get_style(IntPtr op); - public static string GetStyle(IntPtr op) => MarshalFromUtf8(get_style(op), false); + public static string? GetStyle(IntPtr op) => MarshalFromUtf8(get_style(op), false); [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] public static extern uint get_fontchar(IntPtr op); @@ -85,7 +85,7 @@ internal static class XDotFFI [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] private static extern IntPtr get_clr(IntPtr clr); - public static string GetClr(IntPtr clr) => MarshalFromUtf8(get_clr(clr), false); + public static string? GetClr(IntPtr clr) => MarshalFromUtf8(get_clr(clr), false); [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] public static extern IntPtr get_ling(IntPtr clr); @@ -108,7 +108,7 @@ internal static class XDotFFI [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] private static extern IntPtr get_text_str(IntPtr txt); - public static string GetTextStr(IntPtr txt) => MarshalFromUtf8(get_text_str(txt), false); + public static string? GetTextStr(IntPtr txt) => MarshalFromUtf8(get_text_str(txt), false); // Accessors for xdot_linear_grad [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] @@ -160,7 +160,7 @@ internal static class XDotFFI [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] private static extern IntPtr get_color_stop(IntPtr stop); - public static string GetColorStop(IntPtr stop) => MarshalFromUtf8(get_color_stop(stop), false); + public static string? GetColorStop(IntPtr stop) => MarshalFromUtf8(get_color_stop(stop), false); // Accessors for xdot_polyline [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]