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.
Links
- BetterOffice — the project
- Documentation
- Source —
bindings/python-docx - betteroffice-docx on crates.io — the engine this wraps
Apache-2.0.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file betteroffice_docx-0.0.1.tar.gz.
File metadata
- Download URL: betteroffice_docx-0.0.1.tar.gz
- Upload date:
- Size: 1.5 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a7d92224fe4f74e8dcff8a3d311320a21c9becabc71cdbb8f17ea5244f8cac24
|
|
| MD5 |
69366723a2081322bf58005ac70ff868
|
|
| BLAKE2b-256 |
cccdaa32141056f364e1c832c68e8f3902b7242f83540e66b4fd607f08ae60ee
|
Provenance
The following attestation bundles were made for betteroffice_docx-0.0.1.tar.gz:
Publisher:
publish-python-binding.yml on openooxml/betteroffice
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
betteroffice_docx-0.0.1.tar.gz -
Subject digest:
a7d92224fe4f74e8dcff8a3d311320a21c9becabc71cdbb8f17ea5244f8cac24 - Sigstore transparency entry: 2492890872
- Sigstore integration time:
-
Permalink:
openooxml/betteroffice@b6a5f3bf2d1a7468397069237cfea28a5cda02ab -
Branch / Tag:
refs/heads/main - Owner: https://github.com/openooxml
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-python-binding.yml@b6a5f3bf2d1a7468397069237cfea28a5cda02ab -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file betteroffice_docx-0.0.1-cp39-abi3-win_amd64.whl.
File metadata
- Download URL: betteroffice_docx-0.0.1-cp39-abi3-win_amd64.whl
- Upload date:
- Size: 5.4 MB
- Tags: CPython 3.9+, Windows x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
10f5ac0c78bfedc1520ce8166095e5a6af7ef744593c61e41f1fb85300bc403a
|
|
| MD5 |
1370d503a6dcaaae2699567900ab72be
|
|
| BLAKE2b-256 |
ac8c1bf42b93a5805c61679e311deba68b9f7b0624c2091ce621cf9de70a1cf4
|
Provenance
The following attestation bundles were made for betteroffice_docx-0.0.1-cp39-abi3-win_amd64.whl:
Publisher:
publish-python-binding.yml on openooxml/betteroffice
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
betteroffice_docx-0.0.1-cp39-abi3-win_amd64.whl -
Subject digest:
10f5ac0c78bfedc1520ce8166095e5a6af7ef744593c61e41f1fb85300bc403a - Sigstore transparency entry: 2492891108
- Sigstore integration time:
-
Permalink:
openooxml/betteroffice@b6a5f3bf2d1a7468397069237cfea28a5cda02ab -
Branch / Tag:
refs/heads/main - Owner: https://github.com/openooxml
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-python-binding.yml@b6a5f3bf2d1a7468397069237cfea28a5cda02ab -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file betteroffice_docx-0.0.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.
File metadata
- Download URL: betteroffice_docx-0.0.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
- Upload date:
- Size: 5.1 MB
- Tags: CPython 3.9+, manylinux: glibc 2.17+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
98021544f8623ccac6b9dbc8823159dc2ea67e2f55ddc8cdbfa05b78f0488844
|
|
| MD5 |
e9ef0e3582328ee82de8612e17a3367d
|
|
| BLAKE2b-256 |
850fb59a7cbcf7079e435e289b46162c2600d29cfa8fd29a86694198662fbcd4
|
Provenance
The following attestation bundles were made for betteroffice_docx-0.0.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:
Publisher:
publish-python-binding.yml on openooxml/betteroffice
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
betteroffice_docx-0.0.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl -
Subject digest:
98021544f8623ccac6b9dbc8823159dc2ea67e2f55ddc8cdbfa05b78f0488844 - Sigstore transparency entry: 2492890952
- Sigstore integration time:
-
Permalink:
openooxml/betteroffice@b6a5f3bf2d1a7468397069237cfea28a5cda02ab -
Branch / Tag:
refs/heads/main - Owner: https://github.com/openooxml
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-python-binding.yml@b6a5f3bf2d1a7468397069237cfea28a5cda02ab -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file betteroffice_docx-0.0.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.
File metadata
- Download URL: betteroffice_docx-0.0.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
- Upload date:
- Size: 4.9 MB
- Tags: CPython 3.9+, manylinux: glibc 2.17+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3160481a8cd3ef537c9b04c1f8c69f518c9f67a7c74cb4056cb3a2a98fc9db4f
|
|
| MD5 |
4367c19352e5d0baf5e79b170f68c61e
|
|
| BLAKE2b-256 |
357298cb62e732897a914a5cc23ae60c78be0f7929035f154181a80f1c51de8f
|
Provenance
The following attestation bundles were made for betteroffice_docx-0.0.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl:
Publisher:
publish-python-binding.yml on openooxml/betteroffice
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
betteroffice_docx-0.0.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl -
Subject digest:
3160481a8cd3ef537c9b04c1f8c69f518c9f67a7c74cb4056cb3a2a98fc9db4f - Sigstore transparency entry: 2492891059
- Sigstore integration time:
-
Permalink:
openooxml/betteroffice@b6a5f3bf2d1a7468397069237cfea28a5cda02ab -
Branch / Tag:
refs/heads/main - Owner: https://github.com/openooxml
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-python-binding.yml@b6a5f3bf2d1a7468397069237cfea28a5cda02ab -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file betteroffice_docx-0.0.1-cp39-abi3-macosx_11_0_arm64.whl.
File metadata
- Download URL: betteroffice_docx-0.0.1-cp39-abi3-macosx_11_0_arm64.whl
- Upload date:
- Size: 4.8 MB
- Tags: CPython 3.9+, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5df03747bc31523af7b8b605e86561b7688fa63a06aef8ef4ff637096a98d683
|
|
| MD5 |
1e559c3960ffa427bb56b11e503b9747
|
|
| BLAKE2b-256 |
0e31432d1941fd94accf92b080d3ed2d3bf0643d37b59ac5689fe579edcf41db
|
Provenance
The following attestation bundles were made for betteroffice_docx-0.0.1-cp39-abi3-macosx_11_0_arm64.whl:
Publisher:
publish-python-binding.yml on openooxml/betteroffice
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
betteroffice_docx-0.0.1-cp39-abi3-macosx_11_0_arm64.whl -
Subject digest:
5df03747bc31523af7b8b605e86561b7688fa63a06aef8ef4ff637096a98d683 - Sigstore transparency entry: 2492890917
- Sigstore integration time:
-
Permalink:
openooxml/betteroffice@b6a5f3bf2d1a7468397069237cfea28a5cda02ab -
Branch / Tag:
refs/heads/main - Owner: https://github.com/openooxml
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-python-binding.yml@b6a5f3bf2d1a7468397069237cfea28a5cda02ab -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file betteroffice_docx-0.0.1-cp39-abi3-macosx_10_12_x86_64.whl.
File metadata
- Download URL: betteroffice_docx-0.0.1-cp39-abi3-macosx_10_12_x86_64.whl
- Upload date:
- Size: 5.0 MB
- Tags: CPython 3.9+, macOS 10.12+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e4267bccd1143e249809944108f2252e1cf692fb3f9a5238382cf299a6f40bfe
|
|
| MD5 |
0d4fdd1abca2b1560672bb4df3b6084d
|
|
| BLAKE2b-256 |
26d02e157db4b04ea3430beffaeb968c266900bcbabb85e0750ede16cd458a33
|
Provenance
The following attestation bundles were made for betteroffice_docx-0.0.1-cp39-abi3-macosx_10_12_x86_64.whl:
Publisher:
publish-python-binding.yml on openooxml/betteroffice
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
betteroffice_docx-0.0.1-cp39-abi3-macosx_10_12_x86_64.whl -
Subject digest:
e4267bccd1143e249809944108f2252e1cf692fb3f9a5238382cf299a6f40bfe - Sigstore transparency entry: 2492891005
- Sigstore integration time:
-
Permalink:
openooxml/betteroffice@b6a5f3bf2d1a7468397069237cfea28a5cda02ab -
Branch / Tag:
refs/heads/main - Owner: https://github.com/openooxml
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-python-binding.yml@b6a5f3bf2d1a7468397069237cfea28a5cda02ab -
Trigger Event:
workflow_dispatch
-
Statement type: