Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/guides/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ PDF機能の実装に合わせて、仕様の読み方と実装の振る舞い
| [字句トークンと描画命令の境界](token-layers.html) | 字句解析と演算子の再分類、LexicalTokenとContentStreamToken、巻き戻しと画像辞書の型境界、Operator.value、正常例・未知の命令・壊れた画像 | [Issue #308](https://github.com/DIO0550/pdfmod/issues/308) / `packages/core/src/pdf/types/token/index.ts` |
| [暗号化辞書](encryption-dictionary.html) | Standard の6組と方式ごとの型・生成処理、crypt filter と Identity、鍵長の単位正規化、符号なし権限、未対応辞書の保持、間接参照と解析の境界 | [Issue #651](https://github.com/DIO0550/pdfmod/issues/651) / `packages/core/src/pdf/encryption/index.ts` / `rust/crates/core/src/encrypt.rs` |
| [拡張グラフィックス状態](ext-gstate.html) | gs の名前解決、各パラメータ、Font の辞書指定、透明度、q/Q、警告と回復 | [Issue #667](https://github.com/DIO0550/pdfmod/issues/667) / `packages/core/src/content-stream/ext-gstate/index.ts` |
| [パターンとシェーディング](patterns-shadings.html) | PatternType・ShadingType、着色と基底色、scn / SCN / sh、間接参照、Functionの未評価保持、警告と回復 | [Issue #677](https://github.com/DIO0550/pdfmod/issues/677) / `packages/core/src/content-stream/paint-resources/` / `packages/core/src/document/resources/pattern/` / `packages/core/src/document/resources/shading/` |

## 読み方

Expand Down
270 changes: 270 additions & 0 deletions docs/guides/patterns-shadings.html

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions docs/guides/resources.html
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,7 @@ <h3>保存した名前をAPIへ渡す</h3>
<tr><td>Pattern</td><td>0、または基底の成分数</td><td>繰り返し模様。scn / SCN の最後にパターン名を指定。</td></tr>
</tbody></table></div>
<h3>間接参照とPattern</h3>
<p>Pattern / Shadingの種別と主要属性の取得、scn / SCN / shによる名前解決については、<a href="patterns-shadings.html">パターンとシェーディングの解説</a>を参照してください。実行前に <code>PaintResources.fromResources(resources, resolveRef)</code> を呼び、<code>createDefault</code> の第3引数へ渡します。</p>
<p>カテゴリ辞書・各定義・ICCBasedのプロファイルや <code>/N</code>・DeviceNの名前配列・IndexedやPatternの基底は間接参照でも解決します。着色パターンは <code>/Pattern cs /P1 scn</code>、非着色パターンは基底色の成分も必要です。たとえば <code>[/Pattern /DeviceRGB]</code> を選んだ後は <code>0.1 0.2 0.3 /P1 scn</code> と書きます。名前は数値成分数に含めません。</p>
<h3>不一致・未知の定義を読むとき</h3>
<p>RGBを選んだ後の <code>0.1 0.2 sc</code> は成分が1個不足するためエラーです。余分な成分もエラーになります。色空間配列の先頭に未知の族がある場合は、選択位置に <code>COLOR_SPACE_UNSUPPORTED</code> の警告を出し、成分数不明として保持します。その後の数値は型を確認して保存しますが、個数は検証しません。名前が見つからない場合は、直接の指定でも定義内の別名・基底からの参照でも <code>COLOR_SPACE_NOT_FOUND</code> を報告し、以前の成分数で検証を続けません。基底が不明なIndexedでも、添字の成分数は1です。参照先の構文解析エラーは警告へ置き換えず返します。</p>
Expand Down
25 changes: 24 additions & 1 deletion docs/specs/04_resources_graphics_state.md
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ Q % グラフィックスステート復元
- 色空間配列の先頭にある未知の族は `COLOR_SPACE_UNSUPPORTED`、成分数を確定するための値の不正・欠落や循環は `COLOR_SPACE_INVALID` を報告する。不在の名前は、`cs` / `CS` で直接指定した場合も、定義内の別名や基底色空間から参照した場合も `COLOR_SPACE_NOT_FOUND` とする。いずれも選択した命令の位置で報告する。再帰は32段を上限とし、上限でも不明な定義として回復する。
- 不明な色空間は以前の成分数を引き継がず、成分数不明として保持する。その後の色命令は数値型を検証して保持するが、個数の一致は検証しない。Indexed は基底が不明でも添字は1成分である。
- 既知の成分数の過不足は `OPERATOR_OPERAND_COUNT_MISMATCH`、型の不一致は `OPERATOR_OPERAND_TYPE_MISMATCH`。Pattern に `sc` / `SC` を使う場合は `OPERATOR_ILLEGAL_STATE` を返す。
- 色空間と色値は `q` / `Q` の保存・復元対象。ここで扱うのは族・成分数・色値の保持までであり、ICCプロファイル変換、CIEパラメータの数値検証、パレット展開、特色の関数評価、パターンの取得・描画は後段の処理とする。
- 色空間と色値は `q` / `Q` の保存・復元対象。ここで扱うのは族・成分数・色値の保持までであり、ICCプロファイル変換、CIEパラメータの数値検証、パレット展開、特色の関数評価、パターンの描画は後段の処理とする。パターンの取得は次節の描画リソースの準備が担当する。

### 3.5 /Pattern(パターン辞書)

Expand All @@ -241,6 +241,14 @@ Q % グラフィックスステート復元
| タイリングパターン | 1 | 繰り返し描画される小さな図形の定義 |
| シェーディングパターン | 2 | グラデーション等のスムーズな色変化 |

#### 主要属性と名前オペランド

- 種別1はストリームを持ち、`/PaintType`(1=着色、2=非着色)、`/TilingType`(1〜3)、`/BBox`(4数値)、`/XStep`・`/YStep`(有限・0以外)、`/Resources`(辞書)を読む。`/Matrix` は6数値で、省略時は単位行列を使う。未復号のセルストリームを保持し、内部の命令は実行しない。
- 種別2は `/Shading` を解決して次節の型へ変換し、`/Matrix` と任意の `/ExtGState` 辞書を保持する。拡張状態の適用は行わない。
- `scn` / `SCN` の名前オペランドは、現在の色空間が Pattern の場合だけ許可する。名前から現在の `/Pattern` サブ辞書を引く。Pattern以外では数値オペランドとして型・個数を検証し、名前を受理しない。
- 着色タイリングとシェーディングパターンは基底色空間を伴わない Pattern、非着色タイリングは基底を伴う Pattern と組み合わせる。異なる組み合わせは `OPERATOR_ILLEGAL_STATE`。数値成分数は基底の成分数に従い、名前は数えない。
- 名前と数値成分は従来どおり色状態へ保存する。取得した型付き定義は `PaintResources.patterns` で参照できる。実際のパターン描画は行わない。

### 3.6 /Shading(シェーディング辞書)

グラデーション描画を定義する。
Expand All @@ -255,6 +263,21 @@ Q % グラフィックスステート復元
| クーンズパッチメッシュ | 6 | クーンズパッチによるメッシュ |
| テンソル積パッチメッシュ | 7 | テンソル積パッチによるメッシュ |

#### 共通属性と sh

- `/ColorSpace` を名前または配列として取得し、任意の `/Background` を数値配列、`/BBox` を4数値として読む。色空間配列の内部の解釈、背景の成分数やシェーディング固有の必須キー・値域の全面検証は後段が担当する。
- 種別4〜7はメッシュの未復号ストリームを必須とする。種別番号は1〜7に閉じ、未知の番号は元のオブジェクトを持つ Unknown とする。
- `/Function` は元の `PdfValue` のまま保持する。間接参照を解決せず、関数辞書・配列の内部も評価しない。
- `sh` は名前オペランドを正確に1個受け取り、現在の `/Shading` サブ辞書を引く。既定のレジストリにも登録する。色・色空間・パス・クリップを変更せず、この段階では描画しない。

#### pdfmod の描画リソースの準備・回復方針

- 同期実行前に `PaintResources.fromResources(resources, resolveRef)` で Pattern / Shading のカテゴリ・各定義・主要属性を間接参照も含めて解決する。数値配列は各要素も解決する。別のページ・フォームの同名定義へフォールバックしない。
- 解決結果は `OperatorRegistry.createDefault(extGStates, colorSpaces, paints)`、または色命令に限れば `registerColorOperators(registry, colorSpaces, paints)` へ渡す。
- カテゴリの参照解決エラーは準備時に返す。各定義・主要属性の参照エラーは名前別に保持し、実際にその名前を使う `scn` / `SCN` / `sh` で元の原因を返す。参照先が存在せずリゾルバがPDFのnullを返した場合は、解決エラーとせず不正な定義として扱う。
- 不在の名前は `PATTERN_NOT_FOUND` / `SHADING_NOT_FOUND`。未知・欠落・不正な型の種別番号は `PATTERN_UNSUPPORTED` / `SHADING_UNSUPPORTED`、既知種別の主要属性が不正なら `PATTERN_INVALID` / `SHADING_INVALID`。警告は命令の位置で報告し後続命令を処理する。不正なMatrixを単位行列へ置き換える回復はしない。
- 名前不在・Unknownのパターンでも指定名と数値成分は色状態へ保存する。警告による回復はpdfmod独自の方針であり、未知の定義が描画可能であることを意味しない。

### 3.7 /ProcSet(手続きセット配列)

```
Expand Down
30 changes: 30 additions & 0 deletions packages/core/src/content-stream/color-operation/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { PdfPattern } from "../../document/resources/pattern/index";
import type { PdfOperatorIllegalStateError } from "../../pdf/errors/index";
import { none, type Option, some } from "../../utils/option/index";
import type { ValueOf } from "../../utils/types/utility-type";
Expand All @@ -17,6 +18,35 @@ export type ColorOperation = {
};

export const ColorOperation = {
/**
* 非着色パターンと基底色空間の組み合わせを検証する。
* @param operation - 現在の色空間と色指定命令
* @param pattern - 名前から取得したパターン
* @returns 着色方式と基底の有無が一致しなければ不正状態のエラー
*/
patternError(
operation: ColorOperation,
pattern: PdfPattern,
): Option<PdfOperatorIllegalStateError> {
const space = operation.colorSpace;
const hasPatternBase =
typeof space !== "string" &&
space.family === "Pattern" &&
space.base.some;
if (pattern.patternType === "Unknown") {
return none;
}

const requiresBase = pattern.patternType === 1 && pattern.paintType === 2;
return hasPatternBase !== requiresBase
? some({
code: "OPERATOR_ILLEGAL_STATE",
operatorName: operation.operatorName,
message:
"Pattern PaintType does not match the selected base color space",
})
: none;
},
/**
* 色空間に使用できない色指定命令を検出する。
* @param operation - 選択中の色空間と実行する命令
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { assert, expect, test } from "vitest";
import { GraphicsStateStack } from "../../graphics-state/index";
import { tilingPattern } from "../../paint-resources/__tests__/setup-paint";
import { executeColorSpace } from "./setup-color-space";

test.each([
Expand Down Expand Up @@ -44,7 +45,8 @@ test.each([
target,
}) => {
const result = await executeColorSpace({
resources: `<< /ColorSpace << /Pat ${definition} >> >>`,
resources: `<< /ColorSpace << /Pat ${definition} >> /Pattern << /P1 1 0 R >> >>`,
objects: [tilingPattern(components.length > 0 ? "/PaintType 2" : "")],
content: `/Pat ${selection} ${operands} ${operatorName}`,
});
assert(result.ok);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type { Result } from "../../../utils/result/index";
import type { ContentStreamInterpreterResult } from "../../interpreter/index";
import { ContentStreamInterpreter } from "../../interpreter/index";
import { OperatorRegistry } from "../../operator-registry/index";
import { PaintResources } from "../../paint-resources/index";
import { ColorSpaceResources } from "../index";

/**
Expand All @@ -23,7 +24,14 @@ export async function executeColorSpace(source: {
resolveRef,
);
assert(prepared.ok);
const registry = OperatorRegistry.createDefault(undefined, prepared.value);
const paints = await PaintResources.fromResources(resources, resolveRef);
assert(paints.ok);

const registry = OperatorRegistry.createDefault(
undefined,
prepared.value,
paints.value,
);
assert(registry.ok);

return ContentStreamInterpreter.execute({
Expand Down
7 changes: 6 additions & 1 deletion packages/core/src/content-stream/operator-registry/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import { reHandler } from "../operators/path/re/index";
import { strokeHandler } from "../operators/path/stroke/index";
import { vHandler } from "../operators/path/v/index";
import { yHandler } from "../operators/path/y/index";
import { ShOperator } from "../operators/shading/sh/index";
import { apostropheHandler } from "../operators/text/apostrophe/index";
import { btHandler } from "../operators/text/bt/index";
import { etHandler } from "../operators/text/et/index";
Expand All @@ -68,6 +69,7 @@ import { tsHandler } from "../operators/text/ts/index";
import { twHandler } from "../operators/text/tw/index";
import { tzHandler } from "../operators/text/tz/index";
import { doHandler } from "../operators/xobject/do/index";
import { PaintResources } from "../paint-resources/index";

/**
* Content stream operator handler が受け取り、更新後に返す実行コンテキスト。
Expand Down Expand Up @@ -229,15 +231,18 @@ export const OperatorRegistry = {
*
* @param extGStates - 実行対象スコープの解決済み拡張状態
* @param colorSpaces - 実行対象スコープの解決済み色空間
* @param paints - 実行対象スコープの解決済みパターン・シェーディング
* @returns 全ビルトインオペレータ登録済みの OperatorRegistry、または重複登録時の PdfError
*/
createDefault(
extGStates: ExtGStateResources = ExtGStateResources.empty(),
colorSpaces: ColorSpaceResources = ColorSpaceResources.empty(),
paints: PaintResources = PaintResources.empty(),
): Result<OperatorRegistry, PdfError> {
const operators = [
...BUILTIN_OPERATORS,
...ColorSpaceOperators.create(colorSpaces),
...ColorSpaceOperators.create(colorSpaces, paints),
["sh", ShOperator.create(paints)] as const,
];
return operators.reduce<Result<OperatorRegistry, PdfError>>(
(acc, [name, handler]) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { flatMap, ok } from "../../../../utils/result/index";
import { ColorSpaceResources } from "../../../color-space-resources/index";
import type { OperatorHandler } from "../../../operator-registry/index";
import { OperatorRegistry } from "../../../operator-registry/index";
import { PaintResources } from "../../../paint-resources/index";
import { kHandler } from "../cmyk/fill";
import { KHandler } from "../cmyk/stroke";
import { gHandler } from "../gray/fill";
Expand Down Expand Up @@ -36,15 +37,18 @@ const COLOR_OPERATORS: ReadonlyArray<readonly [string, OperatorHandler]> = [
*
* @param registry - 登録元 registry
* @param colorSpaces - 実行対象スコープの解決済み色空間
* @param paints - 実行対象スコープのパターンリソース
* @returns 全 operator が登録された新しい registry、または重複登録時の PdfError
*/
export const registerColorOperators = (
registry: OperatorRegistry,
colorSpaces: ColorSpaceResources = ColorSpaceResources.empty(),
paints: PaintResources = PaintResources.empty(),
): Result<OperatorRegistry, PdfError> =>
[...COLOR_OPERATORS, ...ColorSpaceOperators.create(colorSpaces)].reduce<
Result<OperatorRegistry, PdfError>
>(
[
...COLOR_OPERATORS,
...ColorSpaceOperators.create(colorSpaces, paints),
].reduce<Result<OperatorRegistry, PdfError>>(
(acc, [name, handler]) =>
flatMap(acc, (r) => OperatorRegistry.register(r, name, handler)),
ok(registry),
Expand Down
53 changes: 43 additions & 10 deletions packages/core/src/content-stream/operators/color/selection/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import type {
OperatorHandler,
OperatorHandlerContext,
} from "../../../operator-registry/index";
import { PaintResources } from "../../../paint-resources/index";
import { OperandExtractor } from "../../operand-extractor/index";

type ColorTarget = "fill" | "stroke";
Expand All @@ -26,18 +27,20 @@ export const ColorSpaceOperators = {
/**
* 現在のスコープのリソースを使用するハンドラを作る。
* @param resources - 解決済みの色空間リソース
* @param paints - 解決済みのパターンリソース
* @returns 登録する命令名とハンドラの組
*/
create(
resources: ColorSpaceResources = ColorSpaceResources.empty(),
paints: PaintResources = PaintResources.empty(),
): ReadonlyArray<readonly [string, OperatorHandler]> {
return [
["cs", ColorSpaceOperators.select("fill", resources)],
["CS", ColorSpaceOperators.select("stroke", resources)],
["sc", ColorSpaceOperators.setColor("sc", "fill")],
["SC", ColorSpaceOperators.setColor("SC", "stroke")],
["scn", ColorSpaceOperators.setColor("scn", "fill")],
["SCN", ColorSpaceOperators.setColor("SCN", "stroke")],
["sc", ColorSpaceOperators.setColor("sc", "fill", paints)],
["SC", ColorSpaceOperators.setColor("SC", "stroke", paints)],
["scn", ColorSpaceOperators.setColor("scn", "fill", paints)],
["SCN", ColorSpaceOperators.setColor("SCN", "stroke", paints)],
];
},

Expand Down Expand Up @@ -87,11 +90,13 @@ export const ColorSpaceOperators = {
* 現在の色空間の成分数を検証して色値を更新する。
* @param operatorName - 色指定命令
* @param target - 塗りまたは輪郭
* @param resources - 解決済みのパターンリソース
* @returns 個数・型の不一致を Err にするハンドラ
*/
setColor(
operatorName: ColorOperation["operatorName"],
target: ColorTarget,
resources: PaintResources,
): OperatorHandler {
return (context) => {
const state = GraphicsStateStack.current(context.graphicsStateStack);
Expand Down Expand Up @@ -132,12 +137,40 @@ export const ColorSpaceOperators = {
return values;
}

return ok(
ColorSpaceOperators.update(context, target, {
color: Color.fromComponents(space, values.value, patternName.value),
space,
}),
);
const pattern = patternName.value.some
? (resources.patterns.get(patternName.value.value) ?? ok(none))
: ok(none);
if (!pattern.ok) {
return pattern;
}

const patternError = pattern.value.some
? ColorOperation.patternError(
{ colorSpace: space, operatorName },
pattern.value.value,
)
: none;
if (patternError.some) {
return err(patternError.value);
}

const warnings = pattern.value.some ? pattern.value.value.warnings : [];
const missingPattern = patternName.value.some && !pattern.value.some;
const next = ColorSpaceOperators.update(context, target, {
color: Color.fromComponents(space, values.value, patternName.value),
space,
});
return ok({
...next,
warnings: missingPattern
? [
{
code: "PATTERN_NOT_FOUND",
message: `Pattern resource not found: /${patternName.value.value}`,
},
]
: warnings,
});
};
},

Expand Down
Loading
Loading