From fe149bc525c41da4fc97ec42b902cdeffbe6cded Mon Sep 17 00:00:00 2001
From: DIO0550 <47467473+DIO0550@users.noreply.github.com>
Date: Thu, 12 Nov 2026 22:19:46 +0900
Subject: [PATCH] feat(core): provide scoped resources to content stream
handlers
---
docs/guides/README.md | 2 +-
docs/guides/ext-gstate.html | 43 +--
docs/guides/patterns-shadings.html | 24 +-
docs/guides/resources.html | 112 +++----
.../src/content-stream/ext-gstate/index.ts | 31 +-
.../graphics-state/text-font/index.ts | 8 +-
.../src/content-stream/interpreter/index.ts | 21 +-
.../content-stream/operator-registry/index.ts | 3 +
.../operators/color/selection/index.ts | 86 ++++--
.../operators/graphics-state/gs/index.ts | 24 +-
.../operators/shading/sh/index.ts | 22 +-
.../content-stream/operators/text/tf/index.ts | 33 ++-
.../operators/xobject/do/index.ts | 42 ++-
.../content-stream/paint-resources/index.ts | 38 +--
...ntent-stream-resources.integration.test.ts | 280 ++++++++++++++++++
.../src/content-stream/resources/index.ts | 119 ++++++++
packages/core/src/document/resources/index.ts | 50 +++-
.../src/document/resources/xobject/index.ts | 14 +-
packages/core/src/index.ts | 6 +-
packages/core/src/pdf/errors/warning/index.ts | 2 +
20 files changed, 744 insertions(+), 216 deletions(-)
create mode 100644 packages/core/src/content-stream/resources/__tests__/content-stream-resources.integration.test.ts
create mode 100644 packages/core/src/content-stream/resources/index.ts
diff --git a/docs/guides/README.md b/docs/guides/README.md
index b7f46666f..2b8bde6d4 100644
--- a/docs/guides/README.md
+++ b/docs/guides/README.md
@@ -11,7 +11,7 @@ PDF機能の実装に合わせて、仕様の読み方と実装の振る舞い
| [ドキュメントカタログ](document-catalog.html) | Root・Catalog・Pagesの関係、必須キー、バージョンの優先関係、表示設定、未解釈エントリの保持、位置付きエラー | [Issue #635](https://github.com/DIO0550/pdfmod/issues/635) / [PR #736](https://github.com/DIO0550/pdfmod/pull/736) の作成時点(`6c654ce`) |
| [ドキュメント情報辞書](document-info.html) | Info参照、6つのテキスト項目・2つの日付・Trapped、項目ごとの成功/不在/失敗、未知キー保持、間接参照とNull | [Issue #661](https://github.com/DIO0550/pdfmod/issues/661) / `rust/crates/core/src/document_info.rs` / `rust/crates/core/src/resolver/document_info.rs` |
| [ページツリーと属性の継承](page-tree.html) | Kids の文書順走査、Count・Type の検証、循環と共有の区別、型付きページ位置、Parent チェーンによる4属性の継承、無効値の回復、間接座標参照、回転の正規化と既定値 | [Issue #658](https://github.com/DIO0550/pdfmod/issues/658) / [Issue #659](https://github.com/DIO0550/pdfmod/issues/659) / [Issue #660](https://github.com/DIO0550/pdfmod/issues/660) / `rust/crates/core/src/page_tree.rs` / `rust/crates/core/src/resolver/page_attributes.rs` |
-| [リソース辞書と属性継承](resources.html) | /Resourcesの継承・間接参照・ProcSet、Tfからのフォント辞書、Doからの画像・フォーム、cs / CSの色空間選択とsc / scnの成分数検証 | [Issue #668](https://github.com/DIO0550/pdfmod/issues/668) / [Issue #666](https://github.com/DIO0550/pdfmod/issues/666) / [Issue #665](https://github.com/DIO0550/pdfmod/issues/665) / [Issue #648](https://github.com/DIO0550/pdfmod/issues/648) / `packages/core/src/document/resources/` / `packages/core/src/content-stream/color-space-resources/` |
+| [リソース辞書と属性継承](resources.html) | /Resourcesの継承・間接参照・ProcSet、Tf・Do・gsへの実行スコープの供給、参照失敗からの回復、cs / CSの色空間選択とsc / scnの成分数検証 | [Issue #678](https://github.com/DIO0550/pdfmod/issues/678) / [Issue #668](https://github.com/DIO0550/pdfmod/issues/668) / [Issue #666](https://github.com/DIO0550/pdfmod/issues/666) / [Issue #665](https://github.com/DIO0550/pdfmod/issues/665) / [Issue #648](https://github.com/DIO0550/pdfmod/issues/648) / `packages/core/src/document/resources/` / `packages/core/src/content-stream/resources/` |
| [ページ矩形と境界ボックス](page-boxes.html) | 対角の正規化、5種のボックスの既定値、MediaBoxとの交差、不正入力と未解決参照の扱い | [Issue #636](https://github.com/DIO0550/pdfmod/issues/636) 実装版 |
| [テキスト文字列](text-strings.html) | PDFDocEncoding・UTF-16BE・UTF-8、BOM判定、未割当バイト、サロゲート、不正入力の拒否/置換、バイト列との型境界 | [Issue #637](https://github.com/DIO0550/pdfmod/issues/637) / `rust/crates/core/src/text_string.rs` |
| [日付文字列](date-format.html) | ISO 32000-1 / ISO 32000-2 構文、D: 接頭辞、階層的省略、タイムゾーン形式(' 区切り・Z)、うるう年・カレンダー厳格検証 | [Issue #638](https://github.com/DIO0550/pdfmod/issues/638) / `rust/crates/core/src/date.rs` |
diff --git a/docs/guides/ext-gstate.html b/docs/guides/ext-gstate.html
index 8c72cf09d..7ab183d95 100644
--- a/docs/guides/ext-gstate.html
+++ b/docs/guides/ext-gstate.html
@@ -188,10 +188,17 @@
03 パラメータと型
| キー | PDFの値 | 現在の状態での役割 |
/LW | 数値 | 線幅。w と同じ値を更新 |
/LC | 整数 0 / 1 / 2 | 線端。順に平端・丸端・突出した四角端 |
/LJ | 整数 0 / 1 / 2 | 線接合。順にマイター・丸・面取り |
/ML | 数値 | マイター限界。M と同じ値を更新 |
/D | [[2 3] 1] | 描画長2・空白長3を繰り返し、開始位置を1ずらす |
/CA /ca | 0〜1 | 輪郭/塗りの不透明度 |
/BM | 名前 または 名前の配列 | 背景と描画色を混ぜる方式 |
/SMask | 辞書 または /None | 位置ごとの透明度を与えるマスク、または解除 |
/Font | [font_ref size] | フォント辞書とサイズ |
ブレンドモードは、たとえば /Normal(通常)、/Multiply(乗算)、/Screen(スクリーン)です。配列 [/Future /Multiply /Screen] は優先順位を表し、最初に対応できるモードを使います。この実装では Multiply が選ばれます。対応できる名前のない配列は Normal になります。
状態の保持と、実際の描画は別この実装は透明度・ブレンドモード・ソフトマスクを状態として保持します。ピクセルの合成、透明グループの描画、ソフトマスク辞書の内部検証は実装範囲外です。線幅・マイター・破線の値域検証も既存の専用オペレータと同様に今後の範囲です。
-04 フォント指定の違い
命令で指定する場合
/F1 12 Tf
F1 はリソースの /Font 内を引くための名前です。テキスト状態は名前指定を保持します。
拡張状態で指定する場合
/Font [6 0 R 12]
配列の先頭はフォント辞書への参照です。/F1 ではありません。参照を解決して、辞書とサイズ12を保持します。
-TypeScriptでは、名前指定と辞書指定を判別可能なユニオンとして表します。type を見れば、次に読める値が name か dictionary かが決まります。
type TextFont =
- | { readonly type: "name"; readonly name: string }
- | { readonly type: "dictionary"; readonly dictionary: PdfDictionary };
どちらの指定も、同じ TextState.withFont を通ります。gs の後に Tf が来れば名前指定へ置き換わり、以前の辞書と新しい名前が併存することはありません。文字間隔や行送りなどは保持します。既存の fontName は font から求める読み取り用アクセサで、辞書指定では None を返します。
+04 フォント指定の違い
+
+
命令で指定する場合
/F1 12 Tf
F1 はリソースの /Font 内を引くための名前です。テキスト状態に名前・サイズと取得できた辞書を保持します。
+
拡張状態で指定する場合
/Font [6 0 R 12]
配列の先頭はフォント辞書への間接参照です。オブジェクト6を解決し、辞書とサイズ12を保持します。
+
+ TypeScriptの TextFont は、type によって2種類の指定を区別します。
+ type TextFont =
+ | { readonly type: "name"; readonly name: string; readonly dictionary?: PdfDictionary }
+ | { readonly type: "dictionary"; readonly dictionary: PdfDictionary };
+ どちらの指定も TextState.withFont を通り、現在のフォント指定を置き換えます。文字間隔や行送りは引き継ぎます。fontName は名前指定のときにその名前を返し、辞書指定では None を返します。
+
05 保存と復元
q は現在のグラフィックス状態を保存し、Q は直前に保存した状態を復元します。拡張状態も同じ状態に入るため、専用の「拡張状態スタック」は必要ありません。
5 w
q
/GS1 gs
@@ -199,23 +206,25 @@
9 w
Q
Q
| 実行したところ | 現在の線幅 | 塗りの不透明度 |
|---|
最初の 5 w | 5 | 1 |
/GS1 gs | 3 | 0.4 |
9 w | 9 | 0.4 |
内側の Q | 3 | 0.4 |
外側の Q | 5 | 1 |
-06 警告と回復
以下はpdfmodの回復方針です。仕様上不正な値を推奨するものではありません。
| 入力 | 結果 |
|---|
<< /LW 3 /Unknown true /ca .4 >> | 未知キーを警告し、線幅3・不透明度0.4を適用 |
<< /CA 2 /LW 3 >> | 範囲外の不透明度だけを警告して無視。以前の不透明度を保持し、線幅3を適用 |
/Missing gs | 名前の不在を警告。状態を保持して次の命令へ進む |
| 選択した辞書の参照先が構文エラー | 原因のエラーをそのまま返す。未解決警告とは区別 |
| 未使用の名前の参照先が構文エラー | 名前ごとにエラーを保持。別の正常な名前の実行は可能 |
警告には gs 命令のバイト位置が付きます。同じ辞書を再度適用した場合は、その命令位置で再び警告します。後続の専用オペレータで警告が重複することはありません。
-07 実装の使い方
間接参照の解決は非同期、コンテンツストリームの実行は同期です。最初に現在のスコープの拡張状態を解決し、既定のレジストリを組み立てます。
const prepared = await ExtGStateResources.fromResources(
+06 警告と回復
以下は実行スコープを渡したときのpdfmodの回復方針です。
+ | 入力 | 結果 |
+ << /LW 3 /Unknown true /ca .4 >> | 未知キーを警告し、線幅3・不透明度0.4を適用 |
+ << /CA 2 /LW 3 >> | 範囲外の不透明度を警告。以前の不透明度を保持し、線幅3を適用 |
+ /Missing gs | EXT_GSTATE_NOT_FOUND。状態を保持して続行 |
+ | 選択した辞書や内部の参照先が構文エラー | RESOURCES_RESOLVE_FAILED。原因を報告し、状態を保持して続行 |
+ | 未使用の名前の参照先が構文エラー | 取得結果に原因を保持。実行中の警告は追加しない |
+
+ 警告には gs 命令のバイト位置が付きます。同じ辞書を再度使うと、新しい命令位置で警告します。
+
+07 実装の使い方
間接参照を非同期で解決し、コンテンツストリームは準備したスコープを使って同期で実行します。
const scope = await ContentStreamResources.fromResources(
resources, resolveRef,
);
-if (!prepared.ok) {
- return prepared;
-}
-
-const registry = OperatorRegistry.createDefault(prepared.value);
-if (!registry.ok) {
- return registry;
-}
-
return ContentStreamInterpreter.execute({
data: contentBytes,
- registry: registry.value,
-});
Result は成功の値と失敗の理由を分ける型です。ok を確認した後でだけ、成功値 value を使えます。準備時はカテゴリ全体を解決しますが、各名前の参照エラーはその名前を使うまで実行エラーになりません。ページやフォームのスコープが変わるときは、そのスコープ用に準備し直してください。
registerGraphicsStateOperators(registry, prepared.value) で、グラフィックス関係だけを登録する方法もあります。/OP、/RI など今回扱わない辞書キーは未対応警告になります。/Type は描画パラメータではないため読み飛ばします。
+ resources: scope,
+});
+ 各カテゴリ・名前の取得結果はスコープに保持されます。ページやフォームの辞書が変わるときは、その辞書を使って準備し直します。/OP や /RI など未対応の辞書キーは個別に警告します。/Type は辞書の種別として読み飛ばします。
+