Convert Markdown file to PDF using themes extracted directly from Word templates
Project description
mark-my-word
You have a Word template — the right fonts, colours, headers, footers. You have a Markdown file you want to print. mark-my-word connects the two: it reads your Word template and uses it to render your Markdown as a styled PDF.
Is this for me?
It's a good fit if:
- You have a
.docxor.dotxWord template with the styling you want - You want your PDFs to match that template without manual CSS work
- You're happy running a command-line tool
It's probably not for you if you need pixel-perfect fidelity to complex Word layouts (tables with merged cells, tracked changes, etc.) or if you need to render Word documents directly — this tool works with Markdown input.
Install
uv tool install mark-my-word
# or
pipx install mark-my-word
Requires Python 3.11+. WeasyPrint (the PDF engine) needs some system libraries — see the WeasyPrint docs if the install fails.
Basic usage
Pass your Word template directly and mark-my-word does the rest:
mark-my-word render document.md --template my-template.dotx
Output lands next to the input file by default (document.pdf). Use --output to put it somewhere else:
mark-my-word render document.md --template my-template.dotx --output /tmp/document.pdf
If the output file already exists, the command refuses rather than silently overwriting. Use --force (or -f) to overwrite:
mark-my-word render document.md --template my-template.dotx --force
Or extract the theme separately
If you render from the same template often, extract it once and reuse the theme directory:
mark-my-word extract my-template.dotx --output themes/my-theme/
mark-my-word render document.md --theme themes/my-theme/
The extracted theme is plain CSS and HTML — easy to inspect or tweak. Add --save-theme themes/my-theme/ to the one-step command if you want to keep the theme without a separate extract step.
Document frontmatter
Headers and footers in your theme can include placeholders like {{title}} or {{author}}. You can also supply a fallback: {{title: Untitled}} is used when title is not set. Matching is case-insensitive.
Set values in your Markdown file's YAML frontmatter:
---
title: Q1 Report
author: Daniel
---
# Introduction
...
Pass extra values on the command line with --set:
mark-my-word render document.md --theme themes/my-theme/ --set version=draft
Skipping preamble
If you write notes in an app like NotePlan, there's often metadata at the top — tags, links, app-specific syntax — that you don't want in the PDF. Put <!--more--> on its own line to mark where your real content starts:
@project(Quarterly Review)
[[linked-note]]
<!--more-->
# Q1 Report
...
Everything before <!--more--> is ignored. Everything after renders normally. If <!--more--> appears inside a code block in the body, put a second one at the top of the document to act as the separator instead.
What gets extracted
From your Word template, mark-my-word extract pulls:
- Page margins
- Theme colours → CSS custom properties (
--color-body,--color-heading, etc.) - Body text formatting: font family, size, line height, and paragraph spacing (from the Normal style)
- Heading styles: font size, weight, colour, and spacing for H1–H6
- Header and footer content, including tab-separated left/right layouts, page numbers, and paragraph borders (top and bottom)
- Watermark images
The result is a plain directory you can inspect and edit. The stylesheet uses CSS custom properties throughout, so tweaking colours or fonts is a one-line change.
Theme directory layout
themes/my-theme/
style.css required
header.html header on all pages (or cont-header.html / first-header.html)
footer.html footer on all pages (or cont-footer.html / first-footer.html)
watermark.png optional full-page background image
If your Word template has a different header on the first page, extract writes first-header.html and cont-header.html separately. render handles both layouts automatically.
Tips for Word templates
Left/right footer layouts
For a footer with content on the left and right (e.g. company name on the left, page number on the right), use a single paragraph with a right-aligned tab stop rather than a table:
- In your footer, type the left content
- Press Tab
- Type the right content
- Set a right-aligned tab stop at the right margin (Format → Tabs, or drag the tab marker in the ruler)
mark-my-word converts the tab character into a flex-row layout. Tables work too, but they carry implicit cell spacing that doesn't always translate cleanly.
Horizontal rules
To draw a decorative line above or below footer content, add a paragraph border (Format → Borders and Shading → Borders tab) rather than an actual horizontal rule or a separate table row. Paragraph top and bottom borders are extracted and rendered faithfully.
Options
mark-my-word render --help
mark-my-word extract --help
Changelog
0.2.3
- More style properties are now extracted from the Word template and applied to the PDF: body text font size, line height, and paragraph spacing (from the Normal style) join heading size, weight, colour, and spacing (H1–H6) as template-driven values rather than hardcoded defaults
- Internal cleanup: removed unused helper functions
0.2.2
- README updated with 0.2.1 changes (should have shipped with the release)
0.2.1
- Paragraph borders (top and bottom) in Word headers and footers are now extracted and rendered with correct spacing
- Empty paragraphs used as horizontal rules extract at natural line height, matching Word's visual spacing
- Footer height estimation fixed:
<p>elements inside flex-row cells are no longer counted as separate lines, preventing over-large body margins - Page break orphan/widow protection relaxed from 3 lines to 2
0.2.0
- First-page header/footer fallback: if no distinct first-page fragments exist, the running header/footer is used on all pages
<!--more-->preamble separator support{{variable: default}}syntax for fragment placeholders- H1 extracted as document title and removed from body when no
title:frontmatter is set --forceflag to overwrite existing output files- Watermark extraction from Word VML
<w:pict>elements
Links
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file mark_my_word-0.2.3.tar.gz.
File metadata
- Download URL: mark_my_word-0.2.3.tar.gz
- Upload date:
- Size: 187.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.15 {"installer":{"name":"uv","version":"0.11.15","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1fb31fba2778bf7747b6988549824240d7b5fce791fa57eb8759b4d977e7e0dc
|
|
| MD5 |
df32a05ad7df061b2ab5b0333385f58d
|
|
| BLAKE2b-256 |
1ac6db646577cf6c5d6d0ec111b2bf93163c76561b1e890ce74cb35354be18b5
|
File details
Details for the file mark_my_word-0.2.3-py3-none-any.whl.
File metadata
- Download URL: mark_my_word-0.2.3-py3-none-any.whl
- Upload date:
- Size: 27.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.15 {"installer":{"name":"uv","version":"0.11.15","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
73728fae36803361c11aca115925e82b55b6215ced084221bd39fc48faba1f24
|
|
| MD5 |
8a9946ea43fca2f5b1f4834b88039579
|
|
| BLAKE2b-256 |
81cdc72f3a657214be99fdcd92951d940de21854e8f6db958de4da12d37e0f4d
|