Skip to main content

See every page side by side: jandira-tech.github.io/neurotic_docx_bench
jubarte vs Microsoft Word, docxide-pdf, LibreOffice, PyMuPDF Pro, MiniPdf, rdocx and office2pdf on 808 documents, DOCX to PDF, scored per page.

jubarte-redlines (Python)

PyPI CI license

Word-faithful DOCX redline engine: compare two Word documents into a tracked-changes document that opens cleanly in Microsoft Word; list, accept, or reject revisions; render DOCX to PDF.

Python bindings (PyO3 + maturin) for the Rust jubarte-redlines engine. Pure compute, no Word/LibreOffice dependency, no network, no temp files — documents go in and come out as bytes (whole DOCX/PDF packages), revision metadata comes back as parsed records (get_revisions → list[dict], get_revisions_json → JSON string), and the GIL is released while the engine runs.

Install

pip install jubarte-redlines
# or: uv add jubarte-redlines

Prebuilt wheels are abi3 (one wheel per platform, CPython ≥ 3.10).

Command line

The wheel installs a jubarte-redlines command; uvx runs it without installing anything:

uvx jubarte-redlines redline original.docx modified.docx -o redline.docx --author Legal
uvx jubarte-redlines changes redline.docx
uvx jubarte-redlines accept redline.docx -o clean.docx --kind formatting
uvx jubarte-redlines reject redline.docx -o original-again.docx --id body:rev:12
uvx jubarte-redlines convert redline.docx --revisions word
uvx jubarte-redlines inspect contract.docx --json
uvx jubarte-redlines text contract.docx
uvx jubarte-redlines edit contract.docx --plan plan.json --out-dir review --pdf
uvx jubarte-redlines capabilities --json
uvx jubarte-redlines --help

redline is an alias of compare; python -m jubarte_redlines runs the same commands. revisions and changes list what a document tracks (--json for one object per line); accept and reject take repeatable --id, --author and --kind to resolve a selection and keep the rest tracked, or everything when none is given; text prints the Markdown with the [body:p:N] ids edit plans use, inspect the paragraph/package snapshot; edit applies an edit plan to --out-dir (clean copy, redline, patch.diff, report.jsonl, optional PDF/PNG pages); capabilities reports what the build can do. Exit codes: 0 success, 1 error, 2 usage, 3 edit plan refused. Inputs are .docx: save a Word 97-2003 .doc as .docx first.

Library

from pathlib import Path
from jubarte_redlines import (
    compare_documents,
    get_revisions,
    accept_revisions,
    reject_revisions,
    docx_to_pdf,
)

original = Path("original.docx").read_bytes()
modified = Path("modified.docx").read_bytes()

# Word-mode compare → tracked-changes DOCX (w:ins / w:del)
redline = compare_documents(original, modified, author="Reviewer")
Path("redline.docx").write_bytes(redline)

# List revisions (same shape as the CLI `jubarte revisions --json`)
for rev in get_revisions(redline):
    print(rev["type"], rev["author"], repr(rev.get("text")))

# Accept / reject every tracked revision, package-wide
clean = accept_revisions(redline)   # ≙ modified content
base = reject_revisions(redline)    # ≙ original content

# Render to PDF (Word-style layout)
Path("redline.pdf").write_bytes(docx_to_pdf(redline))

compare_documents(original, modified, author="jubarte", date=None) stamps revisions with a fixed epoch date by default so output is deterministic; pass an ISO-8601 date to override. Errors raise jubarte_redlines.JubarteError.

Both inputs are admitted under a size budget before anything is inflated (512 MiB per file and per part, 2 GiB inflated, 10,000 entries, XML depth 256). input_limits overrides it key by key, for example compare_documents(a, b, input_limits={"max_uncompressed_bytes": 256 << 20}); the keys are max_compressed_bytes, max_entries, max_part_bytes, max_uncompressed_bytes and max_xml_depth. A package past the budget raises JubarteError with INPUT_LIMIT, and an unknown key raises invalid input limits. get_revisions_json and CompareOptions(input_limits=...) take the same mapping.

read() returns a Document — an immutable snapshot, path I/O done once, no operation mutating it or writing files:

from jubarte_redlines import read, EditPlan, diff

doc = read("contract.docx")
doc.markdown()    # body and every header/footer/notes story, [body:p:N] ids
doc.inspect()     # Snapshot: summary + paragraphs (ids, style, spans, limitations) + tables
doc.sha256()      # the source_sha256 guard an edit plan carries
doc.changes()     # each tracked change, with the id accept/reject and plans take
doc.accept(ids=["body:rev:12"])   # or reject(...): resolve a selection,
                                  # keep the rest tracked (no selection = all)
