@jasy/pdf 1.0.0-beta.3 → 1.0.0-beta.5

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 (246) hide show
  1. package/dist/api/color.d.ts +1 -24
  2. package/dist/api/color.js +2 -278
  3. package/dist/api/content.d.ts +10 -5
  4. package/dist/api/content.js +29 -13
  5. package/dist/api/descriptor.js +30 -3
  6. package/dist/api/flow.d.ts +55 -0
  7. package/dist/api/flow.js +71 -0
  8. package/dist/api/font-url.d.ts +1 -2
  9. package/dist/api/font-url.js +8 -74
  10. package/dist/api/forms.d.ts +3 -0
  11. package/dist/api/forms.js +3 -0
  12. package/dist/api/gradient.d.ts +1 -53
  13. package/dist/api/gradient.js +2 -97
  14. package/dist/api/index.d.ts +4 -0
  15. package/dist/api/index.js +3 -0
  16. package/dist/api/layout.d.ts +6 -0
  17. package/dist/api/layout.js +9 -3
  18. package/dist/api/page-builder.d.ts +4 -0
  19. package/dist/api/page-builder.js +4 -0
  20. package/dist/api/structure.d.ts +10 -2
  21. package/dist/api/structure.js +9 -6
  22. package/dist/api/table.d.ts +5 -3
  23. package/dist/api/table.js +101 -22
  24. package/dist/api/text.d.ts +8 -8
  25. package/dist/api/text.js +6 -0
  26. package/dist/canvas/painter.d.ts +2 -2
  27. package/dist/canvas/painter.js +2 -2
  28. package/dist/common/color-input.d.ts +24 -0
  29. package/dist/common/color-input.js +278 -0
  30. package/dist/common/diagnostics.d.ts +61 -0
  31. package/dist/common/diagnostics.js +77 -0
  32. package/dist/common/gradient.d.ts +53 -0
  33. package/dist/common/gradient.js +97 -0
  34. package/dist/edit/acroform-reader.js +7 -7
  35. package/dist/edit/appearance.js +3 -3
  36. package/dist/edit/attachments.d.ts +52 -0
  37. package/dist/edit/attachments.js +178 -0
  38. package/dist/edit/document.d.ts +38 -8
  39. package/dist/edit/document.js +208 -51
  40. package/dist/edit/fill.js +8 -23
  41. package/dist/edit/filters.d.ts +15 -0
  42. package/dist/edit/filters.js +157 -0
  43. package/dist/edit/index.d.ts +4 -1
  44. package/dist/edit/index.js +3 -1
  45. package/dist/edit/objects.d.ts +3 -4
  46. package/dist/edit/objects.js +5 -18
  47. package/dist/elements/canvas-element.d.ts +3 -0
  48. package/dist/elements/canvas-element.js +14 -3
  49. package/dist/elements/container-element.d.ts +8 -1
  50. package/dist/elements/container-element.js +49 -17
  51. package/dist/elements/forms/checkbox-element.d.ts +2 -0
  52. package/dist/elements/forms/checkbox-element.js +12 -3
  53. package/dist/elements/forms/choice-element.d.ts +6 -1
  54. package/dist/elements/forms/choice-element.js +17 -6
  55. package/dist/elements/forms/field-font.d.ts +8 -0
  56. package/dist/elements/forms/field-font.js +5 -0
  57. package/dist/elements/forms/push-button-element.d.ts +6 -1
  58. package/dist/elements/forms/push-button-element.js +16 -4
  59. package/dist/elements/forms/radio-element.d.ts +2 -0
  60. package/dist/elements/forms/radio-element.js +12 -3
  61. package/dist/elements/forms/signature-element.d.ts +6 -1
  62. package/dist/elements/forms/signature-element.js +16 -4
  63. package/dist/elements/forms/text-field-element.d.ts +6 -1
  64. package/dist/elements/forms/text-field-element.js +16 -4
  65. package/dist/elements/image-element.d.ts +70 -21
  66. package/dist/elements/image-element.js +173 -52
  67. package/dist/elements/index.d.ts +3 -0
  68. package/dist/elements/index.js +3 -0
  69. package/dist/elements/layout/anchor-element.d.ts +8 -10
  70. package/dist/elements/layout/anchor-element.js +10 -18
  71. package/dist/elements/layout/bookmark-element.d.ts +9 -11
  72. package/dist/elements/layout/bookmark-element.js +11 -20
  73. package/dist/elements/layout/column-break-element.d.ts +17 -0
  74. package/dist/elements/layout/column-break-element.js +26 -0
  75. package/dist/elements/layout/default-text-style-element.d.ts +11 -13
  76. package/dist/elements/layout/default-text-style-element.js +14 -31
  77. package/dist/elements/layout/deferred-element.d.ts +20 -4
  78. package/dist/elements/layout/deferred-element.js +41 -9
  79. package/dist/elements/layout/expanded-element.d.ts +11 -0
  80. package/dist/elements/layout/expanded-element.js +53 -18
  81. package/dist/elements/layout/flow-element.d.ts +109 -0
  82. package/dist/elements/layout/flow-element.js +317 -0
  83. package/dist/elements/layout/keep-together-element.d.ts +8 -18
  84. package/dist/elements/layout/keep-together-element.js +33 -45
  85. package/dist/elements/layout/link-element.d.ts +8 -13
  86. package/dist/elements/layout/link-element.js +9 -19
  87. package/dist/elements/layout/padding-element.d.ts +18 -1
  88. package/dist/elements/layout/padding-element.js +63 -28
  89. package/dist/elements/layout/page-break-element.d.ts +4 -1
  90. package/dist/elements/layout/page-break-element.js +6 -1
  91. package/dist/elements/layout/page-builder-element.d.ts +5 -0
  92. package/dist/elements/layout/page-builder-element.js +13 -2
  93. package/dist/elements/layout/positioned-element.d.ts +23 -4
  94. package/dist/elements/layout/positioned-element.js +42 -12
  95. package/dist/elements/layout/proxy-element.d.ts +47 -0
  96. package/dist/elements/layout/proxy-element.js +106 -0
  97. package/dist/elements/layout/region-element.d.ts +53 -0
  98. package/dist/elements/layout/region-element.js +108 -0
  99. package/dist/elements/layout/repeating-header-element.d.ts +9 -0
  100. package/dist/elements/layout/repeating-header-element.js +55 -13
  101. package/dist/elements/layout/rotated-box-element.d.ts +6 -0
  102. package/dist/elements/layout/rotated-box-element.js +27 -14
  103. package/dist/elements/layout/rotated-element.d.ts +9 -0
  104. package/dist/elements/layout/rotated-element.js +33 -2
  105. package/dist/elements/layout/struct-group.d.ts +9 -17
  106. package/dist/elements/layout/struct-group.js +10 -35
  107. package/dist/elements/line-element.d.ts +1 -0
  108. package/dist/elements/line-element.js +6 -0
  109. package/dist/elements/page-element.d.ts +10 -3
  110. package/dist/elements/page-element.js +39 -31
  111. package/dist/elements/pdf-document-element.d.ts +3 -0
  112. package/dist/elements/pdf-document-element.js +7 -0
  113. package/dist/elements/pdf-element.d.ts +105 -16
  114. package/dist/elements/pdf-element.js +118 -19
  115. package/dist/elements/rectangle-element.d.ts +22 -2
  116. package/dist/elements/rectangle-element.js +119 -55
  117. package/dist/elements/row-element.d.ts +17 -12
  118. package/dist/elements/row-element.js +89 -9
  119. package/dist/elements/svg-element.d.ts +30 -6
  120. package/dist/elements/svg-element.js +91 -26
  121. package/dist/elements/text-element.d.ts +48 -13
  122. package/dist/elements/text-element.js +161 -116
  123. package/dist/forms/acroform.d.ts +9 -1
  124. package/dist/forms/acroform.js +83 -34
  125. package/dist/forms/appearance.d.ts +17 -6
  126. package/dist/forms/appearance.js +18 -16
  127. package/dist/ir/display-list.d.ts +18 -3
  128. package/dist/layout/box-constraints.d.ts +17 -0
  129. package/dist/layout/box-constraints.js +31 -0
  130. package/dist/layout/collect-images.d.ts +6 -4
  131. package/dist/layout/collect-images.js +14 -8
  132. package/dist/layout/flex-item.d.ts +11 -0
  133. package/dist/layout/flex-item.js +51 -0
  134. package/dist/layout/fragmentation.d.ts +23 -9
  135. package/dist/layout/fragmentation.js +62 -19
  136. package/dist/layout/layout-cache.d.ts +53 -0
  137. package/dist/layout/layout-cache.js +110 -0
  138. package/dist/platform/browser-fs.d.ts +1 -2
  139. package/dist/platform/browser-fs.js +3 -6
  140. package/dist/platform/node-fs.d.ts +0 -1
  141. package/dist/platform/node-fs.js +0 -4
  142. package/dist/renderer/anchor-renderer.d.ts +2 -1
  143. package/dist/renderer/anchor-renderer.js +5 -4
  144. package/dist/renderer/bookmark-renderer.d.ts +2 -1
  145. package/dist/renderer/bookmark-renderer.js +5 -4
  146. package/dist/renderer/canvas-renderer.d.ts +2 -1
  147. package/dist/renderer/canvas-renderer.js +5 -2
  148. package/dist/renderer/column-break-renderer.d.ts +8 -0
  149. package/dist/renderer/column-break-renderer.js +15 -0
  150. package/dist/renderer/container-renderer.d.ts +2 -1
  151. package/dist/renderer/container-renderer.js +6 -5
  152. package/dist/renderer/default-text-style-renderer.d.ts +2 -1
  153. package/dist/renderer/default-text-style-renderer.js +2 -3
  154. package/dist/renderer/deferred-renderer.d.ts +2 -1
  155. package/dist/renderer/deferred-renderer.js +2 -3
  156. package/dist/renderer/emoji-image.js +3 -1
  157. package/dist/renderer/expanded-renderer.d.ts +2 -1
  158. package/dist/renderer/expanded-renderer.js +2 -3
  159. package/dist/renderer/flow-renderer.d.ts +16 -0
  160. package/dist/renderer/flow-renderer.js +21 -0
  161. package/dist/renderer/form-field-renderer.d.ts +2 -1
  162. package/dist/renderer/form-field-renderer.js +16 -3
  163. package/dist/renderer/image-renderer.d.ts +2 -1
  164. package/dist/renderer/image-renderer.js +33 -53
  165. package/dist/renderer/index.d.ts +2 -0
  166. package/dist/renderer/index.js +2 -0
  167. package/dist/renderer/keep-together-renderer.d.ts +2 -1
  168. package/dist/renderer/keep-together-renderer.js +2 -3
  169. package/dist/renderer/line-renderer.d.ts +2 -1
  170. package/dist/renderer/line-renderer.js +7 -2
  171. package/dist/renderer/link-renderer.d.ts +2 -1
  172. package/dist/renderer/link-renderer.js +36 -5
  173. package/dist/renderer/padding-renderer.d.ts +2 -1
  174. package/dist/renderer/padding-renderer.js +5 -5
  175. package/dist/renderer/page-break-renderer.d.ts +6 -5
  176. package/dist/renderer/page-break-renderer.js +14 -7
  177. package/dist/renderer/page-builder-renderer.d.ts +2 -1
  178. package/dist/renderer/page-builder-renderer.js +2 -3
  179. package/dist/renderer/page-renderer.js +30 -10
  180. package/dist/renderer/pdf-backend.d.ts +4 -3
  181. package/dist/renderer/pdf-backend.js +14 -7
  182. package/dist/renderer/pdf-document-renderer.d.ts +1 -8
  183. package/dist/renderer/pdf-document-renderer.js +17 -17
  184. package/dist/renderer/pdf-renderer.js +20 -11
  185. package/dist/renderer/positioned-renderer.d.ts +18 -1
  186. package/dist/renderer/positioned-renderer.js +43 -6
  187. package/dist/renderer/rectangle-renderer.d.ts +2 -1
  188. package/dist/renderer/rectangle-renderer.js +18 -8
  189. package/dist/renderer/repeating-header-renderer.d.ts +2 -1
  190. package/dist/renderer/repeating-header-renderer.js +6 -5
  191. package/dist/renderer/rotated-renderer.d.ts +2 -1
  192. package/dist/renderer/rotated-renderer.js +9 -5
  193. package/dist/renderer/row-renderer.d.ts +2 -1
  194. package/dist/renderer/row-renderer.js +6 -5
  195. package/dist/renderer/struct-group-renderer.d.ts +2 -1
  196. package/dist/renderer/struct-group-renderer.js +2 -3
  197. package/dist/renderer/svg-renderer.d.ts +2 -1
  198. package/dist/renderer/svg-renderer.js +8 -2
  199. package/dist/renderer/text-renderer.d.ts +4 -6
  200. package/dist/renderer/text-renderer.js +114 -127
  201. package/dist/svg/gradients.js +5 -0
  202. package/dist/svg/index.d.ts +1 -1
  203. package/dist/svg/index.js +36 -6
  204. package/dist/svg/style.d.ts +2 -0
  205. package/dist/svg/style.js +12 -1
  206. package/dist/text/advance.d.ts +4 -0
  207. package/dist/text/advance.js +17 -4
  208. package/dist/text/bidi.d.ts +0 -2
  209. package/dist/text/bidi.js +0 -10
  210. package/dist/text/glyph-coverage.d.ts +1 -1
  211. package/dist/text/glyph-coverage.js +7 -3
  212. package/dist/text/hyphen-breaks.d.ts +17 -0
  213. package/dist/text/hyphen-breaks.js +54 -0
  214. package/dist/text/line-breaker.d.ts +55 -29
  215. package/dist/text/line-breaker.js +453 -188
  216. package/dist/text/line-metrics.d.ts +8 -3
  217. package/dist/text/line-metrics.js +11 -2
  218. package/dist/text/pdf-text-string.d.ts +21 -0
  219. package/dist/text/pdf-text-string.js +114 -0
  220. package/dist/text/text-decoration.d.ts +5 -5
  221. package/dist/text/text-decoration.js +5 -5
  222. package/dist/text/word-splitting.d.ts +15 -2
  223. package/dist/text/word-splitting.js +45 -13
  224. package/dist/utils/afm-parser.d.ts +2 -0
  225. package/dist/utils/afm-parser.js +10 -1
  226. package/dist/utils/dest-registry.d.ts +1 -1
  227. package/dist/utils/dest-registry.js +3 -2
  228. package/dist/utils/fetch-bytes.d.ts +19 -0
  229. package/dist/utils/fetch-bytes.js +79 -0
  230. package/dist/utils/flex-layout.d.ts +16 -14
  231. package/dist/utils/flex-layout.js +427 -217
  232. package/dist/utils/font-metrics.d.ts +4 -3
  233. package/dist/utils/image-helper.d.ts +59 -11
  234. package/dist/utils/image-helper.js +209 -81
  235. package/dist/utils/pdf-object-manager.d.ts +17 -6
  236. package/dist/utils/pdf-object-manager.js +70 -20
  237. package/dist/utils/renderer-registry.d.ts +49 -2
  238. package/dist/utils/renderer-registry.js +40 -0
  239. package/dist/utils/struct-tree.d.ts +24 -0
  240. package/dist/utils/struct-tree.js +61 -9
  241. package/dist/utils/ttf-parser.d.ts +4 -0
  242. package/dist/utils/ttf-parser.js +30 -6
  243. package/dist/validators/element-validator.js +2 -1
  244. package/package.json +1 -1
  245. package/dist/elements/layout/sized-container-element.d.ts +0 -14
  246. package/dist/elements/layout/sized-container-element.js +0 -32
