@shuji-bonji/pdf-writer-mcp 0.5.1 → 0.6.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.
Files changed (40) hide show
  1. package/CHANGELOG.md +96 -17
  2. package/README.ja.md +7 -1
  3. package/README.md +7 -1
  4. package/dist/constants.d.ts +23 -0
  5. package/dist/constants.js +36 -0
  6. package/dist/constants.js.map +1 -1
  7. package/dist/services/attachment.d.ts +52 -0
  8. package/dist/services/attachment.js +145 -0
  9. package/dist/services/attachment.js.map +1 -0
  10. package/dist/services/editor.d.ts +6 -1
  11. package/dist/services/editor.js +204 -3
  12. package/dist/services/editor.js.map +1 -1
  13. package/dist/services/form.d.ts +75 -0
  14. package/dist/services/form.js +253 -0
  15. package/dist/services/form.js.map +1 -0
  16. package/dist/services/output.d.ts +6 -2
  17. package/dist/services/output.js +6 -2
  18. package/dist/services/output.js.map +1 -1
  19. package/dist/services/page-number.d.ts +43 -0
  20. package/dist/services/page-number.js +79 -0
  21. package/dist/services/page-number.js.map +1 -0
  22. package/dist/services/struct-append.d.ts +8 -0
  23. package/dist/services/struct-append.js +17 -1
  24. package/dist/services/struct-append.js.map +1 -1
  25. package/dist/services/watermark.d.ts +52 -0
  26. package/dist/services/watermark.js +75 -0
  27. package/dist/services/watermark.js.map +1 -0
  28. package/dist/tools/definitions.d.ts +236 -0
  29. package/dist/tools/definitions.js +177 -0
  30. package/dist/tools/definitions.js.map +1 -1
  31. package/dist/tools/handlers.d.ts +6 -1
  32. package/dist/tools/handlers.js +27 -2
  33. package/dist/tools/handlers.js.map +1 -1
  34. package/dist/types/index.d.ts +131 -0
  35. package/dist/utils/page-spec.js +4 -1
  36. package/dist/utils/page-spec.js.map +1 -1
  37. package/dist/utils/validation.d.ts +6 -1
  38. package/dist/utils/validation.js +129 -1
  39. package/dist/utils/validation.js.map +1 -1
  40. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -2,6 +2,102 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [0.6.0] - 2026-07-16
