pdf-codec 4.1.0 → 4.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.
package/dist/write.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { n as widthOfCode, p as winAnsiGlyphName, t as STANDARD_METRICS } from "./afm-widths-De_QMWgC.js";
2
2
  import { NOTES_ANNOTATION_AUTHOR } from "./notes-annotation-author.js";
3
- import { pdfArray, pdfDict, pdfHexString, pdfName, pdfNull, pdfNum, pdfRef, pdfStream } from "./objects.js";
3
+ import { pdfArray, pdfDict, pdfHexString, pdfLiteralString, pdfName, pdfNull, pdfNum, pdfRef, pdfStream } from "./objects.js";
4
4
  import { ByteWriter, concatBytes } from "./bytes/writer.js";
5
5
  import { writeObject } from "./serialize.js";
6
6
  import { deflate } from "./bytes/flate.js";
@@ -201,11 +201,14 @@ function destinationViewArray(target) {
201
201
  if (target.kind === "fitBV") return [pdfName("FitBV"), n(target.leftPt)];
202
202
  return [pdfName(target.kind === "fitB" ? "FitB" : "Fit")];
203
203
  }
204
- function buildInternalLinkAnnotDict(link, doc, pageAllocs) {
205
- const destination = doc.destinations?.find((d) => d.name === link.destination);
206
- 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`);
204
+ function resolveDestinationArray(doc, pageAllocs, name, what) {
205
+ const destination = doc.destinations?.find((d) => d.name === name);
206
+ if (destination === void 0) throw new Error(`${what} names destination "${name}", which the document's destinations table does not carry -- this is a caller-invariant violation`);
207
207
  const targetPage = pageAllocs[destination.pageIndex];
208
- 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`);
208
+ if (targetPage === void 0) throw new Error(`destination "${destination.name}" names page index ${destination.pageIndex}, which is beyond the document's own pages -- this is a caller-invariant violation`);
209
+ return [pdfRef(targetPage.pageNum, 0), ...destinationViewArray(destination.target)];
210
+ }
211
+ function buildInternalLinkAnnotDict(link, doc, pageAllocs) {
209
212
  return pdfDict({
210
213
  Type: pdfName("Annot"),
211
214
  Subtype: pdfName("Link"),
@@ -220,7 +223,7 @@ function buildInternalLinkAnnotDict(link, doc, pageAllocs) {
220
223
  0,
221
224
  0
222
225
  ].map((v) => pdfNum(v))),
223
- Dest: pdfArray([pdfRef(targetPage.pageNum, 0), ...destinationViewArray(destination.target)])
226
+ Dest: pdfArray(resolveDestinationArray(doc, pageAllocs, link.destination, "internal link"))
224
227
  });
225
228
  }
226
229
  function isLinkItem(item) {
@@ -321,13 +324,28 @@ function writePdf(doc, options = {}) {
321
324
  contentsNum: nextObjNum++
322
325
  }));
323
326
  const encryptDictNum = options.encryption === void 0 ? void 0 : nextObjNum++;
327
+ const attachmentAllocs = (doc.attachments ?? []).map(() => ({
328
+ fileNum: nextObjNum++,
329
+ specNum: nextObjNum++
330
+ }));
331
+ const attachmentsNamesNum = attachmentAllocs.length > 0 ? nextObjNum++ : void 0;
332
+ const outlineRootNum = (doc.outline ?? []).length > 0 ? nextObjNum++ : void 0;
333
+ const outlineItemNums = [];
334
+ if (outlineRootNum !== void 0) {
335
+ const countItems = (items) => {
336
+ let n = 0;
337
+ for (const item of items) n += 1 + countItems(item.children);
338
+ return n;
339
+ };
340
+ for (let i = 0; i < countItems(doc.outline ?? []); i += 1) outlineItemNums.push(nextObjNum++);
341
+ }
324
342
  const objects = [];
343
+ const catalogEntries = [["Type", pdfName("Catalog")], ["Pages", pdfRef(pagesNum, 0)]];
344
+ if (attachmentsNamesNum !== void 0) catalogEntries.push(["Names", pdfRef(attachmentsNamesNum, 0)]);
345
+ if (outlineRootNum !== void 0) catalogEntries.push(["Outlines", pdfRef(outlineRootNum, 0)]);
325
346
  objects.push({
326
347
  num: catalogNum,
327
- value: pdfDict({
328
- Type: pdfName("Catalog"),
329
- Pages: pdfRef(pagesNum, 0)
330
- })
348
+ value: pdfDict(Object.fromEntries(catalogEntries))
331
349
  });
