Convert one or more Markdown files into styled PDFs using WeasyPrint and the Python markdown library. The default styling is neutral; apply your own brand stylesheet via --css.
How to use
Run the conversion script via uv run:
uv run "${SKILL_DIR}/scripts/convert.py" <files...> [-o output_dir] [--css custom.css]
${SKILL_DIR} is the directory containing this SKILL.md. The script resolves assets/style.css relative to its own location, so it works at any path. Its dependencies (markdown, weasyprint) are declared in the inline script metadata at the top of convert.py; uv run reads that block and installs them.
Steps
- Identify target
.mdfiles from the request. If none specified, look in the current directory and ask which to convert. - Run the conversion:
uv run <skill-dir>/scripts/convert.py file1.md file2.md
- Use
-o <dir>to place PDFs in a specific output directory. - Use
--css <path>to override the default stylesheet (e.g.,netresearch-branding-skill/assets/markdown-pdf.css). - Glob patterns like
*.mdare supported. - Use
--allow-scheme <scheme>(repeatable) only when the document needs images, stylesheets or fonts over a scheme other thanhttpsordata(see below).
- Report which PDF files were created and where.
Default styling
The bundled assets/style.css provides:
- system fonts (no external font fetches)
- neutral grayscale headers
- monospace code blocks
- A4 page size, sensible margins
- page numbers in footer
Remote and local resources
WeasyPrint loads the images, stylesheets and fonts that the Markdown (including raw HTML in it) and the --css file reference. The script lets it load only https: and data: URLs. Any other scheme (http:, file:, ftp:) stops the conversion with refused to load <url> and exit code 1; no PDF is written. Pass --allow-scheme http or --allow-scheme file only for a document you trust. Relative paths such as  are not resolved, because the HTML has no base URL.
Companion skills
netresearch-branding-skillships amarkdown-pdf.cssbrand asset. Netresearch users: install both skills, then pass--css "$CLAUDE_PLUGIN_ROOT/.../netresearch-branding-skill/.../assets/markdown-pdf.css".- That brand CSS expects two wrapper elements this script does not generate:
.page-header(with an.header-logoimage) and.page-footer(with.footer-info/.footer-page). Passing--csswith a branded stylesheet yields a PDF with no logo and no footer — no error, just missing brand elements. For compliant output, build the HTML yourself: wrap the body in the.page-header/.page-footerdivs the CSS expects, then callweasyprintdirectly. See netresearch-branding-skill'sSKILL.md.
Known pitfalls
- Bullet list right after a lead-in line, no blank line between them:
python-markdown(unlike CommonMark) starts a list only when a blank line precedes it.Intro:\n- itemrenders as literal- itemtext, not a<ul>— silently. Always leave a blank line between a lead-in sentence and its list. - Such rendering bugs raise no error and don't affect the exit code. Before calling a conversion done, render the first page to a PNG (
pymupdf/fitz:page.get_pixmap(dpi=150).save(...)) and inspect it — don't trust✓ converted ... (N KB)alone.
Output format
Per file:
✓ converted README.md → README.pdf (12.3 KB)
✓ converted RFC-001.md → RFC-001.pdf (4.7 KB)
Errors
| Error | Action |
|---|---|
No .md files matched |
List directory contents and ask user |
| WeasyPrint missing | uv run convert.py installs it from the script's inline metadata; if it was started as python3 convert.py, rerun it with uv run |
--css file not found |
Surface the missing path; do not fall back silently |
refused to load <url> |
The document references a scheme other than https/data. Tell the user which URL; rerun with --allow-scheme <scheme> only if they trust the document |