v0.2.0  ·  Markdown to DOCX & PDF

Markdown in.
Polished DOCX & PDF out.

md2office turns a plain Markdown report into a Word document and PDF that look like they came from a publisher — title & classification in the headers, wide autofit tables, shaded monospace code, and a static table of contents.

Python 3.9+ pandoc LibreOffice GPL-3.0
Markdown → DOCX & PDF

Markdown in. A finished report out.

The migration risk register from the bundled sample — the Aurora Order Service Migration Report (examples/sample.md). A pipe table in Markdown on the left; on the right, the same table typeset by md2office — autofit width, explicit borders, ready for print.

migration-report.md
# 7. Risks and Mitigations

The risk register below was maintained throughout the migration and reviewed
at each phase gate. The "status" column reflects the position at the time of
this report.


| #  | Risk                                                       | Likelihood | Impact | Mitigation                                                             | Status   |
| -- | ---------------------------------------------------------- | ---------- | ------ | ---------------------------------------------------------------------- | -------- |
| R1 | Order duplication introduces duplicate records              | Medium     | High   | Reconciliation job with full daily comparison; duplicate key in store  | Closed   |
| R2 | Cutover exceeds the zero-downtime window                    | Medium     | High   | Parallel-run parity gate; rehearsed cutover checklist                  | Closed   |
| R3 | Pricing rules diverge between platforms                      | Medium     | High   | Shared rule package; per-order diff in reconciliation report           | Closed   |
| R4 | Secrets exposed in Git or logs                             | Low        | High   | Gatekeeper policy; scan in pipeline; vault-only secret resolution      | Closed   |
| R5 | Legacy hosts still receive traffic after decommission        | Medium     | Medium | Ingress ACL on legacy fleet; traffic audit for two weeks               | Closed   |
| R6 | Image registry downtime blocks releases                    | Low        | Medium | Registry replicated across regions; cached base images                 | Accepted |
| R7 | Team skill gap on Kubernetes operations                    | Medium     | Low    | Pairing sessions; runbooks in the repository; staged permissions       | Active   |
Table 5 — Migration risk register.
→
rendered · docx & pdf
Aurora Order Service Migration ReportSection 7

Risks and Mitigations

The risk register below was maintained throughout the migration and reviewed at each phase gate. The "status" column reflects the position at the time of this report.

#RiskLikelihoodImpactMitigationStatus
R1Order duplication introduces duplicate recordsMediumHighReconciliation job with full daily comparison; duplicate key in storeClosed
R2Cutover exceeds the zero-downtime windowMediumHighParallel-run parity gate; rehearsed cutover checklistClosed
R3Pricing rules diverge between platformsMediumHighShared rule package; per-order diff in reconciliation reportClosed
R4Secrets exposed in Git or logsLowHighGatekeeper policy; scan in pipeline; vault-only secret resolutionClosed
R5Legacy hosts still receive traffic after decommissionMediumMediumIngress ACL on legacy fleet; traffic audit for two weeksClosed
R6Image registry downtime blocks releasesLowMediumRegistry replicated across regions; cached base imagesAccepted
R7Team skill gap on Kubernetes operationsMediumLowPairing sessions; runbooks in the repository; staged permissionsActive

Table 5 — Migration risk register.

page 12

Why md2office

Everything a document build needs, in one import — no scripts to copy between repos.

📄

Bring your own template

Pass a Word reference template (.docx/.dotx) via --template for a custom cover and header classification. No template? Pandoc's built-in default gives a clean standard look.

🔖

Header injection

Title and bilingual classification go into every page header and the core document metadata automatically.

📊

Table fixes

Pandoc tables are rewritten to 100% autofit width with explicit borders, so LibreOffice stops crushing them.

⌨️

Code styling

SourceCode and VerbatimChar get Consolas 9pt and light-grey shading.

🔁

DOCX → PDF

Headless LibreOffice with a fresh per-run profile, so concurrent conversions don't lock each other out.

🩺

Prerequisite doctor

md2office doctor checks pandoc, LibreOffice, python-docx, and the fonts the PDF needs.

How it works

One pipeline: pandoc builds the DOCX against your reference template, the package post-processes it, LibreOffice renders the PDF.

01

Markdown

Frontmatter title, tables, fenced code, mermaid blocks.

→
02

pandoc

Renders against your reference template with the Lua filters.

→
03

postprocess

Headers, classification, table width, code style, TOC.

→
04

soffice

Headless DOCX → PDF export, ready to share or print.

Quick start

Install, run doctor, convert. That's it.

terminal
# 1. install
pip install md2office

# 2. check prerequisites
md2office doctor

# 3. convert (supply your own reference template)
md2office docx --template template.dotx report.md report.docx --classification "Protected A"
md2office pdf  --template template.dotx report.md report.pdf

No template handy? Omit --template and pandoc's built-in reference document is used instead — still clean, just without a custom cover or header classification. The title still defaults to your YAML frontmatter.

Project structure

One Markdown file becomes one document. Keep everything a document needs in a single folder, and point md2office at the master file.

project layout
docs/
  report.md        # master document
  chapter-2.md     # optional: long sections as separate files
  images/
    diagram.png
template.dotx     # your Word reference template (user-supplied)
output/            # generated files (gitignore this)
  report.docx
  report.pdf
report.md
---
title: Developer Guide
author: Platform Team
version: 1.2.0
---

# Overview

Reference images relative to this file:
![Architecture](images/diagram.png)
📁

One folder per document

Images and attachments resolve relative to the Markdown file's folder, which is automatically on pandoc's resource path. Add more with -r <dir>.

🏷️

Frontmatter drives the header

The title:, classification:, author, and version in YAML frontmatter are written into the page headers and cover metadata.

⚡

Code, styled for free

Fenced code blocks get monospace 9pt and shading automatically. Mermaid blocks (```mermaid) render when mmdc is installed.

📑

TOC & page breaks

A static table of contents is inserted at the top. Drop a \newpage, or pass --page-breaks sections to start each Heading1 on a new page.

convert
cd docs
md2office pdf --template ../template.dotx report.md ../output/report.pdf

Multi-chapter books: md2office doesn't assemble multiple files. Keep one master file per document, or combine chapters first (e.g. cat chapters/*.md > book.md) and convert once.

Options

Everything is exposed on the CLI and through ConvertOptions.

OptionDefaultMeaning
titlefrontmatterDocument title; written into headers + core metadata
templateNoneWord reference template; falls back to pandoc's built-in default
classificationUnclassified | Non classifiéSensitivity label in every page header
number_sectionsFalsePass --number-sections to pandoc
code_styleTrueConsolas 9pt + shading on code styles
page_breaks"none""sections" puts each Heading1 on a new page
font"default""arial" switches the document font
mermaid"auto"Render ``` ```mermaid``` blocks (needs mmdc)
keep_docxFalseKeep the intermediate DOCX when producing a PDF

Library

Use it directly from Python — no shell needed.

python
import md2office

md2office.to_docx("report.md", "report.docx",
                  template="template.dotx",
                  title="My Report", classification="Protected A")
md2office.to_pdf("report.md", "report.pdf", template="template.dotx")

# fine-grained control
from md2office import ConvertOptions, to_pdf

opts = ConvertOptions(template="template.dotx",
                           number_sections=True, font="arial",
                           page_breaks="sections")
to_pdf("report.md", "report.pdf", options=opts)