@@ -1,5 +1,5 @@
1
1
  import { PdfEncryptedError } from "./document.js";
2
- import { get, isDict, isRef, nameOf, numberOf, textOf } from "./objects.js";
2
+ import { get, isDict, isRef, nameOf, numberOf, textStringOf } from "./objects.js";
3
3
  const FIELD_TYPES = new Set(["Tx", "Btn", "Ch", "Sig"]);
4
4
  /** Join a parent's qualified name with a child's partial name. */
5
5
  const joinName = (parent, part) => part === undefined || part === "" ? parent : parent === "" ? part : `${parent}.${part}`;
@@ -10,10 +10,10 @@ function readOptions(doc, raw) {
10
10
  return raw.map((entry) => {
11
11
  const e = doc.resolve(entry);
12
12
  if (Array.isArray(e)) {
13
- const value = textOf(doc.resolve(e[0])) ?? "";
14
- return { value, label: textOf(doc.resolve(e[1])) ?? value };
13
+ const value = textStringOf(doc.resolve(e[0])) ?? "";
14
+ return { value, label: textStringOf(doc.resolve(e[1])) ?? value };
15
15
  }
16
- const value = textOf(e) ?? "";
16
+ const value = textStringOf(e) ?? "";
17
17
  return { value, label: value };
18
18
  });
19
19
  }
