-
Notifications
You must be signed in to change notification settings - Fork 0
Architecture
npond edited this page Aug 24, 2026
·
1 revision
One shipping library, one test project, .NET 10. Data flows one direction:
Packaging → Ooxml → Styling → Layout → Pdf
(Fonts serves Layout and Pdf)
Word's page origin is top-left and PDF's is bottom-left; the flip happens once, in PdfRenderer, and nowhere else.
src/n8PDF/
Converter.cs the public API; everything else is internal
Packaging/ OPC container: zip, content types, relationships, PackageLimits
Ooxml/ WordprocessingML model and parsers, plus Units (twips/EMU/points)
Styling/ the formatting cascade, producing Resolved*Format
Fonts/ SFNT parsing, metrics, resolution, shaping (+ OpenType/, Aat/), subsetting
Text/ the bidirectional algorithm and its generated Unicode tables
Images/ PNG, GIF, BMP, TIFF, EMF and JPEG decoding
Layout/ measurement, line breaking, page composition, list counters
Pdf/ object model, writer, content streams, Type0 embedding
Diagnostics/ LayoutTrace — the testing spine
tests/n8PDF.Tests/
Fixtures/Minimal/ hand-authored .docx, one feature each (generated, committed)
Fixtures/Real/ documents Word itself wrote
Fixtures/Reference/ Word-exported reference PDFs, named after the fixtures
Golden/ committed layout traces
tools/ build-input generators only: Unicode/hyphenation tables, reference PDFs
-
Packaging opens the ZIP as an OPC package: content types, relationships, part resolution. This is also where the untrusted-input bounds live (
PackageLimits, the DTD-prohibiting XML reader) — see Security. -
Ooxml parses WordprocessingML into a document model: body blocks, runs, tables, sections, notes, charts, diagrams, equations.
Converter.LayoutDocumentorchestrates loading the related parts — styles, numbering, theme, headers/footers, footnotes/endnotes, images, hyperlinks, diagrams, charts, embedded fonts — each part owning its own relationship scope (a header'srId1and the body's are different pictures). -
Styling resolves the cascade — document defaults, Word's built-in style fallbacks (below
docDefaults, optional), table styles with their thirteen conditional formats, paragraph and character styles, toggle-property cancellation, direct formatting — producingResolved*Formatobjects that layout consumes. - Fonts resolves families (theme-aware, embedded-first), reads real metrics, shapes text (OpenType GSUB/GPOS, Apple AAT, the Indic engines, the Universal Shaping Engine), and later subsets faces for embedding.
- Layout measures text against the real font files, breaks lines (Unicode line breaking, hyphenation, bidi reordering), composes pages (floats, columns, notes, tables breaking across pages, widow control), and places every run at exact coordinates — with baselines snapped to Word's 1/300-inch grid. A document holding page-dependent fields is laid out twice: the first pass records where everything fell, the second uses it (converging, as Word converges).
-
Pdf serialises the laid-out pages: content streams, the object graph and cross-reference table (hand-rolled,
qpdf-validated), subset font embedding withToUnicodemaps, outline, structure tree, and optionally PDF/A-2b metadata. The single top-left→bottom-left coordinate flip happens here.
-
Layout is the product; the PDF is its serialisation. The fidelity suite asserts against the layout trace (
Diagnostics/LayoutTrace), not against rendered pixels, which is what makes failures diagnostic: a golden failure names the run that moved and by how much. -
Deterministic by construction. No wall-clock, no randomness in the output path: the same document converts to identical bytes (given a fixed
CreationDate), which is what makes golden comparison possible at all. - Errors cost their placement, not the conversion. A missing or unreadable image, chart, or embedded font is skipped where it stands; the document loses the piece rather than the page. The bounds and the conversion-level exception net behind this are described in Security.
-
Generated tables are generated. The Unicode bidi/shaping tables under
Text/andFonts/and the hyphenation patterns are output oftools/make-*-tables.py. The library has no dependencies and no run-time data files, so the generators' output is committed as source — and never hand-edited. - Per-part relationship scoping. Relationship ids are scoped to the part that declares them; ids are rewritten to include the part name as they are collected, so the whole document shares one address/image table without two parts' ids colliding.
- Font discovery is paid once. The platform scan builds a 1.6MB index (name, style, file) shared for the process; a face reads its own file only when a document asks for it. First conversion ~600ms for the scan; after that, a page of text converts in about 1.4ms.
The one-way flow is the review heuristic: a fix that makes a later stage reach back into an earlier one, or that makes Pdf aware of Word semantics, or a second place that flips coordinates, is in the wrong place. Fonts is the deliberate exception — it serves both Layout (measurement, shaping) and Pdf (subsetting, embedding).
Using n8PDF
What it does
How it works
Contributing