332
350
  objects.push({
333
351
  num: pagesNum,
@@ -341,6 +359,73 @@ function writePdf(doc, options = {}) {
341
359
  num: infoNum,
342
360
  value: buildInfoDict(doc)
343
361
  });
362
+ for (const [index, attachment] of (doc.attachments ?? []).entries()) {
363
+ const alloc = attachmentAllocs[index];
364
+ const fileEntries = [["Type", pdfName("EmbeddedFile")]];
365
+ if (attachment.mimeType !== void 0) fileEntries.push(["Subtype", pdfName(attachment.mimeType)]);
366
+ objects.push({
367
+ num: alloc.fileNum,
368
+ value: pdfStream(pdfDict(Object.fromEntries(fileEntries)), base64ToBytes(attachment.base64))
369
+ });
370
+ const specEntries = [
371
+ ["Type", pdfName("Filespec")],
372
+ ["F", pdfLiteralString(new TextEncoder().encode(attachment.name))],
373
+ ["EF", pdfDict({ F: pdfRef(alloc.fileNum, 0) })]
374
+ ];
375
+ if (attachment.description !== void 0) specEntries.push(["Desc", pdfLiteralString(new TextEncoder().encode(attachment.description))]);
376
+ objects.push({
377
+ num: alloc.specNum,
378
+ value: pdfDict(Object.fromEntries(specEntries))
379
+ });
380
+ }
381
+ if (attachmentsNamesNum !== void 0) {
382
+ const names = [];
383
+ for (const [index, attachment] of (doc.attachments ?? []).entries()) {
384
+ names.push(pdfLiteralString(new TextEncoder().encode(attachment.name)));
385
+ names.push(pdfRef(attachmentAllocs[index].specNum, 0));
386
+ }
387
+ objects.push({
388
+ num: attachmentsNamesNum,
389
+ value: pdfDict({ EmbeddedFiles: pdfDict({ Names: pdfArray(names) }) })
390
+ });
391
+ }
392
+ if (outlineRootNum !== void 0 && (doc.outline ?? []).length > 0) {
393
+ let itemCursor = 0;
394
+ const emitItems = (items, parentNum) => {
395
+ const siblingNums = [];
396
+ let prevNum;
397
+ for (const item of items) {
398
+ const ownNum = outlineItemNums[itemCursor];
399
+ itemCursor += 1;
400
+ siblingNums.push(ownNum);
401
+ const childNums = emitItems(item.children, ownNum);
402
+ const entries = [["Title", pdfLiteralString(new TextEncoder().encode(item.title))], ["Parent", pdfRef(parentNum, 0)]];
403
+ if (item.destination !== void 0) entries.push(["Dest", pdfArray(resolveDestinationArray(doc, pageAllocs, item.destination, `outline item "${item.title}"`))]);
404
+ if (prevNum !== void 0) entries.push(["Prev", pdfRef(prevNum, 0)]);
405
+ if (siblingNums.length < items.length) entries.push(["Next", pdfRef(outlineItemNums[itemCursor], 0)]);
406
+ if (childNums.length > 0) {
407
+ entries.push(["First", pdfRef(childNums[0], 0)]);
408
+ entries.push(["Last", pdfRef(childNums[childNums.length - 1], 0)]);
409
+ entries.push(["Count", pdfNum(childNums.length)]);
410
+ }
411
+ objects.push({
412
+ num: ownNum,
413
+ value: pdfDict(Object.fromEntries(entries))
414
+ });
415
+ prevNum = ownNum;
416
+ }
417
+ return siblingNums;
418
+ };
419
+ const topLevelNums = emitItems(doc.outline ?? [], outlineRootNum);
420
+ objects.push({
421
+ num: outlineRootNum,
422
+ value: pdfDict({
423
+ Type: pdfName("Outlines"),
424
+ First: pdfRef(topLevelNums[0], 0),
425
+ Last: pdfRef(topLevelNums[topLevelNums.length - 1], 0)
426
+ })
427
+ });
428
+ }
344
429
  for (const [standardName, alloc] of fontAllocs) {
345
430
  const { font, descriptor } = buildFontObjects(standardName, pdfRef(alloc.descNum, 0));
346
431
  objects.push({
@@ -396,9 +481,10 @@ function writePdf(doc, options = {}) {
396
481
  });
397
482
  }
398
483
  for (const [face, alloc] of embeddedAllocs) {
399
- const subset = subsetSfnt(face.font, [...alloc.codePoints].sort((a, b) => a - b));
484
+ const usedGlyphs = collectEmbeddedGlyphs(alloc.texts, face);
485
+ const subset = subsetSfnt(face.font, [...alloc.codePoints].sort((a, b) => a - b), [...usedGlyphs.keys()].sort((a, b) => a - b));
400
486
  if (subset === void 0) throw new Error(`font "${face.postScriptName}" resolved to an embeddable face, but its glyph outlines could not be subsetted -- only TrueType-outline ('glyf') fonts can be embedded, so supply a TrueType face for this family or drop it from the registry`);
401
- const built = buildEmbeddedFontObjects(face, subset, collectEmbeddedGlyphs(alloc.texts, face), {
487
+ const built = buildEmbeddedFontObjects(face, subset, usedGlyphs, {
402
488
  cidFontRef: pdfRef(alloc.cidFontNum, 0),
403
489
  descriptorRef: pdfRef(alloc.descriptorNum, 0),
404
490
  fontFileRef: pdfRef(alloc.fontFileNum, 0),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pdf-codec",
3
- "version": "4.1.0",
3
+ "version": "4.3.0",
4
4
  "description": "Hand-written, dependency-minimal PDF codec: parses arbitrary real-world PDFs and generates new ones, built on its own codec-owned LayoutDocument item model and Zod 4 codecs.",
5
5
  "type": "module",
6
6
  "repository": {