Skip to content

feat: add tables, task lists, images, alerts and syntax highlighting to Markdown - #37

Merged
InDieTasten merged 3 commits into
mainfrom
feat/markdown-extensions
Oct 8, 2026
Merged

InDieTasten merged 3 commits into
mainfrom
feat/markdown-extensions

Conversation

@rsubrama83

Copy link
Copy Markdown
Collaborator

Closes #33

What

Extends ShinyPDF.Markdown with the remaining items from #33:

  • Tables: pipe tables with a bold header that repeats on every page, plus column alignment (:--, :-:, --:).
  • Task lists: - [ ] / - [x] show a checkbox instead of the bullet.
  • Images: data: URIs render directly. Other URLs go through the new options.ImageResolver. By default Markdown never reads files or the network. Images that are missing or can't be decoded fall back to their alt text.
  • HTML entities: decoded (covered by tests). Raw HTML is still shown as plain text.
  • Alerts: > [!NOTE], TIP, IMPORTANT, WARNING, CAUTION, with titles and colors configurable through options.AlertStyles.
  • Syntax highlighting: regex-based, with built-in rules for C#, JS/TS, JSON, XML/HTML, CSS, SQL, Python, Shell, PowerShell and YAML. These are available as templates in SyntaxLanguages. Custom languages are added via new SyntaxLanguage(...).Rule(...), colors are set through options.SyntaxColors.
  • Mermaid / LaTeX: no built-in renderer. The new options.CodeBlockRenderers["mermaid"] = (container, code) => ... hook lets consumers plug in their own rendering.

Behavior change: pipe tables and task lists were plain text before and now render as tables and checkboxes. No public API is removed or changed.

Example

Verification

  • dotnet build src/ShinyPDF.slnx: 0 errors
  • dotnet test src/ShinyPDF.UnitTests: 215 passed (30 new Markdown tests)
  • MarkdownExtendedFeatures example rendered and checked visually
  • dotnet pack for ShinyPDF.Markdown succeeds

🤖 Generated with Claude Code

…to Markdown

- Pipe tables with bold repeating header and column alignment
- Task list checkboxes
- Images from data: URIs or a user-provided ImageResolver (no file or
  network access by default), alt text as fallback
- GitHub alerts with configurable titles and colors
- Regex-based syntax highlighting with built-in languages, extensible
  through SyntaxLanguage and SyntaxLanguages templates
- CodeBlockRenderers hook for custom rendering such as Mermaid or math

Refs #33

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Unresolved rendering defects can prevent PDF generation and produce incorrect output.

5 open findings
What changed in this PR

Extends ShinyPDF.Markdown with advanced Markdown rendering for the fluent PDF API, addressing #33.

Changes:

  • Adds tables, task lists, alerts and resolver-backed images.
  • Adds configurable syntax highlighting and custom code-block renderers.
  • Expands tests, examples and documentation.
File Description
src/​ShinyPDF.UnitTests/​MarkdownTests.cs Tests new rendering features and highlighting.
src/​ShinyPDF.Markdown/​SyntaxTokenKind.cs Defines highlighting categories.
src/​ShinyPDF.Markdown/​SyntaxLanguages.cs Provides built-in language rules.
src/​ShinyPDF.Markdown/​SyntaxLanguage.cs Implements configurable regex tokenization.
src/​ShinyPDF.Markdown/​PackageReadme.md Updates supported-feature documentation.
src/​ShinyPDF.Markdown/​MarkdownRenderer.cs Implements the new rendering paths.
src/​ShinyPDF.Markdown/​MarkdownOptions.cs Adds styling, image and renderer options.
src/​ShinyPDF.Markdown/​MarkdownExtensions.cs Updates public API documentation.
src/​ShinyPDF.Markdown/​MarkdownAlertStyle.cs Defines configurable alert styling.
src/​ShinyPDF.Examples/​MarkdownExamples.cs Demonstrates extended Markdown features.
docs/​how-to/​render-markdown.md Documents configuration and usage.
AGENTS.md Updates Markdown implementation status.

🧠 Review effort: Balanced


Give feedback about Copilot approvals in this survey to enter a drawing for a $150 gift card.

Comment thread src/ShinyPDF.Markdown/MarkdownRenderer.cs Outdated
Comment thread src/ShinyPDF.Markdown/MarkdownRenderer.cs Outdated
Comment thread src/ShinyPDF.Markdown/MarkdownRenderer.cs Outdated
Comment thread src/ShinyPDF.Markdown/MarkdownRenderer.cs Outdated
Comment thread src/ShinyPDF.Markdown/SyntaxLanguages.cs Outdated
…hlighting