6
+
7
+ ### Added
8
+
9
+ - **`fill_form`** — fill AcroForm fields: text, checkbox, dropdown, optionlist,
10
+ radio. Values are validated against the field's type and, for choice fields,
11
+ against its options; both errors name what the document actually offers.
12
+ Naming a field that does not exist lists the real field names, which is how a
13
+ caller discovers the form without a separate listing tool.
14
+
15
+ - Values are rendered with an **embedded font**, so Japanese works. pdf-lib
16
+ would otherwise regenerate every appearance with Helvetica on `save()` and
17
+ fail on `WinAnsi cannot encode "山"`; the font goes through the same
18
+ font-manager as the create tools (ADR-7/8).
19
+ - `flatten: true` fills and flattens in one pass.
20
+ - Filling does **not** touch the structure tree, so a tagged PDF keeps whatever
21
+ conformance it came in with.
22
+
23
+ - **`flatten_form`** — flatten an AcroForm so the filled values become static
24
+ content, for fixing values before distribution.
25
+
26
+ - **Refuses tagged PDFs by default.** Flattening removes the Widget
27
+ annotations that the `Form` structure elements point to and bakes their
28
+ appearance in as untagged content. Measured, not assumed: veraPDF reports
29
+ `7.1-3 Content shall be marked as Artifact or tagged as real content`.
30
+ `allowBreakingTags: true` overrides and reports a warning.
31
+ - Drops the now-empty `/AcroForm` and prunes the dangling references that
32
+ pdf-lib's `flatten()` leaves behind in `/Annots` and `/Kids` (poppler
33
+ otherwise reports `Invalid XRef entry`).
34
+
35
+ - **`add_watermark`** — overlay a diagonal watermark ("社外秘" / "DRAFT" /
36
+ "COPY") at the centre of each page.
37
+
38
+ - `text`, `fontSize` (60), `color` (`#808080`), `opacity` (0.15), `angle` (45),
39
+ `behind` (true), `pages`.
40
+ - **Behind the body content by default.** pdf-lib can only append to a content
41
+ stream, so the watermark is drawn and then moved to the front of the
42
+ `/Contents` array; each stream is self-contained in `q`/`Q`, so reordering is
43
+ safe.
44
+ - **Becomes an artifact on tagged PDFs** (PDF/UA-1 7.1-3). Verified: veraPDF
45
+ `ua1` 106/106.
46
+
47
+ - **`stamp_page_numbers`** — stamp page numbers onto an existing PDF.
48
+
49
+ - `format` expands `{n}` and `{total}` (`{n} / {total}`, `- {n} -`, `{n} ページ`).
50
+ - `position` (six corners), `margin`, `fontSize`, `color`, `pages`, `startAt` —
51
+ `pages: "2-"` with `startAt: 1` numbers everything but the cover from 1.
52
+ - **Stamps become artifacts on tagged PDFs** (PDF/UA-1 7.1-3). Page numbers
53
+ carry no meaning for a screen reader, and content that is neither tagged nor
54
+ marked as an artifact breaks conformance. Verified: veraPDF `ua1` 106/106.
55
+ - Rotated pages (`/Rotate`) are compensated for, so "bottom-right" lands where
56
+ the reader sees the bottom right.
57
+ - The first editing tool that needs a font: it goes through the same
58
+ font-manager as the create tools, so harfbuzz subsetting (ADR-7/8) and the
59
+ missing-glyph check apply. Japanese formats need `fontPath` or
60
+ `PDF_WRITER_FONT`.
61
+
62
+ ### Changed
63
+
64
+ - **biome adopted** for linting and formatting, matching the rest of the family
65
+ (same `biome.json`, same scripts). `npm run check` now runs in CI and in the
66
+ publish workflow. Existing sources were reformatted; the suite is clean.
67
+ - The version is pinned to an exact `2.5.4` rather than a caret range. Biome's
68
+ formatting output changes between minor releases, so a range lets a local
69
+ `npm install` drift ahead of CI and produce spurious diffs.
70
+
71
+ ### Fixed
72
+
73
+ - `parsePageSpec` reported an open-ended chunk past the end (`"2-"` on a 1-page
74
+ document) as *reversed* rather than *out of range* — the open end collapses to
75
+ `pageCount`, which made `from > to` trigger first.
76
+ - `handleCreateTablePdf` duplicated the Latin-1 check as a regex containing
77
+ control characters; it now reuses the existing `hasNonLatin1` helper.
78
+ - `src/config.ts` imports `node:module` rather than the bare `module` specifier.
79
+
80
+
81
+ - **`attach_file`** — embed a file in a PDF (Tier B, first tool). Registers it in
82
+ `/Names /EmbeddedFiles`, references it from the catalog `/AF`, and sets
83
+ `/AFRelationship`. This is the shape PDF/A-3 (ISO 19005-3) requires, and the
84
+ one used to bundle a human-readable invoice with its machine-readable
85
+ counterpart (CSV/XML) in a single file — the 電子帳簿保存法 use case.
86
+
87
+ - `relationship`: `Data` (machine-readable counterpart), `Source` (the data the
88
+ document came from), `Alternative`, `Supplement`, `Unspecified` (default).
89
+ Omitting it produces a warning — PDF/A-3 §6.8 wants a meaningful value, and
90
+ `Unspecified` says nothing about why the file is there.
91
+ - `mimeType` is inferred from the extension when omitted (`.csv` → `text/csv`),
92
+ falling back to `application/octet-stream`.
93
+ - `name` renames the attachment inside the PDF; duplicates are rejected because
94
+ name-tree keys must be unique.
95
+ - Attaching to a tagged PDF leaves it conformant (verified: veraPDF `ua1`,
96
+ 106/106).
97
+
98
+ Descriptions and names round-trip in Japanese, and attached bytes come back
99
+ byte-identical.
100
+
5
101
  ## [0.5.1] - 2026-07-16
6
102
 
7
103
  ### Added
@@ -74,23 +170,6 @@ an annotation was added.
74
170
  - XMP metadata is written as UTF-8 bytes. `context.stream(string)` writes one
75
171
  byte per character, which mangled Japanese titles.
76
172
 
