@kitsy/pdf 0.0.0-stage → 0.1.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 (132) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/LICENSE +201 -0
  3. package/NOTICE +16 -0
  4. package/README.md +229 -2
  5. package/THIRD_PARTY_NOTICES.md +48 -0
  6. package/dist/chunk-2K5X7L5J.js +43 -0
  7. package/dist/chunk-2K5X7L5J.js.map +1 -0
  8. package/dist/chunk-4EAYBJ6G.js +362 -0
  9. package/dist/chunk-4EAYBJ6G.js.map +1 -0
  10. package/dist/chunk-4LBJTDV5.js +165 -0
  11. package/dist/chunk-4LBJTDV5.js.map +1 -0
  12. package/dist/chunk-4USCGC24.js +304 -0
  13. package/dist/chunk-4USCGC24.js.map +1 -0
  14. package/dist/chunk-5OIMR74G.js +273 -0
  15. package/dist/chunk-5OIMR74G.js.map +1 -0
  16. package/dist/chunk-6NFNAKWA.js +257 -0
  17. package/dist/chunk-6NFNAKWA.js.map +1 -0
  18. package/dist/chunk-7MTEMQLL.js +47 -0
  19. package/dist/chunk-7MTEMQLL.js.map +1 -0
  20. package/dist/chunk-7V4SWTUK.js +83 -0
  21. package/dist/chunk-7V4SWTUK.js.map +1 -0
  22. package/dist/chunk-7XH7MFZ4.js +295 -0
  23. package/dist/chunk-7XH7MFZ4.js.map +1 -0
  24. package/dist/chunk-BK3MYSUS.js +61 -0
  25. package/dist/chunk-BK3MYSUS.js.map +1 -0
  26. package/dist/chunk-BLHNKR3S.js +35 -0
  27. package/dist/chunk-BLHNKR3S.js.map +1 -0
  28. package/dist/chunk-CCJ43SIX.js +342 -0
  29. package/dist/chunk-CCJ43SIX.js.map +1 -0
  30. package/dist/chunk-CNEDWATL.js +210 -0
  31. package/dist/chunk-CNEDWATL.js.map +1 -0
  32. package/dist/chunk-CYTWDJE2.js +152 -0
  33. package/dist/chunk-CYTWDJE2.js.map +1 -0
  34. package/dist/chunk-DKVOICYZ.js +45 -0
  35. package/dist/chunk-DKVOICYZ.js.map +1 -0
  36. package/dist/chunk-DPEJU4P6.js +79 -0
  37. package/dist/chunk-DPEJU4P6.js.map +1 -0
  38. package/dist/chunk-DQPABZTU.js +22 -0
  39. package/dist/chunk-DQPABZTU.js.map +1 -0
  40. package/dist/chunk-EKHEY3LI.js +46 -0
  41. package/dist/chunk-EKHEY3LI.js.map +1 -0
  42. package/dist/chunk-G467LFKC.js +191 -0
  43. package/dist/chunk-G467LFKC.js.map +1 -0
  44. package/dist/chunk-GLYWSVSB.js +250 -0
  45. package/dist/chunk-GLYWSVSB.js.map +1 -0
  46. package/dist/chunk-GXBIMUO7.js +135 -0
  47. package/dist/chunk-GXBIMUO7.js.map +1 -0
  48. package/dist/chunk-H6EERRCR.js +180 -0
  49. package/dist/chunk-H6EERRCR.js.map +1 -0
  50. package/dist/chunk-HDTM3LXD.js +35 -0
  51. package/dist/chunk-HDTM3LXD.js.map +1 -0
  52. package/dist/chunk-IGMX56C3.js +180 -0
  53. package/dist/chunk-IGMX56C3.js.map +1 -0
  54. package/dist/chunk-J4IRICCJ.js +74 -0
  55. package/dist/chunk-J4IRICCJ.js.map +1 -0
  56. package/dist/chunk-JXKQ7S6W.js +331 -0
  57. package/dist/chunk-JXKQ7S6W.js.map +1 -0
  58. package/dist/chunk-KY6DXS6S.js +34 -0
  59. package/dist/chunk-KY6DXS6S.js.map +1 -0
  60. package/dist/chunk-NOFGCYGS.js +22 -0
  61. package/dist/chunk-NOFGCYGS.js.map +1 -0
  62. package/dist/chunk-OSHUJRKD.js +131 -0
  63. package/dist/chunk-OSHUJRKD.js.map +1 -0
  64. package/dist/chunk-PMH7OWPF.js +40 -0
  65. package/dist/chunk-PMH7OWPF.js.map +1 -0
  66. package/dist/chunk-PRBKSX3A.js +299 -0
  67. package/dist/chunk-PRBKSX3A.js.map +1 -0
  68. package/dist/chunk-SVPUX7AM.js +48 -0
  69. package/dist/chunk-SVPUX7AM.js.map +1 -0
  70. package/dist/chunk-TR6HJ4NO.js +659 -0
  71. package/dist/chunk-TR6HJ4NO.js.map +1 -0
  72. package/dist/chunk-TV3E4YUF.js +69 -0
  73. package/dist/chunk-TV3E4YUF.js.map +1 -0
  74. package/dist/chunk-U2ROTOPF.js +256 -0
  75. package/dist/chunk-U2ROTOPF.js.map +1 -0
  76. package/dist/chunk-VCTN77K2.js +112 -0
  77. package/dist/chunk-VCTN77K2.js.map +1 -0
  78. package/dist/chunk-WEOPDD3P.js +120 -0
  79. package/dist/chunk-WEOPDD3P.js.map +1 -0
  80. package/dist/chunk-ZBZ7OC6I.js +228 -0
  81. package/dist/chunk-ZBZ7OC6I.js.map +1 -0
  82. package/dist/config-CJYo46X9.d.ts +19 -0
  83. package/dist/index.d.ts +487 -0
  84. package/dist/index.js +23 -0
  85. package/dist/index.js.map +1 -0
  86. package/dist/react/certificate-sign.d.ts +5 -0
  87. package/dist/react/certificate-sign.js +14 -0
  88. package/dist/react/certificate-sign.js.map +1 -0
  89. package/dist/react/compress.d.ts +5 -0
  90. package/dist/react/compress.js +8 -0
  91. package/dist/react/compress.js.map +1 -0
  92. package/dist/react/fill-form.d.ts +5 -0
  93. package/dist/react/fill-form.js +8 -0
  94. package/dist/react/fill-form.js.map +1 -0
  95. package/dist/react/images-to-pdf.d.ts +5 -0
  96. package/dist/react/images-to-pdf.js +7 -0
  97. package/dist/react/images-to-pdf.js.map +1 -0
  98. package/dist/react/merge.d.ts +5 -0
  99. package/dist/react/merge.js +8 -0
  100. package/dist/react/merge.js.map +1 -0
  101. package/dist/react/metadata.d.ts +5 -0
  102. package/dist/react/metadata.js +8 -0
  103. package/dist/react/metadata.js.map +1 -0
  104. package/dist/react/organize.d.ts +5 -0
  105. package/dist/react/organize.js +12 -0
  106. package/dist/react/organize.js.map +1 -0
  107. package/dist/react/pdf-to-images.d.ts +5 -0
  108. package/dist/react/pdf-to-images.js +13 -0
  109. package/dist/react/pdf-to-images.js.map +1 -0
  110. package/dist/react/protect.d.ts +5 -0
  111. package/dist/react/protect.js +8 -0
  112. package/dist/react/protect.js.map +1 -0
  113. package/dist/react/sign.d.ts +5 -0
  114. package/dist/react/sign.js +13 -0
  115. package/dist/react/sign.js.map +1 -0
  116. package/dist/react/split.d.ts +5 -0
  117. package/dist/react/split.js +11 -0
  118. package/dist/react/split.js.map +1 -0
  119. package/dist/react/verify-signatures.d.ts +5 -0
  120. package/dist/react/verify-signatures.js +8 -0
  121. package/dist/react/verify-signatures.js.map +1 -0
  122. package/dist/react/watermark.d.ts +5 -0
  123. package/dist/react/watermark.js +10 -0
  124. package/dist/react/watermark.js.map +1 -0
  125. package/dist/react.d.ts +40 -0
  126. package/dist/react.js +40 -0
  127. package/dist/react.js.map +1 -0
  128. package/dist/styles.css +2 -0
  129. package/dist/vite.d.ts +5 -0
  130. package/dist/vite.js +9 -0
  131. package/dist/vite.js.map +1 -0
  132. package/package.json +163 -4
