Convert Microsoft Word DOCX files to PDF without Microsoft Office, LibreOffice, or any cloud API.
dxpdf is an open-source, standalone DOCX-to-PDF conversion engine written in Rust and powered by Skia. It reads .docx files and produces high-fidelity PDF output — preserving text formatting, tables, images, headers, footers, hyperlinks, and page layout. Available as a CLI tool, a Rust library, and a Python package.
Built by nerdy.pro.
- Blazing fast — converts multi-page documents in under 100 ms on modern hardware
- High fidelity — Flutter-inspired measure → layout → paint pipeline with pixel-accurate baseline positioning
- Type-safe — compile-time dimensional type system (
Twips,Pt,Emu) prevents unit mixing bugs - Standalone — no Office installation, no LibreOffice, no external services needed
- Cross-platform — runs natively on macOS, Linux, and Windows
- Three interfaces — use as a CLI tool, Rust library (
use dxpdf;), or Python package (import dxpdf) - ISO 29500 compliant — validated against the Office Open XML specification
cargo install dxpdfAdd to your Cargo.toml:
[dependencies]
dxpdf = "0.2.25"pip install dxpdfdxpdf input.docx # produces input.pdf
dxpdf input.docx -o output.pdf # specify output pathlet docx_bytes = std::fs::read("document.docx")?;
let pdf_bytes = dxpdf::convert(&docx_bytes)?;
std::fs::write("output.pdf", &pdf_bytes)?;You can also inspect or transform the parsed document model before conversion:
use dxpdf::{docx, model, render};
let document = docx::parse(&std::fs::read("document.docx")?)?;
for block in &document.blocks {
match block {
model::Block::Paragraph(p) => { /* inspect paragraph content */ }
model::Block::Table(t) => { /* inspect table structure */ }
}
}
let pdf_bytes = render::render(&document)?;import dxpdf
# Bytes in, bytes out
pdf_bytes = dxpdf.convert(open("input.docx", "rb").read())
# File path to file path
dxpdf.convert_file("input.docx", "output.pdf")dxpdf handles the most common DOCX features found in real-world business documents, reports, and forms:
| Category | Features |
|---|---|
| Text formatting | Bold, italic, underline, highlighting, font size/family/color, character spacing, superscript/subscript, run shading |
| Paragraphs | Alignment (left/center/right), spacing (before/after/line with auto/exact/atLeast), indentation, tab stops, paragraph borders, paragraph shading |
| Tables | Column widths, cell margins (3-level cascade), merged cells (gridSpan + vMerge), row heights, borders, cell shading, nested tables |
| Images | Inline images (PNG, JPEG, BMP, WebP), floating/anchored images with alignment and percentage-based positioning |
| Styles | Paragraph and character styles, basedOn inheritance, document defaults, theme fonts |
| Headers & footers | Text, images, page numbers via PAGE/NUMPAGES field codes |
| Lists | Bullets, decimal, lower/upper letter, lower/upper roman numbering with counter tracking |
| Hyperlinks | Clickable PDF link annotations with URL resolution |
| Page layout | Multiple page sizes/margins, section breaks, portrait and landscape orientation |
| Pagination | Automatic page breaking, word wrapping, line spacing modes, floating image text flow |
Benchmarked on Apple M3 Max with hyperfine (20 runs, 3 warmup):
| Document type | Pages | Conversion time | Memory usage |
|---|---|---|---|
| Short form with tables and images | 2 | 48 ms | 20 MB |
| Multi-page report | 7 | 52 ms | 24 MB |
| Image-heavy document (60+ images) | 24 | 353 ms | 76 MB |
dxpdf processes most business documents in under 100 ms, making it suitable for batch processing, server-side conversion, and CI/CD pipelines.
-
Rust toolchain (1.70+)
-
clang(required byskia-safefor building Skia bindings) -
Linux only:
libfontconfig1-devandlibfreetype-devsudo apt-get install -y libfontconfig1-dev libfreetype-dev
cargo build --releaseThe release binary will be at target/release/dxpdf.
cargo testdxpdf follows a measure → layout → paint pipeline inspired by Flutter's rendering model:
DOCX (ZIP) → Parse → Document Model → Measure → Layout → Paint → PDF
Twips/Emu/HalfPoints ←── Pt throughout ──→ Skia
Type-safe dimensions flow through the entire pipeline: OOXML units (Twips, Emu, HalfPoints) in the parsed model, Pt (typographic points) in layout, and f32 only at the Skia rendering boundary.
Each layout element (paragraphs, table cells, headers/footers) goes through three phases:
- Measure — collect text fragments, fit lines, produce draw commands with relative coordinates
- Layout — assign absolute positions, handle page breaks, distribute heights (e.g., vertically merged cells)
- Paint — emit draw commands at final positions (shading → content → borders)
| Module | Purpose |
|---|---|
dimension |
Type-safe dimensional units (Twips, HalfPoints, EighthPoints, Emu, Pt) with compile-time unit safety |
geometry |
Spatial types (Offset, Size, Rect, EdgeInsets, LineSegment) — generic over unit, with Skia interop |
model |
Algebraic data types representing the full document tree (Document, Block, Inline, etc.) |
docx |
DOCX ZIP extraction, declarative serde-based XML parser, style and numbering resolution |
render/layout |
Measure → layout → paint pipeline: fragment-based line fitting, paragraph layout, three-pass table layout, header/footer handling |
render/painter |
Skia canvas operations for PDF output |
render/fonts |
Font resolution with metric-compatible substitution (e.g., Calibri → Carlito, Cambria → Caladea) |
Validated against ISO 29500 (Office Open XML). 37 features fully implemented, 9 partial, 13 planned.
Full feature matrix (click to expand)
| Feature | Status |
|---|---|
| Bold, italic | ✅ with toggle support |
| Underline | ✅ font-proportional stroke width |
| Font size, family, color | ✅ |
| Superscript/subscript | ✅ |
| Character spacing | ✅ |
Character scaling (w:w horizontal compression/expansion) |
✅ |
| Run shading | ✅ |
| Strikethrough | |
| Highlighting | ✅ full ST_HighlightColor palette |
| Caps, smallCaps | ❌ |
| Shadow, outline, emboss, imprint | ❌ |
| Hidden text | ❌ |
| Feature | Status |
|---|---|
| Alignment (left, center, right) | ✅ |
| Alignment (justify) | |
| Spacing before/after, line spacing | ✅ auto/exact/atLeast |
| Indentation (left, right, first-line, hanging) | ✅ |
| Tab stops (left) | ✅ |
| Tab stops (center, right) | ✅ |
| Tab stops (decimal) | |
| Paragraph shading | ✅ |
| Paragraph borders | ✅ with adjacent border merging, w:space offset |
| Keep with next, widow/orphan control | ❌ |
| Feature | Status |
|---|---|
| Paragraph styles, character styles | ✅ |
basedOn inheritance |
✅ |
| Document defaults, theme fonts | ✅ |
| Feature | Status |
|---|---|
| Grid columns, cell widths (dxa) | ✅ |
| Cell widths (pct, auto) | |
| Cell margins (3-level cascade) | ✅ |
| Merged cells (gridSpan, vMerge) | ✅ |
| Row heights | ✅ min / |
| Table borders (per-cell, per-table) | ✅ |
| Border styles (single) | ✅ |
| Border styles (double, dashed, dotted) | |
| Cell shading (solid) | ✅ |
| Cell shading (patterns) | ❌ |
| Vertical alignment (top / center / bottom) | ✅ incl. vMerge-aware bottom alignment |
| Row splitting across page breaks | ✅ §17.4.1 cantSplit honored |
| Nested tables | ✅ |
| Feature | Status |
|---|---|
| Inline images | ✅ PNG, JPEG, BMP, WebP |
| Floating images | ✅ offset, align, wp14:pctPos |
| Wrap modes | ✅ none/square/tight/through |
| VML images | ❌ |
| Feature | Status |
|---|---|
| Page size and orientation | ✅ |
| Page margins (all 6) | ✅ |
| Section breaks (nextPage) | ✅ |
| Section breaks (continuous) | ✅ continues on current page |
| Section breaks (even, odd) | |
| Multi-column, page borders, doc grid | ❌ |
| Feature | Status |
|---|---|
| Default header/footer | ✅ |
| First page, even/odd, per-section | ❌ |
| Feature | Status |
|---|---|
| Bullet, decimal, letter, roman | ✅ |
| Multi-level lists |
| Feature | Status |
|---|---|
| PAGE, NUMPAGES | ✅ |
| Hyperlinks | ✅ clickable PDF annotations |
| Unknown fields | ✅ cached value fallback |
| TOC, MERGEFIELD, DATE | ❌ |
| Feature | Status |
|---|---|
| Footnotes/endnotes | ❌ warned |
| Comments, tracked changes | ❌ / |
| DrawingML shapes (preset geometry, fill, stroke, outer shadow) | |
| Text boxes, SmartArt, charts | ❌ |
| RTL text, automatic hyphenation | ❌ |
| Crate | Purpose |
|---|---|
quick-xml |
Declarative XML parsing via serde deserializers |
zip |
DOCX ZIP archive reading |
skia-safe |
PDF rendering, text measurement, link annotations |
clap |
CLI argument parsing |
thiserror |
Error types |
log + env_logger |
Logging for unsupported features (RUST_LOG=warn) |
pyo3 (optional) |
Python bindings via maturin |
Install dxpdf with cargo install dxpdf, then run dxpdf input.docx. The PDF will be created in the same directory. You can also specify an output path with -o output.pdf.
No. dxpdf is a standalone converter that reads DOCX files directly and renders PDF output using Skia. No Office installation or external service is needed.
Yes. In Rust, add dxpdf as a dependency and call dxpdf::convert(&docx_bytes). In Python, install with pip install dxpdf and call dxpdf.convert(bytes) or dxpdf.convert_file("input.docx", "output.pdf").
dxpdf supports text formatting, paragraphs, tables (including nested and merged cells), inline and floating images, styles with inheritance, headers/footers, lists, hyperlinks, section breaks, and automatic pagination. See the full feature matrix above.
On Apple M3 Max, dxpdf converts a typical multi-page business document in under 100 ms. A 24-page image-heavy document takes about 350 ms. It is designed for batch processing and server-side use.
dxpdf runs on macOS, Linux, and Windows. On Linux, you need libfontconfig1-dev and libfreetype-dev installed.
Contributions are welcome. Please open an issue before submitting large PRs.
Built by nerdy.pro.
MIT