ストリーミングモード
--streamingを付けると、HTMLをチャンク単位で読みながら、ページが確定したそばからPDFを書き出して、そのページ分のメモリを手放します。
sghtmltopdf big.html -o big.pdf --streaming
数万要素規模のHTMLで効果があります(メモリと処理時間)。 その代わり、文書全体を見ないと決まらないものが使えなくなります。
使えないもの(エラーになる)
指定するとexit code 3で終了します。
| 使えないもの | 理由 |
|---|---|
counter(pages) / ヘッダー・フッターの[topage] | 総ページ数は1パスでは決まらない |
--toc | 同上(目次には本文のページ番号が要る) |
<html>/<body>自身の背景色・枠線 | 複数ページにまたがる装飾の再現が必要になる |
<body>より後の<style> / <link rel="stylesheet"> | 既に書き出したページへ遡って適用できない |
警告のうえ続行するもの
結果が変わるため、黙って進めずに警告を出します。
| 制約 | 挙動 |
|---|---|
font-family名からのシステムフォント自動探索 | 既定のフォントで描画する。--font/--gothic-font/--serif-font/--mono-fontか@font-faceで明示すれば解決できる |
| 文字を描画できるフォントのシステム探索 | 文書全体を先読みできないため行わない。フォントを1つも指定しなかった場合だけ、CJK用のフォントを1本先回りで読み込む(フォント)。描画できない文字が出たら文字ごとに警告する |
:last-child・:nth-last-child・:last-of-type・:nth-last-of-type・:only-child・:only-of-type・:empty | 常に非マッチになる(親の子リストが完結するまで判定できないため) |
後方参照のセレクタが効かないのは、要素を読んだ時点でスタイルを確定させる必要があるためです。
「最後の行だけ罫線を消す」といった指定は、クラスを振って.last { … }のように書き換えてください。
使えるもの
以下はストリーミングモードでも通常どおり使えます。
--cover(表紙)- ヘッダー/フッター(
[page]まで。[topage]は不可) - ページ設定・PDFメタデータ
--grayscale/--dpi/--zoom- コンテンツ挙動のオプション(
--no-images等) break-before/break-after/break-inside・orphans/widowsなどのページ分割
メモリと処理時間
同じHTMLを両モードで変換した実測値です。 各セルは「ピークメモリ / 処理時間」を表します。
| 要素数 | HTMLサイズ | 通常モード | --streaming |
|---|---|---|---|
| 1,000 | 46KB | 11MB / 0.02秒 | 8MB / 0.02秒 |
| 5,000 | 233KB | 26MB / 0.08秒 | 10MB / 0.10秒 |
| 20,000 | 946KB | 81MB / 0.35秒 | 15MB / 0.38秒 |
| 60,000 | 2.8MB | 228MB / 1.05秒 | 28MB / 1.07秒 |
通常モードのピークメモリは文書サイズにほぼ比例して増えますが、--streamingではほとんど増えません。
60,000要素では約8分の1になります。
--streamingでもわずかに増えるのは、PDFの相互参照表(全オブジェクトの位置)と使用グリフの集計を最後まで保持するためです。
処理時間は両モードでほぼ変わりません。
--streamingのほうがわずかに遅く出ますが、差はどの規模でも0.03秒以内で、文書が大きくなるほど相対差は縮まります(60,000要素では2%)。
メモリを大きく減らす代わりに時間を損なう、という関係にはなっていません。
測定条件は次のとおりです。
- sghtmltopdf 0.1.0のreleaseビルド、Intel Core Ultra 7 258V / メモリ16GB / WSL2(Linux 5.15)
- 高さ60pxの
<p>を要素数ぶん並べただけのHTML。フォントは--fontで明示 - ピークメモリはプロセスの最大常駐セットサイズ(RSS)。2回実行して良いほうの値
いつ使うか
- 数千ページ規模の帳票を1本のHTMLから出す
- メモリの上限が厳しい環境(コンテナのメモリ制限、サーバレス)で動かす
逆に、数十ページの請求書のような文書では通常モードで十分です。 制約を負ってまで使うものではありません。
サーバモードでは、クエリにstreamingを付けると同じモードになります。
?stream=1(chunkedで返す)と組み合わせると、入力・レンダリング・出力のすべてが逐次処理になります。
curl --data-binary @big.html 'http://127.0.0.1:8080/pdf?stream=1&streaming' -o out.pdf
ストリーミング時の処理の流れ
flowchart TD
A["HTMLチャンク"] --> B["ストリーミングパーサ"]
B --> C["スタイルカスケード"]
C --> D["レイアウト + ページ分割"]
D --> E["PDFライター"]
E --> F["PDF出力"]
D -. "ページが確定するたび" .-> E
E -. "メモリを解放して読み進む" .-> B
ページの区切りが確定した時点でPDFを書き出し、そのページ分のメモリを解放して先へ読み進めることで、大きな文書でも消費メモリを増やさず処理を可能にしています。