Skip to content

Coming from QuestPDF

Papyra’s API is openly influenced by QuestPDF - if you’ve composed documents there, the builder style will feel familiar. The differences are architectural: Papyra renders one document model to four formats through its own PDF engine, while QuestPDF renders to PDF (and images) through SkiaSharp.

Papyra when you need DOCX/XLSX from the same composition, archival PDF/A output verified against veraPDF, or a dependency tree without native binaries (no SkiaSharp).

Wondering how both compare to DevExpress Reporting, Telerik Reporting and the other .NET reporting suites? That is a different category - see the comparison page.

QuestPDF when you need its mature layout engine today - rich cells and its broad element catalog are ahead of Papyra right now (see known limitations - we publish this honestly).

QuestPDF Papyra
Document.Create(container => container.Page(...)) Document.Create(doc => ...) - no page wrapper; page setup is doc.PageSize(...), doc.Margin(...)
page.Header() / page.Footer() doc.Header(h => ...) / doc.Footer(f => ...)
page.Content().Column(...) implicit - builder calls stack vertically
.Text("…").FontSize(10).Bold() .Text("…", TextStyle.Default.WithFontSize(10).Bold()) - chainable the same way, starting from TextStyle.Default
.Strikethrough() / .Superscript() / .LetterSpacing(0.1f) TextStyle.Default.WithStrikethrough() / .Superscript() / .WithLetterSpacing(1.5f) - spacing is absolute points per glyph, not an em factor
.Width(120).Image(x) / .Height(40).Image(x) / .Image(x).FitArea() doc.Image(x, width: 120f) / (height: 40f) / both together = contain-in-box; alignment: replaces .AlignCenter() around the image
.Svg(svg) / .Svg(svg).FitArea() doc.Svg(bytes, ...) from the optional Papyra.Svg package - same sizing parameters as Image. QuestPDF rasterizes SVG through Skia; Papyra emits true PDF vectors where the SVG’s features allow it
hand-built bullets (row.ConstantItem(12).Text("•")) doc.List(l => l.Item("…", sub => sub.Item("nested"))) - native nesting, number formats, start values
column.Spacing(12) new ParagraphStyle(SpacingAfter: 12f) per paragraph or via doc.DefaultParagraphStyle(...) - between non-paragraph blocks (tables, rows), insert a spacer paragraph
row.RelativeItem(2) / row.ConstantItem(210) row.RelativeItem(item => ..., weight: 2f) / row.FixedItem(210f, item => ...)
table.ColumnsDefinition(c => c.ConstantColumn(48).RelativeColumn()) table.Columns(c => c.FixedColumn(48f).RelativeColumn())
table.Header(...) (repeats) table.Header(row => ..., TableRowRepeat.EveryPage) - footers can repeat too
cell.Element(...) composition Cell(p => ...) takes the full paragraph API - mixed runs, links, images
Colors.Blue.Medium Colors.Blue.Medium - same palette, same names
QuestPDF.Settings.License = LicenseType.Community <PapyraLicense>…</PapyraLicense> in the project file, checked at build time (licensing)
.GeneratePdf(path) document.RenderAsPdf() returns byte[] - plus DOCX and XLSX

The Papyra repository ships the same invoice implemented twice - once per library - so you can diff real code instead of toy snippets:

Terminal window
dotnet run --project Samples
# writes invoice-sample-….pdf (Papyra), invoice-sample-….docx, invoice-sample-….xlsx
# and invoice-sample-…-questpdf.pdf (QuestPDF) side by side

Compare Samples/PapyraInvoiceReport.cs with Samples/QuestPdfInvoiceReport.cs.

The shape is close: both compose a header, address rows, a line-item table with repeating header, totals, and payment details. The Papyra version has no Page/Content nesting; styles (TextStyle, ParagraphStyle, CellStyle) are immutable records, buildable either with named arguments (new TextStyle(FontSize: 10f, ...)) or chained fluently from a .Default (TextStyle.Default.WithFontSize(10f)...) - same end result, pick whichever reads better at the call site.

Moving from QuestPDF you will notice these gaps first:

  • Automatic hyphenation is opt-in via doc.Hyphenation("de") (seven embedded languages); soft hyphens and CJK line breaking work out of the box
  • Page numbers work in headers/footers and in body text (QuestPDF’s CurrentPageNumber()/TotalPages() map to PageNumber()/TotalPages()/PageXofY()); no ShowOnce/SkipOnce variants
  • Smaller element catalog: no Decoration, Inlined, Layers, canvas drawing (a public vector drawing API is on the roadmap)
  • document.RenderAsDocx() and XLSX from the unchanged composition
  • PDF/A-2b/3b/4 with veraPDF-verified conformance
  • Three managed dependencies, no native SkiaSharp binaries to deploy
  • The companion app (beta): live preview with click-to-source and layout issue detection