"""Export CNX-Vinyl-Business-Plan-A.docx and -B.docx to PDF with LibreOffice, filling in the table of contents. Run with LibreOffice's own Python (it ships the uno module): "C:/Program Files/LibreOffice/program/python.exe" export_pdf.py A plain `soffice --convert-to pdf` leaves the Word TOC field unfilled. """ import uno, sys, time, subprocess, os from pathlib import Path HERE = Path(__file__).resolve().parent DOCXS = [HERE.parent / f"CNX-Vinyl-Business-Plan-{p}.docx" for p in ("A", "B")] DOCXS = [d for d in DOCXS if d.exists()] from com.sun.star.beans import PropertyValue def pv(n, v): p = PropertyValue(); p.Name = n; p.Value = v; return p soffice = r"C:\Program Files\LibreOffice\program\soffice.exe" proc = subprocess.Popen([soffice, "--headless", "--invisible", "--norestore", "--accept=socket,host=localhost,port=2083;urp;"]) local = uno.getComponentContext() resolver = local.ServiceManager.createInstanceWithContext("com.sun.star.bridge.UnoUrlResolver", local) for i in range(60): try: ctx = resolver.resolve("uno:socket,host=localhost,port=2083;urp;StarOffice.ComponentContext"); break except Exception: time.sleep(0.5) smgr = ctx.ServiceManager desktop = smgr.createInstanceWithContext("com.sun.star.frame.Desktop", ctx) for DOCX in DOCXS: PDF = DOCX.with_suffix(".pdf") src = uno.systemPathToFileUrl(str(DOCX)) doc = desktop.loadComponentFromURL(src, "_blank", 0, (pv("Hidden", True),)) idx = doc.getDocumentIndexes() print(DOCX.name, "indexes", idx.getCount()) for i in range(idx.getCount()): idx.getByIndex(i).update() doc.refresh() # update fields (page numbers) doc.getTextFields().refresh() for i in range(idx.getCount()): idx.getByIndex(i).update() out = uno.systemPathToFileUrl(str(PDF)) doc.storeToURL(out, (pv("FilterName", "writer_pdf_Export"),)) doc.close(True) try: desktop.terminate() except Exception: pass time.sleep(1) proc.kill() if proc.poll() is None else None print("done")