Skip to content

Instantly share code, notes, and snippets.

@flew2bits
Created March 11, 2023 18:45
Show Gist options
  • Save flew2bits/434ea7e849c389edcf3569c817a28b31 to your computer and use it in GitHub Desktop.
Save flew2bits/434ea7e849c389edcf3569c817a28b31 to your computer and use it in GitHub Desktop.
Initial implementation of Markdown Component for QuestPDF
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