pdf-creator について
pdf-creator は、中国語フォントとテーマに対応し、Markdown から PDF を作成するドキュメント用スキルです。内容とテーマに応じてレンダリングバックエンドを選び、表の罫線を目視確認する手順も備えています。
主な機能
- 01Markdown から PDF への変換
- 02中国語フォントとテーマへの対応
- 03内容とテーマに応じたバックエンド選択
- 04Markdown ファイルの一括変換
- 05PDF の表罫線レンダリング確認
活用シーン
- 01中国語の PDF 文書の作成
- 02印刷向けまたはスマートフォン向け版の生成
- 03Markdown 文書の一括変換
- 04PDF の表罫線の欠落や切れの確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ daymade/ claude-code-skills --skill pdf-creatorインストール後、Claude Code で/pdf-creatorを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/illustrated-manual.mdscripts/batch_convert.pycheck_table_borders.pymd_to_pdf.pyscripts/tests/test_backend_routing.pytest_cjk_code_blocks.pytest_cjk_tables.pytest_executable_copy.pytest_list_rendering.pytest_relative_images.pytest_self_check_preview.pytest_table_border_check.pytest_typography_lint.pythemes/cjk-auto.cssdefault.cssmobile.csswarm-terra-menu.csswarm-terra.css詳細
翻訳後に原文が更新されています。最新の内容は原文をご確認ください。
PDF クリエーター
中国語フォントとテーマシステムに対応した、プロフェッショナルな PDF ドキュメントを Markdown から作成します。
既存の .docx ソースの場合は、代わりに docx-creator/references/word-to-pdf.md を使用してください。
出力の拡張子だけでなく、正式な入力ファイルに基づいて選択してください。
クイックスタート
以下の scripts/… のパスはすべて、このスキルのディレクトリを基準とした相対パスです。そこからコマンドを実行するか、絶対パスに置き換えてください。Markdown ファイルがあるディレクトリから実行すると、Failed to spawn: 'scripts/md_to_pdf.py' が発生します。出力パスは省略できます。その場合、入力ファイルのベース名から出力先が決まります(input.md → input.pdf)。出力先は入力ファイルと同じディレクトリです。
# Default theme (formal: Songti SC + black/grey, A4 print)
uv run --with weasyprint scripts/md_to_pdf.py input.md output.pdf
# Warm theme (training: PingFang SC + terra cotta)
uv run --with weasyprint scripts/md_to_pdf.py input.md --theme warm-terra
# Mobile theme (narrow page, large font — for phone reading / WeChat sharing)
uv run --with weasyprint scripts/md_to_pdf.py input.md --theme mobile
# Batch convert all markdown files with a specific theme
uv run --with weasyprint scripts/batch_convert.py *.md --theme warm-terra --no-preview
# No weasyprint? Use Chrome backend (auto-detected if weasyprint unavailable)
python scripts/md_to_pdf.py input.md --theme warm-terra --backend chrome
# List available themes
python scripts/md_to_pdf.py --list-themes dummy.md
テーマ
themes/*.css に保存されています。各テーマは独立した CSS ファイルです。
| テーマ | ページサイズ | フォント | 色 | 最適な用途 |
|---|---|---|---|---|
default | A4 | Songti SC + Heiti SC | 黒/グレー | 法務文書、契約書、正式なレポート |
cjk-auto | A4 | Songti SC + Heiti SC | 黒/グレー | 列ごとの内容に偏りがある表(コーススケジュール、項目別リスト) |
warm-terra | A4 | PingFang SC | テラコッタ(#d97756)+暖色系のニュートラルカラー | コース概要、トレーニング資料、ワークショップ |
warm-terra-menu | A4 | PingFang SC | テラコッタ(#d97756)+暖色系のニュートラルカラー | モジュールメニュー/リスト向けに調整した warm-terra の派生テーマです。長いテキストを含む 2 列の表で、最初の列がはみ出さずに折り返されます。さらに、Menlo の unicode-range により、Preview/Adobe で CJK のインラインコードが空白にならずに表示されます |
mobile | 148mm × 210mm | PingFang SC | テラコッタ+暖色系のニュートラルカラー | スマートフォンでの閲覧、WeChat での共有、外出先での参照 |
新しいテーマを作成するには、themes/default.css をコピーして編集し、themes/your-theme.css として保存してください。
印刷用とモバイル用:適切なテーマを選ぶ
| シナリオ | おすすめのテーマ | 理由 |
|---|---|---|
| A4 用紙への印刷、配布資料、契約書 | default | 標準的なページサイズ、フォーマルなタイポグラフィ |
| トレーニング資料、コース概要 | warm-terra | 温かみのあるアクセントカラーで、ワークショップの場面でも読みやすい |
| WeChat で送信、スマートフォンで閲覧 | mobile | 幅の狭いページ(148mm)、15px のフォント、1.9 の行間で、小さな画面でも快適 |
| 印刷用とモバイル用の両方が必要 | テーマを変えて 2 回実行 | このスキルは高速なので、両方のバージョンを生成できます |
選択ルール: ユーザーから指定がない場合、トレーニング/コース関連のコンテンツには warm-terra、正式な文書には default を使用します。ただし、文書の表で列ごとの内容に偏りがある場合(スケジュール、項目別リスト)は default ではなく cjk-auto を選んでください。このテーマには、そのための table-layout: auto が設定されています。出力先が不明確な場合に限り、「スマートフォン向けのバージョンも必要ですか?」と確認してください。
バックエンド
スクリプトは コンテンツ × テーマ に基づいて、利用可能な最適なバックエンドを自動検出します。テーマ側も重要です。CJK に Chrome が必要だった理由は、テキストが中国語であることではなく、テーマのフォントスタックの特性にあるためです。
- CJK + Songti/Heiti テーマ(
default、cjk-auto)→ weasyprint。これらのテーマは、どのリーダーでも描画できる CID TrueType を埋め込むため、Chrome を使っても利点はなく、後述するクリップの問題が発生します。 - CJK + PingFang テーマ(
warm-terra、mobile、warm-terra-menu、および安全リストにないテーマ)→ Chrome。weasyprint は PingFang SC を CID Type 0C OpenType としてサブセット埋め込みしますが、macOS Preview/Adobe Reader では正しく描画できません。Chrome 自身の PDF ビューアーでは問題なく見えても、受信者のデバイスではテキストが文字化けします。 - CJK 以外のコンテンツ → weasyprint(高速で、ブラウザーの起動も不要)。
ルーティングは scripts/tests/test_backend_routing.py で固定されています。独自に追加したテーマは、md_to_pdf.py の _WEASYPRINT_SAFE_CJK_THEMES に追加するまで PingFang クラスとして扱われます。追加するのは、そのテーマの CJK フォントが CID TrueType であると確認してからにしてください。
| バックエンド | インストール | メリット | デメリット |
|---|---|---|---|
weasyprint | pip install weasyprint | CSS を正確に描画でき、ブラウザー不要で、オーバーフローもクリップされません | PingFang SC を CID Type 0C としてサブセット埋め込みするため、Preview/Adobe では読み取れません |
chrome | Google Chrome installed | Python の依存関係が不要で、PingFang を正しく描画します | @page ボックスを超えた部分はすべてクリップされます(下記参照) |
--backend chrome または --backend weasyprint で指定できます。明示的なフラグは常に自動検出より優先されます。
テーマから、どのバックエンドが実行されたかはわかりません。 weasyprint をインポートできない場合、default/cjk-auto のレンダリングは Chrome にフォールバックします。その際は stderr に警告が出ますが、終了コードは 0 です。実際に使用されたバックエンドを確認するには、Generated: 行の backend= フィールドを確認してください。Chrome 専用の後述の手順が必要かどうかを決めるのは、テーマ名ではなくこのフィールドです。
Chrome は単にオーバーフローさせるのではなく、クリップします
Chrome は各ページを @page のコンテンツボックスに沿った re W* n クリップパスで囲みます。そのボックスを超えたコンテンツは PDF のオブジェクトレイヤーには残っていますが、描画されることはありません。A4 で margin: 2.5cm 2cm 2cm 2cm の場合の計測値では、クリップパスは 538.90pt で終わる一方、テーブルの右罫線は 545.18pt にあります。そのため、罫線が気づかれないまま切り落とされます。
これは「幅の広いテーブル」の問題ではありません。default と cjk-auto では、すべてのテーブルで発生します。 どちらのテーマも table { width: 100% } を設定しています。default は固定列を使用し、cjk-auto はタイポグラフィのパッチを上書きして、コンテンツに応じた自動レイアウトを使用します。2 列のテーブルでも、コンテンツ幅いっぱいに広がります。計測では、単純な | 周一 | 周二 | のテーブルも、6 列の料金表と同じようにクリップされました。コンテンツの幅は関係ありません。境界を超える原因は、テーマ自体の width: 100% とセルのパディングです。
オーバーフロー自体は意図的な設計です。CJK タイポグラフィレイヤーでは、トークンの途中で改行するのではなくコンテンツをオーバーフローさせるために、overflow-wrap: normal を設定しています(下記「CJK タイポグラフィ」を参照)。このトレードオフは weasyprint では問題ありませんが、Chrome では表示を損ないます。
これが繰り返しの納品をすり抜けてきたのは、プレビューが実態と異なるためではなく、症状が意図的なものに見えるためです。 最後の列のテキストは完全で、間隔も正しく、消えているのは細い罫線だけなので、スタイル上の選択のように見えます。一方、下記の目視チェックリストでは、テキストの欠けに注目するよう促していますが、ここではまさにそれが起きません。
ラスタライザーはクリップを反映するため、このスクリプトがすでに生成しているプレビュー PNG(130 dpi の pdftoppm)にも、400 dpi のレンダリングにも欠陥が現れます。400 dpi では、想定される罫線をまたぐ 25 ピクセル幅の列にインクがまったくありません。一方、座標を読み取るだけでピクセルを確認しないチェックでは、欠陥はわかりません。オブジェクト自体は実際に存在するため、pdfplumber は 545.18pt に矩形があると報告します。
細い罫線が消えていることに気づけるかどうかに頼るのではなく、チェックを実行してください。チェックには 2 つの形式があり、Chrome でレンダリングされた PDF では、2 つ目だけが判定になります。
# Form 1 — one file. Compares ink against the PDF's own object layer.
uv run --with pdfplumber --with pillow --with numpy scripts/check_table_borders.py out.pdf
フォーム 1 は、検出された各テーブルの列境界を取得し、pdftoppm のラスター上で実際にインクがある境界の数を数え、PDF には存在するが紙面には存在しない境界があればその名前を挙げて非ゼロで終了します。計測したテーブルの数を報告し、1 つも見つからなかった場合は PASS ではなく NOTHING CHECKED と表示します。テーブルを検出できなかったドキュメントは、合格したのではなく、スキップされたのです。
境界は生の垂直エッジではなくセルグリッドから取得します。<hr> の端のキャップやインライン <code> スパンの背景は、エッジベースのリーダーには列の罫線とまったく同じに見えるからです。実際のナレッジベースから得た WeasyPrint 製 PDF 49 件(うち 46 件はエッジベース版が検査したジオメトリを持つ)で計測したところ、その 46 件のうち 34 件で失敗しました。残る限界: 他のツールで生成された PDF では、pdfplumber が装飾を、存在しなかったテーブルとして組み立ててしまうことがあり、その場合このチェックはそれに対する罫線を報告します。このスキルが生成したものではない PDF での失敗は、判定ではなく、確認を促すサインとして扱ってください。
Chrome のレンダリングを単独で合格にすることはできません。クリップが証拠を 2 通りの方法で破壊するからです。 Chrome はクリップをまたぐジオメトリは保持します(フォーム 1 はこれを捉えます。罫線は約束されているのに描画されていないためです)が、クリップの完全に外側にあるジオメトリは破棄し、そもそもオブジェクトレイヤーに書き込まれなかった罫線は探されもしません。計測例: 垂直罫線付きでセル塗りなしのスタイルのテーブルは、右罫線が実際には消えているのに 5/5 promised rules painted — PASS と出力します。同梱のテーマがこれを免れているのは偶然にすぎません。それらのセル背景の塗りがクリップをまたぎ、罫線の位置にエッジを残すため、フォーム 1 が見つけられるのです。
したがって、テーマが Chrome にルーティングされ、ドキュメントにテーブルがある場合は、同じソースをもう一方のバックエンドでレンダリングして比較してください:
# Form 2 — render a reference with the other backend, then compare.
# Use the SAME --theme as the deliverable; only the backend differs.
uv run --with weasyprint scripts/md_to_pdf.py doc.md /tmp/ref.pdf \
--theme warm-terra --backend weasyprint --no-preview
uv run --with pdfplumber --with pillow --with numpy \
scripts/check_table_borders.py out.pdf --reference /tmp/ref.pdf
終了コード: 0 合格、1 少なくとも 1 つのチェックが合格しなかった、2 チェックを実行できなかった、3 テーブルが検出されなかったため何も計測されなかった。3 は合格ではありません: ドキュメントに実際にテーブルがあるなら、そのスタイルが pdfplumber の見つけられる罫線を描かなかったということであり、このチェックはそれについて何も言えません。テーブルの右エッジで自分でラスターを読むことに切り替え、引き継ぎ時にこのゲートが実行されなかったことを伝えてください。なお pdfplumber はサブセット化された CJK フォントに対して Could not get FontBBox from font descriptor を出力しますが、これはパーサーのノイズであり、指摘事項ではありません。
リファレンスは物差しであり、決して成果物ではありません。そもそもこのテーマを Chrome に送る原因となった CID Type 0C の問題を抱えている可能性がありますが、それはそのジオメトリには影響しません。
両方のファイルがインクチェックされ、罫線の数は双方向で比較されます。したがって、渡す順序によって、破損したファイルが検査されるかどうかが決まることはありません。これは聞こえ以上に重要です。Chrome が破棄ではなくクリップした罫線の場合、2 つのレンダリングは同じ罫線数になります(ジオメトリはまだそこにあり、単に描画されていないだけです)。そのため数の比較では何も見つからず、正しいファイルのインクチェックだけがそれを見つけます。リファレンスもインクチェックされる前は、クリップされたファイルを --reference として渡すと、無条件の PASS が出力されていました。
数の比較は双方向で行います: リファレンスより罫線が少なければ、レンダラーがいくつか破棄したということです。多ければ、引数が入れ替わっているか、2 つのファイルが同じドキュメントではありません。位置ではなく数で、ページ単位ではなくドキュメント全体で比較するのは、2 つのバックエンドが同じソースを異なる形で分割するためです。60 行の CJK テーブル 1 つで計測したところ、行の内容によって分割位置が決まります: default では 5 ページ対 3 ページ、warm-terra-menu では 3 対 15 で、罫線数は全体を通じて同一でした。
5 テーマ × 単一ページ・複数ページ × 両方の引数順、計 20 回の実行で校正しました: クリップされたファイルを含まない 12 ペアは両方の順序で合格し、含む 8 ペアは両方の順序で捕捉されます。別途、実際の markdown ドキュメント 14 件 × 2 テーマ = 28 回の単一ファイル実行で、偽陽性はゼロでした。
一括変換
# Default theme, same directory
uv run --with weasyprint scripts/batch_convert.py *.md
# Specific theme, output directory, skip previews for speed
uv run --with weasyprint scripts/batch_convert.py *.md --theme warm-terra --output-dir ./pdfs --no-preview
# Mobile theme for phone reading
uv run --with weasyprint scripts/batch_convert.py *.md --theme mobile --output-dir ./mobile-pdfs --no-preview
# The border check takes several files at once, so a batch is still gated
uv run --with pdfplumber --with pillow --with numpy \
scripts/check_table_borders.py ./pdfs/*.pdf
--no-preview は、一括処理の要となる目視によるセルフチェックを無効にしますが、確認の責務までなくすものではありません。上記のように、出力に対して枠線チェックを実行してください。複数のファイルを処理できます。一方、--reference はそうではないため、表を含む Chrome レンダリング済みドキュメントを一括処理する場合は、ドキュメントごとにペアで実行する必要があります。
アンチパターン:pandoc + Chrome を手動で実行しないでください
このスキルが存在する理由: 手動の pandoc input.md -o out.html + chrome --headless --print-to-pdf ワークフローは、検出しにくい問題を気付かれないまま引き起こします。
| 手順(手動) | 発生する問題 | このスキルによる対処 |
|---|---|---|
pandoc -o out.html | CJK 対応 CSS がないため、中国語がボックスや空白で表示される | CJK フォントスタックとタイポグラフィの修正を注入します |
Chrome --print-to-pdf | デフォルトのヘッダー/フッター(ファイル名、日付、URL、ページ番号)が表示される | --no-pdf-header-footer を指定します |
macOS での Chrome --print-to-pdf | Chrome が PDF を書き出した後も終了しないことがあります(Chrome 154 は --user-data-dir を指定すると常にこうなります)。そのためコマンドが停止し、Chrome を強制終了すると一時ディレクトリに Chrome アプリのコピーが残ります | PDF が %%EOF で終わると Chrome を停止し、タイムアウト後に処理を断念して、--disable-features=MacAppCodeSignClone を指定します |
| レンダリング後のチェックがない | 「終了コード 0」を成功と見なし、レンダリングの不具合が隠れます | ページごとの PNG プレビューとタイポグラフィの静的検査を自動生成します |
| テーマシステムがない | すべてに同じ設定が適用され、スマートフォンでの閲覧が困難になります | 厳選した 3 つのテーマ(default / warm-terra / mobile)を提供します |
batch_convert.py がない | その場しのぎのループを書き、フラグの指定に一貫性がなくなります | --theme に対応した組み込みの一括処理モードを提供します |
ルール: Markdown → PDF には、pandoc/Chrome の手動コマンドではなく、このスキルを使用してください。 既存の Word/WPS → PDF には、上記の docx-creator 経由の方法を使用してください。
トラブルシューティング
漢字が四角で表示される場合:中国語フォント(Songti SC、PingFang SC など)がインストールされていることを確認してください。
weasyprint のインポートエラー:uv run --with weasyprint を実行するか、代わりに --backend chrome を使用してください。
コードブロック内のCJKテキストが文字化けする場合(weasyprint):このスクリプトは中国語・日本語・韓国語の文字を含むコードブロックを自動検出し、CJK対応フォントを使ったスタイル付きの div に変換します。それでも問題が発生する場合は、CJKをネイティブサポートする --backend chrome を使用してください。ただし、ドキュメントに表が含まれている場合は、scripts/check_table_borders.py <the chrome pdf> --reference <a weasyprint render of the same source> で結果を確認してください。対象を先に指定し、その後にフラグを指定します。Chrome は @page ボックスからはみ出した表の罫線をクリップするため、単一ファイル形式では、Chrome がクリップしたのではなく削除した罫線を検出できません(上記の「Chrome はクリップするのであって、単にオーバーフローさせるわけではない」を参照)。別の方法として、PDFを生成する前にコードブロックをMarkdownの表に変換してください。
Chrome のヘッダー/フッターが表示される場合:このスクリプトは --no-pdf-header-footer を渡します。それでも表示される場合は、お使いの Chrome のバージョンがこのフラグをサポートしていない可能性があります。Chrome を更新してください。**注:**このスキルを迂回して手動で Chrome headless を使用した場合、これが最初に現れる症状です。上記の「アンチパターン」セクションを参照してください。
インラインコードにCJK文字とASCII文字が混在していると、macOS Previewで空白になる場合(例:`Terminal/终端` がCJK部分を欠いた Terminal/ としてのみ表示される):weasyprint は PingFang SC を OpenType(CID Type 0C) としてサブセット埋め込みするため、厳格なPDFリーダー(macOS Preview/Adobe Reader)では正しく描画されません。Chrome のPDFビューアーは自動的にフォールバックするため、この問題は表面化しません。修正はデフォルトテーマにあります。コードフォントの font-family チェーンでは、OpenTypeフォント(PingFang SC)より先に CID TrueType のCJKフォント(Songti SC/Heiti SC)を優先します。確認するには、pdfplumber を使用してCJK文字の font['fontname'] を確認してください。PingFang-SC(CID Type 0C OT)を参照しているものがあれば、リーダーで正しく表示されない可能性があります。CID TrueType が先になるよう、フォントチェーンの順序を変更してください。
表の1列目にある短いラベルが途中で改行される場合(例:4/28(周|二)下|午):pandoc はMarkdownの区切り行にあるダッシュの数から、<colgroup><col style="width:X%"> を自動出力します。| ----- | --- | --- | -------- | のようにダッシュの幅が均等でない場合、pandoc は1列目に約17%を割り当てるため、CJK文字9字のラベルには狭すぎます。同じ詳細度ではインラインの style="" が外部CSSより優先されるため、td:first-child { width:... } は気づかれないまま上書きされます。修正はデフォルトテーマにあります。table colgroup col { width: auto !important } によって pandoc のヒントを無効化し、table-layout: fixed で列幅を均等に割り当てます(4列の表では各列25%)。確認するには、pandoc input.md -t html | grep colgroup を実行してください。<col style="width:X%"> が表示された場合、この問題が該当します。**適用範囲:**この無効化設定があるのは default.css のみです。warm-terra と mobile のテーマでは別の方法(th/tdを折り返さず、最後の子要素は折り返す方法と、全体を流し込んで折り返す方法)を使っているため、意図的にこの設定を省いています。この設定は scripts/tests/test_cjk_tables.py::test_default_theme_neutralizes_pandoc_colgroup_hint によって固定されています。
見た目のセルフチェック(必須 — 省略しないでください)
これは任意ではありません。 PDFを生成するたびに、スクリプトが自動で次の処理を行います。
pdftoppm(poppler-utils)を使用して各ページをPNGに変換し、システムの一時ディレクトリ内にある<pdf-name>/サブディレクトリへ保存します(PDFと同じ場所ではありません。プレビューは使い捨てのセルフチェック用生成物であり、作業ツリーやgitリポジトリに残してはいけません)。実行結果にはディレクトリがPreviews:として表示され、既存のFirst page:パスも表示されます。Poppler はページ数に応じてページ番号をゼロ埋めする場合があるため、ファイル名を組み立てるのではなく、実際のpage-*.pngファイルを列挙してください。- 各ページを目視確認するよう促す、構造化されたセルフチェック用チェックリストを出力します。
- CJKの不適切な改行パターンを検出するため、タイポグラフィのリントを実行します。 必須である理由:「PDF が問題なく生成された」≠「Markdown の意図どおりにレンダリングされた」。よくある、気づきにくい失敗には次のものがあります。
- 段落がひとつにまとまる(連続する空行のない行に対する CommonMark のソフト改行)
- 表がページの余白からはみ出す
- テキストはそのままなのに、表の右罫線が欠ける(Chrome のクリッピング。この一覧で、失敗に見えない唯一の失敗です。判定は
scripts/check_table_borders.pyが行い、Chrome でレンダリングした場合に判定となるのは--reference形式のみです) - CJK 文字や絵文字のグリフが欠ける
- コードブロックが文字化けする
- Chrome のデフォルトのヘッダー/フッター(このスキルを使わずに回避した場合)
ワークフロー。生成後、納品前に一時停止して、次の手順を行います。
-
ページを確認します。 出力された
Previews:ディレクトリ内の実際の PNG をすべて列挙してReadし、Markdown ソースと照合します。意図と異なるレンダリングがあれば、Markdown を修正して(疑似リストではなく実際のリスト-を使う、空行を挿入する、表を組み直すなど)、再実行します。このスクリプトは、標準外の Markdown を暗黙に「修正」しません。そうすると、ソースに誤りがあることを示す兆候が隠れ、他のプロセッサー(Obsidian、GitHub、VS Code プレビュー)でも同じ Markdown が誤ってレンダリングされるためです。 -
ドキュメントに表がひとつでもある場合は、PDF に対して
scripts/check_table_borders.pyを実行します。 レンダリングに Chrome を使用した場合は、判定となる唯一の形式である--reference形式を実行します(どちらの形式についても、上記「Chrome clips, it does not merely overflow」を参照してください)。手順 1 でこれを代用することはできません。罫線が欠けていてもスタイル上の選択に見え、手順 1 のチェックリストではテキストの欠落に注目しやすくなりますが、実際にはテキストは欠けません。手順 1 だけを根拠に、表を含む PDF を納品しないでください。 -
PDF にコマンドが含まれる場合は、実行可能なコマンドが正しくコピーされるか確認します。 作成者は、最終 PDF からコマンドブロックを抽出し(例:
pdftotext -layoutを使用)、文章とページフッターを除外しつつ、改行を保持します。トークン、引用符、パイプ、リダイレクト、継続の意味について、Markdown ソースと比較してください。コードが目に見えることは、忠実にコピーされている証拠にはなりません。折り返された行が別のコマンドになったり、|が新しいシェル文の先頭に置かれたりすることがあります。シェルの場合、sh -nで確認できるのは構文のみであり、引数やリダイレクト先が変更されていないことは証明できません。実行時の意味を確認する必要がある場合は、無害な引数キャプチャ用スタブを使用してください。折り返しによってコマンドが変わる場合は、ソースの安全な位置に明示的な継続を手動で追加し、再ビルドしてから最終 PDF を再度抽出します。バックスラッシュを自動的に挿入したり、すべてのコードブロックをnowrapに変更したり、コピーを試すためだけに実際の診断コマンドを実行したりしないでください。この比較は作成者が実行するものであり、スクリプトのチェックリストでは強制されません。
これらの確認ですべての問題を検出できるわけではありません。PNG の生成には pdftoppm を使用しますが、macOS Preview や Adobe Reader では空白として表示される CID Type 0C フォントもレンダリングします。そのため、フォント埋め込みの不具合が手順 1 では問題なく見えても、受信者の環境では読めないことがあります。この問題は、実際に目視するのではなく、テーマのルーティングで対処します(「Backends」を参照)。手動で実行するチェックは、トラブルシューティングの「Inline code with mixed CJK + ASCII」に記載されています。
バッチ実行/非対話実行では、--no-preview を指定して無効化します。
python scripts/md_to_pdf.py input.md output.pdf --no-preview
必要条件:pdftoppm(macOS では brew install poppler)。インストールされていない場合、スクリプトはヒントをログに出力してプレビュー生成をスキップしますが、PDF は引き続き生成します。
CJK のタイポグラフィ(デフォルトの動作)
スクリプトは、ユーザーの Markdown ソースやテーマ CSS ファイルを変更せずに、CJK に対応した処理を自動的に 2 層適用します。
レイヤー 1:CSS パッチ(自動挿入、約 80% のケースを修正)
_load_theme() は、読み込んだテーマ CSS に CJK タイポグラフィ用の CSS パッチを追加します。このパッチの内容は次のとおりです。
table { table-layout: fixed; width: 100% }— 列幅を均等にすることで、隣接する列の内容が 5 倍多い場合に、weasyprint の自動レイアウトで一方の列の幅が約 10% まで縮むのを防ぎます。cjk-autoテーマではこれをtable-layout: auto !importantで上書きし、セルにwhite-space: normal !importantを設定します。これにより、内容に応じた幅を割り当てつつ、長いセルのテキストを折り返せます。td, th { word-break: keep-all; overflow-wrap: normal; line-break: strict }— CJK 文字を分割しないようにします。overflow-wrap: normal(break-wordではない)に意図的に設定することで、トークンの途中で折り返すのではなく、内容がわずかにはみ出すことを許容しています。この判断の根拠は、md_to_pdf.pyのCJK typography patch (auto-injectedコメントブロックに記載されており、scripts/tests/test_cjk_tables.pyで固定されています。th { white-space: nowrap }— 短いヘッダーを 1 行に収め、列幅を予測しやすくします。
これにより、CJK 文字の間でセルの内容が強制的に折り返され、1 文字だけの行ができるという、最もよくあるアンチパターンをユーザーのソースに手を加えずに解消できます。ディスク上のユーザーのテーマ CSS ファイルは変更されません。
レイヤー 2:タイポグラフィのリント(レンダリング後に検出し、残りを捕捉)
PDF の生成後、スクリプトは各ページで pdftotext -layout を実行し、「中文文案排版指北」(中国語組版スタイルガイド)に基づいて、既知の CJK アンチパターンを検出します。
- CJK 文字が 1 文字だけの行(レイヤー 1 適用後もセルの幅が狭すぎる場合)
- 行末が
(で、次の行に内容が続く(括弧の対が分断されている) - 行頭が
)(前の行にある括弧の対から分断されている) - 考えの途中に使われる句読点
、,;:で終わる短い行
検出結果は、ページ番号と行番号とともに stderr に出力されます。これらはエラーではなく警告であり、PDF は引き続き生成されます。作成者は検出結果を確認し、次のいずれかを選択します。
- 許容する(例:長いドキュメントに孤立した文字が 1 つある程度なら問題ない場合があります)
- 問題のあるセルの内容を短くし、列幅に収める
- 構成を変更する(例:長い内容を表の下の段落に移す)
すべてを暗黙に自動修正しない理由
レイヤー 2 では、意図的に Markdown を変更しません。CLAUDE.md の「禁止隐式行为」ルールに従うためです。標準外の Markdown(例:疑似リストを実際のリストに展開する)を暗黙に書き換えると、ソースに誤りがあるという兆候が隠れ、同じ Markdown がほかのプロセッサーで正しくレンダリングされない原因になります。レイヤー 1 は、Markdown ソース自体ではなく、すでに標準に準拠した Markdown(CJK の場合に weasyprint が不完全にレンダリングすることがある標準的な表)のレンダリング動作にパッチを適用するため、許容されます。
既知の制限事項
1 つのセルの内容が使用可能な列幅をわずかに超える場合(例:幅を均等に分けた後、9 文字分のセルに CJK 文字が 10 文字入る場合)、keep-all が指定されていても、weasyprint は強制改行に切り替えます。レイヤー 1 ではこれを修正できません。レイヤー 2 で検出されるため、作成者はセルの内容を短くするか、構成を変更するよう促されます。