Troubleshooting
Runtime errors and their fixes. Organised by symptom.
”Unable to load DLL ‘Pragmatic.Pdf.Native.dll’” at startup
Section titled “”Unable to load DLL ‘Pragmatic.Pdf.Native.dll’” at startup”The PDF renderer is native-backed. The native binary ships in the NuGet under runtimes/{rid}/native/. If it isn’t resolved:
- Verify the RID is supported. Currently
win-x64,linux-x64,osx-arm64. For other platforms, open an issue. - Check your publish output. For self-contained deployments the native binary must be next to the app (
dotnet publish -r win-x64 --self-contained). - Check Docker images. Pragmatic.Documents.Pdf needs a base image with glibc on Linux —
alpine(musl) is not supported today.
PDF opens but renders blank pages
Section titled “PDF opens but renders blank pages”- Did you call
.Page(p => ...)? ADocumentModelwith zero pages produces a 0-page PDF. - Did every page builder add at least one node? An empty
.Page(p => { })emits an empty page, which some viewers elide.
Fonts fall back to a default (Helvetica, Arial)
Section titled “Fonts fall back to a default (Helvetica, Arial)”The renderer couldn’t find the requested font family. Options:
- Use the default fonts (Helvetica/Times/Courier) which are embedded in every PDF viewer
- Register a custom font via
PdfResources.AddFont(name, bytes)and reference it inNodeStyle.FontFamily
PDF size is unexpectedly large
Section titled “PDF size is unexpectedly large”- Embedded images are stored as-is. Compress them before
AddImageif they’re larger than necessary. - Embedded fonts add size — every font is shipped inside the PDF. Use system fonts when portability isn’t a concern.
Table of contents is empty in Word
Section titled “Table of contents is empty in Word”The TOC is a field. Press F9 in Word, or pass UpdateFieldsOnOpen = true in DocxRenderOptions so Word refreshes automatically:
var options = new DocxRenderOptions { UpdateFieldsOnOpen = true };new DocxRenderer().RenderTo(fs, doc, options: options);Headings don’t look like headings
Section titled “Headings don’t look like headings”DOCX heading levels map to the document’s Heading1..Heading9 paragraph styles. If the target Word template has different styles for those names (or no styles at all), appearance will vary.
Force visual styles by combining a heading + a styled TextNode:
.Heading("Title", level: 1) // sets the outline level (for TOC).Paragraph(new TextNode { // sets the visual appearance Content = "Title", Style = new NodeStyle { FontSize = 28, FontWeight = FontWeight.Bold, Color = "#1F497D" }})Or pass a DocxTheme to customise the built-in style definitions.
Hyperlinks work but aren’t coloured
Section titled “Hyperlinks work but aren’t coloured”The default hyperlink style in DOCX is defined by the template (styles.xml). The renderer emits the hyperlink correctly — if it shows up as black text, the consuming template has overridden the Hyperlink style.
Formulas show as text (=SUM(...) instead of the computed value)
Section titled “Formulas show as text (=SUM(...) instead of the computed value)”- You used
.Row("=SUM(A2:A10)")instead of.FormulaRow(...). Plain.Row(value)writes the string literally. - Use
.FormulaRow("=SUM(A2:A10)")or build aCell { Formula = "..." }.
Freeze / merge not respected
Section titled “Freeze / merge not respected”Freeze(rows, columns)must be called before any rows — the freeze configuration is per-sheet metadata, not per-row.Merge("A1", "C1")coordinates must be valid cell references; malformed references throw anArgumentExceptionat render time.
Opened XLSX has lost formatting applied in Excel
Section titled “Opened XLSX has lost formatting applied in Excel”If you read → modify → write a file that used features the model doesn’t represent (pivot tables, conditional formatting, charts), those are dropped on read. Keep the original file and apply modifications via a dedicated XLSX tool if you need to preserve them.
Cells with embedded commas appear split
Section titled “Cells with embedded commas appear split”The writer quotes cells that contain the delimiter, ", or newline. If you’re seeing split cells in the output, inspect the actual bytes:
"hello, world","regular" ✅ quoted correctlyhello, world,regular ❌ malformed — shouldn't happen from CsvWriterIf CsvWriter produced unquoted output, file an issue with the input.
Excel shows every row in column A
Section titled “Excel shows every row in column A”Excel’s CSV auto-detection picks the delimiter based on the system locale. An Italian Excel expects ; and treats ,-separated CSV as “all one column”.
Export with the locale-appropriate delimiter:
var italian = new CsvOptions { Delimiter = ';', DecimalSeparator = ',' };CsvWriter.Write(fs, book, italian);Encoding characters appear corrupted
Section titled “Encoding characters appear corrupted”UTF-8 without BOM is the default. Some Excel versions misinterpret UTF-8 without BOM. Force a BOM for broader compatibility:
var options = new CsvOptions { WriteByteOrderMark = true };HTML email
Section titled “HTML email”Outlook (desktop) renders the email with the wrong width
Section titled “Outlook (desktop) renders the email with the wrong width”- Ensure
Widthis set (new EmailBuilder().Width(600)); Outlook respects the MSO-conditional wrapper table the renderer emits. - Don’t replace the output’s wrapper table by post-processing the HTML — it’s load-bearing for Outlook.
Preheader shows up in the visible body
Section titled “Preheader shows up in the visible body”The preheader is hidden text at the top of the body; some clients with unusual CSS overrides may still show it. This is a known email-client quirk across the industry. You can reduce the preheader text to 1-2 words as a workaround.
Images are blocked by default
Section titled “Images are blocked by default”Gmail, Outlook, and most mobile clients block remote images until the user clicks “show images”. Always set alt text and make sure the email reads without images. The renderer emits alt from Image(src, alt, ...).
Markup
Section titled “Markup”MarkupParseException: Root element must be <document>
Section titled “MarkupParseException: Root element must be <document>”The PDX-Doc parser expects <document> as the root. If you have a different root, or no XML declaration, re-author with the right wrapper:
<document title="..."> <page> <!-- ... --> </page></document>XmlException on parse
Section titled “XmlException on parse”PDX uses well-formed XML. Stray < characters, unclosed elements, unescaped & all break parsing. Escape entity characters inside text:
<text>Q&A session</text> <!-- not "Q&A session" -->Expressions render literally as {{ foo.bar }}
Section titled “Expressions render literally as {{ foo.bar }}”You parsed the markup into a DocumentTemplate but didn’t resolve it with data. Templates must go through a resolver:
var template = PdxDocParser.Parse(markup);var resolver = new DocumentTemplateResolver(PipeRegistry.Default.WithI18N());var model = await resolver.ResolveAsync(template, data); // ← this stepPdfRenderer.RenderTo(fs, model);Templating
Section titled “Templating”Missing data leaves the output empty
Section titled “Missing data leaves the output empty”Intentional: missing properties render as null. Use | default:"..." for fallbacks, and inspect resolver.Warnings to see which expressions couldn’t resolve.
Pipe not found
Section titled “Pipe not found”PipeRegistry.Default has 5 core pipes (upper, lower, trim, default, number). Currency, date, percent, translate need I18N:
var resolver = new DocumentTemplateResolver(PipeRegistry.Default.WithI18N());For custom pipes, register explicitly:
var pipes = PipeRegistry.Default.WithI18N().With(new TruncatePipe());Currency/date format looks wrong
Section titled “Currency/date format looks wrong”Pipes honour the data context’s culture. If you forgot to set one, they use CultureInfo.CurrentCulture — which in a server process may be en-US.
var data = new TemplateDataContext(CultureInfo.GetCultureInfo("it-IT")) .AddSource("invoice", invoice);General
Section titled “General””Types exist in multiple assemblies”
Section titled “”Types exist in multiple assemblies””You probably installed Pragmatic.Documents.Model + a renderer but also took a transitive reference via another Pragmatic package. Run dotnet list package --include-transitive and ensure only one version of each model package is in the graph. Central Package Management (Directory.Packages.props) eliminates this category of issue.
Output is non-deterministic
Section titled “Output is non-deterministic”The renderers are deterministic given the same input. If byte-for-byte comparison fails, check:
- Embedded creation date metadata (set a fixed
DateTimevia the builder’s metadata API when snapshot-testing) - Image compression — if you load an image from disk, ensure the bytes haven’t changed between runs
- Font-resource enumeration order — register fonts in a deterministic order
Still stuck?
Section titled “Still stuck?”- Check the corresponding
samples/project — it’s a minimal, runnable demonstration - Inspect the generated
obj/Generated/folder if your issue might be source-generator related - Open an issue with a reproducer — include the model (serialise with
DocumentSerializer.Serialize) and the exact renderer / options used