Perfect DOCX-to-PDF/A on macOS
So there I was, automating my life like a proper nerd, when I hit a wall that made me want to throw my MacBook into the sea.
The setup: I’m a lawyer (yes, we can be geeks too), and I deal with a lot of legal documents. Spanish court filings, specifically. My documents have very particular formatting—numbered paragraphs with hanging indents, nested lists, the whole bureaucratic aesthetic. I built a beautiful Keyboard Maestro macro that converted my Word docs to PDF/A-1b using LibreOffice headless mode. One hotkey, boom, done.
Except… it wasn’t done. It was broken.
The Problem
I noticed the PDFs looked off. The hanging indents—those lovely paragraph numbers sitting in the margin with text neatly aligned beside them—were completely mangled. LibreOffice was adding extra indentation to continuation lines, making my professional legal documents look like amateurish.
I compared the LibreOffice output with a “proper” PDF (exported directly from Word) and there it was: LibreOffice simply doesn’t interpret Word’s hanging indent styles correctly. It reads the left indent but loses the negative first-line indent that creates the hanging effect.
This is a known issue, by the way. The internet is full of sad developers asking why their DOCX-to-PDF conversions look wrong. Welcome to the club, friends.
The Solution
Plot twist: use Microsoft Word itself.
Enter docx2pdf, a delightful Python package that uses Word’s actual API (via AppleScript/JXA on macOS) to do the conversion. No interpretation, no guessing—just Word doing what Word does best.
pip3 install docx2pdf
For PDF/A-1b compliance (required for legal archiving), I added a second step: Ghostscript converts the pristine Word-generated PDF into the archival format.
brew install ghostscript
Two-step process, perfect output, sanity preserved.
The Scripts
I ended up with two Keyboard Maestro macros:
- DOCX → PDF: Simple, fast, perfect formatting
FILE_PATH="$KMVAR_CurrentFile"
DIR_PATH=$(dirname "$FILE_PATH")
FILE_NAME_EXT=$(basename "$FILE_PATH")
FILE_NAME_BASE="${FILE_NAME_EXT%.*}"
RESULT_PATH="${DIR_PATH}/${FILE_NAME_BASE}.pdf"
/Library/Frameworks/Python.framework/Versions/3.9/bin/docx2pdf "$FILE_PATH" "$RESULT_PATH"
osascript -e "tell application \"Finder\" to reveal POSIX file \"$RESULT_PATH\""
osascript -e "tell application \"Finder\" to activate"
open -b com.adobe.Acrobat.Pro "$RESULT_PATH"
- DOCX → PDF/A-1b: Same fidelity, plus archival compliance
FILE_PATH="$KMVAR_CurrentFile"
DIR_PATH=$(dirname "$FILE_PATH")
FILE_NAME_EXT=$(basename "$FILE_PATH")
FILE_NAME_BASE="${FILE_NAME_EXT%.*}"
TEMP_PDF="${DIR_PATH}/${FILE_NAME_BASE}_temp.pdf"
RESULT_PATH="${DIR_PATH}/${FILE_NAME_BASE}.pdf"
/Library/Frameworks/Python.framework/Versions/3.9/bin/docx2pdf "$FILE_PATH" "$TEMP_PDF"
/opt/homebrew/bin/gs \
-dPDFA=1 \
-dBATCH \
-dNOPAUSE \
-dNOOUTERSAVE \
-sColorConversionStrategy=UseDeviceIndependentColor \
-sDEVICE=pdfwrite \
-dPDFACompatibilityPolicy=1 \
-sOutputFile="$RESULT_PATH" \
"$TEMP_PDF"
rm "$TEMP_PDF"
osascript -e "tell application \"Finder\" to reveal POSIX file \"$RESULT_PATH\""
osascript -e "tell application \"Finder\" to activate"
open -b com.adobe.Acrobat.Pro "$RESULT_PATH"
Both use docx2pdf for the heavy lifting, ensuring my hanging indents hang exactly where they should.
The Moral
Sometimes the “lightweight” solution (LibreOffice headless) isn’t actually solving your problem. Sometimes you need the “heavy” tool (actual Microsoft Word) to get things right. And sometimes a two-hour debugging session with Claude saves you thousands of malformed legal documents.
Now if you’ll excuse me, I have court filings to convert. Correctly.
P.S.: Time to tell the truth. I started this project with Gemini 3.0. It hit the LibreOffice wall and suggested switching to AppleScript—no way, that never works reliably. So I turned to Claude (Opus 4.5), and there it was: the solution arrived quickly. Claude even installed docx2pdf and Ghostscript for me (with proper permission at every step, of course). So thanks, Claude.