Asia/Shanghai
March 12, 2026

Zero-Margin Markdown to PDF with Playwright and Pandoc

如何完美地把 Markdown 转成无边距 PDF(以及如何让 AI Agent 替你打工)

Mingjian Shao
Zero-Margin Markdown to PDF with Playwright and Pandoc
Generating a PDF from Markdown is easy. Generating a beautiful, dark-themed PDF without those ugly white system margins? Not so much. Here's how I solved it and turned it into an AI skill so I never have to touch CSS again.
I write a lot of design specs and follow-up reports in Markdown. Sometimes, you just need a clean PDF to send to stakeholders.I prefer dark mode (specifically, my Obsidian OpenCode theme), so I wanted my PDFs to match that aesthetic: deep #0d1117 background, vibrant purple headings, gold accents.But if you use standard tools (like Chrome's "Save as PDF" or basic Pandoc HTML generation), you hit a wall: the physical page margins are always white.Even if you set your body { background: #0d1117 }, the browser injects a non-negotiable physical margin around the print area. The result is a dark rectangle awkwardly floating in the middle of a glaring white page.The trick is to use a two-step pipeline: pandoc to convert Markdown to HTML5, and then headless Chromium (playwright) to print the PDF.But here is the magic formula: you must eliminate the physical margins entirely and use CSS padding to create "breathing room" (the visual margins).
  • Tell Playwright to use 0 margins:
Python
# Playwright PDF generation
page.pdf(
    path=output,
    format="A4",
    margin={"top": "0", "bottom": "0", "left": "0", "right": "0"},
    print_background=True
)
  • Tell CSS to fill the void:
Css
@page {
  size: A4;
  margin: 0; /* Kills the physical print margin */
}
html, body {
  margin: 0;
  padding: 0;
  background: #0d1117; /* Full bleed background! */
}
body {
  /* Use CSS padding to push content inward */
  padding: 60px 80px; 
}
By doing this, the dark background bleeds all the way to the edge of the PDF, and your text stays nicely centered.Another annoyance: Markdown tables rendering to PDF. If you have a column with very short text (like # or a Status column with "✅ Done") next to a column with a long paragraph, the browser sometimes awkwardly wraps the short text to give more room to the long text."✅ \n Done" looks terrible.The fix is CSS shrink-wrapping for specific columns:
Css
table {
  table-layout: auto;
}
th, td {
  white-space: normal;
  word-break: break-word;
}
/* Force the 1st and 3rd columns to shrink to their content */
td:first-child, th:first-child,
td:nth-child(3), th:nth-child(3) {
  white-space: nowrap;
  width: 1%;
}
I didn't want to remember to run a 200-line Python script every time I needed a PDF. Since I use OpenCode as my daily AI orchestrator, I packaged this into a custom command /pdf.I created ~/.agents/skills/md2pdf/scripts/md2pdf.py which handles the pandoc parsing, inline CSS injection, and playwright rendering (using uv run --with playwright so I don't pollute my global Python environment).Then, I registered the command in ~/.config/opencode/command/pdf.md:
Markdown
# /pdf — Markdown to PDF

Convert Markdown files to styled PDF using pandoc → HTML → Playwright pipeline.

**Argument**: Path to `.md` file. Required.

```bash
uv run --with playwright ~/.agents/skills/md2pdf/scripts/md2pdf.py $ARGUMENTS
```
Now, I just type /pdf report.md in my terminal, and the AI agent runs the pipeline, generating a beautiful, zero-margin dark PDF. It even supports --light for print-friendly versions and --today to auto-replace dates.Don't do manual labor twice. Write it once, hand it to the agent, and move on.
把 Markdown 转成 PDF 很容易,但要转成一个漂亮、没有丑陋系统白边的深色主题 PDF?这就有点折腾了。这篇文章记录了我是如何解决这个问题,并把它封装成 AI Agent 的专属技能的,这样我就再也不用碰 CSS 了。
我经常用 Markdown 写各种设计文档和进度汇报。有时候你需要发一份干净的 PDF 给相关方。我个人偏好深色模式(尤其是我的 Obsidian OpenCode 主题),所以我希望我的 PDF 也能保持这种审美:深邃的 #0d1117 背景,亮紫色的标题,金色的强调文字。但如果你用常规工具(比如 Chrome 的“另存为 PDF”或原生的 Pandoc HTML 转换),你会撞到一堵墙:PDF 的物理页边距永远是白色的即便你在 CSS 里设置了 body { background: #0d1117 },浏览器在打印时依然会强行在四周留下一圈白边。结果就是,一个深色的矩形尴尬地悬浮在一张刺眼的白纸中间。诀窍在于使用两段式 Pipeline:先用 pandoc 把 Markdown 转成 HTML5,然后用 Headless Chromium (playwright) 来渲染 PDF。这里的“魔法公式”是:你必须彻底消灭物理边距,转而使用 CSS 的 padding 来创造“呼吸感”(视觉边距)。
  • 告诉 Playwright 把 margin 设为 0:
