Skip to main content

Offline batch .doc/.docx → PDF converter using Microsoft Word on macOS

Project description

DocToPDF — Offline macOS Batch Document Converter

Python 3.9+ Platform: macOS Automation: AppleScript License: MIT Version: 1.2.0 CI

Convert .doc and .docx files to PDF using Microsoft Word as the rendering engine.
Runs entirely offline with zero cloud dependencies.

How It Works

Input (.doc/.docx)  ──→  Microsoft Word (JXA automation)  ──→  Output (.pdf)

The tool uses AppleScript/JXA (JavaScript for Automation) to tell Microsoft Word to open each document and export it as PDF. This guarantees maximum formatting fidelity — the PDF looks exactly as it would if you manually opened the file in Word and chose File → Export as PDF.

Requirements (what you need before starting)

Before you can use this tool, make sure you have everything listed below:

Requirement Why you need it How to check you have it
macOS 11 (Big Sur) or newer Only runs on Mac Click the Apple menu  → About This Mac → look at macOS version
Python 3.9 or later The tool is written in Python Open Terminal and type python3 --version. You should see Python 3.9.x or higher. If you don't have Python, see the note below.
Microsoft Word for Mac This is the engine that actually converts the files to PDF. You need Word 2019, Microsoft 365, or any newer version. Open Word and go to Word menu → About Microsoft Word
Automation permission macOS needs your permission before this tool can talk to Word. You'll be prompted on first use. You don't need to check this in advance — see the Permissions section below.

No Python? macOS usually comes with Python 3 pre-installed. If python3 --version gives an error, install Python from python.org. Download the macOS installer, run it, and follow the steps.

Installation (step by step)

There are three ways to use this tool. Choose whichever is easiest for you:

Option Method Best for
A Homebrewbrew install harryw00/doctopdf/doctopdf Everyone with Homebrew installed
B pip — pip3 install . (from source) Users who prefer pip / don't have Homebrew
C Run without install — python3 -m doctopdf Quick test, no installation wanted

Option A: Install via Homebrew (easiest)

If you have Homebrew installed, this is the one-command way:

brew install harryw00/doctopdf/doctopdf

Homebrew automatically sets up all paths — no cd, no pip, no PATH fixes needed. It also keeps doctopdf up-to-date when you run brew update && brew upgrade.

Verify it worked

convert-word-pdf --check

Option B: Install via pip

📦 PyPI package pending — Once published, you'll be able to run pip3 install doctopdf. For now, install from the source checkout.

Step 1: Open Terminal

  • Press Cmd + Space → type "Terminal" → Enter

Step 2: Get the source

git clone https://github.com/HarryW00/doctopdf.git
cd doctopdf

(Or download the ZIP from GitHub and extract it.)

Step 3: Install

pip3 install .

⚠️ You may see a warning like this:

WARNING: The script convert-word-pdf is installed in
'/Users/yourname/Library/Python/3.9/bin' which is not on PATH.

This is normal on macOS — Python's install folder isn't on your PATH by default. See the Troubleshooting section below to fix this in one minute.

Step 4: Verify it worked

convert-word-pdf --check

You should see:

✓ Microsoft Word is installed (version: 16.72)
  The automation bridge is operational.

Option C: Run directly (no installation)

If you'd rather not install anything, run the tool straight from the project folder:

cd path/to/doctopdf
python3 -m doctopdf --check

Use python3 -m doctopdf wherever the docs say convert-word-pdf:

Instead of this Use this
convert-word-pdf --check python3 -m doctopdf --check
convert-word-pdf -i ./docs -o ./pdfs python3 -m doctopdf -i ./docs -o ./pdfs
convert-word-pdf --version python3 -m doctopdf --version

After installing (all options)

Run the check to make sure Word is detected:

# Installed (Option A or B)
convert-word-pdf --check

# Direct run (Option C)
python3 -m doctopdf --check

Expected output:

✓ Microsoft Word is installed (version: 16.72)
  The automation bridge is operational.

If you see "Word is NOT installed":

  1. Make sure Microsoft Word is actually installed (open it from Applications)
  2. If Word opens but the check still fails, close Word completely (Word menu → Quit Microsoft Word) and try again
  3. If you just installed Word, restart your Mac and try again

If you see a permission error (-1743): This means macOS hasn't granted Automation permission yet. Run the command a second time — macOS should show a permission dialog. Click Allow. See the macOS Permissions section below if this doesn't happen.

