Skip to main content

betteroffice-docx

Read, edit, lay out, and rasterize DOCX documents from Python. python-docx reads a document and writes one back; this also paginates it — page boxes, a display list, PNG pages — because the Rust BetterOffice DOCX core is compiled into the wheel: no Word, no LibreOffice subprocess, no COM.

pip install betteroffice-docx

The distribution is hyphenated, the module is not: import betteroffice_docx.

Read a document

from betteroffice_docx import Document

document = Document.open_path("report.docx")

print(document.structure())          # Structure(body_paragraphs=42, body_tables=3, sections=2)
for paragraph in document:
    print(paragraph.id, paragraph.style, repr(paragraph.text))

for table in document.tables():
    for row in table.rows:
        print([cell.text for cell in row.cells])

paragraphs() walks the body in document order and descends into table cells and content controls, so a cell paragraph is reachable both ways. document[key] and document.paragraph(key) take either a w14:paraId or a body index. Everything a read returns is a value, not a live view — read again after an edit.

Page geometry is in twips — 1440 to the inch. The module exports TWIPS_PER_INCH and TWIPS_PER_POINT.

section = document.sections()[0]
print(section.page_width, section.page_height, section.margin_left)
print(document.headers()[0].text)

Edit text

edit = document.replace_text("11111111", "Edited from Python")
print(edit.para_id, edit.start, edit.end)

document.save_path("report-edited.docx")

replace_text rewrites one paragraph and keeps its style, alignment, and the run formatting it already had. The engine rebuilds the paragraph from a single run, so a paragraph that mixes runs — half bold, a hyperlink, a field — raises UnsupportedEditError rather than flattening the formatting you did not ask it to touch. An unknown w14:paraId raises KeyError.

Only paragraphs Word stamped with a w14:paraId can be addressed: document.paragraph_ids reports None for the rest.

Write

Unlike the PPTX binding, edits reach the file: save() serializes the edited model, and reopening the result gives the edited text back.

document = Document.open(data)
document.replace_text(document.paragraph_ids[0], "New first line")
reopened = Document.open(document.save())
reopened.paragraph(0).text          # 'New first line'

Saving is deterministic. The engine has no clock, so timestamps come from document.timestamp — the epoch until you set one — and the same input plus the same edits produce the same bytes. save(now=..., update_modified_date=True, modified_by=...) overrides that for one call.

The container is rebuilt rather than patched, so output is not byte-identical to the source even with no edits; the parts the model retained survive unchanged.

Lay a document out

Layout is a two-stage contract. Something else measures text — the browser, or ooxml-text — and the engine paginates the measured blocks and compiles them into a display list:

layout = document.layout({"measured": measured_blocks, "options": {...}})
print(len(layout), layout.pages)
layout.write("layout.json")

pages = layout.display_list
print(len(pages), pages.primitives)

layout() takes the envelope as a dict or as a JSON string, and returns the page boxes (layout.json, layout.to_dict()) beside the display list that paints them.

Rasterize

No font is compiled into the wheel, so a page with text needs at least one registered face:

from pathlib import Path

document.register_font("Carlito", Path("Carlito-Regular.ttf").read_bytes())
document.register_font("Carlito", Path("Carlito-Bold.ttf").read_bytes(), bold=True)

png = document.render_png(layout.display_list, 0)
png.write("page-0.png")
print(len(png), png.skipped_images)

Text whose family has no chain raises RenderError naming the chain it wanted — missing font chain for `calibri|0|0` — so a missing face is loud rather than silently blank.

Images are the opposite: an image reference the backend cannot resolve is skipped and counted in png.skipped_images instead of failing the page. Word hands out relationship ids per part, so rId9 in the body and rId9 in a header are different images and registration is scoped:

document.register_image("rId9", body_png)
document.register_image("rId9", header_png, scope="header_footer", part="rId7")
document.register_image("rId4", note_png, scope="footnotes")

Images the display list already carries as data: URLs need no registration. A page past MAX_PIXMAP_DIM per side or MAX_PIXMAP_PIXELS in area is refused before any surface is allocated.

Compared with python-docx

python-docx betteroffice-docx
Read paragraphs, tables, sections yes yes
Write text back to a file yes yes, single-run paragraphs
Build a document from scratch yes no — it edits what you open
Paginate (page boxes, display list) no yes
Rasterize pages to PNG no yes
Engine pure Python Rust, compiled

python-docx is a far broader authoring library. If what you need is pagination, page images, or an engine that reads what Word actually wrote, that is the gap this fills.

API

Document.open(data) / open_path(path) open from bytes or a path
document.structure() paragraph, table, section, and note counts
document.paragraphs() / tables() / sections() body content
document.headers() / footers() header and footer stories
document[key] / document.paragraph(key) one paragraph by ID or index
document.paragraph_ids / text body IDs, and the whole text
document.warnings / template_variables what the parser found
document.replace_text(para_id, text) rewrite one paragraph
document.author / origin / timestamp how an edit is attributed and stamped
document.layout(input) paginate a measured envelope
document.register_font / register_image raster resources
document.render_png(display_list, page) rasterize one page
document.save() / save_path(path) serialize to DOCX

Errors raise DocxError or a more specific subclass: ParseError, EditError, UnsupportedEditError, LayoutError, RenderError. An unknown paragraph ID raises KeyError, an out-of-range index IndexError, and a bad argument — an unknown parse limit, an unknown image scope, malformed font bytes — ValueError.

