PDF Pure Python · docx → pdf

Word2PDF

Converts .docx to PDF without Word, LibreOffice or any binary — and lays the page out the way Word does, down to fractions of a point.

$ pip install word2pdf
Python 3.9+ · one dependency
an 11 pt run drawn at 11.04 pt space-before dropped here tblInd measured to the cell text median dy 0.52 pt
Word does not lay text out in exact points. Copying its rounding, its spacing collapse and its table geometry is what closes the last fraction of a point.

Why it exists

No Word on the box

No COM automation, no soffice subprocess, no headless install to babysit. The same wheel runs on Windows, macOS, Linux, Docker and Lambda.

One dependency

ReportLab draws the result; everything above it — the OPC container, WordprocessingML, the style cascade, fonts, layout — is this package.

Measured, not eyeballed

Every release is compared with Word's own PDF export, page by page, word by word, and the distances are in the table below.

Against Word's own export

The page counts match. So do the words.

dx and dy are the median distance between a word in this PDF and the same word in Word's, in points — 1 pt is 1/72 inch.
Document Pages Text dx dy
Work instruction — landscape annex, diagrams, tables4/4100%0.060.52
Application form — label/value tables, tick boxes2/2100%0.070.87
Account statement — transactions, decimal tabs1/1100%0.070.77
Headings, lists, tables, image, header/footer3/3100%0.082.19
Feature soup — merged cells, nested tables, columns3/399%0.092.36
Benefit statement — tables over three pages3/397%0.135.68
Baseline and font-metric probes2/2100%0.060.83
Page-top spacing probes13/13100%0.460.09
Table geometry and indent probes2/2100%0.050.10

Every document in that corpus is generated by the test suite — the repository holds no real documents. Reproduce it with python tests/fidelity.py on a machine that has Word installed.

Use it

Three lines, or one command.

Python

from word2pdf import convert

convert('report.docx', 'report.pdf')
pdf = convert('report.docx')  # bytes
convert(uploaded_bytes, 'out.pdf')

# one warm font index for a batch
from word2pdf import Converter
c = Converter()
for name in filenames:
    c.convert(name, name[:-5] + '.pdf')

Command line

word2pdf report.docx
word2pdf *.docx -d out/
word2pdf invoice.docx --data values.json
word2pdf big.docx --image-quality 85

python -m word2pdf --help

Dynamic documents

Design the template in Word. Fill it at conversion time.

Word splits a placeholder across runs as you type. The engine stitches those back together first, so {{ customer.name }} works even when the file stored it as three fragments.

Fill and convert

convert('invoice.docx', 'invoice.pdf', context={
    'company': 'Northwind Example Ltd',
    'customer': {'name': 'A. Customer'},
    'lines': [
        {'description': 'Annual subscription',
         'qty': 1, 'amount': '1,440.00'},
    ],
    'total': '1,452.00',
})
In the .docxDoes
{{ value }}insert a value
{{ a.b }}, {{ items[0] }}nested and indexed
{{ name|upper }}filters
{%tr for r in rows %}repeat the table row
{%p for x in xs %}repeat the paragraph
{% if cond %}conditional text

Jinja2 is used when it is installed; otherwise a built-in engine covers variables, loops, conditionals and the common filters. Headers, footers and footnotes are filled too.

What it handles

Text
Bold, italic, four underline styles, strike, super- and subscript, small caps, character spacing and scaling, highlighting, theme colours with tint and shade, symbols.
Paragraphs
Word's compress-to-fit justification, indents, the spacing collapse, exact/at-least/multiple line spacing, keep-next, widow control, borders, and tab stops with leaders.
Lists
Multilevel numbering from numbering.xml — roman, letters, ordinals, cardinal text, legal numbering, restarts, custom level text, and Symbol/Wingdings bullets mapped to Unicode.
Tables
Table styles with conditional formatting, merged cells, nested tables, cell margins, fixed and autofit widths, repeating header rows, and rows that cannot split.
Page setup
Multiple sections, orientation, gutters, headers and footers (default/first/even), page borders, columns with separators, and page numbering that restarts.
Objects
Inline and floating images with cropping and rotation, text boxes, VML shapes, footnotes and endnotes, hyperlinks, bookmarks, and a PDF outline built from heading levels.
Fonts
Indexes the installed fonts, matches family, weight, italic and width, consults Windows' substitution table, and swaps in a face per character when a glyph is missing.
Fields
PAGE, NUMPAGES, DATE, TIME, HYPERLINK, REF and friends; anything else falls back to the result Word cached in the file.

Known limits

What it does not do yet.

  • A table row is never split across a page. A row that does not fit moves to the next page whole, where Word would fill the page first. Rows marked cantSplit are correct either way.
  • Text does not wrap around floating images. Anchored objects are placed correctly, but text flows under them rather than around.
  • A justified line can break one word early, where Word squeezes that word onto the line before.
  • EMF and WMF vector images are skipped, as are DrawingML shapes; everything else on the page still renders.
  • .doc (Word 97–2003) is not supported — only Open XML .docx. The error message says so when you hand it one.