pdf-codec 3.1.4 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/README.md +4 -2
  2. package/dist/annotations.cjs +104 -0
  3. package/dist/annotations.d.cts +9 -0
  4. package/dist/annotations.d.ts +9 -0
  5. package/dist/annotations.js +103 -0
  6. package/dist/attachments.cjs +65 -0
  7. package/dist/attachments.d.cts +8 -0
  8. package/dist/attachments.d.ts +8 -0
  9. package/dist/attachments.js +64 -0
  10. package/dist/bytes/flate.cjs +1 -1
  11. package/dist/bytes/flate.js +1 -1
  12. package/dist/codec.d.cts +109 -0
  13. package/dist/codec.d.ts +109 -0
  14. package/dist/content-read.cjs +1 -1
  15. package/dist/content-read.js +1 -1
  16. package/dist/content-write.cjs +2 -2
  17. package/dist/content-write.js +2 -2
  18. package/dist/document.cjs +12 -3
  19. package/dist/document.d.cts +2 -0
  20. package/dist/document.d.ts +2 -0
  21. package/dist/document.js +12 -3
  22. package/dist/embedded-font-write.cjs +1 -1
  23. package/dist/embedded-font-write.js +1 -1
  24. package/dist/encrypt.cjs +1 -1
  25. package/dist/encrypt.js +1 -1
  26. package/dist/filters.cjs +1 -1
  27. package/dist/filters.js +1 -1
  28. package/dist/font-read.cjs +1 -1
  29. package/dist/font-read.js +1 -1
  30. package/dist/font-registry.cjs +1 -1
  31. package/dist/font-registry.js +1 -1
  32. package/dist/form.cjs +138 -0
  33. package/dist/form.d.cts +9 -0
  34. package/dist/form.d.ts +9 -0
  35. package/dist/form.js +137 -0
  36. package/dist/image/png-decode.cjs +1 -1
  37. package/dist/image/png-decode.js +1 -1
  38. package/dist/image/png-encode.cjs +1 -1
  39. package/dist/image/png-encode.js +1 -1
  40. package/dist/index.cjs +12 -2
  41. package/dist/index.d.cts +3 -3
  42. package/dist/index.d.ts +3 -3
  43. package/dist/index.js +3 -3
  44. package/dist/interpret.cjs +70 -10
  45. package/dist/interpret.d.cts +9 -1
  46. package/dist/interpret.d.ts +9 -1
  47. package/dist/interpret.js +70 -10
  48. package/dist/layout.cjs +132 -2
  49. package/dist/layout.d.cts +335 -1
  50. package/dist/layout.d.ts +335 -1
  51. package/dist/layout.js +124 -4
  52. package/dist/math-content-write.cjs +1 -1
  53. package/dist/math-content-write.js +1 -1
  54. package/dist/math-font.cjs +1 -1
  55. package/dist/math-font.js +1 -1
  56. package/dist/names.cjs +41 -0
  57. package/dist/names.d.cts +11 -0
  58. package/dist/names.d.ts +11 -0
  59. package/dist/names.js +40 -0
  60. package/dist/navigation.cjs +210 -0
  61. package/dist/navigation.d.cts +18 -0
  62. package/dist/navigation.d.ts +18 -0
  63. package/dist/navigation.js +207 -0
  64. package/dist/optional-content.cjs +63 -0
  65. package/dist/optional-content.d.cts +12 -0
  66. package/dist/optional-content.d.ts +12 -0
  67. package/dist/optional-content.js +62 -0
  68. package/dist/parse.cjs +1 -1
  69. package/dist/parse.d.cts +1 -1
  70. package/dist/parse.d.ts +1 -1
  71. package/dist/parse.js +1 -1
  72. package/dist/pdf-text.cjs +21 -0
  73. package/dist/pdf-text.d.cts +5 -0
  74. package/dist/pdf-text.d.ts +5 -0
  75. package/dist/pdf-text.js +19 -0
  76. package/dist/read.cjs +272 -72
  77. package/dist/read.d.cts +2 -4
  78. package/dist/read.d.ts +2 -4
  79. package/dist/read.js +269 -67
  80. package/dist/serialize.cjs +5 -1
  81. package/dist/serialize.d.cts +3 -2
  82. package/dist/serialize.d.ts +3 -2
  83. package/dist/serialize.js +5 -2
  84. package/dist/write.cjs +51 -5
  85. package/dist/write.js +51 -5
  86. package/dist/xmp.cjs +46 -0
  87. package/dist/xmp.d.cts +14 -0
  88. package/dist/xmp.d.ts +14 -0
  89. package/dist/xmp.js +45 -0
  90. package/dist/xref.cjs +2 -2
  91. package/dist/xref.js +2 -2
  92. package/package.json +2 -2
package/dist/read.js CHANGED
@@ -1,17 +1,25 @@
1
+ import "./notes-annotation-author.js";
2
+ import { asArray, asName, asNumber, dictGet, pdfArray, pdfDict, pdfNull, pdfNum } from "./objects.js";
3
+ import { decodePdfString, parsePdfDate } from "./pdf-text.js";
4
+ import { concatBytes } from "./bytes/writer.js";
5
+ import { serializeObjectToText } from "./serialize.js";
6
+ import { applyMatrix, matrixRotationDegrees, matrixScaleX, matrixScaleY, multiplyMatrices, rotatePointAboutCenter, translationMatrix } from "./matrix.js";
7
+ import { readPageAnnotations } from "./annotations.js";
8
+ import { decodeStream } from "./filters.js";
1
9
  import { bytesToBase64 } from "./util/base64.js";
10
+ import { readAttachments } from "./attachments.js";
2
11
  import { crc32 } from "./bytes/crc32.js";
3
- import { concatBytes } from "./bytes/writer.js";
12
+ import { readAcroForm } from "./form.js";
13
+ import { readXmpMetadata } from "./xmp.js";
14
+ import { readOptionalContent } from "./optional-content.js";
4
15
  import "./layout.js";
5
16
  import { NOOP_DIAGNOSTIC_SINK, PdfParseError } from "./diagnostics.js";
6
- import { asArray, asName, asNumber, dictGet } from "./objects.js";
7
- import { decodeStream } from "./filters.js";
8
17
  import { openPdfDocument } from "./document.js";
9
18
  import { throwIfAborted } from "./util/abort.js";
10
19
  import { createFontResolver } from "./font-read.js";
11
20
  import { readImageXObject } from "./images-read.js";
12
- import { applyMatrix, matrixRotationDegrees, matrixScaleX, matrixScaleY, multiplyMatrices, translationMatrix } from "./matrix.js";
13
21
  import { interpretContentStream } from "./interpret.js";
14
- import "./notes-annotation-author.js";
22
+ import { createDestinationRegistry, readOutline } from "./navigation.js";
15
23
  //#region src/read.ts
16
24
  const PDF_HEADER_BYTES = new TextEncoder().encode("%PDF-");
17
25
  const HEADER_SEARCH_WINDOW = 1024;
@@ -37,29 +45,42 @@ function readPdf(bytes, options) {
37
45
  });
38
46
  const images = {};
39
47
  const imageIdCache = /* @__PURE__ */ new Map();
40
- const pages = doc.pages().map((pageDict) => {
48
+ const pageDicts = doc.pages();
49
+ const destinationRegistry = createDestinationRegistry(doc.catalog, doc, (obj) => doc.pageIndex(obj), sink);
50
+ const outline = readOutline(doc.catalog, destinationRegistry, doc, sink);
51
+ const attachments = readAttachments(doc.catalog, pageDicts, doc, sink);
52
+ const optionalContent = readOptionalContent(doc.catalog, doc, sink);
53
+ const form = readAcroForm(doc.catalog, doc, (obj) => doc.pageIndex(obj), sink);
54
+ const source = readDocumentResidue(doc, sink);
55
+ const pageBoxRows = [];
56
+ const pages = pageDicts.map((pageDict, index) => {
41
57
  throwIfAborted(signal);
42
- return readPage(pageDict, doc, fontResolver, images, imageIdCache, sink);
58
+ return readPage(index, pageDict, doc, fontResolver, images, imageIdCache, destinationRegistry, optionalContent.layerNameOf, pageBoxRows, sink);
43
59
  });
60
+ if (pageBoxRows.length > 0) source["page-boxes"] = {
61
+ format: "pdf",
62
+ xml: serializeObjectToText(pdfArray(pageBoxRows))
63
+ };
44
64
  return {
45
65
  formatVersion: 1,
46
- metadata: readMetadata(doc.trailer, doc),
66
+ metadata: readMetadata(doc.trailer, doc, doc.catalog, sink),
47
67
  pages,
48
- images
68
+ images,
69
+ ...destinationRegistry.entries.length > 0 ? { destinations: [...destinationRegistry.entries] } : {},
70
+ ...outline.length > 0 ? { outline } : {},
71
+ ...attachments.length > 0 ? { attachments } : {},
72
+ ...optionalContent.layers.length > 0 ? { layers: [...optionalContent.layers] } : {},
73
+ ...form.length > 0 ? { form } : {},
74
+ ...Object.keys(source).length > 0 ? { source } : {}
49
75
  };
50
76
  }
51
- function readMediaBox(page) {
52
- const arr = asArray(dictGet(page, "MediaBox"));
53
- if (arr === void 0) return {
54
- llx: 0,
55
- lly: 0,
56
- urx: DEFAULT_PAGE_WIDTH_PT,
57
- ury: DEFAULT_PAGE_HEIGHT_PT
58
- };
77
+ function readDeclaredPageBox(page, key) {
78
+ const arr = asArray(dictGet(page, key));
79
+ if (arr === void 0) return;
59
80
  const a = asNumber(arr[0]) ?? 0;
60
81
  const b = asNumber(arr[1]) ?? 0;
61
- const c = asNumber(arr[2]) ?? DEFAULT_PAGE_WIDTH_PT;
62
- const d = asNumber(arr[3]) ?? DEFAULT_PAGE_HEIGHT_PT;
82
+ const c = asNumber(arr[2]) ?? 0;
83
+ const d = asNumber(arr[3]) ?? 0;
63
84
  return {
64
85
  llx: Math.min(a, c),
65
86
  lly: Math.min(b, d),
@@ -67,6 +88,33 @@ function readMediaBox(page) {
67
88
  ury: Math.max(b, d)
68
89
  };
69
90
  }
91
+ function readMediaBox(page) {
92
+ return readDeclaredPageBox(page, "MediaBox") ?? {
93
+ llx: 0,
94
+ lly: 0,
95
+ urx: DEFAULT_PAGE_WIDTH_PT,
96
+ ury: DEFAULT_PAGE_HEIGHT_PT
97
+ };
98
+ }
99
+ function rotatedRectBounds(rect, matrix) {
100
+ const transformed = [
101
+ [rect.llx, rect.lly],
102
+ [rect.urx, rect.lly],
103
+ [rect.llx, rect.ury],
104
+ [rect.urx, rect.ury]
105
+ ].map(([x, y]) => applyMatrix(matrix, {
106
+ x,
107
+ y
108
+ }));
109
+ const xs = transformed.map((point) => point.x);
110
+ const ys = transformed.map((point) => point.y);
111
+ return {
112
+ minX: Math.min(...xs),
113
+ minY: Math.min(...ys),
114
+ maxX: Math.max(...xs),
115
+ maxY: Math.max(...ys)
116
+ };
117
+ }
70
118
  function normalizeRotation(rotate) {
71
119
  if (rotate === void 0) return 0;
72
120
  const normalized = (Math.round(rotate / 90) * 90 % 360 + 360) % 360;
@@ -135,35 +183,141 @@ function readPageContentBytes(page, resolver, sink) {
135
183
  }
136
184
  return /* @__PURE__ */ new Uint8Array(0);
137
185
  }
138
- function readPage(page, resolver, fontResolver, images, imageIdCache, sink) {
186
+ function contentItemBounds(item) {
187
+ if (item.kind === "link" || item.kind === "internalLink") return;
188
+ const frameBounds = (xPt, yPt, widthPt, heightPt) => ({
189
+ minX: xPt,
190
+ minY: yPt,
191
+ maxX: xPt + widthPt,
192
+ maxY: yPt + heightPt
193
+ });
194
+ if (item.kind === "text" || item.kind === "image" || item.kind === "rect" || item.kind === "ellipse") {
195
+ if (item.kind !== "text" || item.rotationDeg === void 0 || item.rotationDeg % 180 === 0) return frameBounds(item.xPt, item.yPt, item.widthPt ?? 0, item.kind === "text" ? item.sizePt : item.heightPt);
196
+ const rotationDeg = item.rotationDeg;
197
+ const anchorX = item.xPt;
198
+ const anchorY = item.yPt;
199
+ const rotated = [
200
+ [0, 0],
201
+ [item.widthPt ?? 0, 0],
202
+ [0, item.sizePt],
203
+ [item.widthPt ?? 0, item.sizePt]
204
+ ].map(([dx, dy]) => rotatePointAboutCenter({
205
+ x: anchorX + dx,
206
+ y: anchorY + dy
207
+ }, {
208
+ x: anchorX,
209
+ y: anchorY
210
+ }, rotationDeg));
211
+ const xs = rotated.map((point) => point.x);
212
+ const ys = rotated.map((point) => point.y);
213
+ return {
214
+ minX: Math.min(...xs),
215
+ minY: Math.min(...ys),
216
+ maxX: Math.max(...xs),
217
+ maxY: Math.max(...ys)
218
+ };
219
+ }
220
+ if (item.kind === "line") return {
221
+ minX: Math.min(item.x1Pt, item.x2Pt),
222
+ minY: Math.min(item.y1Pt, item.y2Pt),
223
+ maxX: Math.max(item.x1Pt, item.x2Pt),
224
+ maxY: Math.max(item.y1Pt, item.y2Pt)
225
+ };
226
+ let minX = Number.POSITIVE_INFINITY;
227
+ let minY = Number.POSITIVE_INFINITY;
228
+ let maxX = Number.NEGATIVE_INFINITY;
229
+ let maxY = Number.NEGATIVE_INFINITY;
230
+ const include = (x, y) => {
231
+ minX = Math.min(minX, x);
232
+ minY = Math.min(minY, y);
233
+ maxX = Math.max(maxX, x);
234
+ maxY = Math.max(maxY, y);
235
+ };
236
+ for (const subpath of item.subpaths) {
237
+ include(subpath.startXPt, subpath.startYPt);
238
+ for (const segment of subpath.segments) {
239
+ include(segment.xPt, segment.yPt);
240
+ if (segment.kind === "cubic") {
241
+ include(segment.c1xPt, segment.c1yPt);
242
+ include(segment.c2xPt, segment.c2yPt);
243
+ }
244
+ }
245
+ }
246
+ return {
247
+ minX,
248
+ minY,
249
+ maxX,
250
+ maxY
251
+ };
252
+ }
253
+ function itemIntersectsVisibleRegion(item, widthPt, heightPt) {
254
+ if (item.kind === "link" || item.kind === "internalLink") return true;
255
+ const bounds = contentItemBounds(item);
256
+ if (bounds === void 0) return true;
257
+ return bounds.maxX >= 0 && bounds.minX <= widthPt && bounds.maxY >= 0 && bounds.minY <= heightPt;
258
+ }
259
+ function pageBoxResidueEntry(pageIndex, page, mediaBox, cropBox) {
260
+ const declaredBoxes = [
261
+ ["MediaBox", dictGet(page, "MediaBox")],
262
+ ["CropBox", dictGet(page, "CropBox")],
263
+ ["BleedBox", dictGet(page, "BleedBox")],
264
+ ["TrimBox", dictGet(page, "TrimBox")],
265
+ ["ArtBox", dictGet(page, "ArtBox")]
266
+ ];
267
+ const cropDeclared = dictGet(page, "CropBox") !== void 0;
268
+ const productionBoxDeclared = declaredBoxes.slice(2).some(([, value]) => value !== void 0);
269
+ if (!(cropDeclared && (cropBox.llx !== mediaBox.llx || cropBox.lly !== mediaBox.lly || cropBox.urx !== mediaBox.urx || cropBox.ury !== mediaBox.ury)) && !productionBoxDeclared) return;
270
+ const entries = { Page: pdfNum(pageIndex) };
271
+ for (const [key, value] of declaredBoxes) if (value !== void 0) entries[key] = value;
272
+ return pdfDict(entries);
273
+ }
274
+ function readPage(pageIndex, page, resolver, fontResolver, images, imageIdCache, destinationRegistry, layerNameOf, pageBoxRows, sink) {
139
275
  const resources = resolver.resolveDict(dictGet(page, "Resources"));
140
276
  const mediaBox = readMediaBox(page);
277
+ let cropBox = readDeclaredPageBox(page, "CropBox") ?? mediaBox;
278
+ if (cropBox.urx - cropBox.llx <= 0 || cropBox.ury - cropBox.lly <= 0) {
279
+ sink({
280
+ code: "pdf/invalid-crop-box",
281
+ severity: "warning",
282
+ pageIndex,
283
+ message: "page /CropBox is degenerate (zero width or height); falling back to the /MediaBox as the visible region"
284
+ });
285
+ cropBox = mediaBox;
286
+ }
141
287
  const rotationResult = pageRotationTransform(normalizeRotation(asNumber(dictGet(page, "Rotate"))), mediaBox.urx - mediaBox.llx, mediaBox.ury - mediaBox.lly);
142
- const pageMatrix = multiplyMatrices(translationMatrix(-mediaBox.llx, -mediaBox.lly), rotationResult.matrix);
288
+ const visibleRect = rotatedRectBounds(cropBox, rotationResult.matrix);
289
+ const widthPt = visibleRect.maxX - visibleRect.minX;
290
+ const heightPt = visibleRect.maxY - visibleRect.minY;
291
+ const pageMatrix = multiplyMatrices(rotationResult.matrix, translationMatrix(-visibleRect.minX, -visibleRect.minY));
292
+ const boxRow = pageBoxResidueEntry(pageIndex, page, mediaBox, cropBox);
293
+ if (boxRow !== void 0) pageBoxRows.push(boxRow);
143
294
  const items = [];
144
295
  if (resources !== void 0) {
145
296
  const contentBytes = readPageContentBytes(page, resolver, sink);
146
297
  const extracted = interpretContentStream(contentBytes, resources, {
147
298
  fontMetrics: fontResolver.metrics,
148
299
  resolver,
149
- sink
300
+ sink,
301
+ layerNameOf
150
302
  });
151
303
  for (const item of extracted) {
152
304
  const converted = convertExtractedItem(item, pageMatrix, fontResolver, images, imageIdCache, resolver, sink);
153
- if (converted !== void 0) items.push(converted);
305
+ if (converted !== void 0 && itemIntersectsVisibleRegion(converted, widthPt, heightPt)) items.push(converted);
154
306
  }
155
307
  } else sink({
156
308
  code: "pdf/object-missing-value",
157
309
  severity: "warning",
158
310
  message: "page has no /Resources dict; its content stream cannot be interpreted"
159
311
  });
160
- items.push(...readLinkAnnotations(page, pageMatrix, resolver));
312
+ items.push(...readLinkAnnotations(page, pageMatrix, resolver, destinationRegistry));
161
313
  const notes = readPageNotes(page, resolver);
314
+ const annotations = readPageAnnotations(page, pageMatrix, resolver, sink);
162
315
  return {
163
- widthPt: rotationResult.widthPt,
164
- heightPt: rotationResult.heightPt,
316
+ widthPt,
317
+ heightPt,
165
318
  items,
166
- ...notes !== void 0 ? { notes } : {}
319
+ ...notes !== void 0 ? { notes } : {},
320
+ ...annotations.length > 0 ? { annotations } : {}
167
321
  };
168
322
  }
169
323
  function convertExtractedItem(item, pageMatrix, fontResolver, images, imageIdCache, resolver, sink) {
@@ -198,7 +352,10 @@ function convertText(item, pageMatrix, fontResolver) {
198
352
  sizePt: sizePt > 0 ? sizePt : item.sizePt,
199
353
  color: item.color,
200
354
  widthPt,
201
- rotationDeg: rotationDeg !== 0 ? rotationDeg : void 0
355
+ rotationDeg: rotationDeg !== 0 ? rotationDeg : void 0,
356
+ ...item.layerName !== void 0 ? { layer: item.layerName } : {},
357
+ ...item.actualText !== void 0 ? { actualText: item.actualText } : {},
358
+ ...item.alt !== void 0 ? { alt: item.alt } : {}
202
359
  };
203
360
  }
204
361
  function paintFields(paint) {
@@ -227,14 +384,16 @@ function convertRect(item, pageMatrix) {
227
384
  return {
228
385
  kind: "rect",
229
386
  ...transformBox(item, pageMatrix),
230
- ...paintFields(item)
387
+ ...paintFields(item),
388
+ ...item.layerName !== void 0 ? { layer: item.layerName } : {}
231
389
  };
232
390
  }
233
391
  function convertEllipse(item, pageMatrix) {
234
392
  return {
235
393
  kind: "ellipse",
236
394
  ...transformBox(item, pageMatrix),
237
- ...paintFields(item)
395
+ ...paintFields(item),
396
+ ...item.layerName !== void 0 ? { layer: item.layerName } : {}
238
397
  };
239
398
  }
240
399
  function convertLine(item, pageMatrix) {
@@ -253,7 +412,8 @@ function convertLine(item, pageMatrix) {
253
412
  x2Pt: p2.x,
254
413
  y2Pt: p2.y,
255
414
  color: item.color,
256
- widthPt: item.widthPt
415
+ widthPt: item.widthPt,
416
+ ...item.layerName !== void 0 ? { layer: item.layerName } : {}
257
417
  };
258
418
  }
259
419
  function transformSubpath(subpath, pageMatrix) {
@@ -308,7 +468,8 @@ function convertPath(item, pageMatrix) {
308
468
  subpaths: item.subpaths.map((subpath) => transformSubpath(subpath, pageMatrix)),
309
469
  ...item.fill !== void 0 ? { fill: item.fill } : {},
310
470
  ...item.fill !== void 0 && item.fillRule === "evenodd" ? { fillRule: "evenodd" } : {},
311
- ...item.stroke !== void 0 ? { stroke: item.stroke } : {}
471
+ ...item.stroke !== void 0 ? { stroke: item.stroke } : {},
472
+ ...item.layerName !== void 0 ? { layer: item.layerName } : {}
312
473
  };
313
474
  }
314
475
  function imagePlacementFrom(matrix) {
@@ -351,7 +512,8 @@ function convertImage(item, pageMatrix, images, cache, resolver, sink) {
351
512
  return {
352
513
  kind: "image",
353
514
  imageId,
354
- ...imagePlacementFrom(multiplyMatrices(item.matrix, pageMatrix))
515
+ ...imagePlacementFrom(multiplyMatrices(item.matrix, pageMatrix)),
516
+ ...item.layerName !== void 0 ? { layer: item.layerName } : {}
355
517
  };
356
518
  }
357
519
  function convertInlineImage(item, pageMatrix, images, resolver, sink) {
@@ -360,19 +522,22 @@ function convertInlineImage(item, pageMatrix, images, resolver, sink) {
360
522
  return {
361
523
  kind: "image",
362
524
  imageId: registerExtractedImage(decoded.format, decoded.bytes, decoded.widthPx, decoded.heightPx, images),
363
- ...imagePlacementFrom(multiplyMatrices(item.matrix, pageMatrix))
525
+ ...imagePlacementFrom(multiplyMatrices(item.matrix, pageMatrix)),
526
+ ...item.layerName !== void 0 ? { layer: item.layerName } : {}
364
527
  };
365
528
  }
366
- function readLinkAnnotations(page, pageMatrix, resolver) {
529
+ function readLinkAnnotations(page, pageMatrix, resolver, destinationRegistry) {
367
530
  const annotsArr = asArray(dictGet(page, "Annots"));
368
531
  if (annotsArr === void 0) return [];
369
532
  const links = [];
370
533
  for (const annotRef of annotsArr) {
371
534
  const annot = resolver.resolveDict(annotRef);
372
535
  if (annot === void 0 || asName(dictGet(annot, "Subtype")) !== "Link") continue;
373
- const uri = readLinkUri(annot, resolver);
374
536
  const rectArr = asArray(dictGet(annot, "Rect"));
375
- if (uri === void 0 || rectArr === void 0) continue;
537
+ if (rectArr === void 0) continue;
538
+ const uri = readLinkUri(annot, resolver);
539
+ const destination = uri === void 0 ? readInternalDestination(annot, resolver, destinationRegistry) : void 0;
540
+ if (uri === void 0 && destination === void 0) continue;
376
541
  const x1 = asNumber(rectArr[0]) ?? 0;
377
542
  const y1 = asNumber(rectArr[1]) ?? 0;
378
543
  const x2 = asNumber(rectArr[2]) ?? 0;
@@ -385,13 +550,24 @@ function readLinkAnnotations(page, pageMatrix, resolver) {
385
550
  x: Math.max(x1, x2),
386
551
  y: Math.max(y1, y2)
387
552
  });
388
- links.push({
389
- kind: "link",
390
- uri,
553
+ const contentsObj = dictGet(annot, "Contents");
554
+ const title = contentsObj?.kind === "string" ? decodePdfString(contentsObj.bytes) : void 0;
555
+ const box = {
391
556
  xPt: Math.min(p1.x, p2.x),
392
557
  yPt: Math.min(p1.y, p2.y),
393
558
  widthPt: Math.abs(p2.x - p1.x),
394
- heightPt: Math.abs(p2.y - p1.y)
559
+ heightPt: Math.abs(p2.y - p1.y),
560
+ ...title !== void 0 ? { title } : {}
561
+ };
562
+ if (uri !== void 0) links.push({
563
+ kind: "link",
564
+ uri,
565
+ ...box
566
+ });
567
+ else if (destination !== void 0) links.push({
568
+ kind: "internalLink",
569
+ destination,
570
+ ...box
395
571
  });
396
572
  }
397
573
  return links;
@@ -402,6 +578,12 @@ function readLinkUri(annot, resolver) {
402
578
  const uriObj = dictGet(action, "URI");
403
579
  return uriObj?.kind === "string" ? decodePdfString(uriObj.bytes) : void 0;
404
580
  }
581
+ function readInternalDestination(annot, resolver, destinationRegistry) {
582
+ const dest = dictGet(annot, "Dest");
583
+ if (dest !== void 0) return destinationRegistry.intern(dest);
584
+ const action = resolver.resolveDict(dictGet(annot, "A"));
585
+ if (action !== void 0 && asName(dictGet(action, "S")) === "GoTo") return destinationRegistry.intern(dictGet(action, "D"));
586
+ }
405
587
  function readPageNotes(page, resolver) {
406
588
  const annotsArr = asArray(dictGet(page, "Annots"));
407
589
  if (annotsArr === void 0) return;
@@ -414,23 +596,7 @@ function readPageNotes(page, resolver) {
414
596
  if (contentsObj?.kind === "string") return decodePdfString(contentsObj.bytes);
415
597
  }
416
598
  }
417
- function decodePdfString(bytes) {
418
- if (bytes.length >= 2 && bytes[0] === 254 && bytes[1] === 255) {
419
- let out = "";
420
- for (let i = 2; i + 1 < bytes.length; i += 2) out += String.fromCharCode((bytes[i] ?? 0) << 8 | (bytes[i + 1] ?? 0));
421
- return out;
422
- }
423
- return Array.from(bytes, (b) => String.fromCharCode(b)).join("");
424
- }
425
- const PDF_DATE_PATTERN = /^D:(\d{4})(\d{2})?(\d{2})?(\d{2})?(\d{2})?(\d{2})?([+\-Z])?(\d{2})?'?(\d{2})?'?$/;
426
- function parsePdfDate(raw) {
427
- if (raw === void 0) return;
428
- const match = PDF_DATE_PATTERN.exec(raw);
429
- if (match === null) return;
430
- const [, year, month = "01", day = "01", hour = "00", minute = "00", second = "00", tzSign, tzHour = "00", tzMinute = "00"] = match;
431
- return `${year}-${month}-${day}T${hour}:${minute}:${second}${tzSign === void 0 || tzSign === "Z" ? "Z" : `${tzSign}${tzHour}:${tzMinute}`}`;
432
- }
433
- function readMetadata(trailer, resolver) {
599
+ function readMetadata(trailer, resolver, catalog, sink) {
434
600
  const info = resolver.resolveDict(dictGet(trailer, "Info"));
435
601
  if (info === void 0) return {};
436
602
  const stringField = (key) => {
@@ -438,16 +604,52 @@ function readMetadata(trailer, resolver) {
438
604
  return obj?.kind === "string" ? decodePdfString(obj.bytes) : void 0;
439
605
  };
440
606
  const keywords = stringField("Keywords")?.split(",").map((k) => k.trim()).filter((k) => k.length > 0);
607
+ const langObj = dictGet(catalog, "Lang");
608
+ const xmp = xmpPacket(catalog, resolver, sink);
609
+ const mirrored = readXmpMetadata(xmp ?? "");
441
610
  return {
442
- title: stringField("Title"),
443
- author: stringField("Author"),
444
- subject: stringField("Subject"),
445
- keywords: keywords !== void 0 && keywords.length > 0 ? keywords : void 0,
446
- creator: stringField("Creator"),
447
- producer: stringField("Producer"),
448
- createdIso: parsePdfDate(stringField("CreationDate")),
449
- modifiedIso: parsePdfDate(stringField("ModDate"))
611
+ title: stringField("Title") ?? mirrored.title,
612
+ author: stringField("Author") ?? mirrored.author,
613
+ subject: stringField("Subject") ?? mirrored.subject,
614
+ keywords: (keywords !== void 0 && keywords.length > 0 ? keywords : void 0) ?? mirrored.keywords,
615
+ creator: stringField("Creator") ?? mirrored.creator,
616
+ producer: stringField("Producer") ?? mirrored.producer,
617
+ createdIso: parsePdfDate(stringField("CreationDate")) ?? mirrored.createdIso,
618
+ modifiedIso: parsePdfDate(stringField("ModDate")) ?? mirrored.modifiedIso,
619
+ ...langObj?.kind === "string" ? { language: decodePdfString(langObj.bytes) } : {}
620
+ };
621
+ }
622
+ function xmpPacket(catalog, resolver, sink) {
623
+ const metadataObj = resolver.resolve(dictGet(catalog, "Metadata"));
624
+ if (metadataObj?.kind !== "stream") return;
625
+ const decoded = decodeStream(metadataObj.raw, metadataObj.dict, sink);
626
+ return new TextDecoder().decode(decoded.bytes);
627
+ }
628
+ function readDocumentResidue(doc, sink) {
629
+ const source = {};
630
+ const residue = (key, obj) => {
631
+ if (obj !== void 0) source[key] = {
632
+ format: "pdf",
633
+ xml: serializeObjectToText(obj)
634
+ };
635
+ };
636
+ const catalog = doc.catalog;
637
+ const packet = xmpPacket(catalog, doc, sink);
638
+ if (packet !== void 0) source.xmp = {
639
+ format: "pdf",
640
+ xml: packet
450
641
  };
642
+ residue("viewer-preferences", dictGet(catalog, "ViewerPreferences"));
643
+ residue("page-mode", dictGet(catalog, "PageMode"));
644
+ residue("page-layout", dictGet(catalog, "PageLayout"));
645
+ residue("open-action", dictGet(catalog, "OpenAction"));
646
+ const outputIntents = asArray(dictGet(catalog, "OutputIntents"));
647
+ if (outputIntents !== void 0) residue("output-intents", pdfArray(outputIntents.map((intent) => doc.resolve(intent) ?? pdfNull())));
648
+ residue("piece-info", dictGet(catalog, "PieceInfo"));
649
+ residue("legal", dictGet(catalog, "Legal"));
650
+ residue("collection", dictGet(catalog, "Collection"));
651
+ residue("trailer-id", dictGet(doc.trailer, "ID"));
652
+ return source;
451
653
  }
452
654
  //#endregion
453
- export { decodePdfString, normalizeRotation, pageRotationTransform, parsePdfDate, readPdf };
655
+ export { normalizeRotation, pageRotationTransform, readPdf };
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_bytes_writer = require("./bytes/writer.cjs");
3
2
  const require_objects = require("./objects.cjs");
3
+ const require_bytes_writer = require("./bytes/writer.cjs");
4
4
  //#region src/serialize.ts
5
5
  const NUMBER_DECIMAL_PLACES = 4;
6
6
  const NUMBER_EPSILON = 10 ** -4;
@@ -63,7 +63,11 @@ function serializeObject(obj) {
63
63
  writeObject(writer, obj);
64
64
  return writer.toBytes();
65
65
  }
66
+ function serializeObjectToText(obj) {
67
+ return new TextDecoder().decode(serializeObject(obj));
68
+ }
66
69
  //#endregion
67
70
  exports.formatNumber = formatNumber;
68
71
  exports.serializeObject = serializeObject;
72
+ exports.serializeObjectToText = serializeObjectToText;
69
73
  exports.writeObject = writeObject;
@@ -1,8 +1,9 @@
1
- import { ByteWriter } from "./bytes/writer.cjs";
2
1
  import { PdfObject } from "./objects.cjs";
2
+ import { ByteWriter } from "./bytes/writer.cjs";
3
3
  //#region src/serialize.d.ts
4
4
  declare function formatNumber(n: number): string;
5
5
  declare function writeObject(writer: ByteWriter, obj: PdfObject): void;
6
6
  declare function serializeObject(obj: PdfObject): Uint8Array<ArrayBuffer>;
7
+ declare function serializeObjectToText(obj: PdfObject): string;
7
8
  //#endregion
8
- export { formatNumber, serializeObject, writeObject };
9
+ export { formatNumber, serializeObject, serializeObjectToText, writeObject };
@@ -1,8 +1,9 @@
1
- import { ByteWriter } from "./bytes/writer.js";
2
1
  import { PdfObject } from "./objects.js";
2
+ import { ByteWriter } from "./bytes/writer.js";
3
3
  //#region src/serialize.d.ts
4
4
  declare function formatNumber(n: number): string;
5
5
  declare function writeObject(writer: ByteWriter, obj: PdfObject): void;
6
6
  declare function serializeObject(obj: PdfObject): Uint8Array<ArrayBuffer>;
7
+ declare function serializeObjectToText(obj: PdfObject): string;
7
8
  //#endregion
8
- export { formatNumber, serializeObject, writeObject };
9
+ export { formatNumber, serializeObject, serializeObjectToText, writeObject };
package/dist/serialize.js CHANGED
@@ -1,5 +1,5 @@
1
- import { ByteWriter } from "./bytes/writer.js";
2
1
  import { pdfNum } from "./objects.js";
2
+ import { ByteWriter } from "./bytes/writer.js";
3
3
  //#region src/serialize.ts
4
4
  const NUMBER_DECIMAL_PLACES = 4;
5
5
  const NUMBER_EPSILON = 10 ** -4;
@@ -62,5 +62,8 @@ function serializeObject(obj) {
62
62
  writeObject(writer, obj);
63
63
  return writer.toBytes();
64
64
  }
65
+ function serializeObjectToText(obj) {
66
+ return new TextDecoder().decode(serializeObject(obj));
67
+ }
65
68
  //#endregion
66
- export { formatNumber, serializeObject, writeObject };
69
+ export { formatNumber, serializeObject, serializeObjectToText, writeObject };
package/dist/write.cjs CHANGED
@@ -1,15 +1,15 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_afm_widths = require("./afm-widths-BoeTOK2r.cjs");
3
- const require_util_base64 = require("./util/base64.cjs");
4
- const require_bytes_writer = require("./bytes/writer.cjs");
3
+ const require_notes_annotation_author = require("./notes-annotation-author.cjs");
5
4
  const require_objects = require("./objects.cjs");
5
+ const require_bytes_writer = require("./bytes/writer.cjs");
6
+ const require_serialize = require("./serialize.cjs");
6
7
  const require_bytes_flate = require("./bytes/flate.cjs");
8
+ const require_util_base64 = require("./util/base64.cjs");
7
9
  const require_util_abort = require("./util/abort.cjs");
8
10
  const require_image_jpeg_info = require("./image/jpeg-info.cjs");
9
- const require_notes_annotation_author = require("./notes-annotation-author.cjs");
10
11
  const require_image_png_decode = require("./image/png-decode.cjs");
11
12
  const require_embedded_font = require("./embedded-font.cjs");
12
- const require_serialize = require("./serialize.cjs");
13
13
  const require_content_write = require("./content-write.cjs");
14
14
  const require_embedded_font_write = require("./embedded-font-write.cjs");
15
15
  const require_font_registry = require("./font-registry.cjs");
@@ -178,9 +178,55 @@ function buildLinkAnnotDict(link) {
178
178
  })
179
179
  });
180
180
  }
181
+ function destinationViewArray(target) {
182
+ const n = (value) => value === void 0 ? require_objects.pdfNull() : require_objects.pdfNum(value);
183
+ if (target.kind === "xyz") return [
184
+ require_objects.pdfName("XYZ"),
185
+ n(target.leftPt),
186
+ n(target.topPt),
187
+ n(target.zoom)
188
+ ];
189
+ if (target.kind === "fitH") return [require_objects.pdfName("FitH"), n(target.topPt)];
190
+ if (target.kind === "fitV") return [require_objects.pdfName("FitV"), n(target.leftPt)];
191
+ if (target.kind === "fitR") return [
192
+ require_objects.pdfName("FitR"),
193
+ n(target.leftPt),
194
+ n(target.bottomPt),
195
+ n(target.rightPt),
196
+ n(target.topPt)
197
+ ];
198
+ if (target.kind === "fitBH") return [require_objects.pdfName("FitBH"), n(target.topPt)];
199
+ if (target.kind === "fitBV") return [require_objects.pdfName("FitBV"), n(target.leftPt)];
200
+ return [require_objects.pdfName(target.kind === "fitB" ? "FitB" : "Fit")];
201
+ }
202
+ function buildInternalLinkAnnotDict(link, doc, pageAllocs) {
203
+ const destination = doc.destinations?.find((d) => d.name === link.destination);
204
+ if (destination === void 0) throw new Error(`internal link names destination "${link.destination}", which the document's destinations table does not carry -- this is a caller-invariant violation`);
205
+ const targetPage = pageAllocs[destination.pageIndex];
206
+ if (targetPage === void 0) throw new Error(`destination "${link.destination}" names page index ${destination.pageIndex}, which is beyond the document's own pages -- this is a caller-invariant violation`);
207
+ return require_objects.pdfDict({
208
+ Type: require_objects.pdfName("Annot"),
209
+ Subtype: require_objects.pdfName("Link"),
210
+ Rect: require_objects.pdfArray([
211
+ link.xPt,
212
+ link.yPt,
213
+ link.xPt + link.widthPt,
214
+ link.yPt + link.heightPt
215
+ ].map((v) => require_objects.pdfNum(v))),
216
+ Border: require_objects.pdfArray([
217
+ 0,
218
+ 0,
219
+ 0
220
+ ].map((v) => require_objects.pdfNum(v))),
221
+ Dest: require_objects.pdfArray([require_objects.pdfRef(targetPage.pageNum, 0), ...destinationViewArray(destination.target)])
222
+ });
223
+ }
181
224
  function isLinkItem(item) {
182
225
  return item.kind === "link";
183
226
  }
227
+ function isInternalLinkItem(item) {
228
+ return item.kind === "internalLink";
229
+ }
184
230
  const NOTES_ANNOTATION_HIDDEN_FLAG = 2;
185
231
  function buildNotesAnnotDict(notes) {
186
232
  return require_objects.pdfDict({
@@ -436,7 +482,7 @@ function writePdf(doc, options = {}) {
436
482
  num: contentsNum,
437
483
  value: require_objects.pdfStream(contentsDict, finalContentBytes)
438
484
  });
439
- const annots = page.items.filter(isLinkItem).map((link) => buildLinkAnnotDict(link));
485
+ const annots = [...page.items.filter(isLinkItem).map((link) => buildLinkAnnotDict(link)), ...page.items.filter(isInternalLinkItem).map((link) => buildInternalLinkAnnotDict(link, doc, pageAllocs))];
440
486
  if (page.notes !== void 0 && page.notes.length > 0) annots.push(buildNotesAnnotDict(page.notes));
441
487
  const pageEntries = /* @__PURE__ */ new Map([
442
488
  ["Type", require_objects.pdfName("Page")],