result = doc.render(pdf=True, png_dpi=144)  # Rendered: pdf, pngs, page report

doc.compare(other, author=...) is compare_documents; doc.to_pdf() and doc.to_png(dpi=...) are the one-shot renderers.

An EditPlan builds the tracked-changes transaction doc.edit(plan) applies — all-or-nothing: a refused plan raises EditPlanError (stable code, the failed operation, every operation's outcome) and produces nothing. doc.preview(plan) resolves and reports without applying:

plan = (
    EditPlan(author="Reviewer")
    .for_document(doc)             # bind to this snapshot; STALE_SOURCE if it changed
    .replace("body:p:3", find="30 days", replacement="45 days", format={"bold": True})
    .comment("body:p:3", text="Check with the client", find="45 days")
    .insert_paragraph("body:p:3", runs=["New sentence."], like="body:p:3")
)
out = doc.edit(plan)               # EditResult: clean, redline, report, diff

replace, insert, delete, comment and format_run edit run text; insert_paragraph, delete_paragraph, format_paragraph, merge_paragraphs and rewrite work on whole paragraphs; insert_table(paragraph, rows=[[...], ...]) adds a table and list_paragraphs([...], kind_of_list="decimal") (wire kind list) numbers paragraphs; insert_footnote adds a footnote after an anchor; insert_image adds a picture paragraph; page_setup changes the page size, orientation and margins; resolving(accept={...}, reject={...}) settles existing tracked changes first. plan.to_json() is exactly what edit --plan reads.

diff(old, new) shows the changed paragraphs between two documents — or a document and Markdown text — as [-old-]{+new+} marks (format="critic" for CriticMarkup):

print(diff(read("v1.docx"), read("v2.docx")))

doc.append(other) puts other after doc on a new page and returns Appended(document, warnings): images, links, headers, styles, lists and notes come along. Comments are dropped (COMMENTS_DROPPED in warnings) unless comments="carry", which brings the comments other's body anchors with their threads and resolution. section_break="continuous" joins on the same page and keep_sections=True keeps other's page setup, headers and footers.

Before a document goes out, or after an agent edited it:

doc.validate()        # Word-validity findings beyond the schema; () is a pass
fixed = doc.repair()  # Repaired: document, repaired, remaining
doc.audit_tracked(read("original.docx"), author="Reviewer")  # untracked or foreign edits
doc.audit("a11y,structure")  # accessibility, style and structure findings by paragraph id
out = doc.scrub()     # authors as "Author", no rsids, document properties or comments
doc.update_fields()   # PAGEREF, REF, NUMPAGES, SEQ and TOC results from jubarte's layout

from_markdown(text, author=..., track_changes="all") writes Markdown as a Word document, CriticMarkup becoming tracked changes and comments, as jubarte convert draft.md does; reference= lends a document's styles and page setup. diff_render(a, b, dpi=100) says which pages of two documents look different, pixel for pixel, with magenta overlays on the changed pages.

Document.from_bytes(data) holds bytes you already have and to_bytes() returns them; inspect_json() is the engine's inspect snapshot as JSON text, unchanged (inspect() decodes it). scrub takes author_alias, rsids, docprops and comments to choose what goes.

Also available as

Rendering and fonts

The PDF engine reconstructs Word-oriented layout directly; it does not call Microsoft Word or LibreOffice.

The documented rendering surface includes:

  • tracked changes and comments;
  • paragraph/run formatting and advanced line layout;
  • CJK and RTL/complex-script text;
  • installed and embedded fonts;
  • merged, vertically merged, autofit and floating tables;
  • DrawingML/VML graphics, text boxes, charts and SmartArt;
  • raster and EMF/WMF images;
  • headers, footers and floating frames;
  • sections, columns, backgrounds and page borders;
  • footnotes and selected fields.

Rendering fidelity still depends on fonts and on document features.

Supplemental font lookup can be configured with:

export JUBARTE_FONT_DIR=/path/to/jubarte/fonts
export JUBARTE_FONT_INDEX=/path/to/font-index.tsv

Set:

export JUBARTE_FONT_INDEX=off

to disable the persistent font index.

Default per-user font directories are documented in the installation section of this repository.

For known Word-layout differences, see:

Comparison modes

The default comparison mode is:

--mode word

It targets Word-like comparison behavior.

Classic PowerTools behavior is available with:

--mode powertools

Choose explicitly if exact comparison semantics matter to a regression test or downstream workflow.

Supported environments

Surface Supported/tested target
Rust / CLI CI tests Linux, macOS and Windows
Rust toolchain Rust 1.88+
Python CPython 3.10+
Python release wheels macOS x86_64/arm64, manylinux_2_28 x86_64/arm64, musllinux_1_2 x86_64/arm64, Windows x86_64 (abi3, CPython 3.10+)
Node Node 18+
Browser WebAssembly builds
CLI release workflow Linux x86_64/arm64, macOS x86_64/arm64, Windows x86_64 targets

Release assets can vary by tag. Check the release page before scripting a binary download.

Legacy binary .doc files are not supported: a .doc, or an encrypted document of any Word version, is refused with LEGACY_DOC and the hint to save it as .docx without a password, and RTF with UNSUPPORTED_PACKAGE. Convert them to .docx first.

Troubleshooting

Problem What to check
PDF layout differs from Word Verify required fonts; inspect --font-report; read docs/WORD_DIFFERENCES.md
Text is missing or wraps differently Check font resolution and embedded/system font availability
Edit exits with a refusal Read report.jsonl / stdout for STALE_SOURCE, ANCHOR_NOT_FOUND, AMBIGUOUS_ANCHOR, OVERLAPPING_EDITS, UNSUPPORTED_STRUCTURE, etc.
STALE_SOURCE Re-run jubarte inspect and regenerate the plan from the new source_sha256
AMBIGUOUS_ANCHOR Use a paragraph ID or a more specific exact anchor
Document already contains revisions List them with jubarte changes; explicitly accept/reject them or set an edit-plan revision policy
Comment is present in DOCX but missing from rendered markup Check the documented comment-balloon limitations and docs/WORD_COMMENT_BALLOONS.md
Output page count is not exactly Word's The renderer targets Word behavior but is independent; inspect known differences before treating page count as an invariant
Prebuilt archive is absent for a platform Install with Cargo or build from source; then check the release assets for a later tag
Need exact package differences Use jubarte debug diff and the text, runs, xml or render debug catalogs

License

jubarte-redlines is licensed under GNU Affero General Public License v3.0 only (AGPL-3.0-only).

Review the license requirements before embedding or deploying the software in a product or network service.

Engine comparison site · jandira.tech · arthur.law · Cicero · LinkedIn · contact@arthur.law

Metadata

Release files for jubarte-redlines 0.11.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 jubarte-redlines 0.11.0
File Size Uploaded
jubarte_redlines-0.11.0.tar.gz 6.0 MB Details

Built distributions (wheels)

Table of built distributions (wheels) for jubarte-redlines 0.11.0
File
jubarte_redlines-0.11.0-cp310-abi3-win_amd64.whl CPython 3.10 abi3 Windows x86-64 Details
jubarte_redlines-0.11.0-cp310-abi3-musllinux_1_2_x86_64.whl CPython 3.10 abi3 Linux musl 1.2+ x86-64 Details
jubarte_redlines-0.11.0-cp310-abi3-musllinux_1_2_aarch64.whl CPython 3.10 abi3 Linux musl 1.2+ ARM64 Details
jubarte_redlines-0.11.0-cp310-abi3-manylinux_2_28_x86_64.whl CPython 3.10 abi3 Linux glibc 2.28+ x86-64 Details
jubarte_redlines-0.11.0-cp310-abi3-manylinux_2_28_aarch64.whl CPython 3.10 abi3 Linux glibc 2.28+ ARM64 Details
jubarte_redlines-0.11.0-cp310-abi3-macosx_11_0_arm64.whl CPython 3.10 abi3 macOS 11.0+ ARM64 Details
jubarte_redlines-0.11.0-cp310-abi3-macosx_10_12_x86_64.whl CPython 3.10 abi3 macOS 10.12+ x86-64 Details

Total release size: 76.9 MB

Release files / jubarte_redlines-0.11.0.tar.gz

Download URL jubarte_redlines-0.11.0.tar.gz
Size 6.0 MB
Tags Source
SHA-256 checksum
How to use checksums
03de3a9f8f0165a64172e379a063192b048a71f94645e4f96d4b653ec7084e90
BLAKE2b-256 checksum
How to use checksums
8ac4844a9c87cdd9f3f364f65fe5c05c1de25b2710f344ff14b8f16ae3a55d25
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.26 {"installer":{"name":"uv","version":"0.11.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / jubarte_redlines-0.11.0-cp310-abi3-win_amd64.whl

Download URL jubarte_redlines-0.11.0-cp310-abi3-win_amd64.whl
Size 10.4 MB
Tags CPython 3.10 Windows x86-64 abi3
SHA-256 checksum
How to use checksums
e0dffd6d53ef2a6301697f4a8ad6e7872bc3c4651c2248ae85ee2a0d6059388d
BLAKE2b-256 checksum
How to use checksums
f96a9dc37c5170aff12528f17d06a0a55271c569514253204085266e245203fd
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.26 {"installer":{"name":"uv","version":"0.11.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / jubarte_redlines-0.11.0-cp310-abi3-musllinux_1_2_x86_64.whl

Download URL jubarte_redlines-0.11.0-cp310-abi3-musllinux_1_2_x86_64.whl
Size 10.5 MB
Tags CPython 3.10 Linux musl 1.2+ x86-64 abi3
SHA-256 checksum
How to use checksums
fdfe122166d4d93feb8c0ca1650d3db9aca2d5d2cf4be390f8332b96378b5115
BLAKE2b-256 checksum
How to use checksums
dab194ebb1637b069182fbdaf16b54abeb95add5202f6e4c65bb6597fd42ee03
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.26 {"installer":{"name":"uv","version":"0.11.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / jubarte_redlines-0.11.0-cp310-abi3-musllinux_1_2_aarch64.whl

Download URL jubarte_redlines-0.11.0-cp310-abi3-musllinux_1_2_aarch64.whl
Size 10.1 MB
Tags CPython 3.10 Linux musl 1.2+ ARM64 abi3
SHA-256 checksum
How to use checksums
fd6687df7406dd3105607fd774ea6c7a71bc525a1e72097055c3b4fdcffe9f29
BLAKE2b-256 checksum
How to use checksums
b37d9e7f0005c17922d8496cce291059f82a46bf7107eda121308cc9957604ec
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.26 {"installer":{"name":"uv","version":"0.11.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / jubarte_redlines-0.11.0-cp310-abi3-manylinux_2_28_x86_64.whl

Download URL jubarte_redlines-0.11.0-cp310-abi3-manylinux_2_28_x86_64.whl
Size 10.3 MB
Tags CPython 3.10 Linux glibc 2.28+ x86-64 abi3
SHA-256 checksum
How to use checksums
e5012875a3bad40de7220ad5fca788e9eb7157d43d4bbede5a6ab00e4c5ae365
BLAKE2b-256 checksum
How to use checksums
f355e70cfd404c0580b132c605fa7880757220a1535f5262ec49abfcc4905701
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.26 {"installer":{"name":"uv","version":"0.11.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / jubarte_redlines-0.11.0-cp310-abi3-manylinux_2_28_aarch64.whl

Download URL jubarte_redlines-0.11.0-cp310-abi3-manylinux_2_28_aarch64.whl
Size 9.9 MB
Tags CPython 3.10 Linux glibc 2.28+ ARM64 abi3
SHA-256 checksum
How to use checksums
3a2676a1a7818fc4eae13f4238b8cc1fee9dfc771fe7ea38167a990a76a64a55
BLAKE2b-256 checksum
How to use checksums
653ced0249a6e2841acdb0de117d29738b53f29dd85b150112fef54b1d876ad9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.26 {"installer":{"name":"uv","version":"0.11.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / jubarte_redlines-0.11.0-cp310-abi3-macosx_11_0_arm64.whl

Download URL jubarte_redlines-0.11.0-cp310-abi3-macosx_11_0_arm64.whl
Size 9.7 MB
Tags CPython 3.10 abi3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
d8d77be170e2592b25e3f979cc28f84d479448459928fad8d97dca95cde3fa5c
BLAKE2b-256 checksum
How to use checksums
de15c943b404cf6d9cf3032f5960c32de1a69c3f1aafd468a0e5519461358a75
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.26 {"installer":{"name":"uv","version":"0.11.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / jubarte_redlines-0.11.0-cp310-abi3-macosx_10_12_x86_64.whl

Download URL jubarte_redlines-0.11.0-cp310-abi3-macosx_10_12_x86_64.whl
Size 10.1 MB
Tags CPython 3.10 abi3 macOS 10.12+ x86-64
SHA-256 checksum
How to use checksums
a67ad0255fed8f8bfb8f8afe096fc56b766b87f756f65c6a70babfbc93c37563
BLAKE2b-256 checksum
How to use checksums
4b71b0b7b011b118334a2f43ced67589e7823f7dc12b6e7969871d50cdaeed78
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.26 {"installer":{"name":"uv","version":"0.11.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release history Release notifications | RSS feed

0.12.0

8 release files

This release

0.11.0 This release

8 release files

0.10.1

5 release files

0.10.0

5 release files

0.9.3

5 release files

0.9.2

5 release files

0.7.1

5 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