A local-first document converter: a command-line tool, a browser workbench that
runs on your own computer, a packaged macOS app, and an agent skill
(SKILL.md) for Codex and Claude. Documents are converted on your
machine; nothing is uploaded to a remote service.
How it was built, and the ten defects that looked fine until they were checked: the case study, also in 繁體中文 and 简体中文.
Inputs. What a given install accepts depends on which engines it has.
| Input | From source, Docling installed | macOS app, text profile |
macOS app, pdf profile |
macOS app, office-ocr profile |
|---|---|---|---|---|
Markdown .md .markdown |
✅ | ✅ | ✅ | ✅ |
UTF-8 text .txt |
✅ | ✅ | ✅ | ✅ |
| PDF with a text layer | ✅ | ❌ | ✅ | ✅ |
| PDF that is a scan or photo | ✅ with OCR: Docling's models (downloaded on first use), or on a Mac the apple-vision extra |
❌ | ❌ | ✅ macOS's own OCR |
Office .docx .pptx .xlsx and older |
✅ (.docx .pptx .xlsx also with just the office extra, no PyTorch) |
❌ | ❌ | ✅ .docx .pptx .xlsx only |
.html .htm .epub .csv .rtf OpenDocument |
✅ | ❌ | ❌ | ❌ |
Images .png .jpg .jpeg .webp |
✅ with OCR, as for a scan | ❌ | ❌ | ✅ macOS's own OCR |
A file the install does not accept is refused before any conversion starts, and
the message names the formats that would work — for example, from the pdf
app: 這個版本不支援「.docx」檔案。可以轉換的格式:.markdown、.md、.pdf、.txt。
A text file must be UTF-8. A Big5, GBK or UTF-16 file is refused with a message saying so and asking you to re-save it as UTF-8; it is not converted into mojibake.
Limits. The web page and the macOS app take up to 5 files per conversion, each up to 24 MiB (about 25 MB as Finder counts it), 25 MiB (about 26 MB) per request. A MiB is 1,048,576 bytes; the MB Finder shows is 1,000,000, so a file Finder lists as 25.1 MB is still accepted. The CLI has no size or file-count limit.
Nothing is ever returned empty. If a conversion recovers no text — a scan with no text layer, a page that is one picture, an empty file — the answer says that and why, rather than downloading a 0-byte file.
Outputs.
| Output | From source (CLI and browser) | macOS app, text profile |
macOS app, pdf profile |
macOS app, office-ocr profile |
|---|---|---|---|---|
Markdown (.md) |
✅ | ✅ | ✅ | ✅ |
HTML (.html) |
✅ | ✅ | ✅ | ✅ |
Plain text (.txt) |
✅ | ✅ | ✅ | ✅ |
Docling JSON (.json) |
✅ when a Docling engine parsed the file | — | ✅ PDF input only | ✅ Office files and PDFs with a text layer |
Docling JSON is the engine's own structural parse of a document, so it exists
only where an engine read the file. Markdown and text files are read directly,
and the text app runs no engine at all.
It runs on Apple Silicon (M-series) Macs only. Three reviewed profiles exist:
| Profile | Accepts | Bundles |
|---|---|---|
text |
UTF-8 .md, .markdown, .txt |
No document engine and no model |
pdf |
The above, plus PDFs that already contain a text layer | Docling's model-free PDF reader — no OCR, no AI model, no PyTorch, nothing downloaded |
office-ocr |
The above, plus Word, PowerPoint and Excel (.docx, .pptx, .xlsx), scanned PDFs and images (.png, .jpg, .jpeg, .webp) |
The pdf profile, Docling's Office readers and PyObjC, which reaches the text recognition built into macOS — no OCR engine, no AI model, no PyTorch, nothing downloaded |
The pdf profile extracts the text a PDF already holds. It does not rebuild
heading levels or tables, and a scanned PDF has no text layer: the app says so
instead of returning an empty file. Office documents and images are not
accepted by the text or pdf profile.
The office-ocr profile reads a PDF's text layer first and hands a page-image
PDF, or an image, to macOS's own text recognition (Apple's Vision framework,
the recognizer Photos uses to select text in a picture). Recognized text comes
back as paragraphs, without heading levels or tables, and the page says it
should be checked. A PDF that mixes text pages with scanned pages is read from
its text layer only, so its scanned pages are left out. Word, PowerPoint and Excel keep their headings, lists and
tables. Older .doc, .ppt and .xls are not accepted.
- An app you built yourself opens with a double-click: macOS only gates files that arrived from the internet.
- An app you received — downloaded, sent by AirDrop or through a chat app — is blocked on first launch, because the candidate is only ad-hoc signed, with no Developer ID signature or Apple notarization. From macOS 15 onward, Control-click › Open no longer gets past this. Try to open the app once, then go to System Settings › Privacy & Security, find the app under Security, choose Open Anyway and enter an administrator password.
Do not disable Gatekeeper to get around this.
- Double-click
Document Converter.app. It starts a local service and opens your default browser athttp://127.0.0.1:<port>. - Drop in or select up to 5 files. Each file, and the selection as a whole, may be up to 24 MiB (about 25 MB).
- Choose an output format and select Convert. One file downloads on its own; several download as a ZIP. Each download is named after the document it came from, Chinese file names included.
- Uploads are held in the system's temporary folder under generated names and removed after every conversion.
- Closing the browser does not stop the app. To quit it, open
Activity Monitor, find
Document Converterand quit it.
You do not need Python, Node.js, Docker or VS Code to run the app.
| ✅ Verified on an Apple Silicon Mac (2026-10-12/13) | pdf candidate built, signed ad-hoc and zipped; opened like any app and converted a real document; its frozen runtime reported no forbidden module; it carried 124 third-party notice files |
| ✅ Rebuilt and checked (2026-11-08, macOS 27.2) | pdf candidate built from ccd843d, with Pillow 13.0.0, so it has the plain text and Docling JSON outputs and the download names; the 9 release tests pass; --diagnostics reports pdf-text and no forbidden module; pip-audit finds no known vulnerability in its pinned versions; the frozen app, started without a browser, converted a Chinese-named text-layer PDF to text and JSON under its own name, returned two files as a ZIP, refused .docx and explained a scan. Not yet opened through Finder |
| ⏳ Not yet built | The office-ocr candidate. CI installs its pinned macOS lock on an Apple Silicon runner and runs it unfrozen with the real Vision framework, reading back a picture of English and Traditional Chinese; freezing it needs the audited Python build on the owner's Mac |
| ⏳ Not yet verified | Installing on a second Mac — docs-release/macos-vm-verification.md covers it with a free macOS virtual machine |
| ➖ Not planned | Developer ID signing and Apple notarization — the owner decided on 2026-10-29 to stay with ad-hoc signing, since the app is not sold through the App Store and goes to few people (ROADMAP); recipients open it once as described above, and every candidate stays marked distribution_ready: false |
| ❌ Not available | A Windows app — there is no installer; running from source on Windows is covered below |
RELEASE_PROFILE=pdf BUILD_PYTHON="$HOME/.local/bin/python3.12" \
sh scripts/packaging/bootstrap_macos.sh --output-dir build/private-release/candidate-pdfUse RELEASE_PROFILE=office-ocr and another output directory for the
office-ocr profile, or leave RELEASE_PROFILE out for the text profile. The full build and
acceptance procedure is in docs-release/macos.md.
Python 3.11 to 3.14 is supported; CI tests 3.11 and 3.12.
python -m venv .venv
source .venv/bin/activate # Windows PowerShell: .\.venv\Scripts\Activate.ps1
python -m pip install -U pipIf python -m venv fails, the Python install lacks the venv module. On Debian
or Ubuntu, install it with sudo apt install python3-venv and run the line
again. Or create the environment with uv, which
does not need that module, and keep uv pip in place of pip below:
uv venv .venv && source .venv/bin/activate
uv pip install -e .Avoid pip install --user: newer Linux distributions refuse it outside a
virtual environment (PEP 668), and it mixes this project's packages into your
user site.
Choose the installation tier that matches your needs:
-
Minimal install (UTF-8 Markdown / plain text only + CLI + PDF text-layer detection):
pip install -e .Without Docling installed,
.mdand.txtfiles convert directly; PDF/Office documents shownot detectedunderlist-engines, andconvertreports engine unavailable rather than an installation error. -
Common install (adds Docling for PDF, Office, and layout conversion):
pip install -e ".[docling]" -
Browser workbench:
pip install -e ".[web]" # or with Docling: pip install -e ".[docling,web]"
-
Full install (Docling + web workbench):
pip install -e ".[full]" -
Office without PyTorch (
.docx,.pptx,.xlsxthrough Docling's readers, about 0.4 GB, no model):pip install -e ".[office]" -
OCR on a Mac with nothing to download (scanned PDFs and photos, read by the text recognition built into macOS):
pip install -e ".[apple-vision]"
On Windows, a source install is checked in CI: a job installs it with the web
extra on windows-latest (Python 3.12) and the test suite passes. Converting
Markdown and plain text, and the browser workbench, work there. A second job
adds the docling extra and passes with no test skipped, so text-layer PDFs
convert on Windows too. CI downloads no Docling models, so the model pipeline
(layout, tables, OCR) has not been checked on Windows.
On Windows, docling-native does not render page images: Docling's page
renderer corrupts memory there when its worker threads exit
(docling-parse #357),
which crashed conversions at random in CI. The text, Markdown and HTML are the
same; the Docling JSON simply carries no picture of each page. Docling's model
pipeline needs those pictures, so it still renders them and remains exposed
until docling-parse is fixed.
document-converter web # http://127.0.0.1:8765 ; --port 8766 for another portThe server binds to loopback only. The same limits as the app apply: up to five
files, 24 MiB (about 25 MB) each and in total, 25 MiB (about 26 MB) per request. It uses the CLI's auto
engine selection, so it uses whichever engines are installed — Marker and
MinerU included — but it never installs one.
document-converter convert paper.pdf --to md
document-converter convert paper.pdf --to md --to html --to json --to txt
document-converter convert ./documents --to md --output ./converted
document-converter list-enginesChoose an engine explicitly:
document-converter convert paper.pdf --engine docling --to md
document-converter convert paper.pdf --engine docling-native --to md # no PyTorch, no model download
document-converter convert report.docx --engine docling-office --to md # Word/PowerPoint/Excel, no model
document-converter convert scan.pdf --engine apple-vision --to md # macOS built-in OCR
document-converter convert paper.pdf --engine marker --to md
document-converter convert paper.pdf --engine mineru --to mdRemove Markdown noise:
document-converter convert paper.pdf --to md --clean
document-converter convert paper.pdf --to md --clean --strip-page-numbers--clean removes labelled page markers such as Page 3 of 12, 4 / 20 and
- 5 -. It keeps lines holding only digits, because engines put table cells and
other values on their own line; add --strip-page-numbers only when a
document's page numbers survive as bare numerals and you accept losing
standalone numbers with them.
document-converter convert ./documents --to md --output ./converted --parallel 4- Default is 1 worker; the safe upper bound is 4.
- Use higher values when converting many files in one run (directory or multiple inputs).
- A single large PDF rarely benefits: work is dominated by the conversion engine, and in-process engines share the interpreter GIL.
- Failed files do not abort the rest of the batch.
Docling's default PDF pipeline runs layout and table models: it needs PyTorch, a
multi-gigabyte install, and downloads model weights on first use.
docling-native uses Docling's model-free NativePdfPipeline instead:
pip install "docling-slim[convert-core,format-pdf]" typer rich pypdf
pip install -e . --no-depsThat install is about 0.4 GB. It converts PDF only, rebuilds no structure, runs
no OCR, and records that limitation as a warning on every conversion. auto
tries it last, so model-based engines are preferred wherever they are
installed.
Word, PowerPoint and Excel files need no model at all: Docling reads them with
python-docx, python-pptx and openpyxl and keeps their headings, lists and
tables. docling-office restricts Docling to those three formats, so it never
reaches a pipeline that wants model weights. pip install -e ".[office]"
installs it; auto uses it when the full Docling is not installed. Pictures
inside a document stay pictures: their text is not read.
A scan or a photo has no text to extract; it has to be recognized. Two ways
are supported, and auto picks whichever is installed:
| Docling's models | apple-vision |
|
|---|---|---|
| Install | pip install -e ".[docling]" |
pip install -e ".[apple-vision]" |
| Runs on | macOS, Linux, Windows | macOS only |
| Downloads | Layout model from Hugging Face, OCR model (RapidOCR) on first use | Nothing: the recognizer is part of macOS |
| Gives back | Headings, tables and reading order | Paragraphs only |
| Languages | RapidOCR's Chinese and English model | Traditional Chinese, Simplified Chinese, English |
apple-vision uses Apple's Vision framework, the same recognizer that lets
Photos select text in a picture. It renders each PDF page at 300 dpi, reads
it, and rebuilds paragraphs from the gaps between lines. Every conversion
carries a warning that the text was recognized and should be checked. CI
checks both: a page drawn with English and Traditional Chinese is read back by
Docling's models on Linux and by Vision on an Apple Silicon Mac.
pip install ".[marker]" # check the package and model license terms first
pip install ".[mineru]" # MinerU 4.x; read docs/LICENSE_AUDIT.md firstThe mineru extra installs MinerU 4.x, which brings PyTorch and fetches its
model files from Hugging Face or ModelScope (mineru-models-download). This
project runs only mineru-kit parse, on your machine, and never passes
MinerU's remote-service options. MinerU's other command, mineru, collects
usage telemetry for mineru.net: it asks on first run, with yes as the default,
and mineru telemetry disable turns it off.
Every engine produces one normalized conversion result, and the writer turns it
into the requested outputs. Parsing stays separate from rendering, so an input
format or an output can be added without touching the CLI. Engines sit behind
adapters in document_converter/engines/; output serialization lives in
writer.py.
This project writes the interfaces, the engine selection, the scanned-PDF detection, the Markdown cleaner, the renderer and the packaging. The conversion itself is done by these:
| Part | Component | License | Bundled in the macOS app? |
|---|---|---|---|
| PDF reading, no model | Docling docling-slim, docling-core, docling-parse |
MIT | ✅ pdf, office-ocr |
| PDF parsing underneath it | pypdfium2 and Google's PDFium | BSD-3-Clause / Apache-2.0 | ✅ pdf, office-ocr |
| Browser interface | FastAPI, Starlette, Uvicorn | MIT / BSD | ✅ all three |
| Command line | Typer, Rich | MIT | — the apps have no command line |
| Scanned-PDF detection | pypdf, or a built-in stream reader when it is absent | BSD-3-Clause | — the apps read the text layer directly |
| Packaging into an app | PyInstaller | GPL-2.0-or-later with its Bootloader exception | ✅ bootloader only |
| PDF with layout and table models | Docling's model pipeline, needing PyTorch and downloaded weights | MIT code, model terms separate | ❌ |
| Office reading, no model | python-docx, python-pptx, openpyxl through Docling | MIT | ✅ office-ocr |
| OCR in Docling's model pipeline | RapidOCR with PaddleOCR models, run by ONNX Runtime | Apache-2.0 / MIT | ❌ |
| OCR on macOS | Apple's Vision framework, reached through PyObjC | Part of macOS / MIT | ✅ PyObjC in office-ocr; Vision is part of macOS |
| Alternative PDF/OCR engines | Marker, MinerU | Apache-2.0 code; separate model and additional terms | ❌ |
OCR: this project bundles none. No OCR engine or model is included in
any macOS profile. The text and pdf apps report a scan as having no text
layer rather than guessing at it; the office-ocr app asks the recognizer
macOS already has, through PyObjC. From source, OCR comes from Docling's model
pipeline, from apple-vision (which uses the recognizer macOS already has),
or from Marker or MinerU under their own terms — see
docs/LICENSE_AUDIT.md, which records what was
checked and when.
The repository is public so that it can be read and run. It is not open source (section 8): forking to read, study or run it is fine, and republishing or selling the project's own software is not.
# 1. Fork on GitHub, then clone your fork
git clone https://github.com/<your-account>/document_converter.git
cd document_converter
# 2. Python 3.11 to 3.14
python -m venv .venv && source .venv/bin/activate
python -m pip install -U pip
# 3. Markdown and text only, no engine, smallest install
pip install -e ".[web]"
# 4. Add PDF without PyTorch - about 0.4 GB, downloads no model
pip install "docling-slim[convert-core,format-pdf]"
# or add PDF with layout and table models - multi-gigabyte, downloads weights
# pip install docling
# 5. Check what it found, then start the browser interface
document-converter list-engines
document-converter weblist-engines prints which engines this install actually has, so step 5 tells
you what you really got rather than what the documentation hopes for.
To run the tests and the checks this project runs on itself:
pip install -e ".[dev,web]"
python -m pytest
document-converter --help
document-converter list-engines
python3 scripts/packaging/audit_notices.py --profile pdf # needs networkBuilding the macOS app needs an Apple Silicon Mac; see
docs-release/macos.md.
Important
- Before converting any document — PDF, Office file, image or scan — make sure you hold the right to process and, where relevant, distribute it.
- Never commit client confidential material, real business documents, personal data or unlicensed copyrighted documents to this repository.
- The automated tests use only synthetic, public-domain or non-sensitive documents.
- Local-first. Documents are processed by locally installed engines. Network behaviour has not been audited for every engine, package and model downloader combination; check the engine you select before processing sensitive files.
- Uploads. Kept under generated temporary names and removed after each request, including failed ones.
- Logging. The CLI reports status and errors. Third-party engines may log on their own.
- Sensitive files.
.gitignorecovers common outputs, credentials, temporary assets and model caches. It does not remove files already tracked; checkgit statusbefore every commit.
This is a personal project, published so that it can be read and used. It comes as it is, with no commitment to support, to answer questions, to fix a problem or to keep it working. Issues and pull requests may go unanswered. Before you process anything that matters, check the output: the limits each engine has are described above and recorded as warnings on every conversion.
This project. Copyright (c) 2026 Jake Chiu, under a
proprietary use license: personal use and a company's internal use,
including internal commercial use, are permitted; resale and redistribution of
the project's own software are not. It is not an open-source license, and this
repository being public does not make it one: reading the source here grants no
right the LICENSE does not give. The LICENSE file is authoritative; this
paragraph only summarizes it.
Third-party components keep their own licenses, and nothing here restricts
the rights their authors grant. See THIRD_PARTY_NOTICES.md
and docs/LICENSE_AUDIT.md. Each installer carries
every component's original license and notice texts in its legal/ folder.
The pdf profile of the macOS app includes software based in part on the work
of the FreeType Team and of the Independent JPEG Group.