@@ -21,10 +21,10 @@ function readOptions(doc, raw) {
21
21
  function readValue(doc, raw) {
22
22
  const v = doc.resolve(raw);
23
23
  if (Array.isArray(v)) {
24
- const values = v.map((x) => textOf(doc.resolve(x)) ?? nameOf(doc.resolve(x)) ?? "");
24
+ const values = v.map((x) => textStringOf(doc.resolve(x)) ?? nameOf(doc.resolve(x)) ?? "");
25
25
  return { value: values[0], values };
26
26
  }
27
- const single = textOf(v) ?? nameOf(v);
27
+ const single = textStringOf(v) ?? nameOf(v);
28
28
  return { value: single, values: undefined };
29
29
  }
30
30
  /** The export names a button widget can show, read from the keys of its `/AP /N` state dictionary. */
@@ -72,7 +72,7 @@ function walk(doc, ref, parentName, parent, out, depth) {
72
72
  const node = doc.resolve(ref);
73
73
  if (node === undefined || !isDict(node))
74
74
  return;
75
- const name = joinName(parentName, textOf(doc.lookup(node, "T")));
75
+ const name = joinName(parentName, textStringOf(doc.lookup(node, "T")));
76
76
  const type = nameOf(doc.lookup(node, "FT")) ?? parent.type;
77
77
  const flags = numberOf(doc.lookup(node, "Ff")) ?? parent.flags;
78
78
  const maxLen = numberOf(doc.lookup(node, "MaxLen")) ?? parent.maxLen;
@@ -1,5 +1,5 @@
1
1
  import { PDFObjectManager, FontStyle } from "../utils/pdf-object-manager.js";
2
- import { checkboxOff, checkboxOn, fieldDrawSize, listBoxFace, radioOff, radioOn, textFieldFace, wrapFieldValue, } from "../forms/appearance.js";
2
+ import { HELV, checkboxOff, checkboxOn, fieldDrawSize, listBoxFace, radioOff, radioOn, textFieldFace, wrapFieldValue, } from "../forms/appearance.js";
3
3
  import { Color } from "../common/color.js";
4
4
  import { get, isDict, isString, numberOf } from "./objects.js";
5
5
  import { latin1FromBytes } from "../utils/bytes.js";
@@ -130,7 +130,7 @@ export function bakeAppearance(look, field, value, values, metrics) {
130
130
  ...measure(o.label),
131
131
  selected: chosen.has(o.value),
132
132
  }));