Python
# Playwright PDF generation
page.pdf(
    path=output,
    format="A4",
    margin={"top": "0", "bottom": "0", "left": "0", "right": "0"},
    print_background=True
)
  • 用 CSS 填补空白:
Css
@page {
  size: A4;
  margin: 0; /* 杀掉物理打印白边 */
}
html, body {
  margin: 0;
  padding: 0;
  background: #0d1117; /* 背景色铺满全画幅! */
}
body {
  /* 用 CSS padding 把内容往里挤,营造留白 */
  padding: 60px 80px; 
}
这样一来,深色背景就能一直延伸到 PDF 的物理边缘,而你的文字依然能优雅地居中。另一个痛点是 Markdown 表格渲染。如果你的表格有一列文本很短(比如序号 #,或者状态列写着“✅ 已完成”),而旁边一列是长篇大论,浏览器有时候为了给长文本腾地方,会丧心病狂地把短文本折行。“✅ \n 已完成”看起来简直糟透了。修复方法是用 CSS 精准“包裹”这些短文本列:
Css
table {
  table-layout: auto;
}
th, td {
  white-space: normal;
  word-break: break-word;
}
/* 强迫第 1 列和第 3 列收缩到内容的最小宽度 */
td:first-child, th:first-child,
td:nth-child(3), th:nth-child(3) {
  white-space: nowrap;
  width: 1%;
}
我可不想每次转个 PDF 还要去跑一个 200 行的 Python 脚本。因为我每天都在用 OpenCode 作为我的 AI 编排器,所以我把这套逻辑封装成了一个自定义命令 /pdf我写了一个 ~/.agents/skills/md2pdf/scripts/md2pdf.py 脚本,它负责调度 pandoc、注入内联 CSS 以及驱动 playwright 渲染(使用 uv run --with playwright 来运行,这样就不会污染我全局的 Python 环境)。然后,我在 ~/.config/opencode/command/pdf.md 里注册了这个命令:
Markdown
# /pdf — Markdown to PDF

Convert Markdown files to styled PDF using pandoc → HTML → Playwright pipeline.

**Argument**: Path to `.md` file. Required.

```bash
uv run --with playwright ~/.agents/skills/md2pdf/scripts/md2pdf.py $ARGUMENTS
```
现在,我只需要在终端里敲下 /pdf report.md,AI Agent 就会自动拉起整套 pipeline,给我吐出一个漂亮的、零白边的深色 PDF。这个脚本甚至支持加 --light 参数输出打印友好版,或者加 --today 自动把文中的日期替换成今天。永远不要手动干两次同样的活。写一遍脚本,交给 AI Agent,然后继续前进。如果你也想在自己的机器上部署这套 Markdown 转 PDF 的工作流(支持深浅双色主题 + 零白边),可以直接复制下面这段 Prompt 发给你的 AI Agent(比如 OpenCode / Cursor / Claude):
Text
Please help me set up a Markdown to PDF conversion pipeline on my machine. 

Requirements:
1. I want a Python script that uses `pandoc` to convert MD to HTML, and `playwright` to render the HTML to PDF.
2. The script should use `uv run --with playwright` so it doesn't pollute my global environment.
3. The PDF must have ZERO margins in Playwright (`margin: 0`), using CSS `body { padding: 60px 80px; }` for visual spacing to ensure the background color bleeds to the absolute edge.
4. It should have two themes: a Dark theme (background `#0d1117`, text `#f0f6fc`) as default, and a Light theme (GitHub style) triggered by a `--light` flag.
5. Please handle Markdown tables properly: make the first column and third column "shrink-wrap" their content (`white-space: nowrap; width: 1%`) so short status text doesn't awkwardly line-break.

Please write the script to a path like `~/.local/bin/md2pdf` or my agent skills folder, make it executable, and show me how to test it.
Share this post:
Enjoy this post? Subscribe via RSS: English | 中文