"""Export CNX-Vinyl-Business-Plan.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 DOCX = HERE.parent / "CNX-Vinyl-Business-Plan.docx" PDF = HERE.parent / "CNX-Vinyl-Business-Plan.pdf" 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) src = uno.systemPathToFileUrl(str(DOCX)) doc = desktop.loadComponentFromURL(src, "_blank", 0, (pv("Hidden", True),)) idx = doc.getDocumentIndexes() print("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")