@@ -0,0 +1,112 @@
1
+ import { visualSize, textOrigin } from './chunk-PMH7OWPF.js';
2
+ import { parsePageSelection } from './chunk-BLHNKR3S.js';
3
+ import { loadPdf } from './chunk-EKHEY3LI.js';
4
+ import { StandardFonts, degrees, rgb } from 'pdf-lib';
5
+
6
+ function hexToRgb(hex, fallback = "#64748b") {
7
+ const value = /^#?[0-9a-f]{6}$/i.test(hex || "") ? hex : fallback;
8
+ const clean = value.replace("#", "");
9
+ return rgb(
10
+ parseInt(clean.slice(0, 2), 16) / 255,
11
+ parseInt(clean.slice(2, 4), 16) / 255,
12
+ parseInt(clean.slice(4, 6), 16) / 255
13
+ );
14
+ }
15
+ function formatPageNumber(format, n, total) {
16
+ return (format || "{n}").replace(/\{n\}/g, String(n)).replace(/\{total\}/g, String(total));
17
+ }
18
+ function measure(font, text, size) {
19
+ try {
20
+ return {
21
+ width: font.widthOfTextAtSize(text, size),
22
+ height: font.heightAtSize(size, { descender: false })
23
+ };
24
+ } catch {
25
+ throw new Error(
26
+ `"${text}" contains characters the standard PDF font cannot draw. Use basic Latin letters, numbers, and punctuation.`
27
+ );
28
+ }
29
+ }
30
+ function drawVisualText(page, font, text, { vx, vy, size, angle = 0, color, opacity = 1 }) {
31
+ const point = textOrigin(page, vx, vy);
32
+ page.drawText(text, {
33
+ x: point.x,
34
+ y: point.y,
35
+ size,
36
+ font,
37
+ color,
38
+ opacity,
39
+ rotate: degrees(point.angle + angle)
40
+ });
41
+ }
42
+ function pageNumberAnchor(position, visualWidth, visualHeight, textWidth, textHeight, margin) {
43
+ const [vertical, horizontal] = position.split("-");
44
+ const vy = vertical === "top" ? visualHeight - margin - textHeight : margin;
45
+ let vx = (visualWidth - textWidth) / 2;
46
+ if (horizontal === "left") vx = margin;
47
+ if (horizontal === "right") vx = visualWidth - margin - textWidth;
48
+ return { vx, vy };
49
+ }
50
+ function planStampedPages(targets, startAt = 1) {
51
+ const unique = [...new Set(targets)].sort((a, b) => a - b);
52
+ const first = Math.floor(Number(startAt)) || 1;
53
+ const total = unique.length + first - 1;
54
+ return unique.map((pageIndex, order) => ({ pageIndex, n: order + first, total }));
55
+ }
56
+ async function stampPdf(bytes, options) {
57
+ const watermark = options.watermark?.text?.trim() ? options.watermark : null;
58
+ const pageNumbers = options.pageNumbers || null;
59
+ if (!watermark && !pageNumbers) {
60
+ throw new Error("Turn on a watermark or page numbers first.");
61
+ }
62
+ const doc = await loadPdf(bytes);
63
+ const pages = doc.getPages();
64
+ const targets = parsePageSelection(options.pages, pages.length);
65
+ const font = await doc.embedFont(StandardFonts.Helvetica);
66
+ const boldFont = await doc.embedFont(StandardFonts.HelveticaBold);
67
+ planStampedPages(targets, pageNumbers?.startAt ?? 1).forEach(({ pageIndex, n, total }) => {
68
+ const page = pages[pageIndex];
69
+ const { visualWidth, visualHeight } = visualSize(page);
70
+ if (watermark) {
71
+ const size = Math.max(6, Number(watermark.fontSize) || 48);
72
+ const text = watermark.text.trim();
73
+ const { width, height } = measure(boldFont, text, size);
74
+ const angle = Number(watermark.rotation ?? 45);
75
+ const radians = angle * Math.PI / 180;
76
+ const vx = visualWidth / 2 - width / 2 * Math.cos(radians) + height / 2 * Math.sin(radians);
77
+ const vy = visualHeight / 2 - width / 2 * Math.sin(radians) - height / 2 * Math.cos(radians);
78
+ drawVisualText(page, boldFont, text, {
79
+ vx,
80
+ vy,
81
+ size,
82
+ angle,
83
+ color: hexToRgb(watermark.color, "#e11d48"),
84
+ opacity: Math.min(1, Math.max(0.05, Number(watermark.opacity ?? 0.2)))
85
+ });
86
+ }
87
+ if (pageNumbers) {
88
+ const size = Math.max(6, Number(pageNumbers.fontSize) || 11);
89
+ const text = formatPageNumber(pageNumbers.format, n, total);
90
+ const { width, height } = measure(font, text, size);
91
+ const { vx, vy } = pageNumberAnchor(
92
+ pageNumbers.position || "bottom-center",
93
+ visualWidth,
94
+ visualHeight,
95
+ width,
96
+ height,
97
+ Math.max(0, Number(pageNumbers.margin ?? 28))
98
+ );
99
+ drawVisualText(page, font, text, {
100
+ vx,
101
+ vy,
102
+ size,
103
+ color: hexToRgb(pageNumbers.color, "#334155")
104
+ });
105
+ }
106
+ });
107
+ return doc.save();
108
+ }
109
+
110
+ export { formatPageNumber, planStampedPages, stampPdf };
111
+ //# sourceMappingURL=chunk-VCTN77K2.js.map
112
+ //# sourceMappingURL=chunk-VCTN77K2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/stamp/stampPdfCore.ts"],"names":[],"mappings":"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","file":"chunk-VCTN77K2.js","sourcesContent":["import { PDFFont, PDFPage, StandardFonts, degrees, rgb } from \"pdf-lib\";\nimport { loadPdf } from \"../shared/files\";\nimport { parsePageSelection } from \"../shared/pageRanges\";\nimport { textOrigin, visualSize } from \"../shared/pageGeometry\";\n\nexport { textOrigin, visualToUser } from \"../shared/pageGeometry\";\n\nexport type NumberPosition =\n | \"bottom-left\"\n | \"bottom-center\"\n | \"bottom-right\"\n | \"top-left\"\n | \"top-center\"\n | \"top-right\";\n\nexport type WatermarkOptions = {\n text: string;\n fontSize?: number;\n /** 0-1 */\n opacity?: number;\n /** Degrees, counter-clockwise, as seen on screen. */\n rotation?: number;\n /** \"#rrggbb\" */\n color?: string;\n};\n\nexport type PageNumberOptions = {\n /** Tokens: {n} page number, {total} numbered page count. */\n format?: string;\n position?: NumberPosition;\n fontSize?: number;\n startAt?: number;\n margin?: number;\n color?: string;\n};\n\nexport type StampPdfOptions = {\n /** 1-based selection of pages to stamp. Empty means all pages. */\n pages?: string;\n watermark?: WatermarkOptions | null;\n pageNumbers?: PageNumberOptions | null;\n};\n\nexport function hexToRgb(hex: string | undefined, fallback = \"#64748b\") {\n const value = /^#?[0-9a-f]{6}$/i.test(hex || \"\") ? hex : fallback;\n const clean = value.replace(\"#\", \"\");\n return rgb(\n parseInt(clean.slice(0, 2), 16) / 255,\n parseInt(clean.slice(2, 4), 16) / 255,\n parseInt(clean.slice(4, 6), 16) / 255\n );\n}\n\nexport function formatPageNumber(format: string | undefined, n: number, total: number) {\n return (format || \"{n}\").replace(/\\{n\\}/g, String(n)).replace(/\\{total\\}/g, String(total));\n}\n\nfunction measure(font: PDFFont, text: string, size: number) {\n try {\n return {\n width: font.widthOfTextAtSize(text, size),\n height: font.heightAtSize(size, { descender: false }),\n };\n } catch {\n throw new Error(\n `\"${text}\" contains characters the standard PDF font cannot draw. Use basic Latin letters, numbers, and punctuation.`\n );\n }\n}\n\nfunction drawVisualText(\n page: PDFPage,\n font: PDFFont,\n text: string,\n { vx, vy, size, angle = 0, color, opacity = 1 }\n) {\n const point = textOrigin(page, vx, vy);\n page.drawText(text, {\n x: point.x,\n y: point.y,\n size,\n font,\n color,\n opacity,\n rotate: degrees(point.angle + angle),\n });\n}\n\nexport function pageNumberAnchor(\n position: NumberPosition,\n visualWidth: number,\n visualHeight: number,\n textWidth: number,\n textHeight: number,\n margin: number\n) {\n const [vertical, horizontal] = position.split(\"-\");\n const vy = vertical === \"top\" ? visualHeight - margin - textHeight : margin;\n let vx = (visualWidth - textWidth) / 2;\n if (horizontal === \"left\") vx = margin;\n if (horizontal === \"right\") vx = visualWidth - margin - textWidth;\n return { vx, vy };\n}\n\n/**\n * Which pages get stamped and the number each one shows. Pages listed twice\n * are stamped once; numbering counts only stamped pages.\n */\nexport function planStampedPages(targets: number[], startAt = 1) {\n // Stamping never reorders pages, so number them in document order.\n const unique = [...new Set(targets)].sort((a, b) => a - b);\n const first = Math.floor(Number(startAt)) || 1;\n const total = unique.length + first - 1;\n return unique.map((pageIndex, order) => ({ pageIndex, n: order + first, total }));\n}\n\nexport async function stampPdf(bytes: ArrayBuffer | Uint8Array, options: StampPdfOptions) {\n const watermark = options.watermark?.text?.trim() ? options.watermark : null;\n const pageNumbers = options.pageNumbers || null;\n if (!watermark && !pageNumbers) {\n throw new Error(\"Turn on a watermark or page numbers first.\");\n }\n\n const doc = await loadPdf(bytes);\n const pages = doc.getPages();\n const targets = parsePageSelection(options.pages, pages.length);\n const font = await doc.embedFont(StandardFonts.Helvetica);\n const boldFont = await doc.embedFont(StandardFonts.HelveticaBold);\n\n planStampedPages(targets, pageNumbers?.startAt ?? 1).forEach(({ pageIndex, n, total }) => {\n const page = pages[pageIndex];\n const { visualWidth, visualHeight } = visualSize(page);\n\n if (watermark) {\n const size = Math.max(6, Number(watermark.fontSize) || 48);\n const text = watermark.text.trim();\n const { width, height } = measure(boldFont, text, size);\n const angle = Number(watermark.rotation ?? 45);\n const radians = (angle * Math.PI) / 180;\n // Start the baseline so the text's centre sits on the page centre.\n const vx = visualWidth / 2 - (width / 2) * Math.cos(radians) + (height / 2) * Math.sin(radians);\n const vy = visualHeight / 2 - (width / 2) * Math.sin(radians) - (height / 2) * Math.cos(radians);\n drawVisualText(page, boldFont, text, {\n vx,\n vy,\n size,\n angle,\n color: hexToRgb(watermark.color, \"#e11d48\"),\n opacity: Math.min(1, Math.max(0.05, Number(watermark.opacity ?? 0.2))),\n });\n }\n\n if (pageNumbers) {\n const size = Math.max(6, Number(pageNumbers.fontSize) || 11);\n const text = formatPageNumber(pageNumbers.format, n, total);\n const { width, height } = measure(font, text, size);\n const { vx, vy } = pageNumberAnchor(\n pageNumbers.position || \"bottom-center\",\n visualWidth,\n visualHeight,\n width,\n height,\n Math.max(0, Number(pageNumbers.margin ?? 28))\n );\n drawVisualText(page, font, text, {\n vx,\n vy,\n size,\n color: hexToRgb(pageNumbers.color, \"#334155\"),\n });\n }\n });\n\n return doc.save();\n}\n"]}
@@ -0,0 +1,120 @@
1
+ import { producerName } from './chunk-DQPABZTU.js';
2
+ import { PDFDocument } from 'pdf-lib';
3
+
4
+ var PAGE_SIZES = {
5
+ a4: [595.28, 841.89],
6
+ letter: [612, 792]
7
+ };
8
+ function detectImageKind(bytes) {
9
+ const view = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);
10
+ if (view.length >= 8 && view[0] === 137 && view[1] === 80 && view[2] === 78 && view[3] === 71) {
11
+ return "png";
12
+ }
13
+ if (view.length >= 3 && view[0] === 255 && view[1] === 216 && view[2] === 255) {
14
+ return "jpg";
15
+ }
16
+ return null;
17
+ }
18
+ function getJpegOrientation(bytes) {
19
+ const view = new DataView(
20
+ bytes instanceof Uint8Array ? bytes.buffer : bytes,
21
+ bytes instanceof Uint8Array ? bytes.byteOffset : 0,
22
+ bytes.byteLength
23
+ );
24
+ if (view.byteLength < 4 || view.getUint16(0) !== 65496) return 1;
25
+ let offset = 2;
26
+ while (offset + 4 <= view.byteLength) {
27
+ const marker = view.getUint16(offset);
28
+ if ((marker & 65280) !== 65280 || marker === 65498) break;
29
+ const length = view.getUint16(offset + 2);
30
+ if (marker === 65505 && view.getUint32(offset + 4) === 1165519206) {
31
+ const tiff = offset + 10;
32
+ if (tiff + 8 > view.byteLength) return 1;
33
+ const little = view.getUint16(tiff) === 18761;
34
+ const ifd = tiff + view.getUint32(tiff + 4, little);
35
+ if (ifd + 2 > view.byteLength) return 1;
36
+ const entries = view.getUint16(ifd, little);
37
+ for (let i = 0; i < entries; i += 1) {
38
+ const entry = ifd + 2 + i * 12;
39
+ if (entry + 12 > view.byteLength) return 1;
40
+ if (view.getUint16(entry, little) === 274) {
41
+ const value = view.getUint16(entry + 8, little);
42
+ return value >= 1 && value <= 8 ? value : 1;
43
+ }
44
+ }
45
+ return 1;
46
+ }
47
+ offset += 2 + length;
48
+ }
49
+ return 1;
50
+ }
51
+ function layoutImage(imageWidth, imageHeight, {
52
+ pageSize = "a4",
53
+ orientation = "auto",
54
+ margin = 0
55
+ } = {}) {
56
+ const safeMargin = Math.max(0, margin);
57
+ if (pageSize === "fit") {
58
+ return {
59
+ pageWidth: imageWidth + safeMargin * 2,
60
+ pageHeight: imageHeight + safeMargin * 2,
61
+ x: safeMargin,
62
+ y: safeMargin,
63
+ width: imageWidth,
64
+ height: imageHeight
65
+ };
66
+ }
67
+ const [portraitWidth, portraitHeight] = PAGE_SIZES[pageSize];
68
+ const landscape = orientation === "landscape" || orientation === "auto" && imageWidth > imageHeight;
69
+ const pageWidth = landscape ? portraitHeight : portraitWidth;
70
+ const pageHeight = landscape ? portraitWidth : portraitHeight;
71
+ const boxWidth = Math.max(1, pageWidth - safeMargin * 2);
72
+ const boxHeight = Math.max(1, pageHeight - safeMargin * 2);
73
+ const scale = Math.min(1, boxWidth / imageWidth, boxHeight / imageHeight);
74
+ const width = imageWidth * scale;
75
+ const height = imageHeight * scale;
76
+ return {
77
+ pageWidth,
78
+ pageHeight,
79
+ x: (pageWidth - width) / 2,
80
+ y: (pageHeight - height) / 2,
81
+ width,
82
+ height
83
+ };
84
+ }
85
+ async function imagesToPdf(images, options = {}) {
86
+ if (!images.length) {
87
+ throw new Error("Add at least one image.");
88
+ }
89
+ const doc = await PDFDocument.create();
90
+ for (const image of images) {
91
+ const kind = detectImageKind(image.bytes);
92
+ if (!kind) {
93
+ throw new Error(
94
+ `${image.name}: only PNG and JPG images can be embedded.`
95
+ );
96
+ }
97
+ let embedded;
98
+ try {
99
+ embedded = kind === "png" ? await doc.embedPng(image.bytes) : await doc.embedJpg(image.bytes);
100
+ } catch {
101
+ throw new Error(`${image.name}: this image could not be read.`);
102
+ }
103
+ const box = layoutImage(embedded.width, embedded.height, options);
104
+ const page = doc.addPage([box.pageWidth, box.pageHeight]);
105
+ page.drawImage(embedded, {
106
+ x: box.x,
107
+ y: box.y,
108
+ width: box.width,
109
+ height: box.height
110
+ });
111
+ }
112
+ if (options.title) doc.setTitle(options.title);
113
+ doc.setProducer(producerName());
114
+ doc.setCreationDate(/* @__PURE__ */ new Date());
115
+ return doc.save();
116
+ }
117
+
118
+ export { detectImageKind, getJpegOrientation, imagesToPdf, layoutImage };
119
+ //# sourceMappingURL=chunk-WEOPDD3P.js.map
120
+ //# sourceMappingURL=chunk-WEOPDD3P.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/images/imagesToPdfCore.ts"],"names":[],"mappings":"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","file":"chunk-WEOPDD3P.js","sourcesContent":["import { PDFDocument } from \"pdf-lib\";\nimport { producerName } from \"../config\";\n\nexport type ImageKind = \"png\" | \"jpg\";\nexport type PageSizeOption = \"fit\" | \"a4\" | \"letter\";\nexport type OrientationOption = \"auto\" | \"portrait\" | \"landscape\";\n\nexport type ImageSource = { name: string; bytes: ArrayBuffer | Uint8Array };\n\nexport type ImagesToPdfOptions = {\n pageSize?: PageSizeOption;\n orientation?: OrientationOption;\n /** Margin in PDF points (1/72 inch). */\n margin?: number;\n title?: string;\n};\n\n// Portrait width x height in PDF points.\nexport const PAGE_SIZES: Record<\n Exclude<PageSizeOption, \"fit\">,\n [number, number]\n> = {\n a4: [595.28, 841.89],\n letter: [612, 792],\n};\n\nexport function detectImageKind(\n bytes: ArrayBuffer | Uint8Array,\n): ImageKind | null {\n const view = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);\n if (\n view.length >= 8 &&\n view[0] === 0x89 &&\n view[1] === 0x50 &&\n view[2] === 0x4e &&\n view[3] === 0x47\n ) {\n return \"png\";\n }\n if (\n view.length >= 3 &&\n view[0] === 0xff &&\n view[1] === 0xd8 &&\n view[2] === 0xff\n ) {\n return \"jpg\";\n }\n return null;\n}\n\n/**\n * Read the EXIF orientation (1-8) from a JPEG, or 1 when absent. Values other\n * than 1 mean viewers rotate/flip the photo, which pdf-lib does not do.\n */\nexport function getJpegOrientation(bytes: ArrayBuffer | Uint8Array) {\n const view = new DataView(\n bytes instanceof Uint8Array ? bytes.buffer : bytes,\n bytes instanceof Uint8Array ? bytes.byteOffset : 0,\n bytes.byteLength,\n );\n if (view.byteLength < 4 || view.getUint16(0) !== 0xffd8) return 1;\n\n let offset = 2;\n while (offset + 4 <= view.byteLength) {\n const marker = view.getUint16(offset);\n if ((marker & 0xff00) !== 0xff00 || marker === 0xffda) break;\n const length = view.getUint16(offset + 2);\n\n if (marker === 0xffe1 && view.getUint32(offset + 4) === 0x45786966) {\n const tiff = offset + 10;\n if (tiff + 8 > view.byteLength) return 1;\n const little = view.getUint16(tiff) === 0x4949;\n const ifd = tiff + view.getUint32(tiff + 4, little);\n if (ifd + 2 > view.byteLength) return 1;\n const entries = view.getUint16(ifd, little);\n for (let i = 0; i < entries; i += 1) {\n const entry = ifd + 2 + i * 12;\n if (entry + 12 > view.byteLength) return 1;\n if (view.getUint16(entry, little) === 0x0112) {\n const value = view.getUint16(entry + 8, little);\n return value >= 1 && value <= 8 ? value : 1;\n }\n }\n return 1;\n }\n offset += 2 + length;\n }\n return 1;\n}\n\n/**\n * Page size and image placement for one image. For \"fit\" the page wraps the\n * image (plus margin); otherwise the image is scaled down to fit and centred.\n */\nexport function layoutImage(\n imageWidth: number,\n imageHeight: number,\n {\n pageSize = \"a4\",\n orientation = \"auto\",\n margin = 0,\n }: ImagesToPdfOptions = {},\n) {\n const safeMargin = Math.max(0, margin);\n\n if (pageSize === \"fit\") {\n return {\n pageWidth: imageWidth + safeMargin * 2,\n pageHeight: imageHeight + safeMargin * 2,\n x: safeMargin,\n y: safeMargin,\n width: imageWidth,\n height: imageHeight,\n };\n }\n\n const [portraitWidth, portraitHeight] = PAGE_SIZES[pageSize];\n const landscape =\n orientation === \"landscape\" ||\n (orientation === \"auto\" && imageWidth > imageHeight);\n const pageWidth = landscape ? portraitHeight : portraitWidth;\n const pageHeight = landscape ? portraitWidth : portraitHeight;\n\n const boxWidth = Math.max(1, pageWidth - safeMargin * 2);\n const boxHeight = Math.max(1, pageHeight - safeMargin * 2);\n const scale = Math.min(1, boxWidth / imageWidth, boxHeight / imageHeight);\n const width = imageWidth * scale;\n const height = imageHeight * scale;\n\n return {\n pageWidth,\n pageHeight,\n x: (pageWidth - width) / 2,\n y: (pageHeight - height) / 2,\n width,\n height,\n };\n}\n\nexport async function imagesToPdf(\n images: ImageSource[],\n options: ImagesToPdfOptions = {},\n) {\n if (!images.length) {\n throw new Error(\"Add at least one image.\");\n }\n\n const doc = await PDFDocument.create();\n\n for (const image of images) {\n const kind = detectImageKind(image.bytes);\n if (!kind) {\n throw new Error(\n `${image.name}: only PNG and JPG images can be embedded.`,\n );\n }\n\n let embedded;\n try {\n embedded =\n kind === \"png\"\n ? await doc.embedPng(image.bytes)\n : await doc.embedJpg(image.bytes);\n } catch {\n throw new Error(`${image.name}: this image could not be read.`);\n }\n\n const box = layoutImage(embedded.width, embedded.height, options);\n const page = doc.addPage([box.pageWidth, box.pageHeight]);\n page.drawImage(embedded, {\n x: box.x,\n y: box.y,\n width: box.width,\n height: box.height,\n });\n }\n\n if (options.title) doc.setTitle(options.title);\n doc.setProducer(producerName());\n doc.setCreationDate(new Date());\n\n return doc.save();\n}\n"]}
@@ -0,0 +1,228 @@
1
+ import { loadPdf } from './chunk-EKHEY3LI.js';
2
+ import { PDFArray, PDFRawStream, PDFName, PDFNumber, PDFRef, PDFDict } from 'pdf-lib';
3
+
4
+ var COMPRESSION_LEVELS = {
5
+ light: { label: "Light", maxDimension: 2400, quality: 0.82 },
6
+ balanced: { label: "Balanced", maxDimension: 1600, quality: 0.7 },
7
+ strong: { label: "Strong", maxDimension: 1100, quality: 0.55 }
8
+ };
9
+ var MIN_STREAM_BYTES = 8 * 1024;
10
+ var MAX_PIXELS = 4e7;
11
+ var MIN_SAVING = 0.9;
12
+ var N = (name) => PDFName.of(name);
13
+ function numberOf(dict, key, fallback) {
14
+ const value = dict.lookup(N(key));
15
+ return value instanceof PDFNumber ? value.asNumber() : fallback;
16
+ }
17
+ function filterNames(dict) {
18
+ const filter = dict.lookup(N("Filter"));
19
+ if (filter instanceof PDFName) return [filter.decodeText()];
20
+ if (filter instanceof PDFArray) {
21
+ return filter.asArray().map((item) => item instanceof PDFName ? item.decodeText() : "?");
22
+ }
23
+ return [];
24
+ }
25
+ function decodeParms(dict) {
26
+ const parms = dict.lookup(N("DecodeParms"));
27
+ if (parms instanceof PDFDict) return parms;
28
+ if (parms instanceof PDFArray && parms.size() === 1) {
29
+ const first = parms.lookup(0);
30
+ return first instanceof PDFDict ? first : null;
31
+ }
32
+ return null;
33
+ }
34
+ function colorComponents(dict) {
35
+ const space = dict.lookup(N("ColorSpace"));
36
+ if (space === N("DeviceRGB")) return 3;
37
+ if (space === N("DeviceGray")) return 1;
38
+ if (space instanceof PDFArray && space.size() === 2 && space.lookup(0) === N("ICCBased")) {
39
+ const profile = space.lookup(1);
40
+ const count = profile instanceof PDFRawStream ? numberOf(profile.dict, "N") : void 0;
41
+ if (count === 3) return 3;
42
+ if (count === 1) return 1;
43
+ }
44
+ return null;
45
+ }
46
+ function unpredictPng(data, columns, colors, bitsPerComponent = 8) {
47
+ const bytesPerPixel = Math.max(1, Math.ceil(colors * bitsPerComponent / 8));
48
+ const rowLength = Math.ceil(columns * colors * bitsPerComponent / 8);
49
+ const rows = Math.floor(data.length / (rowLength + 1));
50
+ const out = new Uint8Array(rows * rowLength);
51
+ let previous = new Uint8Array(rowLength);
52
+ for (let row = 0; row < rows; row += 1) {
53
+ const filter = data[row * (rowLength + 1)];
54
+ const input = data.subarray(row * (rowLength + 1) + 1, (row + 1) * (rowLength + 1));
55
+ const current = out.subarray(row * rowLength, (row + 1) * rowLength);
56
+ for (let i = 0; i < rowLength; i += 1) {
57
+ const left = i >= bytesPerPixel ? current[i - bytesPerPixel] : 0;
58
+ const up = previous[i];
59
+ const upLeft = i >= bytesPerPixel ? previous[i - bytesPerPixel] : 0;
60
+ let value = input[i];
61
+ if (filter === 1) value += left;
62
+ else if (filter === 2) value += up;
63
+ else if (filter === 3) value += Math.floor((left + up) / 2);
64
+ else if (filter === 4) {
65
+ const p = left + up - upLeft;
66
+ const pa = Math.abs(p - left);
67
+ const pb = Math.abs(p - up);
68
+ const pc = Math.abs(p - upLeft);
69
+ value += pa <= pb && pa <= pc ? left : pb <= pc ? up : upLeft;
70
+ } else if (filter !== 0) {
71
+ throw new Error(`Unknown PNG filter ${filter}.`);
72
+ }
73
+ current[i] = value & 255;
74
+ }
75
+ previous = current;
76
+ }
77
+ return out;
78
+ }
79
+ function collectMaskRefs(entries) {
80
+ const masks = /* @__PURE__ */ new Set();
81
+ for (const [, object] of entries) {
82
+ if (!(object instanceof PDFRawStream)) continue;
83
+ for (const key of ["SMask", "Mask"]) {
84
+ const value = object.dict.get(N(key));
85
+ if (value instanceof PDFRef) masks.add(value.toString());
86
+ }
87
+ }
88
+ return masks;
89
+ }
90
+ async function compressPdf(bytes, target, codec, onProgress) {
91
+ const originalSize = bytes.byteLength;
92
+ const doc = await loadPdf(bytes, { updateMetadata: false });
93
+ const entries = doc.context.enumerateIndirectObjects();
94
+ const masks = collectMaskRefs(entries);
95
+ const candidates = entries.filter(
96
+ ([, object]) => object instanceof PDFRawStream && object.dict.lookup(N("Subtype")) === N("Image")
97
+ );
98
+ let compressed = 0;
99
+ for (let index = 0; index < candidates.length; index += 1) {
100
+ const [ref, stream] = candidates[index];
101
+ onProgress?.(index, candidates.length);
102
+ const input = await readImage(ref, stream, masks, codec);
103
+ if (!input) continue;
104
+ let encoded;
105
+ try {
106
+ encoded = await codec.encodeJpeg(input, target);
107
+ } catch {
108
+ continue;
109
+ }
110
+ if (encoded.bytes.length >= stream.contents.length * MIN_SAVING) continue;
111
+ const dict = doc.context.obj({
112
+ Type: "XObject",
113
+ Subtype: "Image",
114
+ Width: encoded.width,
115
+ Height: encoded.height,
116
+ ColorSpace: "DeviceRGB",
117
+ BitsPerComponent: 8,
118
+ Filter: "DCTDecode"
119
+ });
120
+ for (const key of ["SMask", "Interpolate", "Intent", "Metadata", "OC"]) {
121
+ const value = stream.dict.get(N(key));
122
+ if (value) dict.set(N(key), value);
123
+ }
124
+ doc.context.assign(ref, PDFRawStream.of(dict, encoded.bytes));
125
+ compressed += 1;
126
+ }
127
+ onProgress?.(candidates.length, candidates.length);
128
+ const out = await doc.save({ useObjectStreams: true });
129
+ const stats = {
130
+ images: candidates.length,
131
+ compressed,
132
+ originalSize,
133
+ newSize: out.byteLength
134
+ };
135
+ return { bytes: out, stats };
136
+ }
137
+ async function readImage(ref, stream, masks, codec) {
138
+ const { dict } = stream;
139
+ if (masks.has(ref.toString())) return null;
140
+ if (stream.contents.length < MIN_STREAM_BYTES) return null;
141
+ if (dict.lookup(N("ImageMask"))?.toString() === "true") return null;
142
+ if (dict.get(N("Decode"))) return null;
143
+ if (dict.get(N("Mask"))) return null;
144
+ const softMask = dict.lookup(N("SMask"));
145
+ if (softMask instanceof PDFRawStream && softMask.dict.get(N("Matte"))) return null;
146
+ if (numberOf(dict, "BitsPerComponent") !== 8) return null;
147
+ const width = numberOf(dict, "Width", 0);
148
+ const height = numberOf(dict, "Height", 0);
149
+ if (!width || !height || width * height > MAX_PIXELS) return null;
150
+ const components = colorComponents(dict);
151
+ if (!components) return null;
152
+ const filters = filterNames(dict);
153
+ if (filters.length === 1 && filters[0] === "DCTDecode") {
154
+ return { kind: "jpeg", bytes: stream.contents, width, height };
155
+ }
156
+ if (filters.length !== 1 || filters[0] !== "FlateDecode") return null;
157
+ const parms = decodeParms(dict);
158
+ const predictor = parms ? numberOf(parms, "Predictor", 1) : 1;
159
+ if (predictor !== 1 && predictor < 10) return null;
160
+ if (predictor >= 10 && numberOf(parms, "Colors", 1) !== components) return null;
161
+ let pixels;
162
+ try {
163
+ pixels = await codec.inflate(stream.contents);
164
+ if (predictor >= 10) {
165
+ pixels = unpredictPng(pixels, numberOf(parms, "Columns", width), components, 8);
166
+ }
167
+ } catch {
168
+ return null;
169
+ }
170
+ if (pixels.length < width * height * components) return null;
171
+ return {
172
+ kind: "raw",
173
+ pixels: pixels.subarray(0, width * height * components),
174
+ width,
175
+ height,
176
+ components
177
+ };
178
+ }
179
+
180
+ // src/compress/browserCodec.ts
181
+ async function inflate(bytes) {
182
+ const stream = new Blob([bytes]).stream().pipeThrough(new DecompressionStream("deflate"));
183
+ return new Uint8Array(await new Response(stream).arrayBuffer());
184
+ }
185
+ async function toSource(input) {
186
+ if (input.kind === "jpeg") {
187
+ return createImageBitmap(new Blob([input.bytes], { type: "image/jpeg" }), {
188
+ imageOrientation: "none"
189
+ });
190
+ }
191
+ const { width, height, pixels, components } = input;
192
+ const rgba = new Uint8ClampedArray(width * height * 4);
193
+ for (let i = 0, j = 0; i < width * height; i += 1, j += components) {
194
+ rgba[i * 4] = pixels[j];
195
+ rgba[i * 4 + 1] = pixels[components === 3 ? j + 1 : j];
196
+ rgba[i * 4 + 2] = pixels[components === 3 ? j + 2 : j];
197
+ rgba[i * 4 + 3] = 255;
198
+ }
199
+ const canvas = document.createElement("canvas");
200
+ canvas.width = width;
201
+ canvas.height = height;
202
+ canvas.getContext("2d").putImageData(new ImageData(rgba, width, height), 0, 0);
203
+ return canvas;
204
+ }
205
+ async function encodeJpeg(input, target) {
206
+ const source = await toSource(input);
207
+ const scale = Math.min(1, target.maxDimension / Math.max(source.width, source.height));
208
+ const width = Math.max(1, Math.round(source.width * scale));
209
+ const height = Math.max(1, Math.round(source.height * scale));
210
+ const canvas = document.createElement("canvas");
211
+ canvas.width = width;
212
+ canvas.height = height;
213
+ const context = canvas.getContext("2d");
214
+ context.imageSmoothingQuality = "high";
215
+ context.drawImage(source, 0, 0, width, height);
216
+ if ("close" in source) source.close();
217
+ const blob = await new Promise(
218
+ (resolve, reject) => canvas.toBlob((result) => result ? resolve(result) : reject(new Error("encode failed")), "image/jpeg", target.quality)
219
+ );
220
+ canvas.width = 0;
221
+ canvas.height = 0;
222
+ return { bytes: new Uint8Array(await blob.arrayBuffer()), width, height };
223
+ }
224
+ var browserCodec = { inflate, encodeJpeg };
225
+
226
+ export { COMPRESSION_LEVELS, browserCodec, colorComponents, compressPdf, unpredictPng };
227
+ //# sourceMappingURL=chunk-ZBZ7OC6I.js.map
228
+ //# sourceMappingURL=chunk-ZBZ7OC6I.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/compress/compressPdfCore.ts","../src/compress/browserCodec.ts"],"names":[],"mappings":"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","file":"chunk-ZBZ7OC6I.js","sourcesContent":["import { PDFArray, PDFDict, PDFName, PDFNumber, PDFRawStream, PDFRef } from \"pdf-lib\";\nimport { loadPdf } from \"../shared/files\";\n\nexport type ImageInput =\n | { kind: \"jpeg\"; bytes: Uint8Array; width: number; height: number }\n | { kind: \"raw\"; pixels: Uint8Array; width: number; height: number; components: 1 | 3 };\n\nexport type CompressTarget = { maxDimension: number; quality: number };\n\n/** Browser (canvas) or test implementation of the pixel work. */\nexport type ImageCodec = {\n inflate(bytes: Uint8Array): Promise<Uint8Array>;\n encodeJpeg(\n input: ImageInput,\n target: CompressTarget\n ): Promise<{ bytes: Uint8Array; width: number; height: number }>;\n};\n\nexport const COMPRESSION_LEVELS: Record<string, CompressTarget & { label: string }> = {\n light: { label: \"Light\", maxDimension: 2400, quality: 0.82 },\n balanced: { label: \"Balanced\", maxDimension: 1600, quality: 0.7 },\n strong: { label: \"Strong\", maxDimension: 1100, quality: 0.55 },\n};\n\nconst MIN_STREAM_BYTES = 8 * 1024;\nconst MAX_PIXELS = 40_000_000;\n/** A re-encoded image must be at least this much smaller to replace the original. */\nconst MIN_SAVING = 0.9;\n\nconst N = (name: string) => PDFName.of(name);\n\nfunction numberOf(dict: PDFDict, key: string, fallback?: number) {\n const value = dict.lookup(N(key));\n return value instanceof PDFNumber ? value.asNumber() : fallback;\n}\n\nfunction filterNames(dict: PDFDict) {\n const filter = dict.lookup(N(\"Filter\"));\n if (filter instanceof PDFName) return [filter.decodeText()];\n if (filter instanceof PDFArray) {\n return filter.asArray().map((item) => (item instanceof PDFName ? item.decodeText() : \"?\"));\n }\n return [];\n}\n\nfunction decodeParms(dict: PDFDict) {\n const parms = dict.lookup(N(\"DecodeParms\"));\n if (parms instanceof PDFDict) return parms;\n if (parms instanceof PDFArray && parms.size() === 1) {\n const first = parms.lookup(0);\n return first instanceof PDFDict ? first : null;\n }\n return null;\n}\n\n/** Number of colour components for colour spaces we can safely re-encode. */\nexport function colorComponents(dict: PDFDict): 1 | 3 | null {\n const space = dict.lookup(N(\"ColorSpace\"));\n if (space === N(\"DeviceRGB\")) return 3;\n if (space === N(\"DeviceGray\")) return 1;\n if (space instanceof PDFArray && space.size() === 2 && space.lookup(0) === N(\"ICCBased\")) {\n const profile = space.lookup(1);\n const count = profile instanceof PDFRawStream ? numberOf(profile.dict, \"N\") : undefined;\n if (count === 3) return 3;\n if (count === 1) return 1;\n }\n return null;\n}\n\n/** Reverse PNG row predictors (PDF /Predictor 10-15). */\nexport function unpredictPng(data: Uint8Array, columns: number, colors: number, bitsPerComponent = 8) {\n const bytesPerPixel = Math.max(1, Math.ceil((colors * bitsPerComponent) / 8));\n const rowLength = Math.ceil((columns * colors * bitsPerComponent) / 8);\n const rows = Math.floor(data.length / (rowLength + 1));\n const out = new Uint8Array(rows * rowLength);\n let previous = new Uint8Array(rowLength);\n\n for (let row = 0; row < rows; row += 1) {\n const filter = data[row * (rowLength + 1)];\n const input = data.subarray(row * (rowLength + 1) + 1, (row + 1) * (rowLength + 1));\n const current = out.subarray(row * rowLength, (row + 1) * rowLength);\n\n for (let i = 0; i < rowLength; i += 1) {\n const left = i >= bytesPerPixel ? current[i - bytesPerPixel] : 0;\n const up = previous[i];\n const upLeft = i >= bytesPerPixel ? previous[i - bytesPerPixel] : 0;\n let value = input[i];\n if (filter === 1) value += left;\n else if (filter === 2) value += up;\n else if (filter === 3) value += Math.floor((left + up) / 2);\n else if (filter === 4) {\n const p = left + up - upLeft;\n const pa = Math.abs(p - left);\n const pb = Math.abs(p - up);\n const pc = Math.abs(p - upLeft);\n value += pa <= pb && pa <= pc ? left : pb <= pc ? up : upLeft;\n } else if (filter !== 0) {\n throw new Error(`Unknown PNG filter ${filter}.`);\n }\n current[i] = value & 0xff;\n }\n previous = current;\n }\n return out;\n}\n\n/** Refs used as another image's soft mask or stencil mask; never re-encode those. */\nfunction collectMaskRefs(entries: Array<[PDFRef, unknown]>) {\n const masks = new Set<string>();\n for (const [, object] of entries) {\n if (!(object instanceof PDFRawStream)) continue;\n for (const key of [\"SMask\", \"Mask\"]) {\n const value = object.dict.get(N(key));\n if (value instanceof PDFRef) masks.add(value.toString());\n }\n }\n return masks;\n}\n\nexport type CompressStats = {\n images: number;\n compressed: number;\n originalSize: number;\n newSize: number;\n};\n\n/**\n * Re-encode large embedded images as smaller JPEGs. Images that are masks,\n * use unusual colour spaces, or would not shrink are left untouched.\n */\nexport async function compressPdf(\n bytes: ArrayBuffer | Uint8Array,\n target: CompressTarget,\n codec: ImageCodec,\n onProgress?: (done: number, total: number) => void\n) {\n const originalSize = bytes.byteLength;\n const doc = await loadPdf(bytes, { updateMetadata: false });\n const entries = doc.context.enumerateIndirectObjects();\n const masks = collectMaskRefs(entries);\n\n const candidates = entries.filter(\n ([, object]) =>\n object instanceof PDFRawStream && object.dict.lookup(N(\"Subtype\")) === N(\"Image\")\n ) as Array<[PDFRef, PDFRawStream]>;\n\n let compressed = 0;\n for (let index = 0; index < candidates.length; index += 1) {\n const [ref, stream] = candidates[index];\n onProgress?.(index, candidates.length);\n\n const input = await readImage(ref, stream, masks, codec);\n if (!input) continue;\n\n let encoded;\n try {\n encoded = await codec.encodeJpeg(input, target);\n } catch {\n continue;\n }\n if (encoded.bytes.length >= stream.contents.length * MIN_SAVING) continue;\n\n const dict = doc.context.obj({\n Type: \"XObject\",\n Subtype: \"Image\",\n Width: encoded.width,\n Height: encoded.height,\n ColorSpace: \"DeviceRGB\",\n BitsPerComponent: 8,\n Filter: \"DCTDecode\",\n }) as PDFDict;\n for (const key of [\"SMask\", \"Interpolate\", \"Intent\", \"Metadata\", \"OC\"]) {\n const value = stream.dict.get(N(key));\n if (value) dict.set(N(key), value);\n }\n doc.context.assign(ref, PDFRawStream.of(dict, encoded.bytes));\n compressed += 1;\n }\n onProgress?.(candidates.length, candidates.length);\n\n const out = await doc.save({ useObjectStreams: true });\n const stats: CompressStats = {\n images: candidates.length,\n compressed,\n originalSize,\n newSize: out.byteLength,\n };\n return { bytes: out, stats };\n}\n\nasync function readImage(\n ref: PDFRef,\n stream: PDFRawStream,\n masks: Set<string>,\n codec: ImageCodec\n): Promise<ImageInput | null> {\n const { dict } = stream;\n if (masks.has(ref.toString())) return null;\n if (stream.contents.length < MIN_STREAM_BYTES) return null;\n if (dict.lookup(N(\"ImageMask\"))?.toString() === \"true\") return null;\n if (dict.get(N(\"Decode\"))) return null;\n // Colour-key (array) and stencil (stream) masks: a colour key can't survive a\n // lossy JPEG, and the new dict doesn't carry /Mask, so transparency would be lost.\n if (dict.get(N(\"Mask\"))) return null;\n // A soft mask may differ in size from its image, except when it has /Matte.\n const softMask = dict.lookup(N(\"SMask\"));\n if (softMask instanceof PDFRawStream && softMask.dict.get(N(\"Matte\"))) return null;\n if (numberOf(dict, \"BitsPerComponent\") !== 8) return null;\n\n const width = numberOf(dict, \"Width\", 0);\n const height = numberOf(dict, \"Height\", 0);\n if (!width || !height || width * height > MAX_PIXELS) return null;\n\n const components = colorComponents(dict);\n if (!components) return null;\n\n const filters = filterNames(dict);\n if (filters.length === 1 && filters[0] === \"DCTDecode\") {\n return { kind: \"jpeg\", bytes: stream.contents, width, height };\n }\n if (filters.length !== 1 || filters[0] !== \"FlateDecode\") return null;\n\n const parms = decodeParms(dict);\n const predictor = parms ? numberOf(parms, \"Predictor\", 1) : 1;\n if (predictor !== 1 && predictor < 10) return null;\n if (predictor >= 10 && numberOf(parms, \"Colors\", 1) !== components) return null;\n\n let pixels: Uint8Array;\n try {\n pixels = await codec.inflate(stream.contents);\n if (predictor >= 10) {\n pixels = unpredictPng(pixels, numberOf(parms, \"Columns\", width), components, 8);\n }\n } catch {\n return null;\n }\n if (pixels.length < width * height * components) return null;\n\n return {\n kind: \"raw\",\n pixels: pixels.subarray(0, width * height * components),\n width,\n height,\n components,\n };\n}\n","import type { CompressTarget, ImageCodec, ImageInput } from \"./compressPdfCore\";\n\nasync function inflate(bytes: Uint8Array) {\n // PDF FlateDecode is zlib-wrapped deflate, which DecompressionStream calls \"deflate\".\n const stream = new Blob([bytes as BlobPart]).stream().pipeThrough(new DecompressionStream(\"deflate\"));\n return new Uint8Array(await new Response(stream).arrayBuffer());\n}\n\nasync function toSource(input: ImageInput) {\n if (input.kind === \"jpeg\") {\n // PDF viewers ignore EXIF orientation, so must we.\n return createImageBitmap(new Blob([input.bytes as BlobPart], { type: \"image/jpeg\" }), {\n imageOrientation: \"none\",\n });\n }\n\n const { width, height, pixels, components } = input;\n const rgba = new Uint8ClampedArray(width * height * 4);\n for (let i = 0, j = 0; i < width * height; i += 1, j += components) {\n rgba[i * 4] = pixels[j];\n rgba[i * 4 + 1] = pixels[components === 3 ? j + 1 : j];\n rgba[i * 4 + 2] = pixels[components === 3 ? j + 2 : j];\n rgba[i * 4 + 3] = 255;\n }\n const canvas = document.createElement(\"canvas\");\n canvas.width = width;\n canvas.height = height;\n canvas.getContext(\"2d\").putImageData(new ImageData(rgba, width, height), 0, 0);\n return canvas;\n}\n\nasync function encodeJpeg(input: ImageInput, target: CompressTarget) {\n const source = await toSource(input);\n const scale = Math.min(1, target.maxDimension / Math.max(source.width, source.height));\n const width = Math.max(1, Math.round(source.width * scale));\n const height = Math.max(1, Math.round(source.height * scale));\n\n const canvas = document.createElement(\"canvas\");\n canvas.width = width;\n canvas.height = height;\n const context = canvas.getContext(\"2d\");\n context.imageSmoothingQuality = \"high\";\n context.drawImage(source, 0, 0, width, height);\n if (\"close\" in source) source.close();\n\n const blob = await new Promise<Blob>((resolve, reject) =>\n canvas.toBlob((result) => (result ? resolve(result) : reject(new Error(\"encode failed\"))), \"image/jpeg\", target.quality)\n );\n canvas.width = 0;\n canvas.height = 0;\n return { bytes: new Uint8Array(await blob.arrayBuffer()), width, height };\n}\n\nexport const browserCodec: ImageCodec = { inflate, encodeJpeg };\n"]}
@@ -0,0 +1,19 @@
1
+ type PdfConfig = {
2
+ /** Written to the Producer field of PDFs that tools create from scratch. Default "@kitsy/pdf". */
3
+ producer?: string;
4
+ /** URL of pdfjs-dist/build/pdf.worker.min.mjs. Needed for page previews and PDF to Images. */
5
+ pdfjsWorkerSrc?: string;
6
+ /**
7
+ * Folder that holds pdf.js `cmaps/` and `standard_fonts/` (copied from
8
+ * pdfjs-dist). Optional: without it, PDFs with CJK text or unembedded
9
+ * standard fonts may render with substitute glyphs.
10
+ */
11
+ pdfjsAssetsBase?: string;
12
+ /** URL of @neslinesli93/qpdf-wasm/dist/qpdf.wasm. Needed for Protect / Unlock. */
13
+ qpdfWasmUrl?: string;
14
+ };
15
+ /** Set asset URLs and options. Later calls merge into earlier ones. */
16
+ declare function configurePdf(next: PdfConfig): void;
17
+ declare function getPdfConfig(): Readonly<PdfConfig>;
18
+
19
+ export { type PdfConfig as P, configurePdf as c, getPdfConfig as g };