Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
MauiRenderer.cs
1using CommunityToolkit.Maui.Markup;
2using Microsoft.Maui.Controls;
3using SkiaSharp;
4using System.Globalization;
5using System.Runtime.CompilerServices;
6using System.Text;
7using System.Xml;
8using Waher.Content;
17using Waher.Events;
18using Waher.Script;
22using Microsoft.Maui.Graphics.Text;
26using Svg;
27using Microsoft.Maui.Controls.Shapes;
29using System.Runtime.Intrinsics.Arm;
30
32{
41 public class MauiRenderer : IRenderer
42 {
43 #region Fields & properties
44
49
53 private VerticalStackLayout mainStackLayout;
54
58 private View currentElement;
59
63 public bool Bold = false;
64
68 public bool Italic = false;
69
73 public bool StrikeThrough = false;
74
78 public bool Underline = false;
79
83 public bool Superscript = false;
84
88 public bool Subscript = false;
89
93 public bool Code = false;
94
98 public bool InLabel = false;
99
103 public string? Hyperlink = null;
104
109
110 #endregion
111
112 #region Entry & Exit
118 {
119 this.Document = Document;
120 this.mainStackLayout = new VerticalStackLayout();
121 this.mainStackLayout.Spacing = 10;
122 this.currentElement = new ContentView();
123 }
124
129 public VerticalStackLayout? Output()
130 {
131 if (this.mainStackLayout.Children.Count == 0) return null;
132 return this.mainStackLayout;
133 }
134 #endregion
135
136 #region Render Document
137
143 public Task RenderDocument(MarkdownDocument Document, bool Inclusion)
144 {
145 this.Alignment = Waher.Content.Markdown.Model.TextAlignment.Left;
146 this.Bold = false;
147 this.Italic = false;
148 this.StrikeThrough = false;
149 this.Underline = false;
150 this.Superscript = false;
151 this.Subscript = false;
152 this.Code = false;
153 this.InLabel = false;
154 this.Hyperlink = null;
155
156 return this.RenderDocumentEntry(Document, Inclusion);
157 }
158
164 public virtual async Task RenderDocumentEntry(MarkdownDocument Document, bool Inclusion)
165 {
166 MarkdownDocument DocBak = this.Document;
167
168 this.Document = Document;
169
170 if (!Inclusion && this.Document.TryGetMetaData("BODYONLY", out KeyValuePair<string, bool>[] Values))
171 {
172 if (CommonTypes.TryParse(Values[0].Key, out bool B) && B)
173 Inclusion = true;
174 }
175
176 if (!Inclusion)
177 await this.RenderDocumentHeader();
178
179 foreach (MarkdownElement E in this.Document.Elements)
180 {
181 this.currentElement = new ContentView();
182
183 await E.Render(this);
184
185 this.mainStackLayout.Add(this.currentElement);
186 }
187
188 if (this.NeedsToDisplayFootnotes())
189 {
190 await this.RenderFootnotes();
191 this.mainStackLayout.Add(this.currentElement);
192 }
193
194 this.Document = DocBak;
195 }
196
201 {
202 return Task.CompletedTask;
203 }
204
208 public async Task RenderFootnotes()
209 {
210 Footnote CurrentFootnote;
211 int FootnoteNumber;
212 int RowIndex = 0;
213
214 ContentView SeparatorContentView = new ContentView
215 {
216 Content = new Rectangle
217 {
218 Fill = Brush.Black,
219 HeightRequest = 1,
220 Aspect = Stretch.Fill
221 }
222 };
223
224 this.mainStackLayout.Add(SeparatorContentView);
225
226 Grid FootnoteGrid = new Grid
227 {
228 RowSpacing = 0,
229 ColumnSpacing = 0,
230 ColumnDefinitions =
231 {
232 new ColumnDefinition(GridLength.Auto),
233 new ColumnDefinition(GridLength.Star)
234 }
235 };
236
237 foreach (string Key in this.Document.FootnoteOrder)
238 {
239 if ((this.Document?.TryGetFootnoteNumber(Key, out FootnoteNumber) ?? false) &&
240 (this.Document?.TryGetFootnote(Key, out CurrentFootnote) ?? false) &&
241 CurrentFootnote.Referenced)
242 {
243 FootnoteGrid.AddRowDefinition(new RowDefinition { Height = GridLength.Auto });
244 }
245 }
246
247 if (this.Document is not null)
248 {
249 foreach (string Key in this.Document.FootnoteOrder)
250 {
251 if ((this.Document?.TryGetFootnoteNumber(Key, out FootnoteNumber) ?? false) &&
252 (this.Document?.TryGetFootnote(Key, out CurrentFootnote) ?? false) &&
253 CurrentFootnote.Referenced)
254 {
255 ContentView FootnoteNumberView = new ContentView
256 {
257 Margin = AppStyles.SmallMargins,
258 Scale = 0.75,
259 TranslationY = -5,
260 Content = new Label
261 {
262 Text = FootnoteNumber.ToString(CultureInfo.InvariantCulture)
263 },
264 };
265
266 FootnoteGrid.Add(FootnoteNumberView, 0, RowIndex);
267
268 ContentView FootnoteContentView = new ContentView();
269 this.currentElement = FootnoteContentView;
270 await this.Render(CurrentFootnote);
271
272 FootnoteGrid.Add(FootnoteContentView, 1, RowIndex);
273
274 RowIndex++;
275 }
276 }
277 }
278
279 this.currentElement = FootnoteGrid;
280 }
281 #endregion
282
283 #region Children Render Helpers
284
289 public async Task RenderChildren(MarkdownElementChildren Element)
290 {
291 if (this.InLabel && !(Element.Children is null))
292 {
293 foreach (MarkdownElement E in Element.Children)
294 {
295 await E.Render(this);
296 }
297 }
298 else
299 {
300 ContentView Bakup = (ContentView)this.currentElement;
301
302 VerticalStackLayout ChildrenContainer = new();
303 ChildrenContainer.Spacing = 8; // Same size as small margins
304 Bakup.Content = ChildrenContainer;
305
306 if (!(Element.Children is null))
307 {
308 foreach (MarkdownElement E in Element.Children)
309 {
310 ContentView ChildContentView = new ContentView();
311 this.currentElement = ChildContentView;
312 await E.Render(this);
313 ChildrenContainer.Add(ChildContentView);
314 }
315 }
316 this.currentElement = Bakup;
317 }
318 }
319
324 public async Task RenderChildren(MarkdownElement Element)
325 {
326 IEnumerable<MarkdownElement> Children = Element.Children;
327
328 if (this.InLabel && !(Children is null))
329 {
330 foreach (MarkdownElement E in Children)
331 {
332 await E.Render(this);
333 }
334 }
335 else
336 {
337 ContentView Bakup = (ContentView)this.currentElement;
338
339 VerticalStackLayout ChildrenContainer = new();
340 Bakup.Content = ChildrenContainer;
341
342 if (!(Children is null))
343 {
344 foreach (MarkdownElement E in Children)
345 {
346 ContentView ChildContentView = new ContentView();
347 this.currentElement = ChildContentView;
348 await E.Render(this);
349 ChildrenContainer.Add(ChildContentView);
350 }
351 }
352
353 this.currentElement = Bakup;
354 }
355 }
356
362 {
363 if (Element.Child is null)
364 return Task.CompletedTask;
365
366 Element.Child.Render(this);
367
368 return Task.CompletedTask;
369 }
370
371 #endregion
372
373 #region Base Element Renderers
374
379 public async Task Render(Footnote Element)
380 {
381 await this.RenderChildren(Element);
382 }
383
388 public async Task Render(Abbreviation Element)
389 {
390 await this.RenderChildren(Element);
391 }
392
397 public async Task Render(Delete Element)
398 {
399 bool Bak = this.StrikeThrough;
400 this.StrikeThrough = true;
401
402 await this.RenderChildren(Element);
403
404 this.StrikeThrough = Bak;
405 }
406
411 public async Task Render(Emphasize Element)
412 {
413 bool Bak = this.Italic;
414 this.Italic = true;
415
416 await this.RenderChildren(Element);
417
418 this.Italic = Bak;
419 }
420
425 public Task Render(UnnumberedItem Element)
426 {
427 this.RenderChild(Element);
428
429 return Task.CompletedTask;
430 }
431
436 public async Task Render(Underline Element)
437 {
438 bool Bak = this.Underline;
439 this.Underline = true;
440
441 await this.RenderChildren(Element);
442
443 this.Underline = Bak;
444 }
445
450 public async Task Render(TaskList Element)
451 {
452 ContentView Bakup = (ContentView)this.currentElement;
453
454 int RowIndex = 0;
455 bool ParagraphBullet;
456
457 Grid TaskListGrid = new Grid
458 {
459 RowSpacing = 0,
460 ColumnSpacing = 0,
461 ColumnDefinitions =
462 {
463 new ColumnDefinition { Width = GridLength.Auto },
464 new ColumnDefinition { Width = GridLength.Star },
465 },
466 };
467
468 foreach (MarkdownElement _ in Element.Children)
469 {
470 TaskListGrid.AddRowDefinition(new RowDefinition { Height = GridLength.Auto });
471 }
472
473 foreach (MarkdownElement E in Element.Children)
474 {
475 if (E is TaskItem TaskItem)
476 {
477 ParagraphBullet = !E.InlineSpanElement || E.OutsideParagraph;
478
480 {
481 this.RenderContentView(AppStyles.SmallRightMargins);
482
483 ContentView CheckmarkContentView = (ContentView)this.currentElement;
484
485 CheckmarkContentView.Column(0);
486 CheckmarkContentView.Row(RowIndex);
487
488 CheckmarkContentView.Content = (new Label { Text = "✓" });
489
490 TaskListGrid.Add(CheckmarkContentView);
491 }
492
493 ContentView TaskContentView = new ContentView();
494 TaskContentView.Column(1);
495 TaskContentView.Row(RowIndex);
496
497 this.currentElement = TaskContentView;
498
499 if (ParagraphBullet)
500 await E.Render(this);
501 else
502 await this.RenderLabel(TaskItem, false);
503
504 TaskListGrid.Add(TaskContentView);
505 }
506
507 RowIndex++;
508 }
509
510 Bakup.Content = TaskListGrid;
511 this.currentElement = Bakup;
512 }
513
518 public Task Render(TaskItem Element)
519 {
520 this.RenderChild(Element);
521
522 return Task.CompletedTask;
523 }
524
529 public async Task Render(Table Element)
530 {
531 ContentView Bakup = (ContentView)this.currentElement;
532
533 int Column;
534 int RowIndex, NrRows;
535 int RowNr = 0;
536
537 Grid TableGrid = new Grid
538 {
539 RowSpacing = -2,
540 ColumnSpacing = -2,
541 };
542
543 // TODO: Tooltip/caption
544
545 for (Column = 0; Column < Element.Columns; Column++)
546 {
547 TableGrid.AddColumnDefinition(new ColumnDefinition { Width = GridLength.Auto });
548 }
549
550 for (RowIndex = 0, NrRows = Element.Rows.Length + Element.Headers.Length; RowIndex < NrRows; RowIndex++)
551 {
552 TableGrid.AddRowDefinition(new RowDefinition { Height = GridLength.Auto });
553 }
554
555 ScrollView TableScrollView = new ScrollView
556 {
557 Orientation = ScrollOrientation.Horizontal,
558 Content = TableGrid
559 };
560
561 for (RowIndex = 0, NrRows = Element.Headers.Length; RowIndex < NrRows; RowIndex++, RowNr++)
562 {
563 this.currentElement = TableGrid;
564 await this.Render(Element.Headers[RowIndex], Element.HeaderCellAlignments[RowIndex], RowNr, true, Element);
565 }
566
567 for (RowIndex = 0, NrRows = Element.Rows.Length; RowIndex < NrRows; RowIndex++, RowNr++)
568 {
569 this.currentElement = TableGrid;
570 await this.Render(Element.Rows[RowIndex], Element.RowCellAlignments[RowIndex], RowNr, false, Element);
571 }
572
573 Bakup.Content = TableScrollView;
574 this.currentElement = Bakup;
575 }
576
586 private async Task Render(MarkdownElement[] CurrentRow, Waher.Content.Markdown.Model.TextAlignment?[] CellAlignments,
587 int RowNr, bool Bold, Table Element)
588 {
591 int Column;
592 int NrColumns = Element.Columns;
593 int ColSpan;
594 StateBackup Bak = this.Backup();
595
596 Grid Bakup = (Grid)this.currentElement;
597
598 this.ClearState();
599
600 for (Column = 0; Column < NrColumns; Column++)
601 {
602 E = CurrentRow[Column];
603 if (E is null)
604 continue;
605
606 TextAlignment = CellAlignments[Column] ?? Element.ColumnAlignments[Column];
607 ColSpan = Column + 1;
608 while (ColSpan < NrColumns && CurrentRow[ColSpan] is null)
609 ColSpan++;
610
611 ColSpan -= Column;
612
613 Border Frame = new Border();
614
615 if ((RowNr & 1) == 0)
616 Frame.Style = AppStyles.TableCellEven;
617
618 else
619 Frame.Style = AppStyles.TableCellOdd;
620
621 Frame.Column(Column);
622 Frame.Row(RowNr);
623
624 if (ColSpan > 1)
625 Frame.ColumnSpan(ColSpan);
626
627 if (E.InlineSpanElement)
628 {
629 this.RenderContentView(TextAlignment, new Thickness(0, 0, 0, 0), AppStyles.TableCell);
630
631 ContentView Cv = (ContentView)this.currentElement;
632 this.currentElement = Cv;
633
634 this.Bold = Bold;
635 await this.RenderLabel(E, true);
636
637 Frame.Content = Cv;
638 this.Bold = false;
639 }
640 else
641 {
642 ContentView Cv = new ContentView
643 {
644 Style = AppStyles.TableCell
645 };
646 this.currentElement = Cv;
647
648 await E.Render(this);
649
650 Frame.Content = Cv;
651 }
652 Bakup.Add(Frame);
653 }
654 this.Restore(Bak);
655
656 this.currentElement = Bakup;
657 }
658
663 public async Task Render(SuperScript Element)
664 {
665 bool Bak = this.Superscript;
666 this.Superscript = true;
667
668 await this.RenderChildren(Element);
669
670 this.Superscript = Bak;
671 }
672
677 public async Task Render(SubScript Element)
678 {
679 bool Bak = this.Subscript;
680 this.Subscript = true;
681
682 await this.RenderChildren(Element);
683
684 this.Subscript = Bak;
685 }
686
691 public async Task Render(Strong Element)
692 {
693 bool Bak = this.Bold;
694 this.Bold = true;
695
696 await this.RenderChildren(Element);
697
698 this.Bold = Bak;
699 }
700
705 public async Task Render(StrikeThrough Element)
706 {
707 bool Bak = this.StrikeThrough;
708 this.StrikeThrough = true;
709
710 await this.RenderChildren(Element);
711
712 this.StrikeThrough = Bak;
713 }
714
719 public Task Render(SectionSeparator Element)
720 {
721 ContentView Bakup = (ContentView)this.currentElement;
722
723 Rectangle Separator = new Rectangle
724 {
725 Fill = Brush.Black,
726 HeightRequest = 1,
727 Aspect = Stretch.Fill
728 };
729
730 Bakup.Margin = AppStyles.SmallTopMargins + AppStyles.SmallBottomMargins;
731 Bakup.Content = Separator;
732
733 return Task.CompletedTask;
734 }
735
740 public async Task Render(Sections Element)
741 {
742 await this.RenderChildren(Element);
743 }
744
749 public async Task Render(RightAligned Element)
750 {
752 this.Alignment = Waher.Content.Markdown.Model.TextAlignment.Right;
753
754 await this.RenderChildren(Element);
755
756 this.Alignment = Bak;
757 }
758
763 public async Task Render(Paragraph Element)
764 {
765 await this.RenderLabel(Element, false);
766 }
767
772 public async Task Render(NumberedList Element)
773 {
774 ContentView Bakup = (ContentView)this.currentElement;
775
776 int Expected = 0;
777 int RowIndex = 0;
778 bool ParagraphBullet;
779
780 Grid ListGrid = new Grid
781 {
782 RowSpacing = 0,
783 ColumnSpacing = 0,
784 ColumnDefinitions =
785 {
786 new ColumnDefinition { Width = GridLength.Auto },
787 new ColumnDefinition { Width = GridLength.Star }
788 }
789 };
790
791 foreach (MarkdownElement _ in Element.Children)
792 {
793 ListGrid.AddRowDefinition(new RowDefinition { Height = GridLength.Auto });
794 }
795
796 foreach (MarkdownElement E in Element.Children)
797 {
798 if (E is BlockElementSingleChild Item)
799 {
800 Expected++;
801
802 ParagraphBullet = !E.InlineSpanElement || E.OutsideParagraph;
803
804 this.RenderContentView(AppStyles.SmallRightMargins);
805 ContentView InnerContentView = (ContentView)this.currentElement;
806 InnerContentView.Column(0);
807 InnerContentView.Row(RowIndex);
808
809 Label Label = new Label();
810
811 if (Item is NumberedItem NumberedItem)
812 Label.Text = (Expected = NumberedItem.Number).ToString(CultureInfo.InvariantCulture) + ".";
813 else
814 Label.Text = Expected.ToString(CultureInfo.InvariantCulture) + ".";
815
816 InnerContentView.Content = Label;
817 ListGrid.Add(InnerContentView);
818
819 ContentView ItemContentView = new ContentView();
820 ItemContentView.Column(1);
821 ItemContentView.Row(RowIndex);
822 this.currentElement = ItemContentView;
823
824 if (ParagraphBullet)
825 {
826 await E.Render(this);
827 }
828 else
829 {
830 await this.RenderLabel(Item, false);
831 }
832
833 ListGrid.Add(ItemContentView);
834 }
835
836 RowIndex++;
837 }
838
839 Bakup.Content = ListGrid;
840 this.currentElement = Bakup;
841 }
842
847 public Task Render(NumberedItem Element)
848 {
849 this.RenderChild(Element);
850
851 return Task.CompletedTask;
852 }
853
858 public async Task Render(NestedBlock Element)
859 {
860 ContentView Bakup = (ContentView)this.currentElement;
861
862 if (Element.HasOneChild)
863 {
864 await Element.FirstChild.Render(this);
865 }
866 else
867 {
868 HtmlSettings Settings = new()
869 {
870 XmlEntitiesOnly = true
871 };
872 HtmlRenderer? Html = null;
873
874 VerticalStackLayout OuterVerticalStackLayout = new VerticalStackLayout();
875
876 try
877 {
878 foreach (MarkdownElement E in Element.Children)
879 {
880 if (E.InlineSpanElement)
881 {
882 Html ??= new HtmlRenderer(Settings, this.Document);
883 await E.Render(Html);
884 }
885 else
886 {
887 if (Html is not null)
888 {
889 Label HTMLLabel = new Label
890 {
891 LineBreakMode = LineBreakMode.WordWrap,
892 HorizontalTextAlignment = this.LabelAlignment(),
893 TextType = TextType.Html,
894 Text = Html.ToString()
895 };
896
897 Html.Dispose();
898 Html = null;
899
900 OuterVerticalStackLayout.Add(HTMLLabel);
901 }
902
903 ContentView InnerContentView = new ContentView();
904 this.currentElement = InnerContentView;
905 await E.Render(this);
906 OuterVerticalStackLayout.Add(InnerContentView);
907 }
908 }
909
910 if (Html is not null)
911 {
912 Label HTMLLabel = new Label
913 {
914 LineBreakMode = LineBreakMode.WordWrap,
915 HorizontalTextAlignment = this.LabelAlignment(),
916 TextType = TextType.Html,
917 Text = Html.ToString()
918 };
919 OuterVerticalStackLayout.Add(HTMLLabel);
920 }
921 }
922 finally
923 {
924 Html?.Dispose();
925 }
926
927 Bakup.Content = OuterVerticalStackLayout;
928 this.currentElement = Bakup;
929 }
930 }
931
936 public async Task Render(MultimediaReference Element)
937 {
939
940 if (Multimedia is not null)
941 {
942 // TODO
944 if (Renderer is not null)
945 {
946 await this.RenderMaui(Multimedia);
947 return;
948 }
949 }
950 await this.RenderChildren(Element);
951 }
952
958 {
960 if (Renderer is null)
961 await this.RenderChildren(Element);
962 else
963 await this.RenderMaui(Element);
964 }
965
970 public Task Render(MetaReference Element)
971 {
972 StringBuilder StringBuilder = new();
973 bool FirstOnRow = true;
974
975 if (Element.TryGetMetaData(out KeyValuePair<string, bool>[] Values))
976 {
977 foreach (KeyValuePair<string, bool> P in Values)
978 {
979 if (FirstOnRow)
980 FirstOnRow = false;
981 else
982 StringBuilder.Append(' ');
983
984 StringBuilder.Append(P.Key);
985 if (P.Value)
986 {
987 StringBuilder.Append(Environment.NewLine);
988 FirstOnRow = true;
989 }
990 }
991 }
992
993 this.RenderSpan(StringBuilder.ToString());
994
995 return Task.CompletedTask;
996 }
997
1002 public async Task Render(MarginAligned Element)
1003 {
1005 this.Alignment = Waher.Content.Markdown.Model.TextAlignment.Left;
1006
1007 await this.RenderChildren(Element);
1008
1009 this.Alignment = Bak;
1010 }
1011
1016 public async Task Render(LinkReference Element)
1017 {
1019
1020 string? Bak = this.Hyperlink;
1021
1022 if (Multimedia is not null)
1023 this.Hyperlink = Multimedia.Items[0].Url;
1024
1025 await this.RenderChildren(Element);
1026
1027 this.Hyperlink = Bak;
1028 }
1029
1034 public async Task Render(Link Element)
1035 {
1036 string? Bak = this.Hyperlink;
1037 this.Hyperlink = Element.Url;
1038
1039 await this.RenderChildren(Element);
1040
1041 this.Hyperlink = Bak;
1042 }
1043
1048 public Task Render(LineBreak Element)
1049 {
1050 this.RenderSpan(Environment.NewLine);
1051
1052 return Task.CompletedTask;
1053 }
1054
1059 public async Task Render(LeftAligned Element)
1060 {
1062 this.Alignment = Waher.Content.Markdown.Model.TextAlignment.Left;
1063
1064 await this.RenderChildren(Element);
1065
1066 this.Alignment = Bak;
1067 }
1068
1073 public Task Render(InvisibleBreak Element)
1074 {
1075 //TODO
1076 return Task.CompletedTask;
1077 }
1078
1083 public async Task Render(InsertBlocks Element)
1084 {
1085 ContentView Bakup = (ContentView)this.currentElement;
1086
1087 Border BlockBorder = new Border
1088 {
1089 Padding = AppStyles.SmallMargins,
1090 Stroke = new SolidColorBrush
1091 {
1092 Color = AppColors.InsertedBorder,
1093 },
1094 StrokeThickness = 1,
1095 StrokeShape = new RoundRectangle
1096 {
1097 CornerRadius = 2
1098 }
1099 };
1100
1101 Bakup.Content = BlockBorder;
1102
1103 ContentView InnerContentView = new ContentView();
1104 this.currentElement = InnerContentView;
1105
1106 await this.RenderChildren(Element);
1107
1108 BlockBorder.Content = InnerContentView;
1109 this.currentElement = Bakup;
1110 }
1111
1116 public async Task Render(Insert Element)
1117 {
1118 bool Bak = this.Underline;
1119 this.Underline = true;
1120
1121 await this.RenderChildren(Element);
1122
1123 this.Underline = Bak;
1124 }
1125
1130 public Task Render(InlineText Element)
1131 {
1132 this.RenderSpan(Element.Value);
1133
1134 return Task.CompletedTask;
1135 }
1136
1141 public async Task Render(InlineScript Element)
1142 {
1143 object Result = await Element.EvaluateExpression();
1144 await this.RenderObject(Result, Element.AloneInParagraph, Element.Variables);
1145 }
1146
1151 public Task Render(InlineHTML Element)
1152 {
1153 if (this.currentElement is Label ExistingLabel)
1154 {
1155 ExistingLabel.TextType = TextType.Html;
1156 ExistingLabel.Text = $"<--- {Element.HTML} --->";
1157 }
1158 else
1159 {
1160 ContentView Bakup = (ContentView)this.currentElement;
1161
1162 Label NewLabel = new Label
1163 {
1164 TextType = TextType.Html,
1165 Text = $"<--- {Element.HTML} --->",
1166 };
1167
1168 Bakup.Content = NewLabel;
1169 }
1170
1171 return Task.CompletedTask;
1172 }
1173
1178 public Task Render(InlineCode Element)
1179 {
1180 bool Bak = this.Code;
1181 this.Code = true;
1182
1183 this.RenderSpan(Element.Code);
1184
1185 this.Code = Bak;
1186
1187 return Task.CompletedTask;
1188 }
1189
1194 public Task Render(HtmlEntityUnicode Element)
1195 {
1196 this.RenderSpan(new string((char)Element.Code, 1));
1197
1198 return Task.CompletedTask;
1199 }
1200
1205 public Task Render(HtmlEntity Element)
1206 {
1208 if (!string.IsNullOrEmpty(S))
1209 {
1210 this.RenderSpan(S);
1211 }
1212
1213 return Task.CompletedTask;
1214 }
1215
1220 public async Task Render(HtmlBlock Element)
1221 {
1222 ContentView Bakup = (ContentView)this.currentElement;
1223
1224 Thickness Margins = AppStyles.SmallTopMargins + AppStyles.SmallBottomMargins;
1225 Bakup.Margin = Margins;
1226
1227 Label HTMLTextLabel = new Label
1228 {
1229 LineBreakMode = LineBreakMode.WordWrap,
1230 HorizontalTextAlignment = this.LabelAlignment(),
1231 TextType = TextType.Html,
1232 };
1233
1234 using HtmlRenderer HTMLRenderer = new(new HtmlSettings()
1235 {
1236 XmlEntitiesOnly = true
1237 }, this.Document);
1238
1239 await HTMLRenderer.RenderChildren(Element);
1240
1241 HTMLTextLabel.Text = HTMLRenderer.ToString();
1242
1243 Bakup.Content = HTMLTextLabel;
1244 this.currentElement = Bakup;
1245 }
1246
1251 public Task Render(HorizontalRule Element)
1252 {
1253 ContentView CurrentContentView = (ContentView)this.currentElement;
1254
1255 CurrentContentView.Content = new Rectangle
1256 {
1257 Fill = Brush.Black,
1258 HeightRequest = 1,
1259 Aspect = Stretch.Fill,
1260 };
1261
1262 return Task.CompletedTask;
1263 }
1264
1269 public async Task Render(Header Element)
1270 {
1271 int Level = Math.Max(0, Math.Min(9, Element.Level));
1272
1273 Label HeaderLabel = new Label
1274 {
1275 LineBreakMode = LineBreakMode.WordWrap,
1276 HorizontalTextAlignment = this.LabelAlignment(),
1277 TextType = TextType.Html
1278 };
1279
1280 using (HtmlRenderer Renderer = new(new HtmlSettings()
1281 {
1282 XmlEntitiesOnly = true
1283 }, this.Document))
1284 {
1285 await Renderer.RenderChildren(Element);
1286
1287 HeaderLabel.Text = Renderer.ToString();
1288 }
1289
1290 HeaderLabel.Style = AppStyles.GetHeaderStyle(Level);
1291
1292 ContentView Bakup = (ContentView)this.currentElement;
1293 Bakup.Content = HeaderLabel;
1294 this.currentElement = Bakup;
1295
1296 }
1297
1302 public Task Render(HashTag Element)
1303 {
1304 ContentView Bakup = (ContentView)this.currentElement;
1305
1306 ContentView InnerContentView = new();
1307 this.currentElement = InnerContentView;
1308
1309 Border ContentBorder = new Border
1310 {
1311 Background = Color.FromArgb("FFFAFAD2"),
1312 Content = InnerContentView
1313 };
1314
1315 this.RenderSpan(Element.Tag);
1316
1317 Bakup.Content = ContentBorder;
1318 return Task.CompletedTask;
1319 }
1320
1325 public async Task Render(FootnoteReference Element)
1326 {
1327 if (!(this.Document?.TryGetFootnote(Element.Key, out Footnote? Footnote) ?? false))
1328 Footnote = null;
1329
1330 if (Element.AutoExpand && Footnote is not null)
1331 await this.Render(Footnote);
1332 else if (this.Document?.TryGetFootnoteNumber(Element.Key, out int Nr) ?? false)
1333 {
1334 bool Bak = this.Superscript;
1335 this.Superscript = true;
1336
1337 await this.RenderSpan(Nr.ToString(CultureInfo.InvariantCulture));
1338
1339 this.Superscript = Bak;
1340
1341 if (Footnote is not null)
1342 Footnote.Referenced = true;
1343 }
1344 }
1345
1350 public async Task Render(EmojiReference Element)
1351 {
1352 if (this.InLabel)
1353 await this.RenderSpan(Element.Emoji.Unicode);
1354 else
1355 {
1356 IEmojiSource EmojiSource = this.Document.EmojiSource;
1357
1358 if (EmojiSource is null)
1359 await this.RenderSpan(Element.Delimiter + Element.Emoji.ShortName + Element.Delimiter);
1360 else if (!EmojiSource.EmojiSupported(Element.Emoji))
1361 await this.RenderSpan(Element.Emoji.Unicode);
1362 else
1363 {
1364 Waher.Content.Emoji.IImageSource Source = await this.Document.EmojiSource.GetImageSource(Element.Emoji, Element.Level);
1365 await this.OutputImage(Source);
1366 }
1367 }
1368 }
1369
1374 public Task Render(DetailsReference Element)
1375 {
1376 if (this.Document.Detail is not null)
1377 this.RenderDocument(this.Document.Detail, false);
1378 else
1379 this.Render((MetaReference)Element);
1380
1381 return Task.CompletedTask;
1382 }
1383
1388 public async Task Render(DeleteBlocks Element)
1389 {
1390 ContentView Bakup = (ContentView)this.currentElement;
1391
1392 Border Border = new Border
1393 {
1394 Padding = AppStyles.SmallMargins,
1395 Stroke = new SolidColorBrush
1396 {
1397 Color = AppColors.DeletedBorder,
1398 },
1399 StrokeThickness = 1,
1400 StrokeShape = new RoundRectangle
1401 {
1402 CornerRadius = 2
1403 }
1404 };
1405
1406 Bakup.Content = Border;
1407
1408 ContentView InnerContentView = new ContentView();
1409 this.currentElement = InnerContentView;
1410
1411 await this.RenderChildren(Element);
1412
1413 Border.Content = InnerContentView;
1414 this.currentElement = Bakup;
1415 }
1416
1421 public async Task Render(DefinitionTerms Element)
1422 {
1423 bool Top = true;
1424
1425 ContentView Bakup = (ContentView)this.currentElement;
1426
1427 foreach (MarkdownElement Term in Element.Children)
1428 {
1429 Thickness Margins = AppStyles.SmallLeftMargins + AppStyles.SmallRightMargins + AppStyles.SmallBottomMargins;
1430 if (Top)
1431 Margins += AppStyles.SmallTopMargins;
1432
1433 this.RenderContentView(Margins);
1434 ContentView InnerContentView = (ContentView)this.currentElement;
1435 this.currentElement = InnerContentView;
1436
1437 bool BoldBak = this.Bold;
1438 this.Bold = true;
1439
1440 await this.RenderLabel(Term, true);
1441 Bakup.Content = InnerContentView;
1442 this.Bold = BoldBak;
1443
1444 Top = false;
1445 }
1446
1447 this.currentElement = Bakup;
1448 }
1449
1454 public async Task Render(DefinitionList Element)
1455 {
1456 await this.RenderChildren(Element);
1457 }
1458
1463 public async Task Render(DefinitionDescriptions Element)
1464 {
1465 ContentView Bakup = (ContentView)this.currentElement;
1466
1467 MarkdownElement? Last = null;
1468
1469 foreach (MarkdownElement Description in Element.Children)
1470 Last = Description;
1471
1472 foreach (MarkdownElement Description in Element.Children)
1473 {
1474 if (Description.InlineSpanElement && !Description.OutsideParagraph)
1475 {
1476 Bakup.Margin = AppStyles.SmallTopMargins + AppStyles.SmallBottomMargins;
1477
1478 Label HTMLLabel = new Label
1479 {
1480 LineBreakMode = LineBreakMode.WordWrap,
1481 HorizontalTextAlignment = this.LabelAlignment(),
1482 TextType = TextType.Html
1483 };
1484
1485 using (HtmlRenderer Renderer = new(new HtmlSettings()
1486 {
1487 XmlEntitiesOnly = true
1488 }, this.Document))
1489 {
1490 await Description.Render(Renderer);
1491 HTMLLabel.Text = Renderer.ToString();
1492 }
1493
1494 Bakup.Content = HTMLLabel;
1495 }
1496 else
1497 {
1498 Bakup.Padding = AppStyles.SmallLeftMargins;
1499
1500 if (Description == Last)
1501 Bakup.Padding += AppStyles.SmallBottomMargins;
1502
1503 ContentView InnerCV = new();
1504 this.currentElement = InnerCV;
1505 await Description.Render(this); //TODO?
1506
1507 //No idea why a VSL is used for just one element but that is the case in the original renderer
1508 VerticalStackLayout Vsl = new VerticalStackLayout();
1509 Vsl.Add(InnerCV);
1510 Bakup.Content = Vsl;
1511 }
1512 }
1513 }
1514
1519 public async Task Render(CodeBlock Element)
1520 {
1521 ContentView Bakup = (ContentView)this.currentElement;
1522 VerticalStackLayout BlockStackLayout = new();
1523
1524 StringBuilder Output = new();
1525 MauiXamlRenderer Rend = new(Output, XML.WriterSettings(false, true));
1527
1528 if (Renderer is not null)
1529 {
1530 try
1531 {
1532 if (Element.Language.Equals("image/png", StringComparison.OrdinalIgnoreCase))
1533 {
1534 string Bin = Element.Rows[0];
1535 byte[] Data = Convert.FromBase64String(Bin);
1536 string Uri = "data:image/png;base64," + Convert.ToBase64String(Data, 0, Data.Length);
1537
1538 await this.OutputMaui(new Waher.Content.Emoji.ImageSource()
1539 {
1540 Url = Uri
1541 });
1542
1543 return;
1544 }
1545
1546 // TODO ?
1547 //if (await Renderer.RenderMauiXaml(Rend, Element.Rows, Element.Language, Element.Indent, Element.Document))
1548 // return;
1549 }
1550 catch (Exception Ex)
1551 {
1552 Ex = Log.UnnestException(Ex);
1553
1554 if (Ex is AggregateException Ex2)
1555 {
1556 foreach (Exception Ex3 in Ex2.InnerExceptions)
1557 {
1558 Label ExceptionLabel = new Label
1559 {
1560 LineBreakMode = LineBreakMode.WordWrap,
1561 TextColor = AppColors.Alert,
1562 Text = Ex3.Message
1563 };
1564
1565 BlockStackLayout.Add(ExceptionLabel);
1566 }
1567 }
1568 else
1569 {
1570 Label ExceptionLabel = new Label
1571 {
1572 LineBreakMode = LineBreakMode.WordWrap,
1573 TextColor = AppColors.Alert,
1574 Text = Ex.Message
1575 };
1576
1577 BlockStackLayout.Add(ExceptionLabel);
1578 }
1579 }
1580 }
1581
1582 for (int i = Element.Start; i <= Element.End; i++)
1583 {
1584 Label ContentLabel = new Label
1585 {
1586 LineBreakMode = LineBreakMode.NoWrap,
1587 HorizontalTextAlignment = this.LabelAlignment(),
1588 FontFamily = "SpaceGroteskRegular",
1589 Text = Element.Rows[i]
1590 };
1591
1592 BlockStackLayout.Add(ContentLabel);
1593 }
1594
1595 Bakup.Content = BlockStackLayout;
1596 this.currentElement = Bakup;
1597 }
1598
1603 public Task Render(CommentBlock Element)
1604 {
1605 //TODO
1606 return Task.CompletedTask;
1607 }
1608
1613 public async Task Render(BulletList Element)
1614 {
1615 ContentView Bakup = (ContentView)this.currentElement;
1616
1617 int Row = 0;
1618 bool ParagraphBullet;
1619
1620 Grid ListGrid = new Grid
1621 {
1622 RowSpacing = 0,
1623 ColumnSpacing = 0,
1624 ColumnDefinitions =
1625 {
1626 new ColumnDefinition { Width = GridLength.Auto },
1627 new ColumnDefinition { Width = GridLength.Star }
1628 },
1629 };
1630
1631 foreach (MarkdownElement _ in Element.Children)
1632 {
1633 ListGrid.AddRowDefinition(new RowDefinition { Height = GridLength.Auto });
1634 }
1635
1636 foreach (MarkdownElement E in Element.Children)
1637 {
1638 if (E is UnnumberedItem Item)
1639 {
1640 ParagraphBullet = !E.InlineSpanElement || E.OutsideParagraph;
1641
1642 this.RenderContentView(AppStyles.SmallRightMargins);
1643
1644 ContentView StarContentView = (ContentView)this.currentElement;
1645 StarContentView.Column(0);
1646 StarContentView.Row(Row);
1647
1648 Label Lbl = new Label { Text = "•" };
1649 StarContentView.Content = Lbl;
1650 ListGrid.Add(StarContentView);
1651
1652 ContentView VslContainer = new ContentView();
1653 VslContainer.Column(1);
1654 VslContainer.Row(Row);
1655 this.currentElement = VslContainer;
1656
1657 if (ParagraphBullet)
1658 await E.Render(this);
1659 else
1660 await this.RenderLabel(Item, false);
1661
1662 ListGrid.Add(VslContainer);
1663 }
1664
1665 Row++;
1666 }
1667
1668 Bakup.Content = ListGrid;
1669 this.currentElement = Bakup;
1670 }
1671
1676 public async Task Render(CenterAligned Element)
1677 {
1679 this.Alignment = Waher.Content.Markdown.Model.TextAlignment.Center;
1680
1681 await this.RenderChildren(Element);
1682
1683 this.Alignment = Bak;
1684 }
1685
1690 public async Task Render(BlockQuote Element)
1691 {
1692 ContentView Bakup = (ContentView)this.currentElement;
1693
1694 Border BlockQuoteBorder = new Border
1695 {
1696 Padding = AppStyles.SmallMargins,
1697 Stroke = new SolidColorBrush
1698 {
1700 },
1701 StrokeThickness = 1,
1702 StrokeShape = new RoundRectangle
1703 {
1704 CornerRadius = 2
1705 }
1706 };
1707
1708 Bakup.Content = BlockQuoteBorder;
1709
1710 ContentView BlockQuoteContentView = new ContentView();
1711 this.currentElement = BlockQuoteContentView;
1712
1713 await this.RenderChildren(Element);
1714
1715 BlockQuoteBorder.Content = BlockQuoteContentView;
1716 this.currentElement = Bakup;
1717 }
1718
1723 public Task Render(AutomaticLinkUrl Element)
1724 {
1725 string? Bak = this.Hyperlink;
1726 this.Hyperlink = Element.URL;
1727 this.RenderSpan(Element.URL);
1728 this.Hyperlink = Bak;
1729
1730 return Task.CompletedTask;
1731 }
1732
1737 public Task Render(AutomaticLinkMail Element)
1738 {
1739 string? Bak = this.Hyperlink;
1740 this.Hyperlink = "mailto:" + Element.EMail;
1741 this.RenderSpan(this.Hyperlink);
1742 this.Hyperlink = Bak;
1743
1744 return Task.CompletedTask;
1745 }
1746
1747 #endregion
1748
1749 #region Label & Span
1750
1757 internal async Task RenderLabel(MarkdownElement Element, bool IncludeElement)
1758 {
1759 ContentView Bakup = (ContentView)this.currentElement;
1760
1761 Label ParentLabel = new Label
1762 {
1763 LineBreakMode = LineBreakMode.WordWrap,
1764 HorizontalTextAlignment = this.LabelAlignment()
1765 };
1766
1767 this.currentElement = ParentLabel;
1768
1769 bool WasInLabel = this.InLabel;
1770 this.InLabel = true;
1771
1772 try
1773 {
1774 if (IncludeElement)
1775 await Element.Render(this);
1776 else
1777 await this.RenderChildren(Element);
1778 }
1779 finally
1780 {
1781 this.InLabel = WasInLabel;
1782 this.currentElement = Bakup;
1783 }
1784
1785 Bakup.Content = ParentLabel;
1786 }
1787
1793 internal Task RenderSpan(string Text)
1794 {
1795 Label MainLabel;
1796 FormattedString FormattedString;
1797
1798 if (!this.InLabel)
1799 {
1800 MainLabel = new Label
1801 {
1802 LineBreakMode = LineBreakMode.WordWrap,
1803 };
1804
1805 ContentView Cv = (ContentView)this.currentElement;
1806 Cv.Content = MainLabel;
1807 this.currentElement = Cv;
1808 }
1809 else
1810 {
1811 MainLabel = (Label)this.currentElement;
1812 this.currentElement = MainLabel;
1813 }
1814
1815 if (MainLabel.FormattedText is null)
1816 {
1817 FormattedString = new();
1818 }
1819 else
1820 {
1821 FormattedString = MainLabel.FormattedText;
1822 }
1823
1824 Span MainSpan = new Span();
1825 FormattedString.Spans.Add(MainSpan);
1826 MainLabel.FormattedText = FormattedString;
1827
1828 if (this.Superscript)
1829 Text = TextRenderer.ToSuperscript(Text);
1830 else if (this.Subscript)
1831 Text = TextRenderer.ToSubscript(Text);
1832
1833 MainSpan.Text = Text;
1834
1835 if (this.Bold && this.Italic)
1836 MainSpan.FontAttributes = FontAttributes.Bold | FontAttributes.Italic;
1837 else if (this.Bold)
1838 MainSpan.FontAttributes = FontAttributes.Bold;
1839 else if (this.Italic)
1840 MainSpan.FontAttributes = FontAttributes.Italic;
1841
1842 if (this.StrikeThrough && this.Underline)
1843 MainSpan.TextDecorations = TextDecorations.Underline | TextDecorations.Strikethrough;
1844 else if (this.StrikeThrough)
1845 MainSpan.TextDecorations = TextDecorations.Strikethrough;
1846 else if (this.Underline)
1847 MainSpan.TextDecorations = TextDecorations.Underline;
1848
1849 if (this.Code)
1850 {
1851 MainSpan.FontAttributes |= FontAttributes.Italic;
1852 MainSpan.FontFamily = "SpaceGroteskBold";
1853 }
1854
1855 if (this.Hyperlink is not null)
1856 {
1857 MainSpan.TextColor = AppColors.BlueLink;
1858
1859 MainSpan.GestureRecognizers.Add(new TapGestureRecognizer { CommandParameter = this.Hyperlink, Command = new Command(async Parameter => await App.OpenUrlAsync(Parameter as string ?? string.Empty)) });
1860 }
1861
1862 return Task.CompletedTask;
1863 }
1864
1865 #endregion
1866
1867 #region Dispose
1869 public void Dispose()
1870 {
1871 this.Dispose(true);
1872 GC.SuppressFinalize(this);
1873 }
1874
1875 private bool isDisposed;
1876
1880 protected virtual void Dispose(bool disposing)
1881 {
1882 if (this.isDisposed)
1883 return;
1884
1885 if (disposing)
1886 {
1887
1888 }
1889 this.isDisposed = true;
1890 }
1891
1892 #endregion
1893
1894 #region Logic Helpers
1898 private bool NeedsToDisplayFootnotes()
1899 {
1900 IEnumerable<Footnote> DocumentFootnotes = this.GetFootnotes(this.Document.Footnotes);
1901
1902 if (DocumentFootnotes is null)
1903 return false;
1904
1905 foreach (Footnote Footnote in DocumentFootnotes)
1906 {
1907 if (Footnote.Referenced)
1908 {
1909 return true;
1910 }
1911 }
1912
1913 return false;
1914 }
1915
1921 private IEnumerable<Footnote> GetFootnotes(string[] keys)
1922 {
1923 foreach(string Key in keys)
1924 {
1926 this.Document.TryGetFootnote(Key, out Footnote);
1927 yield return Footnote;
1928 }
1929 }
1930
1936 private async Task OutputImage(Waher.Content.Emoji.IImageSource Source)
1937 {
1938 Source = await CheckDataUri(Source);
1939
1940 Image Image = new Image
1941 {
1942 Source = Source.Url,
1943 };
1944
1945 if (Source.Width.HasValue)
1946 Image.WidthRequest = Source.Width.Value;
1947
1948 if (Source.Height.HasValue)
1949 Image.HeightRequest = Source.Height.Value;
1950
1951 ScrollView ScrollView = new ScrollView
1952 {
1953 Orientation = ScrollOrientation.Horizontal,
1954 Content = Image
1955 };
1956
1957 ContentView Cv = (ContentView)this.currentElement;
1958 Cv.Content = ScrollView;
1959 }
1960
1967 public Microsoft.Maui.TextAlignment LabelAlignment()
1968 {
1969 switch (this.Alignment)
1970 {
1971 case Waher.Content.Markdown.Model.TextAlignment.Left:
1972 return Microsoft.Maui.TextAlignment.Start;
1973
1974 case Waher.Content.Markdown.Model.TextAlignment.Right:
1975 return Microsoft.Maui.TextAlignment.End;
1976
1977 case Waher.Content.Markdown.Model.TextAlignment.Center:
1978 return Microsoft.Maui.TextAlignment.Center;
1979
1980 default:
1981 return Microsoft.Maui.TextAlignment.Center;
1982 }
1983 }
1984
1991 internal void RenderContentView(Waher.Content.Markdown.Model.TextAlignment Alignment, Thickness Margins, Style? BoxStyle)
1992 {
1993 ContentView ContentView = new ContentView();
1994
1995 if (!Margins.Equals(Thickness.Zero))
1996 ContentView.Padding = Margins;
1997
1998 if (BoxStyle is not null)
1999 ContentView.Style = BoxStyle;
2000
2001 switch (Alignment)
2002 {
2003 case Waher.Content.Markdown.Model.TextAlignment.Center:
2004 ContentView.HorizontalOptions = LayoutOptions.Center;
2005 break;
2006
2007 case Waher.Content.Markdown.Model.TextAlignment.Left:
2008 ContentView.HorizontalOptions = LayoutOptions.Start;
2009 break;
2010
2011 case Waher.Content.Markdown.Model.TextAlignment.Right:
2012 ContentView.HorizontalOptions = LayoutOptions.End;
2013 break;
2014 }
2015
2016 this.currentElement = ContentView;
2017 }
2018
2023 internal void RenderContentView(Thickness Margins)
2024 {
2025 this.RenderContentView(this.Alignment, Margins, null);
2026 }
2027
2035 public async Task RenderObject(object? Result, bool AloneInParagraph, Variables Variables)
2036 {
2037 ContentView Bakup = (ContentView)this.currentElement;
2038
2039 if (Result is null)
2040 return;
2041
2042 string? S;
2043
2044 if (Result is XmlDocument Xml)
2045 Result = await MarkdownDocument.TransformXml(Xml, Variables);
2046 else if (Result is IToMatrix ToMatrix)
2047 Result = ToMatrix.ToMatrix();
2048
2049 if (this.InLabel)
2050 {
2051 S = Result?.ToString();
2052 if (!string.IsNullOrEmpty(S))
2053 await this.RenderSpan(Result?.ToString() ?? string.Empty); //TODO
2054
2055 return;
2056 }
2057
2058 if (Result is Graph G)
2059 {
2060 PixelInformation Pixels = G.CreatePixels(Variables);
2061 byte[] Bin = Pixels.EncodeAsPng();
2062
2063 S = "data:image/png;base64," + Convert.ToBase64String(Bin, 0, Bin.Length);
2064
2065 Graph2D? Graph2D = G as Graph2D;
2066 string Title = Graph2D?.Title ?? "Graph";
2067
2068 await this.OutputMaui(new Waher.Content.Emoji.ImageSource()
2069 {
2070 Url = S,
2071 Width = Pixels.Width,
2072 Height = Pixels.Height
2073 }, Title);
2074 }
2075 else if (Result is SKImage Img)
2076 {
2077 using SKData Data = Img.Encode(SKEncodedImageFormat.Png, 100);
2078 byte[] Bin = Data.ToArray();
2079
2080 S = "data:image/png;base64," + Convert.ToBase64String(Bin, 0, Bin.Length);
2081
2082 await this.OutputMaui(new Waher.Content.Emoji.ImageSource()
2083 {
2084 Url = S,
2085 Width = Img.Width,
2086 Height = Img.Height
2087 }, "Image");
2088 }
2089 else if (Result is MarkdownDocument Doc)
2090 {
2091 //TODO maybe
2092 await this.RenderDocument(Doc, true); // Does not call ProcessAsyncTasks()
2093 Doc.ProcessAsyncTasks();
2094 }
2095 else if (Result is MarkdownContent Markdown)
2096 {
2097 Doc = await MarkdownDocument.CreateAsync(Markdown.Markdown);
2098 await this.RenderDocument(Doc, true); // Does not call ProcessAsyncTasks()
2099 Doc.ProcessAsyncTasks();
2100 }
2101 else if (Result is Exception Ex)
2102 {
2103 Ex = Log.UnnestException(Ex);
2104
2105 if (Ex is AggregateException Ex2)
2106 {
2107 VerticalStackLayout Vsl = new();
2108
2109 foreach (Exception Ex3 in Ex2.InnerExceptions)
2110 {
2111 Label Label = new Label
2112 {
2113 LineBreakMode = LineBreakMode.WordWrap,
2114 TextColor = AppColors.Alert,
2115 Text = Ex3.Message
2116 };
2117
2118 Vsl.Add(Label);
2119 }
2120 Bakup.Content = Vsl;
2121 }
2122 else
2123 {
2124 Label Label = new Label
2125 {
2126 LineBreakMode = LineBreakMode.WordWrap,
2127 TextColor = AppColors.Alert,
2128 Text = Ex.Message
2129 };
2130
2131 Bakup.Content = Label;
2132 }
2133 }
2134 else
2135 {
2136 Label Label = new Label
2137 {
2138 LineBreakMode = LineBreakMode.WordWrap,
2139 HorizontalTextAlignment = this.LabelAlignment(),
2140 Text = Result.ToString()
2141 };
2142
2143 Bakup.Content = Label;
2144 }
2145 }
2146
2153 private static void GetMargins(MarkdownElement Element, out bool TopMargin, out bool BottomMargin)
2154 {
2155 if (Element.InlineSpanElement && !Element.OutsideParagraph)
2156 {
2157 TopMargin = false;
2158 BottomMargin = false;
2159 }
2160 else if (Element is NestedBlock NestedBlock)
2161 {
2162 bool First = true;
2163
2164 TopMargin = BottomMargin = false;
2165
2167 {
2168 if (First)
2169 {
2170 First = false;
2171 GetMargins(E, out TopMargin, out BottomMargin);
2172 }
2173 else
2174 GetMargins(E, out bool _, out BottomMargin);
2175 }
2176 }
2177 else if (Element is MarkdownElementSingleChild SingleChild)
2178 GetMargins(SingleChild.Child, out TopMargin, out BottomMargin);
2179 else
2180 {
2181 TopMargin = true;
2182 BottomMargin = true;
2183 }
2184 }
2185
2191 private async Task OutputMaui(Waher.Content.Emoji.IImageSource Source)
2192 {
2193 await this.OutputMaui(Source, null);
2194 }
2195
2200 private async Task OutputMaui(Waher.Content.Emoji.IImageSource Source, string? Title)
2201 {
2202 Source = await CheckDataUri(Source);
2203
2204 Image Image = new Image
2205 {
2206 Source = Source.Url,
2207 };
2208
2209 ScrollView Sv = new ScrollView
2210 {
2211 Orientation = ScrollOrientation.Horizontal,
2212 Content = Image
2213 };
2214
2215 VerticalStackLayout Vsl = new VerticalStackLayout
2216 {
2217 Spacing = AppStyles.SmallSpacing
2218 };
2219
2220 Vsl.Add(Sv);
2221
2222 if (Title is not null)
2223 {
2224 Label Label = new Label
2225 {
2226 LineBreakMode = LineBreakMode.WordWrap,
2227 HorizontalTextAlignment = Microsoft.Maui.TextAlignment.Center,
2228 Text = Title
2229 };
2230 Vsl.Add(Label);
2231 }
2232
2233 if (Source.Height.HasValue)
2234 Sv.HeightRequest = Source.Height.Value;
2235
2236 ContentView Cv = (ContentView)this.currentElement;
2237
2238 Cv.Content = Vsl;
2239 }
2240
2246 private async Task RenderMaui(Waher.Content.Markdown.Model.SpanElements.Multimedia Element)
2247 {
2248 ContentView Bakup = (ContentView)this.currentElement;
2249 VerticalStackLayout MauiStackLayout = new VerticalStackLayout();
2250
2251 foreach (MultimediaItem Item in Element.Items)
2252 {
2253 ContentView ElementContentView = new();
2254 this.currentElement = ElementContentView;
2255
2256 string? Caption = null;
2257 if (!Item.Title.Equals(String.Empty, StringComparison.Ordinal))
2258 {
2259 Caption = Item.Title;
2260 }
2261
2262 await this.OutputMaui(new Waher.Content.Emoji.ImageSource()
2263 {
2264 Url = Element.Document.CheckURL(Item.Url, null),
2265 Width = Item.Width,
2266 Height = Item.Height,
2267 }, Caption);
2268 MauiStackLayout.Add(ElementContentView);
2269 }
2270 Bakup.Content = MauiStackLayout;
2271 this.currentElement = Bakup;
2272 }
2273
2279 {
2280 string Url = Source.Url;
2281 int i;
2282
2283 if (Url.StartsWith("data:", StringComparison.CurrentCultureIgnoreCase) && (i = Url.IndexOf("base64,")) > 0)
2284 {
2285 int? Width = Source.Width;
2286 int? Height = Source.Height;
2287 byte[] Data = Convert.FromBase64String(Url.Substring(i + 7));
2288
2289 using (SKBitmap Bitmap = SKBitmap.Decode(Data))
2290 {
2291 Width = Bitmap.Width;
2292 Height = Bitmap.Height;
2293 }
2294
2295 Url = await ImageContent.GetTemporaryFile(Data);
2296
2297 return new ImageSource()
2298 {
2299 Url = Url,
2300 Width = Width,
2301 Height = Height
2302 };
2303 }
2304 else
2305 return Source;
2306 }
2307 #endregion
2308
2309 #region State Management
2310
2311 private void ClearState()
2312 {
2313 this.Alignment = Waher.Content.Markdown.Model.TextAlignment.Left;
2314 this.Bold = false;
2315 this.Italic = false;
2316 this.StrikeThrough = false;
2317 this.Underline = false;
2318 this.Superscript = false;
2319 this.Subscript = false;
2320 this.Code = false;
2321 this.InLabel = false;
2322 this.Hyperlink = null;
2323 }
2324
2325 private StateBackup Backup()
2326 {
2327 return new StateBackup()
2328 {
2329 Alignment = this.Alignment,
2330 Bold = this.Bold,
2331 Italic = this.Italic,
2332 StrikeThrough = this.StrikeThrough,
2333 Underline = this.Underline,
2334 Superscript = this.Superscript,
2335 Subscript = this.Subscript,
2336 Code = this.Code,
2337 InLabel = this.InLabel,
2338 Hyperlink = this.Hyperlink
2339 };
2340 }
2341
2342 private void Restore(StateBackup Backup)
2343 {
2344 this.Alignment = Backup.Alignment;
2345 this.Bold = Backup.Bold;
2346 this.Italic = Backup.Italic;
2347 this.StrikeThrough = Backup.StrikeThrough;
2348 this.Underline = Backup.Underline;
2349 this.Superscript = Backup.Superscript;
2350 this.Subscript = Backup.Subscript;
2351 this.Code = Backup.Code;
2352 this.InLabel = Backup.InLabel;
2353 this.Hyperlink = Backup.Hyperlink;
2354 }
2355
2356 private class StateBackup
2357 {
2359 public bool Bold;
2360 public bool Italic;
2361 public bool StrikeThrough;
2362 public bool Underline;
2363 public bool Superscript;
2364 public bool Subscript;
2365 public bool Code;
2366 public bool InLabel;
2367 public string? Hyperlink;
2368 }
2369
2370 #endregion
2371 }
2372}
Static class that gives access to app-specific themed colors. All colors are fetched directly from th...
Definition: AppColors.cs:8
static Color DeletedBorder
Deleted Border color.
Definition: AppColors.cs:233
static Color InsertedBorder
Inserted Border color.
Definition: AppColors.cs:221
static Color PrimaryForeground
Primary foreground color.
Definition: AppColors.cs:143
static Color Alert
Alert color.
Definition: AppColors.cs:173
Static class that gives access to app-specific styles
Definition: AppStyles.cs:12
static Thickness SmallMargins
Small margins
Definition: AppStyles.cs:193
static Thickness SmallRightMargins
Right-only small margins
Definition: AppStyles.cs:181
static Style TableCellOdd
Style for odd table cells
Definition: AppStyles.cs:445
static Style TableCell
Style for table cells
Definition: AppStyles.cs:457
static Thickness SmallBottomMargins
Bottom-only small margins
Definition: AppStyles.cs:145
static Style TableCellEven
Style for even table cells
Definition: AppStyles.cs:433
static Thickness SmallLeftMargins
Left-only small margins
Definition: AppStyles.cs:169
static Thickness SmallTopMargins
Top-only small margins
Definition: AppStyles.cs:157
static Style GetHeaderStyle(int x)
Get style for header of certain size
Definition: AppStyles.cs:468
Renders MAUI objects in a verticalStackLayout from a Markdown document.
Definition: MauiRenderer.cs:42
Task Render(NumberedItem Element)
Renders Element .
async Task Render(DefinitionList Element)
Renders Element .
Task RenderChild(MarkdownElementSingleChild Element)
Renders the child of Element .
async Task Render(CenterAligned Element)
Renders Element .
Task Render(InvisibleBreak Element)
Renders Element .
async Task Render(Link Element)
Renders Element .
async Task Render(DeleteBlocks Element)
Renders Element .
async Task Render(HtmlBlock Element)
Renders Element .
async Task Render(NumberedList Element)
Renders Element .
async Task Render(DefinitionTerms Element)
Renders Element .
Task Render(UnnumberedItem Element)
Renders Element .
async Task Render(Footnote Element)
Renders Element .
VerticalStackLayout? Output()
Retrieves the final VerticalStackLayout containing all rendered views.
Task Render(DetailsReference Element)
Renders Element .
Task RenderDocument(MarkdownDocument Document, bool Inclusion)
Sets up default state and calls main render function.
Task Render(SectionSeparator Element)
Renders Element .
Task Render(InlineCode Element)
Renders Element .
bool Superscript
If text is superscript
Definition: MauiRenderer.cs:83
async Task Render(Abbreviation Element)
Renders Element .
bool Underline
If text is underlined
Definition: MauiRenderer.cs:78
Task Render(MetaReference Element)
Renders Element .
Task Render(HashTag Element)
Renders Element .
async Task Render(StrikeThrough Element)
Renders Element .
async Task Render(SuperScript Element)
Renders Element .
async Task Render(TaskList Element)
Renders Element .
async Task Render(DefinitionDescriptions Element)
Renders Element .
Microsoft.Maui.TextAlignment LabelAlignment()
Helper function to determine label text alignment
Task Render(LineBreak Element)
Renders Element .
async Task Render(Sections Element)
Renders Element .
static async Task< Waher.Content.Emoji.IImageSource > CheckDataUri(Waher.Content.Emoji.IImageSource Source)
Checks a Data URI image, that it contains a decodable image.
Task Render(HtmlEntity Element)
Renders Element .
async Task Render(InsertBlocks Element)
Renders Element .
async Task Render(InlineScript Element)
Renders Element .
Task Render(AutomaticLinkMail Element)
Renders Element .
Task Render(InlineHTML Element)
Renders Element .
Task Render(TaskItem Element)
Renders Element .
async Task Render(NestedBlock Element)
Renders Element .
async Task Render(Delete Element)
Renders Element .
async Task Render(MultimediaReference Element)
Renders Element .
bool Subscript
If text is subscript
Definition: MauiRenderer.cs:88
bool StrikeThrough
If text is stricken through
Definition: MauiRenderer.cs:73
async Task Render(Waher.Content.Markdown.Model.SpanElements.Multimedia Element)
Renders Element .
Task Render(HorizontalRule Element)
Renders Element .
virtual void Dispose(bool disposing)
IDisposable.Dispose
async Task Render(Emphasize Element)
Renders Element .
async Task Render(LeftAligned Element)
Renders Element .
async Task Render(Underline Element)
Renders Element .
async Task Render(EmojiReference Element)
Renders Element .
Task Render(AutomaticLinkUrl Element)
Renders Element .
async Task RenderChildren(MarkdownElement Element)
Renders the children of Element .
async Task Render(Table Element)
Renders Element .
string? Hyperlink
Link, if rendering a hyperlink, null otherwise.
async Task Render(RightAligned Element)
Renders Element .
Task Render(InlineText Element)
Renders Element .
Task Render(CommentBlock Element)
Renders Element .
async Task Render(FootnoteReference Element)
Renders Element .
async Task RenderChildren(MarkdownElementChildren Element)
Renders the children of Element .
async Task Render(Insert Element)
Renders Element .
async Task RenderObject(object? Result, bool AloneInParagraph, Variables Variables)
Renders an object such as images, graphs, exceptions, or other documents.
async Task Render(LinkReference Element)
Renders Element .
MarkdownDocument Document
Reference to Markdown document being processed.
Definition: MauiRenderer.cs:48
async Task Render(MarginAligned Element)
Renders Element .
async Task Render(CodeBlock Element)
Renders Element .
Task Render(HtmlEntityUnicode Element)
Renders Element .
async Task Render(Header Element)
Renders Element .
async Task Render(SubScript Element)
Renders Element .
bool InLabel
If rendering is inside a label.
Definition: MauiRenderer.cs:98
async Task Render(BlockQuote Element)
Renders Element .
Task RenderDocumentHeader()
Renders the document header.
async Task Render(Paragraph Element)
Renders Element .
Waher.Content.Markdown.Model.TextAlignment Alignment
Current text-alignment.
virtual async Task RenderDocumentEntry(MarkdownDocument Document, bool Inclusion)
Renders a document.
MauiRenderer(MarkdownDocument Document)
Initializes a new instance of the MauiRenderer class.
async Task Render(BulletList Element)
Renders Element .
bool Code
If text is inline code.
Definition: MauiRenderer.cs:93
async Task RenderFootnotes()
Renders footnotes.
async Task Render(Strong Element)
Renders Element .
Renders XAML (Maui flavour) from a Markdown document.
Helps with parsing of commong data types.
Definition: CommonTypes.cs:15
static bool TryParse(string s, out double Value)
Tries to decode a string encoded double.
Definition: CommonTypes.cs:48
string ShortName
Emoji short name.
string Unicode
Unicode representation of emoji.
Contains information about an emoji image.
Definition: ImageSource.cs:7
static string EntityToCharacter(string Entity)
Converts an HTML entity into a character.
Definition: HtmlEntity.cs:73
Class that can be used to encapsulate Markdown to be returned from a Web Service, bypassing any encod...
Contains a markdown document. This markdown document class supports original markdown,...
static async Task< object > TransformXml(XmlDocument Xml, Variables Variables)
Transforms XML to an object that is easier to visualize.
bool TryGetFootnote(string Key, out Footnote Footnote)
Tries to get a footnote, given its key.
bool TryGetMetaData(string Key, out KeyValuePair< string, bool >[] Value)
Tries to get a meta-data value given its key.
IEnumerable< string > FootnoteOrder
Order of footnotes.
IEmojiSource EmojiSource
Source for emojis in the document.
string[] Footnotes
Gets the keys of the footnotes in the order that they are referenced in the document....
MarkdownDocument Detail
Detail document of a master document.
Multimedia GetReference(string Label)
Gets the multimedia information referenced by a label.
static Task< MarkdownDocument > CreateAsync(string MarkdownText, params Type[] TransparentExceptionTypes)
Contains a markdown document. This markdown document class supports original markdown,...
ChunkedList< MarkdownElement > Elements
Markdown elements making up the document.
Abstract base class for block elements with one child.
Represents a block quote in a markdown document.
Definition: BlockQuote.cs:11
Represents a bullet list in a markdown document.
Definition: BulletList.cs:11
Represents a center-aligned set of blocks in a markdown document.
Represents a code block in a markdown document.
Definition: CodeBlock.cs:17
Represents a comment block in a markdown document.
Definition: CommentBlock.cs:10
Represents a definition list in a markdown document.
Represents inserted blocks in a markdown document.
Definition: DeleteBlocks.cs:11
bool Referenced
If the Footnote has been referenced during rendering, and therefore needs to be shown at the end of t...
Definition: Footnote.cs:51
Represents a header in a markdown document.
Definition: Header.cs:15
Represents a block of HTML in a markdown document.
Definition: HtmlBlock.cs:11
Represents inserted blocks in a markdown document.
Definition: InsertBlocks.cs:11
Represents a left-aligned set of blocks in a markdown document.
Definition: LeftAligned.cs:11
Represents a margin-aligned set of blocks in a markdown document.
Represents a nested block with no special formatting rules in a markdown document.
Definition: NestedBlock.cs:12
Represents a numbered item in an ordered list.
Definition: NumberedItem.cs:10
Represents a numbered list in a markdown document.
Definition: NumberedList.cs:11
Represents a paragraph in a markdown document.
Definition: Paragraph.cs:11
Represents a right-aligned set of blocks in a markdown document.
Definition: RightAligned.cs:11
Represents a sequence of sections.
Definition: Sections.cs:11
Represents a table in a markdown document.
Definition: Table.cs:11
TextAlignment[] ColumnAlignments
Table column alignments.
Definition: Table.cs:65
MarkdownElement[][] Headers
Headers in table.
Definition: Table.cs:55
TextAlignment?[][] RowCellAlignments
Row cell alignments in table.
Definition: Table.cs:80
override ChunkedList< MarkdownElement > Children
Any children of the element.
Definition: Table.cs:101
TextAlignment?[][] HeaderCellAlignments
Header cell alignments in table.
Definition: Table.cs:75
MarkdownElement[][] Rows
Rows in table.
Definition: Table.cs:60
Represents a task item in a task list.
Definition: TaskItem.cs:10
bool IsChecked
If the item is checked or not.
Definition: TaskItem.cs:31
Represents a task list in a markdown document.
Definition: TaskList.cs:11
Represents an unnumbered item in an ordered list.
Abstract base class for all markdown elements with a variable number of child elements.
override ChunkedList< MarkdownElement > Children
Any children of the element.
bool HasOneChild
If the element has only one child.
MarkdownElement FirstChild
First child, or null if none.
Abstract base class for all markdown elements.
abstract Task Render(IRenderer Output)
Renders the element.
abstract bool InlineSpanElement
If the element is an inline span element.
virtual bool OutsideParagraph
If element, parsed as a span element, can stand outside of a paragraph if alone in it.
virtual ChunkedList< MarkdownElement > Children
Any children of the element.
MarkdownDocument Document
Markdown document.
Abstract base class for all markdown elements with one child element.
static Task< string > GetTemporaryFile(byte[] BinaryImage)
Stores an image in binary form as a temporary file. Files will be deleted when application closes.
Definition: ImageContent.cs:89
string Delimiter
Delimiter string used to identify emoji.
int Level
Level (number of colons used to define the emoji)
bool AutoExpand
If the footnote should automatically be expanded when rendered, if format supports auto-expansion.
Represents an HTML entity in Unicode format.
bool AloneInParagraph
If the element is alone in a paragraph.
Definition: InlineScript.cs:55
async Task< object > EvaluateExpression()
Evaluates the script expression.
Definition: InlineScript.cs:71
bool TryGetMetaData(out KeyValuePair< string, bool >[] Values)
Tries to get meta-data from the document.
MultimediaItem[] Items
Multimedia items.
Definition: Multimedia.cs:41
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
Abstract base class for Markdown renderers.
Definition: Renderer.cs:15
Task RenderChildren(MarkdownElementChildren Element)
Renders the children of Element .
Definition: Renderer.cs:147
override string ToString()
Returns the renderer output.
Definition: Renderer.cs:130
virtual void Dispose()
Disposes of the renderer.
Definition: Renderer.cs:66
Renders plain text from a Markdown document.
Definition: TextRenderer.cs:18
static string ToSubscript(string s)
Converts a string to subscript (as far as it goes).
static string ToSuperscript(string s)
Converts a string to superscript (as far as it goes).
Helps with common XML-related tasks.
Definition: XML.cs:21
static XmlWriterSettings WriterSettings(bool Indent, bool OmitXmlDeclaration)
Gets an XML writer settings object.
Definition: XML.cs:1351
Static class managing the application event log. Applications and services log events on this static ...
Definition: Log.cs:14
static Exception UnnestException(Exception Exception)
Unnests an exception, to extract the relevant inner exception.
Definition: Log.cs:828
Euler's number.
Definition: E.cs:12
Handles two-dimensional graphs.
Definition: Graph2D.cs:27
string Title
Title for graph.
Definition: Graph2D.cs:282
Base class for graphs.
Definition: Graph.cs:88
Contains pixel information
virtual byte[] EncodeAsPng()
Encodes the pixels into a binary PNG image.
ToMatrix(ScriptNode Operand, bool NullCheck, int Start, int Length, Expression Expression)
To-Matrix operator.
Definition: ToMatrix.cs:22
Collection of variables.
Definition: Variables.cs:25
Interface for multimedia content Maui XAML renderers.
Interface for Emoji sources. Emoji sources provide emojis to content providers.
Definition: IEmojiSource.cs:10
Task< IImageSource > GetImageSource(EmojiInfo Emoji)
Gets the image source of an emoji.
bool EmojiSupported(EmojiInfo Emoji)
If the emoji is supported by the emoji source.
Contains information about an emoji image.
Definition: IImageSource.cs:7
Interface for Markdown renderers.
Definition: IRenderer.cs:12
Interface for objects that can be converted into matrices.
Definition: IToMatrix.cs:9
Definition: ImplTypes.g.cs:58
TextAlignment
Text alignment of contents.
delegate string ToString(IElement Element)
Delegate for callback methods that convert an element value to a string.
Definition: App.xaml.cs:4