- Align each text line in centered and right-aligned table columns
- Fully decode images before rendering; incomplete data falls back to alt text
- Keep the enclosing link on rendered images
- Bound image height with MaxImageHeight so tall images fit on a page
- Highlight full unquoted shell variable names

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Image decoding and syntax highlighting lack resource limits for untrusted Markdown.

2 open findings
5 resolved since last review
Previously missed (3)

In code that hasn't changed since last review

Medium severity Preserve explicit left alignment in RTL tables

src/​ShinyPDF.Markdown/​MarkdownRenderer.cs:330

Explicit left-aligned columns (:--) map to null, and ComposeText never calls AlignLeft(). Under ContentFromRightToLeft(), a null alignment defaults to right (src/ShinyPDF/Elements/Text/TextBlock.cs:56-63), so these columns ignore their Markdown alignment. Map TableColumnAlign.Left explicitly and apply text.AlignLeft(). Update the tests that expect null for these cells and add a right-to-left rendering case.

Medium severity Treat empty keyword and type lists as no-ops

src/​ShinyPDF.Markdown/​SyntaxLanguage.cs:53

An empty Keywords() or Types() list registers a zero-width regex instead of doing nothing. If a string rule is added afterward, the empty rule can win at an opening quote; Tokenize skips that match, and regex iteration advances past the quote before the string rule can match it. Treat an empty word list as a no-op so it cannot suppress later highlighting rules.

Medium severity Preserve capture numbering across highlighting rules

src/​ShinyPDF.Markdown/​SyntaxLanguage.cs:94

Combining independently validated patterns changes their numeric backreferences because capture numbering is shared. For example, (a)\1 and (b)\1 each match doubled letters alone, but the second rule's \1 now refers to the first rule's capture, so bb is not highlighted. Preserve each rule's capture semantics, for example by matching regexes independently and selecting the earliest match, with rule order breaking ties. Add a regression test with both rules.

🧠 Review effort: Balanced


Give feedback about Copilot approvals in this survey to enter a drawing for a $150 gift card.

Comment thread src/ShinyPDF.Markdown/MarkdownRenderer.cs
Comment thread src/ShinyPDF.Markdown/SyntaxLanguage.cs Outdated
- MaxImagePixels (default 40 MP) is checked from the image header before
  any pixel buffer is allocated; larger images show their alt text
- SyntaxHighlightingTimeout (default 500 ms) bounds highlighting of one
  code block, both per regex match and in total; the rest stays plain text
- Unterminated block comments and multi-line strings run to the end of
  the code instead of rescanning the input for every opening marker

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Unresolved rendering and custom-highlighting correctness issues need fixes.

0 open findings

2 resolved since last review
Previously missed (3)

In code that hasn't changed since last review

Medium severity Explicit left alignment falls through to null

src/​ShinyPDF.Markdown/​MarkdownRenderer.cs:330

Explicit left alignment (:--) falls through to null. In a document using ContentFromRightToLeft(), TextBlock.SetDefaultAlignment then chooses Right (src/ShinyPDF/Elements/Text/TextBlock.cs:56-63), contradicting the table's alignment marker. Map TableColumnAlign.Left to HorizontalAlignment.Left and handle it with text.AlignLeft() in ComposeText. Update the existing assertions that expect null for these cells and cover a right-to-left document.

Medium severity Support percent-encoded non-base64 data URI images

src/​ShinyPDF.Markdown/​MarkdownRenderer.cs:463

The ;base64 requirement rejects valid percent-encoded data URIs, such as a PNG supplied as data:image/png,%89PNG.... These images always fall back to alt text despite the documented data URI support. Decode percent escapes directly to bytes for non-base64 payloads, without interpreting binary image bytes as UTF-8, and test both encoding forms.

Medium severity Combined regexes break numbered backreferences

src/​ShinyPDF.Markdown/​SyntaxLanguage.cs:126

Combining rules into one regex changes numbered backreferences. After a (#).* rule, a string rule such as (['"]).*?\1 references the first rule's capture rather than its own quote, so valid quoted strings no longer match. Preserve each rule's capture scope, for example by compiling rules separately and selecting the earliest match with rule order breaking ties. Keep the timeout fallback and add a regression test with captures in multiple rules.

🧠 Review effort: Balanced


Give feedback about Copilot approvals in this survey to enter a drawing for a $150 gift card.

@InDieTasten InDieTasten left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@InDieTasten
InDieTasten merged commit 806ab70 into main Oct 8, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add markdown rendering

3 participants