77
- ## [Unreleased]
78
-
79
- ### Changed
80
-
81
- - **biome adopted** for linting and formatting, matching the rest of the family
82
- (same `biome.json`, same scripts). `npm run check` now runs in CI and in the
83
- publish workflow. Existing sources were reformatted; the suite is clean.
84
- - The version is pinned to an exact `2.5.4` rather than a caret range. Biome's
85
- formatting output changes between minor releases, so a range lets a local
86
- `npm install` drift ahead of CI and produce spurious diffs.
87
-
88
- ### Fixed
89
-
90
- - `handleCreateTablePdf` duplicated the Latin-1 check as a regex containing
91
- control characters; it now reuses the existing `hasNonLatin1` helper.
92
- - `src/config.ts` imports `node:module` rather than the bare `module` specifier.
93
-
94
173
  ## [0.4.0] - 2026-07-16
95
174
 
96
175
  ### Added
package/README.ja.md CHANGED
@@ -49,6 +49,11 @@ PDF/UA はタイトルを要求するため、`tagged: true` では `title` が
49
49
  | `rotate_pages` | 時計回りに回転(90/180/270)。既存の回転に加算 |
50
50
  | `add_bookmarks` | しおり(アウトライン)の設定。`children` で入れ子にでき、既存のしおりは置換 |
51
51
  | `add_annotation` | 付箋(`text`)/ ハイライト(`highlight`)/ 矩形(`square`)の注釈を追加。タグ付き PDF では `Annot` 構造要素に内包され PDF/UA 準拠を維持する(`alt` で説明を渡す) |
52
+ | `attach_file` | ファイルを埋め込む(`/Names /EmbeddedFiles` + catalog `/AF` + `/AFRelationship`)。PDF/A-3 が要求する形式で、機械可読データを文書に同梱する |
53
+ | `stamp_page_numbers` | ページ番号を刻む(`{n}` / `{total}`、6 箇所の配置、`pages`、`startAt`)。タグ付き PDF では Artifact になるため準拠を維持する |
54
+ | `fill_form` | AcroForm に値を記入する。日本語は埋め込みフォントで描画。同時にフラット化も可能 |
55
+ | `flatten_form` | フォームを静的な内容に焼き込む。タグ付き PDF は既定で拒否(PDF/UA が壊れるため) |
56
+ | `add_watermark` | 斜めの透かしを重ねる("社外秘" / "DRAFT")。既定で本文の背面。タグ付き PDF では Artifact になる |
52
57
 
53
58
  共通オプション: `outputPath` / `returnBase64` / `allowBreakingSignatures`。
54
59
 
@@ -146,7 +151,8 @@ TEST_FONT_PATH=/path/to/NotoSansJP-Regular.otf npm test
146
151
  - [x] 編集系 Tier A 第2波 — しおり・注釈(v0.4.0)
147
152
  - [x] タグ付き PDF / PDF/UA-1 — veraPDF で準拠を確認(v0.5.0)
148
153
  - [x] タグ付き出力での注釈の `Annot` タグ内包(v0.5.1)
149
- - [ ] 編集系 Tier B — フォーム記入 / フラット化、透かし、添付ファイル、ページ番号スタンプ
154
+ - [x] 編集系 Tier B — 添付ファイル(v0.6.0)
155
+ - [ ] 編集系 Tier B — フォーム記入 / フラット化、透かし、ページ番号スタンプ
150
156
  - [ ] 画像の代替テキスト(`Figure` + `/Alt`)
151
157
  - [ ] `.ttc` からのフェイス自動抽出
152
158
  - [ ] 見出し用と本文用のフォント分け(太字フェイス埋め込み)
