Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
GraphViz.cs
1using System;
3using System.Diagnostics;
4using System.IO;
5using System.Text;
6using System.Threading;
7using System.Threading.Tasks;
8using System.Xml;
9using SkiaSharp;
20using Waher.Events;
25using Waher.Script;
27using Waher.Security;
28
30{
34 public enum ResultType
35 {
39 Svg,
40
44 Png
45 }
46
47 internal class GraphInfo
48 {
49 public string FileName;
50 public string TextFileName;
51 public string Title;
52 public string MapFileName;
53 public string Hash;
54 }
55
61 {
62 private const int chartGenerationTimeout = 30000;
63
64 private static readonly AsyncProcessor<ChartRecord> queue = new AsyncProcessor<ChartRecord>(1, "GraphViz processor");
65 private static readonly Random rnd = new Random();
66 private static Scheduler scheduler = null;
67 private static string installationFolder = null;
68 private static string binFolder = null;
69 private static string graphVizFolder = null;
70 private static string contentRootFolder = null;
71 private static bool supportsDot = false;
72 private static bool supportsNeato = false;
73 private static bool supportsFdp = false;
74 private static bool supportsSfdp = false;
75 private static bool supportsTwopi = false;
76 private static bool supportsCirco = false;
77 private static IMarkdownAsynchronousOutput asyncHtmlOutput = null;
78
82 public GraphViz()
83 {
84 }
85
91 public static void Init(string ContentRootFolder)
92 {
93 try
94 {
95 contentRootFolder = ContentRootFolder;
96
97 if (scheduler is null)
98 {
99 if (Types.TryGetModuleParameter("Scheduler", out Scheduler Scheduler))
100 scheduler = Scheduler;
101 else
102 {
103 scheduler = new Scheduler();
104
105 Log.Terminating += (Sender, e) =>
106 {
107 scheduler?.Dispose();
108 scheduler = null;
109 return Task.CompletedTask;
110 };
111 }
112 }
113
114 string Folder = SearchForInstallationFolder();
115
116 if (string.IsNullOrEmpty(Folder))
117 Log.Warning("GraphViz not found. GraphViz support will not be available in Markdown.");
118 else
119 {
120 SetInstallationFolder(Folder);
121
122 Log.Informational("GraphViz found. Integration with Markdown added.",
123 new KeyValuePair<string, object>("Installation Folder", installationFolder),
124 new KeyValuePair<string, object>("Binary Folder", binFolder),
125 new KeyValuePair<string, object>("dot", supportsDot),
126 new KeyValuePair<string, object>("neato", supportsNeato),
127 new KeyValuePair<string, object>("fdp", supportsFdp),
128 new KeyValuePair<string, object>("sfdp", supportsSfdp),
129 new KeyValuePair<string, object>("twopi", supportsTwopi),
130 new KeyValuePair<string, object>("circo", supportsCirco));
131
133 }
134 }
135 catch (Exception ex)
136 {
137 Log.Exception(ex);
138 }
139 }
140
145 public static async Task Terminate()
146 {
147 try
148 {
149 await queue.CloseForTermination(true, chartGenerationTimeout);
150 await queue.DisposeAsync();
151 }
152 catch (Exception ex)
153 {
154 Log.Exception(ex);
155 }
156 }
157
164 public static void SetInstallationFolder(string Folder)
165 {
166 if (!string.IsNullOrEmpty(installationFolder) && Folder != installationFolder)
167 throw new Exception("GraphViz installation folder has already been set.");
168
169 string Suffix = FileSystem.ExecutableExtension;
170
171 installationFolder = Folder;
172
173 switch (Environment.OSVersion.Platform)
174 {
175 case PlatformID.Win32S:
176 case PlatformID.Win32Windows:
177 case PlatformID.Win32NT:
178 case PlatformID.WinCE:
179 default:
180 binFolder = Path.Combine(installationFolder, "bin");
181 break;
182
183 case PlatformID.Unix:
184 case PlatformID.MacOSX:
185 binFolder = installationFolder;
186 break;
187 }
188
189 supportsDot = File.Exists(Path.Combine(binFolder, "dot" + Suffix));
190 supportsNeato = File.Exists(Path.Combine(binFolder, "neato" + Suffix));
191 supportsFdp = File.Exists(Path.Combine(binFolder, "fdp" + Suffix));
192 supportsSfdp = File.Exists(Path.Combine(binFolder, "sfdp" + Suffix));
193 supportsTwopi = File.Exists(Path.Combine(binFolder, "twopi" + Suffix));
194 supportsCirco = File.Exists(Path.Combine(binFolder, "circo" + Suffix));
195
196 graphVizFolder = Path.Combine(contentRootFolder, "GraphViz");
197
198 if (!Directory.Exists(graphVizFolder))
199 Directory.CreateDirectory(graphVizFolder);
200
201 DeleteOldFiles(TimeSpan.FromDays(7));
202 }
203
204 private static void DeleteOldFiles(object P)
205 {
206 if (P is TimeSpan MaxAge)
207 DeleteOldFiles(MaxAge, true);
208 }
209
215 public static void DeleteOldFiles(TimeSpan MaxAge, bool Reschedule)
216 {
217 if (string.IsNullOrEmpty(graphVizFolder))
218 return;
219
220 DateTime Limit = DateTime.Now - MaxAge;
221 int Count = 0;
222
223 DirectoryInfo GraphVizFolder = new DirectoryInfo(graphVizFolder);
224 FileInfo[] Files = GraphVizFolder.GetFiles("*.*");
225
226 foreach (FileInfo FileInfo in Files)
227 {
228 if (FileInfo.LastAccessTime < Limit)
229 {
230 try
231 {
232 File.Delete(FileInfo.FullName);
233 Count++;
234 }
235 catch (Exception ex)
236 {
237 Log.Error("Unable to delete old file: " + ex.Message, FileInfo.FullName);
238 }
239 }
240 }
241
242 if (Count > 0)
243 Log.Informational(Count.ToString() + " old file(s) deleted.", graphVizFolder);
244
245 if (Reschedule)
246 {
247 lock (rnd)
248 {
249 scheduler.Add(DateTime.Now.AddDays(rnd.NextDouble() * 2), DeleteOldFiles, MaxAge);
250 }
251 }
252 }
253
258 public static string SearchForInstallationFolder()
259 {
260 string InstallationFolder;
261
262 switch (Environment.OSVersion.Platform)
263 {
264 case PlatformID.Win32S:
265 case PlatformID.Win32Windows:
266 case PlatformID.Win32NT:
267 case PlatformID.WinCE:
268 default:
269 InstallationFolder = SearchForInstallationFolder(Environment.SpecialFolder.ProgramFilesX86);
270 if (string.IsNullOrEmpty(InstallationFolder))
271 {
272 InstallationFolder = SearchForInstallationFolder(Environment.SpecialFolder.ProgramFiles);
273 if (string.IsNullOrEmpty(InstallationFolder))
274 {
275 InstallationFolder = SearchForInstallationFolder(Environment.SpecialFolder.Programs);
276 if (string.IsNullOrEmpty(InstallationFolder))
277 {
278 InstallationFolder = SearchForInstallationFolder(Environment.SpecialFolder.CommonProgramFilesX86);
279 if (string.IsNullOrEmpty(InstallationFolder))
280 {
281 InstallationFolder = SearchForInstallationFolder(Environment.SpecialFolder.CommonProgramFiles);
282 if (string.IsNullOrEmpty(InstallationFolder))
283 InstallationFolder = SearchForInstallationFolder(Environment.SpecialFolder.CommonPrograms);
284 }
285 }
286 }
287 }
288 break;
289
290 case PlatformID.Unix:
291 case PlatformID.MacOSX:
292 InstallationFolder = "/opt/local/bin";
293 if (!Directory.Exists(InstallationFolder))
294 InstallationFolder = null;
295 break;
296 }
297
298 return InstallationFolder;
299 }
300
301 private static string SearchForInstallationFolder(Environment.SpecialFolder SpecialFolder)
302 {
303 string Folder;
304
305 try
306 {
307 Folder = Environment.GetFolderPath(SpecialFolder);
308 }
309 catch (Exception)
310 {
311 return null; // Folder not defined for the operating system.
312 }
313
314 string Result = SearchForInstallationFolder(Folder);
315
316 if (string.IsNullOrEmpty(Result) && Types.TryGetModuleParameter("Runtime", out string RuntimeFolder))
317 Result = SearchForInstallationFolder(Path.Combine(RuntimeFolder, SpecialFolder.ToString()));
318
319 return Result;
320 }
321
322 private static string SearchForInstallationFolder(string Folder)
323 {
324 if (string.IsNullOrEmpty(Folder))
325 return null;
326
327 if (!Directory.Exists(Folder))
328 return null;
329
330 string FolderName;
331 string BestFolder = null;
332 double BestVersion = 0;
333 string[] SubFolders;
334
335 try
336 {
337 SubFolders = Directory.GetDirectories(Folder);
338 }
339 catch (UnauthorizedAccessException)
340 {
341 return null;
342 }
343 catch (Exception ex)
344 {
345 Log.Exception(ex);
346 return null;
347 }
348
349 foreach (string SubFolder in SubFolders)
350 {
351 FolderName = Path.GetFileName(SubFolder);
352 if (!FolderName.StartsWith("Graphviz", StringComparison.CurrentCultureIgnoreCase))
353 continue;
354
355 if (!CommonTypes.TryParse(FolderName.Substring(8), out double Version))
356 Version = 1.0;
357
358 if (BestFolder is null || Version > BestVersion)
359 {
360 BestFolder = SubFolder;
361 BestVersion = Version;
362 }
363 }
364
365 return BestFolder;
366 }
367
373 public Grade Supports(string Language)
374 {
375 int i = Language.IndexOf(':');
376 if (i > 0)
377 Language = Language.Substring(0, i).TrimEnd();
378
379 switch (Language.ToLower())
380 {
381 case "dot":
382 if (supportsDot)
383 return Grade.Excellent;
384 break;
385
386 case "neato":
387 if (supportsNeato)
388 return Grade.Excellent;
389 break;
390
391 case "fdp":
392 if (supportsFdp)
393 return Grade.Excellent;
394 break;
395
396 case "sfdp":
397 if (supportsSfdp)
398 return Grade.Excellent;
399 break;
400
401 case "twopi":
402 if (supportsTwopi)
403 return Grade.Excellent;
404 break;
405
406 case "circo":
407 if (supportsCirco)
408 return Grade.Excellent;
409 break;
410 }
411
412 return Grade.NotAtAll;
413 }
414
418 public bool EvaluatesScript => false;
419
424 public void Register(MarkdownDocument Document)
425 {
426 // Do nothing.
427 }
428
438 public async Task<bool> RenderHtml(HtmlRenderer Renderer, string[] Rows, string Language, int Indent, MarkdownDocument Document)
439 {
440 GraphInfo Info = await GetFileName(Language, Rows, ResultType.Svg, asyncHtmlOutput is null, Document.Settings?.Variables);
441 if (!(Info is null))
442 {
443 await this.GenerateHTML(Renderer.Output, Info);
444 return true;
445 }
446
447 string Title;
448 int i = Language.IndexOf(':');
449 if (i > 0)
450 Title = Language.Substring(i + 1).Trim();
451 else
452 Title = null;
453
454 string Id = await asyncHtmlOutput.GenerateStub(MarkdownOutputType.Html, Renderer.Output, Title, Document);
455
456 Document.QueueAsyncTask(this.ExecuteGraphViz, new AsyncState()
457 {
458 Id = Id,
459 Language = Language,
460 Rows = Rows,
461 Document = Document
462 });
463
464 return true;
465 }
466
467 private class AsyncState
468 {
469 public string Id;
470 public string Language;
471 public string[] Rows;
472 public MarkdownDocument Document;
473 }
474
475 private async Task ExecuteGraphViz(object State)
476 {
477 AsyncState AsyncState = (AsyncState)State;
478 StringBuilder Output = new StringBuilder();
479
480 try
481 {
482 GraphInfo Info = await GetFileName(AsyncState.Language, AsyncState.Rows, ResultType.Svg, true,
483 AsyncState.Document.Settings?.Variables);
484
485 if (!(Info is null))
486 await this.GenerateHTML(Output, Info);
487 }
488 catch (Exception ex)
489 {
490 using (HtmlRenderer Renderer = new HtmlRenderer(Output, new HtmlSettings()
491 {
492 XmlEntitiesOnly = true
493 }))
494 {
495 await Renderer.RenderObject(ex, true, new Variables());
496 }
497 }
498
499 await asyncHtmlOutput.ReportResult(MarkdownOutputType.Html, AsyncState.Id, Output.ToString());
500 }
501
502 private async Task GenerateHTML(StringBuilder Output, GraphInfo Info)
503 {
504 Info.FileName = Info.FileName.Substring(contentRootFolder.Length).Replace(Path.DirectorySeparatorChar, '/');
505 if (!Info.FileName.StartsWith("/"))
506 Info.FileName = "/" + Info.FileName;
507
508 Output.Append("<figure>");
509 Output.Append("<img src=\"");
510 Output.Append(XML.HtmlAttributeEncode(Info.FileName));
511
512 if (!string.IsNullOrEmpty(Info.Title))
513 {
514 Output.Append("\" alt=\"");
515 Output.Append(XML.HtmlAttributeEncode(Info.Title));
516
517 Output.Append("\" title=\"");
518 Output.Append(XML.HtmlAttributeEncode(Info.Title));
519 }
520 else
521 Output.Append("\" alt=\"GraphViz graph");
522
523 if (!string.IsNullOrEmpty(Info.MapFileName))
524 {
525 Output.Append("\" usemap=\"#Map");
526 Output.Append(Info.Hash);
527 }
528
529 Output.Append("\" class=\"aloneUnsized\"/>");
530
531 if (!string.IsNullOrEmpty(Info.Title))
532 {
533 Output.Append("<figcaption>");
534 Output.Append(XML.HtmlValueEncode(Info.Title));
535 Output.Append("</figcaption>");
536 }
537
538 Output.AppendLine("</figure>");
539
540 if (!string.IsNullOrEmpty(Info.MapFileName))
541 {
542 Output.Append("<map id=\"Map");
543 Output.Append(Info.Hash);
544 Output.Append("\" name=\"Map");
545 Output.Append(Info.Hash);
546 Output.AppendLine("\">");
547
548 string Map = await Files.ReadAllTextAsync(Info.MapFileName);
549 string[] MapRows = Map.Split(CommonTypes.CRLF, StringSplitOptions.RemoveEmptyEntries);
550 int i, c;
551
552 for (i = 1, c = MapRows.Length - 1; i < c; i++)
553 Output.AppendLine(MapRows[i]);
554
555 Output.AppendLine("</map>");
556 }
557 }
558
559 internal static Task<GraphInfo> GetFileName(string Language, string[] Rows, ResultType Type, bool GenerateIfNotExists, Variables Variables)
560 {
561 return GetFileName(Language, MarkdownDocument.AppendRows(Rows), Type, GenerateIfNotExists, Variables);
562 }
563
564 internal static async Task<GraphInfo> GetFileName(string Language, string GraphText, ResultType Type, bool GenerateIfNotExists, Variables Variables)
565 {
566 GraphInfo Result = new GraphInfo();
567 int i = Language.IndexOf(':');
568
569 if (i > 0)
570 {
571 Result.Title = Language.Substring(i + 1).Trim();
572 Language = Language.Substring(0, i).TrimEnd();
573 }
574 else
575 Result.Title = string.Empty;
576
577 string GraphBgColor = GetColor(Graph.GraphBgColorVariableName, Variables);
578 string GraphFgColor = GetColor(Graph.GraphFgColorVariableName, Variables);
579
580 Result.Hash = Hashes.ComputeSHA256HashString(Encoding.UTF8.GetBytes(GraphText + Language + GraphBgColor + GraphFgColor));
581
582 string GraphVizFolder = Path.Combine(contentRootFolder, "GraphViz");
583 string FileName = Path.Combine(GraphVizFolder, Result.Hash);
584
585 switch (Type)
586 {
587 case ResultType.Svg:
588 default:
589 Result.FileName = FileName + "." + ImageCodec.FileExtensionSvg;
590 break;
591
592 case ResultType.Png:
593 Result.FileName = FileName + "." + ImageCodec.FileExtensionPng;
594 break;
595 }
596
597 Result.TextFileName = FileName + ".txt";
598 Result.MapFileName = FileName + ".map";
599
600 if (File.Exists(Result.FileName))
601 {
602 FileInfo Info = new FileInfo(Result.FileName);
603 if (Info.Length > 0)
604 {
605 if (!File.Exists(Result.MapFileName))
606 Result.MapFileName = null;
607
608 return Result;
609 }
610 }
611
612 if (!GenerateIfNotExists)
613 return null;
614
615 ChartRecord Rec = new ChartRecord(Result, Language, GraphText, GraphFgColor, GraphBgColor, Type);
616 queue.Queue(Rec);
617
618 if (!await Rec.Wait())
619 throw new Exception("Unable to process GraphViz chart.");
620
621 return Result;
622 }
623
624 private class ChartRecord : IWorkItem
625 {
626 private readonly GraphInfo info;
627 private readonly TaskCompletionSource<bool> result;
628 private readonly string language;
629 private readonly string graphText;
630 private readonly string graphFgColor;
631 private readonly string graphBgColor;
632 private readonly ResultType type;
633
634 public ChartRecord(GraphInfo Result, string Language, string GraphText,
635 string GraphFgColor, string GraphBgColor, ResultType Type)
636 {
637 this.result = new TaskCompletionSource<bool>();
638 this.info = Result;
639 this.language = Language;
640 this.graphText = GraphText;
641 this.graphFgColor = GraphFgColor;
642 this.graphBgColor = GraphBgColor;
643 this.type = Type;
644 }
645
649 public Task Execute()
650 {
651 return this.Execute(CancellationToken.None);
652 }
653
658 public async Task Execute(CancellationToken Cancel)
659 {
660 await Files.WriteAllTextAsync(this.info.TextFileName, this.graphText, Encoding.Default); // Use UTF-8 ?
661
662 StringBuilder Arguments = new StringBuilder();
663
664 Arguments.Append("-Tcmapx -o\"");
665 Arguments.Append(this.info.MapFileName);
666 Arguments.Append("\" -T");
667 Arguments.Append(this.type.ToString().ToLower());
668
669 if (!string.IsNullOrEmpty(this.graphBgColor))
670 {
671 Arguments.Append(" -Gbgcolor=\"");
672 Arguments.Append(this.graphBgColor);
673 Arguments.Append('"');
674 }
675
676 if (!string.IsNullOrEmpty(this.graphFgColor))
677 {
678 Arguments.Append(" -Gcolor=\"");
679 Arguments.Append(this.graphFgColor);
680 //Arguments.Append("\" -Nfillcolor=\"");
681 //Arguments.Append(defaultFgColor);
682 Arguments.Append("\" -Nfontcolor=\"");
683 Arguments.Append(this.graphFgColor);
684 Arguments.Append("\" -Nlabelfontcolor=\"");
685 Arguments.Append(this.graphFgColor);
686 Arguments.Append("\" -Npencolor=\"");
687 Arguments.Append(this.graphFgColor);
688 Arguments.Append("\" -Efontcolor=\"");
689 Arguments.Append(this.graphFgColor);
690 Arguments.Append("\" -Elabelfontcolor=\"");
691 Arguments.Append(this.graphFgColor);
692 Arguments.Append("\" -Epencolor=\"");
693 Arguments.Append(this.graphFgColor);
694 Arguments.Append('"');
695 }
696
697 Arguments.Append(" -q -o\"");
698 Arguments.Append(this.info.FileName);
699 Arguments.Append("\" \"");
700 Arguments.Append(this.info.TextFileName + "\"");
701
702 ProcessStartInfo ProcessInformation = new ProcessStartInfo()
703 {
704 FileName = Path.Combine(binFolder, this.language.ToLower() + FileSystem.ExecutableExtension),
705 Arguments = Arguments.ToString(),
706 UseShellExecute = false,
707 RedirectStandardError = true,
708 RedirectStandardOutput = true,
709 RedirectStandardInput = false,
710 WorkingDirectory = graphVizFolder,
711 CreateNoWindow = true,
712 WindowStyle = ProcessWindowStyle.Hidden
713 };
714
715 Process P = new Process();
716 TaskCompletionSource<int> ExitSource = new TaskCompletionSource<int>();
717
718 P.Exited += (Sender, e) =>
719 {
720 ExitSource.TrySetResult(P.ExitCode);
721 };
722
723 Task _ = Task.Delay(chartGenerationTimeout).ContinueWith(Prev =>
724 {
725 try
726 {
727 P.Kill();
728
729 if (File.Exists(this.info.FileName))
730 File.Delete(this.info.FileName);
731
732 if (!string.IsNullOrEmpty(this.info.MapFileName) &&
733 File.Exists(this.info.MapFileName))
734 {
735 File.Delete(this.info.MapFileName);
736 }
737 }
738 catch (Exception ex)
739 {
740 Log.Exception(ex);
741 }
742 finally
743 {
744 ExitSource.TrySetException(new TimeoutException("GraphViz process did not terminate properly."));
745 }
746
747 return Task.CompletedTask;
748 });
749
750 P.StartInfo = ProcessInformation;
751 P.EnableRaisingEvents = true;
752 P.Start();
753
754 int ExitCode = await ExitSource.Task;
755
756 if (ExitCode != 0)
757 {
758 string Error = P.StandardError.ReadToEnd();
759 this.result.TrySetException(new Exception(Error));
760 }
761
762 try
763 {
764 if (File.Exists(this.info.MapFileName))
765 {
766 string Map = await Files.ReadAllTextAsync(this.info.MapFileName);
767 string[] MapRows = Map.Split(CommonTypes.CRLF, StringSplitOptions.RemoveEmptyEntries);
768 if (MapRows.Length <= 2)
769 {
770 File.Delete(this.info.MapFileName);
771 this.info.MapFileName = null;
772 }
773 }
774 else
775 this.info.MapFileName = null;
776 }
777 catch (Exception ex)
778 {
779 Log.Exception(ex);
780 }
781 }
782
787 public void Processed(bool Result)
788 {
789 this.result.TrySetResult(Result);
790 }
791
796 public Task<bool> Wait()
797 {
798 return this.result.Task;
799 }
800
806 public Task<bool> Wait(CancellationToken Cancel)
807 {
808 if (Cancel.CanBeCanceled)
809 Cancel.Register(() => this.result.TrySetException(new OperationCanceledException(Cancel)));
810
811 return this.result.Task;
812 }
813 }
814
815 private static string GetColor(string VariableName, Variables Variables)
816 {
817 if (Variables is null)
818 return null;
819
820 if (!Variables.TryGetVariable(VariableName, out Variable v))
821 return null;
822
823 if (v.ValueObject is SKColor Color)
824 return Graph.ToRGBAStyle(Color);
825 else if (v.ValueObject is string s)
826 return s;
827 else
828 return null;
829 }
830
840 public async Task<bool> RenderText(TextRenderer Renderer, string[] Rows, string Language, int Indent, MarkdownDocument Document)
841 {
842 GraphInfo Info = await GetFileName(Language, Rows, ResultType.Svg, true, Document.Settings?.Variables);
843 if (Info is null)
844 return false;
845
846 Renderer.Output.AppendLine(Info.Title);
847
848 return true;
849 }
850
860 public async Task<bool> RenderMarkdown(MarkdownRenderer Renderer, string[] Rows, string Language, int Indent, MarkdownDocument Document)
861 {
862 GraphInfo Info = await GetFileName(Language, Rows, ResultType.Png, true, Document.Settings?.Variables);
863 if (Info is null)
864 return false;
865
866 return await ImageContent.GenerateMarkdownFromFile(Renderer.Output, Info.FileName, Info.Title);
867 }
868
878 public async Task<bool> RenderWpfXaml(WpfXamlRenderer Renderer, string[] Rows, string Language, int Indent, MarkdownDocument Document)
879 {
880 GraphInfo Info = await GetFileName(Language, Rows, ResultType.Png, true, Document.Settings?.Variables);
881 if (Info is null)
882 return false;
883
884 XmlWriter Output = Renderer.XmlOutput;
885
886 Output.WriteStartElement("Image");
887 Output.WriteAttributeString("Source", Info.FileName);
888 Output.WriteAttributeString("Stretch", "None");
889
890 if (!string.IsNullOrEmpty(Info.Title))
891 Output.WriteAttributeString("ToolTip", Info.Title);
892
893 Output.WriteEndElement();
894
895 return true;
896 }
897
907 public async Task<bool> RenderXamarinFormsXaml(XamarinFormsXamlRenderer Renderer, string[] Rows, string Language, int Indent, MarkdownDocument Document)
908 {
909 GraphInfo Info = await GetFileName(Language, Rows, ResultType.Png, true, Document.Settings?.Variables);
910 if (Info is null)
911 return false;
912
913 XmlWriter Output = Renderer.XmlOutput;
914
915 Output.WriteStartElement("Image");
916 Output.WriteAttributeString("Source", Info.FileName);
917 Output.WriteEndElement();
918
919 return true;
920 }
921
931 public async Task<bool> RenderLatex(LatexRenderer Renderer, string[] Rows, string Language, int Indent, MarkdownDocument Document)
932 {
933 GraphInfo Info = await GetFileName(Language, Rows, ResultType.Png, true, Document.Settings?.Variables);
934 StringBuilder Output = Renderer.Output;
935
936 Output.AppendLine("\\begin{figure}[h]");
937 Output.AppendLine("\\centering");
938
939 Output.Append("\\fbox{\\includegraphics{");
940 Output.Append(Info.FileName.Replace('\\', '/'));
941 Output.AppendLine("}}");
942
943 if (!string.IsNullOrEmpty(Info.Title))
944 {
945 Output.Append("\\caption{");
946 Output.Append(LatexRenderer.EscapeLaTeX(Info.Title));
947 Output.AppendLine("}");
948 }
949
950 Output.AppendLine("\\end{figure}");
951 Output.AppendLine();
952
953 return true;
954 }
955
963 public async Task<PixelInformation> GenerateImage(string[] Rows, string Language, MarkdownDocument Document)
964 {
965 GraphInfo Info = await GetFileName(Language, Rows, ResultType.Png, true, Document.Settings?.Variables);
966 if (Info is null)
967 return null;
968
969 byte[] Data = await Runtime.IO.Files.ReadAllBytesAsync(Info.FileName);
970
971 using (SKBitmap Bitmap = SKBitmap.Decode(Data))
972 {
973 return new PixelInformationPng(Data, Bitmap.Width, Bitmap.Height);
974 }
975 }
976
986 public async Task<bool> RenderContractXml(ContractsRenderer Renderer, string[] Rows, string Language, int Indent, MarkdownDocument Document)
987 {
988 try
989 {
990 GraphInfo Info = await GetFileName(Language, Rows, ResultType.Png, true, Document.Settings?.Variables);
991 if (Info is null)
992 return false;
993
994 byte[] Data = await Runtime.IO.Files.ReadAllBytesAsync(Info.FileName);
995 string ContentType = ImageCodec.ContentTypePng;
996 ContentResponse Content = await InternetContent.DecodeAsync(ContentType, Data, null);
997
998 if (Content.HasError || !(Content.Decoded is SKImage Image))
999 return false;
1000
1001 XmlWriter Output = Renderer.XmlOutput;
1002 int Width = Image.Width;
1003 int Height = Image.Height;
1004
1005 Output.WriteStartElement("imageStandalone");
1006
1007 Output.WriteAttributeString("contentType", ContentType);
1008 Output.WriteAttributeString("width", Width.ToString());
1009 Output.WriteAttributeString("height", Height.ToString());
1010
1011 Output.WriteStartElement("binary");
1012 Output.WriteValue(Convert.ToBase64String(Data));
1013 Output.WriteEndElement();
1014
1015 Output.WriteStartElement("caption");
1016 if (string.IsNullOrEmpty(Info.Title))
1017 Output.WriteElementString("text", "Graph");
1018 else
1019 Output.WriteElementString("text", Info.Title);
1020
1021 Output.WriteEndElement();
1022 Output.WriteEndElement();
1023
1024 return true;
1025 }
1026 catch (Exception ex)
1027 {
1028 Log.Exception(ex);
1029 return false;
1030 }
1031 }
1032 }
1033}
Helps with parsing of commong data types.
Definition: CommonTypes.cs:15
static readonly char[] CRLF
Contains the CR LF character sequence.
Definition: CommonTypes.cs:19
static bool TryParse(string s, out double Value)
Tries to decode a string encoded double.
Definition: CommonTypes.cs:48
Contains information about a response to a content request.
bool HasError
If an error occurred.
object Decoded
Decoded object.
Image encoder/decoder.
Definition: ImageCodec.cs:14
const string FileExtensionPng
png
Definition: ImageCodec.cs:75
const string ContentTypePng
image/png
Definition: ImageCodec.cs:30
const string FileExtensionSvg
svg
Definition: ImageCodec.cs:120
Static class managing encoding and decoding of internet content.
static Task< ContentResponse > DecodeAsync(string ContentType, byte[] Data, Encoding Encoding, KeyValuePair< string, string >[] Fields, Uri BaseUri)
Decodes an object.
Renders Contracts XML from a Markdown document.
Class managing GraphViz integration into Markdown documents.
Definition: GraphViz.cs:61
async Task< bool > RenderLatex(LatexRenderer Renderer, string[] Rows, string Language, int Indent, MarkdownDocument Document)
Generates LaTeX for the code content.
Definition: GraphViz.cs:931
GraphViz()
Class managing GraphViz integration into Markdown documents.
Definition: GraphViz.cs:82
bool EvaluatesScript
If script is evaluated for this type of code block.
Definition: GraphViz.cs:418
async Task< bool > RenderWpfXaml(WpfXamlRenderer Renderer, string[] Rows, string Language, int Indent, MarkdownDocument Document)
Generates WPF XAML for the code content.
Definition: GraphViz.cs:878
async Task< bool > RenderHtml(HtmlRenderer Renderer, string[] Rows, string Language, int Indent, MarkdownDocument Document)
Generates HTML for the code content.
Definition: GraphViz.cs:438
static string SearchForInstallationFolder()
Searches for the installation folder on the local machine.
Definition: GraphViz.cs:258
static void SetInstallationFolder(string Folder)
Sets the installation folder of GraphViz.
Definition: GraphViz.cs:164
async Task< bool > RenderXamarinFormsXaml(XamarinFormsXamlRenderer Renderer, string[] Rows, string Language, int Indent, MarkdownDocument Document)
Generates Xamarin.Forms XAML for the code content.
Definition: GraphViz.cs:907
static async Task Terminate()
Terminates GraphViz processing.
Definition: GraphViz.cs:145
static void DeleteOldFiles(TimeSpan MaxAge, bool Reschedule)
Deletes generated files older than MaxAge .
Definition: GraphViz.cs:215
Grade Supports(string Language)
Checks how well the handler supports multimedia content of a given type.
Definition: GraphViz.cs:373
async Task< bool > RenderContractXml(ContractsRenderer Renderer, string[] Rows, string Language, int Indent, MarkdownDocument Document)
Generates smart contract XML for the code content.
Definition: GraphViz.cs:986
async Task< bool > RenderMarkdown(MarkdownRenderer Renderer, string[] Rows, string Language, int Indent, MarkdownDocument Document)
Generates Markdown for the code content.
Definition: GraphViz.cs:860
static void Init(string ContentRootFolder)
Initializes the GraphViz-Markdown integration.
Definition: GraphViz.cs:91
void Register(MarkdownDocument Document)
Is called on the object when an instance of the element has been created in a document.
Definition: GraphViz.cs:424
async Task< bool > RenderText(TextRenderer Renderer, string[] Rows, string Language, int Indent, MarkdownDocument Document)
Generates plain text for the code content.
Definition: GraphViz.cs:840
async Task< PixelInformation > GenerateImage(string[] Rows, string Language, MarkdownDocument Document)
Generates an image of the contents.
Definition: GraphViz.cs:963
Renders LaTeX from a Markdown document.
static string EscapeLaTeX(string s)
Escapes text for output in a LaTeX document.
Contains a markdown document. This markdown document class supports original markdown,...
static string AppendRows(string[] Rows)
Appends a set of rows into a single string with newlines between rows.
MarkdownSettings Settings
Markdown settings.
void QueueAsyncTask(AsyncMarkdownProcessing Callback, object State)
Queues an asynchronous task to be executed. Asynchronous tasks will be executed after the main docume...
Variables Variables
Collection of variables. Providing such a collection enables script execution inside markdown documen...
static async Task< bool > GenerateMarkdownFromFile(StringBuilder Output, string FileName, string Title)
Generates Markdown embedding an image available in a file.
Definition: ImageContent.cs:56
Renders HTML from a Markdown document.
Definition: HtmlRenderer.cs:25
Contains settings that the HTML export uses to customize HTML output.
Definition: HtmlSettings.cs:7
Renders portable Markdown from a Markdown document.
Abstract base class for Markdown renderers.
Definition: Renderer.cs:15
readonly StringBuilder Output
Renderer output.
Definition: Renderer.cs:19
Renders plain text from a Markdown document.
Definition: TextRenderer.cs:18
Renders XAML (WPF flavour) from a Markdown document.
Renders XAML (Xamarin.Forms flavour) from a Markdown document.
Static class helping modules to find files installed on the system.
Definition: FileSystem.cs:12
static string ExecutableExtension
Extension used by executable files on the platform.
Definition: FileSystem.cs:231
Helps with common XML-related tasks.
Definition: XML.cs:21
static string HtmlValueEncode(string s)
Differs from Encode(String), in that it does not encode the aposotrophe or the quote.
Definition: XML.cs:211
static string HtmlAttributeEncode(string s)
Differs from Encode(String), in that it does not encode the aposotrophe.
Definition: XML.cs:121
Static class managing the application event log. Applications and services log events on this static ...
Definition: Log.cs:14
static void Exception(Exception Exception, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, params KeyValuePair< string, object >[] Tags)
Logs an exception. Event type will be determined by the severity of the exception.
Definition: Log.cs:1657
static void Warning(string Message, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, string StackTrace, params KeyValuePair< string, object >[] Tags)
Logs a warning event.
Definition: Log.cs:576
static void Error(string Message, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, string StackTrace, params KeyValuePair< string, object >[] Tags)
Logs an error event.
Definition: Log.cs:692
static void Informational(string Message, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, string StackTrace, params KeyValuePair< string, object >[] Tags)
Logs an informational event.
Definition: Log.cs:344
Contains static methods
Definition: Files.cs:14
static async Task< string > ReadAllTextAsync(string FileName)
Reads a text file asynchronously.
Definition: Files.cs:84
static Task WriteAllTextAsync(string FileName, string Text)
Creates a text file asynchronously.
Definition: Files.cs:95
Static class that dynamically manages types and interfaces available in the runtime environment.
Definition: Types.cs:15
static bool TryGetModuleParameter(string Name, out object Value)
Tries to get a module parameter value.
Definition: Types.cs:607
Processes work tasks, in an asynchronous manner.
Class that can be used to schedule events in time. It uses a timer to execute tasks at the appointed ...
Definition: Scheduler.cs:14
void Dispose()
IDisposable.Dispose
Definition: Scheduler.cs:34
DateTime Add(DateTime When, Action< object > Callback, object State)
Adds an event.
Definition: Scheduler.cs:54
Base class for graphs.
Definition: Graph.cs:88
const string GraphFgColorVariableName
Variable name for graph foreground color.
Definition: Graph.cs:107
const string GraphBgColorVariableName
Variable name for graph background color.
Definition: Graph.cs:102
static string ToRGBAStyle(SKColor Color)
Converts a color to an RGB(A) style string.
Definition: Graph.cs:919
Contains pixel information in PNG format
Contains information about a variable.
Definition: Variable.cs:10
Collection of variables.
Definition: Variables.cs:25
virtual bool TryGetVariable(string Name, out Variable Variable)
Tries to get a variable object, given its name.
Definition: Variables.cs:56
Contains methods for simple hash calculations.
Definition: Hashes.cs:57
static string ComputeSHA256HashString(byte[] Data)
Computes the SHA-256 hash of a block of binary data.
Definition: Hashes.cs:449
Interface for classes that help output asynchronous markdown output.
Task< string > GenerateStub(MarkdownOutputType Type, StringBuilder Output, string Title, MarkdownDocument Document)
Generates a stub in the output, that will be filled with the asynchronously generated content,...
Task ReportResult(MarkdownOutputType Type, string Id, string Result)
Method called when asynchronous result has been generated in a Markdown document.
Interface for all markdown handlers of code content that generates an image output.
Interface for code content plain text renderers.
Interface for code content WPF XAML renderers.
Interface for asynchronous operations.
Definition: IWorkItem.cs:10
Definition: ImplTypes.g.cs:58
ResultType
Image types supported by GraphViz
Definition: GraphViz.cs:35
MarkdownOutputType
Markdown output type.
Grade
Grade enumeration
Definition: Grade.cs:7