@normativepdf/recover 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.ja.md +100 -0
- package/README.md +105 -0
- package/dist/cos.d.ts +97 -0
- package/dist/cos.d.ts.map +1 -0
- package/dist/cos.js +177 -0
- package/dist/cos.js.map +1 -0
- package/dist/document.d.ts +106 -0
- package/dist/document.d.ts.map +1 -0
- package/dist/document.js +334 -0
- package/dist/document.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +66 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -0
- package/dist/xref-walk.d.ts +114 -0
- package/dist/xref-walk.d.ts.map +1 -0
- package/dist/xref-walk.js +383 -0
- package/dist/xref-walk.js.map +1 -0
- package/package.json +70 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 shuji-bonji
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.ja.md
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# @normativepdf/recover
|
|
2
|
+
|
|
3
|
+
相互参照表が ISO 32000-2 §7.5 のとおりでない PDF を、読める範囲まで読む。
|
|
4
|
+
|
|
5
|
+
`startxref` から `/Prev` を順に辿る。辿れなければ 1 つ前の `startxref` を試す。
|
|
6
|
+
それでも節が 1 つも読めなければ、ファイルの中の `N G obj` を数え上げて表を組み直す。
|
|
7
|
+
そして **どこまで読んだかを返り値に入れて返す。**
|
|
8
|
+
|
|
9
|
+
> 🔴 **このパッケージは適合を判定しない。** `DocumentScope` と `ReadingScope` が
|
|
10
|
+
> 答えるのは「どこまで読んだか」だけで、「条文に適合しているか」ではない。
|
|
11
|
+
> 適合の判定は利用する側が行う。
|
|
12
|
+
|
|
13
|
+
## なぜコアと別のパッケージなのか
|
|
14
|
+
|
|
15
|
+
[normativepdf](https://github.com/shuji-bonji/normativepdf) は §7.5 を条文どおりに
|
|
16
|
+
読み、条文に反する文書は受け取らずに例外を投げる。それは正しい。
|
|
17
|
+
ただし監査に持ち込まれる文書は壊れていることが多く、受け取らないだけで終わると、
|
|
18
|
+
その文書に何が書いてあるかを誰も見ないまま終わる。
|
|
19
|
+
|
|
20
|
+
このパッケージはコアの外側にある。コアが「条文に反するので受け取らない」と言い、
|
|
21
|
+
こちらが「受け取らなかった文書をどう読むか」を持つ。読み直す手順には推測が入る
|
|
22
|
+
(1 つ前の `startxref` を試す・巡回を検出する・表を組み直す)ので、
|
|
23
|
+
条文どおりに読むことを 2,907 件の検体で検査しているコアには入れない。
|
|
24
|
+
決めた経緯は [ADR-0010](https://github.com/shuji-bonji/normativepdf/blob/main/docs/adr/0010-recover-package.md)。
|
|
25
|
+
|
|
26
|
+
## インストール
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
npm install @normativepdf/recover
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
依存しているのは `normativepdf` 1 つだけである。
|
|
33
|
+
|
|
34
|
+
## 使い方
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
import { openDocument, toReadingScope } from '@normativepdf/recover';
|
|
38
|
+
|
|
39
|
+
const { doc, scope } = await openDocument(bytes);
|
|
40
|
+
|
|
41
|
+
if (scope.reconstructed) {
|
|
42
|
+
// このとき相互参照表は、ファイルが持っているものではなく、
|
|
43
|
+
// このパッケージが組み直したものである。報告の読み手に伏せてよい事実ではない。
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// 出力に載せる形(COS 辞書を落としたもの)
|
|
47
|
+
const declared = toReadingScope(scope);
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`openDocument` は 2 段になっている。
|
|
51
|
+
|
|
52
|
+
1. まず `parsePdf` に渡す。読めたらその結果をそのまま使う(**手を加えない**)
|
|
53
|
+
2. 断られたら、下の手順で読み直して文書を組み立てる
|
|
54
|
+
|
|
55
|
+
pdf-verify-mcp の検体 2,947 件では、2,930 件が 1 で読める。2 に入るのは残り 17 件
|
|
56
|
+
だけで、その 17 件だけが「ライブラリが読んだ結果」ではなくなる。
|
|
57
|
+
どちらだったかは `scope.recovered` が持っている。
|
|
58
|
+
|
|
59
|
+
## 読み直す手順は 4 つ
|
|
60
|
+
|
|
61
|
+
1. 最後の `startxref` が読めなければ、1 つ前の `startxref` を順に試す
|
|
62
|
+
(試したことは `newestSectionUnreadable` に入れる)
|
|
63
|
+
2. `/Prev` が正の整数でなければ、そこで辿るのをやめる。
|
|
64
|
+
このとき「最後まで辿れた」とは言わない
|
|
65
|
+
3. 巡回している場合と、200 リビジョンを超えた場合は止める
|
|
66
|
+
4. 線形化(Annex F)の 2 つの節は、1 リビジョンとして数える
|
|
67
|
+
|
|
68
|
+
辿るのをやめた理由は `scope.chainStop.kind` に入る。値は normativepdf と同じ 5 つ
|
|
69
|
+
(`complete` / `prev-zero` / `unreadable` / `cyclic` / `malformed`)。
|
|
70
|
+
**「最後まで辿れた」と「これ以上辿れなかった」は別の値になる。**
|
|
71
|
+
|
|
72
|
+
## デバッグ出力
|
|
73
|
+
|
|
74
|
+
既定では何も出力しない。要るなら出力する関数を渡す。
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
await openDocument(bytes, {
|
|
78
|
+
onDebug: (context, message) => process.stderr.write(`[${context}] ${message}\n`),
|
|
79
|
+
});
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
> 🔴 **stdout に書く関数を渡さないこと。** このパッケージを最初に使うのは
|
|
83
|
+
> MCP サーバで、そこでは stdout に書いたバイトが JSON-RPC のメッセージとして
|
|
84
|
+
> 読まれ、パースに失敗する。
|
|
85
|
+
|
|
86
|
+
## 入っているもの
|
|
87
|
+
|
|
88
|
+
| ファイル | 中身 |
|
|
89
|
+
|---|---|
|
|
90
|
+
| `cos.ts` | COS を読むための関数(`asDict` / `asRef` / `tryResolve` / `enumerateObjects` / `decodedBytes` など)。**ここに判定は書かない** |
|
|
91
|
+
| `xref-walk.ts` | `walkXrefChain` と `reconstructXref`、および上の 4 つの手順。`findOrigin` / `readToken` などのバイト操作も |
|
|
92
|
+
| `document.ts` | `openDocument` / `DocumentScope` / `toReadingScope` |
|
|
93
|
+
|
|
94
|
+
pdf-verify-mcp 0.21.1 の `src/services/` から、1,189 行をそのまま移したものである。
|
|
95
|
+
移したときに出力が変わっていないことは、検体 2,950 件 × ツール 7 本 =
|
|
96
|
+
20,650 回の呼び出しで差が 0 件であることを確かめてある。
|
|
97
|
+
|
|
98
|
+
## ライセンス
|
|
99
|
+
|
|
100
|
+
MIT
|
package/README.md
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# @normativepdf/recover
|
|
2
|
+
|
|
3
|
+
Reads a PDF as far as it can when the cross-reference table does not follow
|
|
4
|
+
ISO 32000-2 §7.5.
|
|
5
|
+
|
|
6
|
+
It walks `startxref` → `/Prev`. When a link cannot be followed it falls back to
|
|
7
|
+
an earlier `startxref`. When no section can be read at all it scans the file for
|
|
8
|
+
`N G obj` and rebuilds the table. **Whatever it does, it returns how far it got.**
|
|
9
|
+
|
|
10
|
+
> 🔴 **This package does not judge conformance.** `DocumentScope` and
|
|
11
|
+
> `ReadingScope` answer "how much was read", not "does this conform". Conformance
|
|
12
|
+
> is the caller's decision.
|
|
13
|
+
|
|
14
|
+
## Why this is not part of the core
|
|
15
|
+
|
|
16
|
+
[normativepdf](https://github.com/shuji-bonji/normativepdf) reads §7.5 as
|
|
17
|
+
written and throws on a document that violates it. That is the right behaviour
|
|
18
|
+
for a library. But documents that arrive for auditing are usually the damaged
|
|
19
|
+
ones, and refusing them means nobody ever sees what is inside.
|
|
20
|
+
|
|
21
|
+
This package sits outside the core. The core says "this violates the clause, so
|
|
22
|
+
I will not accept it"; this package decides what to do with the documents it
|
|
23
|
+
refused. Re-reading involves guesswork — trying an earlier `startxref`,
|
|
24
|
+
detecting cycles, rebuilding the table — so it does not belong in a core whose
|
|
25
|
+
2,907-specimen gate asserts that it reads exactly what the clause says.
|
|
26
|
+
The reasoning is in
|
|
27
|
+
[ADR-0010](https://github.com/shuji-bonji/normativepdf/blob/main/docs/adr/0010-recover-package.md).
|
|
28
|
+
|
|
29
|
+
## Install
|
|
30
|
+
|
|
31
|
+
```sh
|
|
32
|
+
npm install @normativepdf/recover
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
`normativepdf` is the only dependency.
|
|
36
|
+
|
|
37
|
+
## Usage
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
import { openDocument, toReadingScope } from '@normativepdf/recover';
|
|
41
|
+
|
|
42
|
+
const { doc, scope } = await openDocument(bytes);
|
|
43
|
+
|
|
44
|
+
if (scope.reconstructed) {
|
|
45
|
+
// The cross-reference table here was rebuilt by this package. It is not the
|
|
46
|
+
// one the file carries, and a reader of the report needs to be told so.
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// The shape meant for output (COS dictionaries dropped)
|
|
50
|
+
const declared = toReadingScope(scope);
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
`openDocument` works in two steps.
|
|
54
|
+
|
|
55
|
+
1. Hand the bytes to `parsePdf`. If that succeeds, use the result **unchanged**.
|
|
56
|
+
2. If it refuses, re-read using the recovery steps below and assemble a document.
|
|
57
|
+
|
|
58
|
+
Across pdf-verify-mcp's 2,947 specimens, 2,930 succeed at step 1. Only the
|
|
59
|
+
remaining 17 reach step 2, and only those 17 stop being "what the library read".
|
|
60
|
+
`scope.recovered` records which path a document took.
|
|
61
|
+
|
|
62
|
+
## The four recovery steps
|
|
63
|
+
|
|
64
|
+
1. If the last `startxref` is unreadable, try earlier ones in turn
|
|
65
|
+
(recorded in `newestSectionUnreadable`).
|
|
66
|
+
2. If `/Prev` is not a positive integer, stop following it — and do not claim
|
|
67
|
+
the chain was walked to the end.
|
|
68
|
+
3. Stop on a cycle, and at 200 revisions.
|
|
69
|
+
4. Fold the two sections of a linearised file (Annex F) into one revision.
|
|
70
|
+
|
|
71
|
+
Why the walk stopped is in `scope.chainStop.kind`, using the same five values as
|
|
72
|
+
normativepdf (`complete` / `prev-zero` / `unreadable` / `cyclic` / `malformed`).
|
|
73
|
+
**"Reached the end" and "could not go further" are different values.**
|
|
74
|
+
|
|
75
|
+
## Debug output
|
|
76
|
+
|
|
77
|
+
Nothing is written anywhere by default. Pass a function if you want output.
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
await openDocument(bytes, {
|
|
81
|
+
onDebug: (context, message) => process.stderr.write(`[${context}] ${message}\n`),
|
|
82
|
+
});
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
> 🔴 **Do not pass a function that writes to stdout.** The first consumer of this
|
|
86
|
+
> package is an MCP server, where bytes on stdout are read as JSON-RPC messages
|
|
87
|
+
> and fail to parse.
|
|
88
|
+
|
|
89
|
+
## What is in it
|
|
90
|
+
|
|
91
|
+
| File | Contents |
|
|
92
|
+
|---|---|
|
|
93
|
+
| `cos.ts` | Functions for reading COS (`asDict`, `asRef`, `tryResolve`, `enumerateObjects`, `decodedBytes`, …). **No judgement is written here** |
|
|
94
|
+
| `xref-walk.ts` | `walkXrefChain` and `reconstructXref`, plus the four steps above. Also byte-level helpers such as `findOrigin` and `readToken` |
|
|
95
|
+
| `document.ts` | `openDocument` / `DocumentScope` / `toReadingScope` |
|
|
96
|
+
|
|
97
|
+
These 1,189 lines were moved unchanged out of pdf-verify-mcp 0.21.1's
|
|
98
|
+
`src/services/`. That the move changed no output was checked over 2,950
|
|
99
|
+
specimens × 7 tools = 20,650 calls, with zero differences.
|
|
100
|
+
|
|
101
|
+
日本語版は [README.ja.md](./README.ja.md)。
|
|
102
|
+
|
|
103
|
+
## License
|
|
104
|
+
|
|
105
|
+
MIT
|
package/dist/cos.d.ts
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* COS の読み口。**ここに判定は書かない。**
|
|
3
|
+
*
|
|
4
|
+
* pdf-lib の `instanceof PDFDict` / `context.lookup` に当たるものを 1 か所に集める。
|
|
5
|
+
* 6 ファイルに散らすと同じ変換が 6 通りになり、そのうち 1 つだけ条文の読み方が違う、
|
|
6
|
+
* という形で壊れる(B1 = pdf-constraints の撤去で実測。`src/facts/cos.ts` と同じ位置づけ)。
|
|
7
|
+
*
|
|
8
|
+
* 🔴 **「読めなかった」と「そこに無い」を分ける。** 未定義の間接参照は null と等価で
|
|
9
|
+
* (R-7.3.10-13)、それは**観測できた事実**である。条文に反していて復号できないストリームは
|
|
10
|
+
* 観測できていない。2 つを同じ `null` に畳むと、「フォントが埋め込まれていない」と
|
|
11
|
+
* 「フォントを読めなかった」が同じ顔になる(B1 で一度そうして、唯一 fail していた検体を消した)。
|
|
12
|
+
*/
|
|
13
|
+
import { type CosArray, type CosDict, type CosObject, type CosRef, type CosStream, type PdfDocument } from 'normativepdf';
|
|
14
|
+
export declare function asDict(value: CosObject | undefined | null): CosDict | null;
|
|
15
|
+
export declare function asStream(value: CosObject | undefined | null): CosStream | null;
|
|
16
|
+
export declare function asArray(value: CosObject | undefined | null): CosArray | null;
|
|
17
|
+
export declare function asRef(value: CosObject | undefined | null): CosRef | null;
|
|
18
|
+
/**
|
|
19
|
+
* ストリームの辞書。**ストリームは辞書ではない。**
|
|
20
|
+
* pdf-lib の `enumerateIndirectObjects` を `instanceof PDFDict` で絞ると
|
|
21
|
+
* `PDFRawStream` は落ちる —— その範囲を変えると、いままで見ていなかった辞書が
|
|
22
|
+
* 規則の対象に入って判定が動く。範囲を変えるなら別の変更として測ること。
|
|
23
|
+
*/
|
|
24
|
+
export declare function dictOfStream(value: CosObject | undefined | null): CosDict | null;
|
|
25
|
+
/** 名前オブジェクトの値(`#xx` の解決と UTF-8 復号は lexer が済ませている・R-7.3.5-13)。 */
|
|
26
|
+
export declare function nameOf(value: CosObject | undefined | null): string | null;
|
|
27
|
+
/** 数値(整数と実数のどちらでも受ける)。 */
|
|
28
|
+
export declare function numberOf(value: CosObject | undefined | null): number | null;
|
|
29
|
+
/** 整数だけを受ける(R-7.3.3-6「実数が来てはならない」を区別できるようにしておく)。 */
|
|
30
|
+
export declare function integerOf(value: CosObject | undefined | null): number | null;
|
|
31
|
+
export declare function boolOf(value: CosObject | undefined | null): boolean | null;
|
|
32
|
+
/** 文字列のバイト列そのまま(復号は §7.9.2 の話で、ここではしない)。 */
|
|
33
|
+
export declare function bytesOf(value: CosObject | undefined | null): Uint8Array | null;
|
|
34
|
+
/**
|
|
35
|
+
* テキスト文字列(§7.9.2)。PDFDocEncoding / UTF-16BE / UTF-8(PDF 2.0)を
|
|
36
|
+
* 見分けて復号し、言語エスケープ列を落とす。
|
|
37
|
+
* pdf-lib 1.x は `R-7.9.2.2.1-4`(UTF-8 のバイト順マーク)を実装しておらず、
|
|
38
|
+
* 適合している文書に「文法に合わない」と誤報していた。
|
|
39
|
+
*/
|
|
40
|
+
export declare function textOf(value: CosObject | undefined | null): string | null;
|
|
41
|
+
/** 直接値を引く(`null` の値は「無い」と等価に畳む・R-7.3.7-7)。参照は解決しない。 */
|
|
42
|
+
export declare function get(dict: CosDict | null, key: string): CosObject | undefined;
|
|
43
|
+
/** 鍵があるか。**値が `null` でも「ある」と数える**(`get` とはここが違う)。 */
|
|
44
|
+
export declare function has(dict: CosDict | null, key: string): boolean;
|
|
45
|
+
/**
|
|
46
|
+
* 引いて解決する。**読めたかどうかを分けて返す。**
|
|
47
|
+
*
|
|
48
|
+
* - `{ value: X, unreadable: false }` —— 読めた(`null` は「未定義の間接参照」= 観測できた事実)
|
|
49
|
+
* - `{ value: null, unreadable: true }` —— **観測できなかった**。オブジェクトストリームが
|
|
50
|
+
* 条文に反して復号できない、などがここに来る。判定に食わせてはいけない
|
|
51
|
+
*/
|
|
52
|
+
export interface Lookup {
|
|
53
|
+
value: CosObject | null;
|
|
54
|
+
unreadable: boolean;
|
|
55
|
+
}
|
|
56
|
+
export declare function tryResolve(doc: PdfDocument, value: CosObject | undefined): Promise<Lookup>;
|
|
57
|
+
/** 辞書の鍵を引いて解決する。 */
|
|
58
|
+
export declare function tryGet(doc: PdfDocument, dict: CosDict | null, key: string): Promise<Lookup>;
|
|
59
|
+
/** 読めなかったことを捨ててよい場所だけで使う短縮形(`unreadable` は `null` に畳まれる)。 */
|
|
60
|
+
export declare function resolved(doc: PdfDocument, value: CosObject | undefined): Promise<CosObject | null>;
|
|
61
|
+
export interface IndirectEntry {
|
|
62
|
+
objectNumber: number;
|
|
63
|
+
generation: number;
|
|
64
|
+
object: CosObject;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* 相互参照表に載っている間接オブジェクトを、番号順に読む。
|
|
68
|
+
* 読めなかったものは**飛ばさずに数える**(`unreadable` として返る)。
|
|
69
|
+
*/
|
|
70
|
+
export declare function enumerateObjects(doc: PdfDocument): Promise<{
|
|
71
|
+
objects: IndirectEntry[];
|
|
72
|
+
unreadable: number;
|
|
73
|
+
}>;
|
|
74
|
+
/**
|
|
75
|
+
* 間接オブジェクトのうち**辞書だけ**(ストリームの辞書は含めない)。
|
|
76
|
+
* pdf-lib の `enumerateIndirectObjects()` + `instanceof PDFDict` と同じ範囲である。
|
|
77
|
+
* 🔴 ここにストリームを足すと、いままで見ていなかった辞書が規則の対象に入る。
|
|
78
|
+
*/
|
|
79
|
+
export declare function enumerateDicts(doc: PdfDocument): Promise<{
|
|
80
|
+
dicts: CosDict[];
|
|
81
|
+
unreadable: number;
|
|
82
|
+
}>;
|
|
83
|
+
/**
|
|
84
|
+
* ストリームを復号する。**読めなかったら `unreadable`** —— 空のバイト列にしない。
|
|
85
|
+
* 空を返すと「中身が無い」と「読めなかった」が同じ顔になる。
|
|
86
|
+
*
|
|
87
|
+
* 🔴 `/Length` `/Filter` `/DecodeParms` は間接参照でよい(相互参照ストリームだけは
|
|
88
|
+
* 直接であることを §7.5.8.2 が要求している)。`decodeStream` は同期の解決関数しか
|
|
89
|
+
* 受け取れないので、辞書の第 1 階層の参照を**先に解いてから**渡す。
|
|
90
|
+
* これを渡さないと、`/Length 12 0 R` の DSS 証明書ストリームなどが「読めない」に落ち、
|
|
91
|
+
* 失効情報がまるごと消える —— 実検体で `revocation: revoked` が `unknown` に化けた。
|
|
92
|
+
*/
|
|
93
|
+
export declare function decodedBytes(doc: PdfDocument, stream: CosStream | null): Promise<{
|
|
94
|
+
bytes: Uint8Array | null;
|
|
95
|
+
unreadable: boolean;
|
|
96
|
+
}>;
|
|
97
|
+
//# sourceMappingURL=cos.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cos.d.ts","sourceRoot":"","sources":["../src/cos.ts"],"names":[],"mappings":"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"}
|
package/dist/cos.js
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* COS の読み口。**ここに判定は書かない。**
|
|
3
|
+
*
|
|
4
|
+
* pdf-lib の `instanceof PDFDict` / `context.lookup` に当たるものを 1 か所に集める。
|
|
5
|
+
* 6 ファイルに散らすと同じ変換が 6 通りになり、そのうち 1 つだけ条文の読み方が違う、
|
|
6
|
+
* という形で壊れる(B1 = pdf-constraints の撤去で実測。`src/facts/cos.ts` と同じ位置づけ)。
|
|
7
|
+
*
|
|
8
|
+
* 🔴 **「読めなかった」と「そこに無い」を分ける。** 未定義の間接参照は null と等価で
|
|
9
|
+
* (R-7.3.10-13)、それは**観測できた事実**である。条文に反していて復号できないストリームは
|
|
10
|
+
* 観測できていない。2 つを同じ `null` に畳むと、「フォントが埋め込まれていない」と
|
|
11
|
+
* 「フォントを読めなかった」が同じ顔になる(B1 で一度そうして、唯一 fail していた検体を消した)。
|
|
12
|
+
*/
|
|
13
|
+
import { COS_NULL, decodeStream, decodeTextString, dictGet, } from 'normativepdf';
|
|
14
|
+
/* ------------------------------------------------------------------ *
|
|
15
|
+
* 形を見る(pdf-lib の instanceof に当たる)
|
|
16
|
+
* ------------------------------------------------------------------ */
|
|
17
|
+
export function asDict(value) {
|
|
18
|
+
return value?.kind === 'dict' ? value : null;
|
|
19
|
+
}
|
|
20
|
+
export function asStream(value) {
|
|
21
|
+
return value?.kind === 'stream' ? value : null;
|
|
22
|
+
}
|
|
23
|
+
export function asArray(value) {
|
|
24
|
+
return value?.kind === 'array' ? value : null;
|
|
25
|
+
}
|
|
26
|
+
export function asRef(value) {
|
|
27
|
+
return value?.kind === 'ref' ? value : null;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* ストリームの辞書。**ストリームは辞書ではない。**
|
|
31
|
+
* pdf-lib の `enumerateIndirectObjects` を `instanceof PDFDict` で絞ると
|
|
32
|
+
* `PDFRawStream` は落ちる —— その範囲を変えると、いままで見ていなかった辞書が
|
|
33
|
+
* 規則の対象に入って判定が動く。範囲を変えるなら別の変更として測ること。
|
|
34
|
+
*/
|
|
35
|
+
export function dictOfStream(value) {
|
|
36
|
+
return value?.kind === 'stream' ? value.dict : null;
|
|
37
|
+
}
|
|
38
|
+
/** 名前オブジェクトの値(`#xx` の解決と UTF-8 復号は lexer が済ませている・R-7.3.5-13)。 */
|
|
39
|
+
export function nameOf(value) {
|
|
40
|
+
return value?.kind === 'name' ? value.value : null;
|
|
41
|
+
}
|
|
42
|
+
/** 数値(整数と実数のどちらでも受ける)。 */
|
|
43
|
+
export function numberOf(value) {
|
|
44
|
+
return value?.kind === 'integer' || value?.kind === 'real' ? value.value : null;
|
|
45
|
+
}
|
|
46
|
+
/** 整数だけを受ける(R-7.3.3-6「実数が来てはならない」を区別できるようにしておく)。 */
|
|
47
|
+
export function integerOf(value) {
|
|
48
|
+
return value?.kind === 'integer' ? value.value : null;
|
|
49
|
+
}
|
|
50
|
+
export function boolOf(value) {
|
|
51
|
+
return value?.kind === 'boolean' ? value.value : null;
|
|
52
|
+
}
|
|
53
|
+
/** 文字列のバイト列そのまま(復号は §7.9.2 の話で、ここではしない)。 */
|
|
54
|
+
export function bytesOf(value) {
|
|
55
|
+
return value?.kind === 'string' ? value.bytes : null;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* テキスト文字列(§7.9.2)。PDFDocEncoding / UTF-16BE / UTF-8(PDF 2.0)を
|
|
59
|
+
* 見分けて復号し、言語エスケープ列を落とす。
|
|
60
|
+
* pdf-lib 1.x は `R-7.9.2.2.1-4`(UTF-8 のバイト順マーク)を実装しておらず、
|
|
61
|
+
* 適合している文書に「文法に合わない」と誤報していた。
|
|
62
|
+
*/
|
|
63
|
+
export function textOf(value) {
|
|
64
|
+
const bytes = bytesOf(value);
|
|
65
|
+
return bytes === null ? null : decodeTextString(bytes);
|
|
66
|
+
}
|
|
67
|
+
/* ------------------------------------------------------------------ *
|
|
68
|
+
* 辞書を引く
|
|
69
|
+
* ------------------------------------------------------------------ */
|
|
70
|
+
/** 直接値を引く(`null` の値は「無い」と等価に畳む・R-7.3.7-7)。参照は解決しない。 */
|
|
71
|
+
export function get(dict, key) {
|
|
72
|
+
return dict ? dictGet(dict, key) : undefined;
|
|
73
|
+
}
|
|
74
|
+
/** 鍵があるか。**値が `null` でも「ある」と数える**(`get` とはここが違う)。 */
|
|
75
|
+
export function has(dict, key) {
|
|
76
|
+
return dict ? dict.entries.has(key) : false;
|
|
77
|
+
}
|
|
78
|
+
export async function tryResolve(doc, value) {
|
|
79
|
+
if (value === undefined)
|
|
80
|
+
return { value: null, unreadable: false };
|
|
81
|
+
try {
|
|
82
|
+
const resolved = await doc.resolve(value);
|
|
83
|
+
return { value: resolved.kind === 'null' ? null : resolved, unreadable: false };
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return { value: null, unreadable: true };
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/** 辞書の鍵を引いて解決する。 */
|
|
90
|
+
export async function tryGet(doc, dict, key) {
|
|
91
|
+
return tryResolve(doc, get(dict, key));
|
|
92
|
+
}
|
|
93
|
+
/** 読めなかったことを捨ててよい場所だけで使う短縮形(`unreadable` は `null` に畳まれる)。 */
|
|
94
|
+
export async function resolved(doc, value) {
|
|
95
|
+
return (await tryResolve(doc, value)).value;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* 相互参照表に載っている間接オブジェクトを、番号順に読む。
|
|
99
|
+
* 読めなかったものは**飛ばさずに数える**(`unreadable` として返る)。
|
|
100
|
+
*/
|
|
101
|
+
export async function enumerateObjects(doc) {
|
|
102
|
+
const objects = [];
|
|
103
|
+
let unreadable = 0;
|
|
104
|
+
const numbers = [...doc.xref.keys()].sort((a, b) => a - b);
|
|
105
|
+
for (const objectNumber of numbers) {
|
|
106
|
+
if (objectNumber === 0)
|
|
107
|
+
continue;
|
|
108
|
+
const entry = doc.xref.get(objectNumber);
|
|
109
|
+
if (!entry || entry.type === 'free' || entry.type === 'unknown')
|
|
110
|
+
continue;
|
|
111
|
+
const generation = entry.type === 'in-use' ? entry.generation : 0;
|
|
112
|
+
try {
|
|
113
|
+
const object = await doc.getObject(objectNumber, generation);
|
|
114
|
+
if (object.kind === 'null')
|
|
115
|
+
continue;
|
|
116
|
+
objects.push({ objectNumber, generation, object });
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
unreadable += 1;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return { objects, unreadable };
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* 間接オブジェクトのうち**辞書だけ**(ストリームの辞書は含めない)。
|
|
126
|
+
* pdf-lib の `enumerateIndirectObjects()` + `instanceof PDFDict` と同じ範囲である。
|
|
127
|
+
* 🔴 ここにストリームを足すと、いままで見ていなかった辞書が規則の対象に入る。
|
|
128
|
+
*/
|
|
129
|
+
export async function enumerateDicts(doc) {
|
|
130
|
+
const { objects, unreadable } = await enumerateObjects(doc);
|
|
131
|
+
const dicts = [];
|
|
132
|
+
for (const { object } of objects)
|
|
133
|
+
if (object.kind === 'dict')
|
|
134
|
+
dicts.push(object);
|
|
135
|
+
return { dicts, unreadable };
|
|
136
|
+
}
|
|
137
|
+
/* ------------------------------------------------------------------ *
|
|
138
|
+
* ストリームの中身
|
|
139
|
+
* ------------------------------------------------------------------ */
|
|
140
|
+
/**
|
|
141
|
+
* ストリームを復号する。**読めなかったら `unreadable`** —— 空のバイト列にしない。
|
|
142
|
+
* 空を返すと「中身が無い」と「読めなかった」が同じ顔になる。
|
|
143
|
+
*
|
|
144
|
+
* 🔴 `/Length` `/Filter` `/DecodeParms` は間接参照でよい(相互参照ストリームだけは
|
|
145
|
+
* 直接であることを §7.5.8.2 が要求している)。`decodeStream` は同期の解決関数しか
|
|
146
|
+
* 受け取れないので、辞書の第 1 階層の参照を**先に解いてから**渡す。
|
|
147
|
+
* これを渡さないと、`/Length 12 0 R` の DSS 証明書ストリームなどが「読めない」に落ち、
|
|
148
|
+
* 失効情報がまるごと消える —— 実検体で `revocation: revoked` が `unknown` に化けた。
|
|
149
|
+
*/
|
|
150
|
+
export async function decodedBytes(doc, stream) {
|
|
151
|
+
if (!stream)
|
|
152
|
+
return { bytes: null, unreadable: false };
|
|
153
|
+
const cache = new Map();
|
|
154
|
+
for (const [, value] of stream.dict.entries) {
|
|
155
|
+
if (value.kind !== 'ref')
|
|
156
|
+
continue;
|
|
157
|
+
const key = `${value.objectNumber} ${value.generationNumber}`;
|
|
158
|
+
if (cache.has(key))
|
|
159
|
+
continue;
|
|
160
|
+
try {
|
|
161
|
+
cache.set(key, await doc.resolve(value));
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
cache.set(key, COS_NULL);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
const resolve = (value) => value.kind === 'ref'
|
|
168
|
+
? (cache.get(`${value.objectNumber} ${value.generationNumber}`) ?? COS_NULL)
|
|
169
|
+
: value;
|
|
170
|
+
try {
|
|
171
|
+
return { bytes: await decodeStream(stream, { resolve }), unreadable: false };
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
return { bytes: null, unreadable: true };
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
//# sourceMappingURL=cos.js.map
|
package/dist/cos.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cos.js","sourceRoot":"","sources":["../src/cos.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 文書を開く 1 つの入口と、**その読みの射程**。
|
|
3
|
+
*
|
|
4
|
+
* normativepdf は §7.5 を条文どおりに読み、条文に反する文書を受け取らない。
|
|
5
|
+
* それは正しいが、消費者は「受け取らない」で終われない —— 監査の対象として持ち込まれる
|
|
6
|
+
* 文書こそ壊れているからである。そこで:
|
|
7
|
+
*
|
|
8
|
+
* 1. まず `parsePdf` に渡す。読めたらそれをそのまま使う(**方針を挟まない**)
|
|
9
|
+
* 2. 断られたら `xref-walk.ts` の回復方針で歩き、読めた節から文書を組み立てる
|
|
10
|
+
*
|
|
11
|
+
* この順番が肝で、2,947 検体のうち 2,930 件は 1 で読める。回復方針が当たるのは
|
|
12
|
+
* 残り 17 件だけであり、そこだけが「ライブラリの答え」ではなく「このパッケージの方針」になる。
|
|
13
|
+
* どちらだったかは `scope.recovered` が申告する。
|
|
14
|
+
*
|
|
15
|
+
* 🔴 **射程は判定ではない。** `scope` は「どこまで読んだか」であって、
|
|
16
|
+
* 「この文書が条文に適合しているか」ではない。適合の判定は validator が下す。
|
|
17
|
+
*/
|
|
18
|
+
import { type CosDict, PdfDocument, type XrefChainStop } from 'normativepdf';
|
|
19
|
+
import type { DebugSink, ReadingScope } from './types.js';
|
|
20
|
+
/** どこまで読めたか。**判定ではない。** */
|
|
21
|
+
export interface DocumentScope {
|
|
22
|
+
/**
|
|
23
|
+
* true = normativepdf が受け取らず、このパッケージの回復方針で組み立てた文書である。
|
|
24
|
+
* このとき「条文に反する箇所がある」ことは既に分かっている(`refusal` が条文を名指しする)。
|
|
25
|
+
*/
|
|
26
|
+
recovered: boolean;
|
|
27
|
+
/** 回復に入った理由。ライブラリが投げた、条文を名指しするエラーの文面 */
|
|
28
|
+
refusal: string | null;
|
|
29
|
+
/** チェーンの歩きがどこで止まったか(§7.5.6) */
|
|
30
|
+
chainStop: XrefChainStop;
|
|
31
|
+
/** 最後の `startxref` が読めず、古い入口から入った = 末尾のバイトは代表されていない */
|
|
32
|
+
newestSectionUnreadable: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* 読めた相互参照節の数(§7.5.4/§7.5.6)。
|
|
35
|
+
*
|
|
36
|
+
* 🔴 **経路によらず同じ意味を持たせること。** 0.19.0 では、ライブラリが
|
|
37
|
+
* そのまま読んだ経路でこの値を数えておらず、**健全な文書がすべて `0`** を
|
|
38
|
+
* 返していた(2,947 検体のうち 2,931 件)。読み手はそれを「節が 0 個」と読む。
|
|
39
|
+
* 観測していないことと、観測して 0 だったことが同じ顔をしていた ——
|
|
40
|
+
* `observation` で直したのと同じ間違いを、この項目自身が持っていた。
|
|
41
|
+
*
|
|
42
|
+
* 数えられなかったときは `null`。`0` は「節が 1 つも読めなかった」という
|
|
43
|
+
* 観測結果のときだけ使う(表を組み直した経路)。
|
|
44
|
+
*/
|
|
45
|
+
sections: number | null;
|
|
46
|
+
/**
|
|
47
|
+
* チェーンが止まったあと、`startxref` の値を頼りに読み続けて文書を組み立てた。
|
|
48
|
+
* **`/Prev` が繋いだものではない**ので、リビジョンの一覧とは別の読み方である。
|
|
49
|
+
* 目録に届かない文書でだけ起きる(§7.5.6 の `/Prev 0` など)。
|
|
50
|
+
*/
|
|
51
|
+
continuedPastStop: boolean;
|
|
52
|
+
/**
|
|
53
|
+
* チェーンが辿れない文書で、**表に載っていないオブジェクトを数え上げて埋めた**。
|
|
54
|
+
* 表そのものは信じたうえで、穴だけを埋める(表にある定義は上書きしない)。
|
|
55
|
+
* ここで埋めた分は「ファイルの中にこう書いてある」であって、
|
|
56
|
+
* 「相互参照表がそう言っている」ではない。
|
|
57
|
+
*/
|
|
58
|
+
filledFromScan: number;
|
|
59
|
+
/**
|
|
60
|
+
* 🔴 **相互参照表を組み直した。** ファイルの中の `N G obj` を数え上げて作った表であり、
|
|
61
|
+
* ファイルが持っているものではない。リビジョンの境目は言えない
|
|
62
|
+
* (pdf-verify-mcp の `verify_integrity` は `revisionChain` に `unwalkable` を返す)。
|
|
63
|
+
*/
|
|
64
|
+
reconstructed: boolean;
|
|
65
|
+
/** 相互参照表に載っているオブジェクトの数 */
|
|
66
|
+
objects: number;
|
|
67
|
+
/** trailer に `/Encrypt` がある(§7.6) */
|
|
68
|
+
encrypted: boolean;
|
|
69
|
+
/**
|
|
70
|
+
* 暗号化文書の鍵が導けた。false のとき **オブジェクトは 1 つも読めない** ——
|
|
71
|
+
* normativepdf は暗号文を平文の顔で返さない(ADR-0008)。
|
|
72
|
+
* 「読めなかった」ことを申告したうえで、読めた範囲だけで答えるのは呼び出し側の仕事。
|
|
73
|
+
*/
|
|
74
|
+
authenticated: boolean;
|
|
75
|
+
/**
|
|
76
|
+
* `/Encrypt` 辞書。**鍵が導けなくても読める** —— §7.6.2 が暗号化の対象から
|
|
77
|
+
* 除いているので、生バイトから直接読んでよい。ISO 14289-1 7.16(支援技術への
|
|
78
|
+
* 許可ビット)はこれだけで判定できる。
|
|
79
|
+
*/
|
|
80
|
+
encryptDict: CosDict | null;
|
|
81
|
+
}
|
|
82
|
+
export interface OpenedDocument {
|
|
83
|
+
doc: PdfDocument;
|
|
84
|
+
scope: DocumentScope;
|
|
85
|
+
}
|
|
86
|
+
export interface OpenOptions {
|
|
87
|
+
/** 暗号化文書のパスワード(既定は空 —— §7.6.4.4 の NOTE が言う「まず空を試す」) */
|
|
88
|
+
password?: string;
|
|
89
|
+
/**
|
|
90
|
+
* 回復に入った理由などの申告先。既定は「どこにも出さない」。
|
|
91
|
+
* 🔴 stdout に書く実装を渡さないこと(`DebugSink`)。
|
|
92
|
+
*/
|
|
93
|
+
onDebug?: DebugSink;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* 文書を開く。ライブラリが読めればそれを、断ったら回復方針で組み立てたものを返す。
|
|
97
|
+
* どちらも無理なら、**ライブラリが名指しした条文をそのまま**投げる。
|
|
98
|
+
*/
|
|
99
|
+
export declare function openDocument(bytes: Uint8Array, options?: OpenOptions): Promise<OpenedDocument>;
|
|
100
|
+
/**
|
|
101
|
+
* 内部の申告を、出力に載せる形にする。落とすのは `encryptDict` だけ
|
|
102
|
+
* —— COS 辞書なので JSON にすると内部表現が出る。`/V` `/R` `/Filter` の値が
|
|
103
|
+
* 要るなら、呼び出し側がそこから取り出す。
|
|
104
|
+
*/
|
|
105
|
+
export declare function toReadingScope(scope: DocumentScope): ReadingScope;
|
|
106
|
+
//# sourceMappingURL=document.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"document.d.ts","sourceRoot":"","sources":["../src/document.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAGL,KAAK,OAAO,EAIZ,WAAW,EAIX,KAAK,aAAa,EAEnB,MAAM,cAAc,CAAC;AAEtB,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAW1D,2BAA2B;AAC3B,MAAM,WAAW,aAAa;IAC5B;;;OAGG;IACH,SAAS,EAAE,OAAO,CAAC;IACnB,wCAAwC;IACxC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,+BAA+B;IAC/B,SAAS,EAAE,aAAa,CAAC;IACzB,uDAAuD;IACvD,uBAAuB,EAAE,OAAO,CAAC;IACjC;;;;;;;;;;;OAWG;IACH,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB;;;;OAIG;IACH,iBAAiB,EAAE,OAAO,CAAC;IAC3B;;;;;OAKG;IACH,cAAc,EAAE,MAAM,CAAC;IACvB;;;;OAIG;IACH,aAAa,EAAE,OAAO,CAAC;IACvB,0BAA0B;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,qCAAqC;IACrC,SAAS,EAAE,OAAO,CAAC;IACnB;;;;OAIG;IACH,aAAa,EAAE,OAAO,CAAC;IACvB;;;;OAIG;IACH,WAAW,EAAE,OAAO,GAAG,IAAI,CAAC;CAC7B;AAED,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,WAAW,CAAC;IACjB,KAAK,EAAE,aAAa,CAAC;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B,uDAAuD;IACvD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,OAAO,CAAC,EAAE,SAAS,CAAC;CACrB;AA6LD;;;GAGG;AACH,wBAAsB,YAAY,CAChC,KAAK,EAAE,UAAU,EACjB,OAAO,GAAE,WAAgB,GACxB,OAAO,CAAC,cAAc,CAAC,CAyJzB;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,aAAa,GAAG,YAAY,CAgBjE"}
|