133
- content = listBoxFace(look.width, look.height, look.style, rows, capHeight, size, "Helv");
133
+ content = listBoxFace(look.width, look.height, look.style, rows, capHeight, size, HELV);
134
134
  }
135
135
  else {
136
136
  // A choice stores an EXPORT value but shows its label - "DE" in /V, "Germany" on screen.
@@ -139,7 +139,7 @@ export function bakeAppearance(look, field, value, values, metrics) {
139
139
  const lines = shown
140
140
  ? wrapFieldValue(shown, "Helvetica", size, FontStyle.Normal, innerWidth, om, look.multiline).map(measure)
141
141
  : [];
142
- content = textFieldFace(look.width, look.height, look.style, lines, capHeight, size, "Helv", look.multiline, look.align);
142
+ content = textFieldFace(look.width, look.height, look.style, lines, capHeight, size, HELV, look.multiline, look.align);
143
143
  }
144
144
  return { content, bbox: [0, 0, look.width, look.height] };
145
145
  }
@@ -0,0 +1,52 @@
1
+ import type { PdfDocument } from "./document.js";
2
+ /**
3
+ * The files embedded in a PDF - every one of them, with everything the file says about it, and its
4
+ * exact bytes on demand.
5
+ *
6
+ * Attachments live in three places, and real files use all three: the document's `/EmbeddedFiles` name
7
+ * tree (a tree - large ones split into `/Kids`), the `/AF` arrays of PDF/A-3 (the catalog's, and a page's),
8
+ * and `FileAttachment` annotations on pages. One file spec reached from two places is ONE attachment;
9
+ * two different file specs with the same name are TWO - telling those apart is the caller's business,
10
+ * so nothing is merged by name.
11
+ */
12
+ /** Thrown when an attachment's data cannot be handed back exactly as it was embedded. */
13
+ export declare class AttachmentError extends Error {
14
+ constructor(message: string);
15
+ }
16
+ /** Where an attachment hangs: the document, or a page (0-based), through an annotation or its `/AF`. */
17
+ export type AttachmentLocation = {
18
+ kind: "document";
19
+ } | {
20
+ kind: "page";
21
+ page: number;
22
+ };
23
+ export interface Attachment {
24
+ /** The file's name: `/UF`, else `/F`, else its key in the name tree. */
25
+ name: string;
26
+ /** Its key in the `/EmbeddedFiles` name tree, when it is listed there. */
27
+ key?: string;
28
+ description?: string;
29
+ /** The media type the file declares (`/Subtype` of the embedded stream), e.g. `application/xml`. */
30
+ mimeType?: string;
31
+ /** `/AFRelationship` - `Source`, `Data`, `Alternative`, `Supplement`, `Unspecified`, ... */
32
+ relationship?: string;
33
+ /** The size the file DECLARES (`/Params /Size`). Informative only: `bytes()` is bounded by what it decodes to. */
34
+ declaredSize?: number;
35
+ created?: Date;
36
+ modified?: Date;
37
+ /** The MD5 the file declares (`/Params /CheckSum`); `bytes()` checks the data against it. */
38
+ checksum?: Uint8Array;
39
+ /** Every place it hangs - one file spec may be listed for the document AND annotated on a page. */
40
+ locations: AttachmentLocation[];
41
+ /** The file specification's object, when it is one - stable identity within this document. */
42
+ ref?: {
43
+ num: number;
44
+ gen: number;
45
+ };
46
+ /** The file's exact bytes: deciphered, every filter decoded, checked against its checksum. */
47
+ bytes(): Uint8Array;
48
+ /** The bytes as UTF-8 text, a byte-order mark dropped. */
49
+ text(): string;
50
+ }
51
+ /** Every attachment in the document, in the order: name tree, catalog `/AF`, then page by page. */
52
+ export declare function readAttachments(doc: PdfDocument): Attachment[];
@@ -0,0 +1,178 @@
1
+ import { md5 } from "../utils/md5.js";
2
+ import { get, isDict, isRef, isStream, isString, nameOf, numberOf, textStringOf, } from "./objects.js";
3
+ /**
4
+ * The files embedded in a PDF - every one of them, with everything the file says about it, and its
5
+ * exact bytes on demand.
6
+ *
7
+ * Attachments live in three places, and real files use all three: the document's `/EmbeddedFiles` name
8
+ * tree (a tree - large ones split into `/Kids`), the `/AF` arrays of PDF/A-3 (the catalog's, and a page's),
9
+ * and `FileAttachment` annotations on pages. One file spec reached from two places is ONE attachment;
10
+ * two different file specs with the same name are TWO - telling those apart is the caller's business,
11
+ * so nothing is merged by name.
12
+ */
13
+ /** Thrown when an attachment's data cannot be handed back exactly as it was embedded. */
14
+ export class AttachmentError extends Error {
15
+ constructor(message) {
16
+ super(`@jasy/pdf: ${message}`);
17
+ }
18
+ }
19
+ /** Every attachment in the document, in the order: name tree, catalog `/AF`, then page by page. */
20
+ export function readAttachments(doc) {
21
+ const out = [];
22
+ const byRef = new Map();
23
+ const add = (spec, where, key) => {
24
+ const known = isRef(spec) ? byRef.get(spec.num) : undefined;
25
+ if (known) {
26
+ const same = known.locations.some((l) => l.kind === where.kind &&
27
+ (l.kind === "document" || l.page === where.page));
28
+ if (!same)
29
+ known.locations.push(where);
30
+ if (key !== undefined)
31
+ known.key ?? (known.key = key);
32
+ return;
33
+ }
34
+ const attachment = toAttachment(doc, spec, where, key);
35
+ if (!attachment)
36
+ return;
37
+ out.push(attachment);
38
+ if (isRef(spec))
39
+ byRef.set(spec.num, attachment);
40
+ };
41
+ const catalog = doc.catalog;
42
+ const tree = doc.lookup(doc.lookup(catalog, "Names"), "EmbeddedFiles");
43
+ walkNameTree(doc, tree, (key, spec) => add(spec, { kind: "document" }, key));
44
+ for (const spec of arrayOf(doc, doc.lookup(catalog, "AF")))
45
+ add(spec, { kind: "document" });
46
+ pagesOf(doc).forEach((page, index) => {
47
+ const where = { kind: "page", page: index };
48
+ for (const spec of arrayOf(doc, doc.lookup(page, "AF")))
49
+ add(spec, where);
50
+ for (const annot of arrayOf(doc, doc.lookup(page, "Annots"))) {
51
+ const dict = doc.resolve(annot);
52
+ if (nameOf(get(dict, "Subtype")) === "FileAttachment")
53
+ add(get(dict, "FS"), where);
54
+ }
55
+ });
56
+ return out;
57
+ }
58
+ /** One file specification as an attachment, or nothing when it embeds no file (a mere reference). */
59
+ function toAttachment(doc, specRef, where, key) {
60
+ const spec = doc.resolve(specRef);
61
+ if (spec === undefined || !isDict(spec))
62
+ return undefined;
63
+ const ef = doc.resolve(get(spec, "EF"));
64
+ const fileRef = get(ef, "F") ?? get(ef, "UF");
65
+ const file = doc.resolve(fileRef);
66
+ if (file === undefined || !isStream(file))
67
+ return undefined;
68
+ const params = doc.resolve(get(file, "Params"));
69
+ const name = textStringOf(doc.resolve(get(spec, "UF"))) ??
70
+ textStringOf(doc.resolve(get(spec, "F"))) ??
71
+ key ??
72
+ "";
73
+ const checksum = doc.resolve(get(params, "CheckSum"));
74
+ const declared = isString(checksum) ? checksum.bytes : undefined;
75
+ let decoded;
76
+ const bytes = () => {
77
+ if (decoded)
78
+ return decoded;
79
+ let data;
80
+ try {
81
+ data = doc.streamData(file, true);
82
+ }
83
+ catch (e) {
84
+ throw new AttachmentError(`the attachment "${name}" cannot be read: ${String(e?.message ?? e).replace(/^@jasy\/pdf: /, "")}`);
85
+ }
86
+ if (declared && declared.length === 16 && !sameBytes(md5(data), declared)) {
87
+ throw new AttachmentError(`the attachment "${name}" does not match its own checksum - it was changed or damaged after ` +
88
+ `it was embedded`);
89
+ }
90
+ return (decoded = data);
91
+ };
92
+ return {
93
+ name,
94
+ key,
95
+ description: textStringOf(doc.resolve(get(spec, "Desc"))),
96
+ mimeType: nameOf(get(file, "Subtype")),
97
+ relationship: nameOf(get(spec, "AFRelationship")),
98
+ declaredSize: numberOf(doc.resolve(get(params, "Size"))),
99
+ created: dateOf(doc.resolve(get(params, "CreationDate"))),
100
+ modified: dateOf(doc.resolve(get(params, "ModDate"))),
101
+ checksum: declared,
102
+ locations: [where],
103
+ ref: isRef(specRef) ? { num: specRef.num, gen: specRef.gen } : undefined,
104
+ bytes,
105
+ text: () => {
106
+ const b = bytes();
107
+ const bom = b.length >= 3 && b[0] === 0xef && b[1] === 0xbb && b[2] === 0xbf;
108
+ return new TextDecoder().decode(bom ? b.subarray(3) : b);
109
+ },
110
+ };
111
+ }
112
+ /** A name tree's leaves in key order, through `/Kids` - bounded, and a node reached twice is read once. */
113
+ function walkNameTree(doc, root, visit) {
114
+ const seen = new Set();
115
+ const node = (n, depth) => {
116
+ if (depth > 64)
117
+ return;
118
+ if (isRef(n)) {
119
+ if (seen.has(n.num))
120
+ return;
121
+ seen.add(n.num);
122
+ }
123
+ const dict = doc.resolve(n);
124
+ if (dict === undefined || !isDict(dict))
125
+ return;
126
+ const names = doc.resolve(get(dict, "Names"));
127
+ if (Array.isArray(names)) {
128
+ for (let i = 0; i + 1 < names.length; i += 2) {
129
+ visit(textStringOf(doc.resolve(names[i])) ?? "", names[i + 1]);
130
+ }
131
+ }
132
+ for (const kid of arrayOf(doc, get(dict, "Kids")))
133
+ node(kid, depth + 1);
134
+ };
135
+ node(root, 0);
136
+ }
137
+ /** The page dictionaries in document order - bounded, a page tree node reached twice is read once. */
138
+ function pagesOf(doc) {
139
+ const out = [];
140
+ const seen = new Set();
141
+ const node = (n, depth) => {
142
+ if (depth > 64)
143
+ return;
144
+ if (isRef(n)) {
145
+ if (seen.has(n.num))
146
+ return;
147
+ seen.add(n.num);
148
+ }
149
+ const dict = doc.resolve(n);
150
+ if (dict === undefined || !isDict(dict))
151
+ return;
152
+ const kids = doc.lookup(dict, "Kids");
153
+ if (Array.isArray(kids))
154
+ for (const kid of kids)
155
+ node(kid, depth + 1);
156
+ else
157
+ out.push(dict);
158
+ };
159
+ node(get(doc.catalog, "Pages"), 0);
160
+ return out;
161
+ }
162
+ const arrayOf = (doc, o) => {
163
+ const a = doc.resolve(o);
164
+ return Array.isArray(a) ? a : [];
165
+ };
166
+ const sameBytes = (a, b) => a.length === b.length && a.every((v, i) => v === b[i]);
167
+ /** A PDF date, `D:YYYYMMDDHHmmSSOHH'mm`, everything after the year optional. */
168
+ function dateOf(o) {
169
+ const s = textStringOf(o);
170
+ const m = s &&
171
+ /^(?:D:)?(\d{4})(\d{2})?(\d{2})?(\d{2})?(\d{2})?(\d{2})?([Zz+-])?(\d{2})?'?(\d{2})?'?/.exec(s.trim());
172
+ if (!m)
173
+ return undefined;
174
+ const [, y, mo = "01", d = "01", h = "00", mi = "00", sec = "00", sign, oh = "00", om = "00"] = m;
175
+ const utc = Date.UTC(+y, +mo - 1, +d, +h, +mi, +sec);
176
+ const offset = sign === "+" || sign === "-" ? (sign === "+" ? 1 : -1) * (+oh * 60 + +om) : 0;
177
+ return new Date(utc - offset * 60000);
178
+ }
@@ -13,7 +13,9 @@ export interface OpenOptions extends LoadOptions {
13
13
  /** Thrown when a file cannot be opened because of its encryption - always saying which of the reasons it
14
14
  * is: no password, wrong password, or a revision we do not implement. */
15
15
  export declare class PdfEncryptedError extends Error {
16
- constructor(message: string);
16
+ readonly needsPassword: boolean;
17
+ /** `needsPassword`: the password is missing or wrong - as opposed to a file we cannot decrypt at all. */
18
+ constructor(message: string, needsPassword?: boolean);
17
19
  }
18
20
  export declare class PdfDocument {
19
21
  readonly bytes: Uint8Array;
@@ -23,6 +25,10 @@ export declare class PdfDocument {
23
25
  private readonly objStmCache;
24
26
  /** Set once an encrypted file has been opened with its password. */
25
27
  private security?;
28
+ /** Which of streams, strings and attached files this file enciphers (all of them, but for `/Identity`). */
29
+ private enciphered?;
30
+ /** Attached files encrypted on their own (`/EFF`) and opened without the password: their streams. */
31
+ private readonly lockedFiles;
26
32
  /** Decrypted stream payloads, keyed by the stream's data offset (unique per stream). */
27
33
  private readonly plainStreams;
28
34
  /** True when the cross-reference index was unusable and had to be rebuilt by scanning. Surfaced so
@@ -46,6 +52,8 @@ export declare class PdfDocument {
46
52
  * before the synchronous constructor).
47
53
  */
48
54
  static open(bytes: Uint8Array, opts?: OpenOptions): Promise<PdfDocument>;
55
+ /** Which parts the `/Encrypt` dictionary enciphers. Before V4 there are no crypt filters: everything. */
56
+ private encipheredParts;
49
57
  /**
50
58
  * Build the handler for this file's `/Encrypt` dictionary, or say precisely why we cannot.
51
59
  *
@@ -54,13 +62,15 @@ export declare class PdfDocument {
54
62
  */
55
63
  private securityHandlerFor;
56
64
  /**
57
- * Which cipher and key length actually apply to the streams.
65
+ * Which cipher and key length a legacy (V2-V4) file uses.
58
66
  *
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.
67
+ * For V4 the answer is NOT the top-level `/Length`: the document names crypt filters - `/StmF` for
68
+ * streams, `/StrF` for strings, `/EFF` for attached files - and each carries its own `/CFM` and
69
+ * `/Length` (in BYTES, where the top-level one is in bits). `/Identity` means "not enciphered". One
70
+ * handler serves them all, so the filters that DO encipher must agree; two different ciphers in one
71
+ * file are refused by name.
62
72
  */
63
- private streamCipher;
73
+ private legacyCipher;
64
74
  /**
65
75
  * Decrypt every string and stream, once, in place.
66
76
  *
@@ -70,6 +80,10 @@ export declare class PdfDocument {
70
80
  * the clear already.
71
81
  */
72
82
  private decryptEverything;
83
+ /** A stream that names the `/Identity` crypt filter in its own `/Filter` is not enciphered. */
84
+ private identityCrypt;
85
+ /** The object numbers of every attached file's stream: whatever an /EF dictionary points at. */
86
+ private embeddedFileStreams;
73
87
  /** The byte offset in the `startxref` trailer at the very end of the file. */
74
88
  private findStartXref;
75
89
  /** Read the index at `startxref`, then walk `/Prev` back through earlier revisions. Entries already
@@ -110,6 +124,8 @@ export declare class PdfDocument {
110
124
  /** True once the password has been accepted and the contents decrypted, so what is read is plaintext.
111
125
  * Always true for a document that was never encrypted. */
112
126
  get isReadable(): boolean;
127
+ /** Whether streams or strings are enciphered - not the case when only attached files are. */
128
+ private get encryptsDocument();
113
129
  /**
114
130
  * Encipher a value that is about to be written back INTO this document, or `undefined` when the
115
131
  * document is not encrypted and the value goes in as it is.
@@ -132,13 +148,27 @@ export declare class PdfDocument {
132
148
  get catalog(): PdfObject | undefined;
133
149
  /** A stream's bytes as they sit in the file: sliced by `/Length`, still filtered and still encrypted. */
134
150
  private rawStream;
135
- /** A stream's DECODED bytes: deciphered if the document is encrypted, then run through its filters. */
136
- streamData(stream: PdfStream): Uint8Array;
151
+ /**
152
+ * A stream's DECODED bytes: deciphered if the document is encrypted, then run through its filters.
153
+ *
154
+ * `strict` is for data handed to a caller as it is (an attachment): every filter must decode, or this
155
+ * throws a `PdfFilterError` - never bytes still encoded or half decoded. The default suits the
156
+ * structural reads, where a filter that fails is skipped and the rest is read as it stands.
157
+ */
158
+ streamData(stream: PdfStream, strict?: boolean): Uint8Array;
159
+ /**
160
+ * One filter. Each pass is bounded on what it produces, because /Filter is a list and
161
+ * [/FlateDecode /FlateDecode] is a legal nested bomb. The image codecs (DCT, JPX, CCITT, JBIG2) decode
162
+ * to pixels, not to a file: strict refuses them by name, the default leaves them encoded.
163
+ */
164
+ private decodeFilter;
137
165
  /**
138
166
  * Undo a PNG predictor (`/Predictor` 10-15), which xref streams almost always use - the IRS form in
139
167
  * our fixtures does. Each row is prefixed with its filter type and is decoded against the row above.
140
168
  */
141
169
  private undoPredictor;
170
+ /** TIFF predictor 2: each component is the difference from the one to its left, per row. */
171
+ private undoTiffPredictor;
142
172
  /** Every object number the index knows, ascending - for walking a whole document. */
143
173
  objectNumbers(): number[];
144
174
  }