End QuestPDF and Markdig across Novolis
Policies that keep the org coherent
name: End QuestPDF Markdig overview: Eliminate QuestPDF and Markdig from all Novolis repos by routing text PDFs through Documents/Skia, growing Novolis.Markup.Markdown into the sole parser/HTML path, rewriting score PDF on Skia, then deleting the packages from CPM. todos: [] isProject: false
Definition of done
No PackageReference / PackageVersion for QuestPDF or Markdig remains under d:\novolis. No using QuestPDF / using Markdig in product or test code. CI and verify-nuget-only stay green. BooksWriterStudio, manuscript print, Avalonia markdown preview, novolis-mdpdf, and MIDI score PDF all run without those packages.
Current dependency map
flowchart LR
subgraph keep [Already Markdig/QuestPDF free]
MD["Novolis.Markup.Markdown"]
MDD["Markdown.Documents"]
Docs["Documents + Skia"]
Tool["Tools.MarkdownPdf"]
MD --> MDD --> Docs
Tool --> Docs
end
subgraph kill [Must retire]
Render["Markdown.Rendering"]
Ava["Avalonia.Markdown"]
ExpPdf["Manuscript.Export.Pdf"]
Score["Audio.Midi ScorePdfExporter"]
Markdig["Markdig 0.41.3"]
Quest["QuestPDF 2025.7.4"]
Render --> Markdig
Render --> Quest
Ava --> Markdig
ExpPdf --> Markdig
ExpPdf --> Quest
Score --> Quest
endLive producers today
| Surface | Repo / project | Stack |
|---|---|---|
| Books + reference PDF/HTML/TXT | [Novolis.Manuscript.Export.Pdf](d:\novolis\novolis-manuscript\src\Novolis.Manuscript.Export.Pdf) | Markdig AST + QuestPDF |
| Generic MD PDF/HTML | [Novolis.Markup.Markdown.Rendering](d:\novolis\novolis-markup\src\Novolis.Markup.Markdown.Rendering) | Markdig + QuestPDF |
| Studio GFM preview | [Novolis.Avalonia.Markdown](d:\novolis\novolis-avalonia\src\Novolis.Avalonia.Markdown) | Markdig HTML + Mermaid |
| Score PDF | [ScorePdfExporter.cs](d:\novolis\novolis-audio\src\Novolis.Audio.Midi\ScorePdfExporter.cs) | QuestPDF + SVG |
| Orphan ref | [MusicMakerLab.csproj](d:\novolis\novolis-dogfooding\apps\avalonia\MusicMakerLab\MusicMakerLab.csproj) | QuestPDF unused |
Target architecture
flowchart TB
rawMd["Raw Markdown / chapter files"]
novMd["Novolis.Markup.Markdown\nParse + ToHtml"]
meta["ManuscriptMetadata / assembler"]
paged["PagedDocument"]
skiaDoc["Documents.Skia DocumentPdf"]
skiaScore["Audio Skia score PDF"]
preview["Avalonia MarkdownPreviewPane"]
rawMd --> novMd
meta -->|"structured TextBox + blocks"| paged
novMd -->|"body blocks via Markdown.Documents"| paged
paged --> skiaDoc
novMd -->|"HTML"| preview
MusicScore["MusicScore"] --> skiaScoreOwnership decisions (fixed)
- All prose/report/book PDF →
Novolis.Documents+.Layout+.Skiaonly. Never reintroduce QuestPDF. - Sole Markdown engine → grow `Novolis.Markup.Markdown` (Parse AST +
MarkdownToHtmlConverter). Markdig is not replaced by a different third-party parser. - Manuscript print maps domain → `PagedDocument` (metadata panels from
ManuscriptMetadata, not MarkdigQuoteBlockdetection). Assembled reader.mdmay keep> [!tag]for human files, but PDF must not require Markdig quote merging. - Score PDF stays in
Novolis.Audio.Midi, rewritten to SkiaSharp `SKDocument` (reuse existing SVG builders via Svg.Skia or draw with SKCanvas). Do not force scores through the one-columnPagedDocumentmodel. - `MarkdownPdfExporter` (QuestPDF) is deleted; callers use
MarkdownDocumentPdfExporter/novolis-mdpdf. - `Novolis.Markup.Markdown.Rendering` becomes Markdig/QuestPDF-free HTML helpers over Novolis MD, or is folded into
Novolis.Markup.Markdown/ deleted once Avalonia and manuscript HTML move — end state: package either gone or thin and Markdig-free.
Phase 0 — Guardrails (governance + inventory lock)
- Add a short policy note under novolis-governance (or extend nuget/library docs): QuestPDF and Markdig are forbidden; PDF = Documents/Skia; Markdown =
Novolis.Markup.Markdown. - Optional analyzer / script check listing banned package IDs (same spirit as layer-boundary verify).
- Update documents-vs-questpdf canvas status as phases land (tracking only).
Phase 1 — Quick kills (same week)
- Remove unused QuestPDF
PackageReferencefrom MusicMakerLab. - Retarget any remaining samples/docs that advertise
MarkdownPdfExporterto Documents /novolis-mdpdf. - Delete or obsolete `MarkdownPdfExporter.cs` and its tests; drop QuestPDF from Markup.Rendering CPM once no other file in that package uses it.
- Strip redundant
QuestPDF.Settingsbootstrap from BooksWriterStudio Program.cs only after Export.Pdf no longer needs the package (Phase 3 end) — until then leave it.
Exit: Markup no longer references QuestPDF. Dogfood/tools already Documents-only.
Phase 2 — Grow Novolis Markdown + Documents blocks
Work in novolis-markup and novolis-documents.
Parser / HTML (`Novolis.Markup.Markdown`) — expand MarkdownDocument.Parse + HTML converter to cover what manuscript + Avalonia actually need:
- Fenced / indented code blocks
- Thematic breaks (
---) - Pipe tables (already partial) with stable cell model
- Nested lists depth used in books/reference
- Inline emphasis, code spans, links (AST runs — required for preview HTML; PDF may still flatten to plain until Documents gains inline runs)
- Callout /
> [!tag] valueas first-class nodes (not only mapper heuristics) - Keep Mermaid as Avalonia HTML post-process (no Markdig required)
Documents blocks (close books/reference gaps from prior analysis):
CodeBlock(mono + fill) in model + paginator + Skia- First-class or improved list placement (hanging indent), not only
•paragraphs - Mapper updates in MarkdownPagedDocumentMapper
- Themes in MarkdownPdfThemes pick up code/list styling
Exit: Golden tests: Calypso chapter-144 + a reference-manual fixture through Markdown.Documents match acceptance A1–A3 shape (cover/TOC/chapter breaks/footers/metadata TextBox/tables/code).
Phase 3 — Manuscript off Markdig + QuestPDF
Work in novolis-manuscript.
flowchart LR
asm["BookPrintAssembler / metadata"]
bridge["ManuscriptPagedDocumentBuilder NEW"]
pdf["DocumentPdf.Write"]
html["MarkdownToHtml / emitters"]
asm --> bridge --> pdf
asm --> html- Add `ManuscriptPagedDocumentBuilder` in
Export.Pdf(or sibling package) that buildsPagedDocumentfrom assembled chapters +ManuscriptPrintSettings(trim 6×9, cover, TOC, H1 breaks, footer{page}, metadata →TextBoxBlockfrom structured tags — port logic from ChapterMetadata* / QuestPdfBlockRenderer without Markdig types). - Body markdown:
MarkdownDocument.Parse→ existing Documents mapper (not Markdig walk). - Dual-run behind env/CLI flag (
NOVOLIS_MANUSCRIPT_PDF=documentsor--engine documents) with QuestPDF default until goldens pass; then flip default to Documents; then deleteQuestPdfDocuments,QuestPdfBlockRenderer, Markdig pipeline files, and PackageReferences. - Retarget HTML/TXT companions in ManuscriptDocumentEmitters to Novolis MD HTML/plain (port
PlainTextRendereronto Novolis AST). - Rewrite unit tests that
Markdown.ParseMarkdig or assert QuestPDF-only branches; keep behavioral tests (PDF magic bytes, metadata in HTML, list/table TXT, page count smoke on Calypso folder). - Export.Markdown stops depending on Markdig Rendering HTML; use Novolis
ToHtml.
Exit: Novolis.Manuscript.Export.Pdf and unit tests have zero Markdig/QuestPDF refs. BooksWriterStudio + manuscript-cli print produce Documents PDFs.
Phase 4 — Avalonia preview off Markdig
Work in novolis-avalonia.
- Rewrite MarkdownPreviewPipeline to
MarkdownDocument.Parse→MarkdownToHtmlConverter+ existing Mermaid rewrite. - Drop Markdig from Avalonia CPM / csproj.
- Adjust preview tests to Novolis HTML (GFM subset parity for tables/code/lists/headings used in studios).
Exit: Novolis.Avalonia.Markdown Markdig-free.
Phase 5 — Score PDF off QuestPDF
Work in novolis-audio.
- Replace QuestPDF page chrome in
ScorePdfExporterwith SkiaSharp PDF pages (landscape A4, header/footer, multi-page systems). - Keep orchestral / piano-roll SVG generation; render via Svg.Skia into the PDF canvas (same approach Documents uses for SVG images).
- Remove QuestPDF from Audio.Midi + MusicMakerLab leftovers; drop
EnsureCommunityLicense. - Update audio unit tests for byte/file export.
Exit: No QuestPDF in novolis-audio / apps CPM.
Phase 6 — Package retirement and org cleanup
- Delete or gut Markdig/QuestPDF surfaces in
Markdown.Rendering; update novolis-markup README and package docs. - Remove
PackageVersionentries from: manuscript, markup, avalonia, audio, apps, dogfoodingDirectory.Packages.props. - Grep gate: zero hits for
QuestPDF/Markdigpackage IDs outside historical docs/plans (or mark plans superseded). - Publish bumped packages via normal main push → GPR; consumers restore nuget.org + GitHub only.
- Run
pwsh -File d:\novolis\novolis-governance\scripts\verify-nuget-only.ps1and platform ProjectRef build of affected slnx.
Suggested execution order and ownership
| Phase | Primary repos | Unblocks |
|---|---|---|
| 1 | markup, dogfooding | QuestPDF count ↓ |
| 2 | markup, documents, tools | Books-grade fidelity |
| 3 | manuscript, apps | Largest QuestPDF+Markdig surface |
| 4 | avalonia | Last Markdig product surface |
| 5 | audio | Last QuestPDF product surface |
| 6 | all CPM + docs | Definition of done |
Do not route books through the old Markup MarkdownPdfExporter at any step. Do not put book/domain flags into Novolis.Documents public API — keep manuscript chrome in manuscript builder + Documents primitives.
Main risks (accept and mitigate)
- Parser fidelity: Novolis MD must reach “good enough GFM” for Calypso + studio preview or Phase 3/4 stall — invest in Phase 2 tests before flipping manuscript default.
- Page reflow: Documents line-breaks ≠ QuestPDF; freeze page-count / TOC goldens on a real Calypso tree before default flip.
- Score rewrite: Independent Skia work; schedule after books so license pressure drops first where Community eligibility matters most.
- External books repo:
D:\repos\booksonly shells to manuscript-cli — no QuestPDF code there; regenerating prints after Phase 3 is the validation.
Out of scope
- Becoming a QuestPDF-style constraint layout engine inside Documents.
- Replacing YamlDotNet or Mermaid.
- Pixel-identical PDFs vs historical QuestPDF output (behavioral + visual sign-off, not bitwise).