@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
@@ -0,0 +1,126 @@
1
+ import { PdfEncryptedError } from "./document.js";
2
+ import { get, isDict, isRef, nameOf, numberOf, textOf } from "./objects.js";
3
+ const FIELD_TYPES = new Set(["Tx", "Btn", "Ch", "Sig"]);
4
+ /** Join a parent's qualified name with a child's partial name. */
5
+ const joinName = (parent, part) => part === undefined || part === "" ? parent : parent === "" ? part : `${parent}.${part}`;
6
+ /** `/Opt` entries are either a bare string or a `[export, label]` pair. */
7
+ function readOptions(doc, raw) {
8
+ if (!Array.isArray(raw))
9
+ return undefined;
10
+ return raw.map((entry) => {
11
+ const e = doc.resolve(entry);
12
+ if (Array.isArray(e)) {
13
+ const value = textOf(doc.resolve(e[0])) ?? "";
14
+ return { value, label: textOf(doc.resolve(e[1])) ?? value };
15
+ }
16
+ const value = textOf(e) ?? "";
17
+ return { value, label: value };
18
+ });
19
+ }
20
+ /** A value that may be a single string/name or an array of them (a multi-select choice). */
21
+ function readValue(doc, raw) {
22
+ const v = doc.resolve(raw);
23
+ if (Array.isArray(v)) {
24
+ const values = v.map((x) => textOf(doc.resolve(x)) ?? nameOf(doc.resolve(x)) ?? "");
25
+ return { value: values[0], values };
26
+ }
27
+ const single = textOf(v) ?? nameOf(v);
28
+ return { value: single, values: undefined };
29
+ }
30
+ /** The export names a button widget can show, read from the keys of its `/AP /N` state dictionary. */
31
+ function readOnValues(doc, widget) {
32
+ const normal = doc.resolve(get(doc.resolve(get(widget, "AP")), "N"));
33
+ if (normal === undefined || !isDict(normal))
34
+ return [];
35
+ return [...normal.map.keys()].filter((k) => k !== "Off");
36
+ }
37
+ /**
38
+ * Read the form of a loaded document. Returns `undefined` when there is no `/AcroForm` at all - that is
39
+ * a plain PDF, not an error.
40
+ */
41
+ export function readAcroForm(doc) {
42
+ // Until the password has been accepted, every string in an encrypted file is still ciphertext, so a
43
+ // field "name" read out of it is noise. Handing that back would be the silent guess this reader exists
44
+ // to avoid. `PdfDocument.open(bytes, { password })` is what makes it readable.
45
+ if (!doc.isReadable) {
46
+ throw new PdfEncryptedError("this PDF is encrypted; open it with PdfDocument.open(bytes, { password }) first");
47
+ }
48
+ const acro = doc.lookup(doc.catalog, "AcroForm");
49
+ if (acro === undefined || !isDict(acro))
50
+ return undefined;
51
+ const roots = doc.resolve(get(acro, "Fields"));
52
+ const fields = [];
53
+ if (Array.isArray(roots)) {
54
+ for (const ref of roots)
55
+ walk(doc, ref, "", { type: undefined, flags: 0, maxLen: undefined, value: undefined }, fields, 0);
56
+ }
57
+ return {
58
+ fields,
59
+ needAppearances: doc.resolve(get(acro, "NeedAppearances")) === true,
60
+ hasXfa: get(acro, "XFA") !== undefined,
61
+ recovered: doc.recovered,
62
+ };
63
+ }
64
+ /**
65
+ * Walk one node of the field tree. A node is a FIELD when it has a type (its own or inherited); its
66
+ * `/Kids` are either more fields or the widgets that display it. The two are told apart by the kids
67
+ * themselves: a widget has no `/T` and no `/FT` of its own.
68
+ */
69
+ function walk(doc, ref, parentName, parent, out, depth) {
70
+ if (depth > 32)
71
+ return; // a cyclic file must not hang us
72
+ const node = doc.resolve(ref);
73
+ if (node === undefined || !isDict(node))
74
+ return;
75
+ const name = joinName(parentName, textOf(doc.lookup(node, "T")));
76
+ const type = nameOf(doc.lookup(node, "FT")) ?? parent.type;
77
+ const flags = numberOf(doc.lookup(node, "Ff")) ?? parent.flags;
78
+ const maxLen = numberOf(doc.lookup(node, "MaxLen")) ?? parent.maxLen;
79
+ const rawValue = get(node, "V") ?? parent.value;
80
+ const kidsRaw = doc.lookup(node, "Kids");
81
+ const kids = Array.isArray(kidsRaw) ? kidsRaw : [];
82
+ // Kids that are themselves fields (they name themselves or state a type) mean this node is only a
83
+ // branch; otherwise the kids are this field's widget annotations.
84
+ const childFields = kids.filter((k) => {
85
+ const kid = doc.resolve(k);
86
+ return get(kid, "T") !== undefined || get(kid, "FT") !== undefined;
87
+ });
88
+ if (childFields.length > 0) {
89
+ for (const k of childFields)
90
+ walk(doc, k, name, { type, flags, maxLen, value: rawValue }, out, depth + 1);
91
+ return;
92
+ }
93
+ if (type === undefined || !FIELD_TYPES.has(type))
94
+ return;
95
+ // The widgets: either the kids, or this very object when field and widget are merged.
96
+ const widgetRefs = kids.length > 0 ? kids : [ref];
97
+ const widgets = widgetRefs.map((w) => doc.resolve(w));
98
+ const readWidgets = widgetRefs.map((w, i) => ({
99
+ num: isRef(w) ? w.num : undefined,
100
+ onValues: type === "Btn" ? readOnValues(doc, widgets[i]) : [],
101
+ hasAppearance: get(widgets[i], "AP") !== undefined,
102
+ }));
103
+ let { value, values } = readValue(doc, rawValue);
104
+ const onValues = type === "Btn" ? [...new Set(readWidgets.flatMap((w) => w.onValues))] : undefined;
105
+ // A button's value is a NAME by spec (`/Yes`), and that is what its `/AP` state keys are. Producers
106
+ // disagree: PDFKit writes the string `(Yes)`, react-pdf the string `(/Yes)`. All three mean the same
107
+ // export value, so the leading slash is dropped - otherwise comparing a value against the appearance
108
+ // states, which is exactly what filling does, would silently never match.
109
+ if (type === "Btn" && value !== undefined && value.startsWith("/"))
110
+ value = value.slice(1);
111
+ out.push({
112
+ name,
113
+ type: type,
114
+ value,
115
+ values,
116
+ flags,
117
+ maxLen,
118
+ options: readOptions(doc, doc.lookup(node, "Opt")),
119
+ onValues: onValues && onValues.length > 0 ? onValues : undefined,
120
+ objNum: isRef(ref) ? ref.num : undefined,
121
+ widgets: readWidgets,
122
+ // Only meaningful when we know where the widgets are; a field whose widgets all lack /AP has to
123
+ // have one generated before its value can be seen.
124
+ needsAppearance: readWidgets.every((w) => !w.hasAppearance),
125
+ });
126
+ }
@@ -0,0 +1,47 @@
1
+ import { PDFObjectManager } from "../utils/pdf-object-manager.js";
2
+ import type { FieldAlign, FieldStyle } from "../forms/field.js";
3
+ import type { PdfDocument } from "./document.js";
4
+ import type { ReadField } from "./acroform-reader.js";
5
+ import { type PdfObject } from "./objects.js";
6
+ /**
7
+ * Font metrics for measuring. Created per call rather than kept in a module-level variable: jasy has no
8
+ * global object manager by design (a shared one caused the mixed-page-size bug), and a reader that holds
9
+ * mutable state at module scope would quietly reintroduce it.
10
+ */
11
+ export declare function fontMetrics(): PDFObjectManager;
12
+ /** What the widget says about how it should look. Missing entries fall back to the sober defaults. */
13
+ export interface WidgetLook {
14
+ width: number;
15
+ height: number;
16
+ style: FieldStyle;
17
+ align: FieldAlign;
18
+ multiline: boolean;
19
+ password: boolean;
20
+ }
21
+ export declare function readLook(doc: PdfDocument, widget: PdfObject | undefined, field: ReadField): WidgetLook | undefined;
22
+ /**
23
+ * The content stream for a text or choice field showing `value`, and the BBox it is drawn in.
24
+ * Returns `undefined` for a kind we do not draw (a button keeps its own state pictures).
25
+ */
26
+ export declare function bakeAppearance(look: WidgetLook, field: ReadField, value: string | undefined, values: string[] | undefined, metrics?: PDFObjectManager): {
27
+ content: string;
28
+ bbox: [number, number, number, number];
29
+ } | undefined;
30
+ /** The `/DR` a regenerated appearance refers to: our own Helvetica, added when the form has none. */
31
+ export declare const HELV_FONT = "<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding /WinAnsiEncoding >>";
32
+ /** True when the form's `/DR /Font` already offers a usable `/Helv`. */
33
+ export declare function hasHelv(doc: PdfDocument): boolean;
34
+ /**
35
+ * The two state pictures a check box or radio button needs, for a widget that ships none at all.
36
+ *
37
+ * A button's `/AP /N` is a dictionary of STATES, not a picture of a value, so an existing one stays
38
+ * valid however often the value changes - this is only for the producers that write none (PDFKit,
39
+ * react-pdf) and leave every box empty until a viewer redraws it. `on` is the export name the widget
40
+ * switches to.
41
+ */
42
+ export declare function bakeButtonStates(look: WidgetLook, on: string, radio: boolean): {
43
+ on: string;
44
+ off: string;
45
+ bbox: [number, number, number, number];
46
+ state: string;
47
+ };
@@ -0,0 +1,171 @@
1
+ import { PDFObjectManager, FontStyle } from "../utils/pdf-object-manager.js";
2
+ import { checkboxOff, checkboxOn, fieldDrawSize, listBoxFace, radioOff, radioOn, textFieldFace, wrapFieldValue, } from "../forms/appearance.js";
3
+ import { Color } from "../common/color.js";
4
+ import { get, isDict, isString, numberOf } from "./objects.js";
5
+ import { latin1FromBytes } from "../utils/bytes.js";
6
+ /**
7
+ * Re-drawing a field's appearance after its value changed.
8
+ *
9
+ * A widget's appearance is a picture of its value. Change the value and the picture is wrong - which is
10
+ * why filling used to DROP it and ask the viewer to redraw (`/NeedAppearances`). That works, but it hands
11
+ * control of how the value looks to whatever program opens the file, and it leaves the field invisible in
12
+ * anything that does not honour the flag. Baking it is what jasy does when it CREATES a field; there is
13
+ * no reason the edit side should be different.
14
+ *
15
+ * The face builders in `forms/appearance.ts` need nothing from the writer - plain numbers, a style and
16
+ * pre-measured lines - so the only pieces to bring are the font metrics and the widget's own style, and
17
+ * the latter is readable straight out of the file: `/DA` for size and colour, `/MK` for the border and
18
+ * background, `/Q` for alignment, `/Ff` for multiline.
19
+ */
20
+ const FF_MULTILINE = 1 << 12;
21
+ const FF_PASSWORD = 1 << 13;
22
+ /**
23
+ * Font metrics for measuring. Created per call rather than kept in a module-level variable: jasy has no
24
+ * global object manager by design (a shared one caused the mixed-page-size bug), and a reader that holds
25
+ * mutable state at module scope would quietly reintroduce it.
26
+ */
27
+ export function fontMetrics() {
28
+ const om = new PDFObjectManager();
29
+ om.registerFont("Helvetica", FontStyle.Normal, "Helvetica");
30
+ return om;
31
+ }
32
+ /** `[r g b]` in 0..1, as `/MK` writes colours. An empty array means "none". */
33
+ function colorOf(raw) {
34
+ if (!Array.isArray(raw) || raw.length === 0)
35
+ return undefined;
36
+ const n = raw.map((x) => (typeof x === "number" ? x : 0));
37
+ if (n.length === 1)
38
+ return new Color(n[0] * 255, n[0] * 255, n[0] * 255); // grey
39
+ if (n.length >= 3)
40
+ return new Color(n[0] * 255, n[1] * 255, n[2] * 255);
41
+ return undefined;
42
+ }
43
+ /**
44
+ * The bits of `/DA` we need: the font size and the fill colour.
45
+ *
46
+ * `/DA` is a content-stream fragment, not a dictionary - `"/Helv 12 Tf 0 0 0 rg"`. Size `0` is the PDF
47
+ * convention for "auto, fit the box", which is what a viewer would do; we resolve it the same way the
48
+ * writer does when it bakes.
49
+ */
50
+ function fromDA(da) {
51
+ let size = 0;
52
+ let color = new Color(0, 0, 0);
53
+ if (da) {
54
+ const tf = /(-?[\d.]+)\s+Tf/.exec(da);
55
+ if (tf)
56
+ size = Number(tf[1]);
57
+ const rg = /(-?[\d.]+)\s+(-?[\d.]+)\s+(-?[\d.]+)\s+rg/.exec(da);
58
+ const g = /(-?[\d.]+)\s+g\b/.exec(da);
59
+ if (rg)
60
+ color = new Color(Number(rg[1]) * 255, Number(rg[2]) * 255, Number(rg[3]) * 255);
61
+ else if (g)
62
+ color = new Color(Number(g[1]) * 255, Number(g[1]) * 255, Number(g[1]) * 255);
63
+ }
64
+ return { size, color };
65
+ }
66
+ export function readLook(doc, widget, field) {
67
+ const rect = doc.lookup(widget, "Rect");
68
+ if (!Array.isArray(rect) || rect.length < 4)
69
+ return undefined;
70
+ const n = rect.map((x) => (typeof x === "number" ? x : NaN));
71
+ if (n.some(Number.isNaN))
72
+ return undefined;
73
+ const width = Math.abs(n[2] - n[0]);
74
+ const height = Math.abs(n[3] - n[1]);
75
+ // /DA may be on the widget, the field, or the form - it is inheritable.
76
+ const daOf = (o) => {
77
+ const v = doc.lookup(o, "DA");
78
+ return isString(v) ? latin1FromBytes(v.bytes) : undefined;
79
+ };
80
+ const acro = doc.lookup(doc.catalog, "AcroForm");
81
+ const da = daOf(widget) ??
82
+ (field.objNum !== undefined ? daOf(doc.getObject(field.objNum)) : undefined) ??
83
+ daOf(acro);
84
+ const { size, color } = fromDA(da);
85
+ const mk = doc.resolve(get(widget, "MK"));
86
+ const border = isDict(mk) ? colorOf(doc.lookup(mk, "BC")) : undefined;
87
+ const background = isDict(mk) ? colorOf(doc.lookup(mk, "BG")) : undefined;
88
+ const bs = doc.resolve(get(widget, "BS"));
89
+ const borderWidth = border ? (numberOf(isDict(bs) ? doc.lookup(bs, "W") : undefined) ?? 1) : 0;
90
+ // /Q is inheritable in the same chain as /DA: widget, then field, then the form.
91
+ const q = numberOf(doc.lookup(widget, "Q")) ??
92
+ (field.objNum !== undefined
93
+ ? numberOf(doc.lookup(doc.getObject(field.objNum), "Q"))
94
+ : undefined) ??
95
+ numberOf(doc.lookup(acro, "Q")) ??
96
+ 0;
97
+ return {
98
+ width,
99
+ height,
100
+ style: { border, background, color, fontSize: size, borderWidth },
101
+ align: q === 1 ? "center" : q === 2 ? "right" : "left",
102
+ multiline: (field.flags & FF_MULTILINE) !== 0,
103
+ password: (field.flags & FF_PASSWORD) !== 0,
104
+ };
105
+ }
106
+ /** The auto-size a viewer would pick: fit the cap height into the box, clamped to something readable. */
107
+ function drawSize(look, capHeight) {
108
+ return fieldDrawSize(look.style.fontSize, look.height, look.style.borderWidth, capHeight);
109
+ }
110
+ /**
111
+ * The content stream for a text or choice field showing `value`, and the BBox it is drawn in.
112
+ * Returns `undefined` for a kind we do not draw (a button keeps its own state pictures).
113
+ */
114
+ export function bakeAppearance(look, field, value, values, metrics) {
115
+ if (field.type !== "Tx" && field.type !== "Ch")
116
+ return undefined;
117
+ const om = metrics ?? fontMetrics();
118
+ const { capHeight } = om.getFontDecoration("Helvetica", FontStyle.Normal);
119
+ const size = drawSize(look, capHeight);
120
+ const innerWidth = Math.max(1, look.width - 2 * (2 + look.style.borderWidth));
121
+ const measure = (t) => ({
122
+ text: t,
123
+ width: om.getStringWidth(t, "Helvetica", size, FontStyle.Normal),
124
+ });
125
+ let content;
126
+ if (field.type === "Ch" && field.options && look.height > size * 2) {
127
+ // A list box draws every option, with the selected ones highlighted.
128
+ const chosen = new Set(values ?? (value !== undefined ? [value] : []));
129
+ const rows = field.options.map((o) => ({
130
+ ...measure(o.label),
131
+ selected: chosen.has(o.value),
132
+ }));
133
+ content = listBoxFace(look.width, look.height, look.style, rows, capHeight, size, "Helv");
134
+ }
135
+ else {
136
+ // A choice stores an EXPORT value but shows its label - "DE" in /V, "Germany" on screen.
137
+ const display = field.type === "Ch" ? (field.options?.find((o) => o.value === value)?.label ?? value) : value;
138
+ const shown = look.password ? "•".repeat([...(display ?? "")].length) : (display ?? "");
139
+ const lines = shown
140
+ ? wrapFieldValue(shown, "Helvetica", size, FontStyle.Normal, innerWidth, om, look.multiline).map(measure)
141
+ : [];
142
+ content = textFieldFace(look.width, look.height, look.style, lines, capHeight, size, "Helv", look.multiline, look.align);
143
+ }
144
+ return { content, bbox: [0, 0, look.width, look.height] };
145
+ }
146
+ /** The `/DR` a regenerated appearance refers to: our own Helvetica, added when the form has none. */
147
+ export const HELV_FONT = "<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding /WinAnsiEncoding >>";
148
+ /** True when the form's `/DR /Font` already offers a usable `/Helv`. */
149
+ export function hasHelv(doc) {
150
+ const dr = doc.lookup(doc.lookup(doc.catalog, "AcroForm"), "DR");
151
+ const fonts = doc.lookup(dr, "Font");
152
+ return isDict(fonts) && fonts.map.has("Helv");
153
+ }
154
+ /**
155
+ * The two state pictures a check box or radio button needs, for a widget that ships none at all.
156
+ *
157
+ * A button's `/AP /N` is a dictionary of STATES, not a picture of a value, so an existing one stays
158
+ * valid however often the value changes - this is only for the producers that write none (PDFKit,
159
+ * react-pdf) and leave every box empty until a viewer redraws it. `on` is the export name the widget
160
+ * switches to.
161
+ */
162
+ export function bakeButtonStates(look, on, radio) {
163
+ const w = look.width;
164
+ const h = look.height;
165
+ return {
166
+ on: radio ? radioOn(w, h, look.style) : checkboxOn(w, h, look.style),
167
+ off: radio ? radioOff(w, h, look.style) : checkboxOff(w, h, look.style),
168
+ bbox: [0, 0, w, h],
169
+ state: on,
170
+ };
171
+ }
@@ -0,0 +1,145 @@
1
+ import { type PdfDict, type PdfObject, type PdfStream } from "./objects.js";
2
+ declare const EMPTY: Uint8Array<ArrayBuffer>;
3
+ /** What `PdfDocument.load` accepts. */
4
+ export interface LoadOptions {
5
+ /** Ceiling on what ONE stream may inflate to, in bytes (default 64 MB). Raise it for a document that
6
+ * is genuinely enormous; a stream past it throws `PdfStreamTooLargeError` rather than being read. */
7
+ maxStreamSize?: number;
8
+ }
9
+ /** What `PdfDocument.open` accepts - everything `load` takes, plus the password. */
10
+ export interface OpenOptions extends LoadOptions {
11
+ password?: string;
12
+ }
13
+ /** Thrown when a file cannot be opened because of its encryption - always saying which of the reasons it
14
+ * is: no password, wrong password, or a revision we do not implement. */
15
+ export declare class PdfEncryptedError extends Error {
16
+ constructor(message: string);
17
+ }
18
+ export declare class PdfDocument {
19
+ readonly bytes: Uint8Array;
20
+ private readonly xref;
21
+ private readonly cache;
22
+ /** Object streams already unpacked, by their object number. */
23
+ private readonly objStmCache;
24
+ /** Set once an encrypted file has been opened with its password. */
25
+ private security?;
26
+ /** Decrypted stream payloads, keyed by the stream's data offset (unique per stream). */
27
+ private readonly plainStreams;
28
+ /** True when the cross-reference index was unusable and had to be rebuilt by scanning. Surfaced so
29
+ * a caller can report it - a silently repaired file is exactly the kind of guess we avoid. */
30
+ recovered: boolean;
31
+ trailer: PdfDict;
32
+ /** Whether this file indexes itself with an xref STREAM rather than a classic table. An incremental
33
+ * update has to follow suit: mixing the two leaves readers that understand only one kind unable to
34
+ * walk the chain back. */
35
+ usesXrefStream: boolean;
36
+ /** Ceiling on what one stream may inflate to; see `inflate.ts`. */
37
+ private maxStreamSize;
38
+ private constructor();
39
+ static load(bytes: Uint8Array, opts?: LoadOptions): PdfDocument;
40
+ /**
41
+ * Open a document, decrypting it when it is password-protected.
42
+ *
43
+ * Async because key derivation and AES are (WebCrypto), and because decryption happens ONCE here,
44
+ * eagerly, rather than at every read: that keeps `getObject`, `streamData` and the whole form layer
45
+ * above them synchronous, which is the same trade the writer makes (`structure.ts` builds the handler
46
+ * before the synchronous constructor).
47
+ */
48
+ static open(bytes: Uint8Array, opts?: OpenOptions): Promise<PdfDocument>;
49
+ /**
50
+ * Build the handler for this file's `/Encrypt` dictionary, or say precisely why we cannot.
51
+ *
52
+ * Every refusal names its reason: no password given, wrong password, or an encryption revision we do
53
+ * not implement. "Cannot open this file" would be the unhelpful answer, and guessing would be worse.
54
+ */
55
+ private securityHandlerFor;
56
+ /**
57
+ * Which cipher and key length actually apply to the streams.
58
+ *
59
+ * For V4 the answer is NOT the top-level `/Length`: the document names a crypt filter in `/StmF` and
60
+ * that filter carries its own `/CFM` and `/Length` (in BYTES, where the top-level one is in bits).
61
+ * `/Identity` means the streams are not enciphered at all - decrypting them anyway would destroy them.
62
+ */
63
+ private streamCipher;
64
+ /**
65
+ * Decrypt every string and stream, once, in place.
66
+ *
67
+ * Three things are deliberately NOT decrypted, because they were never encrypted (ISO 32000-1 7.6.2):
68
+ * the `/Encrypt` dictionary's own strings, a cross-reference stream, and strings of objects that live
69
+ * inside an object stream - the object stream was enciphered as a whole, so its members come out in
70
+ * the clear already.
71
+ */
72
+ private decryptEverything;
73
+ /** The byte offset in the `startxref` trailer at the very end of the file. */
74
+ private findStartXref;
75
+ /** Read the index at `startxref`, then walk `/Prev` back through earlier revisions. Entries already
76
+ * known win: the NEWEST revision is read first, and an older one must not overwrite it. */
77
+ private readXrefChain;
78
+ /** One index section, either format. Returns its trailer dictionary. */
79
+ private readXrefSection;
80
+ /** The classic table: `xref`, then `start count` subsections of 20-byte entries, then `trailer`. */
81
+ private readXrefTable;
82
+ /**
83
+ * An xref STREAM: fixed-width binary rows, widths in `/W`, object numbers from `/Index`. Row type 1
84
+ * is a plain byte offset, type 2 points into an object stream, type 0 is a free slot.
85
+ */
86
+ private readXrefStream;
87
+ /** Record an entry unless a NEWER revision already claimed that object number. */
88
+ private note;
89
+ /**
90
+ * Last resort: scan the whole file for `N G obj` headers and believe those. Used when the index is
91
+ * missing or does not lead anywhere - and flagged via `recovered`, never silently.
92
+ */
93
+ private rebuildByScanning;
94
+ /** The object with that number, or `undefined` when the file does not have it. */
95
+ getObject(num: number): PdfObject | undefined;
96
+ /** Parse `N G obj … endobj` at a byte offset, checking that it really is object `num`. */
97
+ private parseObjectAt;
98
+ /** Unpack an object stream once, then serve its members from the cache. */
99
+ private objectFromStream;
100
+ /** Follow indirect references until a direct object is reached. */
101
+ resolve(o: PdfObject | undefined): PdfObject | undefined;
102
+ /** A dictionary entry, with the reference already followed. */
103
+ lookup(o: PdfObject | undefined, key: string): PdfObject | undefined;
104
+ /**
105
+ * The file is password-protected. Every string and stream in it is enciphered, so anything read out
106
+ * of it is ciphertext until a decryption path exists - which is why the form layer refuses rather
107
+ * than handing back nonsense field names.
108
+ */
109
+ get isEncrypted(): boolean;
110
+ /** True once the password has been accepted and the contents decrypted, so what is read is plaintext.
111
+ * Always true for a document that was never encrypted. */
112
+ get isReadable(): boolean;
113
+ /**
114
+ * Encipher a value that is about to be written back INTO this document, or `undefined` when the
115
+ * document is not encrypted and the value goes in as it is.
116
+ *
117
+ * Needed because we decrypt everything on open: a string we then hand back to the writer is plaintext
118
+ * in memory, and writing it into an encrypted file unchanged would both leak it and produce a document
119
+ * that contradicts its own /Encrypt declaration.
120
+ */
121
+ encryptForWrite(data: Uint8Array): Promise<Uint8Array | undefined>;
122
+ /**
123
+ * Whether values written back into this document can be enciphered again.
124
+ *
125
+ * False for a legacy-encrypted file: we can READ RC4 and AES-128, but writing one would downgrade the
126
+ * user's document to a broken cipher. Asked BEFORE any work is done - otherwise the refusal depends on
127
+ * a string happening to be present in whatever we rewrite, which is a guarantee by accident.
128
+ */
129
+ get canReEncrypt(): boolean;
130
+ /** The generation of an object as the file records it. Members of an object stream are always 0. */
131
+ generationOf(num: number): number;
132
+ get catalog(): PdfObject | undefined;
133
+ /** A stream's bytes as they sit in the file: sliced by `/Length`, still filtered and still encrypted. */
134
+ private rawStream;
135
+ /** A stream's DECODED bytes: deciphered if the document is encrypted, then run through its filters. */
136
+ streamData(stream: PdfStream): Uint8Array;
137
+ /**
138
+ * Undo a PNG predictor (`/Predictor` 10-15), which xref streams almost always use - the IRS form in
139
+ * our fixtures does. Each row is prefixed with its filter type and is decoded against the row above.
140
+ */
141
+ private undoPredictor;
142
+ /** Every object number the index knows, ascending - for walking a whole document. */
143
+ objectNumbers(): number[];
144
+ }
145
+ export { EMPTY };