A high-fidelity rendering engine for OpenXML (DOCX) documents, producing paginated SVG and PDF output.
PanoramicData.Render is a virtual layout engine that reads .docx files and produces visually faithful SVG pages (one per document page) and PDF files. Unlike semantic converters that map document tags to HTML, this library calculates exact glyph positions, line breaks, and object anchors — acting as a layout engine comparable to a word processor's print path.
- Not a DOCX editor. This is read-only, one-way conversion.
- Not an HTML converter. Output is SVG and PDF only; there is no HTML mapping.
- Not pixel-perfect. The goal is "visually indistinguishable at normal zoom." Sub-pixel identity with Microsoft Word is not claimed.
- Not a UI component. This is a library that produces files/strings. Visualization is the consumer's responsibility.
using PanoramicData.Render;
var options = new RenderOptions
{
FontDirectories = ["/usr/share/fonts", "C:\\Windows\\Fonts"],
FallbackFontFamily = "Liberation Sans",
TargetDpi = 96
};
await using var stream = File.OpenRead("document.docx");
var renderer = new DocxRenderer(options);
var result = await renderer.RenderAsync(stream);
// Get SVG for each page
for (var i = 0; i < result.Pages.Count; i++)
{
var svg = result.Pages[i].ToSvg();
await File.WriteAllTextAsync($"page-{i + 1}.svg", svg);
}
// Or export as a single PDF
await using var pdfStream = File.Create("output.pdf");
await result.ToPdfAsync(pdfStream);
// Or get PDF bytes directly
var pdfBytes = result.ToPdf();| Decision | Rationale |
|---|---|
| SkiaSharp for measurement & PDF | MIT-licensed, cross-platform, includes HarfBuzz for complex script shaping |
| Knuth-Plass line breaking | Produces paragraph-optimal line breaks matching Word's behaviour more closely than greedy algorithms |
| Twips as internal unit | Matches Word's internal precision (1/1440 inch), avoiding accumulated rounding errors |
| Full OOXML style cascade | Document Defaults → Theme → Numbering → Table → Paragraph hierarchy → Character hierarchy → Toggle properties → Direct Formatting |
RenderOptions supports these settings:
| Property | Default | Description |
|---|---|---|
FontDirectories |
[] |
Directories to search for .ttf/.otf font files |
FallbackFontFamily |
"" |
Font to use when the document's font cannot be resolved |
TargetDpi |
96 |
Target DPI for SVG output scaling |
EmbedFonts |
false |
Embed fonts as WOFF2 @font-face in SVG output |
EmbedImages |
true |
Embed images as data URIs in SVG output |
EnableHyphenation |
false |
Enable automatic hyphenation using TeX patterns |
PageRange |
null |
Optional Range to render a subset of pages |
ShowHiddenText |
false |
Include runs with w:vanish in the layout |
| Input | Output |
|---|---|
.docx (OOXML) |
SVG (one per page, fonts optionally embedded as WOFF2) |
| PDF (single file via SkiaSharp PDF backend) |
.doc (binary format) is not supported and never will be. Future versions may add .pptx and .xlsx support.
- .NET 10.0+
- Access to
.ttfand/or.otffont files used in the source document (system fonts, a directory, or embedded)
The repository includes a Blazor WebAssembly demo app in PanoramicData.Render.Demo.
The demo requires the .NET WebAssembly workload:
dotnet workload install wasm-toolsIf you have multiple SDK feature bands installed and need to align workloads:
dotnet workload restore PanoramicData.Render.Demo/PanoramicData.Render.Demo.csprojFrom repo root:
dotnet run --project PanoramicData.Render.Demo/PanoramicData.Render.Demo.csproj --configuration Debug --urls "http://localhost:5250"Then open:
http://localhost:5250
dotnet publish PanoramicData.Render.Demo/PanoramicData.Render.Demo.csproj --configuration ReleasePublished web assets are in:
PanoramicData.Render.Demo/bin/Release/net10.0/publish/wwwroot
CI is configured to publish the demo app to GitHub Pages from GitHub Actions on pushes to main and on semantic version tags.
git tag 1.0.2
git push origin 1.0.2Use GitHub UI:
- Actions -> latest
CIrun ->deploy_pagesjob
Or with gh CLI:
gh run list --workflow CI --limit 5
gh run view <run-id> --logCustom Domain: https://render.panoramicdata.com
The repository deploys a CNAME file for render.panoramicdata.com as part of the Pages artifact.
Create a DNS record:
- Type:
CNAME - Host:
render - Target:
panoramicdata.github.io
In repo Settings -> Pages:
- Source:
GitHub Actions - Custom domain:
render.panoramicdata.com - Enable
Enforce HTTPSafter DNS is validated
Set custom domain via API:
gh api --method PUT repos/panoramicdata/PanoramicData.Render/pages -f cname='render.panoramicdata.com'Read current Pages config:
gh api repos/panoramicdata/PanoramicData.Render/pagesYou do not upload a certificate manually for GitHub Pages custom domains. After DNS and CNAME are correct, GitHub automatically provisions and renews TLS certificates.
- DESIGN.md — Architecture and technical design
- PLAN.md — Phased implementation roadmap
- docs/supported-features.md — Supported features matrix
- docs/known-limitations.md — Known limitations
- docs/plans/ — Detailed per-phase deliverables
MIT — Copyright © 2026 Panoramic Data Limited
See CONTRIBUTING.md for development setup and coding standards.