1仕組みと動作原理
パイプラインは pandoc(md→HTML) + CSSテーマ + weasyprint/headless-Chrome レンダリング + poppler セルフチェックで構成され、スクリプトが自動的にCJKフォントスタックと組版パッチを補完します。中核は md_to_pdf.py(813行)にあります。
result = subprocess.run( ["pandoc", "-f", "markdown", "-t", "html"], input=md_content, capture_output=True,
バックエンドの選択は中国語の有無ではなく「コンテンツ×テーマのフォントスタック」に応じて自動ルーティングされます。default/cjk-auto(Songti/Heiti CID TrueType)はweasyprintへ、PingFang系テーマはChromeへルーティングされます。weasyprintがPingFang SCのサブセットをmacOS PreviewやAdobeで読み込めないCID Type 0Cとして埋め込んでしまうためです。
**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.
テーマは独立したCSSファイル(themes/*.css)であり、5つの組み込みテーマが公式文書/研修用/モバイル閲覧をカバーします。新規テーマ作成は default.css をコピーして名前を変更するだけです。
To create a new theme: copy `themes/default.css`, modify, save as `themes/your-theme.css`.
CJK組版の2層防御:Layer 1ではCSSパッチ(`table-layout: fixed`、`keep-all`、`th nowrap` など)を自動注入(ユーザーのソースファイルは変更しません)。Layer 2ではレンダリング後に `pdftotext -layout` を使用し、「中文文案排版指北」に沿って孤立文字や括弧の泣き別れなどのアンチパターンをスキャンし警告します。
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のクリッピング不具合はピクセルレベルまで調査され、2形態の検査を備えています。Chromeの `@page` clip path はコンテンツボックスを超える表の右枠線(538.90pt vs 545.18pt)を切り落としますが、オブジェクトはPDF内に残るためプレビューや座標を見るだけでは発見できません。check_table_borders.py はink検査と `--reference` 照合の2形態を提供し、後者のみがChromeレンダリングに対する有効な判定基準となります。
It takes two forms, and for a Chrome-rendered PDF only the second one is a verdict.
視覚的セルフチェックの義務化:生成ごとに `pdftoppm` が各ページのPNGを自動出力し(システム一時ディレクトリに保存され、作業ツリーを汚染しません)、全ページを目視確認(Read)するよう促すチェックリストを出力します。表を含むファイルは境界線チェックを通過しない限り完了と見なされません。
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).
2コア機能
3外部依存関係
| 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) |
4推奨統制措置と判定根拠
- scripts/: ローカルスクリプトまたはコマンドの実行: `python md_to_pdf.py`(ローカル: pandocサブプロセス、weasyprintインポート、またはheadless Chromeサブプロセス)
- 外部端点: ローカルコンテンツやセッションのサードパーティクラウドストレージへの外部送信
- 本地工作目录: ファイルの読み書きと成果物の出力: 入力ファイルと同じ場所または指定された出力パスにPDFを生成。プレビューPNGはシステム一時ディレクトリ `pdf-creator-previews/<stem>/` に書き込み(古いページは自動クリーンアップ)
5二重目の独立検証
- [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。
6結論
77a0f5b23369e727…