@jasy/pdf 1.0.0-alpha.1 → 1.0.0-alpha.11

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 (279) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +35 -21
  3. package/dist/api/args.d.ts +1 -1
  4. package/dist/api/args.js +2 -5
  5. package/dist/api/color.d.ts +4 -4
  6. package/dist/api/color.js +11 -17
  7. package/dist/api/content.d.ts +20 -13
  8. package/dist/api/content.js +44 -29
  9. package/dist/api/descriptor.d.ts +2 -2
  10. package/dist/api/descriptor.js +121 -31
  11. package/dist/api/dimension.d.ts +76 -0
  12. package/dist/api/dimension.js +43 -0
  13. package/dist/api/font-url.d.ts +28 -0
  14. package/dist/api/font-url.js +134 -0
  15. package/dist/api/forms.d.ts +347 -0
  16. package/dist/api/forms.js +189 -0
  17. package/dist/api/gradient.d.ts +53 -0
  18. package/dist/api/gradient.js +97 -0
  19. package/dist/api/index.d.ts +13 -8
  20. package/dist/api/index.js +13 -24
  21. package/dist/api/insets.d.ts +19 -12
  22. package/dist/api/insets.js +19 -12
  23. package/dist/api/layout.d.ts +149 -25
  24. package/dist/api/layout.js +182 -51
  25. package/dist/api/page-builder.d.ts +40 -0
  26. package/dist/api/page-builder.js +44 -0
  27. package/dist/api/structure.d.ts +107 -13
  28. package/dist/api/structure.js +187 -88
  29. package/dist/api/table.d.ts +5 -5
  30. package/dist/api/table.js +38 -26
  31. package/dist/api/text.d.ts +90 -4
  32. package/dist/api/text.js +116 -31
  33. package/dist/assets/font-data.d.ts +2 -0
  34. package/dist/assets/font-data.js +21 -0
  35. package/dist/assets/font-data.ts +37 -0
  36. package/dist/common/color.js +1 -5
  37. package/dist/constants/page-sizes.js +3 -6
  38. package/dist/constants/pdf-parts.js +1 -4
  39. package/dist/crypto/legacy-handler.d.ts +43 -0
  40. package/dist/crypto/legacy-handler.js +145 -0
  41. package/dist/crypto/security-handler.d.ts +62 -0
  42. package/dist/crypto/security-handler.js +155 -0
  43. package/dist/crypto/webcrypto.d.ts +11 -0
  44. package/dist/crypto/webcrypto.js +62 -0
  45. package/dist/edit/acroform-reader.d.ts +70 -0
  46. package/dist/edit/acroform-reader.js +126 -0
  47. package/dist/edit/appearance.d.ts +47 -0
  48. package/dist/edit/appearance.js +171 -0
  49. package/dist/edit/document.d.ts +145 -0
  50. package/dist/edit/document.js +642 -0
  51. package/dist/edit/fill.d.ts +65 -0
  52. package/dist/edit/fill.js +424 -0
  53. package/dist/edit/flatten.d.ts +54 -0
  54. package/dist/edit/flatten.js +462 -0
  55. package/dist/edit/index.d.ts +20 -0
  56. package/dist/edit/index.js +18 -0
  57. package/dist/edit/lexer.d.ts +30 -0
  58. package/dist/edit/lexer.js +285 -0
  59. package/dist/edit/objects.d.ts +68 -0
  60. package/dist/edit/objects.js +83 -0
  61. package/dist/edit/writer.d.ts +60 -0
  62. package/dist/edit/writer.js +283 -0
  63. package/dist/elements/container-element.d.ts +50 -5
  64. package/dist/elements/container-element.js +100 -39
  65. package/dist/elements/forms/checkbox-element.d.ts +51 -0
  66. package/dist/elements/forms/checkbox-element.js +48 -0
  67. package/dist/elements/forms/choice-element.d.ts +53 -0
  68. package/dist/elements/forms/choice-element.js +59 -0
  69. package/dist/elements/forms/push-button-element.d.ts +56 -0
  70. package/dist/elements/forms/push-button-element.js +49 -0
  71. package/dist/elements/forms/radio-element.d.ts +52 -0
  72. package/dist/elements/forms/radio-element.js +49 -0
  73. package/dist/elements/forms/signature-element.d.ts +54 -0
  74. package/dist/elements/forms/signature-element.js +47 -0
  75. package/dist/elements/forms/text-field-element.d.ts +61 -0
  76. package/dist/elements/forms/text-field-element.js +54 -0
  77. package/dist/elements/image-element.d.ts +58 -6
  78. package/dist/elements/image-element.js +145 -111
  79. package/dist/elements/index.d.ts +26 -10
  80. package/dist/elements/index.js +26 -28
  81. package/dist/elements/layout/anchor-element.d.ts +23 -0
  82. package/dist/elements/layout/anchor-element.js +27 -0
  83. package/dist/elements/layout/bookmark-element.d.ts +27 -0
  84. package/dist/elements/layout/bookmark-element.js +30 -0
  85. package/dist/elements/layout/default-text-style-element.d.ts +30 -0
  86. package/dist/elements/layout/default-text-style-element.js +47 -0
  87. package/dist/elements/layout/deferred-element.d.ts +3 -3
  88. package/dist/elements/layout/deferred-element.js +4 -8
  89. package/dist/elements/layout/expanded-element.d.ts +6 -4
  90. package/dist/elements/layout/expanded-element.js +22 -17
  91. package/dist/elements/layout/keep-together-element.d.ts +42 -0
  92. package/dist/elements/layout/keep-together-element.js +85 -0
  93. package/dist/elements/layout/link-element.d.ts +33 -0
  94. package/dist/elements/layout/link-element.js +39 -0
  95. package/dist/elements/layout/padding-element.d.ts +10 -5
  96. package/dist/elements/layout/padding-element.js +25 -16
  97. package/dist/elements/layout/page-break-element.d.ts +17 -0
  98. package/dist/elements/layout/page-break-element.js +24 -0
  99. package/dist/elements/layout/page-builder-element.d.ts +32 -0
  100. package/dist/elements/layout/page-builder-element.js +45 -0
  101. package/dist/elements/layout/positioned-element.d.ts +47 -0
  102. package/dist/elements/layout/positioned-element.js +73 -0
  103. package/dist/elements/layout/repeating-header-element.d.ts +3 -3
  104. package/dist/elements/layout/repeating-header-element.js +8 -12
  105. package/dist/elements/layout/rotated-box-element.d.ts +35 -0
  106. package/dist/elements/layout/rotated-box-element.js +54 -0
  107. package/dist/elements/layout/rotated-element.d.ts +32 -0
  108. package/dist/elements/layout/rotated-element.js +41 -0
  109. package/dist/elements/layout/sized-container-element.d.ts +2 -2
  110. package/dist/elements/layout/sized-container-element.js +6 -11
  111. package/dist/elements/layout/struct-group.d.ts +28 -0
  112. package/dist/elements/layout/struct-group.js +42 -0
  113. package/dist/elements/line-element.d.ts +3 -3
  114. package/dist/elements/line-element.js +5 -10
  115. package/dist/elements/page-element.d.ts +26 -6
  116. package/dist/elements/page-element.js +72 -27
  117. package/dist/elements/pdf-document-element.d.ts +10 -4
  118. package/dist/elements/pdf-document-element.js +11 -10
  119. package/dist/elements/pdf-element.d.ts +150 -4
  120. package/dist/elements/pdf-element.js +138 -20
  121. package/dist/elements/rectangle-element.d.ts +52 -11
  122. package/dist/elements/rectangle-element.js +129 -36
  123. package/dist/elements/row-element.d.ts +45 -4
  124. package/dist/elements/row-element.js +66 -18
  125. package/dist/elements/text-element.d.ts +139 -11
  126. package/dist/elements/text-element.js +234 -57
  127. package/dist/forms/acroform.d.ts +63 -0
  128. package/dist/forms/acroform.js +427 -0
  129. package/dist/forms/appearance.d.ts +58 -0
  130. package/dist/forms/appearance.js +204 -0
  131. package/dist/forms/field.d.ts +164 -0
  132. package/dist/forms/field.js +1 -0
  133. package/dist/forms/pdf.d.ts +19 -0
  134. package/dist/forms/pdf.js +28 -0
  135. package/dist/index.d.ts +3 -3
  136. package/dist/index.js +3 -19
  137. package/dist/ir/display-list.d.ts +197 -8
  138. package/dist/ir/display-list.js +8 -2
  139. package/dist/layout/alignment.d.ts +10 -0
  140. package/dist/layout/alignment.js +6 -0
  141. package/dist/layout/box-constraints.d.ts +61 -0
  142. package/dist/layout/box-constraints.js +88 -6
  143. package/dist/layout/collect-images.d.ts +9 -0
  144. package/dist/layout/collect-images.js +34 -0
  145. package/dist/layout/fragmentation.d.ts +20 -1
  146. package/dist/layout/fragmentation.js +74 -18
  147. package/dist/layout/insets.d.ts +28 -0
  148. package/dist/layout/insets.js +20 -0
  149. package/dist/platform/browser-fs.d.ts +2 -0
  150. package/dist/platform/browser-fs.js +9 -0
  151. package/dist/platform/browser-image.d.ts +5 -0
  152. package/dist/platform/browser-image.js +13 -0
  153. package/dist/platform/node-fs.d.ts +2 -0
  154. package/dist/platform/node-fs.js +10 -0
  155. package/dist/platform/node-image.d.ts +5 -0
  156. package/dist/platform/node-image.js +9 -0
  157. package/dist/renderer/anchor-renderer.d.ts +6 -0
  158. package/dist/renderer/anchor-renderer.js +11 -0
  159. package/dist/renderer/bookmark-renderer.d.ts +6 -0
  160. package/dist/renderer/bookmark-renderer.js +11 -0
  161. package/dist/renderer/container-renderer.d.ts +3 -3
  162. package/dist/renderer/container-renderer.js +11 -28
  163. package/dist/renderer/default-text-style-renderer.d.ts +6 -0
  164. package/dist/renderer/default-text-style-renderer.js +10 -0
  165. package/dist/renderer/deferred-renderer.d.ts +3 -3
  166. package/dist/renderer/deferred-renderer.js +8 -23
  167. package/dist/renderer/emoji-image.d.ts +9 -0
  168. package/dist/renderer/emoji-image.js +68 -0
  169. package/dist/renderer/expanded-renderer.d.ts +3 -3
  170. package/dist/renderer/expanded-renderer.js +6 -21
  171. package/dist/renderer/form-field-renderer.d.ts +20 -0
  172. package/dist/renderer/form-field-renderer.js +12 -0
  173. package/dist/renderer/image-renderer.d.ts +4 -4
  174. package/dist/renderer/image-renderer.js +86 -75
  175. package/dist/renderer/index.d.ts +10 -10
  176. package/dist/renderer/index.js +10 -26
  177. package/dist/renderer/keep-together-renderer.d.ts +10 -0
  178. package/dist/renderer/keep-together-renderer.js +12 -0
  179. package/dist/renderer/line-renderer.d.ts +3 -3
  180. package/dist/renderer/line-renderer.js +13 -28
  181. package/dist/renderer/link-renderer.d.ts +6 -0
  182. package/dist/renderer/link-renderer.js +11 -0
  183. package/dist/renderer/padding-renderer.d.ts +3 -3
  184. package/dist/renderer/padding-renderer.js +6 -21
  185. package/dist/renderer/page-break-renderer.d.ts +14 -0
  186. package/dist/renderer/page-break-renderer.js +15 -0
  187. package/dist/renderer/page-builder-renderer.d.ts +6 -0
  188. package/dist/renderer/page-builder-renderer.js +11 -0
  189. package/dist/renderer/page-renderer.d.ts +2 -2
  190. package/dist/renderer/page-renderer.js +115 -77
  191. package/dist/renderer/pdf-backend.d.ts +31 -4
  192. package/dist/renderer/pdf-backend.js +287 -49
  193. package/dist/renderer/pdf-config.js +4 -7
  194. package/dist/renderer/pdf-document-class.d.ts +5 -5
  195. package/dist/renderer/pdf-document-class.js +27 -41
  196. package/dist/renderer/pdf-document-renderer.d.ts +19 -10
  197. package/dist/renderer/pdf-document-renderer.js +135 -91
  198. package/dist/renderer/pdf-renderer.d.ts +2 -2
  199. package/dist/renderer/pdf-renderer.js +165 -90
  200. package/dist/renderer/positioned-renderer.d.ts +6 -0
  201. package/dist/renderer/positioned-renderer.js +10 -0
  202. package/dist/renderer/rectangle-renderer.d.ts +3 -3
  203. package/dist/renderer/rectangle-renderer.js +49 -44
  204. package/dist/renderer/repeating-header-renderer.d.ts +3 -3
  205. package/dist/renderer/repeating-header-renderer.js +10 -26
  206. package/dist/renderer/rotated-renderer.d.ts +7 -0
  207. package/dist/renderer/rotated-renderer.js +29 -0
  208. package/dist/renderer/row-renderer.d.ts +3 -3
  209. package/dist/renderer/row-renderer.js +11 -28
  210. package/dist/renderer/struct-group-renderer.d.ts +10 -0
  211. package/dist/renderer/struct-group-renderer.js +24 -0
  212. package/dist/renderer/text-renderer.d.ts +9 -5
  213. package/dist/renderer/text-renderer.js +437 -89
  214. package/dist/text/advance.d.ts +34 -0
  215. package/dist/text/advance.js +45 -0
  216. package/dist/text/arabic.d.ts +20 -0
  217. package/dist/text/arabic.js +101 -0
  218. package/dist/text/bidi.d.ts +33 -0
  219. package/dist/text/bidi.js +94 -0
  220. package/dist/text/emoji-codepoints.d.ts +1 -0
  221. package/dist/text/emoji-codepoints.js +29 -0
  222. package/dist/text/font-fallback.d.ts +25 -0
  223. package/dist/text/font-fallback.js +38 -0
  224. package/dist/text/glyph-coverage.d.ts +15 -0
  225. package/dist/text/glyph-coverage.js +77 -0
  226. package/dist/text/line-breaker.d.ts +50 -12
  227. package/dist/text/line-breaker.js +200 -54
  228. package/dist/text/line-metrics.d.ts +49 -0
  229. package/dist/text/line-metrics.js +57 -0
  230. package/dist/text/orphans-widows.d.ts +27 -0
  231. package/dist/text/orphans-widows.js +53 -0
  232. package/dist/text/shape.d.ts +29 -0
  233. package/dist/text/shape.js +67 -0
  234. package/dist/text/text-decoration.d.ts +50 -0
  235. package/dist/text/text-decoration.js +62 -0
  236. package/dist/text/text-style.d.ts +60 -0
  237. package/dist/text/text-style.js +79 -0
  238. package/dist/text/whitespace.d.ts +4 -0
  239. package/dist/text/whitespace.js +43 -0
  240. package/dist/utils/afm-parser.d.ts +34 -0
  241. package/dist/utils/afm-parser.js +76 -17
  242. package/dist/utils/bytes.d.ts +24 -0
  243. package/dist/utils/bytes.js +76 -0
  244. package/dist/utils/dest-registry.d.ts +22 -0
  245. package/dist/utils/dest-registry.js +32 -0
  246. package/dist/utils/flex-layout.d.ts +36 -9
  247. package/dist/utils/flex-layout.js +207 -30
  248. package/dist/utils/font-metrics.d.ts +26 -1
  249. package/dist/utils/font-metrics.js +1 -2
  250. package/dist/utils/font-path.js +3 -6
  251. package/dist/utils/gsub.d.ts +36 -0
  252. package/dist/utils/gsub.js +178 -0
  253. package/dist/utils/image-helper.d.ts +6 -5
  254. package/dist/utils/image-helper.js +101 -111
  255. package/dist/utils/inflate.d.ts +16 -0
  256. package/dist/utils/inflate.js +52 -0
  257. package/dist/utils/md5.d.ts +4 -0
  258. package/dist/utils/md5.js +79 -0
  259. package/dist/utils/outline.d.ts +21 -0
  260. package/dist/utils/outline.js +79 -0
  261. package/dist/utils/pdf-object-manager.d.ts +127 -8
  262. package/dist/utils/pdf-object-manager.js +615 -141
  263. package/dist/utils/renderer-registry.d.ts +13 -1
  264. package/dist/utils/renderer-registry.js +23 -6
  265. package/dist/utils/struct-tree.d.ts +44 -0
  266. package/dist/utils/struct-tree.js +129 -0
  267. package/dist/utils/ttf-parser.d.ts +134 -2
  268. package/dist/utils/ttf-parser.js +873 -50
  269. package/dist/utils/ttf-subsetter.d.ts +1 -1
  270. package/dist/utils/ttf-subsetter.js +40 -42
  271. package/dist/utils/ua-xmp.d.ts +9 -0
  272. package/dist/utils/ua-xmp.js +32 -0
  273. package/dist/utils/utf8-to-windows1252-encoder.d.ts +6 -0
  274. package/dist/utils/utf8-to-windows1252-encoder.js +11 -4
  275. package/dist/utils/woff.d.ts +10 -0
  276. package/dist/utils/woff.js +116 -0
  277. package/dist/validators/element-validator.d.ts +2 -2
  278. package/dist/validators/element-validator.js +31 -26
  279. package/package.json +22 -2