package/README.md CHANGED
@@ -49,6 +49,11 @@ PDF/UA mandates a document title, so `tagged: true` requires `title`. `lang` (BC
49
49
  | `rotate_pages` | Rotate clockwise (90/180/270), accumulating over existing rotation |
50
50
  | `add_bookmarks` | Set the outline (bookmarks); nestable via `children`, replaces any existing outline |
51
51
  | `add_annotation` | Add a sticky note (`text`), `highlight`, or `square` annotation to a page. On tagged PDFs the annotation is nested in an `Annot` element and stays PDF/UA conformant — pass `alt` to describe it |
52
+ | `attach_file` | Embed a file (`/Names /EmbeddedFiles` + catalog `/AF` + `/AFRelationship`) — the PDF/A-3 shape for bundling machine-readable data with a document |
53
+ | `stamp_page_numbers` | Stamp page numbers (`{n}` / `{total}`, six positions, `pages`, `startAt`). Becomes an artifact on tagged PDFs, so conformance holds |
54
+ | `fill_form` | Fill AcroForm fields. Japanese values via an embedded font; can flatten in the same pass |
55
+ | `flatten_form` | Flatten a form into static content. Refuses tagged PDFs by default (breaks PDF/UA) |
56
+ | `add_watermark` | Overlay a diagonal watermark ("社外秘" / "DRAFT"). Behind the body content by default; artifact on tagged PDFs |
52
57
 
53
58
  Shared options: `outputPath`, `returnBase64`, `allowBreakingSignatures`.
54
59
 
@@ -146,7 +151,8 @@ TEST_FONT_PATH=/path/to/NotoSansJP-Regular.otf npm test
146
151
  - [x] Editing Tier A wave 2 — bookmarks and annotations (v0.4.0)
147
152
  - [x] Tagged PDF / PDF/UA-1 — verified by veraPDF (v0.5.0)
148
153
  - [x] Annotations nested in `Annot` tags on tagged output (v0.5.1)
149
- - [ ] Editing Tier B — form filling/flattening, watermarks, attachments, page-number stamping
154
+ - [x] Editing Tier B — file attachments (v0.6.0)
155
+ - [ ] Editing Tier B — form filling/flattening, watermarks, page-number stamping
150
156
  - [ ] Images with alt text (`Figure` + `/Alt`)
151
157
  - [ ] Automatic face extraction from `.ttc`
152
158
  - [ ] Separate faces for headings and body (bold face embedding)
@@ -36,7 +36,30 @@ export declare const LIMITS: {
36
36
  /** しおりの最大総数・最大ネスト深さ */
37
37
  readonly BOOKMARK_MAX_TOTAL: 2000;
38
38
  readonly BOOKMARK_MAX_DEPTH: 8;
39
+ /** 埋め込みファイルの最大サイズ(バイト)。PDF 全体がメモリに載るため上限を設ける */
40
+ readonly ATTACHMENT_MAX_BYTES: number;
39
41
  };
42
+ /** stamp_page_numbers の配置 */
43
+ export declare const STAMP_POSITIONS: readonly ['bottom-left', 'bottom-center', 'bottom-right', 'top-left', 'top-center', 'top-right'];
44
+ /** stamp_page_numbers の既定値 */
45
+ export declare const STAMP_DEFAULTS: {
46
+ readonly format: '{n}';
47
+ readonly position: 'bottom-center';
48
+ readonly margin: 24;
49
+ readonly fontSize: 9;
50
+ readonly color: '#666666';
51
+ readonly startAt: 1;
52
+ };
53
+ /** add_watermark の既定値 */
54
+ export declare const WATERMARK_DEFAULTS: {
55
+ readonly fontSize: 60;
56
+ readonly color: '#808080';
57
+ readonly opacity: 0.15;
58
+ readonly angle: 45;
59
+ readonly behind: true;
60
+ };
61
+ /** attach_file が受け付ける AFRelationship(PDF/A-3 §6.8) */
62
+ export declare const ATTACHMENT_RELATIONSHIPS: readonly ['Source', 'Data', 'Alternative', 'Supplement', 'Unspecified'];
40
63
  /** add_annotation が受け付ける注釈種別 */
41
64
  export declare const ANNOTATION_TYPES: readonly ['text', 'highlight', 'square'];
42
65
  /** text 注釈のアイコン名(ISO 32000-1 Table 172 の一般的な値) */
package/dist/constants.js CHANGED
@@ -35,7 +35,43 @@ export const LIMITS = {
35
35
  /** しおりの最大総数・最大ネスト深さ */
36
36
  BOOKMARK_MAX_TOTAL: 2_000,
37
37
  BOOKMARK_MAX_DEPTH: 8,
38
+ /** 埋め込みファイルの最大サイズ(バイト)。PDF 全体がメモリに載るため上限を設ける */
39
+ ATTACHMENT_MAX_BYTES: 100 * 1024 * 1024,
38
40
  };
41
+ /** stamp_page_numbers の配置 */
42
+ export const STAMP_POSITIONS = [
43
+ 'bottom-left',
44
+ 'bottom-center',
45
+ 'bottom-right',
46
+ 'top-left',
47
+ 'top-center',
48
+ 'top-right',
49
+ ];
50
+ /** stamp_page_numbers の既定値 */
51
+ export const STAMP_DEFAULTS = {
52
+ format: '{n}',
53
+ position: 'bottom-center',
54
+ margin: 24,
55
+ fontSize: 9,
56
+ color: '#666666',
57
+ startAt: 1,
58
+ };
59
+ /** add_watermark の既定値 */
60
+ export const WATERMARK_DEFAULTS = {
61
+ fontSize: 60,
62
+ color: '#808080',
63
+ opacity: 0.15,
64
+ angle: 45,
65
+ behind: true,
66
+ };
67
+ /** attach_file が受け付ける AFRelationship(PDF/A-3 §6.8) */
68
+ export const ATTACHMENT_RELATIONSHIPS = [
69
+ 'Source',
70
+ 'Data',
71
+ 'Alternative',
72
+ 'Supplement',
73
+ 'Unspecified',
74
+ ];
39
75
  /** add_annotation が受け付ける注釈種別 */
40
76
  export const ANNOTATION_TYPES = ['text', 'highlight', 'square'];
41
77
  /** text 注釈のアイコン名(ISO 32000-1 Table 172 の一般的な値) */
@@ -1 +1 @@
1
- {"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;GAGG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,EAAE,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC;IACpB,EAAE,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC;IACrB,EAAE,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,CAAC,GAAG,EAAE,GAAG,CAAC;IAClB,KAAK,EAAE,CAAC,GAAG,EAAE,IAAI,CAAC;CACV,CAAC;AAIX;;GAEG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB,qBAAqB;IACrB,aAAa,EAAE,CAAC;IAChB,aAAa,EAAE,EAAE;IACjB,sCAAsC;IACtC,UAAU,EAAE,CAAC;IACb,UAAU,EAAE,GAAG;IACf,6CAA6C;IAC7C,eAAe,EAAE,OAAO;IACxB,+BAA+B;IAC/B,cAAc,EAAE,EAAE;IAClB,cAAc,EAAE,KAAK;IACrB,4BAA4B;IAC5B,gBAAgB,EAAE,EAAE;IACpB,wBAAwB;IACxB,eAAe,EAAE,GAAG;IACpB,uBAAuB;IACvB,kBAAkB,EAAE,KAAK;IACzB,kBAAkB,EAAE,CAAC;CACb,CAAC;AAEX,gCAAgC;AAChC,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,MAAM,EAAE,WAAW,EAAE,QAAQ,CAAU,CAAC;AAEzE,kDAAkD;AAClD,MAAM,CAAC,MAAM,gBAAgB,GAAG;IAC9B,MAAM;IACN,SAAS;IACT,KAAK;IACL,MAAM;IACN,cAAc;IACd,WAAW;IACX,QAAQ;CACA,CAAC;AAEX,qCAAqC;AACrC,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,EAAE,EAAE,GAAG,EAAE,GAAG,CAAU,CAAC;AAEvD;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,eAAe,CAAC;AAExD;;;GAGG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,mCAAmC;IACnC,GAAG,EAAE,MAAM;CACH,CAAC"}
1
+ {"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;GAGG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,EAAE,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC;IACpB,EAAE,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC;IACrB,EAAE,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,CAAC,GAAG,EAAE,GAAG,CAAC;IAClB,KAAK,EAAE,CAAC,GAAG,EAAE,IAAI,CAAC;CACV,CAAC;AAIX;;GAEG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB,qBAAqB;IACrB,aAAa,EAAE,CAAC;IAChB,aAAa,EAAE,EAAE;IACjB,sCAAsC;IACtC,UAAU,EAAE,CAAC;IACb,UAAU,EAAE,GAAG;IACf,6CAA6C;IAC7C,eAAe,EAAE,OAAO;IACxB,+BAA+B;IAC/B,cAAc,EAAE,EAAE;IAClB,cAAc,EAAE,KAAK;IACrB,4BAA4B;IAC5B,gBAAgB,EAAE,EAAE;IACpB,wBAAwB;IACxB,eAAe,EAAE,GAAG;IACpB,uBAAuB;IACvB,kBAAkB,EAAE,KAAK;IACzB,kBAAkB,EAAE,CAAC;IACrB,gDAAgD;IAChD,oBAAoB,EAAE,GAAG,GAAG,IAAI,GAAG,IAAI;CAC/B,CAAC;AAEX,6BAA6B;AAC7B,MAAM,CAAC,MAAM,eAAe,GAAG;IAC7B,aAAa;IACb,eAAe;IACf,cAAc;IACd,UAAU;IACV,YAAY;IACZ,WAAW;CACH,CAAC;AAEX,8BAA8B;AAC9B,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,MAAM,EAAE,KAAK;IACb,QAAQ,EAAE,eAAe;IACzB,MAAM,EAAE,EAAE;IACV,QAAQ,EAAE,CAAC;IACX,KAAK,EAAE,SAAS;IAChB,OAAO,EAAE,CAAC;CACF,CAAC;AAEX,yBAAyB;AACzB,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAChC,QAAQ,EAAE,EAAE;IACZ,KAAK,EAAE,SAAS;IAChB,OAAO,EAAE,IAAI;IACb,KAAK,EAAE,EAAE;IACT,MAAM,EAAE,IAAI;CACJ,CAAC;AAEX,sDAAsD;AACtD,MAAM,CAAC,MAAM,wBAAwB,GAAG;IACtC,QAAQ;IACR,MAAM;IACN,aAAa;IACb,YAAY;IACZ,aAAa;CACL,CAAC;AAEX,gCAAgC;AAChC,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,MAAM,EAAE,WAAW,EAAE,QAAQ,CAAU,CAAC;AAEzE,kDAAkD;AAClD,MAAM,CAAC,MAAM,gBAAgB,GAAG;IAC9B,MAAM;IACN,SAAS;IACT,KAAK;IACL,MAAM;IACN,cAAc;IACd,WAAW;IACX,QAAQ;CACA,CAAC;AAEX,qCAAqC;AACrC,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,EAAE,EAAE,GAAG,EAAE,GAAG,CAAU,CAAC;AAEvD;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,eAAe,CAAC;AAExD;;;GAGG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,mCAAmC;IACnC,GAAG,EAAE,MAAM;CACH,CAAC"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Embedded files (attachments)
3
+ *
4
+ * PDF に別ファイルを埋め込む(ISO 32000-1 §7.11.4 / §14.13)。
5
+ * pdf-lib の `doc.attach()` が /Names /EmbeddedFiles・catalog /AF・/UF・/Params まで
6
+ * 書いてくれるため、本モジュールは
7
+ * - 入力の検証(存在・サイズ・重複名)
8
+ * - MIME 型の推定
9
+ * - AFRelationship の既定値と用途の説明
10
+ * - 既存の添付一覧の読み取り
11
+ * を担う。
12
+ *
13
+ * PDF/A-3(ISO 19005-3)と電子帳簿保存法の文脈:
14
+ * PDF/A-3 は任意形式のファイルを埋め込める唯一の PDF/A 部で、
15
+ * 「人が読む請求書(PDF)+機械可読データ(CSV/XML)」を 1 ファイルに束ねる用途に使う。
16
+ * その際 /AFRelationship は必須で、機械可読データなら Data、
17
+ * 元データなら Source を指定する(§6.8)。
18
+ */
19
+ import { type PDFDocument } from 'pdf-lib';
20
+ import type { AttachmentRelationship } from '../types/index.js';
21
+ export declare function guessMimeType(fileName: string): string;
22
+ export interface EmbeddedFileInfo {
23
+ name: string;
24
+ description?: string;
25
+ relationship?: string;
26
+ mimeType?: string;
27
+ }
28
+ /** 既に埋め込まれているファイルの一覧を返す */
29
+ export declare function listEmbeddedFiles(doc: PDFDocument): EmbeddedFileInfo[];
30
+ export interface AttachFileOptions {
31
+ /** 埋め込むファイルのパス */
32
+ filePath: string;
33
+ /** PDF 内での表示名。省略時は元のファイル名 */
34
+ name?: string;
35
+ /** 説明(/Desc) */
36
+ description?: string;
37
+ /** MIME 型。省略時は拡張子から推定 */
38
+ mimeType?: string;
39
+ /** PDF/A-3 §6.8 の関係。既定 Unspecified */
40
+ relationship?: AttachmentRelationship;
41
+ }
42
+ export interface AttachedFile {
43
+ name: string;
44
+ bytes: number;
45
+ mimeType: string;
46
+ relationship: string;
47
+ }
48
+ /**
49
+ * ファイルを PDF に埋め込む。
50
+ * 同名の添付が既にある場合はエラー(名前ツリーのキーは一意であるべき)。
51
+ */
52
+ export declare function attachFile(doc: PDFDocument, options: AttachFileOptions): Promise<AttachedFile>;
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Embedded files (attachments)
3
+ *
4
+ * PDF に別ファイルを埋め込む(ISO 32000-1 §7.11.4 / §14.13)。
5
+ * pdf-lib の `doc.attach()` が /Names /EmbeddedFiles・catalog /AF・/UF・/Params まで
6
+ * 書いてくれるため、本モジュールは
7
+ * - 入力の検証(存在・サイズ・重複名)
8
+ * - MIME 型の推定
9
+ * - AFRelationship の既定値と用途の説明
10
+ * - 既存の添付一覧の読み取り
11
+ * を担う。
12
+ *
13
+ * PDF/A-3(ISO 19005-3)と電子帳簿保存法の文脈:
14
+ * PDF/A-3 は任意形式のファイルを埋め込める唯一の PDF/A 部で、
15
+ * 「人が読む請求書(PDF)+機械可読データ(CSV/XML)」を 1 ファイルに束ねる用途に使う。
16
+ * その際 /AFRelationship は必須で、機械可読データなら Data、
17
+ * 元データなら Source を指定する(§6.8)。
18
+ */
19
+ import { readFile } from 'node:fs/promises';
20
+ import { basename, extname, resolve } from 'node:path';
21
+ import { AFRelationship, PDFArray, PDFDict, PDFName } from 'pdf-lib';
22
+ import { LIMITS } from '../constants.js';
23
+ /** 拡張子 → MIME 型(電帳法・請求まわりで実際に使うものに絞る) */
24
+ const MIME_TYPES = {
25
+ '.csv': 'text/csv',
26
+ '.xml': 'application/xml',
27
+ '.json': 'application/json',
28
+ '.txt': 'text/plain',
29
+ '.pdf': 'application/pdf',
30
+ '.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
31
+ '.xls': 'application/vnd.ms-excel',
32
+ '.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
33
+ '.doc': 'application/msword',
34
+ '.zip': 'application/zip',
35
+ '.png': 'image/png',
36
+ '.jpg': 'image/jpeg',
37
+ '.jpeg': 'image/jpeg',
38
+ '.gif': 'image/gif',
39
+ '.html': 'text/html',
40
+ };
41
+ const DEFAULT_MIME = 'application/octet-stream';
42
+ export function guessMimeType(fileName) {
43
+ return MIME_TYPES[extname(fileName).toLowerCase()] ?? DEFAULT_MIME;
44
+ }
45
+ /** pdf-lib の AFRelationship へ変換する */
46
+ function toAFRelationship(rel) {
47
+ switch (rel) {
48
+ case 'Source':
49
+ return AFRelationship.Source;
50
+ case 'Data':
51
+ return AFRelationship.Data;
52
+ case 'Alternative':
53
+ return AFRelationship.Alternative;
54
+ case 'Supplement':
55
+ return AFRelationship.Supplement;
56
+ default:
57
+ return AFRelationship.Unspecified;
58
+ }
59
+ }
60
+ /** 既に埋め込まれているファイルの一覧を返す */
61
+ export function listEmbeddedFiles(doc) {
62
+ const names = doc.catalog.lookup(PDFName.of('Names'));
63
+ if (!(names instanceof PDFDict))
64
+ return [];
65
+ const ef = names.lookup(PDFName.of('EmbeddedFiles'));
66
+ if (!(ef instanceof PDFDict))
67
+ return [];
68
+ const arr = ef.lookup(PDFName.of('Names'));
69
+ if (!(arr instanceof PDFArray))
70
+ return [];
71
+ const out = [];
72
+ // 名前ツリーの /Names は [name1, spec1, name2, spec2, ...]
73
+ for (let i = 0; i + 1 < arr.size(); i += 2) {
74
+ const nameObj = arr.lookup(i);
75
+ const spec = arr.lookup(i + 1);
76
+ const name = decodeName(nameObj);
77
+ if (name === null)
78
+ continue;
79
+ const info = { name };
80
+ if (spec instanceof PDFDict) {
81
+ const desc = spec.lookup(PDFName.of('Desc'));
82
+ const decoded = decodeName(desc);
83
+ if (decoded)
84
+ info.description = decoded;
85
+ const rel = spec.lookup(PDFName.of('AFRelationship'));
86
+ if (rel instanceof PDFName)
87
+ info.relationship = rel.decodeText();
88
+ const efDict = spec.lookup(PDFName.of('EF'));
89
+ if (efDict instanceof PDFDict) {
90
+ const stream = efDict.get(PDFName.of('F'));
91
+ const resolved = stream ? doc.context.lookup(stream) : undefined;
92
+ const subtype = resolved && 'dict' in resolved
93
+ ? resolved.dict.get(PDFName.of('Subtype'))
94
+ : undefined;
95
+ if (subtype instanceof PDFName)
96
+ info.mimeType = subtype.decodeText();
97
+ }
98
+ }
99
+ out.push(info);
100
+ }
101
+ return out;
102
+ }
103
+ /** PDFString / PDFHexString のいずれでもテキストとして読む */
104
+ function decodeName(value) {
105
+ if (value && typeof value === 'object' && 'decodeText' in value) {
106
+ return value.decodeText();
107
+ }
108
+ return null;
109
+ }
110
+ /**
111
+ * ファイルを PDF に埋め込む。
112
+ * 同名の添付が既にある場合はエラー(名前ツリーのキーは一意であるべき)。
113
+ */
114
+ export async function attachFile(doc, options) {
115
+ const abs = resolve(options.filePath);
116
+ let bytes;
117
+ try {
118
+ bytes = await readFile(abs);
119
+ }
120
+ catch {
121
+ throw new Error(`Cannot read the file to attach: ${abs}`);
122
+ }
123
+ if (bytes.length > LIMITS.ATTACHMENT_MAX_BYTES) {
124
+ throw new Error(`Attachment is too large (${bytes.length} bytes, max ${LIMITS.ATTACHMENT_MAX_BYTES})`);
125
+ }
126
+ const name = options.name ?? basename(abs);
127
+ if (name.trim() === '') {
128
+ throw new Error('name must not be empty');
129
+ }
130
+ const existing = listEmbeddedFiles(doc);
131
+ if (existing.some((f) => f.name === name)) {
132
+ throw new Error(`An attachment named "${name}" already exists in this PDF. Pass a different "name".`);
133
+ }
134
+ const mimeType = options.mimeType ?? guessMimeType(name);
135
+ const relationship = options.relationship ?? 'Unspecified';
136
+ const stat = { creationDate: new Date(), modificationDate: new Date() };
137
+ await doc.attach(bytes, name, {
138
+ mimeType,
139
+ description: options.description,
140
+ afRelationship: toAFRelationship(relationship),
141
+ ...stat,
142
+ });
143
+ return { name, bytes: bytes.length, mimeType, relationship };
144
+ }
145
+ //# sourceMappingURL=attachment.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attachment.js","sourceRoot":"","sources":["../../src/services/attachment.ts"],"names":[],"mappings":"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"}
@@ -12,7 +12,7 @@
12
12
  * allowBreakingSignatures: true があるときのみ続行する。
13
13
  * 署名を保持する増分更新(incremental_save)は Tier C の課題。
14
14
  */
15
- import type { AddAnnotationArgs, AddBookmarksArgs, CommonEditOptions, EditResult, SetMetadataArgs, SplitResult } from '../types/index.js';
15
+ import type { AddAnnotationArgs, AddBookmarksArgs, AddWatermarkArgs, AttachFileArgs, AttachResult, CommonEditOptions, EditResult, FillFormArgs, FlattenFormArgs, FormResult, SetMetadataArgs, SplitResult, StampPageNumbersArgs, StampResult, WatermarkResult } from '../types/index.js';
16
16
  /** 入力バイト列に電子署名(/ByteRange)が含まれるかの軽量検査 */
17
17
  export declare function containsSignature(bytes: Uint8Array): boolean;
18
18
  export declare function setMetadata(args: SetMetadataArgs): Promise<EditResult>;
@@ -23,4 +23,9 @@ export declare function reorderPages(inputPath: string, order: number[], opts: C
23
23
  export declare function rotatePages(inputPath: string, rotation: number, pages: string | undefined, opts: CommonEditOptions): Promise<EditResult>;
24
24
  export declare function addBookmarks(args: AddBookmarksArgs): Promise<EditResult>;
25
25
  export declare function addAnnotation(args: AddAnnotationArgs): Promise<EditResult>;
26
+ export declare function attachFileToPdf(args: AttachFileArgs): Promise<AttachResult>;
27
+ export declare function stampPageNumbers(args: StampPageNumbersArgs): Promise<StampResult>;
28
+ export declare function addWatermark(args: AddWatermarkArgs): Promise<WatermarkResult>;
29
+ export declare function fillForm(args: FillFormArgs): Promise<FormResult>;
30
+ export declare function flattenForm(args: FlattenFormArgs): Promise<FormResult>;
26
31
  export declare function splitPdf(inputPath: string, ranges: string[], outputDir: string, prefix: string | undefined, opts: CommonEditOptions): Promise<SplitResult>;