DOCX Rendering
The Pragmatic.Documents.Docx package renders a DocumentModel to a DOCX (Office Open XML / ISO/IEC 29500) byte stream. The implementation is pure managed — no native dependency, AOT-compatible.
API surface
Section titled “API surface”public interface IDocxRenderer{ void RenderTo(Stream output, DocumentModel model, DocxResources? resources = null, DocxRenderOptions? options = null);}
public sealed class DocxRenderer : IDocxRenderer{ public byte[] Render(DocumentModel model, DocxResources? resources = null, DocxRenderOptions? options = null); public void RenderTo(Stream output, DocumentModel model, DocxResources? resources = null, DocxRenderOptions? options = null);}Rendermaterialises the full DOCX in memory.RenderTo(Stream)streams into the target.- Both accept optional
DocxResources(fonts, images) andDocxRenderOptions.
Basic example
Section titled “Basic example”using Pragmatic.Documents.Model;using Pragmatic.Documents.Docx;
var doc = new DocumentBuilder() .Title("Quarterly report") .Author("Alice") .Page(p => p .Heading("Q1 2026 Review", level: 1) .Paragraph(new TextNode { Content = "Revenue grew 12% QoQ." }) .Table(t => t .HeaderRow("Metric", "Q1", "Q4") .Row("Revenue", "€1.2M", "€1.07M") .Row("Headcount", "42", "38"))) .Build();
using var fs = File.Create("report.docx");new DocxRenderer().RenderTo(fs, doc);Open the file in Word, LibreOffice, or Google Docs.
Shared model with PDF
Section titled “Shared model with PDF”The DOCX renderer accepts the same DocumentModel as the PDF renderer. Swap the output format by swapping the renderer:
DocumentModel doc = BuildReport();
using (var pdf = File.Create("report.pdf")) PdfRenderer.RenderTo(pdf, doc);using (var docx = File.Create("report.docx")) new DocxRenderer().RenderTo(docx, doc);Not every node maps identically — see the “Node mapping” section below.
Node mapping
Section titled “Node mapping”| Node | DOCX output |
|---|---|
TextNode | <w:r><w:t> run with style from NodeStyle |
HeadingNode | Heading paragraph using the Heading1..Heading9 styles (bookmarked for TOC) |
ParagraphNode | <w:p> paragraph |
TableNode | <w:tbl> table with header row styling |
ImageNode | Embedded image (PNG/JPEG) with DrawingML |
HyperlinkNode | External hyperlink run |
TocNode | Field-coded TOC (populated on open — see “Table of contents” below) |
PageBreakNode | <w:br w:type="page"/> |
Spacer | Empty paragraph with height |
HorizontalRule | Paragraph with a bottom border |
FieldNode.PageNumber / PageCount / Date | Field codes (PAGE, NUMPAGES, DATE) |
BookmarkNode | <w:bookmarkStart> / <w:bookmarkEnd> pair |
FootnoteNode | <w:footnoteReference> + footnote content |
BarcodeNode | Rendered as an image (the renderer rasterises barcodes so they survive copy/paste) |
Table of contents
Section titled “Table of contents”DOCX TOCs are field-coded: the TocNode emits a { TOC \o "1-3" \h \z \u } field with placeholder content that Word/LibreOffice refresh when the document is opened.
Enable explicit update on open via DocxRenderOptions:
var options = new DocxRenderOptions { UpdateFieldsOnOpen = true };new DocxRenderer().RenderTo(fs, doc, options: options);The renderer uses a heuristic to pre-populate the TOC text with estimated page numbers so the file looks right even in viewers that don’t auto-update fields. Word overwrites this when it refreshes.
Resources (fonts and images)
Section titled “Resources (fonts and images)”var resources = new DocxResources();resources.AddFont("Inter", File.ReadAllBytes("fonts/Inter-Regular.ttf"));resources.AddImage("logo", File.ReadAllBytes("logo.png"));
new DocxRenderer().RenderTo(fs, doc, resources);Fonts are embedded in the DOCX (you pay bytes in the output but the document renders consistently on any machine). Images are linked from the doc to embedded parts in the OOXML package.
Themes
Section titled “Themes”Set a DocxTheme to customise heading colours, default fonts, and body text colour:
var options = new DocxRenderOptions{ Theme = new DocxTheme { HeadingColor = "1F497D", BodyFontFamily = "Calibri", HeadingFontFamily = "Cambria", }};
new DocxRenderer().RenderTo(fs, doc, options: options);Headers, footers, page numbers
Section titled “Headers, footers, page numbers”Headers and footers are per-document (not per-page) in DOCX. If the DocumentModel has Header(...) / Footer(...) on a page, the renderer uses them for the whole document.
.Page(p => p .Header(new TextNode { Content = "Acme Corp — Q1 2026" }) .Footer( new TextNode { Content = "Page " }, new FieldNode { FieldType = FieldType.PageNumber }, new TextNode { Content = " of " }, new FieldNode { FieldType = FieldType.PageCount }))Limitations
Section titled “Limitations”- No multi-column layout (single-column flow only).
- No advanced drawing shapes (tables and images are supported; SmartArt / charts are not).
- Custom paragraph styling is limited to
NodeStyleproperties — if you need complex styled paragraphs, use a designer template instead. - Track-changes and comments are out of scope.
Related
Section titled “Related”- pdf-rendering.md — same model, PDF output
- markup-parser.md — author templates in PDX-Doc markup
Pragmatic.Documents.Docx.Samples— runnable scenarios