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)
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
- Rust crate:
jubarte-redlines(this engine, plus a CLI) - npm / WebAssembly:
jubarte-wasm(Node and browser builds) - MCP server:
pip install 'jubarte-redlines[mcp]'thenjubarte-mcp --root .(setup for Claude Code, Codex and Gemini CLI)
Text comparisons
jubarte-redlines diff a.docx b.docx --format github
jubarte-redlines diff a.docx b.docx --format github -U0 --accept-changes
jubarte-redlines diff a.docx b.docx --format word
Line views (github, normal, context, side-by-side) retain each input’s
tracked marks. Word comparison accepts all changes in both inputs first,
then creates fresh CriticMarkup; earlier revision provenance disappears.
critic remains a representation of document content with its marks.
Long lines use a 70-character review window; --full-lines shows all text.
Text views support DOCX and Markdown and write stdout or an explicit -o text
file. CLI help and usage errors come from the shared clap-derived grammar.
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 as Word's balloons in the markup pane (a resolved comment faded, a reply numbered under its thread);
- 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.
WebAssembly has no installed fonts: docxToPdf paints with the fonts a
document embeds and the bundled Carlito and Liberation faces, so Chinese,
Japanese or Korean text without an embedded font is not painted (see
KNOWN_ISSUES.md, item 9).
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.94+ |
| 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.
A Word 97-2003 .doc converts to .docx with the native binary's
jubarte convert old.doc (or jubarte::legacy_doc::doc_to_docx in Rust):
text, headings, lists, bold, italic and tables. Every other command, and
the Python and JavaScript packages (the npm command line too), refuse a .doc (or an encrypted document of any Word
version) 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.
Links
Engine comparison site ·
jandira.tech · arthur.law ·
Cicero · LinkedIn ·
contact@arthur.law
Metadata
Release files for jubarte-redlines 0.12.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| jubarte_redlines-0.12.0.tar.gz | 6.8 MB | Details |
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| jubarte_redlines-0.12.0-cp310-abi3-win_amd64.whl | CPython 3.10 | abi3 | Windows x86-64 | Details |
| jubarte_redlines-0.12.0-cp310-abi3-musllinux_1_2_x86_64.whl | CPython 3.10 | abi3 | Linux musl 1.2+ x86-64 | Details |
| jubarte_redlines-0.12.0-cp310-abi3-musllinux_1_2_aarch64.whl | CPython 3.10 | abi3 | Linux musl 1.2+ ARM64 | Details |
| jubarte_redlines-0.12.0-cp310-abi3-manylinux_2_28_x86_64.whl | CPython 3.10 | abi3 | Linux glibc 2.28+ x86-64 | Details |
| jubarte_redlines-0.12.0-cp310-abi3-manylinux_2_28_aarch64.whl | CPython 3.10 | abi3 | Linux glibc 2.28+ ARM64 | Details |
| jubarte_redlines-0.12.0-cp310-abi3-macosx_11_0_arm64.whl | CPython 3.10 | abi3 | macOS 11.0+ ARM64 | Details |
| jubarte_redlines-0.12.0-cp310-abi3-macosx_10_12_x86_64.whl | CPython 3.10 | abi3 | macOS 10.12+ x86-64 | Details |
Total release size: 85.4 MB
Release files / jubarte_redlines-0.12.0.tar.gz
| Download URL | jubarte_redlines-0.12.0.tar.gz |
|---|---|
| Size | 6.8 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
a843f605e2e598aab8a0d113965d9a33c508237ce71ea368a015734a0aad5f1a
|
|
BLAKE2b-256 checksum How to use checksums |
6b6075b514f6712d02f811f8ffddd391bfd6e7d9d985d3f38edeeaade68deb91
|
| 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.12.0-cp310-abi3-win_amd64.whl
| Download URL | jubarte_redlines-0.12.0-cp310-abi3-win_amd64.whl |
|---|---|
| Size | 11.1 MB |
| Tags | CPython 3.10 Windows x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
e4978d3e5cb635a2e4b5b339eca259c424ecd1fcbd6067babd6d12aef99c670b
|
|
BLAKE2b-256 checksum How to use checksums |
e3da8a5f55310623e5de42b689adf4e76f2f2679b033d332d7cea6d90bd0b5c6
|
| 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.12.0-cp310-abi3-musllinux_1_2_x86_64.whl
| Download URL | jubarte_redlines-0.12.0-cp310-abi3-musllinux_1_2_x86_64.whl |
|---|---|
| Size | 11.8 MB |
| Tags | CPython 3.10 Linux musl 1.2+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
47bfdedb8c4e4427b1794b58f1948c12e95384906ae390d4542ca0013a2e5905
|
|
BLAKE2b-256 checksum How to use checksums |
3479ec8ba72530b4aef3009edf740d309764866c75ad89ae2a60858b54acc902
|
| 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.12.0-cp310-abi3-musllinux_1_2_aarch64.whl
| Download URL | jubarte_redlines-0.12.0-cp310-abi3-musllinux_1_2_aarch64.whl |
|---|---|
| Size | 11.3 MB |
| Tags | CPython 3.10 Linux musl 1.2+ ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
b2a74a15705da790f0d6a446c4029597ebb88d20e05c38904c7b2ca8383569d3
|
|
BLAKE2b-256 checksum How to use checksums |
38d3f88e35948fc077c956d8aaf7de9f8a3c8034f990ad0ba836a9070f2c566b
|
| 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.12.0-cp310-abi3-manylinux_2_28_x86_64.whl
| Download URL | jubarte_redlines-0.12.0-cp310-abi3-manylinux_2_28_x86_64.whl |
|---|---|
| Size | 11.6 MB |
| Tags | CPython 3.10 Linux glibc 2.28+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
f4a19e56a77e4d1309b65942fa4ef39a725e28b07da03c3f4fd4f1f68550d1c1
|
|
BLAKE2b-256 checksum How to use checksums |
78292044ea1eac950174d00d005ea1ff99ab6de793b9e278139c982629c7ed24
|
| 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.12.0-cp310-abi3-manylinux_2_28_aarch64.whl
| Download URL | jubarte_redlines-0.12.0-cp310-abi3-manylinux_2_28_aarch64.whl |
|---|---|
| Size | 11.1 MB |
| Tags | CPython 3.10 Linux glibc 2.28+ ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
9c044fbbad4cd9104c3da72e564b6803b87fd5cb1660fca24b27d536ba2d591f
|
|
BLAKE2b-256 checksum How to use checksums |
7ad9c0596ffea17e4adc1e9f933b2626f465817bc81e23f721675fb9da12202d
|
| 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.12.0-cp310-abi3-macosx_11_0_arm64.whl
| Download URL | jubarte_redlines-0.12.0-cp310-abi3-macosx_11_0_arm64.whl |
|---|---|
| Size | 10.6 MB |
| Tags | CPython 3.10 abi3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
2075f935be25ad3567e6886a97ab93dbc1994fe474e315ccfa1e1081dd406213
|
|
BLAKE2b-256 checksum How to use checksums |
2b0e0378dd06d085d528694a6d355a51e0af6def0991de9f037e62db5689903e
|
| 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.12.0-cp310-abi3-macosx_10_12_x86_64.whl
| Download URL | jubarte_redlines-0.12.0-cp310-abi3-macosx_10_12_x86_64.whl |
|---|---|
| Size | 11.0 MB |
| Tags | CPython 3.10 abi3 macOS 10.12+ x86-64 |
|
SHA-256 checksum How to use checksums |
f399672e5add221d96927ec8e320996f09094a9932c7d0ef79eec4b2f44c5c87
|
|
BLAKE2b-256 checksum How to use checksums |
234ffbb1b6b885c4122822c87f1a3445c92c705c939f936abaf6f758be1d9d74
|
| 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}
|