⚠️ Data Safety — Important

DocToPDF is a batch automation tool. If you accidentally point it at the wrong folder or a file gets corrupted during conversion, documents could be lost or damaged.

Before running your first conversion:

  1. Back up your documents — copy your .doc / .docx files to a separate backup folder, or ensure you have Time Machine or cloud backup running
  2. Use a dedicated input folder — copy the files you want to convert into a new folder rather than pointing the tool at your master document directory
  3. Try a dry run first — add --dry-run to see which files will be processed before any conversion happens:
    convert-word-pdf -i ./docs -o ./pdfs --dry-run
    

Quick Start

# Convert all .doc/.docx files in ~/Documents/reports recursively
convert-word-pdf --input ~/Documents/reports --output ~/Documents/pdfs

# Same, but with abbreviated flags
convert-word-pdf -i ./docs -o ./pdfs -r

# Flat output (all PDFs in one folder)
convert-word-pdf -i ./docs -o ./pdfs --flat

# Top-level only (no subdirectory recursion)
convert-word-pdf -i ./docs -o ./pdfs --no-recursive

# Dry run — see what would be converted without doing it
convert-word-pdf -i ./docs -o ./pdfs --dry-run

# Custom timeout and retry for large/complex documents
convert-word-pdf -i ./docs -o ./pdfs --timeout 120 --retry 3

# Export results to a structured log
convert-word-pdf -i ./docs -o ./pdfs --log-file ./conversion-log.json

Output Structure

Mirrored Tree (Default)

The input directory structure is preserved under the output folder:

Input:                              Output:
./docs/                             ./pdfs/
├── report.docx            ──→      ├── report.pdf
├── subdir/                         ├── subdir/
│   ├── notes.docx         ──→      │   ├── notes.pdf
│   └── deep/                       │   └── deep/
│       └── final.docx     ──→      │       └── final.pdf

Flat Mode (--flat)

All PDFs are written directly into the output folder. If filenames collide, a _1, _2, etc. suffix is appended.

./pdfs/
├── report.pdf
├── notes.pdf
├── notes_1.pdf

CLI Reference

Argument Short Default Description
--input -i Input directory (.doc/.docx files)
--output -o Output directory for PDFs
--recursive -r True Scan subdirectories
--no-recursive Top-level scan only
--flat False Flat output (no mirrored tree)
--timeout 60 Seconds per document before timeout
--retry 2 Retries per failed conversion
--restart-every 0 Restart Word every N conversions (0 = never). Use for 100+ file batches
--dry-run -n False Scan only, no conversion
--log-file Export log to .json or .csv
--quiet -q False Suppress per-file status
--check Probe for Word installation
--version -v Show version

Error Handling

Problem What Happens User Action
Word not installed Clear error on startup Install Microsoft Word
Permission denied (-1743) Error with fix instructions System Settings → Automation
File locked / open in Word Retries 2×, then skips Close the file in Word
Corrupt document Skips with error message Repair the source file
Export failure Retries 2×, then skips Try converting manually in Word
Timeout Skips after --timeout seconds Increase --timeout for large files
File not writable Error with path details Check output directory permissions
Word crashes Detects crash, restarts Word, retries Rare; check for Word updates

Retry Policy

The converter retries only recoverable errors (timeouts, transient export failures, Word crashes). It does not retry:

  • Corrupt/unreadable documents (will always fail)
  • Permission errors (user must fix)
  • File access errors (disk or path issues)

macOS Permissions

Automation Permission

The first time you run the tool (or the Automator Quick Action), macOS will show a dialog:

"Terminal" wants access to control "Microsoft Word". [Deny] [Allow]

Click Allow. If you accidentally deny, fix it:

  1. Open System Settings (or System Preferences)
  2. Go to Privacy & Security → Automation
  3. Find Terminal (or your app — e.g., iTerm2, Automator)
  4. Toggle ON the checkbox next to "Microsoft Word"

File Access

The tool needs read access to the input directory and write access to the output directory. Standard macOS file permissions apply. For directories under ~/Documents, your terminal app typically already has access.

When Microsoft Word opens a document from a folder it hasn't accessed before, macOS may show a "Grant File Access" dialog:

Microsoft Word wants to access files in your "Downloads" folder.
[Deny] [Allow]

Click Allow so Word can read the source document and write the PDF. If you click Deny by accident, the conversion will fail with a file-access error — just re-run the tool and macOS will prompt again.

