VectSharp  2.2.1
A light library for C# vector graphics
MarkdownRenderer.cs
1 /*
2  VectSharp - A light library for C# vector graphics.
3  Copyright (C) 2020-2022 Giorgio Bianchini
4 
5  This program is free software: you can redistribute it and/or modify
6  it under the terms of the GNU Lesser General Public License as published by
7  the Free Software Foundation, version 3.
8 
9  This program is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  GNU Lesser General Public License for more details.
13 
14  You should have received a copy of the GNU Lesser General Public License
15  along with this program. If not, see <https://www.gnu.org/licenses/>.
16 */
17 
18 using Markdig;
19 using Markdig.Extensions;
20 using Markdig.Extensions.Tables;
21 using Markdig.Helpers;
22 using Markdig.Renderers.Html;
23 using Markdig.Syntax;
24 using Markdig.Syntax.Inlines;
25 using System;
26 using System.Collections.Generic;
27 using System.Linq;
28 using System.Text;
29 
30 namespace VectSharp.Markdown
31 {
32  /// <summary>
33  /// Renders Markdown documents into VectSharp graphics objects.
34  /// </summary>
35  public class MarkdownRenderer
36  {
37  /// <summary>
38  /// The base font size to use when rendering the document. This will be the size of regular elements, and the size of header elements will be expressed as a multiple of this.
39  /// </summary>
40  public double BaseFontSize { get; set; } = 9.71424;
41 
42  /// <summary>
43  /// The font size for elements at each header level. The values in this array will be multiplied by the <see cref="BaseFontSize"/>.
44  /// </summary>
45  public double[] HeaderFontSizeMultipliers { get; } = new double[]
46  {
47  28 / 12.0, 22 / 12.0, 16 / 12.0, 14 / 12.0, 13 / 12.0, 12 / 12.0
48  };
49 
50  /// <summary>
51  /// The thickness of the separator line after a header of each level. A value of 0 disables the line after headers of that level.
52  /// </summary>
53  public double[] HeaderLineThicknesses { get; } = new double[] { 1, 1, 0, 0, 0, 0 };
54 
55  /// <summary>
56  /// The thickness of thematic break lines.
57  /// </summary>
58  public double ThematicBreakThickness { get; set; } = 2;
59 
60  /// <summary>
61  /// The font family for regular text.
62  /// </summary>
64 
65  /// <summary>
66  /// The font family for bold text.
67  /// </summary>
69 
70  /// <summary>
71  /// The font family for italic text.
72  /// </summary>
74 
75  /// <summary>
76  /// The font family for bold italic text.
77  /// </summary>
79 
80  /// <summary>
81  /// The font family for code elements.
82  /// </summary>
84 
85  /// <summary>
86  /// The font family for bold code elements.
87  /// </summary>
89 
90  /// <summary>
91  /// The font family for italic code elements.
92  /// </summary>
94 
95  /// <summary>
96  /// The font family for bold italic code elements.
97  /// </summary>
99 
100  /// <summary>
101  /// The thickness of underlines. This value will be multiplied by the font size of the element being underlined.
102  /// </summary>
103  public double UnderlineThickness { get; set; } = 0.075;
104 
105  /// <summary>
106  /// The thickness of underlines for bold text. This value will be multiplied by the font size of the element being underlined.
107  /// </summary>
108  public double BoldUnderlineThickness { get; set; } = 0.15;
109 
110  /// <summary>
111  /// The margins of the page.
112  /// </summary>
113  public Margins Margins { get; set; } = new Margins(55, 55, 55, 55);
114 
115  /// <summary>
116  /// The margins for table cells.
117  /// </summary>
118  public Margins TableCellMargins { get; set; } = new Margins(5, 0, 5, 0);
119 
120  /// <summary>
121  /// Defines the options for the vertical alignment of table cells.
122  /// </summary>
123  public enum VerticalAlignment
124  {
125  /// <summary>
126  /// Table cells will be aligned at the top of their row.
127  /// </summary>
128  Top,
129 
130  /// <summary>
131  /// Table cells will be aligned in the middle of their row.
132  /// </summary>
133  Middle,
134 
135  /// <summary>
136  /// Table cells will be aligned at the bottom of their row.
137  /// </summary>
138  Bottom
139  }
140 
141  /// <summary>
142  /// The vertical alignment of table cells.
143  /// </summary>
144  public VerticalAlignment TableVAlign { get; set; } = VerticalAlignment.Middle;
145 
146  /// <summary>
147  /// The size of the page.
148  /// </summary>
149  public Size PageSize { get; set; } = new Size(595, 842);
150 
151  /// <summary>
152  /// The space before each text paragraph. This value will be multiplied by the <see cref="BaseFontSize"/>.
153  /// </summary>
154  public double SpaceBeforeParagaph { get; set; } = 0;
155 
156  /// <summary>
157  /// The space after each text paragraph. This value will be multiplied by the <see cref="BaseFontSize"/>.
158  /// </summary>
159  public double SpaceAfterParagraph { get; set; } = 0.75;
160 
161  /// <summary>
162  /// The space after each line of text. This value will be multiplied by the <see cref="BaseFontSize"/>.
163  /// </summary>
164  public double SpaceAfterLine { get; set; } = 0.25;
165 
166  /// <summary>
167  /// The space before each heading. This value will be multiplied by the font size of the heading.
168  /// </summary>
169  public double SpaceBeforeHeading { get; set; } = 0.25;
170 
171  /// <summary>
172  /// The space after each heading. This value will be multiplied by the font size of the heading.
173  /// </summary>
174  public double SpaceAfterHeading { get; set; } = 0.25;
175 
176  /// <summary>
177  /// The margin at the left and right of code inlines. This value will be multiplied by the current font size.
178  /// </summary>
179  public double CodeInlineMargin { get; set; } = 0.25;
180 
181  /// <summary>
182  /// The indentation width used for list items.
183  /// </summary>
184  public double IndentWidth { get; set; } = 40;
185 
186  /// <summary>
187  /// The indentation width used for block quotes.
188  /// </summary>
189  public double QuoteBlockIndentWidth { get; set; } = 30;
190 
191  /// <summary>
192  /// The thickness of the bar to the left of block quotes.
193  /// </summary>
194  public double QuoteBlockBarWidth { get; set; } = 5;
195 
196  /// <summary>
197  /// The font size for subscripts and superscripts. This value will be multiplied by the current font size.
198  /// </summary>
199  public double SubSuperscriptFontSize { get; set; } = 0.7;
200 
201  /// <summary>
202  /// The upwards shift in the baseline for superscript elements. This value will be multiplied by the current font size.
203  /// </summary>
204  public double SuperscriptShift { get; set; } = 0.33;
205 
206  /// <summary>
207  /// The downwards shift in the baseline for subscript elements. This value will be multiplied by the current font size.
208  /// </summary>
209  public double SubscriptShift { get; set; } = 0.14;
210 
211  /// <summary>
212  /// The base uri for resolving relative image addresses.
213  /// </summary>
214  public string BaseImageUri { get; set; } = "";
215 
216  /// <summary>
217  /// A method used to resolve (possibly remote) image uris into local file paths. The first argument of the method should be the image uri and the second argument the base uri used to resolve relative links. The method should return a tuple containing the path of the local file and a boolean value indicating whether the file has been fetched from a remote location and should be deleted after the program has finished using it.
218  /// </summary>
219  public Func<string, string, (string, bool)> ImageUriResolver { get; set; } = HTTPUtils.ResolveImageURI;
220 
221  /// <summary>
222  /// The base uri for resolving links.
223  /// </summary>
224  public Uri BaseLinkUri { get; set; } = new Uri("about:blank");
225 
226  /// <summary>
227  /// A method used to resolve link addresses. The argument of the method should be the absolute link, and the method should return the resolved address. This can be used to "redirect" links to a different target.
228  /// </summary>
229  public Func<string, string> LinkUriResolver { get; set; } = a => a;
230 
231  /// <summary>
232  /// A method used to a load raster image from a local file. The argument of the method should be the path of a local image file, and the method should return a RasterImage representing that file. For example, this can be achieved using the <c>RasterImageFile</c> class from the <c>VectSharp.MuPDFUtils</c> package. If this is <see langword="null" />, only SVG images will be included in the document.
233  /// </summary>
234  public Func<string, RasterImage> RasterImageLoader { get; set; } = null;
235 
236  /// <summary>
237  /// The size of images (as defined in the image's width and height attributes) will be multiplied by this value to determine the actual size of the image on the page. This has no effect on images without a width or height attribute.
238  /// </summary>
239  public double ImageUnitMultiplier { get; set; } = 0.60714;
240 
241  /// <summary>
242  /// The size of images will be multiplied by this value to determine the actual size of the image on the page. For images that have a width or height attribute, this will be applied in addition to the <see cref="ImageUnitMultiplier"/>. For images without width and height, only this multiplier will be applied.
243  /// </summary>
244  public double ImageMultiplier { get; set; } = 1;
245 
246  /// <summary>
247  /// The margin on the right of left-aligned images and on the left of right-aligned images.
248  /// </summary>
249  public double ImageSideMargin { get; set; } = 10;
250 
251  /// <summary>
252  /// Images will be allowed to extend into the page bottom margin area by this amount before triggering a page break. This should be smaller than the bottom margin, otherwise images risk being cut off by the page boundary.
253  /// </summary>
254  public double ImageMarginTolerance { get; set; } = 25;
255 
256  /// <summary>
257  /// A method used for syntax highlighting. The first argument should be the source code to highlight, while the second parameter is the name of the language to use for the highlight. The method should return a list of lists of <see cref="FormattedString"/>s, with each list of <see cref="FormattedString"/>s representing a line. For each code block, if the method returns <see langword="null" />, no syntax highlighting is used.
258  /// </summary>
259  public Func<string, string, List<List<FormattedString>>> SyntaxHighlighter { get; set; } = VectSharp.Markdown.SyntaxHighlighter.GetSyntaxHighlightedLines;
260 
261  /// <summary>
262  /// Bullet points used for unordered lists. Each element of this list corresponds to the bullet for each level of list indentation. If the list indentation is greater than the number of elements in this list, the bullet points will be reused cyclically.
263  /// Each element of this list is a method taking two arguments: the first is the <see cref="Graphics"/> object on which the bullet point should be drawn, while the second is the colour in which it should be painted. The method should draw the bullet point centered around the origin. The size of the bullet point will be multiplied by the current font size.
264  /// </summary>
265  public List<Action<Graphics, Colour>> Bullets { get; } = new List<Action<Graphics, Colour>>()
266  {
267  (graphics, colour) =>
268  {
269  graphics.FillPath(new GraphicsPath().Arc(-0.5, 0, 0.25, 0, 2 * Math.PI), colour);
270  },
271 
272  (graphics, colour) =>
273  {
274  graphics.StrokePath(new GraphicsPath().Arc(-0.5, 0, 0.25, 0, 2 * Math.PI), colour, 0.1);
275  },
276 
277  (graphics, colour) =>
278  {
279  graphics.StrokeRectangle(-0.75, -0.25, 0.5, 0.5, colour, 0.1);
280  },
281  };
282 
283  /// <summary>
284  /// The foreground colour for text elements.
285  /// </summary>
286  public Colour ForegroundColour { get; set; } = Colours.Black;
287 
288  /// <summary>
289  /// The background colour for the page.
290  /// </summary>
291  public Colour BackgroundColour { get; set; } = Colours.White;
292 
293  /// <summary>
294  /// The colour of the line below headers.
295  /// </summary>
296  public Colour HeaderLineColour { get; set; } = Colour.FromRgb(180, 180, 180);
297 
298  /// <summary>
299  /// The colour for thematic break lines.
300  /// </summary>
301  public Colour ThematicBreakLineColour { get; set; } = Colour.FromRgb(180, 180, 200);
302 
303  /// <summary>
304  /// The colour for hypertext links-
305  /// </summary>
306  public Colour LinkColour { get; set; } = Colour.FromRgb(25, 140, 191);
307 
308  /// <summary>
309  /// The background colour for code inlines.
310  /// </summary>
311  public Colour CodeInlineBackgroundColour { get; set; } = Colour.FromRgb(240, 240, 240);
312 
313  /// <summary>
314  /// The background colour for code blocks.
315  /// </summary>
316  public Colour CodeBlockBackgroundColour { get; set; } = Colour.FromRgb(240, 240, 245);
317 
318  /// <summary>
319  /// The colour for the bar to the left of block quotes.
320  /// </summary>
321  public Colour QuoteBlockBarColour { get; set; } = Colour.FromRgb(75, 152, 220);
322 
323  /// <summary>
324  /// The background colour for block quotes.
325  /// </summary>
326  public Colour QuoteBlockBackgroundColour { get; set; } = Colour.FromRgb(240, 240, 255);
327 
328  /// <summary>
329  /// The colour for text that has been styled as "inserted".
330  /// </summary>
331  public Colour InsertedColour { get; set; } = Colour.FromRgb(0, 158, 115);
332 
333  /// <summary>
334  /// The colour for text that has been styled as "marked".
335  /// </summary>
336  public Colour MarkedColour { get; set; } = Colour.FromRgb(213, 94, 0);
337 
338  /// <summary>
339  /// The colour for the line separating the table header row from normal rows.
340  /// </summary>
342 
343  /// <summary>
344  /// The colour for lines separating table rows from each other.
345  /// </summary>
346  public Colour TableRowSeparatorColour { get; set; } = Colour.FromRgb(180, 180, 180);
347 
348  /// <summary>
349  /// The thickness of the line separating the table header row from normal rows.
350  /// </summary>
351  public double TableHeaderRowSeparatorThickness { get; set; } = 2;
352 
353  /// <summary>
354  /// The thickness of lines separating table rows from each other.
355  /// </summary>
356  public double TableHeaderSeparatorThickness { get; set; } = 1;
357 
358  /// <summary>
359  /// The bullet used for unchecked task list items.
360  /// </summary>
361  public Graphics TaskListUncheckedBullet { get; set; } = new Func<Graphics>(() =>
362  {
363  Graphics tbr = new Graphics();
364 
365  GraphicsPath checkboxPath = new GraphicsPath().MoveTo(-0.7, -0.4).LineTo(-0.3, -0.4).Arc(-0.3, -0.2, 0.2, 3 * Math.PI / 2, 2 * Math.PI).LineTo(-0.1, 0.2).Arc(-0.3, 0.2, 0.2, 0, Math.PI / 2).LineTo(-0.7, 0.4).Arc(-0.7, 0.2, 0.2, Math.PI / 2, Math.PI).LineTo(-0.9, -0.2).Arc(-0.7, -0.2, 0.2, Math.PI, 3 * Math.PI / 2).Close();
366  tbr.FillPath(checkboxPath, Colour.FromRgb(240, 246, 249));
367  tbr.StrokePath(checkboxPath, Colour.FromRgb(0, 114, 178), 0.075);
368 
369  return tbr;
370  })();
371 
372 
373  /// <summary>
374  /// The bullet used for checked task list items.
375  /// </summary>
376  public Graphics TaskListCheckedBullet { get; set; } = new Func<Graphics>(() =>
377  {
378  Graphics tbr = new Graphics();
379 
380  GraphicsPath checkboxPath = new GraphicsPath().MoveTo(-0.7, -0.4).LineTo(-0.3, -0.4).Arc(-0.3, -0.2, 0.2, 3 * Math.PI / 2, 2 * Math.PI).LineTo(-0.1, 0.2).Arc(-0.3, 0.2, 0.2, 0, Math.PI / 2).LineTo(-0.7, 0.4).Arc(-0.7, 0.2, 0.2, Math.PI / 2, Math.PI).LineTo(-0.9, -0.2).Arc(-0.7, -0.2, 0.2, Math.PI, 3 * Math.PI / 2).Close();
381  tbr.FillPath(checkboxPath, Colour.FromRgb(240, 246, 249));
382  tbr.StrokePath(checkboxPath, Colour.FromRgb(0, 114, 178), 0.075);
383 
384  GraphicsPath tickpath = new GraphicsPath().MoveTo(-0.75, -0.1).LineTo(-0.5, 0.15).LineTo(-0.1, -0.4);
385 
386  tbr.StrokePath(new GraphicsPath().MoveTo(-0.5, 0.15).LineTo(-0.1, -0.4), Colour.FromRgb(240, 246, 249), 0.3, LineCaps.Round);
387  tbr.StrokePath(tickpath, Colour.FromRgb(0, 158, 115), 0.2, LineCaps.Round);
388 
389  return tbr;
390  })();
391 
392  /// <summary>
393  /// Determines whether page breaks should be treated as such in the source.
394  /// </summary>
395  public bool AllowPageBreak { get; set; } = true;
396 
397  internal MarkdownRenderer Clone()
398  {
399  return (MarkdownRenderer)this.MemberwiseClone();
400  }
401 
402  /// <summary>
403  /// Parses the supplied <paramref name="markdownSource"/> using all the supported extensions and renders the resulting document. Page breaks are disabled, and the document is rendered as a single page with the specified <paramref name="width"/>. The page will be cropped at the appropriate height to contain the entire document.
404  /// </summary>
405  /// <param name="markdownSource">The markdown source to parse.</param>
406  /// <param name="width">The width of the page.</param>
407  /// <param name="linkDestinations">When this method returns, this value will contain a dictionary used to associate graphic action tags to hyperlinks. This can be used to enable such links when rendering the <see cref="Page"/> to a file.</param>
408  /// <returns>A <see cref="Page"/> containing a rendering of the supplied markdown document.</returns>
409  public Page RenderSinglePage(string markdownSource, double width, out Dictionary<string, string> linkDestinations)
410  {
411  MarkdownDocument document = Markdig.Markdown.Parse(markdownSource, new MarkdownPipelineBuilder().UseGridTables().UsePipeTables().UseEmphasisExtras().UseGenericAttributes().UseAutoIdentifiers().UseAutoLinks().UseTaskLists().UseListExtras().UseCitations().UseMathematics().UseSmartyPants().Build());
412 
413  return this.RenderSinglePage(document, width, out linkDestinations);
414  }
415 
416  /// <summary>
417  /// Renders the supplied <paramref name="markdownDocument"/>. Page breaks are disabled, and the document is rendered as a single page with the specified <paramref name="width"/>. The page will be cropped at the appropriate height to contain the entire document.
418  /// </summary>
419  /// <param name="markdownDocument">The markdown document to render.</param>
420  /// <param name="width">The width of the page.</param>
421  /// <param name="linkDestinations">When this method returns, this value will contain a dictionary used to associate graphic action tags to hyperlinks. This can be used to enable such links when rendering the <see cref="Page"/> to a file.</param>
422  /// <returns>A <see cref="Page"/> containing a rendering of the supplied markdown document.</returns>
423  public Page RenderSinglePage(MarkdownDocument markdownDocument, double width, out Dictionary<string, string> linkDestinations)
424  {
425  Size prevPageSize = this.PageSize;
426  bool allowPageBreak = this.AllowPageBreak;
427 
428  this.PageSize = new Size(width, double.PositiveInfinity);
429  this.AllowPageBreak = false;
430 
431  Page pag = new Page(PageSize.Width, PageSize.Height) { Background = BackgroundColour };
432 
433  Graphics graphics = pag.Graphics;
434 
435  graphics.Save();
436 
437  graphics.Translate(Margins.Left, Margins.Top);
438 
439  void newPageAction(ref MarkdownContext mdContext, ref Graphics pageGraphics)
440  {
441 
442  }
443 
444  MarkdownContext context = new MarkdownContext()
445  {
447  Cursor = new Point(0, 0),
449  Underline = false,
450  Translation = new Point(this.Margins.Left, this.Margins.Top),
451  CurrentLine = null,
452  ListDepth = 0,
453  CurrentPage = pag
454  };
455 
456  int index = 0;
457  foreach (Block block in markdownDocument)
458  {
459  RenderBlock(block, ref context, ref graphics, newPageAction, index > 0, index < markdownDocument.Count - 1);
460  index++;
461  }
462 
463  if (context.CurrentLine != null)
464  {
465  context.CurrentLine.Render(ref graphics, ref context, newPageAction, this.PageSize.Height - this.Margins.Bottom - context.Translation.Y - context.MarginBottomRight.Y);
466  }
467 
468  graphics.Restore();
469 
470  pag.Crop(new Point(0, 0), new Size(width, context.BottomRight.Y + this.Margins.Bottom));
471 
472  linkDestinations = context.LinkDestinations;
473  return pag;
474  }
475 
476  /// <summary>
477  /// Parses the supplied <paramref name="markdownSource"/> using all the supported extensions and renders the resulting document. The <see cref="Document"/> produced consists of one or more pages of the size specified in the <see cref="PageSize"/> of the current instance.
478  /// </summary>
479  /// <param name="markdownSource">The markdown source to parse.</param>
480  /// <param name="linkDestinations">When this method returns, this value will contain a dictionary used to associate graphic action tags to hyperlinks. This can be used to enable such links when rendering the <see cref="Document"/> to a file.</param>
481  /// <returns>A <see cref="Document"/> containing a rendering of the supplied markdown document, consisting of one or more pages of the size specified in the <see cref="PageSize"/> of the current instance.</returns>
482  public Document Render(string markdownSource, out Dictionary<string, string> linkDestinations)
483  {
484  MarkdownDocument document = Markdig.Markdown.Parse(markdownSource, new MarkdownPipelineBuilder().UseGridTables().UsePipeTables().UseEmphasisExtras().UseGenericAttributes().UseAutoIdentifiers().UseAutoLinks().UseTaskLists().UseListExtras().UseCitations().UseMathematics().UseSmartyPants().Build());
485 
486  return this.Render(document, out linkDestinations);
487  }
488 
489  /// <summary>
490  /// Renders the supplied <paramref name="mardownDocument"/>. The <see cref="Document"/> produced consists of one or more pages of the size specified in the <see cref="PageSize"/> of the current instance.
491  /// </summary>
492  /// <param name="mardownDocument">The markdown document to render.</param>
493  /// <param name="linkDestinations">When this method returns, this value will contain a dictionary used to associate graphic action tags to hyperlinks. This can be used to enable such links when rendering the <see cref="Document"/> to a file.</param>
494  /// <returns>A <see cref="Document"/> containing a rendering of the supplied markdown document, consisting of one or more pages of the size specified in the <see cref="PageSize"/> of the current instance.</returns>
495  public Document Render(MarkdownDocument mardownDocument, out Dictionary<string, string> linkDestinations)
496  {
497  Document doc = new Document();
498  Page pag = new Page(PageSize.Width, PageSize.Height) { Background = BackgroundColour };
499  doc.Pages.Add(pag);
500 
501  Graphics graphics = pag.Graphics;
502 
503  graphics.Translate(Margins.Left, Margins.Top);
504 
505  void newPageAction(ref MarkdownContext mdContext, ref Graphics pageGraphics)
506  {
507  Page newPag = new Page(PageSize.Width, PageSize.Height) { Background = BackgroundColour };
508  doc.Pages.Add(newPag);
509 
510  newPag.Graphics.Translate(mdContext.Translation);
511  mdContext.Cursor = new Point(0, 0);
512  mdContext.ForbiddenAreasLeft.Clear();
513  mdContext.ForbiddenAreasRight.Clear();
514 
515  pageGraphics = newPag.Graphics;
516  mdContext.CurrentPage = newPag;
517  }
518 
519  MarkdownContext context = new MarkdownContext()
520  {
522  Cursor = new Point(0, 0),
524  Underline = false,
525  Translation = new Point(Margins.Left, Margins.Top),
526  CurrentLine = null,
527  ListDepth = 0,
528  CurrentPage = pag
529  };
530 
531  int index = 0;
532 
533  foreach (Block block in mardownDocument)
534  {
535  RenderBlock(block, ref context, ref graphics, newPageAction, index > 0, index < mardownDocument.Count - 1);
536  index++;
537  }
538 
539  if (context.CurrentLine != null)
540  {
541  context.CurrentLine.Render(ref graphics, ref context, newPageAction, this.PageSize.Height - this.Margins.Bottom - context.Translation.Y - context.MarginBottomRight.Y);
542  }
543 
544  linkDestinations = context.LinkDestinations;
545 
546  return doc;
547  }
548 
549  private Document RenderSubDocument(ContainerBlock document, ref MarkdownContext context)
550  {
551  Document doc = new Document();
552  Page pag = new Page(PageSize.Width, PageSize.Height) { Background = BackgroundColour };
553  doc.Pages.Add(pag);
554 
555  Graphics graphics = pag.Graphics;
556 
557  graphics.Save();
558 
559  graphics.Translate(Margins.Left, Margins.Top);
560 
561  void newPageAction(ref MarkdownContext mdContext, ref Graphics pageGraphics)
562  {
563  pageGraphics.Restore();
564  Page newPag = new Page(PageSize.Width, PageSize.Height) { Background = BackgroundColour };
565  doc.Pages.Add(newPag);
566 
567  newPag.Graphics.Save();
568  newPag.Graphics.Translate(mdContext.Translation);
569  mdContext.Cursor = new Point(0, 0);
570  mdContext.ForbiddenAreasLeft.Clear();
571  mdContext.ForbiddenAreasRight.Clear();
572 
573  pageGraphics = newPag.Graphics;
574  mdContext.CurrentPage = newPag;
575  }
576 
577  context.Translation = new Point(context.Translation.X + Margins.Left, context.Translation.Y + Margins.Top);
578  context.CurrentPage = pag;
579  context.ListDepth = 0;
580 
581  int index = 0;
582  foreach (Block block in document)
583  {
584  RenderBlock(block, ref context, ref graphics, newPageAction, index > 0, index < document.Count - 1);
585  index++;
586  }
587 
588  if (context.CurrentLine != null)
589  {
590  context.CurrentLine.Render(ref graphics, ref context, newPageAction, this.PageSize.Height - this.Margins.Bottom - context.Translation.Y - context.MarginBottomRight.Y);
591  }
592 
593  graphics.Restore();
594 
595  return doc;
596  }
597 
598  internal delegate void NewPageAction(ref MarkdownContext context, ref Graphics graphics);
599 
600  private void RenderBlock(Block block, ref MarkdownContext context, ref Graphics graphics, NewPageAction newPageAction, bool spaceBefore, bool spaceAfter)
601  {
602  HtmlAttributes attributes = block.TryGetAttributes();
603 
604  if (attributes != null && !string.IsNullOrEmpty(attributes.Id))
605  {
606  Point cursor = context.Cursor;
607  NewPageAction reversibleNewPageAction = (ref MarkdownContext currContext, ref Graphics currGraphics) =>
608  {
609  newPageAction(ref currContext, ref currGraphics);
610  cursor = currContext.Cursor;
611  };
612 
613  RenderHTMLBlock("<a name=\"" + attributes.Id + "\"></a>", false, ref context, ref graphics, reversibleNewPageAction, spaceBefore, spaceAfter);
614  context.Cursor = cursor;
615  }
616 
617  if (block is LeafBlock leaf)
618  {
619  if (leaf is HeadingBlock heading)
620  {
621  RenderHeadingBlock(heading, ref context, ref graphics, newPageAction, spaceBefore, spaceAfter);
622  }
623  else if (leaf is ParagraphBlock paragraph)
624  {
625  RenderParagraphBlock(paragraph, ref context, ref graphics, newPageAction, spaceBefore, spaceAfter);
626  }
627  else if (leaf is CodeBlock code)
628  {
629  if (block is Markdig.Extensions.Mathematics.MathBlock math)
630  {
631  StringBuilder mathBuilder = new StringBuilder();
632  foreach (StringLine line in math.Lines)
633  {
634  mathBuilder.Append(line.ToString());
635  mathBuilder.Append("\n");
636  }
637 
638  string imageUri = "https://render.githubusercontent.com/render/math?math=" + System.Web.HttpUtility.UrlEncode(mathBuilder.ToString());
639 
640  RenderHTMLBlock("<img src=\"" + imageUri + "\">", false, ref context, ref graphics, newPageAction, true, true);
641  }
642  else if (leaf is FencedCodeBlock fenced)
643  {
644  if (!string.IsNullOrEmpty(fenced.Info))
645  {
646  RenderFencedCodeBlock(fenced, ref context, ref graphics, newPageAction, spaceBefore, spaceAfter);
647  }
648  else
649  {
650  RenderCodeBlock(code, ref context, ref graphics, newPageAction, spaceBefore, spaceAfter);
651  }
652 
653  }
654  else
655  {
656  RenderCodeBlock(code, ref context, ref graphics, newPageAction, spaceBefore, spaceAfter);
657  }
658  }
659  else if (leaf is HtmlBlock html)
660  {
661  RenderHTMLBlock(html.Lines.ToString(), false, ref context, ref graphics, newPageAction, spaceBefore, spaceAfter);
662  }
663  else if (leaf is ThematicBreakBlock thematicBreak)
664  {
665  RenderThematicBreakBlock(thematicBreak, ref context, ref graphics, newPageAction, spaceBefore, spaceAfter);
666  }
667  else if (leaf is LinkReferenceDefinition link)
668  {
669  // Nothing to do (the links are correctly referenced by the parser)
670  }
671 
672  }
673  else if (block is ContainerBlock)
674  {
675  if (block is ListBlock list)
676  {
677  RenderListBlock(list, ref context, ref graphics, newPageAction);
678  }
679  else if (block is ListItemBlock listItem)
680  {
681  RenderListItemBlock(listItem, ref context, ref graphics, newPageAction);
682  }
683  else if (block is QuoteBlock quote)
684  {
685  RenderQuoteBlock(quote, ref context, ref graphics, newPageAction);
686  }
687  else if (block is LinkReferenceDefinitionGroup linkGroup)
688  {
689  // Nothing to render here
690  }
691  else if (block is Table table)
692  {
693  RenderTable(table, ref context, ref graphics, newPageAction);
694  }
695  }
696  else if (block is BlankLineBlock)
697  {
698  // Nothing to render here
699  }
700  }
701 
702  private void RenderHeadingBlock(HeadingBlock heading, ref MarkdownContext context, ref Graphics graphics, NewPageAction newPageAction, bool spaceBefore, bool spaceAfter)
703  {
704  MarkdownContext prevContext = context.Clone();
705 
706  context.Font = new Font(this.RegularFontFamily, BaseFontSize * HeaderFontSizeMultipliers[heading.Level - 1]);
707 
708  double minX = context.GetMinX(context.Cursor.Y + SpaceBeforeHeading * context.Font.FontSize, context.Cursor.Y + context.Font.Ascent + SpaceBeforeHeading * context.Font.FontSize - context.Font.Descent);
709 
710  if (spaceBefore)
711  {
712  context.Cursor = new Point(minX, context.Cursor.Y + context.Font.Ascent + SpaceBeforeHeading * context.Font.FontSize);
713  }
714  else
715  {
716  context.Cursor = new Point(minX, context.Cursor.Y + context.Font.Ascent);
717  }
718 
719  if (context.CurrentLine == null)
720  {
721  context.CurrentLine = new Line(context.Font.Ascent);
722  }
723  else
724  {
725  double delta = context.Cursor.Y - (prevContext.Cursor.Y + prevContext.Font.Ascent + SpaceBeforeParagaph * prevContext.Font.FontSize);
726 
727  for (int i = 0; i < context.CurrentLine.Fragments.Count; i++)
728  {
729  context.CurrentLine.Fragments[i].Translate(0, delta);
730  }
731  }
732 
733  foreach (Inline inline in heading.Inline)
734  {
735  RenderInline(inline, ref context, ref graphics, newPageAction);
736  }
737 
738  if (this.HeaderLineThicknesses[heading.Level - 1] > 0)
739  {
740  double lineY = context.Cursor.Y + context.Font.FontSize * 0.3;
741  context.CurrentLine.Fragments.Add(new UnderlineFragment(new Point(minX, context.Cursor.Y + context.Font.FontSize * 0.3), new Point(context.GetMaxX(lineY, PageSize.Width - Margins.Right - context.Translation.X), lineY), this.HeaderLineColour, this.HeaderLineThicknesses[heading.Level - 1], context.Tag));
742  }
743 
744  context.CurrentLine.Render(ref graphics, ref context, newPageAction, this.PageSize.Height - this.Margins.Bottom - context.Translation.Y - context.MarginBottomRight.Y);
745  context.CurrentLine = null;
746 
747  if (this.HeaderLineThicknesses[heading.Level - 1] > 0)
748  {
749  double lineY = context.Cursor.Y + context.Font.FontSize * 0.3;
750  context.Cursor = new Point(context.Cursor.X, lineY + this.HeaderLineThicknesses[heading.Level - 1]);
751  }
752 
753  context.Cursor = new Point(0, context.Cursor.Y - context.Font.Descent + SpaceAfterLine * context.Font.FontSize);
754 
755  if (spaceAfter)
756  {
757  context.Cursor = new Point(0, context.Cursor.Y + SpaceAfterHeading * context.Font.FontSize);
758  }
759 
760  prevContext.Cursor = context.Cursor;
761  prevContext.BottomRight = context.BottomRight;
762  prevContext.CurrentPage = context.CurrentPage;
763  prevContext.CurrentLine = context.CurrentLine;
764 
765  context = prevContext;
766  }
767 
768  private void RenderParagraphBlock(ParagraphBlock paragraph, ref MarkdownContext context, ref Graphics graphics, NewPageAction newPageAction, bool spaceBefore, bool spaceAfter)
769  {
770  double minX = context.GetMinX(context.Cursor.Y + SpaceBeforeParagaph * context.Font.FontSize, context.Cursor.Y + context.Font.Ascent + SpaceBeforeParagaph * context.Font.FontSize - context.Font.Descent);
771 
772  if (spaceBefore)
773  {
774  context.Cursor = new Point(minX, context.Cursor.Y + context.Font.Ascent + SpaceBeforeParagaph * context.Font.FontSize);
775  }
776  else
777  {
778  context.Cursor = new Point(minX, context.Cursor.Y + context.Font.Ascent);
779  }
780 
781  if (context.CurrentLine == null)
782  {
783  context.CurrentLine = new Line(context.Font.Ascent);
784  }
785 
786  foreach (Inline inline in paragraph.Inline)
787  {
788  RenderInline(inline, ref context, ref graphics, newPageAction);
789  }
790 
791  context.CurrentLine.Render(ref graphics, ref context, newPageAction, this.PageSize.Height - this.Margins.Bottom - context.Translation.Y - context.MarginBottomRight.Y);
792  context.CurrentLine = null;
793 
794  context.Cursor = new Point(0, context.Cursor.Y - context.Font.Descent + SpaceAfterLine * context.Font.FontSize);
795 
796  if (spaceAfter)
797  {
798  context.Cursor = new Point(0, context.Cursor.Y + SpaceAfterParagraph * context.Font.FontSize);
799  }
800  }
801 
802  private void RenderFencedCodeBlock(FencedCodeBlock codeBlock, ref MarkdownContext context, ref Graphics graphics, NewPageAction newPageAction, bool spaceBefore, bool spaceAfter)
803  {
804  string info = codeBlock.Info;
805 
806  if (string.IsNullOrEmpty(info) || codeBlock.Lines.Count == 0)
807  {
808  RenderCodeBlock(codeBlock, ref context, ref graphics, newPageAction, spaceBefore, spaceAfter);
809  return;
810  }
811 
812  StringBuilder code = new StringBuilder();
813 
814  foreach (StringLine line in codeBlock.Lines)
815  {
816  code.Append(line.ToString());
817  code.Append('\n');
818  }
819 
820  List<List<FormattedString>> lines = this.SyntaxHighlighter(code.ToString(0, code.Length - 1), info);
821 
822  if (lines == null)
823  {
824  RenderCodeBlock(codeBlock, ref context, ref graphics, newPageAction, spaceBefore, spaceAfter);
825  return;
826  }
827 
828  MarkdownContext prevContext = context.Clone();
829 
830  context.Font = new Font(this.CodeFont, context.Font.FontSize);
831 
832  if (spaceBefore)
833  {
834  context.Cursor = new Point(0, context.Cursor.Y + SpaceBeforeParagaph * context.Font.FontSize);
835  }
836 
837  int index = 0;
838 
839  if (codeBlock.Lines.Count > 0)
840  {
841  foreach (List<FormattedString> line in lines)
842  {
843  if (index < codeBlock.Lines.Count)
844  {
845  if (context.CurrentLine == null)
846  {
847  context.CurrentLine = new Line(context.Font.Ascent);
848  }
849 
850  double maxX = context.GetMaxX(context.Cursor.Y - context.Font.Ascent, context.Cursor.Y - context.Font.Descent, this.PageSize.Width - this.Margins.Right - context.Translation.X - context.MarginBottomRight.X);
851 
852  double minX = context.GetMinX(context.Cursor.Y - context.Font.Ascent, context.Cursor.Y - context.Font.Descent);
853 
854  context.Cursor = new Point(minX + context.Font.FontSize, context.Cursor.Y + context.Font.YMax);
855 
856  if (index == 0)
857  {
858  context.CurrentLine.Fragments.Insert(0, new RectangleFragment(new Point(minX, context.Cursor.Y - context.Font.YMax - this.SpaceAfterLine * context.Font.FontSize), new Size(maxX - minX, this.SpaceAfterLine * context.Font.FontSize * 2), CodeBlockBackgroundColour, context.Tag));
859  }
860 
861  foreach (FormattedString item in line)
862  {
863  context.Colour = item.Colour;
864 
865  if (!item.IsBold && !item.IsItalic)
866  {
867  context.Font = new Font(this.CodeFont, context.Font.FontSize);
868  }
869  else if (item.IsBold && !item.IsItalic)
870  {
871  context.Font = new Font(this.CodeFontBold, context.Font.FontSize);
872  }
873  else if (item.IsBold && item.IsItalic)
874  {
875  context.Font = new Font(this.CodeFontBoldItalic, context.Font.FontSize);
876  }
877  else if (!item.IsBold && item.IsItalic)
878  {
879  context.Font = new Font(this.CodeFontItalic, context.Font.FontSize);
880  }
881 
882  RenderCodeBlockLine(item.Text, ref context, ref graphics, newPageAction);
883  }
884 
885 
886  context.Colour = Colours.Black;
887 
888  context.CurrentLine.Render(ref graphics, ref context, newPageAction, this.PageSize.Height - this.Margins.Bottom - context.Translation.Y - context.MarginBottomRight.Y);
889  context.CurrentLine = null;
890 
891  context.Cursor = new Point(0, context.Cursor.Y - context.Font.YMin);
892  index++;
893  }
894  else
895  {
896  break;
897  }
898  }
899  }
900 
901  context.Cursor = new Point(0, context.Cursor.Y + SpaceAfterLine * context.Font.FontSize);
902 
903  if (spaceAfter)
904  {
905  context.Cursor = new Point(0, context.Cursor.Y + SpaceAfterParagraph * context.Font.FontSize);
906  }
907 
908  prevContext.Cursor = context.Cursor;
909  prevContext.BottomRight = context.BottomRight;
910  prevContext.CurrentPage = context.CurrentPage;
911  prevContext.CurrentLine = context.CurrentLine;
912 
913  context = prevContext;
914  }
915 
916 
917  private void RenderCodeBlock(CodeBlock codeBlock, ref MarkdownContext context, ref Graphics graphics, NewPageAction newPageAction, bool spaceBefore, bool spaceAfter)
918  {
919  MarkdownContext prevContext = context.Clone();
920 
921  context.Font = new Font(this.CodeFont, context.Font.FontSize);
922 
923  if (spaceBefore)
924  {
925  context.Cursor = new Point(0, context.Cursor.Y + SpaceBeforeParagaph * context.Font.FontSize);
926  }
927 
928  int index = 0;
929 
930  if (codeBlock.Lines.Count > 0)
931  {
932  foreach (StringLine line in codeBlock.Lines)
933  {
934  if (index < codeBlock.Lines.Count)
935  {
936  if (context.CurrentLine == null)
937  {
938  context.CurrentLine = new Line(context.Font.Ascent);
939  }
940 
941  double maxX = context.GetMaxX(context.Cursor.Y - context.Font.Ascent, context.Cursor.Y - context.Font.Descent, this.PageSize.Width - this.Margins.Right - context.Translation.X - context.MarginBottomRight.X);
942 
943  double minX = context.GetMinX(context.Cursor.Y - context.Font.Ascent, context.Cursor.Y - context.Font.Descent);
944 
945  context.Cursor = new Point(minX + context.Font.FontSize, context.Cursor.Y + context.Font.YMax);
946 
947  if (index == 0)
948  {
949  context.CurrentLine.Fragments.Insert(0, new RectangleFragment(new Point(minX, context.Cursor.Y - context.Font.YMax - this.SpaceAfterLine * context.Font.FontSize), new Size(maxX - minX, this.SpaceAfterLine * context.Font.FontSize * 2), CodeBlockBackgroundColour, context.Tag));
950  }
951 
952 
953  RenderCodeBlockLine(line.ToString(), ref context, ref graphics, newPageAction);
954 
955 
956  context.Colour = Colours.Black;
957 
958  context.CurrentLine.Render(ref graphics, ref context, newPageAction, this.PageSize.Height - this.Margins.Bottom - context.Translation.Y - context.MarginBottomRight.Y);
959  context.CurrentLine = null;
960 
961  context.Cursor = new Point(0, context.Cursor.Y - context.Font.YMin);
962  index++;
963  }
964  else
965  {
966  break;
967  }
968  }
969  }
970 
971  context.Cursor = new Point(0, context.Cursor.Y + SpaceAfterLine * context.Font.FontSize);
972 
973  if (spaceAfter)
974  {
975  context.Cursor = new Point(0, context.Cursor.Y + SpaceAfterParagraph * context.Font.FontSize);
976  }
977 
978  prevContext.Cursor = context.Cursor;
979  prevContext.BottomRight = context.BottomRight;
980  prevContext.CurrentPage = context.CurrentPage;
981  prevContext.CurrentLine = context.CurrentLine;
982 
983  context = prevContext;
984  }
985 
986  private void RenderThematicBreakBlock(ThematicBreakBlock thematicBreak, ref MarkdownContext context, ref Graphics graphics, NewPageAction newPageAction, bool spaceBefore, bool spaceAfter)
987  {
988  if (context.CurrentLine == null)
989  {
990  context.CurrentLine = new Line(0);
991  }
992 
993  if (spaceBefore)
994  {
995  context.Cursor = new Point(context.Cursor.X, context.Cursor.Y + SpaceBeforeParagaph * context.Font.FontSize + this.ThematicBreakThickness * 0.5);
996  }
997  else
998  {
999  context.Cursor = new Point(context.Cursor.X, context.Cursor.Y + this.ThematicBreakThickness * 0.5);
1000  }
1001 
1002 
1003  double maxX = context.GetMaxX(context.Cursor.Y, this.PageSize.Width - this.Margins.Right - context.Translation.X - context.MarginBottomRight.X);
1004  double minX = context.GetMinX(context.Cursor.Y);
1005 
1006  context.CurrentLine.Fragments.Add(new UnderlineFragment(new Point(minX, context.Cursor.Y), new Point(maxX, context.Cursor.Y), this.ThematicBreakLineColour, this.ThematicBreakThickness, context.Tag));
1007 
1008  context.CurrentLine.Render(ref graphics, ref context, newPageAction, this.PageSize.Height - this.Margins.Bottom - context.Translation.Y - context.MarginBottomRight.Y);
1009  context.CurrentLine = null;
1010 
1011  if (spaceAfter)
1012  {
1013  context.Cursor = new Point(context.Cursor.X, context.Cursor.Y + SpaceAfterParagraph * context.Font.FontSize + this.ThematicBreakThickness * 0.5);
1014  }
1015  else
1016  {
1017  context.Cursor = new Point(context.Cursor.X, context.Cursor.Y + this.ThematicBreakThickness * 0.5);
1018  }
1019  }
1020 
1021  private void RenderInline(Inline inline, ref MarkdownContext context, ref Graphics graphics, NewPageAction newPageAction)
1022  {
1023  HtmlAttributes attributes = inline.TryGetAttributes();
1024 
1025  if (attributes != null && !string.IsNullOrEmpty(attributes.Id))
1026  {
1027  Point cursor = context.Cursor;
1028  RenderHTMLBlock("<a name=\"" + attributes.Id + "\"></a>", true, ref context, ref graphics, newPageAction, false, false);
1029  context.Cursor = cursor;
1030  }
1031 
1032  if (inline is LeafInline)
1033  {
1034  if (inline is AutolinkInline autoLink)
1035  {
1036  LinkInline link = new LinkInline((autoLink.IsEmail ? "mailto:" : "") + autoLink.Url, "");
1037  link.AppendChild(new LiteralInline(autoLink.Url));
1038 
1039  RenderLinkInline(link, ref context, ref graphics, newPageAction);
1040  }
1041  else if (inline is CodeInline code)
1042  {
1043  RenderCodeInline(code, ref context, ref graphics, newPageAction);
1044  }
1045  else if (inline is HtmlEntityInline htmlEntity)
1046  {
1047  RenderLiteralInline(new LiteralInline(htmlEntity.Transcoded), ref context, ref graphics, newPageAction);
1048  }
1049  else if (inline is HtmlInline html)
1050  {
1051  RenderHTMLBlock(html.Tag, true, ref context, ref graphics, newPageAction, true, true);
1052  }
1053  else if (inline is LineBreakInline lineBreak)
1054  {
1055  RenderLineBreakInline(lineBreak.IsHard, false, ref context, ref graphics, newPageAction);
1056  }
1057  else if (inline is LiteralInline literal)
1058  {
1059  RenderLiteralInline(literal, ref context, ref graphics, newPageAction);
1060  }
1061  else if (inline is Markdig.Extensions.Mathematics.MathInline math)
1062  {
1063  string imageUri = "https://render.githubusercontent.com/render/math?math=" + System.Web.HttpUtility.UrlEncode(math.Content.ToString());
1064 
1065  RenderHTMLBlock("<img src=\"" + imageUri + "\">", true, ref context, ref graphics, newPageAction, true, true);
1066  }
1067  else if (inline is Markdig.Extensions.SmartyPants.SmartyPant smartyPant)
1068  {
1069  switch (smartyPant.Type)
1070  {
1071  case Markdig.Extensions.SmartyPants.SmartyPantType.LeftDoubleQuote:
1072  RenderLiteralInline(new LiteralInline("“"), ref context, ref graphics, newPageAction);
1073  break;
1074  case Markdig.Extensions.SmartyPants.SmartyPantType.RightDoubleQuote:
1075  RenderLiteralInline(new LiteralInline("”"), ref context, ref graphics, newPageAction);
1076  break;
1077  case Markdig.Extensions.SmartyPants.SmartyPantType.LeftQuote:
1078  RenderLiteralInline(new LiteralInline("‘"), ref context, ref graphics, newPageAction);
1079  break;
1080  case Markdig.Extensions.SmartyPants.SmartyPantType.RightQuote:
1081  RenderLiteralInline(new LiteralInline("’"), ref context, ref graphics, newPageAction);
1082  break;
1083  case Markdig.Extensions.SmartyPants.SmartyPantType.Dash2:
1084  RenderLiteralInline(new LiteralInline("–"), ref context, ref graphics, newPageAction);
1085  break;
1086  case Markdig.Extensions.SmartyPants.SmartyPantType.Dash3:
1087  RenderLiteralInline(new LiteralInline("—"), ref context, ref graphics, newPageAction);
1088  break;
1089  case Markdig.Extensions.SmartyPants.SmartyPantType.DoubleQuote:
1090  RenderLiteralInline(new LiteralInline("\""), ref context, ref graphics, newPageAction);
1091  break;
1092  case Markdig.Extensions.SmartyPants.SmartyPantType.Ellipsis:
1093  RenderLiteralInline(new LiteralInline("…"), ref context, ref graphics, newPageAction);
1094  break;
1095  case Markdig.Extensions.SmartyPants.SmartyPantType.LeftAngleQuote:
1096  RenderLiteralInline(new LiteralInline("«"), ref context, ref graphics, newPageAction);
1097  break;
1098  case Markdig.Extensions.SmartyPants.SmartyPantType.Quote:
1099  RenderLiteralInline(new LiteralInline("'"), ref context, ref graphics, newPageAction);
1100  break;
1101  case Markdig.Extensions.SmartyPants.SmartyPantType.RightAngleQuote:
1102  RenderLiteralInline(new LiteralInline("»"), ref context, ref graphics, newPageAction);
1103  break;
1104  }
1105  }
1106  else if (inline is Markdig.Extensions.TaskLists.TaskList)
1107  {
1108  // Nothing to render here (the checkbox has already been rendered)
1109  }
1110  }
1111  else if (inline is ContainerInline)
1112  {
1113  if (inline is DelimiterInline)
1114  {
1115  // Nothing to render here
1116  }
1117  else if (inline is EmphasisInline emphasis)
1118  {
1119  RenderEmphasisInline(emphasis, ref context, ref graphics, newPageAction);
1120  }
1121  else if (inline is LinkInline link)
1122  {
1123  RenderLinkInline(link, ref context, ref graphics, newPageAction);
1124  }
1125  }
1126  }
1127 
1128  private void RenderLineBreakInline(bool isHard, bool isPageBreak, ref MarkdownContext context, ref Graphics graphics, NewPageAction newPageAction)
1129  {
1130  if (isHard)
1131  {
1132  context.CurrentLine.Render(ref graphics, ref context, newPageAction, this.PageSize.Height - this.Margins.Bottom - context.Translation.Y - context.MarginBottomRight.Y);
1133  context.CurrentLine = new Line(context.Font.Ascent);
1134  context.Cursor = new Point(0, context.Cursor.Y - context.Font.Descent + SpaceAfterLine * context.Font.FontSize + context.Font.Ascent);
1135 
1136  double minX = context.GetMinX(context.Cursor.Y - context.Font.Ascent, context.Cursor.Y - context.Font.Descent);
1137 
1138  context.Cursor = new Point(minX, context.Cursor.Y);
1139 
1140  if (isPageBreak)
1141  {
1142  newPageAction(ref context, ref graphics);
1143  }
1144  }
1145  else
1146  {
1147  double spaceWidth = context.Font.FontFamily.TrueTypeFile.Get1000EmGlyphWidth(' ') / 1000.0 * context.Font.FontSize;
1148  context.Cursor = new Point(context.Cursor.X + spaceWidth, context.Cursor.Y);
1149  }
1150  }
1151 
1152  private void RenderLiteralInline(LiteralInline literal, ref MarkdownContext context, ref Graphics graphics, NewPageAction newPageAction)
1153  {
1154  string text = literal.Content.ToString();
1155 
1156  double spaceWidth = context.Font.FontFamily.TrueTypeFile.Get1000EmGlyphWidth(' ') / 1000.0 * context.Font.FontSize;
1157 
1158  List<Word> words = Word.GetWords(text, context.Font, this.PageSize.Width - this.Margins.Right - context.Translation.X - context.MarginBottomRight.X).ToList();
1159 
1160  double underlineStart = context.Cursor.X;
1161  double underlineEnd = context.Cursor.X;
1162 
1163  double currLineMaxX = context.GetMaxX(context.Cursor.Y - context.Font.Ascent, context.Cursor.Y - context.Font.Descent, this.PageSize.Width - this.Margins.Right - context.Translation.X - context.MarginBottomRight.X);
1164 
1165  bool ignoreNextWhitespace = false;
1166 
1167  bool broken = false;
1168 
1169  for (int i = 0; i < words.Count; i++)
1170  {
1171  Word w = words[i];
1172 
1173  if (!string.IsNullOrEmpty(w.Text))
1174  {
1175  if (!ignoreNextWhitespace)
1176  {
1177  context.Cursor = new Point(context.Cursor.X + spaceWidth * w.WhitespaceCount * (w.PrecedingWhitespace == '\t' ? 4 : 1), context.Cursor.Y);
1178  }
1179  else
1180  {
1181  ignoreNextWhitespace = false;
1182  }
1183 
1184  Font.DetailedFontMetrics wordMetrics = w.Metrics;
1185 
1186  double finalX = context.Cursor.X + wordMetrics.Width + wordMetrics.RightSideBearing + wordMetrics.LeftSideBearing;
1187 
1188  if (finalX <= currLineMaxX || broken)
1189  {
1190  context.CurrentLine.Fragments.Add(new TextFragment(new Point(context.Cursor.X + wordMetrics.LeftSideBearing, context.Cursor.Y), w.Text, context.Font, context.Colour, context.Tag));
1191  context.Cursor = new Point(context.Cursor.X + wordMetrics.Width + wordMetrics.RightSideBearing + wordMetrics.LeftSideBearing, context.Cursor.Y);
1192 
1193  broken = false;
1194 
1195  if (context.Underline || context.StrikeThrough)
1196  {
1197  underlineEnd = context.Cursor.X;
1198  }
1199  }
1200  else
1201  {
1202  if (context.Underline && underlineStart != underlineEnd)
1203  {
1204  context.CurrentLine.Fragments.Add(new UnderlineFragment(new Point(underlineStart, context.Cursor.Y + context.Font.FontSize * 0.2), new Point(underlineEnd, context.Cursor.Y + context.Font.FontSize * 0.2), context.Colour, context.Font.FontSize * (context.Font.FontFamily.IsBold ? this.BoldUnderlineThickness : this.UnderlineThickness), context.Tag));
1205  }
1206  else if (context.StrikeThrough && underlineStart != underlineEnd)
1207  {
1208  context.CurrentLine.Fragments.Add(new UnderlineFragment(new Point(underlineStart, context.Cursor.Y - context.Font.Ascent * 0.5 - context.Font.Descent * 0.5), new Point(underlineEnd, context.Cursor.Y - context.Font.Ascent * 0.5 - context.Font.Descent * 0.5), context.Colour, context.Font.FontSize * (context.Font.FontFamily.IsBold ? this.BoldUnderlineThickness : this.UnderlineThickness), context.Tag));
1209  }
1210 
1211  context.CurrentLine.Render(ref graphics, ref context, newPageAction, this.PageSize.Height - this.Margins.Bottom - context.Translation.Y - context.MarginBottomRight.Y);
1212 
1213 
1214 
1215  context.CurrentLine = new Line(context.Font.Ascent);
1216  context.Cursor = new Point(0, context.Cursor.Y - context.Font.Descent + SpaceAfterLine * context.Font.FontSize + context.Font.Ascent);
1217  currLineMaxX = context.GetMaxX(context.Cursor.Y - context.Font.Ascent, context.Cursor.Y - context.Font.Descent, this.PageSize.Width - this.Margins.Right - context.Translation.X - context.MarginBottomRight.X);
1218 
1219  double minX = context.GetMinX(context.Cursor.Y - context.Font.Ascent, context.Cursor.Y - context.Font.Descent);
1220 
1221  context.Cursor = new Point(minX, context.Cursor.Y);
1222 
1223  underlineStart = minX;
1224  underlineEnd = minX;
1225 
1226  i--;
1227  ignoreNextWhitespace = true;
1228  broken = true;
1229  }
1230  }
1231  else
1232  {
1233  context.Cursor = new Point(context.Cursor.X + spaceWidth * w.WhitespaceCount * (w.PrecedingWhitespace == '\t' ? 4 : 1), context.Cursor.Y);
1234  }
1235  }
1236 
1237  if (context.Underline && underlineStart != underlineEnd)
1238  {
1239  context.CurrentLine.Fragments.Add(new UnderlineFragment(new Point(underlineStart, context.Cursor.Y + context.Font.FontSize * 0.2), new Point(underlineEnd, context.Cursor.Y + context.Font.FontSize * 0.2), context.Colour, context.Font.FontSize * (context.Font.FontFamily.IsBold ? this.BoldUnderlineThickness : this.UnderlineThickness), context.Tag));
1240  }
1241  else if (context.StrikeThrough && underlineStart != underlineEnd)
1242  {
1243  context.CurrentLine.Fragments.Add(new UnderlineFragment(new Point(underlineStart, context.Cursor.Y - context.Font.Ascent * 0.5 - context.Font.Descent * 0.5), new Point(underlineEnd, context.Cursor.Y - context.Font.Ascent * 0.5 - context.Font.Descent * 0.5), context.Colour, context.Font.FontSize * (context.Font.FontFamily.IsBold ? this.BoldUnderlineThickness : this.UnderlineThickness), context.Tag));
1244  }
1245  }
1246 
1247  private void RenderCodeInline(CodeInline code, ref MarkdownContext context, ref Graphics graphics, NewPageAction newPageAction)
1248  {
1249  MarkdownContext prevContext = context.Clone();
1250 
1251  context.Font = new Font(this.CodeFont, context.Font.FontSize);
1252 
1253  double spaceWidth = context.Font.FontFamily.TrueTypeFile.Get1000EmGlyphWidth(' ') / 1000.0 * context.Font.FontSize;
1254 
1255  string text = code.Content.ToString();
1256  List<Word> words = Word.GetWords(text, context.Font, this.PageSize.Width - this.Margins.Right - context.Translation.X - context.MarginBottomRight.X).ToList();
1257 
1258  double currLineMaxX = context.GetMaxX(context.Cursor.Y - context.Font.Ascent, context.Cursor.Y - context.Font.Descent, this.PageSize.Width - this.Margins.Right - context.Translation.X - context.MarginBottomRight.X);
1259 
1260  context.Cursor = new Point(context.Cursor.X + this.CodeInlineMargin * context.Font.FontSize, context.Cursor.Y);
1261 
1262 
1263  double underlineStart = context.Cursor.X;
1264  double underlineEnd = context.Cursor.X;
1265 
1266  bool broken = false;
1267 
1268  for (int i = 0; i < words.Count; i++)
1269  {
1270  Word w = words[i];
1271 
1272  if (!string.IsNullOrEmpty(w.Text))
1273  {
1274  context.Cursor = new Point(context.Cursor.X + spaceWidth * w.WhitespaceCount * (w.PrecedingWhitespace == '\t' ? 4 : 1), context.Cursor.Y);
1275 
1276  Font.DetailedFontMetrics wordMetrics = w.Metrics;
1277 
1278  double finalX = context.Cursor.X + wordMetrics.Width + wordMetrics.RightSideBearing + wordMetrics.LeftSideBearing;
1279 
1280  if (finalX <= currLineMaxX || broken)
1281  {
1282  if (i == 0)
1283  {
1284  context.CurrentLine.Fragments.Add(new RectangleFragment(new Point(context.Cursor.X - this.CodeInlineMargin * context.Font.FontSize, context.Cursor.Y - context.Font.YMax), new Size(this.CodeInlineMargin * context.Font.FontSize, context.Font.YMax - context.Font.YMin), CodeInlineBackgroundColour, context.Tag));
1285  }
1286 
1287  context.CurrentLine.Fragments.Add(new RectangleFragment(new Point(context.Cursor.X - spaceWidth * w.WhitespaceCount * (w.PrecedingWhitespace == '\t' ? 4 : 1), context.Cursor.Y - context.Font.YMax), new Size(wordMetrics.Width + wordMetrics.LeftSideBearing * 2 + wordMetrics.RightSideBearing + spaceWidth * w.WhitespaceCount * (w.PrecedingWhitespace == '\t' ? 4 : 1), context.Font.YMax - context.Font.YMin), CodeInlineBackgroundColour, context.Tag));
1288 
1289  context.CurrentLine.Fragments.Add(new TextFragment(new Point(context.Cursor.X + wordMetrics.LeftSideBearing, context.Cursor.Y), w.Text, context.Font, context.Colour, context.Tag));
1290  context.Cursor = new Point(context.Cursor.X + wordMetrics.Width + wordMetrics.RightSideBearing + wordMetrics.LeftSideBearing, context.Cursor.Y);
1291 
1292  broken = false;
1293 
1294  if (context.Underline || context.StrikeThrough)
1295  {
1296  underlineEnd = context.Cursor.X;
1297  }
1298  }
1299  else
1300  {
1301  if (context.Underline && underlineStart != underlineEnd)
1302  {
1303  context.CurrentLine.Fragments.Add(new UnderlineFragment(new Point(underlineStart, context.Cursor.Y + context.Font.FontSize * 0.2), new Point(underlineEnd, context.Cursor.Y + context.Font.FontSize * 0.2), context.Colour, context.Font.FontSize * (context.Font.FontFamily.IsBold ? this.BoldUnderlineThickness : this.UnderlineThickness), context.Tag));
1304  }
1305  else if (context.StrikeThrough && underlineStart != underlineEnd)
1306  {
1307  context.CurrentLine.Fragments.Add(new UnderlineFragment(new Point(underlineStart, context.Cursor.Y - context.Font.Ascent * 0.5 - context.Font.Descent * 0.5), new Point(underlineEnd, context.Cursor.Y - context.Font.Ascent * 0.5 - context.Font.Descent * 0.5), context.Colour, context.Font.FontSize * (context.Font.FontFamily.IsBold ? this.BoldUnderlineThickness : this.UnderlineThickness), context.Tag));
1308  }
1309 
1310  context.CurrentLine.Render(ref graphics, ref context, newPageAction, this.PageSize.Height - this.Margins.Bottom - context.Translation.Y - context.MarginBottomRight.Y);
1311 
1312  context.CurrentLine = new Line(prevContext.Font.Ascent);
1313 
1314  context.Cursor = new Point(0, context.Cursor.Y - prevContext.Font.Descent + SpaceAfterLine * prevContext.Font.FontSize + prevContext.Font.Ascent);
1315 
1316 
1317  double minX = context.GetMinX(context.Cursor.Y - context.Font.Ascent, context.Cursor.Y - context.Font.Descent);
1318 
1319  context.Cursor = new Point(minX, context.Cursor.Y);
1320 
1321  if (i == 0)
1322  {
1323  context.Cursor = new Point(context.Cursor.X + this.CodeInlineMargin * context.Font.FontSize, context.Cursor.Y);
1324  }
1325 
1326  underlineStart = minX;
1327  underlineEnd = minX;
1328 
1329  i--;
1330  broken = true;
1331  }
1332  }
1333  }
1334 
1335 
1336  if (context.Underline && underlineStart != underlineEnd)
1337  {
1338  context.CurrentLine.Fragments.Add(new UnderlineFragment(new Point(underlineStart, context.Cursor.Y + context.Font.FontSize * 0.2), new Point(underlineEnd, context.Cursor.Y + context.Font.FontSize * 0.2), context.Colour, context.Font.FontSize * (context.Font.FontFamily.IsBold ? this.BoldUnderlineThickness : this.UnderlineThickness), context.Tag));
1339  }
1340  else if (context.StrikeThrough && underlineStart != underlineEnd)
1341  {
1342  context.CurrentLine.Fragments.Add(new UnderlineFragment(new Point(underlineStart, context.Cursor.Y - context.Font.Ascent * 0.5 - context.Font.Descent * 0.5), new Point(underlineEnd, context.Cursor.Y - context.Font.Ascent * 0.5 - context.Font.Descent * 0.5), context.Colour, context.Font.FontSize * (context.Font.FontFamily.IsBold ? this.BoldUnderlineThickness : this.UnderlineThickness), context.Tag));
1343  }
1344 
1345  context.CurrentLine.Fragments.Add(new RectangleFragment(new Point(context.Cursor.X, context.Cursor.Y - context.Font.YMax), new Size(this.CodeInlineMargin * context.Font.FontSize, context.Font.YMax - context.Font.YMin), CodeInlineBackgroundColour, context.Tag));
1346 
1347  context.Cursor = new Point(context.Cursor.X + this.CodeInlineMargin * context.Font.FontSize, context.Cursor.Y);
1348 
1349  prevContext.Cursor = context.Cursor;
1350  prevContext.BottomRight = context.BottomRight;
1351  prevContext.CurrentPage = context.CurrentPage;
1352  prevContext.CurrentLine = context.CurrentLine;
1353 
1354  context = prevContext;
1355  }
1356 
1357  private void RenderCodeBlockLine(string text, ref MarkdownContext context, ref Graphics graphics, NewPageAction newPageAction)
1358  {
1359  double spaceWidth = context.Font.FontFamily.TrueTypeFile.Get1000EmGlyphWidth(' ') / 1000.0 * context.Font.FontSize;
1360 
1361  List<Word> words = Word.GetWords(text, context.Font, this.PageSize.Width - this.Margins.Right - context.Translation.X - context.Font.FontSize * 2 - context.MarginBottomRight.X).ToList();
1362 
1363  double underlineStart = context.Cursor.X;
1364  double underlineEnd = context.Cursor.X;
1365 
1366  double minX = context.GetMinX(context.Cursor.Y - context.Font.Ascent, context.Cursor.Y - context.Font.Descent);
1367 
1368  double currLineMaxX = context.GetMaxX(context.Cursor.Y - context.Font.Ascent, context.Cursor.Y - context.Font.Descent, this.PageSize.Width - this.Margins.Right - context.Translation.X - context.MarginBottomRight.X) - context.Font.FontSize;
1369 
1370  bool broken = false;
1371 
1372  for (int i = 0; i < words.Count; i++)
1373  {
1374  Word w = words[i];
1375  if (!string.IsNullOrEmpty(w.Text))
1376  {
1377  context.Cursor = new Point(context.Cursor.X + spaceWidth * w.WhitespaceCount * (w.PrecedingWhitespace == '\t' ? 4 : 1), context.Cursor.Y);
1378  Font.DetailedFontMetrics wordMetrics = w.Metrics;
1379 
1380  double finalX = context.Cursor.X + wordMetrics.Width + wordMetrics.RightSideBearing + wordMetrics.LeftSideBearing;
1381 
1382  double effW = wordMetrics.Width + wordMetrics.RightSideBearing + wordMetrics.LeftSideBearing;
1383 
1384  double maxW = this.PageSize.Width - this.Margins.Right - context.Translation.X - context.Font.FontSize * 2 - spaceWidth * w.WhitespaceCount * (w.PrecedingWhitespace == '\t' ? 4 : 1) - context.MarginBottomRight.X;
1385 
1386  if (finalX <= currLineMaxX || broken)
1387  {
1388  broken = false;
1389  context.CurrentLine.Fragments.Add(new TextFragment(new Point(context.Cursor.X + wordMetrics.LeftSideBearing, context.Cursor.Y), w.Text, context.Font, context.Colour, context.Tag));
1390  context.Cursor = new Point(context.Cursor.X + wordMetrics.Width + wordMetrics.RightSideBearing + wordMetrics.LeftSideBearing, context.Cursor.Y);
1391 
1392  if (context.Underline || context.StrikeThrough)
1393  {
1394  underlineEnd = context.Cursor.X;
1395  }
1396  }
1397  else
1398  {
1399  context.CurrentLine.Fragments.Insert(0, new RectangleFragment(new Point(minX, context.Cursor.Y - context.Font.YMax), new Size(currLineMaxX + context.Font.FontSize - minX, context.Font.YMax - context.Font.YMin + this.SpaceAfterLine * context.Font.FontSize), CodeBlockBackgroundColour, context.Tag));
1400 
1401  if (context.Underline && underlineStart != underlineEnd)
1402  {
1403  context.CurrentLine.Fragments.Add(new UnderlineFragment(new Point(underlineStart, context.Cursor.Y + context.Font.FontSize * 0.2), new Point(underlineEnd, context.Cursor.Y + context.Font.FontSize * 0.2), context.Colour, context.Font.FontSize * (context.Font.FontFamily.IsBold ? this.BoldUnderlineThickness : this.UnderlineThickness), context.Tag));
1404  }
1405  else if (context.StrikeThrough && underlineStart != underlineEnd)
1406  {
1407  context.CurrentLine.Fragments.Add(new UnderlineFragment(new Point(underlineStart, context.Cursor.Y - context.Font.Ascent * 0.5 - context.Font.Descent * 0.5), new Point(underlineEnd, context.Cursor.Y - context.Font.Ascent * 0.5 - context.Font.Descent * 0.5), context.Colour, context.Font.FontSize * (context.Font.FontFamily.IsBold ? this.BoldUnderlineThickness : this.UnderlineThickness), context.Tag));
1408  }
1409 
1410  context.CurrentLine.Render(ref graphics, ref context, newPageAction, this.PageSize.Height - this.Margins.Bottom - context.Translation.Y - context.MarginBottomRight.Y);
1411 
1412  underlineStart = 0;
1413  underlineEnd = 0;
1414 
1415  context.CurrentLine = new Line(context.Font.Ascent);
1416 
1417  context.Cursor = new Point(0, context.Cursor.Y - context.Font.Descent + SpaceAfterLine * context.Font.FontSize + context.Font.Ascent);
1418 
1419  currLineMaxX = context.GetMaxX(context.Cursor.Y - context.Font.Ascent, context.Cursor.Y - context.Font.Descent, this.PageSize.Width - this.Margins.Right - context.Translation.X - context.MarginBottomRight.X) - context.Font.FontSize;
1420 
1421  minX = context.GetMinX(context.Cursor.Y - context.Font.Ascent, context.Cursor.Y - context.Font.Descent);
1422 
1423  context.Cursor = new Point(minX + context.Font.FontSize, context.Cursor.Y);
1424 
1425  i--;
1426  broken = true;
1427  }
1428  }
1429  else
1430  {
1431  context.Cursor = new Point(context.Cursor.X + spaceWidth * w.WhitespaceCount * (w.PrecedingWhitespace == '\t' ? 4 : 1), context.Cursor.Y);
1432  }
1433  }
1434 
1435  if (context.Underline && underlineStart != underlineEnd)
1436  {
1437  context.CurrentLine.Fragments.Add(new UnderlineFragment(new Point(underlineStart, context.Cursor.Y + context.Font.FontSize * 0.2), new Point(underlineEnd, context.Cursor.Y + context.Font.FontSize * 0.2), context.Colour, context.Font.FontSize * (context.Font.FontFamily.IsBold ? this.BoldUnderlineThickness : this.UnderlineThickness), context.Tag));
1438  }
1439  else if (context.StrikeThrough && underlineStart != underlineEnd)
1440  {
1441  context.CurrentLine.Fragments.Add(new UnderlineFragment(new Point(underlineStart, context.Cursor.Y - context.Font.Ascent * 0.5 - context.Font.Descent * 0.5), new Point(underlineEnd, context.Cursor.Y - context.Font.Ascent * 0.5 - context.Font.Descent * 0.5), context.Colour, context.Font.FontSize * (context.Font.FontFamily.IsBold ? this.BoldUnderlineThickness : this.UnderlineThickness), context.Tag));
1442  }
1443 
1444  context.CurrentLine.Fragments.Insert(0, new RectangleFragment(new Point(minX, context.Cursor.Y - context.Font.YMax), new Size(currLineMaxX + context.Font.FontSize - minX, context.Font.YMax - context.Font.YMin + this.SpaceAfterLine * context.Font.FontSize), CodeBlockBackgroundColour, context.Tag));
1445  }
1446 
1447  private void RenderEmphasisInline(EmphasisInline emphasis, ref MarkdownContext context, ref Graphics graphics, NewPageAction newPageAction)
1448  {
1449  MarkdownContext prevContext = context.Clone();
1450 
1451  Point translationToUndo = new Point(0, 0);
1452 
1453  switch (emphasis.DelimiterChar)
1454  {
1455  case '*':
1456  case '_':
1457  if (emphasis.DelimiterCount == 2)
1458  {
1459  if (context.Font.FontFamily == this.ItalicFontFamily)
1460  {
1461  context.Font = new Font(this.BoldItalicFontFamily, context.Font.FontSize);
1462  }
1463  else if (context.Font.FontFamily == this.BoldFontFamily)
1464  {
1465  context.Font = new Font(this.RegularFontFamily, context.Font.FontSize);
1466  }
1467  else if (context.Font.FontFamily == this.BoldItalicFontFamily)
1468  {
1469  context.Font = new Font(this.ItalicFontFamily, context.Font.FontSize);
1470  }
1471  else
1472  {
1473  context.Font = new Font(this.BoldFontFamily, context.Font.FontSize);
1474  }
1475  }
1476  else if (emphasis.DelimiterCount == 3)
1477  {
1478  if (context.Font.FontFamily == this.ItalicFontFamily)
1479  {
1480  context.Font = new Font(this.BoldFontFamily, context.Font.FontSize);
1481  }
1482  else if (context.Font.FontFamily == this.BoldFontFamily)
1483  {
1484  context.Font = new Font(this.ItalicFontFamily, context.Font.FontSize);
1485  }
1486  else if (context.Font.FontFamily == this.BoldItalicFontFamily)
1487  {
1488  context.Font = new Font(this.RegularFontFamily, context.Font.FontSize);
1489  }
1490  else
1491  {
1492  context.Font = new Font(this.BoldItalicFontFamily, context.Font.FontSize);
1493  }
1494  }
1495  else
1496  {
1497  if (context.Font.FontFamily == this.ItalicFontFamily)
1498  {
1499  context.Font = new Font(this.RegularFontFamily, context.Font.FontSize);
1500  }
1501  else if (context.Font.FontFamily == this.BoldFontFamily)
1502  {
1503  context.Font = new Font(this.BoldItalicFontFamily, context.Font.FontSize);
1504  }
1505  else if (context.Font.FontFamily == this.BoldItalicFontFamily)
1506  {
1507  context.Font = new Font(this.BoldFontFamily, context.Font.FontSize);
1508  }
1509  else
1510  {
1511  context.Font = new Font(this.ItalicFontFamily, context.Font.FontSize);
1512  }
1513  }
1514  break;
1515  case '"':
1516  if (emphasis.DelimiterCount == 2)
1517  {
1518  if (context.Font.FontFamily == this.ItalicFontFamily)
1519  {
1520  context.Font = new Font(this.RegularFontFamily, context.Font.FontSize);
1521  }
1522  else if (context.Font.FontFamily == this.BoldFontFamily)
1523  {
1524  context.Font = new Font(this.BoldItalicFontFamily, context.Font.FontSize);
1525  }
1526  else if (context.Font.FontFamily == this.BoldItalicFontFamily)
1527  {
1528  context.Font = new Font(this.BoldFontFamily, context.Font.FontSize);
1529  }
1530  else
1531  {
1532  context.Font = new Font(this.ItalicFontFamily, context.Font.FontSize);
1533  }
1534  }
1535  break;
1536  case '~':
1537  if (emphasis.DelimiterCount == 1)
1538  {
1539  //subscript;
1540  context.Cursor = new Point(context.Cursor.X, context.Cursor.Y + context.Font.FontSize * this.SubscriptShift);
1541  translationToUndo = new Point(translationToUndo.X, translationToUndo.Y + context.Font.FontSize * this.SubscriptShift);
1542  context.Font = new Font(context.Font.FontFamily, context.Font.FontSize * this.SubSuperscriptFontSize);
1543  }
1544  else
1545  {
1546  //strikethrough
1547  context.StrikeThrough = true;
1548  }
1549  break;
1550  case '^':
1551  if (emphasis.DelimiterCount == 1)
1552  {
1553  //superscript
1554  context.Cursor = new Point(context.Cursor.X, context.Cursor.Y - context.Font.FontSize * this.SuperscriptShift);
1555  translationToUndo = new Point(translationToUndo.X, translationToUndo.Y - context.Font.FontSize * this.SuperscriptShift);
1556  context.Font = new Font(context.Font.FontFamily, context.Font.FontSize * this.SubSuperscriptFontSize);
1557  }
1558  break;
1559  case '+':
1560  context.Colour = this.InsertedColour;
1561  break;
1562  case '=':
1563  context.Colour = this.MarkedColour;
1564  break;
1565  }
1566 
1567  foreach (Inline innerInline in emphasis)
1568  {
1569  RenderInline(innerInline, ref context, ref graphics, newPageAction);
1570  }
1571 
1572  if (translationToUndo.X != 0 || translationToUndo.Y != 0)
1573  {
1574  context.Cursor = new Point(context.Cursor.X - translationToUndo.X, context.Cursor.Y - translationToUndo.Y);
1575  }
1576 
1577 
1578  prevContext.Cursor = context.Cursor;
1579  prevContext.BottomRight = context.BottomRight;
1580  prevContext.CurrentPage = context.CurrentPage;
1581  prevContext.CurrentLine = context.CurrentLine;
1582 
1583  context = prevContext;
1584  }
1585 
1586  private void RenderLinkInline(LinkInline link, ref MarkdownContext context, ref Graphics graphics, NewPageAction newPageAction)
1587  {
1588  if (!link.IsImage)
1589  {
1590  MarkdownContext prevContext = context.Clone();
1591 
1592  context.Colour = this.LinkColour;
1593  context.Underline = true;
1594  string tag = Guid.NewGuid().ToString("N");
1595 
1596  if (!link.Url.StartsWith("#"))
1597  {
1598  if (Uri.TryCreate(this.BaseLinkUri, link.Url, out Uri uri))
1599  {
1600  context.LinkDestinations[tag] = this.LinkUriResolver(uri.ToString());
1601  }
1602  else
1603  {
1604  context.LinkDestinations[tag] = this.LinkUriResolver(link.Url);
1605  }
1606  }
1607  else
1608  {
1609  if (!context.InternalAnchors.TryGetValue(link.Url, out string anchor))
1610  {
1611  anchor = Guid.NewGuid().ToString("N");
1612  context.InternalAnchors[link.Url] = anchor;
1613  }
1614 
1615  context.LinkDestinations[tag] = "#" + anchor;
1616  }
1617 
1618  context.Tag = tag;
1619 
1620  foreach (Inline innerInline in link)
1621  {
1622  RenderInline(innerInline, ref context, ref graphics, newPageAction);
1623  }
1624 
1625  prevContext.Cursor = context.Cursor;
1626  prevContext.BottomRight = context.BottomRight;
1627  prevContext.CurrentPage = context.CurrentPage;
1628  prevContext.CurrentLine = context.CurrentLine;
1629 
1630  context = prevContext;
1631  }
1632  else
1633  {
1634  HtmlTag tag = HtmlTag.Parse("<img src=\"" + link.Url.Replace("\"", "\\\"") + "\">").FirstOrDefault();
1635 
1636  RenderHTMLImage(tag, true, ref context, ref graphics, newPageAction);
1637  }
1638  }
1639 
1640  private void RenderListBlock(ListBlock list, ref MarkdownContext context, ref Graphics graphics, NewPageAction newPageAction)
1641  {
1642  MarkdownContext prevContext = context.Clone();
1643 
1644  context.ListDepth++;
1645 
1646  double minX = context.GetMinX(context.Cursor.Y + SpaceBeforeParagaph * context.Font.FontSize, context.Cursor.Y - context.Font.Descent + context.Font.Ascent + SpaceBeforeParagaph * context.Font.FontSize);
1647 
1648  if (context.CurrentLine != null)
1649  {
1650  foreach (LineFragment fragment in context.CurrentLine.Fragments)
1651  {
1652  fragment.Translate(-minX - this.IndentWidth, 0);
1653  }
1654  }
1655 
1656  graphics.Translate(minX + this.IndentWidth, 0);
1657  context.Translation = new Point(context.Translation.X + minX + this.IndentWidth, context.Translation.Y);
1658 
1659  foreach (Block block in list)
1660  {
1661  RenderBlock(block, ref context, ref graphics, newPageAction, true, true);
1662  }
1663 
1664  graphics.Translate(-minX - this.IndentWidth, 0);
1665  context.Translation = new Point(context.Translation.X - minX - this.IndentWidth, context.Translation.Y);
1666 
1667  prevContext.Cursor = context.Cursor;
1668  prevContext.BottomRight = context.BottomRight;
1669  prevContext.CurrentPage = context.CurrentPage;
1670  prevContext.CurrentLine = context.CurrentLine;
1671 
1672  context = prevContext;
1673  }
1674 
1675  private void RenderQuoteBlock(QuoteBlock quote, ref MarkdownContext context, ref Graphics graphics, NewPageAction newPageAction)
1676  {
1677  MarkdownContext prevContext = context.Clone();
1678 
1679  double minX = context.GetMinX(context.Cursor.Y + SpaceBeforeParagaph * context.Font.FontSize, context.Cursor.Y - context.Font.Descent + context.Font.Ascent + SpaceBeforeParagaph * context.Font.FontSize);
1680 
1681  Graphics quoteGraphics = new Graphics();
1682 
1683  quoteGraphics.Translate(minX + this.QuoteBlockIndentWidth, 0);
1684  context.Translation = new Point(minX + this.QuoteBlockIndentWidth, 0);
1685  context.MarginBottomRight = new Point(context.MarginBottomRight.X + prevContext.Translation.X, context.MarginBottomRight.Y + prevContext.Translation.Y);
1686 
1687  double maxX = this.PageSize.Width - this.Margins.Right - context.Translation.X - context.MarginBottomRight.X;
1688 
1689  double startY = context.Cursor.Y + context.Font.Ascent - context.Font.YMax;
1690 
1691  Point currTranslation = context.Translation;
1692 
1693  Graphics parentGraphics = graphics;
1694 
1695  NewPageAction newPageActionWithBlockquotes = (ref MarkdownContext currContext, ref Graphics currGraphics) =>
1696  {
1697  double currEndY = currContext.Cursor.Y;
1698 
1699  double currMaxX = maxX;
1700 
1701  Graphics currBackgroundGraphics = new Graphics();
1702 
1703  currBackgroundGraphics.Save();
1704 
1705  currBackgroundGraphics.Translate(currContext.Translation);
1706  currBackgroundGraphics.FillRectangle(new Point(-this.QuoteBlockIndentWidth + this.QuoteBlockBarWidth, startY), new Size(currMaxX + this.QuoteBlockIndentWidth - this.QuoteBlockBarWidth, currEndY - startY), this.QuoteBlockBackgroundColour, tag: currContext.Tag);
1707  currBackgroundGraphics.FillRectangle(new Point(-this.QuoteBlockIndentWidth, startY), new Size(this.QuoteBlockBarWidth, currEndY - startY), this.QuoteBlockBarColour, tag: currContext.Tag);
1708 
1709  currBackgroundGraphics.Restore();
1710 
1711  currBackgroundGraphics.DrawGraphics(0, 0, currGraphics);
1712 
1713  parentGraphics.DrawGraphics(0, 0, currBackgroundGraphics);
1714 
1715  Point currContextTranslation = currContext.Translation;
1716 
1717  currContext.Translation = prevContext.Translation;
1718 
1719  newPageAction(ref currContext, ref parentGraphics);
1720 
1721  currContext.Translation = currContextTranslation;
1722 
1723  currGraphics = new Graphics();
1724  currGraphics.Translate(minX + this.QuoteBlockIndentWidth, 0);
1725 
1726  startY = currContext.Cursor.Y + currContext.Font.Ascent - currContext.Font.YMax;
1727  };
1728 
1729  int index = 0;
1730 
1731  foreach (Block block in quote)
1732  {
1733  RenderBlock(block, ref context, ref quoteGraphics, newPageActionWithBlockquotes, true, index < quote.Count - 1);
1734  index++;
1735  }
1736 
1737  double endY = context.Cursor.Y;
1738 
1739  Graphics backgroundGraphics = new Graphics();
1740 
1741  backgroundGraphics.Save();
1742 
1743  backgroundGraphics.Translate(context.Translation);
1744  backgroundGraphics.FillRectangle(new Point(-this.QuoteBlockIndentWidth + this.QuoteBlockBarWidth, startY), new Size(maxX + this.QuoteBlockIndentWidth - this.QuoteBlockBarWidth, endY - startY), this.QuoteBlockBackgroundColour, tag: context.Tag);
1745  backgroundGraphics.FillRectangle(new Point(-this.QuoteBlockIndentWidth, startY), new Size(this.QuoteBlockBarWidth, endY - startY), this.QuoteBlockBarColour, tag: context.Tag);
1746 
1747  backgroundGraphics.Restore();
1748 
1749  backgroundGraphics.DrawGraphics(0, 0, quoteGraphics);
1750 
1751  parentGraphics.DrawGraphics(0, 0, backgroundGraphics);
1752 
1753  graphics = parentGraphics;
1754 
1755  context.Translation = prevContext.Translation;
1756 
1757  if (!(quote.Parent is QuoteBlock) || quote.Parent.LastChild != quote)
1758  {
1759  context.Cursor = new Point(context.Cursor.X, context.Cursor.Y + SpaceAfterParagraph * context.Font.FontSize);
1760  }
1761 
1762  prevContext.Cursor = context.Cursor;
1763  prevContext.BottomRight = context.BottomRight;
1764  prevContext.CurrentPage = context.CurrentPage;
1765  prevContext.CurrentLine = context.CurrentLine;
1766 
1767  context = prevContext;
1768  }
1769 
1770  private void RenderListItemBlock(ListItemBlock listItem, ref MarkdownContext context, ref Graphics graphics, NewPageAction newPageAction)
1771  {
1772  MarkdownContext prevContext = context.Clone();
1773 
1774  bool isLoose = false;
1775 
1776  double startX = context.Cursor.X;
1777  double startY = context.Cursor.Y;
1778 
1779  if (listItem.Parent is ListBlock list)
1780  {
1781  if (list.IsOrdered)
1782  {
1783  if (context.CurrentLine == null)
1784  {
1785  context.CurrentLine = new Line(context.Font.Ascent);
1786  }
1787 
1788  string bullet;
1789 
1790  switch (list.BulletType)
1791  {
1792  case 'a':
1793  case 'A':
1794  bullet = ((char)((int)list.DefaultOrderedStart[0] + listItem.Order - 1)).ToString() + list.OrderedDelimiter;
1795  break;
1796  case 'i':
1797  bullet = GetRomanNumeral(listItem.Order).ToLower() + list.OrderedDelimiter;
1798  break;
1799  case 'I':
1800  bullet = GetRomanNumeral(listItem.Order) + list.OrderedDelimiter;
1801  break;
1802  default:
1803  bullet = listItem.Order.ToString() + list.OrderedDelimiter;
1804  break;
1805  }
1806 
1807  if (list.IsLoose)
1808  {
1809  isLoose = true;
1810  context.CurrentLine.Fragments.Add(new TextFragment(new Point(context.Cursor.X - context.Font.MeasureText(bullet).Width - this.IndentWidth * 0.15, context.Cursor.Y + context.Font.Ascent + SpaceBeforeParagaph * context.Font.FontSize), bullet, context.Font, context.Colour, context.Tag));
1811  }
1812  else
1813  {
1814  isLoose = false;
1815  context.CurrentLine.Fragments.Add(new TextFragment(new Point(context.Cursor.X - context.Font.MeasureText(bullet).Width - this.IndentWidth * 0.15, context.Cursor.Y + context.Font.Ascent), bullet, context.Font, context.Colour, context.Tag));
1816  }
1817  }
1818  else
1819  {
1820  if (listItem.Count > 0 && listItem[0] is ParagraphBlock paragraph && paragraph.Inline?.FirstChild is Markdig.Extensions.TaskLists.TaskList task)
1821  {
1822  if (context.CurrentLine == null)
1823  {
1824  context.CurrentLine = new Line(context.Font.Ascent);
1825  }
1826 
1827  Graphics bullet = new Graphics();
1828  bullet.Scale(context.Font.FontSize, context.Font.FontSize);
1829 
1830  if (task.Checked)
1831  {
1832  bullet.DrawGraphics(0, 0, this.TaskListCheckedBullet);
1833  }
1834  else
1835  {
1836  bullet.DrawGraphics(0, 0, this.TaskListUncheckedBullet);
1837  }
1838 
1839  if (list.IsLoose)
1840  {
1841  isLoose = true;
1842  context.CurrentLine.Fragments.Add(new GraphicsFragment(new Point(context.Cursor.X - this.IndentWidth * 0.15, context.Cursor.Y + context.Font.Ascent + SpaceBeforeParagaph * context.Font.FontSize - context.Font.Descent * 0.5 - (context.Font.Ascent - context.Font.Descent) * 0.5), bullet, 0));
1843  }
1844  else
1845  {
1846  isLoose = false;
1847  context.CurrentLine.Fragments.Add(new GraphicsFragment(new Point(context.Cursor.X - this.IndentWidth * 0.15, context.Cursor.Y + context.Font.Ascent - context.Font.Descent * 0.5 - (context.Font.Ascent - context.Font.Descent) * 0.5), bullet, 0));
1848  }
1849  }
1850  else
1851  {
1852  if (context.CurrentLine == null)
1853  {
1854  context.CurrentLine = new Line(context.Font.Ascent);
1855  }
1856 
1857  Graphics bullet = new Graphics();
1858  bullet.Scale(context.Font.FontSize, context.Font.FontSize);
1859  this.Bullets[(context.ListDepth - 1) % this.Bullets.Count](bullet, context.Colour);
1860 
1861  if (list.IsLoose)
1862  {
1863  isLoose = true;
1864  context.CurrentLine.Fragments.Add(new GraphicsFragment(new Point(context.Cursor.X - this.IndentWidth * 0.15, context.Cursor.Y + context.Font.Ascent + SpaceBeforeParagaph * context.Font.FontSize - context.Font.Descent * 0.5 - (context.Font.Ascent - context.Font.Descent) * 0.5), bullet, 0));
1865  }
1866  else
1867  {
1868  isLoose = false;
1869  context.CurrentLine.Fragments.Add(new GraphicsFragment(new Point(context.Cursor.X - this.IndentWidth * 0.15, context.Cursor.Y + context.Font.Ascent - context.Font.Descent * 0.5 - (context.Font.Ascent - context.Font.Descent) * 0.5), bullet, 0));
1870  }
1871  }
1872  }
1873  }
1874 
1875  foreach (Block block in listItem)
1876  {
1877  RenderBlock(block, ref context, ref graphics, newPageAction, isLoose || listItem == listItem.Parent[0], isLoose || listItem == listItem.Parent.LastChild);
1878  }
1879 
1880  prevContext.Cursor = context.Cursor;
1881  prevContext.BottomRight = context.BottomRight;
1882  prevContext.CurrentPage = context.CurrentPage;
1883  prevContext.CurrentLine = context.CurrentLine;
1884 
1885  context = prevContext;
1886  }
1887 
1888  private void RenderHTMLBlock(string html, bool isInline, ref MarkdownContext context, ref Graphics graphics, NewPageAction newPageAction, bool spaceBefore, bool spaceAfter)
1889  {
1890  if (!isInline)
1891  {
1892  double minX = context.GetMinX(context.Cursor.Y + SpaceBeforeParagaph * context.Font.FontSize, context.Cursor.Y + context.Font.Ascent + SpaceBeforeParagaph * context.Font.FontSize - context.Font.Descent);
1893 
1894  context.Cursor = new Point(minX, context.Cursor.Y + context.Font.Ascent + SpaceBeforeParagaph * context.Font.FontSize);
1895 
1896  if (context.CurrentLine == null)
1897  {
1898  context.CurrentLine = new Line(context.Font.Ascent);
1899  }
1900  }
1901 
1902  foreach (HtmlTag tag in HtmlTag.Parse(html))
1903  {
1904  if (tag.Tag.Equals("img", StringComparison.OrdinalIgnoreCase) || tag.Tag.Equals("image", StringComparison.OrdinalIgnoreCase))
1905  {
1906  RenderHTMLImage(tag, isInline, ref context, ref graphics, newPageAction);
1907  }
1908  else if (tag.Tag.Equals("br", StringComparison.OrdinalIgnoreCase))
1909  {
1910  RenderLineBreakInline(true, tag.Attributes.TryGetValue("type", out string typeValue) && typeValue.Equals("page", StringComparison.OrdinalIgnoreCase) && this.AllowPageBreak, ref context, ref graphics, newPageAction);
1911  }
1912  else if (tag.Tag.Equals("a"))
1913  {
1914  if (tag.Attributes.TryGetValue("name", out string anchorName))
1915  {
1916  if (!context.InternalAnchors.TryGetValue("#" + anchorName, out string anchor))
1917  {
1918  anchor = Guid.NewGuid().ToString("N");
1919  context.InternalAnchors["#" + anchorName] = anchor;
1920  }
1921 
1922  if (context.CurrentLine == null)
1923  {
1924  context.CurrentLine = new Line(0);
1925  }
1926 
1927  double anchorHeight = this.BaseFontSize * Math.Max(1, this.HeaderFontSizeMultipliers.Max());
1928 
1929  context.CurrentLine.Fragments.Add(new RectangleFragment(new Point(context.Cursor.X, context.Cursor.Y - anchorHeight), new Size(10, anchorHeight), Colour.FromRgba(0, 0, 0, 0), anchor));
1930  }
1931  }
1932  else
1933  {
1934 
1935  }
1936  }
1937 
1938  if (!isInline)
1939  {
1940  if (context.CurrentLine != null)
1941  {
1942  context.CurrentLine.Render(ref graphics, ref context, newPageAction, this.PageSize.Height - this.Margins.Bottom - context.Translation.Y - context.MarginBottomRight.Y);
1943  context.CurrentLine = null;
1944 
1945  context.Cursor = new Point(0, context.Cursor.Y - context.Font.Descent + SpaceAfterLine * context.Font.FontSize);
1946 
1947  context.Cursor = new Point(0, context.Cursor.Y + SpaceAfterParagraph * context.Font.FontSize);
1948  }
1949  }
1950  }
1951 
1952  private void RenderHTMLImage(HtmlTag imgTag, bool isInline, ref MarkdownContext context, ref Graphics graphics, NewPageAction newPageAction)
1953  {
1954  if (imgTag != null)
1955  {
1956  if (imgTag.Attributes.TryGetValue("src", out string imageSrc))
1957  {
1958  (string imageFile, bool wasDownloaded) = this.ImageUriResolver(imageSrc, this.BaseImageUri);
1959 
1960  Page imagePage = null;
1961 
1962  if (imageFile != null)
1963  {
1964  if (System.IO.Path.GetExtension(imageFile) == ".svg")
1965  {
1966  try
1967  {
1968  imagePage = VectSharp.SVG.Parser.FromFile(imageFile);
1969  }
1970  catch
1971  {
1972  imagePage = null;
1973  }
1974  }
1975  else if (RasterImageLoader != null)
1976  {
1977  try
1978  {
1979  RasterImage raster = RasterImageLoader(imageFile);
1980  imagePage = new Page(raster.Width, raster.Height);
1981  imagePage.Graphics.DrawRasterImage(0, 0, raster, tag: context.Tag);
1982  }
1983  catch
1984  {
1985  imagePage = null;
1986  }
1987  }
1988 
1989 
1990  if (wasDownloaded)
1991  {
1992  System.IO.File.Delete(imageFile);
1993  System.IO.Directory.Delete(System.IO.Path.GetDirectoryName(imageFile));
1994  }
1995  }
1996  else if (imageSrc.StartsWith("data:"))
1997  {
1998  try
1999  {
2000  imagePage = VectSharp.SVG.Parser.ParseImageURI(imageSrc, false);
2001  }
2002  catch
2003  {
2004 
2005  imagePage = null;
2006  }
2007  }
2008 
2009  if (imagePage != null)
2010  {
2011  double scaleX = 1;
2012  double scaleY = 1;
2013 
2014  bool hasWidth = false;
2015  bool hasHeight = false;
2016 
2017  if (imgTag.Attributes.TryGetValue("width", out string widthString) && double.TryParse(widthString, System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out double width))
2018  {
2019  hasWidth = true;
2020  scaleX = width * this.ImageUnitMultiplier / imagePage.Width;
2021  }
2022 
2023  if (imgTag.Attributes.TryGetValue("height", out string heightString) && double.TryParse(heightString, System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out double height))
2024  {
2025  hasHeight = true;
2026  scaleY = height * this.ImageUnitMultiplier / imagePage.Height;
2027  }
2028 
2029  if (hasWidth && !hasHeight)
2030  {
2031  scaleY = scaleX;
2032  }
2033  else if (hasHeight && !hasWidth)
2034  {
2035  scaleX = scaleY;
2036  }
2037 
2038  scaleX *= this.ImageMultiplier;
2039  scaleY *= this.ImageMultiplier;
2040 
2041  if (!isInline)
2042  {
2043  string alignValue;
2044 
2045  if (!imgTag.Attributes.TryGetValue("align", out alignValue))
2046  {
2047  alignValue = null;
2048  }
2049 
2050  if (alignValue == "center")
2051  {
2052  if (context.CurrentLine != null)
2053  {
2054  context.CurrentLine.Render(ref graphics, ref context, newPageAction, this.PageSize.Height - this.Margins.Bottom - context.Translation.Y - context.MarginBottomRight.Y);
2055  context.CurrentLine = null;
2056  }
2057 
2058  double minX = context.GetMinX(context.Cursor.Y + context.Translation.Y, context.Cursor.Y + context.Translation.Y + scaleY * imagePage.Height);
2059  double maxX = context.GetMaxX(context.Cursor.Y + context.Translation.Y, context.Cursor.Y + context.Translation.Y + scaleY * imagePage.Height, this.PageSize.Width - this.Margins.Right - context.Translation.X - context.MarginBottomRight.X);
2060 
2061  if (scaleX * imagePage.Width > maxX - minX)
2062  {
2063  scaleX = (maxX - minX) / imagePage.Width;
2064  scaleY = scaleX;
2065  }
2066 
2067  double finalY = context.Cursor.Y + scaleY * imagePage.Height;
2068 
2069  if (finalY + context.Translation.Y > this.PageSize.Height - this.Margins.Bottom + this.ImageMarginTolerance - context.MarginBottomRight.Y)
2070  {
2071  newPageAction(ref context, ref graphics);
2072  finalY = context.Cursor.Y + scaleY * imagePage.Height;
2073  }
2074 
2075  graphics.Save();
2076  graphics.Translate((minX + maxX - scaleX * imagePage.Width) * 0.5, context.Cursor.Y);
2077  graphics.Scale(scaleX, scaleY);
2078  graphics.SetClippingPath(0, 0, imagePage.Width, imagePage.Height);
2079  graphics.DrawGraphics(0, 0, imagePage.Graphics);
2080 
2081  graphics.Restore();
2082 
2083  context.Cursor = new Point(0, finalY + SpaceAfterParagraph * context.Font.FontSize + SpaceAfterLine * context.Font.FontSize);
2084  }
2085  else if (alignValue == "right")
2086  {
2087  if (context.CurrentLine != null)
2088  {
2089  context.CurrentLine.Render(ref graphics, ref context, newPageAction, this.PageSize.Height - this.Margins.Bottom - context.Translation.Y - context.MarginBottomRight.Y);
2090  context.CurrentLine = null;
2091  }
2092 
2093  double finalY = context.Cursor.Y + scaleY * imagePage.Height;
2094 
2095  if (finalY + context.Translation.Y > this.PageSize.Height - this.Margins.Bottom + this.ImageMarginTolerance - context.MarginBottomRight.Y)
2096  {
2097  newPageAction(ref context, ref graphics);
2098  finalY = context.Cursor.Y + scaleY * imagePage.Height;
2099  }
2100 
2101  graphics.Save();
2102  graphics.Translate(this.PageSize.Width - this.Margins.Right - context.Translation.X - scaleX * imagePage.Width - context.MarginBottomRight.X, context.Cursor.Y);
2103  graphics.Scale(scaleX, scaleY);
2104  graphics.SetClippingPath(0, 0, imagePage.Width, imagePage.Height);
2105 
2106  graphics.DrawGraphics(0, 0, imagePage.Graphics);
2107 
2108  graphics.Restore();
2109 
2110  context.ForbiddenAreasRight.Add((this.PageSize.Width - this.Margins.Right - scaleX * imagePage.Width - this.ImageSideMargin - context.MarginBottomRight.X, context.Cursor.Y + context.Translation.Y, context.Cursor.Y + context.Translation.Y + scaleY * imagePage.Height));
2111  }
2112  else if (alignValue == "left")
2113  {
2114  if (context.CurrentLine != null)
2115  {
2116  context.CurrentLine.Render(ref graphics, ref context, newPageAction, this.PageSize.Height - this.Margins.Bottom - context.Translation.Y - context.MarginBottomRight.Y);
2117  context.CurrentLine = null;
2118  }
2119 
2120  double finalY = context.Cursor.Y + scaleY * imagePage.Height;
2121 
2122  if (finalY + context.Translation.Y > this.PageSize.Height - this.Margins.Bottom + this.ImageMarginTolerance - context.MarginBottomRight.Y)
2123  {
2124  newPageAction(ref context, ref graphics);
2125  finalY = context.Cursor.Y + scaleY * imagePage.Height;
2126  }
2127 
2128  graphics.Save();
2129  graphics.Translate(0, context.Cursor.Y);
2130  graphics.Scale(scaleX, scaleY);
2131  graphics.SetClippingPath(0, 0, imagePage.Width, imagePage.Height);
2132 
2133  graphics.DrawGraphics(0, 0, imagePage.Graphics);
2134 
2135  graphics.Restore();
2136 
2137  context.ForbiddenAreasLeft.Add((scaleX * imagePage.Width + context.Translation.X + this.ImageSideMargin, context.Cursor.Y + context.Translation.Y, context.Cursor.Y + context.Translation.Y + scaleY * imagePage.Height));
2138  }
2139  else
2140  {
2141  isInline = true;
2142  }
2143  }
2144 
2145  if (isInline)
2146  {
2147  Graphics scaledImage = new Graphics();
2148  scaledImage.Scale(scaleX, scaleY);
2149  scaledImage.DrawGraphics(0, 0, imagePage.Graphics);
2150 
2151  if (context.CurrentLine == null)
2152  {
2153  context.CurrentLine = new Line(context.Font.Ascent);
2154 
2155  context.Cursor = new Point(0, context.Cursor.Y);
2156  double minX = context.GetMinX(context.Cursor.Y - scaleY * imagePage.Height, context.Cursor.Y);
2157  context.Cursor = new Point(minX, context.Cursor.Y);
2158  }
2159 
2160  double currLineMaxX = context.GetMaxX(context.Cursor.Y - scaleY * imagePage.Height, context.Cursor.Y, this.PageSize.Width - this.Margins.Right - context.Translation.X - context.MarginBottomRight.X);
2161 
2162  double finalX = context.Cursor.X + imagePage.Width;
2163 
2164  if (finalX > currLineMaxX)
2165  {
2166  context.CurrentLine.Render(ref graphics, ref context, newPageAction, this.PageSize.Height - this.Margins.Bottom - context.Translation.Y - context.MarginBottomRight.Y);
2167 
2168  context.CurrentLine = new Line(context.Font.Ascent);
2169  context.Cursor = new Point(0, context.Cursor.Y - context.Font.Descent + SpaceAfterLine * context.Font.FontSize + context.Font.Ascent);
2170  currLineMaxX = context.GetMaxX(context.Cursor.Y - context.Font.Ascent, context.Cursor.Y - context.Font.Descent, this.PageSize.Width - this.Margins.Right - context.Translation.X - context.MarginBottomRight.X);
2171 
2172  double minX = context.GetMinX(context.Cursor.Y - scaleY * imagePage.Height * scaleY, context.Cursor.Y);
2173 
2174  context.Cursor = new Point(minX, context.Cursor.Y);
2175  }
2176 
2177  context.CurrentLine.Fragments.Add(new GraphicsFragment(new Point(context.Cursor.X, context.Cursor.Y - scaleY * imagePage.Height), scaledImage, scaleY * imagePage.Height));
2178  context.Cursor = new Point(context.Cursor.X + scaleX * imagePage.Width, context.Cursor.Y);
2179  }
2180  }
2181  }
2182  }
2183  }
2184 
2185  private void RenderTable(Table table, ref MarkdownContext context, ref Graphics graphics, NewPageAction newPageAction)
2186  {
2187  if (table.Count > 0)
2188  {
2189  if (!table.IsValid())
2190  {
2191  table.NormalizeUsingMaxWidth();
2192  table.NormalizeUsingHeaderRow();
2193  }
2194 
2195  if (table.IsValid() && table.ColumnDefinitions?.Count > 0)
2196  {
2197  bool isGridTable = false;
2198 
2199  foreach (TableColumnDefinition def in table.ColumnDefinitions)
2200  {
2201  if (def.Width > 0)
2202  {
2203  isGridTable = true;
2204  break;
2205  }
2206  }
2207 
2208  if (isGridTable)
2209  {
2210  int maxColumns = 0;
2211 
2212  foreach (TableRow row in table)
2213  {
2214  maxColumns = Math.Max(maxColumns, ((TableCell)row.Last()).ColumnIndex + 1);
2215  }
2216 
2217  if (table.ColumnDefinitions.Count > maxColumns)
2218  {
2219  table.ColumnDefinitions.RemoveRange(maxColumns, table.ColumnDefinitions.Count - maxColumns);
2220  }
2221  }
2222  else
2223  {
2224  int maxColumns = 0;
2225 
2226  foreach (TableRow row in table)
2227  {
2228  maxColumns = Math.Max(maxColumns, row.Count);
2229  }
2230 
2231  if (table.ColumnDefinitions.Count > maxColumns)
2232  {
2233  table.ColumnDefinitions.RemoveRange(maxColumns, table.ColumnDefinitions.Count - maxColumns);
2234  }
2235  }
2236 
2237  double[] columnWidths = new double[table.ColumnDefinitions.Count];
2238 
2239  if (table.ColumnDefinitions.Count == 0)
2240  {
2241  int columnCount = 0;
2242  foreach (TableRow row in table)
2243  {
2244  columnCount = Math.Max(row.Count, columnCount);
2245  }
2246 
2247  columnWidths = new double[columnCount];
2248  }
2249 
2250  int missingColumns = columnWidths.Length;
2251 
2252  for (int i = 0; i < columnWidths.Length; i++)
2253  {
2254  columnWidths[i] = double.NaN;
2255  }
2256 
2257  double remainingPerc = 1;
2258 
2259  for (int i = 0; i < table.ColumnDefinitions.Count; i++)
2260  {
2261  if (table.ColumnDefinitions[i].Width > 0)
2262  {
2263  missingColumns--;
2264  remainingPerc -= table.ColumnDefinitions[i].Width / 100.0;
2265  columnWidths[i] = table.ColumnDefinitions[i].Width / 100.0;
2266  }
2267  }
2268 
2269  if (missingColumns > 0)
2270  {
2271  remainingPerc /= missingColumns;
2272  for (int i = 0; i < columnWidths.Length; i++)
2273  {
2274  if (double.IsNaN(columnWidths[i]))
2275  {
2276  columnWidths[i] = remainingPerc;
2277  }
2278  }
2279  }
2280 
2281  double maxX = context.GetMaxX(context.Cursor.Y, context.Cursor.Y, this.PageSize.Width - this.Margins.Right - context.Translation.X - context.MarginBottomRight.X);
2282 
2283  for (int i = 0; i < columnWidths.Length; i++)
2284  {
2285  columnWidths[i] *= maxX;
2286  }
2287 
2288  int index = 0;
2289 
2290  foreach (TableRow row in table)
2291  {
2292  RenderTableRow(row, columnWidths, index == table.Count - 1, ref context, ref graphics, newPageAction);
2293  index++;
2294  }
2295 
2296  context.Cursor = new Point(context.Cursor.X, context.Cursor.Y + SpaceAfterParagraph * context.Font.FontSize);
2297  }
2298  }
2299  }
2300 
2301  private void RenderTableRow(TableRow row, double[] columnWidths, bool isLastRow, ref MarkdownContext context, ref Graphics graphics, NewPageAction newPageAction)
2302  {
2303  if (context.CurrentLine == null)
2304  {
2305  context.CurrentLine = new Line(0);
2306  }
2307 
2308  int index = 0;
2309 
2310  foreach (TableCell cell in row)
2311  {
2312  if (cell.ColumnIndex < 0)
2313  {
2314  cell.ColumnIndex = index;
2315  index += cell.ColumnSpan;
2316  }
2317  else
2318  {
2319  index = cell.ColumnIndex + cell.ColumnSpan;
2320  }
2321  }
2322 
2323  double maxHeight = 0;
2324  double startX = context.Cursor.X;
2325 
2326  MarkdownContext prevContext = context.Clone();
2327 
2328  if (row.IsHeader)
2329  {
2330  context.Font = new Font(this.BoldFontFamily, context.Font.FontSize);
2331  }
2332 
2333 
2334  foreach (TableCell cell in row)
2335  {
2336  double cellWidth = 0;
2337 
2338  for (int i = 0; i < cell.ColumnSpan && cell.ColumnIndex + i < columnWidths.Length; i++)
2339  {
2340  cellWidth += columnWidths[cell.ColumnIndex + i];
2341  }
2342 
2343  Page cellPage = RenderTableCell(cell, cellWidth, ref context, ref graphics, newPageAction);
2344 
2345  double prevMaxHeight = maxHeight;
2346  maxHeight = Math.Max(maxHeight, cellPage.Height);
2347 
2348  context.CurrentLine.Fragments.Add(new GraphicsFragment(new Point(context.Cursor.X, context.Cursor.Y - cellPage.Height), cellPage.Graphics, cellPage.Height));
2349 
2350  context.Cursor = new Point(context.Cursor.X + cellWidth, context.Cursor.Y);
2351  }
2352 
2353  if (this.TableVAlign == VerticalAlignment.Top)
2354  {
2355  for (int i = 0; i < context.CurrentLine.Fragments.Count; i++)
2356  {
2357  context.CurrentLine.Fragments[i].Translate(0, -maxHeight + ((GraphicsFragment)context.CurrentLine.Fragments[i]).Ascent);
2358  }
2359  }
2360  else if (this.TableVAlign == VerticalAlignment.Middle)
2361  {
2362  for (int i = 0; i < context.CurrentLine.Fragments.Count; i++)
2363  {
2364  context.CurrentLine.Fragments[i].Translate(0, (-maxHeight + ((GraphicsFragment)context.CurrentLine.Fragments[i]).Ascent) * 0.5);
2365  }
2366  }
2367 
2368  context.CurrentLine.Fragments.Add(new UnderlineFragment(new Point(startX, context.Cursor.Y), new Point(columnWidths.Sum() + startX, context.Cursor.Y), row.IsHeader ? this.TableHeaderRowSeparatorColour : this.TableRowSeparatorColour, row.IsHeader ? this.TableHeaderRowSeparatorThickness : this.TableHeaderSeparatorThickness, context.Tag));
2369 
2370  context.CurrentLine.Render(ref graphics, ref context, newPageAction, this.PageSize.Height - this.Margins.Bottom - context.Translation.Y - context.MarginBottomRight.Y);
2371  context.CurrentLine = null;
2372 
2373  context.Cursor = new Point(startX, context.Cursor.Y);
2374 
2375  context.Cursor = new Point(startX, context.Cursor.Y + SpaceAfterLine * context.Font.FontSize + (row.IsHeader ? this.TableHeaderRowSeparatorThickness : this.TableHeaderSeparatorThickness));
2376 
2377  prevContext.Cursor = context.Cursor;
2378  prevContext.BottomRight = context.BottomRight;
2379  prevContext.CurrentPage = context.CurrentPage;
2380  prevContext.CurrentLine = context.CurrentLine;
2381 
2382  context = prevContext;
2383  }
2384 
2385  private Page RenderTableCell(TableCell cell, double cellWidth, ref MarkdownContext context, ref Graphics graphics, NewPageAction newPageAction)
2386  {
2387  MarkdownRenderer clonedRenderer = this.Clone();
2388  clonedRenderer.PageSize = new Size(cellWidth, double.PositiveInfinity);
2389  clonedRenderer.Margins = this.TableCellMargins;
2390 
2391  MarkdownContext clonedContext = context.Clone();
2392  clonedContext.Translation = new Point(0, 0);
2393  clonedContext.Cursor = new Point(0, 0);
2394  clonedContext.BottomRight = new Point(0, 0);
2395  clonedContext.CurrentLine = null;
2396  clonedContext.CurrentPage = null;
2397  clonedContext.ForbiddenAreasLeft = new List<(double MinX, double MinY, double MaxY)>();
2398  clonedContext.ForbiddenAreasRight = new List<(double MinX, double MinY, double MaxY)>();
2399 
2400  Page cellPage = clonedRenderer.RenderSubDocument(cell, ref clonedContext).Pages[0];
2401 
2402  cellPage.Crop(new Point(0, 0), new Size(cellWidth, clonedContext.BottomRight.Y + this.TableCellMargins.Bottom));
2403 
2404  return cellPage;
2405  }
2406 
2407  static (int, string)[] RomanNumbers = new (int, string)[] { (1000, "M"), (900, "CM"), (500, "D"), (400, "CD"), (100, "C"), (90, "XC"), (50, "L"), (40, "XL"), (10, "X"), (9, "IX"), (5, "V"), (4, "IV"), (1, "I") };
2408 
2409  private static string GetRomanNumeral(int number)
2410  {
2411  StringBuilder tbr = new StringBuilder();
2412 
2413  for (int i = 0; i < RomanNumbers.Length; i++)
2414  {
2415  while (number >= RomanNumbers[i].Item1)
2416  {
2417  tbr.Append(RomanNumbers[i].Item2);
2418  number -= RomanNumbers[i].Item1;
2419  }
2420  }
2421 
2422  return tbr.ToString();
2423  }
2424  }
2425 }
VectSharp.Markdown.MarkdownRenderer.CodeFontBold
FontFamily CodeFontBold
The font family for bold code elements.
Definition: MarkdownRenderer.cs:88
VectSharp.GraphicsPath.Arc
GraphicsPath Arc(Point center, double radius, double startAngle, double endAngle)
Trace an arc segment from a circle with the specified center and radius , starting at startAngle an...
Definition: GraphicsPath.cs:103
VectSharp.Markdown.MarkdownRenderer.BaseLinkUri
Uri BaseLinkUri
The base uri for resolving links.
Definition: MarkdownRenderer.cs:224
VectSharp.Graphics.Translate
void Translate(double x, double y)
Translate the coordinate system origin.
Definition: Graphics.cs:370
VectSharp.Markdown.MarkdownRenderer.BoldFontFamily
FontFamily BoldFontFamily
The font family for bold text.
Definition: MarkdownRenderer.cs:68
VectSharp.Markdown.MarkdownRenderer.MarkedColour
Colour MarkedColour
The colour for text that has been styled as "marked".
Definition: MarkdownRenderer.cs:336
VectSharp.Colours.Black
static Colour Black
Black #000000
Definition: StandardColours.cs:187
VectSharp.Markdown.MarkdownRenderer.Margins
Margins Margins
The margins of the page.
Definition: MarkdownRenderer.cs:113
VectSharp.Graphics.FillPath
void FillPath(GraphicsPath path, Brush fillColour, string tag=null)
Fill a GraphicsPath.
Definition: Graphics.cs:276
VectSharp.Markdown.MarkdownRenderer.RenderSinglePage
Page RenderSinglePage(MarkdownDocument markdownDocument, double width, out Dictionary< string, string > linkDestinations)
Renders the supplied markdownDocument . Page breaks are disabled, and the document is rendered as a s...
Definition: MarkdownRenderer.cs:423
VectSharp.TextBaselines.Middle
@ Middle
The current vertical coordinate determines where the middle of the text string will be placed.
VectSharp.Graphics.StrokePath
void StrokePath(GraphicsPath path, Brush strokeColour, double lineWidth=1, LineCaps lineCap=LineCaps.Butt, LineJoins lineJoin=LineJoins.Miter, LineDash? lineDash=null, string tag=null)
Stroke a GraphicsPath.
Definition: Graphics.cs:292
VectSharp.Markdown.MarkdownRenderer.SpaceAfterHeading
double SpaceAfterHeading
The space after each heading. This value will be multiplied by the font size of the heading.
Definition: MarkdownRenderer.cs:174
VectSharp.Markdown.MarkdownRenderer.QuoteBlockIndentWidth
double QuoteBlockIndentWidth
The indentation width used for block quotes.
Definition: MarkdownRenderer.cs:189
VectSharp.SVG.Parser.FromFile
static Page FromFile(string fileName)
Parses an SVG image file into a Page containing the image.
Definition: SVGParser.cs:167
VectSharp.Markdown.MarkdownRenderer.BaseFontSize
double BaseFontSize
The base font size to use when rendering the document. This will be the size of regular elements,...
Definition: MarkdownRenderer.cs:40
VectSharp.SVG.Parser
Contains methods to read an SVG image file.
Definition: SVGParser.cs:34
VectSharp.Colour
Represents an RGB colour.
Definition: Colour.cs:26
VectSharp.Markdown.MarkdownRenderer.TableHeaderSeparatorThickness
double TableHeaderSeparatorThickness
The thickness of lines separating table rows from each other.
Definition: MarkdownRenderer.cs:356
VectSharp.Markdown.MarkdownRenderer.CodeInlineBackgroundColour
Colour CodeInlineBackgroundColour
The background colour for code inlines.
Definition: MarkdownRenderer.cs:311
VectSharp.Markdown.MarkdownRenderer.AllowPageBreak
bool AllowPageBreak
Determines whether page breaks should be treated as such in the source.
Definition: MarkdownRenderer.cs:395
VectSharp.Markdown.MarkdownRenderer
Renders Markdown documents into VectSharp graphics objects.
Definition: MarkdownRenderer.cs:36
VectSharp.Markdown.MarkdownRenderer.SpaceBeforeParagaph
double SpaceBeforeParagaph
The space before each text paragraph. This value will be multiplied by the BaseFontSize.
Definition: MarkdownRenderer.cs:154
VectSharp.TextBaselines.Bottom
@ Bottom
The current vertical coordinate determines where the bottom of the text string will be placed.
VectSharp.Markdown.MarkdownRenderer.PageSize
Size PageSize
The size of the page.
Definition: MarkdownRenderer.cs:149
VectSharp.Markdown.MarkdownRenderer.ThematicBreakLineColour
Colour ThematicBreakLineColour
The colour for thematic break lines.
Definition: MarkdownRenderer.cs:301
VectSharp.Markdown.MarkdownRenderer.HeaderFontSizeMultipliers
double[] HeaderFontSizeMultipliers
The font size for elements at each header level. The values in this array will be multiplied by the B...
Definition: MarkdownRenderer.cs:45
VectSharp.SVG
Definition: SVGContext.cs:28
VectSharp.GraphicsPath
Represents a graphics path that can be filled or stroked.
Definition: GraphicsPath.cs:29
VectSharp.Markdown.MarkdownRenderer.CodeFont
FontFamily CodeFont
The font family for code elements.
Definition: MarkdownRenderer.cs:83
VectSharp.Markdown.MarkdownRenderer.SpaceAfterLine
double SpaceAfterLine
The space after each line of text. This value will be multiplied by the BaseFontSize.
Definition: MarkdownRenderer.cs:164
VectSharp.Markdown.MarkdownRenderer.CodeFontBoldItalic
FontFamily CodeFontBoldItalic
The font family for bold italic code elements.
Definition: MarkdownRenderer.cs:98
VectSharp.Markdown.MarkdownRenderer.ThematicBreakThickness
double ThematicBreakThickness
The thickness of thematic break lines.
Definition: MarkdownRenderer.cs:58
VectSharp.Markdown.MarkdownRenderer.CodeBlockBackgroundColour
Colour CodeBlockBackgroundColour
The background colour for code blocks.
Definition: MarkdownRenderer.cs:316
VectSharp.Markdown.MarkdownRenderer.TableHeaderRowSeparatorThickness
double TableHeaderRowSeparatorThickness
The thickness of the line separating the table header row from normal rows.
Definition: MarkdownRenderer.cs:351
VectSharp.Markdown.MarkdownRenderer.TaskListCheckedBullet
Graphics TaskListCheckedBullet
The bullet used for checked task list items.
Definition: MarkdownRenderer.cs:376
VectSharp
Definition: Brush.cs:26
VectSharp.Document
Represents a collection of pages.
Definition: Document.cs:28
VectSharp.Markdown.MarkdownRenderer.Bullets
List< Action< Graphics, Colour > > Bullets
Bullet points used for unordered lists. Each element of this list corresponds to the bullet for each ...
Definition: MarkdownRenderer.cs:265
VectSharp.Markdown.MarkdownRenderer.TableRowSeparatorColour
Colour TableRowSeparatorColour
The colour for lines separating table rows from each other.
Definition: MarkdownRenderer.cs:346
VectSharp.Page
Represents a Graphics object with a width and height.
Definition: Document.cs:48
VectSharp.Markdown.Margins.Right
double Right
The right margin.
Definition: MarkdownContext.cs:195
VectSharp.Size.Height
double Height
Height of the object.
Definition: Point.cs:155
VectSharp.Markdown.MarkdownRenderer.ImageMarginTolerance
double ImageMarginTolerance
Images will be allowed to extend into the page bottom margin area by this amount before triggering a ...
Definition: MarkdownRenderer.cs:254
VectSharp.LineCaps
LineCaps
Represents line caps.
Definition: Enums.cs:71
VectSharp.Markdown.MarkdownRenderer.SuperscriptShift
double SuperscriptShift
The upwards shift in the baseline for superscript elements. This value will be multiplied by the curr...
Definition: MarkdownRenderer.cs:204
VectSharp.Graphics.Save
void Save()
Save the current transform state (rotation, translation, scale).
Definition: Graphics.cs:524
VectSharp.Colours.White
static Colour White
White #FFFFFF
Definition: StandardColours.cs:775
VectSharp.Markdown.MarkdownRenderer.TableCellMargins
Margins TableCellMargins
The margins for table cells.
Definition: MarkdownRenderer.cs:118
VectSharp.Markdown.Margins.Top
double Top
The top margin.
Definition: MarkdownContext.cs:200
VectSharp.Font
Represents a typeface with a specific size.
Definition: Font.cs:29
VectSharp.Markdown.MarkdownRenderer.LinkUriResolver
Func< string, string > LinkUriResolver
A method used to resolve link addresses. The argument of the method should be the absolute link,...
Definition: MarkdownRenderer.cs:229
VectSharp.Graphics
Represents an abstract drawing surface.
Definition: Graphics.cs:262
VectSharp.Markdown.MarkdownRenderer.BoldItalicFontFamily
FontFamily BoldItalicFontFamily
The font family for bold italic text.
Definition: MarkdownRenderer.cs:78
VectSharp.FontFamily.ResolveFontFamily
static FontFamily ResolveFontFamily(string fontFamily)
Create a new font family from the specified family name or true type file. If the family name or the ...
VectSharp.Markdown.MarkdownRenderer.TaskListUncheckedBullet
Graphics TaskListUncheckedBullet
The bullet used for unchecked task list items.
Definition: MarkdownRenderer.cs:361
VectSharp.Markdown.MarkdownRenderer.RenderSinglePage
Page RenderSinglePage(string markdownSource, double width, out Dictionary< string, string > linkDestinations)
Parses the supplied markdownSource using all the supported extensions and renders the resulting docu...
Definition: MarkdownRenderer.cs:409
VectSharp.FontFamily
Represents a typeface.
Definition: Font.cs:421
VectSharp.Markdown.MarkdownRenderer.CodeFontItalic
FontFamily CodeFontItalic
The font family for italic code elements.
Definition: MarkdownRenderer.cs:93
VectSharp.Markdown.MarkdownRenderer.SubSuperscriptFontSize
double SubSuperscriptFontSize
The font size for subscripts and superscripts. This value will be multiplied by the current font size...
Definition: MarkdownRenderer.cs:199
VectSharp.Page.Graphics
Graphics Graphics
Graphics surface of the page.
Definition: Document.cs:62
VectSharp.Markdown.MarkdownRenderer.SpaceBeforeHeading
double SpaceBeforeHeading
The space before each heading. This value will be multiplied by the font size of the heading.
Definition: MarkdownRenderer.cs:169
VectSharp.Markdown.MarkdownRenderer.QuoteBlockBarWidth
double QuoteBlockBarWidth
The thickness of the bar to the left of block quotes.
Definition: MarkdownRenderer.cs:194
VectSharp.Markdown.MarkdownRenderer.BaseImageUri
string BaseImageUri
The base uri for resolving relative image addresses.
Definition: MarkdownRenderer.cs:214
VectSharp.TextBaselines.Top
@ Top
The current vertical coordinate determines where the top of the text string will be placed.
VectSharp.Markdown.MarkdownRenderer.ForegroundColour
Colour ForegroundColour
The foreground colour for text elements.
Definition: MarkdownRenderer.cs:286
VectSharp.SegmentType.Line
@ Line
The segment represents a straight line from the current point to a new point.
VectSharp.Markdown.MarkdownRenderer.TableVAlign
VerticalAlignment TableVAlign
The vertical alignment of table cells.
Definition: MarkdownRenderer.cs:144
VectSharp.Markdown.HTTPUtils
Contains utilities to resolve absolute and relative URIs.
Definition: HtmlTag.cs:245
VectSharp.Markdown
Definition: HtmlTag.cs:26
VectSharp.GraphicsPath.LineTo
GraphicsPath LineTo(Point p)
Move the current point and trace a segment from the previous point.
Definition: GraphicsPath.cs:66
VectSharp.Markdown.MarkdownRenderer.ImageUnitMultiplier
double ImageUnitMultiplier
The size of images (as defined in the image's width and height attributes) will be multiplied by this...
Definition: MarkdownRenderer.cs:239
VectSharp.Markdown.MarkdownRenderer.HeaderLineColour
Colour HeaderLineColour
The colour of the line below headers.
Definition: MarkdownRenderer.cs:296
VectSharp.Markdown.SyntaxHighlighter.GetSyntaxHighlightedLines
static List< List< FormattedString > > GetSyntaxHighlightedLines(string sourceCode, string language)
Performs syntax highlighting for a specified language on some source code.
Definition: SyntaxHighlighting.cs:129
VectSharp.Markdown.Margins
Represents the margins of a page.
Definition: MarkdownContext.cs:186
VectSharp.Markdown.MarkdownRenderer.RegularFontFamily
FontFamily RegularFontFamily
The font family for regular text.
Definition: MarkdownRenderer.cs:63
VectSharp.GraphicsPath.MoveTo
GraphicsPath MoveTo(Point p)
Move the current point without tracing a segment from the previous point.
Definition: GraphicsPath.cs:41
VectSharp.Markdown.MarkdownRenderer.BackgroundColour
Colour BackgroundColour
The background colour for the page.
Definition: MarkdownRenderer.cs:291
VectSharp.Markdown.MarkdownRenderer.ImageMultiplier
double ImageMultiplier
The size of images will be multiplied by this value to determine the actual size of the image on the ...
Definition: MarkdownRenderer.cs:244
VectSharp.Document.Pages
List< Page > Pages
The pages in the document.
Definition: Document.cs:32
VectSharp.FontFamily.StandardFontFamilies
StandardFontFamilies
The 14 standard font families.
Definition: Font.cs:500
VectSharp.SVG.Parser.ParseImageURI
static Func< string, bool, Page > ParseImageURI
A function that takes as input an image URI and a boolean value indicating whether the image should b...
Definition: SVGParser.cs:46
VectSharp.Markdown.MarkdownRenderer.RasterImageLoader
Func< string, RasterImage > RasterImageLoader
A method used to a load raster image from a local file. The argument of the method should be the path...
Definition: MarkdownRenderer.cs:234
VectSharp.Markdown.MarkdownRenderer.HeaderLineThicknesses
double[] HeaderLineThicknesses
The thickness of the separator line after a header of each level. A value of 0 disables the line afte...
Definition: MarkdownRenderer.cs:53
VectSharp.Size
Represents the size of an object.
Definition: Point.cs:146
VectSharp.Markdown.MarkdownRenderer.UnderlineThickness
double UnderlineThickness
The thickness of underlines. This value will be multiplied by the font size of the element being unde...
Definition: MarkdownRenderer.cs:103
VectSharp.Markdown.MarkdownRenderer.TableHeaderRowSeparatorColour
Colour TableHeaderRowSeparatorColour
The colour for the line separating the table header row from normal rows.
Definition: MarkdownRenderer.cs:341
VectSharp.Markdown.SyntaxHighlighter
Contains methods to perform syntax highlighting.
Definition: SyntaxHighlighting.cs:74
VectSharp.Markdown.MarkdownRenderer.QuoteBlockBarColour
Colour QuoteBlockBarColour
The colour for the bar to the left of block quotes.
Definition: MarkdownRenderer.cs:321
VectSharp.Size.Width
double Width
Width of the object.
Definition: Point.cs:150
VectSharp.GraphicsPath.Close
GraphicsPath Close()
Trace a segment from the current point to the start point of the figure and flag the figure as closed...
Definition: GraphicsPath.cs:295
VectSharp.Graphics.Restore
void Restore()
Restore the previous transform state (rotation, translation scale).
Definition: Graphics.cs:532
VectSharp.Markdown.MarkdownRenderer.SyntaxHighlighter
Func< string, string, List< List< FormattedString > > > SyntaxHighlighter
A method used for syntax highlighting. The first argument should be the source code to highlight,...
Definition: MarkdownRenderer.cs:259
VectSharp.Markdown.MarkdownRenderer.CodeInlineMargin
double CodeInlineMargin
The margin at the left and right of code inlines. This value will be multiplied by the current font s...
Definition: MarkdownRenderer.cs:179
VectSharp.Markdown.MarkdownRenderer.VerticalAlignment
VerticalAlignment
Defines the options for the vertical alignment of table cells.
Definition: MarkdownRenderer.cs:124
VectSharp.Markdown.MarkdownRenderer.IndentWidth
double IndentWidth
The indentation width used for list items.
Definition: MarkdownRenderer.cs:184
VectSharp.Markdown.MarkdownRenderer.SpaceAfterParagraph
double SpaceAfterParagraph
The space after each text paragraph. This value will be multiplied by the BaseFontSize.
Definition: MarkdownRenderer.cs:159
VectSharp.Point
Represents a point relative to an origin in the top-left corner.
Definition: Point.cs:28
VectSharp.Markdown.MarkdownRenderer.InsertedColour
Colour InsertedColour
The colour for text that has been styled as "inserted".
Definition: MarkdownRenderer.cs:331
VectSharp.Colours
Standard colours.
Definition: StandardColours.cs:183
VectSharp.Markdown.MarkdownRenderer.SubscriptShift
double SubscriptShift
The downwards shift in the baseline for subscript elements. This value will be multiplied by the curr...
Definition: MarkdownRenderer.cs:209
VectSharp.Markdown.MarkdownRenderer.ImageUriResolver
Func< string, string,(string, bool)> ImageUriResolver
A method used to resolve (possibly remote) image uris into local file paths. The first argument of th...
Definition: MarkdownRenderer.cs:219
VectSharp.Markdown.MarkdownRenderer.ImageSideMargin
double ImageSideMargin
The margin on the right of left-aligned images and on the left of right-aligned images.
Definition: MarkdownRenderer.cs:249
VectSharp.Markdown.MarkdownRenderer.LinkColour
Colour LinkColour
The colour for hypertext links-
Definition: MarkdownRenderer.cs:306
VectSharp.Colour.FromRgb
static Colour FromRgb(double r, double g, double b)
Create a new colour from RGB (red, green and blue) values.
Definition: Colour.cs:62
VectSharp.Markdown.MarkdownRenderer.BoldUnderlineThickness
double BoldUnderlineThickness
The thickness of underlines for bold text. This value will be multiplied by the font size of the elem...
Definition: MarkdownRenderer.cs:108
VectSharp.Markdown.Margins.Left
double Left
The left margin.
Definition: MarkdownContext.cs:190
VectSharp.Markdown.MarkdownRenderer.QuoteBlockBackgroundColour
Colour QuoteBlockBackgroundColour
The background colour for block quotes.
Definition: MarkdownRenderer.cs:326
VectSharp.Markdown.MarkdownRenderer.Render
Document Render(string markdownSource, out Dictionary< string, string > linkDestinations)
Parses the supplied markdownSource using all the supported extensions and renders the resulting docu...
Definition: MarkdownRenderer.cs:482
VectSharp.Markdown.MarkdownRenderer.Render
Document Render(MarkdownDocument mardownDocument, out Dictionary< string, string > linkDestinations)
Renders the supplied mardownDocument . The Document produced consists of one or more pages of the siz...
Definition: MarkdownRenderer.cs:495
VectSharp.Markdown.MarkdownRenderer.ItalicFontFamily
FontFamily ItalicFontFamily
The font family for italic text.
Definition: MarkdownRenderer.cs:73