HTMLデッキ → ネイティブ編集可能PPTX
このスキルが強制するルール:PowerPointにスライド画像を決して貼り付けないこと。 すべての矩形、バー、ドット、ピル、線、テキストブロックは、ユーザーが移動、色変更、再入力できる実際のPowerPointオブジェクトでなければなりません。コンバーターは、CSSを再推測する代わりにレンダリング済みページ(Playwright + ブラウザ自身の計算レイアウト)を読み取り、python-pptxのシェイプを全く同じ座標に書き出します。
実行方法
python scripts/html_to_pptx.py <deck.html> <out.pptx> [all | 1,3,5]
必要:pip install -r requirements.txt および playwright install chromium。
スライドは .slide(1280x720 px)にマッチする要素です。.html ファイルは、画像をインライン(data: URI)で含むか、または同じディレクトリに配置してください。examples/sample-deck.html は最初に試す小さなデッキです。
キャプチャ時のページの状態(スクリプトが自動実行):<html> から anim クラスを削除、CSSアニメーション/トランジションをオフ、window beforeprint を発火(デッキは印刷時にチャートを表示することが多い)、印刷メディア、ズーム1、.mode または .jump クラスの要素を非表示(テーマ切り替え、スライドナビゲーター)。ライト/ダーク切り替えがあるデッキは表示中のテーマで変換されます——それがユーザーの希望するテーマであることを確認してください。
変換対応表
| HTML内 | PPTX内 |
|---|---|
| 背景・ボーダー・radiusを持つdiv/カード | 矩形/角丸矩形(radius → コーナー調整) |
| グラデーション背景(線形) | ネイティブグラデーション塗りつぶし |
| 片側ボーダー(アクセントストライプ) | 細い矩形;ヘアライン/破線ボーダー → 実線 |
border-radius:999px のピル、円 | ピル/楕円 |
テキスト(任意のブロック、太字/色付きラン、<br>、リンクを含む) | ブロックごとに1テキストボックス、ランは維持、ハイパーリンクも維持 |
<li> | 実際のPowerPoint箇条書き(マーカー色も維持) |
| インラインSVG rect/circle/line/polyline/path/text | 矩形、楕円、コネクタ(矢印先端維持)、自由形状(実ベジェ曲線)、テキストボックス |
SVG <textPath>(曲線上のテキスト) | PowerPoint曲線テキスト(アーチ上/下ワープ) |
CSS clip-path: polygon(...)(シェブロン、斜めカット) | 自由形状;内部の写真はポリゴンに切り抜き |
<img>、写真、アイコンPNG | ピクチャ(ビットマップであることは許容) |
text-transform、letter-spacing、取り消し線、opacity/alpha | ラン/塗りつぶしに適用 |
2ビュートグル(.vtog に data-view=code と data-view=time ボタン) | もう一方のビュー用に2枚目のスライド |
| JSアニメーション | 最終静止状態のみ |
苦労して得たルール(それぞれに時間を費やした——守ること)
- テキスト位置は較正済み。目視で「修正」しないこと。 PowerPoint 100%行間 = 1.2 × フォントサイズ(ArialとArial Black共通)。テキストボックスは
top = rangeTop + (ascent-cap)*fs - lineHeight + K*fsに配置(Arialの場合(ascent-cap, K) = (0.189, 0.95)、Arial Blackの場合(0.385, 1.0))、line_spacing = cssLineHeight / (1.2*fs)。単一行テキストは行間100%、wrap=none、少し余白を持たせて改行しません。 - Windows上のChromeは
font-family: Arialでweight 800/900をARIAL BLACKとしてレンダリングする。 コンバーターはこれをArial Black(太字ではない)にマッピングします。これを守らないと、すべての太い見出しが細く、幅が正しくなくなります。 - インラインブロックの子要素をテキストブロックに吸収しないこと。
display:inlineの子だけが1つのテキストボックスに統合されます。インラインブロック/flexの子は個別に処理します。 - テキストノードはその場で計測する(ノード上のRange)。ノードを一時的な
<span>でラップすると、grid/flexレイアウトが変わります。 - 結果をPowerPointで開いて有効であることを確認すること。 python-pptxはPowerPointが拒否するファイルを平気で書き出します(「PowerPoint でファイルを開けませんでした」)。既知の原因:負の値やNaNのシェイプサイズ(クランプ、NaNポイントを削除)、
clip-path内のcalc()(評価する)。シェイプを半分ずつ削除して開き直すことで原因を特定します。 - カスタムジオメトリを持つピクチャは、フレームではなくパスのバウンディングボックスに収まる。 写真をポリゴンに切り抜くには、ピクチャをポリゴンのバウンディングボックスにサイズ合わせし、残りの部分にソースクロップ(
srcRect)を設定します。 - シェイプをスクリーンショットでキャプチャしない(「変わったものはラスタライズ」)。代わりに、ロゴのSVGパスやクリップパスのポリゴンを自由形状に変換します。
rasterブランチは最終手段です。 - クリップされたゴーストテキストはスキップする(カードで切り取られる大きなぼやけた数字)——PowerPointはテキストボックスをクリップできません。
- PowerPointの
a:lnはspPr内の塗りつぶしの後に配置します;テーマのp:styleを削除するか、シェイプが影を拾わないようにします。 - ユーザーが開いている.pptxを上書きしない——代わりに新しいファイル名で書き出します。
検証(常に——確認せずに出荷しないこと)
PowerPointがインストールされたWindows:
powershell -NoProfile -File scripts/render_pptx.ps1 -pptx <out.pptx> -outdir <png_dir> # PowerPointからPNG出力
python scripts/compare.py <deck.html> <png_dir> 1,2,3 <out.png> # HTML | PPTX 横並び比較
python scripts/compare.py <deck.html> <png_dir> 6:6,7:8 <out.png> # スライド番号が異なる場合 html:pptx
render_pptx.ps1 はユーザーが既にPowerPointを開いている場合はそのまま残します。PowerPointがない場合は、.pptxを他の方法(例:LibreOfficeヘッドレス)でPNGに変換し、s1.png、s2.png…として compare.py を実行します。
比較PNGを確認し、修正し、再ビルドします。実行後の典型的な微調整:PowerPointのテキストがわずかに広い(カーニング)、構造的な問題はありません。
既知の制限(ユーザーに伝えること)
- テキスト配置は ArialおよびArial Black で較正されています。他のフォントでも変換されますが、そのフォントが較正されるまではテキストが数ピクセル上下にずれる可能性があります(PowerPointでキャップハイトと行間を測定してください)。
- 写真、アイコン、ロゴがビットマップで提供された場合は、そのままピクチャとして残ります。テキストはカードの上に個別のボックスとして配置されます(グループ化されません)。
- アニメーション/トグルは1フレームに平坦化されます(トグルの別ビューは追加スライドになります)。
- 放射グラデーションとコーナーごとの半径は近似値です。ボックスシャドウは削除されます。
- スライドサイズは1280x720ピクセル → 13.333 x 7.5インチに固定されます。
クレジット
Impera Business Services によって構築されました。MITライセンス — LICENSE を参照してください。