Created
March 11, 2023 18:45
-
-
Save flew2bits/434ea7e849c389edcf3569c817a28b31 to your computer and use it in GitHub Desktop.
Initial implementation of Markdown Component for QuestPDF
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Markdig; | |
using Markdig.Syntax; | |
using Markdig.Syntax.Inlines; | |
using QuestPDF.Fluent; | |
using QuestPDF.Helpers; | |
using QuestPDF.Infrastructure; | |
namespace PdfExperiments; | |
public record MarkdownBlockStyle(Func<IContainer, IContainer>? ItemStyle, | |
TextStyle BlockDefaultTextStyle) | |
{ | |
public MarkdownBlockStyle(Func<IContainer, IContainer>? itemStyle) : this(itemStyle, TextStyle.Default) | |
{ | |
} | |
public static MarkdownBlockStyle Identity => new(c => c, TextStyle.Default); | |
} | |
public enum BlockType | |
{ | |
Heading1, | |
Heading2, | |
Heading3, | |
Heading4, | |
Heading5, | |
Heading, | |
Paragraph, | |
BlockQuote, | |
BlockQuoteLine, | |
} | |
public class MarkdownComponent : IComponent | |
{ | |
private readonly Dictionary<BlockType, MarkdownBlockStyle> _customStyles; | |
private readonly MarkdownDocument _parsed; | |
private static readonly Dictionary<BlockType, MarkdownBlockStyle> DefaultStyles = new() | |
{ | |
[BlockType.Heading1] = | |
new MarkdownBlockStyle(c => c.PaddingBottom(10).BorderBottom(1).PaddingBottom(4), | |
new TextStyle().FontFamily(Fonts.TimesNewRoman).FontSize(24).Bold()), | |
[BlockType.Heading2] = | |
new MarkdownBlockStyle(c => c.PaddingBottom(6).BorderBottom(1).BorderColor(Colors.Grey.Medium).PaddingBottom(2), | |
new TextStyle().FontFamily(Fonts.TimesNewRoman).FontSize(21).Bold()), | |
[BlockType.Heading3] = | |
new MarkdownBlockStyle(c => c, new TextStyle().FontFamily(Fonts.TimesNewRoman).FontSize(18).Bold()), | |
[BlockType.Heading4] = | |
new MarkdownBlockStyle(c => c, new TextStyle().FontSize(16).Bold()), | |
[BlockType.Heading5] = | |
new MarkdownBlockStyle(c => c, new TextStyle().FontSize(14).Bold()), | |
[BlockType.Heading] = | |
new MarkdownBlockStyle(c => c, new TextStyle().FontFamily(Fonts.TimesNewRoman).FontSize(12).Bold()), | |
[BlockType.BlockQuote] = | |
new MarkdownBlockStyle(c => | |
c.PaddingHorizontal(36).PaddingVertical(18) | |
.BorderLeft(4).BorderColor(Colors.Green.Medium).PaddingBottom(-18).PaddingLeft(18)), | |
[BlockType.BlockQuoteLine] = | |
new MarkdownBlockStyle(c => c.PaddingBottom(18), new TextStyle().FontFamily(Fonts.TimesNewRoman).Italic().FontSize(14)), | |
[BlockType.Paragraph] = | |
new MarkdownBlockStyle(c => c.PaddingBottom(9)) | |
}; | |
public MarkdownComponent(string markdown, Dictionary<BlockType, MarkdownBlockStyle>? customStyles = null) | |
{ | |
_customStyles = customStyles ?? new Dictionary<BlockType, MarkdownBlockStyle>(); | |
_parsed = Markdown.Parse(markdown); | |
} | |
private MarkdownBlockStyle GetStyler(BlockType blockType) | |
=> | |
_customStyles.TryGetValue(blockType, out var styler) | |
? styler | |
: DefaultStyles.TryGetValue(blockType, out var defaultStyler) | |
? defaultStyler | |
: MarkdownBlockStyle.Identity; | |
public void Compose(IContainer container) | |
{ | |
container.Column(columnDescriptor => | |
{ | |
foreach (var block in _parsed) | |
{ | |
RenderBlock(columnDescriptor, block); | |
} | |
}); | |
} | |
private void RenderBlock(ColumnDescriptor columnDescriptor, Block? block, | |
MarkdownBlockStyle? subStyle = null) | |
{ | |
if (block is null) return; | |
switch (block) | |
{ | |
case HeadingBlock heading: | |
{ | |
if (heading.Inline is null) break; | |
var style = heading.Level switch | |
{ | |
1 => BlockType.Heading1, | |
2 => BlockType.Heading2, | |
3 => BlockType.Heading3, | |
4 => BlockType.Heading4, | |
5 => BlockType.Heading5, | |
_ => BlockType.Heading | |
}; | |
var headingStyler = GetStyler(style); | |
var textStyle = headingStyler.BlockDefaultTextStyle; | |
var item = columnDescriptor.Item(); | |
headingStyler.ItemStyle(item).Text(t => RenderInline(t, heading.Inline, textStyle)); | |
break; | |
} | |
case ParagraphBlock paragraph: | |
{ | |
if (paragraph.Inline is null) break; | |
var paragraphStyler = subStyle ?? GetStyler(BlockType.Paragraph); | |
var blockItem = columnDescriptor.Item(); | |
blockItem = paragraphStyler.ItemStyle.Invoke(blockItem); | |
blockItem.Text(textDescriptor => | |
{ | |
foreach (var item in paragraph.Inline) | |
RenderInline(textDescriptor, item, paragraphStyler.BlockDefaultTextStyle); | |
}); | |
break; | |
} | |
case QuoteBlock quote: | |
var blockQuoteStyler = GetStyler(BlockType.BlockQuote); | |
var blockQuoteTextStyle = blockQuoteStyler.BlockDefaultTextStyle; | |
var blockQuoteItem = blockQuoteStyler.ItemStyle(columnDescriptor.Item()); | |
blockQuoteItem.Column(column => | |
{ | |
foreach (var quoteLine in quote) | |
RenderBlock(column, quoteLine, GetStyler(BlockType.BlockQuoteLine)); | |
}); | |
break; | |
default: | |
Console.WriteLine(block.GetType().Name); | |
break; | |
} | |
} | |
private static void RenderParagraph(TextDescriptor text, ParagraphBlock paragraph) | |
{ | |
if (paragraph.Inline is null) return; | |
var textStyle = new TextStyle(); | |
foreach (var item in paragraph.Inline) | |
{ | |
RenderInline(text, item, textStyle); | |
} | |
} | |
private static void RenderInline(TextDescriptor text, Inline inline, TextStyle textStyle) | |
{ | |
switch (inline) | |
{ | |
case LiteralInline literal: | |
text.Span(literal.Content.ToString()).Style(textStyle); | |
break; | |
case EmphasisInline emphasis when emphasis.Any(): | |
{ | |
foreach (var child in emphasis) | |
{ | |
switch (emphasis.DelimiterCount) | |
{ | |
case 1: | |
RenderInline(text, child, textStyle.Italic()); | |
break; | |
case 2: | |
RenderInline(text, child, textStyle.Bold()); | |
break; | |
default: | |
RenderInline(text, child, textStyle); | |
break; | |
} | |
} | |
break; | |
} | |
case ContainerInline container: | |
foreach (var item in container) | |
{ | |
RenderInline(text, item, textStyle); | |
} | |
break; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment