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

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 (275) 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 +100 -13
  28. package/dist/api/structure.js +179 -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 +132 -11
  126. package/dist/elements/text-element.js +206 -56
  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/line-breaker.d.ts +50 -12
  225. package/dist/text/line-breaker.js +158 -37
  226. package/dist/text/line-metrics.d.ts +49 -0
  227. package/dist/text/line-metrics.js +57 -0
  228. package/dist/text/orphans-widows.d.ts +27 -0
  229. package/dist/text/orphans-widows.js +53 -0
  230. package/dist/text/shape.d.ts +29 -0
  231. package/dist/text/shape.js +67 -0
  232. package/dist/text/text-decoration.d.ts +50 -0
  233. package/dist/text/text-decoration.js +62 -0
  234. package/dist/text/text-style.d.ts +60 -0
  235. package/dist/text/text-style.js +79 -0
  236. package/dist/utils/afm-parser.d.ts +34 -0
  237. package/dist/utils/afm-parser.js +76 -17
  238. package/dist/utils/bytes.d.ts +24 -0
  239. package/dist/utils/bytes.js +76 -0
  240. package/dist/utils/dest-registry.d.ts +22 -0
  241. package/dist/utils/dest-registry.js +32 -0
  242. package/dist/utils/flex-layout.d.ts +36 -9
  243. package/dist/utils/flex-layout.js +207 -30
  244. package/dist/utils/font-metrics.d.ts +20 -1
  245. package/dist/utils/font-metrics.js +1 -2
  246. package/dist/utils/font-path.js +3 -6
  247. package/dist/utils/gsub.d.ts +36 -0
  248. package/dist/utils/gsub.js +178 -0
  249. package/dist/utils/image-helper.d.ts +6 -5
  250. package/dist/utils/image-helper.js +101 -111
  251. package/dist/utils/inflate.d.ts +16 -0
  252. package/dist/utils/inflate.js +52 -0
  253. package/dist/utils/md5.d.ts +4 -0
  254. package/dist/utils/md5.js +79 -0
  255. package/dist/utils/outline.d.ts +21 -0
  256. package/dist/utils/outline.js +79 -0
  257. package/dist/utils/pdf-object-manager.d.ts +110 -8
  258. package/dist/utils/pdf-object-manager.js +583 -141
  259. package/dist/utils/renderer-registry.d.ts +13 -1
  260. package/dist/utils/renderer-registry.js +23 -6
  261. package/dist/utils/struct-tree.d.ts +44 -0
  262. package/dist/utils/struct-tree.js +129 -0
  263. package/dist/utils/ttf-parser.d.ts +134 -2
  264. package/dist/utils/ttf-parser.js +873 -50
  265. package/dist/utils/ttf-subsetter.d.ts +1 -1
  266. package/dist/utils/ttf-subsetter.js +40 -42
  267. package/dist/utils/ua-xmp.d.ts +9 -0
  268. package/dist/utils/ua-xmp.js +32 -0
  269. package/dist/utils/utf8-to-windows1252-encoder.d.ts +6 -0
  270. package/dist/utils/utf8-to-windows1252-encoder.js +11 -4
  271. package/dist/utils/woff.d.ts +10 -0
  272. package/dist/utils/woff.js +116 -0
  273. package/dist/validators/element-validator.d.ts +2 -2
  274. package/dist/validators/element-validator.js +31 -26
  275. package/package.json +22 -2
@@ -0,0 +1,285 @@
1
+ const EMPTY = new Uint8Array(0);
2
+ /**
3
+ * The PDF object parser: bytes in, `PdfObject` out.
4
+ *
5
+ * It works on a `Uint8Array` with an explicit cursor rather than on a decoded string, because a PDF is
6
+ * binary - a literal string may hold any byte, and a stream certainly does. Decoding to text first
7
+ * would corrupt exactly the data we came for.
8
+ *
9
+ * Scope: enough of the grammar to walk a document to its `/AcroForm`. No content-stream operators, no
10
+ * function/shading evaluation - those are a renderer's job, not a form reader's.
11
+ */
12
+ // PDF whitespace (7.2.2): null, tab, LF, FF, CR, space.
13
+ const isSpace = (c) => c === 0 || c === 9 || c === 10 || c === 12 || c === 13 || c === 32;
14
+ // Delimiters (7.2.2) - they end a token even without whitespace.
15
+ const isDelim = (c) => c === 0x28 ||
16
+ c === 0x29 ||
17
+ c === 0x3c ||
18
+ c === 0x3e ||
19
+ c === 0x5b ||
20
+ c === 0x5d ||
21
+ c === 0x7b ||
22
+ c === 0x7d ||
23
+ c === 0x2f ||
24
+ c === 0x25;
25
+ const isRegular = (c) => !isSpace(c) && !isDelim(c);
26
+ const isDigit = (c) => c >= 0x30 && c <= 0x39;
27
+ const hexVal = (c) => c >= 0x30 && c <= 0x39
28
+ ? c - 0x30
29
+ : c >= 0x41 && c <= 0x46
30
+ ? c - 55
31
+ : c >= 0x61 && c <= 0x66
32
+ ? c - 87
33
+ : -1;
34
+ export class Lexer {
35
+ constructor(bytes, pos = 0) {
36
+ this.bytes = bytes;
37
+ this.pos = pos;
38
+ }
39
+ /** Skip whitespace and comments (`%` to end of line). */
40
+ skip() {
41
+ for (;;) {
42
+ while (this.pos < this.bytes.length && isSpace(this.bytes[this.pos]))
43
+ this.pos++;
44
+ if (this.bytes[this.pos] !== 0x25)
45
+ return; // '%'
46
+ while (this.pos < this.bytes.length &&
47
+ this.bytes[this.pos] !== 10 &&
48
+ this.bytes[this.pos] !== 13)
49
+ this.pos++;
50
+ }
51
+ }
52
+ /** The next bare token (a keyword like `obj`, `R`, `endstream`), without consuming delimiters. */
53
+ word() {
54
+ const start = this.pos;
55
+ while (this.pos < this.bytes.length && isRegular(this.bytes[this.pos]))
56
+ this.pos++;
57
+ return new TextDecoder("latin1").decode(this.bytes.subarray(start, this.pos));
58
+ }
59
+ /** True when the bytes at the cursor spell `word` (after whitespace). Does not consume. */
60
+ peekWord(word) {
61
+ const save = this.pos;
62
+ this.skip();
63
+ const w = this.word();
64
+ this.pos = save;
65
+ return w === word;
66
+ }
67
+ /** Consume `word` if present; report whether it was. */
68
+ eatWord(word) {
69
+ const save = this.pos;
70
+ this.skip();
71
+ if (this.word() === word)
72
+ return true;
73
+ this.pos = save;
74
+ return false;
75
+ }
76
+ /**
77
+ * Parse one object at the cursor. Returns `undefined` at end of input or on a token this reader does
78
+ * not model - the caller treats that as "not something I can work with" rather than crashing.
79
+ */
80
+ parse() {
81
+ this.skip();
82
+ if (this.pos >= this.bytes.length)
83
+ return undefined;
84
+ const c = this.bytes[this.pos];
85
+ if (c === 0x2f)
86
+ return this.name();
87
+ if (c === 0x28)
88
+ return this.literalString();
89
+ if (c === 0x5b)
90
+ return this.array();
91
+ if (c === 0x3c) {
92
+ return this.bytes[this.pos + 1] === 0x3c ? this.dictOrStream() : this.hexString();
93
+ }
94
+ if (isDigit(c) || c === 0x2b || c === 0x2d || c === 0x2e)
95
+ return this.numberOrRef();
96
+ const w = this.word();
97
+ if (w === "true")
98
+ return true;
99
+ if (w === "false")
100
+ return false;
101
+ if (w === "null")
102
+ return null;
103
+ return undefined; // an unmodelled keyword; the caller decides
104
+ }
105
+ /** `/Name`, with `#XX` escapes decoded (a font or field name may hold `#20` for a space). */
106
+ name() {
107
+ this.pos++; // '/'
108
+ let out = "";
109
+ while (this.pos < this.bytes.length && isRegular(this.bytes[this.pos])) {
110
+ const b = this.bytes[this.pos++];
111
+ if (b === 0x23 && this.pos + 1 < this.bytes.length) {
112
+ const hi = hexVal(this.bytes[this.pos]);
113
+ const lo = hexVal(this.bytes[this.pos + 1]);
114
+ if (hi >= 0 && lo >= 0) {
115
+ out += String.fromCharCode((hi << 4) | lo);
116
+ this.pos += 2;
117
+ continue;
118
+ }
119
+ }
120
+ out += String.fromCharCode(b);
121
+ }
122
+ return { kind: "name", name: out };
123
+ }
124
+ /** `(text)`: balanced nested parens, backslash escapes, and the line continuation. */
125
+ literalString() {
126
+ this.pos++; // '('
127
+ const out = [];
128
+ let depth = 1;
129
+ while (this.pos < this.bytes.length) {
130
+ const b = this.bytes[this.pos++];
131
+ if (b === 0x5c) {
132
+ // backslash
133
+ if (this.pos >= this.bytes.length)
134
+ break; // truncated file: nothing follows the escape
135
+ const e = this.bytes[this.pos++];
136
+ const simple = { 0x6e: 10, 0x72: 13, 0x74: 9, 0x62: 8, 0x66: 12 };
137
+ if (e in simple)
138
+ out.push(simple[e]);
139
+ else if (e >= 0x30 && e <= 0x37) {
140
+ // up to three octal digits
141
+ let v = e - 0x30;
142
+ for (let i = 0; i < 2 && this.bytes[this.pos] >= 0x30 && this.bytes[this.pos] <= 0x37; i++)
143
+ v = v * 8 + (this.bytes[this.pos++] - 0x30);
144
+ out.push(v & 0xff);
145
+ }
146
+ else if (e === 10) {
147
+ /* line continuation: the newline is dropped */
148
+ }
149
+ else if (e === 13) {
150
+ if (this.bytes[this.pos] === 10)
151
+ this.pos++;
152
+ }
153
+ else
154
+ out.push(e);
155
+ continue;
156
+ }
157
+ // An UNESCAPED end-of-line inside a literal is a line break, not data: CR, LF and CRLF all mean
158
+ // one LF (7.3.4.2). Passing a CR through verbatim would quietly change the value of any field
159
+ // written across two lines.
160
+ if (b === 13) {
161
+ if (this.bytes[this.pos] === 10)
162
+ this.pos++;
163
+ out.push(10);
164
+ continue;
165
+ }
166
+ if (b === 0x28)
167
+ depth++;
168
+ if (b === 0x29 && --depth === 0)
169
+ break;
170
+ out.push(b);
171
+ }
172
+ return { kind: "string", bytes: new Uint8Array(out), hex: false };
173
+ }
174
+ /** `<48656C6C6F>`: hex digits, whitespace ignored, an odd final digit padded with 0. */
175
+ hexString() {
176
+ this.pos++; // '<'
177
+ const out = [];
178
+ let hi = -1;
179
+ while (this.pos < this.bytes.length) {
180
+ const b = this.bytes[this.pos++];
181
+ if (b === 0x3e)
182
+ break; // '>'
183
+ const v = hexVal(b);
184
+ if (v < 0)
185
+ continue; // whitespace or junk inside a hex string is ignored
186
+ if (hi < 0)
187
+ hi = v;
188
+ else {
189
+ out.push((hi << 4) | v);
190
+ hi = -1;
191
+ }
192
+ }
193
+ if (hi >= 0)
194
+ out.push(hi << 4);
195
+ return { kind: "string", bytes: new Uint8Array(out), hex: true };
196
+ }
197
+ array() {
198
+ this.pos++; // '['
199
+ const out = [];
200
+ for (;;) {
201
+ this.skip();
202
+ if (this.pos >= this.bytes.length)
203
+ break;
204
+ if (this.bytes[this.pos] === 0x5d) {
205
+ this.pos++;
206
+ break;
207
+ }
208
+ const before = this.pos;
209
+ const v = this.parse();
210
+ if (v === undefined) {
211
+ // An unmodelled token inside an array: step past it so we cannot spin forever.
212
+ if (this.pos === before)
213
+ this.pos++;
214
+ continue;
215
+ }
216
+ out.push(v);
217
+ }
218
+ return out;
219
+ }
220
+ /** `<< … >>`, followed by `stream` when it is a stream object. */
221
+ dictOrStream() {
222
+ this.pos += 2; // '<<'
223
+ const map = new Map();
224
+ for (;;) {
225
+ this.skip();
226
+ if (this.pos >= this.bytes.length)
227
+ break;
228
+ if (this.bytes[this.pos] === 0x3e && this.bytes[this.pos + 1] === 0x3e) {
229
+ this.pos += 2;
230
+ break;
231
+ }
232
+ if (this.bytes[this.pos] !== 0x2f) {
233
+ // Not a key where one belongs - skip a token rather than loop.
234
+ const before = this.pos;
235
+ if (this.parse() === undefined && this.pos === before)
236
+ this.pos++;
237
+ continue;
238
+ }
239
+ const key = this.name();
240
+ const value = this.parse();
241
+ if (value !== undefined)
242
+ map.set(key.name, value);
243
+ }
244
+ const dict = { kind: "dict", map };
245
+ // A stream follows its dictionary. Its length is in /Length, which is allowed to be an indirect
246
+ // reference - resolving that needs the whole document, so we only record where the data starts and
247
+ // let the document layer slice it.
248
+ const save = this.pos;
249
+ if (this.eatWord("stream")) {
250
+ if (this.bytes[this.pos] === 13)
251
+ this.pos++;
252
+ if (this.bytes[this.pos] === 10)
253
+ this.pos++;
254
+ return { kind: "stream", dict, start: this.pos, raw: EMPTY };
255
+ }
256
+ this.pos = save;
257
+ return dict;
258
+ }
259
+ /** A number, or an indirect reference when the shape is `int int R`. */
260
+ numberOrRef() {
261
+ const start = this.pos;
262
+ if (this.bytes[this.pos] === 0x2b || this.bytes[this.pos] === 0x2d)
263
+ this.pos++;
264
+ while (this.pos < this.bytes.length &&
265
+ (isDigit(this.bytes[this.pos]) || this.bytes[this.pos] === 0x2e))
266
+ this.pos++;
267
+ const text = new TextDecoder("latin1").decode(this.bytes.subarray(start, this.pos));
268
+ const value = Number(text);
269
+ // Look ahead for `<gen> R`; restore the cursor when it is not there.
270
+ if (Number.isInteger(value) && value >= 0 && !text.includes(".")) {
271
+ const save = this.pos;
272
+ this.skip();
273
+ const genStart = this.pos;
274
+ while (this.pos < this.bytes.length && isDigit(this.bytes[this.pos]))
275
+ this.pos++;
276
+ if (this.pos > genStart) {
277
+ const gen = Number(new TextDecoder("latin1").decode(this.bytes.subarray(genStart, this.pos)));
278
+ if (this.eatWord("R"))
279
+ return { kind: "ref", num: value, gen };
280
+ }
281
+ this.pos = save;
282
+ }
283
+ return value;
284
+ }
285
+ }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * The PDF object model, as READ from an existing file - the counterpart of the writer's string
3
+ * building. Deliberately small: this reader exists to find and change an `/AcroForm`, not to model
4
+ * every corner of the format.
5
+ *
6
+ * Values stay close to the bytes on purpose. A PDF string is kept as RAW BYTES rather than a JS string,
7
+ * because its encoding is not known until you look at it (a literal may be PDFDocEncoded, a hex string
8
+ * is often UTF-16BE with a BOM - pdf-lib writes every field name that way). Decoding early would lose
9
+ * the distinction and mangle names.
10
+ */
11
+ export interface PdfName {
12
+ kind: "name";
13
+ /** The name WITHOUT the leading slash, `#XX` already decoded. */
14
+ name: string;
15
+ }
16
+ export interface PdfString {
17
+ kind: "string";
18
+ bytes: Uint8Array;
19
+ /** True when it was written as `<hex>` rather than `(literal)`. Kept for faithful round-tripping. */
20
+ hex: boolean;
21
+ }
22
+ /** An indirect reference, `12 0 R`. Resolved against the document, never followed by the parser itself. */
23
+ export interface PdfRef {
24
+ kind: "ref";
25
+ num: number;
26
+ gen: number;
27
+ }
28
+ export interface PdfDict {
29
+ kind: "dict";
30
+ map: Map<string, PdfObject>;
31
+ }
32
+ /**
33
+ * A stream: its dictionary plus the RAW (still encoded) bytes between `stream` and `endstream`.
34
+ *
35
+ * The parser records only where the data STARTS, because the length lives in `/Length`, which is itself
36
+ * allowed to be an indirect reference - resolving it needs the whole document. The document layer fills
37
+ * `raw` once it can.
38
+ */
39
+ export interface PdfStream {
40
+ kind: "stream";
41
+ dict: PdfDict;
42
+ /** Byte offset of the first data byte (just past the EOL after the `stream` keyword). */
43
+ start: number;
44
+ /** The still-encoded bytes; empty until the document resolves `/Length`. */
45
+ raw: Uint8Array;
46
+ }
47
+ export type PdfObject = number | boolean | null | PdfName | PdfString | PdfRef | PdfDict | PdfStream | PdfObject[];
48
+ export declare const isName: (o: PdfObject | undefined) => o is PdfName;
49
+ export declare const isString: (o: PdfObject | undefined) => o is PdfString;
50
+ export declare const isRef: (o: PdfObject | undefined) => o is PdfRef;
51
+ export declare const isDict: (o: PdfObject | undefined) => o is PdfDict;
52
+ export declare const isStream: (o: PdfObject | undefined) => o is PdfStream;
53
+ /** A dictionary entry, by key without the slash. Reads through a stream to its dictionary, since the
54
+ * two are asked the same questions (`/Type`, `/Length`, …). */
55
+ export declare function get(o: PdfObject | undefined, key: string): PdfObject | undefined;
56
+ export declare const nameOf: (o: PdfObject | undefined) => string | undefined;
57
+ export declare const numberOf: (o: PdfObject | undefined) => number | undefined;
58
+ /**
59
+ * A PDF text string as JS text. Two encodings live side by side in the wild: UTF-16BE, marked by a
60
+ * `FE FF` byte-order mark (what pdf-lib writes for every field name), and otherwise PDFDocEncoding,
61
+ * which agrees with Latin-1 for everything a field name realistically holds.
62
+ */
63
+ export declare function textOf(o: PdfObject | undefined): string | undefined;
64
+ /**
65
+ * Every string inside an object, however deep. Both sides of the crypto seam use it: strings are
66
+ * deciphered on open and must be enciphered again before an object is written back.
67
+ */
68
+ export declare function stringsIn(o: PdfObject | undefined, out?: PdfString[], depth?: number): PdfString[];
@@ -0,0 +1,83 @@
1
+ /**
2
+ * The PDF object model, as READ from an existing file - the counterpart of the writer's string
3
+ * building. Deliberately small: this reader exists to find and change an `/AcroForm`, not to model
4
+ * every corner of the format.
5
+ *
6
+ * Values stay close to the bytes on purpose. A PDF string is kept as RAW BYTES rather than a JS string,
7
+ * because its encoding is not known until you look at it (a literal may be PDFDocEncoded, a hex string
8
+ * is often UTF-16BE with a BOM - pdf-lib writes every field name that way). Decoding early would lose
9
+ * the distinction and mangle names.
10
+ */
11
+ import { latin1FromBytes } from "../utils/bytes.js";
12
+ // ---------------------------------------------------------------------------------------------
13
+ // Narrowing helpers. Reading a foreign PDF means every lookup can legitimately come back missing or
14
+ // of the wrong type, so these all answer `undefined` rather than throwing - the caller decides what
15
+ // is fatal.
16
+ // ---------------------------------------------------------------------------------------------
17
+ // The guards take an OPTIONAL object on purpose: they are nearly always applied to the result of a
18
+ // lookup that may find nothing (`isRef(get(o, "AP"))`), and a missing entry simply is not of the type.
19
+ export const isName = (o) => typeof o === "object" && o !== null && !Array.isArray(o) && o.kind === "name";
20
+ export const isString = (o) => typeof o === "object" && o !== null && !Array.isArray(o) && o.kind === "string";
21
+ export const isRef = (o) => typeof o === "object" && o !== null && !Array.isArray(o) && o.kind === "ref";
22
+ export const isDict = (o) => typeof o === "object" && o !== null && !Array.isArray(o) && o.kind === "dict";
23
+ export const isStream = (o) => typeof o === "object" && o !== null && !Array.isArray(o) && o.kind === "stream";
24
+ /** A dictionary entry, by key without the slash. Reads through a stream to its dictionary, since the
25
+ * two are asked the same questions (`/Type`, `/Length`, …). */
26
+ export function get(o, key) {
27
+ if (o === undefined)
28
+ return undefined;
29
+ if (isStream(o))
30
+ return o.dict.map.get(key);
31
+ if (isDict(o))
32
+ return o.map.get(key);
33
+ return undefined;
34
+ }
35
+ export const nameOf = (o) => o !== undefined && isName(o) ? o.name : undefined;
36
+ export const numberOf = (o) => typeof o === "number" ? o : undefined;
37
+ /**
38
+ * A PDF text string as JS text. Two encodings live side by side in the wild: UTF-16BE, marked by a
39
+ * `FE FF` byte-order mark (what pdf-lib writes for every field name), and otherwise PDFDocEncoding,
40
+ * which agrees with Latin-1 for everything a field name realistically holds.
41
+ */
42
+ export function textOf(o) {
43
+ if (o === undefined || !isString(o))
44
+ return undefined;
45
+ const b = o.bytes;
46
+ if (b.length >= 2 && b[0] === 0xfe && b[1] === 0xff) {
47
+ let s = "";
48
+ for (let i = 2; i + 1 < b.length; i += 2)
49
+ s += String.fromCharCode((b[i] << 8) | b[i + 1]);
50
+ return s;
51
+ }
52
+ // latin1FromBytes, NOT TextDecoder("latin1"): the latter is a WHATWG alias for windows-1252, which
53
+ // remaps 0x80-0x9F (byte 0x95 becomes a bullet). Mapping each byte to the same code point keeps the
54
+ // value round-trippable, which is what a reader that may write the string back needs.
55
+ return latin1FromBytes(b);
56
+ }
57
+ /**
58
+ * Every string inside an object, however deep. Both sides of the crypto seam use it: strings are
59
+ * deciphered on open and must be enciphered again before an object is written back.
60
+ */
61
+ export function stringsIn(o, out = [], depth = 0) {
62
+ if (o === undefined || o === null)
63
+ return out;
64
+ // Throws rather than returning a short list: a missed string is one written back in the clear.
65
+ // Bounded at all only because the walk does not follow references, just inline containers.
66
+ if (depth > MAX_NESTING) {
67
+ throw new Error(`@jasy/pdf: this PDF nests objects more than ${MAX_NESTING} deep, which no legitimate producer ` +
68
+ `does; refusing to walk it`);
69
+ }
70
+ if (isString(o))
71
+ out.push(o);
72
+ else if (Array.isArray(o))
73
+ for (const e of o)
74
+ stringsIn(e, out, depth + 1);
75
+ // The dictionary only - a stream's DATA is covered by the stream's own encryption.
76
+ else if (isStream(o))
77
+ stringsIn(o.dict, out, depth + 1);
78
+ else if (isDict(o))
79
+ for (const v of o.map.values())
80
+ stringsIn(v, out, depth + 1);
81
+ return out;
82
+ }
83
+ const MAX_NESTING = 64;
@@ -0,0 +1,60 @@
1
+ import type { PdfDocument } from "./document.js";
2
+ import { type PdfObject, type PdfString } from "./objects.js";
3
+ /** Strings already enciphered for write-back, keyed by the very object they replace. */
4
+ export type StringCipher = Map<PdfString, string>;
5
+ /**
6
+ * Serialise an object back to PDF syntax. Used only for objects we deliberately change - everything we
7
+ * do not touch stays in the original bytes, untouched and unre-encoded.
8
+ *
9
+ * A string keeps the spelling it was read with: a name written as `<FEFF…>` stays hex, because that is
10
+ * how the producer encodes unicode and rewriting it as a literal would corrupt it.
11
+ */
12
+ export declare function serialize(o: PdfObject | undefined, cipher?: StringCipher): string;
13
+ /** A stream we are adding: its dictionary entries plus the raw (already encoded) data. */
14
+ export interface NewStream {
15
+ dict: string;
16
+ data: Uint8Array;
17
+ }
18
+ export declare class IncrementalWriter {
19
+ private readonly doc;
20
+ /** Object number -> its new body, in PDF syntax. */
21
+ private readonly changed;
22
+ /** Numbers this writer invented, as opposed to ones it replaces - they have no recorded generation. */
23
+ private readonly added;
24
+ private helvNum?;
25
+ private nextNum;
26
+ constructor(doc: PdfDocument);
27
+ /** Replace an existing object. */
28
+ update(objNum: number, body: string | NewStream): void;
29
+ /** Append a new object and return its number. */
30
+ add(body: string | NewStream): number;
31
+ /** A shared Helvetica for regenerated appearances, added once and only when one is drawn. */
32
+ helv(): number;
33
+ /** The generation to write for an object: the file's own for one we replace, 0 for one we add. */
34
+ private generationOf;
35
+ get hasChanges(): boolean;
36
+ /**
37
+ * Produce the updated file: the original bytes, then every changed object, then a new cross-reference
38
+ * section chained to the old one.
39
+ */
40
+ save(): Uint8Array;
41
+ /**
42
+ * The new cross-reference section, as BYTES.
43
+ *
44
+ * Bytes, not a string: an xref stream is binary, and running it through a UTF-8 encoder would turn
45
+ * every value above 127 into two bytes - which is most offsets, so the whole index becomes garbage.
46
+ *
47
+ * A classic table is written when the file already had one; an xref-STREAM file gets a stream,
48
+ * because mixing the two leaves readers that follow only one kind unable to walk the chain back.
49
+ */
50
+ private xrefSection;
51
+ /** The offset the ORIGINAL file's `startxref` pointed at - our `/Prev`. */
52
+ private previousStartXref;
53
+ }
54
+ /**
55
+ * Encipher every string the given objects carry, into the cipher map `serialize` consults.
56
+ *
57
+ * Not optional on an encrypted document: `serialize` writes any string missing from the map as
58
+ * PLAINTEXT, so an object rewritten without this pass leaks whatever it holds.
59
+ */
60
+ export declare function carryStrings(doc: PdfDocument, objects: Array<PdfObject | undefined>, into?: Map<PdfString, string>): Promise<StringCipher>;