@@ -1,7 +1,4 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Color = void 0;
4
- class Color {
1
+ export class Color {
5
2
  constructor(r, g, b, alpha = 1) {
6
3
  this.r = this.clampColorValue(r, "r");
7
4
  this.g = this.clampColorValue(g, "g");
@@ -59,4 +56,3 @@ class Color {
59
56
  return `${(r / 255).toFixed(3)} ${(g / 255).toFixed(3)} ${(b / 255).toFixed(3)}`;
60
57
  }
61
58
  }
62
- exports.Color = Color;
@@ -1,8 +1,5 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.pageFormats = exports.PageSize = void 0;
4
1
  // Definition of all possible page sizes
5
- var PageSize;
2
+ export var PageSize;
6
3
  (function (PageSize) {
7
4
  PageSize["A0"] = "a0";
8
5
  PageSize["A1"] = "a1";
@@ -45,9 +42,9 @@ var PageSize;
45
42
  PageSize["LEDGER"] = "ledger";
46
43
  PageSize["TABLOID"] = "tabloid";
47
44
  PageSize["CREDIT_CARD"] = "credit-card";
48
- })(PageSize || (exports.PageSize = PageSize = {}));
45
+ })(PageSize || (PageSize = {}));
49
46
  // Definition of page formats
50
- exports.pageFormats = {
47
+ export const pageFormats = {
51
48
  [PageSize.A0]: [2383.94, 3370.39],
52
49
  [PageSize.A1]: [1683.78, 2383.94],
53
50
  [PageSize.A2]: [1190.55, 1683.78],
@@ -1,7 +1,4 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.PDF_PARTS = void 0;
4
- exports.PDF_PARTS = {
1
+ export const PDF_PARTS = {
5
2
  HEADER: "%PDF-1.3",
6
3
  TRAILER: (size) => `trailer\n<< /Size ${size} >>\nstartxref\n%%EOF`,
7
4
  XREF: "xref", // Placeholder for xref elements
@@ -0,0 +1,43 @@
1
+ import type { SecurityHandler } from "./security-handler.js";
2
+ /** RC4. Not available in WebCrypto - for good reasons - so it is written out here, for reading only. */
3
+ declare function rc4(key: Uint8Array, data: Uint8Array): Uint8Array;
4
+ /** What the `/Encrypt` dictionary tells us, already parsed by the reader. */
5
+ export interface LegacyEncryptInfo {
6
+ revision: number;
7
+ /** `/Length` in BITS (default 40). */
8
+ keyBits: number;
9
+ o: Uint8Array;
10
+ u: Uint8Array;
11
+ /** `/P`, the permission flags, as the signed 32-bit value the algorithm feeds in. */
12
+ permissions: number;
13
+ /** The first string of the document `/ID`. */
14
+ id: Uint8Array;
15
+ encryptMetadata: boolean;
16
+ /** True when the crypt filter is AESV2 rather than RC4. */
17
+ aes: boolean;
18
+ }
19
+ export declare class StandardLegacy implements SecurityHandler {
20
+ private readonly fileKey;
21
+ private readonly aes;
22
+ private constructor();
23
+ readonly canEncrypt = false;
24
+ encryptDict(): string;
25
+ encrypt(): Promise<Uint8Array>;
26
+ decrypt(data: Uint8Array, ref?: {
27
+ num: number;
28
+ gen: number;
29
+ }): Promise<Uint8Array>;
30
+ /**
31
+ * Algorithm 1: every object is enciphered with its own key, derived from the file key plus the
32
+ * object's number and generation. Miss this and every single object decrypts to noise.
33
+ */
34
+ private objectKey;
35
+ /**
36
+ * Open a legacy-encrypted document: derive the file key from the password (algorithm 2), then CHECK it
37
+ * against `/U` (algorithm 6) so a wrong password is rejected rather than silently producing noise.
38
+ */
39
+ static open(password: string, info: LegacyEncryptInfo): SecurityHandler;
40
+ /** Algorithm 6: rebuild what `/U` must look like for this key and compare. */
41
+ private static passwordMatches;
42
+ }
43
+ export { rc4 };
@@ -0,0 +1,145 @@
1
+ import { aesCbcDecrypt } from "./webcrypto.js";
2
+ import { md5 } from "../utils/md5.js";
3
+ /**
4
+ * The OLDER standard security handlers, for READING only: R2 and R3 (RC4, 40 and 128 bit) and R4
5
+ * (RC4 or AES-128).
6
+ *
7
+ * Read-only is a deliberate asymmetry, not an omission. RC4 is broken - biased keystream, and R2 uses a
8
+ * 40-bit key that is brute-forced in minutes - and PDF 2.0 removed it outright. But it is what PDFKit,
9
+ * react-pdf and Ghostscript still produce BY DEFAULT when you ask them for a password, so refusing to
10
+ * open such a file means refusing most of the encrypted PDFs anyone actually has. Writing one would be
11
+ * a different matter: offering a user "protection" we know to be broken helps nobody, so `encrypt`
12
+ * here throws.
13
+ *
14
+ * Two things distinguish these revisions from the modern R6:
15
+ * - the file key comes from MD5 over the password, `/O`, `/P` and the first `/ID` string (algorithm 2);
16
+ * - every object gets its OWN key, derived from the file key plus its number and generation
17
+ * (algorithm 1). That is why `SecurityHandler.encrypt/decrypt` take a `ref`.
18
+ */
19
+ /** The padding every legacy revision prepends to a password (algorithm 2, step a). */
20
+ const PAD = new Uint8Array([
21
+ 0x28, 0xbf, 0x4e, 0x5e, 0x4e, 0x75, 0x8a, 0x41, 0x64, 0x00, 0x4e, 0x56, 0xff, 0xfa, 0x01, 0x08,
22
+ 0x2e, 0x2e, 0x00, 0xb6, 0xd0, 0x68, 0x3e, 0x80, 0x2f, 0x0c, 0xa9, 0xfe, 0x64, 0x53, 0x69, 0x7a,
23
+ ]);
24
+ /** A password padded (or truncated) to the 32 bytes the algorithm wants. */
25
+ function padPassword(password) {
26
+ const pw = new Uint8Array(32);
27
+ // Latin-1: the legacy handlers predate unicode passwords entirely.
28
+ const raw = Uint8Array.from(password, (c) => c.charCodeAt(0) & 0xff);
29
+ const take = Math.min(raw.length, 32);
30
+ pw.set(raw.subarray(0, take), 0);
31
+ pw.set(PAD.subarray(0, 32 - take), take);
32
+ return pw;
33
+ }
34
+ /** RC4. Not available in WebCrypto - for good reasons - so it is written out here, for reading only. */
35
+ function rc4(key, data) {
36
+ const s = new Uint8Array(256);
37
+ for (let i = 0; i < 256; i++)
38
+ s[i] = i;
39
+ for (let i = 0, j = 0; i < 256; i++) {
40
+ j = (j + s[i] + key[i % key.length]) & 0xff;
41
+ [s[i], s[j]] = [s[j], s[i]];
42
+ }
43
+ const out = new Uint8Array(data.length);
44
+ for (let k = 0, i = 0, j = 0; k < data.length; k++) {
45
+ i = (i + 1) & 0xff;
46
+ j = (j + s[i]) & 0xff;
47
+ [s[i], s[j]] = [s[j], s[i]];
48
+ out[k] = data[k] ^ s[(s[i] + s[j]) & 0xff];
49
+ }
50
+ return out;
51
+ }
52
+ const concat = (...parts) => {
53
+ const out = new Uint8Array(parts.reduce((n, p) => n + p.length, 0));
54
+ let at = 0;
55
+ for (const p of parts) {
56
+ out.set(p, at);
57
+ at += p.length;
58
+ }
59
+ return out;
60
+ };
61
+ export class StandardLegacy {
62
+ constructor(fileKey, aes) {
63
+ this.fileKey = fileKey;
64
+ this.aes = aes;
65
+ this.canEncrypt = false;
66
+ }
67
+ encryptDict() {
68
+ throw new Error("@jasy/pdf: this handler only READS legacy encryption; jasy writes AES-256 (R6) exclusively");
69
+ }
70
+ async encrypt() {
71
+ throw new Error("@jasy/pdf: refusing to write RC4 or AES-128 - they are obsolete; jasy writes AES-256 (R6)");
72
+ }
73
+ async decrypt(data, ref) {
74
+ const key = this.objectKey(ref);
75
+ if (!this.aes)
76
+ return rc4(key, data);
77
+ if (data.length <= 16)
78
+ return new Uint8Array(0); // an IV and nothing else
79
+ return aesCbcDecrypt(key, data.subarray(0, 16), data.subarray(16));
80
+ }
81
+ /**
82
+ * Algorithm 1: every object is enciphered with its own key, derived from the file key plus the
83
+ * object's number and generation. Miss this and every single object decrypts to noise.
84
+ */
85
+ objectKey(ref) {
86
+ const num = ref?.num ?? 0;
87
+ const gen = ref?.gen ?? 0;
88
+ const extra = new Uint8Array(this.aes ? 9 : 5);
89
+ extra[0] = num & 0xff;
90
+ extra[1] = (num >> 8) & 0xff;
91
+ extra[2] = (num >> 16) & 0xff;
92
+ extra[3] = gen & 0xff;
93
+ extra[4] = (gen >> 8) & 0xff;
94
+ // AES adds the literal "sAlT" - the spec really does spell it that way.
95
+ if (this.aes)
96
+ extra.set([0x73, 0x41, 0x6c, 0x54], 5);
97
+ const digest = md5(concat(this.fileKey, extra));
98
+ return digest.subarray(0, Math.min(this.fileKey.length + 5, 16));
99
+ }
100
+ /**
101
+ * Open a legacy-encrypted document: derive the file key from the password (algorithm 2), then CHECK it
102
+ * against `/U` (algorithm 6) so a wrong password is rejected rather than silently producing noise.
103
+ */
104
+ static open(password, info) {
105
+ const keyLength = info.revision === 2 ? 5 : Math.max(5, Math.floor(info.keyBits / 8));
106
+ const p = new Uint8Array(4);
107
+ new DataView(p.buffer).setInt32(0, info.permissions, true); // little-endian, signed
108
+ let input = concat(padPassword(password), info.o.subarray(0, 32), p, info.id);
109
+ // R4 with /EncryptMetadata false folds in four 0xFF bytes.
110
+ if (info.revision >= 4 && !info.encryptMetadata) {
111
+ input = concat(input, new Uint8Array([0xff, 0xff, 0xff, 0xff]));
112
+ }
113
+ let key = md5(input);
114
+ if (info.revision >= 3) {
115
+ // 50 further rounds over the first keyLength bytes only.
116
+ for (let i = 0; i < 50; i++)
117
+ key = md5(key.subarray(0, keyLength));
118
+ }
119
+ key = key.subarray(0, keyLength);
120
+ if (!StandardLegacy.passwordMatches(key, info)) {
121
+ throw new Error("@jasy/pdf: wrong password.");
122
+ }
123
+ return new StandardLegacy(key, info.aes);
124
+ }
125
+ /** Algorithm 6: rebuild what `/U` must look like for this key and compare. */
126
+ static passwordMatches(key, info) {
127
+ if (info.revision === 2) {
128
+ const expect = rc4(key, PAD);
129
+ return expect.every((b, i) => b === info.u[i]);
130
+ }
131
+ // R3+ : MD5 over the padding and the /ID, then 20 rounds of RC4 with the key XORed by the round.
132
+ let x = md5(concat(PAD, info.id));
133
+ x = rc4(key, x);
134
+ for (let round = 1; round <= 19; round++) {
135
+ const rk = Uint8Array.from(key, (b) => b ^ round);
136
+ x = rc4(rk, x);
137
+ }
138
+ // Only the first 16 bytes are meaningful; the rest of /U is arbitrary padding.
139
+ for (let i = 0; i < 16; i++)
140
+ if (x[i] !== info.u[i])
141
+ return false;
142
+ return true;
143
+ }
144
+ }
145
+ export { rc4 };
@@ -0,0 +1,62 @@
1
+ /** A handler encrypts/decrypts the strings + streams of a document and describes itself as an `/Encrypt` dict. */
2
+ export interface SecurityHandler {
3
+ /** Whether this scheme may be WRITTEN. False for the legacy revisions: we open them as a service to
4
+ * files that already exist, but handing a user protection known to be broken is not one. */
5
+ readonly canEncrypt: boolean;
6
+ /** The `/Encrypt` dictionary body (between `<<` and `>>`). Its own strings are never encrypted. */
7
+ encryptDict(): string;
8
+ /** Encrypt one string/stream. `ref` is unused for V5/R6 (one file key) but kept for future per-object schemes. */
9
+ encrypt(data: Uint8Array, ref?: {
10
+ num: number;
11
+ gen: number;
12
+ }): Promise<Uint8Array>;
13
+ /** Decrypt one string/stream (the groundwork the future "open/edit existing PDF" path reuses). */
14
+ decrypt(data: Uint8Array, ref?: {
15
+ num: number;
16
+ gen: number;
17
+ }): Promise<Uint8Array>;
18
+ }
19
+ /** What the user grants; everything defaults to allowed. Maps to the `/P` bitfield (ISO 32000-2 Table 22). */
20
+ export interface Permissions {
21
+ printing?: boolean;
22
+ copying?: boolean;
23
+ modifying?: boolean;
24
+ annotating?: boolean;
25
+ }
26
+ export interface EncryptOptions {
27
+ /** Only "aes-256" today - the seam for future algorithms. */
28
+ algorithm?: "aes-256";
29
+ userPassword: string;
30
+ /** Full-rights password; defaults to the user password if omitted. */
31
+ ownerPassword?: string;
32
+ permissions?: Permissions;
33
+ }
34
+ declare class StandardAes256 implements SecurityHandler {
35
+ private readonly fileKey;
36
+ private readonly dict;
37
+ private constructor();
38
+ static create(opts: EncryptOptions): Promise<StandardAes256>;
39
+ readonly canEncrypt = true;
40
+ encryptDict(): string;
41
+ encrypt(data: Uint8Array): Promise<Uint8Array>;
42
+ decrypt(data: Uint8Array): Promise<Uint8Array>;
43
+ /** Recover the file key from a password as a reader does: validate it against /U FIRST (so a wrong
44
+ * password is rejected, not silently turned into a garbage key), then decrypt /UE. The groundwork the
45
+ * future "open/edit existing PDF" path reuses. Throws on a wrong password. */
46
+ static recoverFileKey(userPassword: string, u: Uint8Array, ue: Uint8Array): Promise<Uint8Array>;
47
+ /**
48
+ * A handler for READING an existing file: recover its key from the password, then decrypt with it.
49
+ * The `/Encrypt` dictionary is the file's own, so this handler never has to describe one.
50
+ *
51
+ * `revision` 6 is the standard (ISO 32000-2). Revision **5** is Adobe's withdrawn 2008 extension and
52
+ * differs in ONE respect: the key is a plain SHA-256 of password + salt, without the iterated
53
+ * procedure of algorithm 2.B. The cipher is the same AES-256, which is why supporting it costs almost
54
+ * nothing - and it matters, because PDFKit and react-pdf both emit R5 when asked for AES.
55
+ */
56
+ static forReading(userPassword: string, u: Uint8Array, ue: Uint8Array, revision?: 5 | 6): Promise<SecurityHandler>;
57
+ /** Revision 5: validate against /U with a single SHA-256, then unwrap /UE the same way R6 does. */
58
+ private static recoverFileKeyR5;
59
+ }
60
+ /** The factory = the seam. Today it always returns the AES-256 handler. */
61
+ export declare function createSecurityHandler(opts: EncryptOptions): Promise<SecurityHandler>;
62
+ export { StandardAes256 };
@@ -0,0 +1,155 @@
1
+ // PDF Standard security handler. The PDFObjectManager talks to the `SecurityHandler` interface, never to a
2
+ // concrete algorithm - so adding another scheme later (a new revision, or per-object RC4/AES-128) is just a
3
+ // second implementation + a factory branch, with the writer untouched. Today we ship exactly one:
4
+ // `StandardAes256` = AES-256, V5/R6 (ISO 32000-2 / PDF 2.0), the newest standard PDF encryption.
5
+ import { aesCbcDecrypt, aesCbcDecryptNoPad, aesCbcEncrypt, aesCbcEncryptNoPad, randomBytes, sha, } from "./webcrypto.js";
6
+ const utf8 = (s) => new TextEncoder().encode(s).subarray(0, 127); // R6 truncates passwords to 127 bytes
7
+ const concat = (...parts) => {
8
+ const out = new Uint8Array(parts.reduce((n, p) => n + p.length, 0));
9
+ let o = 0;
10
+ for (const p of parts) {
11
+ out.set(p, o);
12
+ o += p.length;
13
+ }
14
+ return out;
15
+ };
16
+ const toHex = (a) => [...a].map((b) => b.toString(16).padStart(2, "0")).join("");
17
+ // Constant-time-ish equality for password validation (no early-out on the first differing byte).
18
+ const bytesEqual = (a, b) => {
19
+ if (a.length !== b.length)
20
+ return false;
21
+ let diff = 0;
22
+ for (let i = 0; i < a.length; i++)
23
+ diff |= a[i] ^ b[i];
24
+ return diff === 0;
25
+ };
26
+ // ISO 32000-2 Algorithm 2.B: the hardened password hash. Loops AES-128-CBC + SHA-256/384/512 until the
27
+ // stopping rule. `udata` is the 48-byte /U for the owner computations, empty for the user ones.
28
+ async function hash2B(password, salt, udata) {
29
+ let k = await sha(256, concat(password, salt, udata));
30
+ let e = new Uint8Array(0);
31
+ // At least 64 rounds; then stop once the last byte of E <= round - 32 (ISO 32000-2 Algorithm 2.B).
32
+ for (let round = 0; round < 64 || e[e.length - 1] > round - 32; round++) {
33
+ const block = concat(password, k, udata);
34
+ const k1 = new Uint8Array(block.length * 64);
35
+ for (let i = 0; i < 64; i++)
36
+ k1.set(block, i * block.length);
37
+ e = await aesCbcEncryptNoPad(k.subarray(0, 16), k.subarray(16, 32), k1);
38
+ let sum = 0;
39
+ for (let i = 0; i < 16; i++)
40
+ sum += e[i]; // big-endian 128-bit mod 3 == byte-sum mod 3 (256 ≡ 1 mod 3)
41
+ k = await sha([256, 384, 512][sum % 3], e);
42
+ }
43
+ return k.subarray(0, 32);
44
+ }
45
+ function permissionBits(p = {}) {
46
+ const { printing = true, copying = true, modifying = true, annotating = true } = p;
47
+ let bits = 0xfffff000 | 0b11000000; // reserved-1 bits (positions 7-8 and 13-32)
48
+ if (printing)
49
+ bits |= 4 | 2048;
50
+ if (modifying)
51
+ bits |= 8 | 1024;
52
+ if (copying)
53
+ bits |= 16 | 512;
54
+ if (annotating)
55
+ bits |= 32 | 256;
56
+ return bits | 0; // int32
57
+ }
58
+ class StandardAes256 {
59
+ constructor(fileKey, dict) {
60
+ this.fileKey = fileKey;
61
+ this.dict = dict;
62
+ this.canEncrypt = true;
63
+ }
64
+ static async create(opts) {
65
+ const user = utf8(opts.userPassword);
66
+ const owner = utf8(opts.ownerPassword ?? opts.userPassword);
67
+ const fileKey = randomBytes(32);
68
+ const p = permissionBits(opts.permissions);
69
+ // Algorithm 8: /U and /UE.
70
+ const uVS = randomBytes(8);
71
+ const uKS = randomBytes(8);
72
+ const empty = new Uint8Array(0);
73
+ const u = concat(await hash2B(user, uVS, empty), uVS, uKS); // 48 bytes
74
+ const ue = await aesCbcEncryptNoPad(await hash2B(user, uKS, empty), new Uint8Array(16), fileKey);
75
+ // Algorithm 9: /O and /OE (these also fold in /U).
76
+ const oVS = randomBytes(8);
77
+ const oKS = randomBytes(8);
78
+ const o = concat(await hash2B(owner, oVS, u), oVS, oKS); // 48 bytes
79
+ const oe = await aesCbcEncryptNoPad(await hash2B(owner, oKS, u), new Uint8Array(16), fileKey);
80
+ // Algorithm 10: /Perms (a 16-byte block, ECB == single-block CBC with a zero IV).
81
+ const perms = new Uint8Array(16);
82
+ perms[0] = p & 0xff;
83
+ perms[1] = (p >> 8) & 0xff;
84
+ perms[2] = (p >> 16) & 0xff;
85
+ perms[3] = (p >> 24) & 0xff;
86
+ perms[4] = perms[5] = perms[6] = perms[7] = 0xff;
87
+ perms[8] = 0x46; // 'F' - metadata (XMP) stays unencrypted (standard; keeps it indexer-readable)
88
+ perms[9] = 0x61; // 'a'
89
+ perms[10] = 0x64; // 'd'
90
+ perms[11] = 0x62; // 'b'
91
+ perms.set(randomBytes(4), 12);
92
+ const permsEnc = await aesCbcEncryptNoPad(fileKey, new Uint8Array(16), perms);
93
+ const dict = `/Filter /Standard /V 5 /R 6 /Length 256 /P ${p} /EncryptMetadata false ` +
94
+ `/CF << /StdCF << /CFM /AESV3 /AuthEvent /DocOpen /Length 32 >> >> /StmF /StdCF /StrF /StdCF ` +
95
+ `/U <${toHex(u)}> /UE <${toHex(ue)}> /O <${toHex(o)}> /OE <${toHex(oe)}> /Perms <${toHex(permsEnc)}>`;
96
+ return new StandardAes256(fileKey, dict);
97
+ }
98
+ encryptDict() {
99
+ return this.dict;
100
+ }
101
+ async encrypt(data) {
102
+ const iv = randomBytes(16);
103
+ const ct = await aesCbcEncrypt(this.fileKey, iv, data);
104
+ return concat(iv, ct);
105
+ }
106
+ async decrypt(data) {
107
+ return aesCbcDecrypt(this.fileKey, data.subarray(0, 16), data.subarray(16));
108
+ }
109
+ /** Recover the file key from a password as a reader does: validate it against /U FIRST (so a wrong
110
+ * password is rejected, not silently turned into a garbage key), then decrypt /UE. The groundwork the
111
+ * future "open/edit existing PDF" path reuses. Throws on a wrong password. */
112
+ static async recoverFileKey(userPassword, u, ue) {
113
+ const pw = utf8(userPassword);
114
+ // Algorithm 11: hash(password + validation salt) must equal the first 32 bytes of /U.
115
+ const check = await hash2B(pw, u.subarray(32, 40), new Uint8Array(0));
116
+ if (!bytesEqual(check, u.subarray(0, 32))) {
117
+ throw new Error("@jasy/pdf: wrong password.");
118
+ }
119
+ const intermediate = await hash2B(pw, u.subarray(40, 48), new Uint8Array(0));
120
+ return aesCbcDecryptNoPad(intermediate, new Uint8Array(16), ue);
121
+ }
122
+ /**
123
+ * A handler for READING an existing file: recover its key from the password, then decrypt with it.
124
+ * The `/Encrypt` dictionary is the file's own, so this handler never has to describe one.
125
+ *
126
+ * `revision` 6 is the standard (ISO 32000-2). Revision **5** is Adobe's withdrawn 2008 extension and
127
+ * differs in ONE respect: the key is a plain SHA-256 of password + salt, without the iterated
128
+ * procedure of algorithm 2.B. The cipher is the same AES-256, which is why supporting it costs almost
129
+ * nothing - and it matters, because PDFKit and react-pdf both emit R5 when asked for AES.
130
+ */
131
+ static async forReading(userPassword, u, ue, revision = 6) {
132
+ const key = revision === 6
133
+ ? await StandardAes256.recoverFileKey(userPassword, u, ue)
134
+ : await StandardAes256.recoverFileKeyR5(userPassword, u, ue);
135
+ return new StandardAes256(key, "");
136
+ }
137
+ /** Revision 5: validate against /U with a single SHA-256, then unwrap /UE the same way R6 does. */
138
+ static async recoverFileKeyR5(userPassword, u, ue) {
139
+ const pw = utf8(userPassword);
140
+ const check = await sha(256, concat(pw, u.subarray(32, 40)));
141
+ if (!bytesEqual(check, u.subarray(0, 32))) {
142
+ throw new Error("@jasy/pdf: wrong password.");
143
+ }
144
+ const intermediate = await sha(256, concat(pw, u.subarray(40, 48)));
145
+ return aesCbcDecryptNoPad(intermediate, new Uint8Array(16), ue);
146
+ }
147
+ }
148
+ /** The factory = the seam. Today it always returns the AES-256 handler. */
149
+ export async function createSecurityHandler(opts) {
150
+ if (opts.algorithm && opts.algorithm !== "aes-256") {
151
+ throw new Error(`@jasy/pdf: unsupported encryption algorithm "${opts.algorithm}" (only "aes-256").`);
152
+ }
153
+ return StandardAes256.create(opts);
154
+ }
155
+ export { StandardAes256 };
@@ -0,0 +1,11 @@
1
+ export declare function randomBytes(n: number): Uint8Array;
2
+ export declare function sha(bits: 256 | 384 | 512, data: Uint8Array): Promise<Uint8Array>;
3
+ /** AES-CBC with PKCS#7 padding (the AESV3 mode for PDF strings/streams). */
4
+ export declare function aesCbcEncrypt(key: Uint8Array, iv: Uint8Array, data: Uint8Array): Promise<Uint8Array>;
5
+ export declare function aesCbcDecrypt(key: Uint8Array, iv: Uint8Array, data: Uint8Array): Promise<Uint8Array>;
6
+ /** AES-CBC WITHOUT padding (`data.length` must be a multiple of 16). WebCrypto pads unconditionally, so we
7
+ * encrypt and drop the extra full pad block - the leading blocks are the true unpadded CBC. */
8
+ export declare function aesCbcEncryptNoPad(key: Uint8Array, iv: Uint8Array, data: Uint8Array): Promise<Uint8Array>;
9
+ /** AES-CBC no-padding DECRYPT (`data` a multiple of 16). We append one cipher block crafted to decrypt to a
10
+ * full 0x10 PKCS#7 pad (so WebCrypto accepts and strips it), leaving the original. */
11
+ export declare function aesCbcDecryptNoPad(key: Uint8Array, iv: Uint8Array, data: Uint8Array): Promise<Uint8Array>;
@@ -0,0 +1,62 @@
1
+ // Isomorphic crypto via the platform WebCrypto (`globalThis.crypto.subtle`) - native in the browser AND in
2
+ // Node 20+, with zero dependencies. This is the primitive layer the PDF Standard security handler (AES-256,
3
+ // R6) builds on. Everything is async (so is `render()`), so that is no constraint.
4
+ //
5
+ // One wrinkle: WebCrypto's AES-CBC ALWAYS applies PKCS#7 padding and offers no raw/no-pad mode. The PDF R6
6
+ // key derivation (Algorithm 2.B, /UE, /OE, /Perms) needs UNPADDED AES, so we synthesize it from the padded
7
+ // primitive (encrypt: drop the trailing pad block; decrypt: append a block that decrypts to a full pad, then
8
+ // let WebCrypto strip it). Document strings/streams use the padded primitive directly - that IS AESV3.
9
+ const subtle = () => {
10
+ const c = globalThis.crypto;
11
+ if (!c?.subtle) {
12
+ throw new Error("@jasy/pdf: PDF encryption needs WebCrypto (globalThis.crypto.subtle), unavailable here.");
13
+ }
14
+ return c.subtle;
15
+ };
16
+ export function randomBytes(n) {
17
+ const b = new Uint8Array(n);
18
+ globalThis.crypto.getRandomValues(b);
19
+ return b;
20
+ }
21
+ // WebCrypto's types want a BufferSource backed by ArrayBuffer; our Uint8Arrays are ArrayBuffer-backed, so
22
+ // this cast at the platform boundary is safe (TS 5.7+ just can't prove it isn't a SharedArrayBuffer).
23
+ const buf = (a) => a;
24
+ export async function sha(bits, data) {
25
+ return new Uint8Array(await subtle().digest(`SHA-${bits}`, buf(data)));
26
+ }
27
+ async function aesKey(key, usage) {
28
+ return subtle().importKey("raw", buf(key), { name: "AES-CBC" }, false, [usage]);
29
+ }
30
+ /** AES-CBC with PKCS#7 padding (the AESV3 mode for PDF strings/streams). */
31
+ export async function aesCbcEncrypt(key, iv, data) {
32
+ return new Uint8Array(await subtle().encrypt({ name: "AES-CBC", iv: buf(iv) }, await aesKey(key, "encrypt"), buf(data)));
33
+ }
34
+ export async function aesCbcDecrypt(key, iv, data) {
35
+ return new Uint8Array(await subtle().decrypt({ name: "AES-CBC", iv: buf(iv) }, await aesKey(key, "decrypt"), buf(data)));
36
+ }
37
+ /** AES-CBC WITHOUT padding (`data.length` must be a multiple of 16). WebCrypto pads unconditionally, so we
38
+ * encrypt and drop the extra full pad block - the leading blocks are the true unpadded CBC. */
39
+ export async function aesCbcEncryptNoPad(key, iv, data) {
40
+ if (data.length % 16 !== 0)
41
+ throw new Error("aesCbcEncryptNoPad: data must be a multiple of 16 bytes.");
42
+ return (await aesCbcEncrypt(key, iv, data)).subarray(0, data.length);
43
+ }
44
+ const xor16 = (a, b) => {
45
+ const o = new Uint8Array(16);
46
+ for (let i = 0; i < 16; i++)
47
+ o[i] = a[i] ^ b[i];
48
+ return o;
49
+ };
50
+ /** AES-CBC no-padding DECRYPT (`data` a multiple of 16). We append one cipher block crafted to decrypt to a
51
+ * full 0x10 PKCS#7 pad (so WebCrypto accepts and strips it), leaving the original. */
52
+ export async function aesCbcDecryptNoPad(key, iv, data) {
53
+ if (data.length === 0 || data.length % 16 !== 0)
54
+ throw new Error("aesCbcDecryptNoPad: data must be a non-zero multiple of 16 bytes.");
55
+ const prev = data.length >= 16 ? data.subarray(data.length - 16) : iv;
56
+ // ECB(x) == first block of CBC(iv=0, x); we want the appended block to decrypt to 0x10*16 after the CBC xor.
57
+ const appended = await aesCbcEncryptNoPad(key, new Uint8Array(16), xor16(new Uint8Array(16).fill(16), prev));
58
+ const withPad = new Uint8Array(data.length + 16);
59
+ withPad.set(data);
60
+ withPad.set(appended, data.length);
61
+ return aesCbcDecrypt(key, iv, withPad);
62
+ }
@@ -0,0 +1,70 @@
1
+ import { type PdfDocument } from "./document.js";
2
+ /**
3
+ * Reads an existing document's `/AcroForm` into a flat list of fields.
4
+ *
5
+ * The field tree is the part every producer lays out differently, and where a naive reader goes wrong:
6
+ *
7
+ * - **A field and its widget need not be the same object.** pdf-lib splits them (the field carries
8
+ * `/T`, `/FT` and `/V`; its `/Kids` are bare widget annotations); PDFKit and our own writer merge
9
+ * them into one. Both are legal, so the walk must cope with either.
10
+ * - **Attributes are inherited.** `/FT`, `/Ff`, `/V` and `/MaxLen` all pass down the tree, so a child may
11
+ * state none of them and still have a type, flags and a value (ISO 32000-1 table 220).
12
+ * - **Names are hierarchical.** The name you address a field by is the `/T` values along the path,
13
+ * joined with dots - `address.street`, not `street`.
14
+ * - **A field may own several widgets** (a radio group, or one field shown on several pages). It is
15
+ * still ONE field with one value.
16
+ */
17
+ /** One field found in an existing document, in the terms a caller thinks in. */
18
+ export interface ReadField {
19
+ /** The fully qualified name: the `/T` values from the root down, joined with dots. */
20
+ name: string;
21
+ /** `Tx` text · `Btn` button (check box, radio, push button) · `Ch` choice · `Sig` signature. */
22
+ type: "Tx" | "Btn" | "Ch" | "Sig";
23
+ /** The current value as text, if it has one. A button's value is a name (`Yes`, `Off`). */
24
+ value?: string;
25
+ /** A multi-select choice field's values. */
26
+ values?: string[];
27
+ /** The `/Ff` field flags, already merged with anything inherited. */
28
+ flags: number;
29
+ /** `/MaxLen`: the longest text the field accepts, in characters. Inherited like the type. */
30
+ maxLen?: number;
31
+ /** The selectable options of a choice field, as `[export, label]`. */
32
+ options?: Array<{
33
+ value: string;
34
+ label: string;
35
+ }>;
36
+ /** For a check box or radio group: every export name its widgets can take, besides `Off`. */
37
+ onValues?: string[];
38
+ /** The object number of the FIELD dictionary - where `/V` lives, and what an update rewrites. */
39
+ objNum?: number;
40
+ /** The widgets that display this field. A radio group has one per button, and each knows only its
41
+ * OWN export name, which is what its `/AS` has to be set to. */
42
+ widgets: ReadWidget[];
43
+ /** True when no widget carries an appearance stream - filling it means generating one. */
44
+ needsAppearance: boolean;
45
+ }
46
+ /** One widget annotation belonging to a field. */
47
+ export interface ReadWidget {
48
+ num?: number;
49
+ /** The export names this particular widget can show (its `/AP /N` keys, minus `Off`). */
50
+ onValues: string[];
51
+ hasAppearance: boolean;
52
+ }
53
+ /** What a document says about its form as a whole. */
54
+ export interface ReadForm {
55
+ fields: ReadField[];
56
+ /** The form asks viewers to regenerate every appearance. */
57
+ needAppearances: boolean;
58
+ /**
59
+ * The document carries an XFA packet beside the AcroForm (a "hybrid" form). Filling only the AcroForm
60
+ * side can be ignored by a viewer that prefers XFA, so this is reported rather than glossed over.
61
+ */
62
+ hasXfa: boolean;
63
+ /** True when the cross-reference table had to be rebuilt to read the file at all. */
64
+ recovered: boolean;
65
+ }
66
+ /**
67
+ * Read the form of a loaded document. Returns `undefined` when there is no `/AcroForm` at all - that is
68
+ * a plain PDF, not an error.
69
+ */
70
+ export declare function readAcroForm(doc: PdfDocument): ReadForm | undefined;