No Network/Accessibility Permissions

This tool requires:

  • ✅ Automation (Terminal → Microsoft Word)
  • ❌ No network/Internet access
  • ❌ No Accessibility/Screen Recording permissions
  • ❌ No Full Disk Access

Automator Quick Action (Finder Right-Click)

Create a macOS Finder right-click service that converts documents to PDF without opening Terminal. See the full setup guide for step-by-step instructions.

Comparison with Other Tools

Dimension DocToPDF (Word) LibreOffice Pandoc python-docx
Rendering fidelity ★★★★★ (Word's engine) ★★★★ (good, minor diffs) ★★ (markdown-centric) ★ (no layout engine)
Speed ★★★ (Word launch overhead) ★★★★ (fast headless) ★★★★★ ★★★★★
License cost Requires Microsoft Word Free Free Free
Offline ✅ Fully ✅ Fully ✅ Fully ✅ Fully
VBA macros ✅ Executed ⚠️ Partial
Complex formatting ✅ Full Word fidelity ⚠️ Minor regressions
PDF bookmarks/TOC ✅ Word-native ⚠️ Varies
Batch-friendly ✅ Sequential ✅ Parallel-capable
macOS native ✅ AppleScript/JXA ⚠️ X11 or headless

When to Use This Tool

  • Fidelity matters: Legal documents, official submissions, contracts, academic papers
  • Complex formatting: Multi-column layouts, embedded fonts, tracked changes, SmartArt
  • Security constraints: The environment blocks cloud uploads and web APIs
  • Workflow integration: You want a Finder right-click option

When to Use LibreOffice Instead

  • You don't have a Microsoft Word license
  • You need to run headless on a server (Linux/CI)
  • You need parallel batch conversion for hundreds of files
  • Minor rendering differences are acceptable

Performance Notes

Expected Conversion Times

File Type Size Typical Time
Simple text (.docx) ~50 KB 3–8 s
With images/tables ~5 MB 8–20 s
Large legacy (.doc) ~20 MB 15–45 s
Very large + complex ~100 MB 60–120 s

Cold start (first conversion): includes Word launch time (+3–8s).
Subsequent conversions are faster (2–5s typical) because Word stays warm.

Keep Word Warm Strategy

The converter never restarts Word between conversions by default. This is deliberate — keeping Word running for the entire batch eliminates the 5–15s relaunch penalty and avoids the reliability issues that come with killing and restarting the application.

For very large batches (100+ files), use --restart-every N to periodically restart Word and prevent any accumulated state degradation. A good rule of thumb is --restart-every 50.

No Parallelism

Microsoft Word on macOS is a single-instance application. Running parallel conversions would create race conditions on Word's internal state. This tool processes files one at a time, which is the reliable default.

Batch Size

Tested with batches of 100+ files. For very large batches (1000+), consider splitting into smaller groups and running sequentially.

Project Structure

doctopdf/
├── pyproject.toml            # Package build configuration
├── DESIGN.md                 # Architecture design document
├── README.md                 # This file
└── doctopdf/                 # Python package
    ├── __init__.py           # Package metadata
    ├── __main__.py           # `python -m doctopdf` entry
    ├── cli.py                # CLI argument parsing and dispatch
    ├── config.py             # Constants and defaults
    ├── converter.py          # Word automation bridge (JXA)
    ├── errors.py             # Exception hierarchy
    ├── logger.py             # Structured logging
    ├── orchestrator.py       # Batch coordination
    ├── scanner.py            # File discovery and path mapping
    └── applescript/          # JXA/AppleScript bridge files
        ├── check_word.applescript
        └── export_document.js

Development

# Install in development mode
pip install -e .

# Run directly
python -m doctopdf --check
python -m doctopdf -i ./test_docs -o ./test_pdfs --dry-run

# Test with real files
python -m doctopdf -i ./test_docs -o ./test_pdfs

Logging

Per-file output (stdout)

  ✓ report.docx → report.pdf [4.2s]
  ✓ notes.docx → notes.pdf [3.1s]
  ✗ broken.docx → broken.pdf [5.0s] — Corrupt document: File format error

Summary (end of run)

── Summary ──────────────────────────────────
  Total processed: 15
  Successful:      14  (62.3s spent on successful conversions)
  Errors:           1
  Average:         4.4s per file
  Total wall time: 67.8s

  Failed files (1):
    ✗ broken.docx: Corrupt document — File format error

Structured log (--log-file)

Use --log-file results.json or --log-file results.csv for post-hoc analysis.

Troubleshooting

"convert-word-pdf: command not found" after installation

This is the most common issue on macOS. Here is what happened and how to fix it.

Why this happens: When you run pip3 install ., Python installs the convert-word-pdf command into a folder like:

/Users/yourname/Library/Python/3.9/bin

That folder is a standard install location used by Python's pip on macOS. However, macOS does not include this folder in your terminal's search path (the technical term is PATH) by default. So even though the tool is installed, your terminal doesn't know where to find it.

How to fix it (two ways):

Fix A: Add the folder to your PATH (do this once)

  1. Find out which Python version you have:

    python3 --version
    

    This will show something like Python 3.9.x. Note the major.minor version number (e.g. 3.9).

  2. Open your shell configuration file:

    • If you use zsh (default on macOS Catalina and newer):

      nano ~/.zshrc
      
    • If you use bash (older macOS versions):

      nano ~/.bash_profile
      
  3. Add this line at the bottom of the file (replace 3.9 with the Python version you saw in step 1):

    export PATH="$HOME/Library/Python/3.9/bin:$PATH"
    

    What this does: It tells your terminal to look in the Python bin folder whenever you type a command, so it can find convert-word-pdf.

  4. Save the file:

    • Press Ctrl + OEnter to save
    • Press Ctrl + X to exit nano
  5. Reload your configuration:

    source ~/.zshrc
    

    (If you used ~/.bash_profile instead, run source ~/.bash_profile.)

  6. Verify it worked:

    convert-word-pdf --version
    

    You should see convert-word-pdf 1.0.0 instead of "command not found."

Fix B: Skip PATH entirely — use Option C instead

If you don't want to edit configuration files, don't install the tool at all. Run it directly from the project folder instead:

cd ~/Documents/doctopdf
python3 -m doctopdf --check

See Option C in the Installation section above for full instructions. The tool works identically either way — you just type a slightly longer command.

Check which Python is active

If you have multiple Python versions installed, pip3 install and python3 -m doctopdf might use different Python installations, causing confusion. Verify they match:

pip3 --version
python3 --version

Both should reference the same Python version (e.g. Python 3.9.x). If they don't, use the full path to your desired Python, for example:

/usr/local/bin/python3 -m doctopdf --check

Permission errors with osascript (-1743)

See the macOS Permissions section earlier in this document.

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

doctopdf-2.0.5b0.tar.gz (37.3 kB view details)

Uploaded Source

Built Distribution

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

doctopdf-2.0.5b0-py3-none-any.whl (31.1 kB view details)

Uploaded Python 3

File details

Details for the file doctopdf-2.0.5b0.tar.gz.

File metadata

  • Download URL: doctopdf-2.0.5b0.tar.gz
  • Upload date:
  • Size: 37.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for doctopdf-2.0.5b0.tar.gz
Algorithm Hash digest
SHA256 d90a5278230267c3fa2bfc4c691742838de1325030973d991a97499690797864
MD5 57c9909c58dc7d73c7b56be7f4e1d8c4
BLAKE2b-256 ad608b749ff80844cd3de95d1b005e1a41c0d63f0745724f338a78e2f9c9e286

See more details on using hashes here.

Provenance

The following attestation bundles were made for doctopdf-2.0.5b0.tar.gz:

Publisher: publish.yml on HarryW00/doctopdf

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

File details

Details for the file doctopdf-2.0.5b0-py3-none-any.whl.

File metadata

  • Download URL: doctopdf-2.0.5b0-py3-none-any.whl
  • Upload date:
  • Size: 31.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for doctopdf-2.0.5b0-py3-none-any.whl
Algorithm Hash digest
SHA256 1e75dc1a8ad7b9c4559f5178ba4ef32ae8b35cb34ccd32f3356d05b17e233c1d
MD5 0be28479859e89855e5e02c2bcb0ea02
BLAKE2b-256 e3f50abc34724983aa8c022dd63b3cdb1c92867b557ef7d3918eb8f671045639

See more details on using hashes here.

Provenance

The following attestation bundles were made for doctopdf-2.0.5b0-py3-none-any.whl:

Publisher: publish.yml on HarryW00/doctopdf

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 Pingdom Monitoring Sentry Error logging StatusPage Status page