Parser bounds can be tightened for untrusted input:

Document.open(untrusted, limits={"max_paragraphs": 5_000, "max_tables": 500})

An unknown limit name raises ValueError rather than being ignored.

Threads

A Document is not pinned to a thread: the engine's document type is Send and Sync, so opening on one thread and dropping on another is fine. Parsing, layout, rasterization, and saving release the GIL for their duration, so several documents genuinely proceed in parallel.

Status

0.0.x, and the API may change before 0.1.0. Editing covers paragraph text on plain single-run paragraphs; richer edits land on the Rust facade first.

Wheels are built for Linux (x86_64, aarch64), macOS (arm64, x86_64), and Windows (x86_64) against the stable ABI for CPython 3.9 and up.

Apache-2.0.

Release files for betteroffice-docx 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for betteroffice-docx 0.1.0
File Size Uploaded
betteroffice_docx-0.1.0.tar.gz 1.9 MB Details

Built distributions (wheels)

Table of built distributions (wheels) for betteroffice-docx 0.1.0
File
betteroffice_docx-0.1.0-cp39-abi3-win_amd64.whl CPython 3.9 abi3 Windows x86-64 Details
betteroffice_docx-0.1.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl CPython 3.9 abi3 Linux glibc 2.17+ x86-64 Details
betteroffice_docx-0.1.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl CPython 3.9 abi3 Linux glibc 2.17+ ARM64 Details
betteroffice_docx-0.1.0-cp39-abi3-macosx_11_0_arm64.whl CPython 3.9 abi3 macOS 11.0+ ARM64 Details
betteroffice_docx-0.1.0-cp39-abi3-macosx_10_12_x86_64.whl CPython 3.9 abi3 macOS 10.12+ x86-64 Details

Total release size: 27.2 MB

Release files / betteroffice_docx-0.1.0.tar.gz

Download URL betteroffice_docx-0.1.0.tar.gz
Size 1.9 MB
Tags Source
SHA-256 checksum
How to use checksums
e829574d5ec9acc73e828bae2a8adb8e04e76848d50f85a5b5cdb7f9ddec6152
BLAKE2b-256 checksum
How to use checksums
5ee747b29e02f9dc4e2f9a535177b6d47c4232fbeaa55130e192825e670b2c22
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 16, 2026.

Transparency log

Release files / betteroffice_docx-0.1.0-cp39-abi3-win_amd64.whl

Download URL betteroffice_docx-0.1.0-cp39-abi3-win_amd64.whl
Size 5.5 MB
Tags CPython 3.9 Windows x86-64 abi3
SHA-256 checksum
How to use checksums
c465f712b48616b4441cb814127c7af5ab0125b0ec3ea773b619dd0501c87b2e
BLAKE2b-256 checksum
How to use checksums
0b5ccf7347f0d4184b9209750a6088c8c2baa9e025b5560eb7bf4a9783127f0f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 16, 2026.

Transparency log

Release files / betteroffice_docx-0.1.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL betteroffice_docx-0.1.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 5.1 MB
Tags CPython 3.9 Linux glibc 2.17+ x86-64 abi3
SHA-256 checksum
How to use checksums
8b44fcc416b272c57e557047c0428a93518fa9cbc851420583598e7dcf20ce43
BLAKE2b-256 checksum
How to use checksums
4de13d7c7d2cd6956838ba61f38912a26543b916e298bb3ab450de003cfdc71b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 16, 2026.

Transparency log

Release files / betteroffice_docx-0.1.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl

Download URL betteroffice_docx-0.1.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Size 4.9 MB
Tags CPython 3.9 Linux glibc 2.17+ ARM64 abi3
SHA-256 checksum
How to use checksums
1dc3ec27324a6890590673359f6afe22dac339a4e2229eaf31c892733d0f4f3b
BLAKE2b-256 checksum
How to use checksums
7ec6b5e86907fbee6e2cc3328efd9eb15c84dfd8b908e047532361948d447f75
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 16, 2026.

Transparency log

Release files / betteroffice_docx-0.1.0-cp39-abi3-macosx_11_0_arm64.whl

Download URL betteroffice_docx-0.1.0-cp39-abi3-macosx_11_0_arm64.whl
Size 4.8 MB
Tags CPython 3.9 abi3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
3d0d118c2ecdd29a9b0d66f59706c06fde524761a00fd4127181f312f96e5fae
BLAKE2b-256 checksum
How to use checksums
0435f7e44ae1ce67416bc10350d49289cb7b05a98f7796c5eca38a45e4808f97
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 16, 2026.

Transparency log

Release files / betteroffice_docx-0.1.0-cp39-abi3-macosx_10_12_x86_64.whl

Download URL betteroffice_docx-0.1.0-cp39-abi3-macosx_10_12_x86_64.whl
Size 5.1 MB
Tags CPython 3.9 abi3 macOS 10.12+ x86-64
SHA-256 checksum
How to use checksums
5f4a885a8d2c4617772007bd320aa24ea59cfa90b396d7257bc24d72de80a0f8
BLAKE2b-256 checksum
How to use checksums
5e0843b6cd480d9a2ec2d3f43775a52e06d010d7187eaec97ffd7ea10440b353
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 16, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.0 This release

6 release files

0.0.2

6 release files

0.0.1

6 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page