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.

Links

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

betteroffice_docx-0.0.1.tar.gz (1.5 MB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

betteroffice_docx-0.0.1-cp39-abi3-win_amd64.whl (5.4 MB view details)

Uploaded CPython 3.9+Windows x86-64

betteroffice_docx-0.0.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (5.1 MB view details)

Uploaded CPython 3.9+manylinux: glibc 2.17+ x86-64

betteroffice_docx-0.0.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (4.9 MB view details)

Uploaded CPython 3.9+manylinux: glibc 2.17+ ARM64

betteroffice_docx-0.0.1-cp39-abi3-macosx_11_0_arm64.whl (4.8 MB view details)

Uploaded CPython 3.9+macOS 11.0+ ARM64

betteroffice_docx-0.0.1-cp39-abi3-macosx_10_12_x86_64.whl (5.0 MB view details)

Uploaded CPython 3.9+macOS 10.12+ x86-64

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

Hashes for betteroffice_docx-0.0.1.tar.gz
Algorithm Hash digest
SHA256 a7d92224fe4f74e8dcff8a3d311320a21c9becabc71cdbb8f17ea5244f8cac24
MD5 69366723a2081322bf58005ac70ff868
BLAKE2b-256 cccdaa32141056f364e1c832c68e8f3902b7242f83540e66b4fd607f08ae60ee

See more details on using hashes here.

Provenance

The following attestation bundles were made for betteroffice_docx-0.0.1.tar.gz:

Publisher: publish-python-binding.yml on openooxml/betteroffice

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file betteroffice_docx-0.0.1-cp39-abi3-win_amd64.whl.

File metadata

File hashes

Hashes for betteroffice_docx-0.0.1-cp39-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 10f5ac0c78bfedc1520ce8166095e5a6af7ef744593c61e41f1fb85300bc403a
MD5 1370d503a6dcaaae2699567900ab72be
BLAKE2b-256 ac8c1bf42b93a5805c61679e311deba68b9f7b0624c2091ce621cf9de70a1cf4

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file betteroffice_docx-0.0.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for betteroffice_docx-0.0.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 98021544f8623ccac6b9dbc8823159dc2ea67e2f55ddc8cdbfa05b78f0488844
MD5 e9ef0e3582328ee82de8612e17a3367d
BLAKE2b-256 850fb59a7cbcf7079e435e289b46162c2600d29cfa8fd29a86694198662fbcd4

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file betteroffice_docx-0.0.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for betteroffice_docx-0.0.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 3160481a8cd3ef537c9b04c1f8c69f518c9f67a7c74cb4056cb3a2a98fc9db4f
MD5 4367c19352e5d0baf5e79b170f68c61e
BLAKE2b-256 357298cb62e732897a914a5cc23ae60c78be0f7929035f154181a80f1c51de8f

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file betteroffice_docx-0.0.1-cp39-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for betteroffice_docx-0.0.1-cp39-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 5df03747bc31523af7b8b605e86561b7688fa63a06aef8ef4ff637096a98d683
MD5 1e559c3960ffa427bb56b11e503b9747
BLAKE2b-256 0e31432d1941fd94accf92b080d3ed2d3bf0643d37b59ac5689fe579edcf41db

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file betteroffice_docx-0.0.1-cp39-abi3-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for betteroffice_docx-0.0.1-cp39-abi3-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 e4267bccd1143e249809944108f2252e1cf692fb3f9a5238382cf299a6f40bfe
MD5 0d4fdd1abca2b1560672bb4df3b6084d
BLAKE2b-256 26d02e157db4b04ea3430beffaeb968c266900bcbabb85e0750ede16cd458a33

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page