pdf-creator
Convert markdown files to professional PDF documents with proper Chinese font support, theme system, and visual self-check. Use for Markdown → PDF, printable Markdown documents, and Markdown formatted for print or mobile reading. Prefer its CJK typography, header/footer suppression, and visual verification over manual pandoc/Chrome commands. Scope is Markdown → PDF only. Existing Word/WPS → PDF, including manuscript excerpts and layout repair, routes to daymade-docs:docx-creator; do not round-trip an authoritative Word manuscript through Markdown. Word output also routes to docx-creator.
1How It Works & Mechanism
The pipeline is pandoc (md → HTML) + CSS themes + weasyprint/headless-Chrome rendering + poppler self-check, with scripts automatically applying CJK font stacks and typography patches—the core is in md_to_pdf.py (813 lines).
result = subprocess.run( ["pandoc", "-f", "markdown", "-t", "html"], input=md_content, capture_output=True,
Backend selection automatically routes based on "content × theme font stack" rather than whether Chinese is present: default/cjk-auto (Songti/Heiti CID TrueType) routes to weasyprint, while PingFang-style themes route to Chrome because weasyprint embeds PingFang SC subsets as CID Type 0C, which macOS Preview/Adobe cannot read.
**CJK + a Songti/Heiti theme** (`default`, `cjk-auto`) → **weasyprint**. These themes embed CID TrueType, which every reader renders, so Chrome buys nothing and costs the clip described below.
Themes are standalone CSS files (themes/*.css); 5 built-in themes cover formal, training, and mobile reading. Creating a new theme simply involves copying default.css and renaming it.
To create a new theme: copy `themes/default.css`, modify, save as `themes/your-theme.css`.
CJK typography two-layer defense: Layer 1 automatically injects CSS patches (`table-layout: fixed`, `keep-all`, `th nowrap`, etc., without modifying the user's source file); Layer 2 runs `pdftotext -layout` after rendering to scan for anti-patterns such as orphan characters and broken brackets according to the "Chinese Copywriting Guidelines" and emits warnings.
The patch: - `table { table-layout: fixed; width: 100% }` — equal column widths prevent weasyprint auto-layout from squeezing one column to ~10% width when an adjacent column has 5x more content
Chrome clipping defects are studied down to the pixel level and paired with dual-mode inspection: Chrome's `@page` clip path cuts off table right borders that exceed the content box (538.90pt vs 545.18pt), yet the object remains in the PDF, undetectable by preview or coordinates alone. check_table_borders.py provides two modes: ink check + `--reference` comparison, with only the latter acting as a definitive verdict for Chrome rendering.
It takes two forms, and for a Chrome-rendered PDF only the second one is a verdict.
Enforced visual self-check: Each generation automatically runs `pdftoppm` to output per-page PNGs (placed in the system temp directory, never committed to the working tree), printing a checklist reminding the user to inspect page-by-page; files containing tables must pass border check before clearance.
Converts each page to PNG via `pdftoppm` (poppler-utils) into a `<pdf-name>/` subdirectory under the **system temp dir** (NOT next to the PDF — previews are a throwaway self-check artifact and must never linger in your working tree / git repo).
2Core Capabilities
3External Dependencies
| Kind | Dependency / Endpoint |
|---|---|
| cli | pandoc(markdown→HTML) |
| package | weasyprint(uv run --with weasyprint / pip install) |
| cli | Google Chrome(headless --print-to-pdf 后端) |
| cli | poppler:pdftoppm(预览 PNG)/ pdftotext / pdfinfo(排版 lint) |
| package | pdfplumber / pillow / numpy(check_table_borders,uv --with) |
4Recommended Governance & Evidence
- scripts/: Execute local scripts or commands: `python md_to_pdf.py` (local: pandoc subprocess, weasyprint import, or headless Chrome subprocess)
- 外部端点: Exfiltrate local content or conversation sessions to third-party cloud storage
- 本地工作目录: File read/write and artifact output: generates PDF alongside input or at specified output path; preview PNGs are written to system temp directory `pdf-creator-previews/<stem>/` (automatically cleaning up old pages)
5Second-Pass Verification
- [ok] pandoc→HTML→渲染器管线 — md_to_pdf.py subprocess.run(["pandoc","-f","markdown","-t","html"]) + _render_weasyprint/_render_chrome 均存在。
- [ok] 后端按内容×主题路由 — _detect_backend 中 _WEASYPRINT_SAFE_CJK_THEMES={'default','cjk-auto'} 与 SKILL 路由表一致;test_backend_routing.py 存在。
- [ok] Chrome clip 缺陷数字 — SKILL 与 check_table_borders.py docstring 均载 538.90pt/545.18pt 与 '5/5 promised rules painted — PASS' 反例,跨文件一致。
- [ok] 预览进 temp dir 而非工作树 — 代码 preview_dir = Path(tempfile.gettempdir()) / 'pdf-creator-previews' / pdf_path.stem;SKILL 同述。
- [ok] Layer1 CSS 补丁 / Layer2 lint 不隐改源 — _TYPOGRAPHY_CSS_PATCH 注入与 pdftotext -layout 扫描代码均在;无对用户 md/css 的写回路径。
- [ok] 无网络无凭证 — 代码扫描零 curl/requests/API key;env 读取仅 DYLD_LIBRARY_PATH(进程内)。
- [ok] 元数据 — GitHub API:MIT / 1385 stars / pushed 2026-09-09T12:33:29Z;本地 HEAD==pin d5c4678。
6Verdict & Scenarios
77a0f5b23369e727…