PDF export
Render a .docx document to PDF with Document.to_pdf and save_pdf — streaming pagination, per-section page geometry, table fidelity, font maps, and degradation warnings.
Document.to_pdf() renders a parsed document to PDF with a streaming flow
layout and real pagination — content flows and breaks across pages the way Word
lays it out.
API
class Document:
def to_pdf(self, *, font_map: dict[str, str] | None = None) -> bytes: ...
def save_pdf(
self, path: str | os.PathLike[str], *, font_map: dict[str, str] | None = None
) -> None: ...import docspine
doc = docspine.open("report.docx")
pdf_bytes = doc.to_pdf() # -> bytes, starts with b"%PDF-"
doc.save_pdf("report.pdf") # writes the same bytes to a filePer-section page geometry
Page size, margins, and orientation come from each sectPr, so a document whose
sections mix paper sizes and orientations paginates correctly — for example a
US-Letter portrait section followed by an A4 landscape one:
# section 1 -> 612 × 792 pt (US Letter, portrait)
# section 2 -> 841.9 × 595.3 pt (A4, landscape)
pdf = docspine.open("mixed-sections.docx").to_pdf()Section breaks and explicit page breaks (w:br w:type="page") both start a new
page.
Fonts and the font_map
The renderer resolves styles.xml + theme effective fonts (including the
east-Asian eastAsia slot for CJK) and embeds each used face once, subset. Pass
font_map to override family resolution — each entry maps a requested family to
a font file path or another family name:
pdf = doc.to_pdf(font_map={"宋体": "Songti SC"})Substitution warns, never fails
When a requested font is missing, docspine substitutes a fallback (e.g. Calibri
→ Liberation Sans) and emits a UserWarning through warnings.warn whose
message names the substitute family — one warning per unique kind, not one
per run. Other degradations (an unsupported multi-column layout, a table row
taller than the page body, custom w:tabs tab stops, text-wrap around an
anchored image, a document-internal w:hyperlink bookmark, and the between-edge
or intra-paragraph-break cases of paragraph borders / shading) warn the same
way. Export never raises for these; it degrades and warns.
import warnings
with warnings.catch_warnings(record=True) as caught:
warnings.simplefilter("always")
pdf = doc.to_pdf(font_map={"Missing Family": "Liberation Serif"})
for w in caught:
print(w.category.__name__, w.message) # UserWarning ...What the renderer covers
| Feature | Support |
|---|---|
| Pagination | Streaming flow layout; content breaks across pages. |
| Section geometry | Per-section page size / margins / orientation from sectPr. |
| Styles | styles.xml inheritance (docDefaults → basedOn → direct) plus theme. |
| Numbering | List counters via the per-numId, per-level numbering engine. |
| Tables | Per-edge borders, shading, cell margins, gridSpan / vMerge merges, and whole-row moves across page breaks. Cell vertical alignment renders — w:vAlign center / bottom offset the cell content after the row height settles. |
| Paragraph borders & shading | A paragraph's w:pBdr (top / right / bottom / left) edges and w:shd fill are drawn (visible in get_drawings); the border @w:space folds into padding. The w:between edge and intra-paragraph page-break cases still degrade with a warning. |
| Indents | Hanging and first-line indents. |
| Tab stops | A w:tab advances to the next tab stop. The interval is settings.xml's w:defaultTabStop (0.5″ Word default when absent); custom per-paragraph w:tabs stops degrade to the default interval with a warning. |
| Hyperlinks | External w:hyperlink runs render as /Link URI annotations (readable via pdfspine get_links). Document-internal anchor bookmarks are stored but not drawn as links, with a one-time warning. |
| Run styling | Underline styles, highlight, and strikethrough. |
| CJK | East-Asian eastAsia font slot resolved for CJK runs. |
| Images | Inline pictures are rendered as block images at their wp:extent size, downscaled to the column width (never upscaled). Anchored (wp:anchor) pictures are absolutely positioned at their posOffset on the section's first page. JPEG passes through; PNG / BMP / GIF / WEBP / TIFF decode on emission. |
Embedded pictures render. Each inline picture (with usable media bytes and a
wp:extent size) is placed in the flow as a block image at the point its
paragraph occurs; a picture that is the whole paragraph takes no extra blank
line. Floating (wp:anchor) raster pictures are absolutely positioned — the
picture is drawn as a page overlay at its wp:positionH / wp:positionV
posOffset (relative to the page or the section margins) on the section's first
page. Text does not wrap around it — that emits a one-time FloatingNoWrap
warning (per-line exclusion rectangles remain out of v1). Vector-format
(EMF / WMF) or byte-missing anchored pictures fall back to the inline-placeholder
or skip paths; a picture with missing media bytes or no size is skipped with a
UserWarning (its bytes stay available at parse level via image_bytes()).
Rendering is deterministic per font environment: the same document and the same installed fonts produce the same PDF bytes.
On main, unreleased
These export changes are merged on main after 0.5.1 and are not yet on PyPI:
- Paragraph borders and shading use pdf-typeset's native paragraph borders
instead of a one-cell table wrapper. Bordered or shaded paragraphs that split
across pages, including in-paragraph page breaks, are drawn on every page;
dashedanddashSmallGaprender as dashes, and matching adjacent paragraphs share one box.w:betweenis still not drawn and warns once. - Superscript / subscript render with a real baseline shift (glyphs at 0.65×, raised or lowered) instead of only shrinking.
w:spacingandw:position(character spacing and manual baseline shift) are parsed, cascaded and rendered. Over-condensed paragraphs fall back to zero spacing with asigned-spacing-fallbackwarning.
Documents that use none of these render byte-identical PDFs.
docspine
A pure-Rust Word (.docx) parser with a first-class table model and Python bindings — structural parsing plus faithful .docx → PDF export. Apache-2.0, on PyPI.
Parsing
Walk a .docx body of interleaved paragraphs and tables with docspine's first-class table model — gridSpan, vMerge, nesting, shading — plus text / Markdown / HTML export and OCR.