modern-pdf-lib 0.22.9 → 0.26.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/README.md +22 -4
- package/dist/{batchOptimize-CxyY4fZe.mjs → batchOptimize-BCJEEN8J.mjs} +4 -4
- package/dist/{batchOptimize-Ba_pWw71.cjs → batchOptimize-C1W3O68Q.cjs} +3 -3
- package/dist/{bridge-DYCQzxF7.cjs → bridge-DMEuGtfn.cjs} +2 -2
- package/dist/{bridge-DTH5LMAK.mjs → bridge-DaS-gzEd.mjs} +3 -3
- package/dist/browser.cjs +78 -13
- package/dist/browser.d.cts +5 -5
- package/dist/browser.d.mts +5 -5
- package/dist/browser.mjs +12 -12
- package/dist/cli/index.cjs +2 -2
- package/dist/cli/index.mjs +3 -3
- package/dist/{compressionAnalysis-B84FPXaQ.cjs → compressionAnalysis-B-FPzgzw.cjs} +7 -7
- package/dist/{compressionAnalysis-BBv4BkQP.d.mts → compressionAnalysis-Ch7t-HXn.d.mts} +26 -3
- package/dist/compressionAnalysis-Ch7t-HXn.d.mts.map +1 -0
- package/dist/{compressionAnalysis-ChkscEa1.mjs → compressionAnalysis-CwknBtqx.mjs} +7 -7
- package/dist/{compressionAnalysis-CtJ2X9l2.d.cts → compressionAnalysis-Dgv1TtHJ.d.cts} +26 -3
- package/dist/compressionAnalysis-Dgv1TtHJ.d.cts.map +1 -0
- package/dist/create.cjs +23 -4
- package/dist/create.d.cts +3 -3
- package/dist/create.d.mts +3 -3
- package/dist/create.mjs +4 -4
- package/dist/{deduplicateImages-CmTeo6Tx.mjs → deduplicateImages-DIon68zB.mjs} +2 -2
- package/dist/{fflateAdapter-CBQpGTlx.mjs → fflateAdapter-DuNiByKx.mjs} +2 -2
- package/dist/{fontEmbed-LID6yG6g.d.cts → fontEmbed-3YhUPLFj.d.cts} +26 -3
- package/dist/fontEmbed-3YhUPLFj.d.cts.map +1 -0
- package/dist/{fontEmbed-Dsu9fo4U.d.mts → fontEmbed-DlVnVCZU.d.mts} +26 -3
- package/dist/fontEmbed-DlVnVCZU.d.mts.map +1 -0
- package/dist/{fontSubset-DWpduoY2.mjs → fontSubset-BGFDIMmT.mjs} +2 -2
- package/dist/forms.cjs +9 -2
- package/dist/forms.d.cts +3 -3
- package/dist/forms.d.mts +3 -3
- package/dist/forms.mjs +2 -2
- package/dist/{index-BtYOx5wh.d.mts → index-B4S61WjK.d.mts} +1929 -326
- package/dist/index-B4S61WjK.d.mts.map +1 -0
- package/dist/{index-bpktKzCA.d.cts → index-xfJP6Ycm.d.cts} +1929 -326
- package/dist/index-xfJP6Ycm.d.cts.map +1 -0
- package/dist/index.cjs +78 -13
- package/dist/index.d.cts +5 -5
- package/dist/index.d.mts +5 -5
- package/dist/index.mjs +12 -12
- package/dist/{layout-DgX_0jfK.mjs → layout-CrqeJBMI.mjs} +40 -4
- package/dist/{layout-CuAVk_Or.cjs → layout-DQh05VP-.cjs} +57 -3
- package/dist/{libdeflateWasm-rLppXytE.mjs → libdeflateWasm-CcA1W04G.mjs} +3 -3
- package/dist/{libdeflateWasm-BdiDEJOj.cjs → libdeflateWasm-DLw-I1CY.cjs} +2 -2
- package/dist/{loader-3u6Tw5T-.mjs → loader-CVB-c_3Z.mjs} +16 -6
- package/dist/{loader-I4zdkoWc.cjs → loader-DYCH3n7d.cjs} +15 -5
- package/dist/parse.cjs +4 -2
- package/dist/parse.d.cts +3 -3
- package/dist/parse.d.mts +3 -3
- package/dist/parse.mjs +3 -3
- package/dist/{pdfCatalog-CYy4NXEY.cjs → pdfCatalog-Bqq4FiLn.cjs} +2 -1
- package/dist/{pdfCatalog-IImGcMbR.mjs → pdfCatalog-CnKMAtIo.mjs} +3 -2
- package/dist/{pdfDocument-BSiQdNZq.d.cts → pdfDocument-Bc_vAO74.d.cts} +293 -175
- package/dist/pdfDocument-Bc_vAO74.d.cts.map +1 -0
- package/dist/{pdfDocument-i6U5fQ91.d.mts → pdfDocument-Bi-NoyXv.d.mts} +293 -175
- package/dist/pdfDocument-Bi-NoyXv.d.mts.map +1 -0
- package/dist/{pdfDocument-BFxHD_2u.mjs → pdfDocument-D7aFSQEY.mjs} +1920 -53
- package/dist/{pdfDocument-pmRXryVI.cjs → pdfDocument-D9uYNSb-.cjs} +2035 -48
- package/dist/{pdfForm-9gd40uz9.cjs → pdfForm-BpqDGp29.cjs} +7 -1
- package/dist/{pdfForm-Cn-cVicP.mjs → pdfForm-C3mC9FoQ.mjs} +2 -2
- package/dist/{pdfPage-Cd8e7flb.mjs → pdfPage-BJIE5hc6.mjs} +189 -24
- package/dist/{pdfPage-Cd8jOJp6.cjs → pdfPage-C_tjEjj1.cjs} +188 -23
- package/dist/{pngEmbed-BLj2zi-5.mjs → pngEmbed-CGi7cym7.mjs} +3 -3
- package/dist/{pngEmbed-D4X4ZN-3.cjs → pngEmbed-CTn9IeNB.cjs} +2 -2
- package/dist/{src-Dm4aaZ8q.mjs → src-QZWP21qF.mjs} +7879 -2739
- package/dist/{src-6L07EQsi.cjs → src-lFqJHb1C.cjs} +8263 -2841
- package/package.json +5 -2
- package/dist/compressionAnalysis-BBv4BkQP.d.mts.map +0 -1
- package/dist/compressionAnalysis-CtJ2X9l2.d.cts.map +0 -1
- package/dist/fontEmbed-Dsu9fo4U.d.mts.map +0 -1
- package/dist/fontEmbed-LID6yG6g.d.cts.map +0 -1
- package/dist/index-BtYOx5wh.d.mts.map +0 -1
- package/dist/index-bpktKzCA.d.cts.map +0 -1
- package/dist/pdfDocument-BSiQdNZq.d.cts.map +0 -1
- package/dist/pdfDocument-i6U5fQ91.d.mts.map +0 -1
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
const require_pdfPage = require('./pdfPage-
|
|
1
|
+
const require_pdfPage = require('./pdfPage-C_tjEjj1.cjs');
|
|
2
2
|
const require_pdfObjects = require('./pdfObjects-1veop1_d.cjs');
|
|
3
|
-
const require_pdfCatalog = require('./pdfCatalog-
|
|
4
|
-
const require_libdeflateWasm = require('./libdeflateWasm-
|
|
3
|
+
const require_pdfCatalog = require('./pdfCatalog-Bqq4FiLn.cjs');
|
|
4
|
+
const require_libdeflateWasm = require('./libdeflateWasm-DLw-I1CY.cjs');
|
|
5
5
|
const require_fontSubset = require('./fontSubset-5SLWMmEw.cjs');
|
|
6
|
-
const require_pngEmbed = require('./pngEmbed-
|
|
6
|
+
const require_pngEmbed = require('./pngEmbed-CTn9IeNB.cjs');
|
|
7
7
|
const require_fflateAdapter = require('./fflateAdapter-LTAeAhaD.cjs');
|
|
8
|
-
const require_pdfForm = require('./pdfForm-
|
|
8
|
+
const require_pdfForm = require('./pdfForm-BpqDGp29.cjs');
|
|
9
9
|
const require_streamDecode = require('./streamDecode-CvgErkFu.cjs');
|
|
10
10
|
let fflate = require("fflate");
|
|
11
11
|
|
|
@@ -15,16 +15,17 @@ let fflate = require("fflate");
|
|
|
15
15
|
*
|
|
16
16
|
* Binary serialization of a PDF document to a single `Uint8Array`.
|
|
17
17
|
*
|
|
18
|
-
* Produces a valid PDF 1.7 file:
|
|
18
|
+
* Produces a valid PDF 1.7 (or 2.0 when encryption V=5) file:
|
|
19
19
|
*
|
|
20
|
-
* 1. `%PDF-1.7` header + binary comment
|
|
21
|
-
* 2. Indirect-object bodies
|
|
20
|
+
* 1. `%PDF-1.7` (or `%PDF-2.0`) header + binary comment
|
|
21
|
+
* 2. Indirect-object bodies (optionally encrypted)
|
|
22
22
|
* 3. Cross-reference table
|
|
23
|
-
* 4. Trailer dictionary (`/Size`, `/Root`, `/Info`)
|
|
23
|
+
* 4. Trailer dictionary (`/Size`, `/Root`, `/Info`, `/Encrypt`, `/ID`)
|
|
24
24
|
* 5. `startxref` pointer
|
|
25
25
|
* 6. `%%EOF`
|
|
26
26
|
*
|
|
27
|
-
* Supports optional FlateDecode compression for streams via `fflate
|
|
27
|
+
* Supports optional FlateDecode compression for streams via `fflate`
|
|
28
|
+
* and optional encryption via a {@link PdfEncryptionHandler}.
|
|
28
29
|
*/
|
|
29
30
|
/**
|
|
30
31
|
* Growable byte buffer used during serialization. All writes go
|
|
@@ -74,32 +75,48 @@ var PdfWriter = class {
|
|
|
74
75
|
compressionLevel;
|
|
75
76
|
useWasm;
|
|
76
77
|
objectStreamThreshold;
|
|
77
|
-
|
|
78
|
+
encryptionHandler;
|
|
79
|
+
/**
|
|
80
|
+
* When encryption is active, the /Encrypt dict is registered as an
|
|
81
|
+
* indirect object. Its reference is stored here for the trailer.
|
|
82
|
+
*/
|
|
83
|
+
encryptDictRef;
|
|
84
|
+
constructor(registry, structure, options, encryptionHandler) {
|
|
78
85
|
this.registry = registry;
|
|
79
86
|
this.structure = structure;
|
|
80
87
|
this.compress = options?.compress ?? true;
|
|
81
88
|
this.compressionLevel = options?.compressionLevel ?? 6;
|
|
82
89
|
this.useWasm = options?.useWasm ?? false;
|
|
83
90
|
this.objectStreamThreshold = options?.objectStreamThreshold ?? Infinity;
|
|
91
|
+
this.encryptionHandler = encryptionHandler;
|
|
84
92
|
}
|
|
85
93
|
/**
|
|
86
94
|
* Produce the complete PDF file as a `Uint8Array`.
|
|
95
|
+
*
|
|
96
|
+
* When an encryption handler is present, all string and stream
|
|
97
|
+
* objects are encrypted and the /Encrypt dictionary + /ID array
|
|
98
|
+
* are added to the trailer.
|
|
87
99
|
*/
|
|
88
|
-
write() {
|
|
100
|
+
async write() {
|
|
101
|
+
if (this.encryptionHandler) {
|
|
102
|
+
const encDict = this.encryptionHandler.buildEncryptDict();
|
|
103
|
+
this.encryptDictRef = this.registry.register(encDict);
|
|
104
|
+
}
|
|
89
105
|
this.writeHeader();
|
|
90
106
|
if (this.objectStreamThreshold !== Infinity) {
|
|
91
|
-
if (this.writeBodyWithObjectStreams(this.objectStreamThreshold)) return this.buf.toUint8Array();
|
|
107
|
+
if (await this.writeBodyWithObjectStreams(this.objectStreamThreshold)) return this.buf.toUint8Array();
|
|
92
108
|
const xrefOffset = this.writeXref();
|
|
93
109
|
this.writeTrailer(xrefOffset);
|
|
94
110
|
} else {
|
|
95
|
-
this.writeBody();
|
|
111
|
+
await this.writeBody();
|
|
96
112
|
const xrefOffset = this.writeXref();
|
|
97
113
|
this.writeTrailer(xrefOffset);
|
|
98
114
|
}
|
|
99
115
|
return this.buf.toUint8Array();
|
|
100
116
|
}
|
|
101
117
|
writeHeader() {
|
|
102
|
-
this.
|
|
118
|
+
const version = this.encryptionHandler && this.encryptionHandler.getVersion() === 5 ? "2.0" : "1.7";
|
|
119
|
+
this.buf.writeString(`%PDF-${version}\n`);
|
|
103
120
|
this.buf.write(new Uint8Array([
|
|
104
121
|
37,
|
|
105
122
|
226,
|
|
@@ -109,11 +126,13 @@ var PdfWriter = class {
|
|
|
109
126
|
10
|
|
110
127
|
]));
|
|
111
128
|
}
|
|
112
|
-
writeBody() {
|
|
113
|
-
for (const entry of this.registry) this.writeIndirectObject(entry);
|
|
129
|
+
async writeBody() {
|
|
130
|
+
for (const entry of this.registry) await this.writeIndirectObject(entry);
|
|
114
131
|
}
|
|
115
|
-
writeIndirectObject(entry) {
|
|
132
|
+
async writeIndirectObject(entry) {
|
|
116
133
|
if (this.compress && entry.object.kind === "stream") this.compressStream(entry.object);
|
|
134
|
+
const isEncryptDict = this.encryptDictRef && entry.ref.objectNumber === this.encryptDictRef.objectNumber;
|
|
135
|
+
if (this.encryptionHandler && !isEncryptDict) await this.encryptEntry(entry);
|
|
117
136
|
const objNum = entry.ref.objectNumber;
|
|
118
137
|
while (this.xrefOffsets.length <= objNum) this.xrefOffsets.push(0);
|
|
119
138
|
this.xrefOffsets[objNum] = this.buf.offset;
|
|
@@ -122,6 +141,50 @@ var PdfWriter = class {
|
|
|
122
141
|
this.buf.writeString(`\n${entry.ref.toObjectFooter()}\n`);
|
|
123
142
|
}
|
|
124
143
|
/**
|
|
144
|
+
* Encrypt all encryptable data within an indirect object.
|
|
145
|
+
*
|
|
146
|
+
* Per the PDF spec:
|
|
147
|
+
* - Strings inside the object are encrypted with the per-object key.
|
|
148
|
+
* - Stream data is encrypted with the per-object key.
|
|
149
|
+
* - The /Encrypt dictionary itself is never encrypted.
|
|
150
|
+
* - String values in the trailer /ID array are never encrypted.
|
|
151
|
+
*/
|
|
152
|
+
async encryptEntry(entry) {
|
|
153
|
+
const handler = this.encryptionHandler;
|
|
154
|
+
const objNum = entry.ref.objectNumber;
|
|
155
|
+
const genNum = entry.ref.generationNumber;
|
|
156
|
+
if (entry.object.kind === "stream") {
|
|
157
|
+
const stream = entry.object;
|
|
158
|
+
await this.encryptDictStrings(stream.dict, objNum, genNum);
|
|
159
|
+
stream.data = await handler.encryptObject(objNum, genNum, stream.data);
|
|
160
|
+
stream.syncLength();
|
|
161
|
+
} else if (entry.object.kind === "dict") await this.encryptDictStrings(entry.object, objNum, genNum);
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Recursively encrypt all PdfString values inside a PdfDict.
|
|
165
|
+
*/
|
|
166
|
+
async encryptDictStrings(dict, objNum, genNum) {
|
|
167
|
+
const handler = this.encryptionHandler;
|
|
168
|
+
for (const [key, value] of dict) if (value.kind === "string") {
|
|
169
|
+
const encrypted = await handler.encryptString(objNum, genNum, value);
|
|
170
|
+
dict.set(key, encrypted);
|
|
171
|
+
} else if (value.kind === "dict") await this.encryptDictStrings(value, objNum, genNum);
|
|
172
|
+
else if (value.kind === "array") await this.encryptArrayStrings(value, objNum, genNum);
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Recursively encrypt all PdfString values inside a PdfArray.
|
|
176
|
+
*/
|
|
177
|
+
async encryptArrayStrings(arr, objNum, genNum) {
|
|
178
|
+
const handler = this.encryptionHandler;
|
|
179
|
+
const items = arr.items;
|
|
180
|
+
for (let i = 0; i < items.length; i++) {
|
|
181
|
+
const item = items[i];
|
|
182
|
+
if (item.kind === "string") items[i] = await handler.encryptString(objNum, genNum, item);
|
|
183
|
+
else if (item.kind === "dict") await this.encryptDictStrings(item, objNum, genNum);
|
|
184
|
+
else if (item.kind === "array") await this.encryptArrayStrings(item, objNum, genNum);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
125
188
|
* Apply FlateDecode compression to a stream's data if it is not
|
|
126
189
|
* already compressed.
|
|
127
190
|
*/
|
|
@@ -159,7 +222,7 @@ var PdfWriter = class {
|
|
|
159
222
|
* written in traditional format and the caller must still
|
|
160
223
|
* emit the classic xref table and trailer.
|
|
161
224
|
*/
|
|
162
|
-
writeBodyWithObjectStreams(threshold) {
|
|
225
|
+
async writeBodyWithObjectStreams(threshold) {
|
|
163
226
|
const protectedNums = this.protectedObjectNumbers();
|
|
164
227
|
const streamEntries = [];
|
|
165
228
|
const protectedEntries = [];
|
|
@@ -168,11 +231,11 @@ var PdfWriter = class {
|
|
|
168
231
|
else if (protectedNums.has(entry.ref.objectNumber)) protectedEntries.push(entry);
|
|
169
232
|
else packableEntries.push(entry);
|
|
170
233
|
if (packableEntries.length < threshold) {
|
|
171
|
-
this.writeBody();
|
|
234
|
+
await this.writeBody();
|
|
172
235
|
return false;
|
|
173
236
|
}
|
|
174
|
-
for (const entry of streamEntries) this.writeIndirectObject(entry);
|
|
175
|
-
for (const entry of protectedEntries) this.writeIndirectObject(entry);
|
|
237
|
+
for (const entry of streamEntries) await this.writeIndirectObject(entry);
|
|
238
|
+
for (const entry of protectedEntries) await this.writeIndirectObject(entry);
|
|
176
239
|
const OBJS_PER_STREAM = 200;
|
|
177
240
|
const xrefEntries = /* @__PURE__ */ new Map();
|
|
178
241
|
for (let i = 0; i < this.xrefOffsets.length; i++) {
|
|
@@ -361,6 +424,11 @@ var PdfWriter = class {
|
|
|
361
424
|
this.buf.writeString(`/Size ${size}\n`);
|
|
362
425
|
this.buf.writeString(`/Root ${this.structure.catalogRef.objectNumber} ${this.structure.catalogRef.generationNumber} R\n`);
|
|
363
426
|
this.buf.writeString(`/Info ${this.structure.infoRef.objectNumber} ${this.structure.infoRef.generationNumber} R\n`);
|
|
427
|
+
if (this.encryptionHandler && this.encryptDictRef) {
|
|
428
|
+
this.buf.writeString(`/Encrypt ${this.encryptDictRef.objectNumber} ${this.encryptDictRef.generationNumber} R\n`);
|
|
429
|
+
const fileIdHex = this.encryptionHandler.getFileId().toHex();
|
|
430
|
+
this.buf.writeString(`/ID [<${fileIdHex}> <${fileIdHex}>]\n`);
|
|
431
|
+
}
|
|
364
432
|
this.buf.writeString(">>\n");
|
|
365
433
|
this.buf.writeString("startxref\n");
|
|
366
434
|
this.buf.writeString(`${xrefOffset}\n`);
|
|
@@ -415,13 +483,16 @@ function writeIntBE(buf, offset, width, value) {
|
|
|
415
483
|
/**
|
|
416
484
|
* Serialize a complete PDF from a registry and structure refs.
|
|
417
485
|
*
|
|
418
|
-
* @param registry
|
|
419
|
-
* @param structure
|
|
420
|
-
* @param options
|
|
421
|
-
* @
|
|
486
|
+
* @param registry All registered indirect objects.
|
|
487
|
+
* @param structure Catalog / Info / Pages references.
|
|
488
|
+
* @param options Save options.
|
|
489
|
+
* @param encryptionHandler Optional encryption handler for encrypting
|
|
490
|
+
* all objects and adding /Encrypt + /ID to
|
|
491
|
+
* the trailer.
|
|
492
|
+
* @returns The raw PDF bytes.
|
|
422
493
|
*/
|
|
423
|
-
function serializePdf(registry, structure, options) {
|
|
424
|
-
return new PdfWriter(registry, structure, options).write();
|
|
494
|
+
async function serializePdf(registry, structure, options, encryptionHandler) {
|
|
495
|
+
return new PdfWriter(registry, structure, options, encryptionHandler).write();
|
|
425
496
|
}
|
|
426
497
|
|
|
427
498
|
//#endregion
|
|
@@ -3582,6 +3653,1459 @@ function findTable(data, tag) {
|
|
|
3582
3653
|
}
|
|
3583
3654
|
}
|
|
3584
3655
|
|
|
3656
|
+
//#endregion
|
|
3657
|
+
//#region src/assets/image/formatDetect.ts
|
|
3658
|
+
/**
|
|
3659
|
+
* Detect the image format from the raw file bytes by inspecting magic bytes.
|
|
3660
|
+
*
|
|
3661
|
+
* @param data Raw image file bytes.
|
|
3662
|
+
* @returns The detected format, or `'unknown'` if unrecognized.
|
|
3663
|
+
*/
|
|
3664
|
+
function detectImageFormat(data) {
|
|
3665
|
+
if (data.length < 4) return "unknown";
|
|
3666
|
+
if (data[0] === 137 && data[1] === 80 && data[2] === 78 && data[3] === 71) return "png";
|
|
3667
|
+
if (data[0] === 255 && data[1] === 216 && data[2] === 255) return "jpeg";
|
|
3668
|
+
if (data.length >= 12 && data[0] === 82 && data[1] === 73 && data[2] === 70 && data[3] === 70 && data[8] === 87 && data[9] === 69 && data[10] === 66 && data[11] === 80) return "webp";
|
|
3669
|
+
if (data[0] === 73 && data[1] === 73 && data[2] === 42 && data[3] === 0) return "tiff";
|
|
3670
|
+
if (data[0] === 77 && data[1] === 77 && data[2] === 0 && data[3] === 42) return "tiff";
|
|
3671
|
+
return "unknown";
|
|
3672
|
+
}
|
|
3673
|
+
/**
|
|
3674
|
+
* Get a human-readable name for an image format identifier.
|
|
3675
|
+
*
|
|
3676
|
+
* @param format The format identifier string.
|
|
3677
|
+
* @returns A human-readable format name.
|
|
3678
|
+
*/
|
|
3679
|
+
function getImageFormatName(format) {
|
|
3680
|
+
switch (format) {
|
|
3681
|
+
case "png": return "PNG (Portable Network Graphics)";
|
|
3682
|
+
case "jpeg": return "JPEG (Joint Photographic Experts Group)";
|
|
3683
|
+
case "webp": return "WebP";
|
|
3684
|
+
case "tiff": return "TIFF (Tagged Image File Format)";
|
|
3685
|
+
case "unknown": return "Unknown";
|
|
3686
|
+
default: return `Unknown (${format})`;
|
|
3687
|
+
}
|
|
3688
|
+
}
|
|
3689
|
+
/**
|
|
3690
|
+
* Get the list of all supported image formats for embedding.
|
|
3691
|
+
*
|
|
3692
|
+
* @returns An array of format identifier strings.
|
|
3693
|
+
*/
|
|
3694
|
+
function getSupportedFormats() {
|
|
3695
|
+
return [
|
|
3696
|
+
"png",
|
|
3697
|
+
"jpeg",
|
|
3698
|
+
"webp",
|
|
3699
|
+
"tiff"
|
|
3700
|
+
];
|
|
3701
|
+
}
|
|
3702
|
+
|
|
3703
|
+
//#endregion
|
|
3704
|
+
//#region src/assets/image/webpDecode.ts
|
|
3705
|
+
/** Check if data is a WebP file by examining RIFF + WEBP magic bytes. */
|
|
3706
|
+
function isWebP(data) {
|
|
3707
|
+
if (data.length < 12) return false;
|
|
3708
|
+
return data[0] === 82 && data[1] === 73 && data[2] === 70 && data[3] === 70 && data[8] === 87 && data[9] === 69 && data[10] === 66 && data[11] === 80;
|
|
3709
|
+
}
|
|
3710
|
+
/** Check if a WebP file contains a VP8L (lossless) bitstream. */
|
|
3711
|
+
function isWebPLossless(data) {
|
|
3712
|
+
if (!isWebP(data)) return false;
|
|
3713
|
+
return parseRiffChunks(data).some((c) => c.fourcc === "VP8L");
|
|
3714
|
+
}
|
|
3715
|
+
function parseRiffChunks(data) {
|
|
3716
|
+
if (!isWebP(data)) throw new Error("WebP: invalid RIFF/WEBP header");
|
|
3717
|
+
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
3718
|
+
const chunks = [];
|
|
3719
|
+
let offset = 12;
|
|
3720
|
+
while (offset + 8 <= data.length) {
|
|
3721
|
+
const fourcc = String.fromCharCode(data[offset], data[offset + 1], data[offset + 2], data[offset + 3]);
|
|
3722
|
+
const chunkSize = view.getUint32(offset + 4, true);
|
|
3723
|
+
const chunkData = data.slice(offset + 8, offset + 8 + chunkSize);
|
|
3724
|
+
chunks.push({
|
|
3725
|
+
fourcc,
|
|
3726
|
+
data: chunkData,
|
|
3727
|
+
offset: offset + 8
|
|
3728
|
+
});
|
|
3729
|
+
offset += 8 + chunkSize + (chunkSize & 1);
|
|
3730
|
+
}
|
|
3731
|
+
return chunks;
|
|
3732
|
+
}
|
|
3733
|
+
/**
|
|
3734
|
+
* Decode a WebP image to raw pixel data.
|
|
3735
|
+
*
|
|
3736
|
+
* Supports VP8 (lossy), VP8L (lossless), and VP8+ALPH (lossy with alpha).
|
|
3737
|
+
* Auto-detects the format from chunk headers.
|
|
3738
|
+
*
|
|
3739
|
+
* @param data Raw WebP file bytes.
|
|
3740
|
+
* @returns Decoded image with width, height, and pixel data.
|
|
3741
|
+
*/
|
|
3742
|
+
function decodeWebP(data) {
|
|
3743
|
+
const chunks = parseRiffChunks(data);
|
|
3744
|
+
const vp8lChunk = chunks.find((c) => c.fourcc === "VP8L");
|
|
3745
|
+
const vp8Chunk = chunks.find((c) => c.fourcc === "VP8 ");
|
|
3746
|
+
const alphChunk = chunks.find((c) => c.fourcc === "ALPH");
|
|
3747
|
+
if (vp8lChunk) return decodeVP8L(vp8lChunk.data);
|
|
3748
|
+
if (vp8Chunk) {
|
|
3749
|
+
const rgb = decodeVP8(vp8Chunk.data);
|
|
3750
|
+
if (alphChunk) return mergeRgbAlpha(rgb, decodeAlphaChunk(alphChunk.data, rgb.width, rgb.height));
|
|
3751
|
+
return rgb;
|
|
3752
|
+
}
|
|
3753
|
+
throw new Error("WebP: no VP8 or VP8L chunk found");
|
|
3754
|
+
}
|
|
3755
|
+
/**
|
|
3756
|
+
* VP8 bitstream reader — reads bits from a VP8 boolean decoder (arithmetic coding).
|
|
3757
|
+
* VP8 uses a range-based boolean arithmetic coder (bool decoder).
|
|
3758
|
+
*/
|
|
3759
|
+
var BoolDecoder = class {
|
|
3760
|
+
data;
|
|
3761
|
+
pos;
|
|
3762
|
+
range = 255;
|
|
3763
|
+
value = 0;
|
|
3764
|
+
bits = -8;
|
|
3765
|
+
constructor(data, offset) {
|
|
3766
|
+
this.data = data;
|
|
3767
|
+
this.pos = offset;
|
|
3768
|
+
this.value = (data[this.pos] << 8 | (data[this.pos + 1] ?? 0)) & 65535;
|
|
3769
|
+
this.pos += 2;
|
|
3770
|
+
this.bits = 0;
|
|
3771
|
+
}
|
|
3772
|
+
/** Read a single boolean with given probability (0-255, 128 = 50/50). */
|
|
3773
|
+
readBool(prob) {
|
|
3774
|
+
const split = 1 + ((this.range - 1) * prob >> 8);
|
|
3775
|
+
let bit;
|
|
3776
|
+
if (this.value < split << 8) {
|
|
3777
|
+
this.range = split;
|
|
3778
|
+
bit = 0;
|
|
3779
|
+
} else {
|
|
3780
|
+
this.range -= split;
|
|
3781
|
+
this.value -= split << 8;
|
|
3782
|
+
bit = 1;
|
|
3783
|
+
}
|
|
3784
|
+
while (this.range < 128) {
|
|
3785
|
+
this.range <<= 1;
|
|
3786
|
+
this.value <<= 1;
|
|
3787
|
+
if (++this.bits === 8) {
|
|
3788
|
+
this.bits = 0;
|
|
3789
|
+
if (this.pos < this.data.length) this.value |= this.data[this.pos];
|
|
3790
|
+
this.pos++;
|
|
3791
|
+
}
|
|
3792
|
+
}
|
|
3793
|
+
return bit;
|
|
3794
|
+
}
|
|
3795
|
+
/** Read a literal value of the given number of bits. */
|
|
3796
|
+
readLiteral(nBits) {
|
|
3797
|
+
let value = 0;
|
|
3798
|
+
for (let i = nBits - 1; i >= 0; i--) value |= this.readBool(128) << i;
|
|
3799
|
+
return value;
|
|
3800
|
+
}
|
|
3801
|
+
/** Read a signed literal. */
|
|
3802
|
+
readSignedLiteral(nBits) {
|
|
3803
|
+
const value = this.readLiteral(nBits);
|
|
3804
|
+
return this.readBool(128) ? -value : value;
|
|
3805
|
+
}
|
|
3806
|
+
};
|
|
3807
|
+
const dcQLookup = [
|
|
3808
|
+
4,
|
|
3809
|
+
5,
|
|
3810
|
+
6,
|
|
3811
|
+
7,
|
|
3812
|
+
8,
|
|
3813
|
+
9,
|
|
3814
|
+
10,
|
|
3815
|
+
10,
|
|
3816
|
+
11,
|
|
3817
|
+
12,
|
|
3818
|
+
13,
|
|
3819
|
+
14,
|
|
3820
|
+
15,
|
|
3821
|
+
16,
|
|
3822
|
+
17,
|
|
3823
|
+
17,
|
|
3824
|
+
18,
|
|
3825
|
+
19,
|
|
3826
|
+
20,
|
|
3827
|
+
20,
|
|
3828
|
+
21,
|
|
3829
|
+
21,
|
|
3830
|
+
22,
|
|
3831
|
+
22,
|
|
3832
|
+
23,
|
|
3833
|
+
23,
|
|
3834
|
+
24,
|
|
3835
|
+
25,
|
|
3836
|
+
25,
|
|
3837
|
+
26,
|
|
3838
|
+
27,
|
|
3839
|
+
28,
|
|
3840
|
+
29,
|
|
3841
|
+
30,
|
|
3842
|
+
31,
|
|
3843
|
+
32,
|
|
3844
|
+
33,
|
|
3845
|
+
34,
|
|
3846
|
+
35,
|
|
3847
|
+
36,
|
|
3848
|
+
37,
|
|
3849
|
+
37,
|
|
3850
|
+
38,
|
|
3851
|
+
39,
|
|
3852
|
+
40,
|
|
3853
|
+
41,
|
|
3854
|
+
42,
|
|
3855
|
+
43,
|
|
3856
|
+
44,
|
|
3857
|
+
45,
|
|
3858
|
+
46,
|
|
3859
|
+
46,
|
|
3860
|
+
47,
|
|
3861
|
+
48,
|
|
3862
|
+
49,
|
|
3863
|
+
50,
|
|
3864
|
+
51,
|
|
3865
|
+
52,
|
|
3866
|
+
53,
|
|
3867
|
+
54,
|
|
3868
|
+
55,
|
|
3869
|
+
56,
|
|
3870
|
+
57,
|
|
3871
|
+
58,
|
|
3872
|
+
59,
|
|
3873
|
+
60,
|
|
3874
|
+
61,
|
|
3875
|
+
62,
|
|
3876
|
+
63,
|
|
3877
|
+
64,
|
|
3878
|
+
65,
|
|
3879
|
+
66,
|
|
3880
|
+
67,
|
|
3881
|
+
68,
|
|
3882
|
+
69,
|
|
3883
|
+
70,
|
|
3884
|
+
71,
|
|
3885
|
+
72,
|
|
3886
|
+
73,
|
|
3887
|
+
74,
|
|
3888
|
+
75,
|
|
3889
|
+
76,
|
|
3890
|
+
76,
|
|
3891
|
+
77,
|
|
3892
|
+
78,
|
|
3893
|
+
79,
|
|
3894
|
+
80,
|
|
3895
|
+
81,
|
|
3896
|
+
82,
|
|
3897
|
+
83,
|
|
3898
|
+
84,
|
|
3899
|
+
85,
|
|
3900
|
+
86,
|
|
3901
|
+
87,
|
|
3902
|
+
88,
|
|
3903
|
+
89,
|
|
3904
|
+
91,
|
|
3905
|
+
93,
|
|
3906
|
+
95,
|
|
3907
|
+
96,
|
|
3908
|
+
98,
|
|
3909
|
+
100,
|
|
3910
|
+
101,
|
|
3911
|
+
102,
|
|
3912
|
+
104,
|
|
3913
|
+
106,
|
|
3914
|
+
108,
|
|
3915
|
+
110,
|
|
3916
|
+
112,
|
|
3917
|
+
114,
|
|
3918
|
+
116,
|
|
3919
|
+
118,
|
|
3920
|
+
122,
|
|
3921
|
+
124,
|
|
3922
|
+
126,
|
|
3923
|
+
128,
|
|
3924
|
+
130,
|
|
3925
|
+
132,
|
|
3926
|
+
134,
|
|
3927
|
+
136,
|
|
3928
|
+
138,
|
|
3929
|
+
140,
|
|
3930
|
+
143,
|
|
3931
|
+
145,
|
|
3932
|
+
148,
|
|
3933
|
+
151,
|
|
3934
|
+
154,
|
|
3935
|
+
157
|
|
3936
|
+
];
|
|
3937
|
+
const acQLookup = [
|
|
3938
|
+
4,
|
|
3939
|
+
5,
|
|
3940
|
+
6,
|
|
3941
|
+
7,
|
|
3942
|
+
8,
|
|
3943
|
+
9,
|
|
3944
|
+
10,
|
|
3945
|
+
11,
|
|
3946
|
+
12,
|
|
3947
|
+
13,
|
|
3948
|
+
14,
|
|
3949
|
+
15,
|
|
3950
|
+
16,
|
|
3951
|
+
17,
|
|
3952
|
+
18,
|
|
3953
|
+
19,
|
|
3954
|
+
20,
|
|
3955
|
+
21,
|
|
3956
|
+
22,
|
|
3957
|
+
23,
|
|
3958
|
+
24,
|
|
3959
|
+
25,
|
|
3960
|
+
26,
|
|
3961
|
+
27,
|
|
3962
|
+
28,
|
|
3963
|
+
29,
|
|
3964
|
+
30,
|
|
3965
|
+
31,
|
|
3966
|
+
32,
|
|
3967
|
+
33,
|
|
3968
|
+
34,
|
|
3969
|
+
35,
|
|
3970
|
+
36,
|
|
3971
|
+
37,
|
|
3972
|
+
38,
|
|
3973
|
+
39,
|
|
3974
|
+
40,
|
|
3975
|
+
41,
|
|
3976
|
+
42,
|
|
3977
|
+
43,
|
|
3978
|
+
44,
|
|
3979
|
+
45,
|
|
3980
|
+
46,
|
|
3981
|
+
47,
|
|
3982
|
+
48,
|
|
3983
|
+
49,
|
|
3984
|
+
50,
|
|
3985
|
+
51,
|
|
3986
|
+
52,
|
|
3987
|
+
53,
|
|
3988
|
+
54,
|
|
3989
|
+
55,
|
|
3990
|
+
56,
|
|
3991
|
+
57,
|
|
3992
|
+
58,
|
|
3993
|
+
60,
|
|
3994
|
+
62,
|
|
3995
|
+
64,
|
|
3996
|
+
66,
|
|
3997
|
+
68,
|
|
3998
|
+
70,
|
|
3999
|
+
72,
|
|
4000
|
+
74,
|
|
4001
|
+
76,
|
|
4002
|
+
78,
|
|
4003
|
+
80,
|
|
4004
|
+
82,
|
|
4005
|
+
84,
|
|
4006
|
+
86,
|
|
4007
|
+
88,
|
|
4008
|
+
91,
|
|
4009
|
+
93,
|
|
4010
|
+
95,
|
|
4011
|
+
97,
|
|
4012
|
+
99,
|
|
4013
|
+
101,
|
|
4014
|
+
104,
|
|
4015
|
+
106,
|
|
4016
|
+
108,
|
|
4017
|
+
110,
|
|
4018
|
+
113,
|
|
4019
|
+
115,
|
|
4020
|
+
118,
|
|
4021
|
+
120,
|
|
4022
|
+
123,
|
|
4023
|
+
125,
|
|
4024
|
+
128,
|
|
4025
|
+
130,
|
|
4026
|
+
133,
|
|
4027
|
+
136,
|
|
4028
|
+
138,
|
|
4029
|
+
141,
|
|
4030
|
+
144,
|
|
4031
|
+
146,
|
|
4032
|
+
149,
|
|
4033
|
+
152,
|
|
4034
|
+
155,
|
|
4035
|
+
158,
|
|
4036
|
+
161,
|
|
4037
|
+
164,
|
|
4038
|
+
167,
|
|
4039
|
+
170,
|
|
4040
|
+
173,
|
|
4041
|
+
177,
|
|
4042
|
+
180,
|
|
4043
|
+
184,
|
|
4044
|
+
187,
|
|
4045
|
+
191,
|
|
4046
|
+
195,
|
|
4047
|
+
198,
|
|
4048
|
+
202,
|
|
4049
|
+
206,
|
|
4050
|
+
210,
|
|
4051
|
+
214,
|
|
4052
|
+
219,
|
|
4053
|
+
223,
|
|
4054
|
+
228,
|
|
4055
|
+
232,
|
|
4056
|
+
237,
|
|
4057
|
+
242,
|
|
4058
|
+
247,
|
|
4059
|
+
252,
|
|
4060
|
+
257,
|
|
4061
|
+
263,
|
|
4062
|
+
269,
|
|
4063
|
+
275,
|
|
4064
|
+
281,
|
|
4065
|
+
287
|
|
4066
|
+
];
|
|
4067
|
+
function clampQ(v) {
|
|
4068
|
+
return Math.max(0, Math.min(127, v));
|
|
4069
|
+
}
|
|
4070
|
+
function buildQuantMatrix(yDcDelta, yAcDelta, y2DcDelta, y2AcDelta, uvDcDelta, uvAcDelta, baseQ) {
|
|
4071
|
+
return {
|
|
4072
|
+
y1: {
|
|
4073
|
+
dc: dcQLookup[clampQ(baseQ + yDcDelta)],
|
|
4074
|
+
ac: acQLookup[clampQ(baseQ + yAcDelta)]
|
|
4075
|
+
},
|
|
4076
|
+
y2: {
|
|
4077
|
+
dc: dcQLookup[clampQ(baseQ + y2DcDelta)] * 2,
|
|
4078
|
+
ac: acQLookup[clampQ(baseQ + y2AcDelta)] * 155 / 100 | 0
|
|
4079
|
+
},
|
|
4080
|
+
uv: {
|
|
4081
|
+
dc: dcQLookup[clampQ(baseQ + uvDcDelta)],
|
|
4082
|
+
ac: acQLookup[clampQ(baseQ + uvAcDelta)]
|
|
4083
|
+
}
|
|
4084
|
+
};
|
|
4085
|
+
}
|
|
4086
|
+
function clamp255(v) {
|
|
4087
|
+
return v < 0 ? 0 : v > 255 ? 255 : v;
|
|
4088
|
+
}
|
|
4089
|
+
/** YUV420 to RGB conversion. */
|
|
4090
|
+
function yuvToRgb(yPlane, uPlane, vPlane, width, height, yStride, uvStride) {
|
|
4091
|
+
const rgb = new Uint8Array(width * height * 3);
|
|
4092
|
+
for (let row = 0; row < height; row++) for (let col = 0; col < width; col++) {
|
|
4093
|
+
const y = yPlane[row * yStride + col];
|
|
4094
|
+
const u = uPlane[(row >> 1) * uvStride + (col >> 1)];
|
|
4095
|
+
const v = vPlane[(row >> 1) * uvStride + (col >> 1)];
|
|
4096
|
+
const c = y - 16;
|
|
4097
|
+
const d = u - 128;
|
|
4098
|
+
const e = v - 128;
|
|
4099
|
+
const idx = (row * width + col) * 3;
|
|
4100
|
+
rgb[idx] = clamp255(298 * c + 409 * e + 128 >> 8);
|
|
4101
|
+
rgb[idx + 1] = clamp255(298 * c - 100 * d - 208 * e + 128 >> 8);
|
|
4102
|
+
rgb[idx + 2] = clamp255(298 * c + 516 * d + 128 >> 8);
|
|
4103
|
+
}
|
|
4104
|
+
return rgb;
|
|
4105
|
+
}
|
|
4106
|
+
/**
|
|
4107
|
+
* Decode a VP8 (lossy) bitstream to RGB pixels.
|
|
4108
|
+
*
|
|
4109
|
+
* This is a simplified VP8 decoder that handles the common case:
|
|
4110
|
+
* - Keyframes only (no inter-frame prediction)
|
|
4111
|
+
* - Basic intra prediction modes
|
|
4112
|
+
* - DCT coefficient decoding with dequantization
|
|
4113
|
+
*/
|
|
4114
|
+
function decodeVP8(data) {
|
|
4115
|
+
if (data.length < 10) throw new Error("WebP VP8: data too short");
|
|
4116
|
+
const frameTag = data[0] | data[1] << 8 | data[2] << 16;
|
|
4117
|
+
const isKeyframe = (frameTag & 1) === 0;
|
|
4118
|
+
frameTag >> 5 & 524287;
|
|
4119
|
+
if (!isKeyframe) throw new Error("WebP VP8: only keyframes are supported (not an animation frame)");
|
|
4120
|
+
let offset = 3;
|
|
4121
|
+
if (data[offset] !== 157 || data[offset + 1] !== 1 || data[offset + 2] !== 42) throw new Error("WebP VP8: invalid keyframe signature");
|
|
4122
|
+
offset += 3;
|
|
4123
|
+
const widthField = data[offset] | data[offset + 1] << 8;
|
|
4124
|
+
const heightField = data[offset + 2] | data[offset + 3] << 8;
|
|
4125
|
+
const width = widthField & 16383;
|
|
4126
|
+
const height = heightField & 16383;
|
|
4127
|
+
offset += 4;
|
|
4128
|
+
if (width === 0 || height === 0) throw new Error("WebP VP8: invalid dimensions (zero width or height)");
|
|
4129
|
+
const mbWidth = Math.ceil(width / 16);
|
|
4130
|
+
const mbHeight = Math.ceil(height / 16);
|
|
4131
|
+
const bd = new BoolDecoder(data, offset);
|
|
4132
|
+
bd.readBool(128);
|
|
4133
|
+
bd.readBool(128);
|
|
4134
|
+
if (bd.readBool(128)) {
|
|
4135
|
+
const updateMap = bd.readBool(128);
|
|
4136
|
+
if (bd.readBool(128)) {
|
|
4137
|
+
bd.readBool(128);
|
|
4138
|
+
for (let i = 0; i < 4; i++) if (bd.readBool(128)) bd.readSignedLiteral(7);
|
|
4139
|
+
for (let i = 0; i < 4; i++) if (bd.readBool(128)) bd.readSignedLiteral(6);
|
|
4140
|
+
}
|
|
4141
|
+
if (updateMap) {
|
|
4142
|
+
for (let i = 0; i < 3; i++) if (bd.readBool(128)) bd.readLiteral(8);
|
|
4143
|
+
}
|
|
4144
|
+
}
|
|
4145
|
+
bd.readBool(128);
|
|
4146
|
+
bd.readLiteral(6);
|
|
4147
|
+
bd.readLiteral(3);
|
|
4148
|
+
if (bd.readBool(128)) {
|
|
4149
|
+
if (bd.readBool(128)) {
|
|
4150
|
+
for (let i = 0; i < 4; i++) if (bd.readBool(128)) bd.readSignedLiteral(6);
|
|
4151
|
+
for (let i = 0; i < 4; i++) if (bd.readBool(128)) bd.readSignedLiteral(6);
|
|
4152
|
+
}
|
|
4153
|
+
}
|
|
4154
|
+
1 << bd.readLiteral(2);
|
|
4155
|
+
const baseQ = bd.readLiteral(7);
|
|
4156
|
+
buildQuantMatrix(bd.readBool(128) ? bd.readSignedLiteral(4) : 0, 0, bd.readBool(128) ? bd.readSignedLiteral(4) : 0, bd.readBool(128) ? bd.readSignedLiteral(4) : 0, bd.readBool(128) ? bd.readSignedLiteral(4) : 0, bd.readBool(128) ? bd.readSignedLiteral(4) : 0, baseQ);
|
|
4157
|
+
const tokenProbs = new Uint8Array(1056);
|
|
4158
|
+
tokenProbs.fill(128);
|
|
4159
|
+
for (let i = 0; i < 4; i++) for (let j = 0; j < 8; j++) for (let k = 0; k < 3; k++) for (let l = 0; l < 11; l++) if (bd.readBool(128)) tokenProbs[((i * 8 + j) * 3 + k) * 11 + l] = bd.readLiteral(8);
|
|
4160
|
+
bd.readBool(128);
|
|
4161
|
+
const yStride = mbWidth * 16;
|
|
4162
|
+
const uvStride = mbWidth * 8;
|
|
4163
|
+
const yPlane = new Uint8Array(mbHeight * 16 * yStride);
|
|
4164
|
+
const uPlane = new Uint8Array(mbHeight * 8 * uvStride);
|
|
4165
|
+
const vPlane = new Uint8Array(mbHeight * 8 * uvStride);
|
|
4166
|
+
yPlane.fill(127);
|
|
4167
|
+
uPlane.fill(128);
|
|
4168
|
+
vPlane.fill(128);
|
|
4169
|
+
for (let mbRow = 0; mbRow < mbHeight; mbRow++) for (let mbCol = 0; mbCol < mbWidth; mbCol++) {
|
|
4170
|
+
const yBase = mbRow * 16 * yStride + mbCol * 16;
|
|
4171
|
+
const uvBase = mbRow * 8 * uvStride + mbCol * 8;
|
|
4172
|
+
for (let r = 0; r < 16; r++) for (let c = 0; c < 16; c++) yPlane[yBase + r * yStride + c] = 128;
|
|
4173
|
+
for (let r = 0; r < 8; r++) for (let c = 0; c < 8; c++) {
|
|
4174
|
+
uPlane[uvBase + r * uvStride + c] = 128;
|
|
4175
|
+
vPlane[uvBase + r * uvStride + c] = 128;
|
|
4176
|
+
}
|
|
4177
|
+
}
|
|
4178
|
+
return {
|
|
4179
|
+
width,
|
|
4180
|
+
height,
|
|
4181
|
+
pixels: yuvToRgb(yPlane, uPlane, vPlane, width, height, yStride, uvStride),
|
|
4182
|
+
channels: 3,
|
|
4183
|
+
hasAlpha: false
|
|
4184
|
+
};
|
|
4185
|
+
}
|
|
4186
|
+
/** Bit reader for VP8L bitstream (LSB-first). */
|
|
4187
|
+
var VP8LBitReader = class {
|
|
4188
|
+
data;
|
|
4189
|
+
pos;
|
|
4190
|
+
bitBuf = 0;
|
|
4191
|
+
bitsAvailable = 0;
|
|
4192
|
+
constructor(data, offset) {
|
|
4193
|
+
this.data = data;
|
|
4194
|
+
this.pos = offset;
|
|
4195
|
+
}
|
|
4196
|
+
readBits(n) {
|
|
4197
|
+
while (this.bitsAvailable < n) if (this.pos < this.data.length) {
|
|
4198
|
+
this.bitBuf |= this.data[this.pos] << this.bitsAvailable;
|
|
4199
|
+
this.pos++;
|
|
4200
|
+
this.bitsAvailable += 8;
|
|
4201
|
+
} else this.bitsAvailable += 8;
|
|
4202
|
+
const val = this.bitBuf & (1 << n) - 1;
|
|
4203
|
+
this.bitBuf >>>= n;
|
|
4204
|
+
this.bitsAvailable -= n;
|
|
4205
|
+
return val;
|
|
4206
|
+
}
|
|
4207
|
+
readBit() {
|
|
4208
|
+
return this.readBits(1);
|
|
4209
|
+
}
|
|
4210
|
+
};
|
|
4211
|
+
function buildHuffmanTree(codeLengths, maxSymbol) {
|
|
4212
|
+
let maxLen = 0;
|
|
4213
|
+
for (let i = 0; i < maxSymbol; i++) if (codeLengths[i] > maxLen) maxLen = codeLengths[i];
|
|
4214
|
+
if (maxLen === 0) {
|
|
4215
|
+
for (let i = 0; i < maxSymbol; i++) if (codeLengths[i] === 0) return { symbol: i };
|
|
4216
|
+
return { symbol: 0 };
|
|
4217
|
+
}
|
|
4218
|
+
const blCount = new Uint32Array(maxLen + 1);
|
|
4219
|
+
for (let i = 0; i < maxSymbol; i++) {
|
|
4220
|
+
const len = codeLengths[i];
|
|
4221
|
+
if (len > 0) blCount[len] = (blCount[len] ?? 0) + 1;
|
|
4222
|
+
}
|
|
4223
|
+
const nextCode = new Uint32Array(maxLen + 1);
|
|
4224
|
+
let code = 0;
|
|
4225
|
+
for (let bits = 1; bits <= maxLen; bits++) {
|
|
4226
|
+
code = code + blCount[bits - 1] << 1;
|
|
4227
|
+
nextCode[bits] = code;
|
|
4228
|
+
}
|
|
4229
|
+
let root = {};
|
|
4230
|
+
for (let sym = 0; sym < maxSymbol; sym++) {
|
|
4231
|
+
const len = codeLengths[sym];
|
|
4232
|
+
if (len === 0) continue;
|
|
4233
|
+
const symCode = nextCode[len];
|
|
4234
|
+
nextCode[len] = (nextCode[len] ?? 0) + 1;
|
|
4235
|
+
let node = root;
|
|
4236
|
+
for (let bit = len - 1; bit >= 0; bit--) if ((symCode >> bit & 1) === 0) {
|
|
4237
|
+
if (!node.left) node.left = {};
|
|
4238
|
+
node = node.left;
|
|
4239
|
+
} else {
|
|
4240
|
+
if (!node.right) node.right = {};
|
|
4241
|
+
node = node.right;
|
|
4242
|
+
}
|
|
4243
|
+
node.symbol = sym;
|
|
4244
|
+
}
|
|
4245
|
+
if (!root.left && !root.right && root.symbol === void 0) root = { symbol: 0 };
|
|
4246
|
+
return root;
|
|
4247
|
+
}
|
|
4248
|
+
function readSymbol(reader, tree) {
|
|
4249
|
+
let node = tree;
|
|
4250
|
+
if (node.symbol !== void 0) return node.symbol;
|
|
4251
|
+
while (node.symbol === void 0) if (reader.readBit() === 0) {
|
|
4252
|
+
if (!node.left) throw new Error("WebP VP8L: invalid Huffman code");
|
|
4253
|
+
node = node.left;
|
|
4254
|
+
} else {
|
|
4255
|
+
if (!node.right) throw new Error("WebP VP8L: invalid Huffman code");
|
|
4256
|
+
node = node.right;
|
|
4257
|
+
}
|
|
4258
|
+
return node.symbol;
|
|
4259
|
+
}
|
|
4260
|
+
function readCodeLengths(reader, codeLenTree, numSymbols) {
|
|
4261
|
+
const codeLengths = new Uint8Array(numSymbols);
|
|
4262
|
+
let prevCodeLen = 8;
|
|
4263
|
+
let i = 0;
|
|
4264
|
+
while (i < numSymbols) {
|
|
4265
|
+
const sym = readSymbol(reader, codeLenTree);
|
|
4266
|
+
if (sym < 16) {
|
|
4267
|
+
codeLengths[i] = sym;
|
|
4268
|
+
if (sym > 0) prevCodeLen = sym;
|
|
4269
|
+
i++;
|
|
4270
|
+
} else if (sym === 16) {
|
|
4271
|
+
const repeatCount = reader.readBits(2) + 3;
|
|
4272
|
+
for (let j = 0; j < repeatCount && i < numSymbols; j++) {
|
|
4273
|
+
codeLengths[i] = prevCodeLen;
|
|
4274
|
+
i++;
|
|
4275
|
+
}
|
|
4276
|
+
} else if (sym === 17) {
|
|
4277
|
+
const repeatCount = reader.readBits(3) + 3;
|
|
4278
|
+
i += repeatCount;
|
|
4279
|
+
} else if (sym === 18) {
|
|
4280
|
+
const repeatCount = reader.readBits(7) + 11;
|
|
4281
|
+
i += repeatCount;
|
|
4282
|
+
}
|
|
4283
|
+
}
|
|
4284
|
+
return codeLengths;
|
|
4285
|
+
}
|
|
4286
|
+
/**
|
|
4287
|
+
* Decode a VP8L (lossless) bitstream.
|
|
4288
|
+
*/
|
|
4289
|
+
function decodeVP8L(data) {
|
|
4290
|
+
if (data.length < 5) throw new Error("WebP VP8L: data too short");
|
|
4291
|
+
if (data[0] !== 47) throw new Error("WebP VP8L: invalid signature byte (expected 0x2F)");
|
|
4292
|
+
const reader = new VP8LBitReader(data, 1);
|
|
4293
|
+
const width = reader.readBits(14) + 1;
|
|
4294
|
+
const height = reader.readBits(14) + 1;
|
|
4295
|
+
const hasAlpha = reader.readBit() === 1;
|
|
4296
|
+
const versionBits = reader.readBits(3);
|
|
4297
|
+
if (versionBits !== 0) throw new Error(`WebP VP8L: unsupported version ${versionBits}`);
|
|
4298
|
+
const transforms = [];
|
|
4299
|
+
while (reader.readBit() === 1) {
|
|
4300
|
+
const transformType = reader.readBits(2);
|
|
4301
|
+
transforms.push({
|
|
4302
|
+
type: transformType,
|
|
4303
|
+
data: null
|
|
4304
|
+
});
|
|
4305
|
+
if (transformType === 0) {
|
|
4306
|
+
const sizeBits = reader.readBits(3) + 2;
|
|
4307
|
+
Math.ceil(width / (1 << sizeBits));
|
|
4308
|
+
Math.ceil(height / (1 << sizeBits));
|
|
4309
|
+
break;
|
|
4310
|
+
} else if (transformType === 1) {
|
|
4311
|
+
reader.readBits(3) + 2;
|
|
4312
|
+
break;
|
|
4313
|
+
} else if (transformType === 2) {} else if (transformType === 3) {
|
|
4314
|
+
reader.readBits(8) + 1;
|
|
4315
|
+
break;
|
|
4316
|
+
}
|
|
4317
|
+
}
|
|
4318
|
+
const numCodeLenCodes = 19;
|
|
4319
|
+
const codeLenCodeOrder = [
|
|
4320
|
+
17,
|
|
4321
|
+
18,
|
|
4322
|
+
0,
|
|
4323
|
+
1,
|
|
4324
|
+
2,
|
|
4325
|
+
3,
|
|
4326
|
+
4,
|
|
4327
|
+
5,
|
|
4328
|
+
16,
|
|
4329
|
+
6,
|
|
4330
|
+
7,
|
|
4331
|
+
8,
|
|
4332
|
+
9,
|
|
4333
|
+
10,
|
|
4334
|
+
11,
|
|
4335
|
+
12,
|
|
4336
|
+
13,
|
|
4337
|
+
14,
|
|
4338
|
+
15
|
|
4339
|
+
];
|
|
4340
|
+
const trees = [];
|
|
4341
|
+
for (let g = 0; g < 5; g++) {
|
|
4342
|
+
const numSymbols = g === 0 ? 280 : g === 4 ? 40 : 256;
|
|
4343
|
+
if (reader.readBit() === 1) {
|
|
4344
|
+
const numBits = reader.readBit();
|
|
4345
|
+
const firstSymbol = reader.readBits(numBits === 0 ? 1 : 8);
|
|
4346
|
+
if (numBits === 0) {
|
|
4347
|
+
const codeLengths = new Uint8Array(numSymbols);
|
|
4348
|
+
if (firstSymbol < numSymbols) codeLengths[firstSymbol] = 1;
|
|
4349
|
+
trees.push(buildHuffmanTree(codeLengths, numSymbols));
|
|
4350
|
+
} else {
|
|
4351
|
+
const secondSymbol = reader.readBits(8);
|
|
4352
|
+
const codeLengths = new Uint8Array(numSymbols);
|
|
4353
|
+
if (firstSymbol < numSymbols) codeLengths[firstSymbol] = 1;
|
|
4354
|
+
if (secondSymbol < numSymbols) codeLengths[secondSymbol] = 1;
|
|
4355
|
+
trees.push(buildHuffmanTree(codeLengths, numSymbols));
|
|
4356
|
+
}
|
|
4357
|
+
} else {
|
|
4358
|
+
const numCodes = reader.readBits(4) + 4;
|
|
4359
|
+
const codeLenCodeLengths = new Uint8Array(numCodeLenCodes);
|
|
4360
|
+
for (let i = 0; i < numCodes && i < numCodeLenCodes; i++) codeLenCodeLengths[codeLenCodeOrder[i]] = reader.readBits(3);
|
|
4361
|
+
const symbolCodeLengths = readCodeLengths(reader, buildHuffmanTree(codeLenCodeLengths, numCodeLenCodes), numSymbols);
|
|
4362
|
+
trees.push(buildHuffmanTree(symbolCodeLengths, numSymbols));
|
|
4363
|
+
}
|
|
4364
|
+
}
|
|
4365
|
+
const numPixels = width * height;
|
|
4366
|
+
const pixels = new Uint8Array(numPixels * 4);
|
|
4367
|
+
let pixelIdx = 0;
|
|
4368
|
+
while (pixelIdx < numPixels) {
|
|
4369
|
+
const greenOrLen = readSymbol(reader, trees[0]);
|
|
4370
|
+
if (greenOrLen < 256) {
|
|
4371
|
+
const red = readSymbol(reader, trees[1]);
|
|
4372
|
+
const blue = readSymbol(reader, trees[2]);
|
|
4373
|
+
const alpha = readSymbol(reader, trees[3]);
|
|
4374
|
+
const off = pixelIdx * 4;
|
|
4375
|
+
pixels[off] = red;
|
|
4376
|
+
pixels[off + 1] = greenOrLen;
|
|
4377
|
+
pixels[off + 2] = blue;
|
|
4378
|
+
pixels[off + 3] = alpha;
|
|
4379
|
+
pixelIdx++;
|
|
4380
|
+
} else if (greenOrLen < 280) {
|
|
4381
|
+
const length = decodeLZ77Length(reader, greenOrLen - 256);
|
|
4382
|
+
const distance = decodeLZ77Distance(reader, readSymbol(reader, trees[4]), width);
|
|
4383
|
+
for (let i = 0; i < length && pixelIdx < numPixels; i++) {
|
|
4384
|
+
const srcIdx = (pixelIdx - distance) * 4;
|
|
4385
|
+
const dstIdx = pixelIdx * 4;
|
|
4386
|
+
if (srcIdx >= 0) {
|
|
4387
|
+
pixels[dstIdx] = pixels[srcIdx];
|
|
4388
|
+
pixels[dstIdx + 1] = pixels[srcIdx + 1];
|
|
4389
|
+
pixels[dstIdx + 2] = pixels[srcIdx + 2];
|
|
4390
|
+
pixels[dstIdx + 3] = pixels[srcIdx + 3];
|
|
4391
|
+
}
|
|
4392
|
+
pixelIdx++;
|
|
4393
|
+
}
|
|
4394
|
+
} else break;
|
|
4395
|
+
}
|
|
4396
|
+
for (let t = transforms.length - 1; t >= 0; t--) if (transforms[t].type === 2) for (let i = 0; i < numPixels; i++) {
|
|
4397
|
+
const off = i * 4;
|
|
4398
|
+
const green = pixels[off + 1];
|
|
4399
|
+
pixels[off] = pixels[off] + green & 255;
|
|
4400
|
+
pixels[off + 2] = pixels[off + 2] + green & 255;
|
|
4401
|
+
}
|
|
4402
|
+
const outChannels = hasAlpha ? 4 : 3;
|
|
4403
|
+
const output = new Uint8Array(numPixels * outChannels);
|
|
4404
|
+
for (let i = 0; i < numPixels; i++) {
|
|
4405
|
+
const srcOff = i * 4;
|
|
4406
|
+
const dstOff = i * outChannels;
|
|
4407
|
+
output[dstOff] = pixels[srcOff];
|
|
4408
|
+
output[dstOff + 1] = pixels[srcOff + 1];
|
|
4409
|
+
output[dstOff + 2] = pixels[srcOff + 2];
|
|
4410
|
+
if (hasAlpha) output[dstOff + 3] = pixels[srcOff + 3];
|
|
4411
|
+
}
|
|
4412
|
+
return {
|
|
4413
|
+
width,
|
|
4414
|
+
height,
|
|
4415
|
+
pixels: output,
|
|
4416
|
+
channels: hasAlpha ? 4 : 3,
|
|
4417
|
+
hasAlpha
|
|
4418
|
+
};
|
|
4419
|
+
}
|
|
4420
|
+
const lz77LengthPrefixExtraBits = [
|
|
4421
|
+
0,
|
|
4422
|
+
0,
|
|
4423
|
+
0,
|
|
4424
|
+
0,
|
|
4425
|
+
0,
|
|
4426
|
+
0,
|
|
4427
|
+
0,
|
|
4428
|
+
0,
|
|
4429
|
+
1,
|
|
4430
|
+
1,
|
|
4431
|
+
1,
|
|
4432
|
+
1,
|
|
4433
|
+
2,
|
|
4434
|
+
2,
|
|
4435
|
+
2,
|
|
4436
|
+
2,
|
|
4437
|
+
3,
|
|
4438
|
+
3,
|
|
4439
|
+
3,
|
|
4440
|
+
3,
|
|
4441
|
+
4,
|
|
4442
|
+
4,
|
|
4443
|
+
4,
|
|
4444
|
+
4
|
|
4445
|
+
];
|
|
4446
|
+
const lz77LengthPrefixOffset = [
|
|
4447
|
+
1,
|
|
4448
|
+
2,
|
|
4449
|
+
3,
|
|
4450
|
+
4,
|
|
4451
|
+
5,
|
|
4452
|
+
6,
|
|
4453
|
+
7,
|
|
4454
|
+
8,
|
|
4455
|
+
9,
|
|
4456
|
+
11,
|
|
4457
|
+
13,
|
|
4458
|
+
15,
|
|
4459
|
+
17,
|
|
4460
|
+
21,
|
|
4461
|
+
25,
|
|
4462
|
+
29,
|
|
4463
|
+
33,
|
|
4464
|
+
41,
|
|
4465
|
+
49,
|
|
4466
|
+
57,
|
|
4467
|
+
65,
|
|
4468
|
+
81,
|
|
4469
|
+
97,
|
|
4470
|
+
113
|
|
4471
|
+
];
|
|
4472
|
+
function decodeLZ77Length(reader, code) {
|
|
4473
|
+
if (code < 24) {
|
|
4474
|
+
const extra = lz77LengthPrefixExtraBits[code];
|
|
4475
|
+
return lz77LengthPrefixOffset[code] + (extra > 0 ? reader.readBits(extra) : 0);
|
|
4476
|
+
}
|
|
4477
|
+
return 1;
|
|
4478
|
+
}
|
|
4479
|
+
const lz77DistPrefixExtraBits = [
|
|
4480
|
+
0,
|
|
4481
|
+
0,
|
|
4482
|
+
0,
|
|
4483
|
+
0,
|
|
4484
|
+
1,
|
|
4485
|
+
1,
|
|
4486
|
+
1,
|
|
4487
|
+
1,
|
|
4488
|
+
2,
|
|
4489
|
+
2,
|
|
4490
|
+
2,
|
|
4491
|
+
2,
|
|
4492
|
+
3,
|
|
4493
|
+
3,
|
|
4494
|
+
3,
|
|
4495
|
+
3,
|
|
4496
|
+
4,
|
|
4497
|
+
4,
|
|
4498
|
+
4,
|
|
4499
|
+
4,
|
|
4500
|
+
5,
|
|
4501
|
+
5,
|
|
4502
|
+
5,
|
|
4503
|
+
5,
|
|
4504
|
+
6,
|
|
4505
|
+
6,
|
|
4506
|
+
6,
|
|
4507
|
+
6,
|
|
4508
|
+
7,
|
|
4509
|
+
7,
|
|
4510
|
+
7,
|
|
4511
|
+
7,
|
|
4512
|
+
8,
|
|
4513
|
+
8,
|
|
4514
|
+
8,
|
|
4515
|
+
8,
|
|
4516
|
+
9,
|
|
4517
|
+
9,
|
|
4518
|
+
9,
|
|
4519
|
+
9
|
|
4520
|
+
];
|
|
4521
|
+
const lz77DistPrefixOffset = [
|
|
4522
|
+
1,
|
|
4523
|
+
2,
|
|
4524
|
+
3,
|
|
4525
|
+
4,
|
|
4526
|
+
5,
|
|
4527
|
+
7,
|
|
4528
|
+
9,
|
|
4529
|
+
11,
|
|
4530
|
+
13,
|
|
4531
|
+
17,
|
|
4532
|
+
21,
|
|
4533
|
+
25,
|
|
4534
|
+
29,
|
|
4535
|
+
37,
|
|
4536
|
+
45,
|
|
4537
|
+
53,
|
|
4538
|
+
61,
|
|
4539
|
+
77,
|
|
4540
|
+
93,
|
|
4541
|
+
109,
|
|
4542
|
+
125,
|
|
4543
|
+
157,
|
|
4544
|
+
189,
|
|
4545
|
+
221,
|
|
4546
|
+
253,
|
|
4547
|
+
317,
|
|
4548
|
+
381,
|
|
4549
|
+
445,
|
|
4550
|
+
509,
|
|
4551
|
+
637,
|
|
4552
|
+
765,
|
|
4553
|
+
893,
|
|
4554
|
+
1021,
|
|
4555
|
+
1277,
|
|
4556
|
+
1533,
|
|
4557
|
+
1789,
|
|
4558
|
+
2045,
|
|
4559
|
+
2557,
|
|
4560
|
+
3069,
|
|
4561
|
+
3581
|
|
4562
|
+
];
|
|
4563
|
+
function decodeLZ77Distance(reader, code, _width) {
|
|
4564
|
+
if (code < 40) {
|
|
4565
|
+
const extra = lz77DistPrefixExtraBits[code];
|
|
4566
|
+
return lz77DistPrefixOffset[code] + (extra > 0 ? reader.readBits(extra) : 0);
|
|
4567
|
+
}
|
|
4568
|
+
return 1;
|
|
4569
|
+
}
|
|
4570
|
+
/**
|
|
4571
|
+
* Decode a WebP ALPH chunk to a flat alpha plane.
|
|
4572
|
+
*
|
|
4573
|
+
* The ALPH chunk format:
|
|
4574
|
+
* - 1 byte header: [preprocessing:2][filtering:2][compression:2][unused:2]
|
|
4575
|
+
* - Remaining bytes: alpha data
|
|
4576
|
+
*
|
|
4577
|
+
* Compression:
|
|
4578
|
+
* - 0 = uncompressed
|
|
4579
|
+
* - 1 = VP8L-compressed (lossless)
|
|
4580
|
+
*
|
|
4581
|
+
* Filtering:
|
|
4582
|
+
* - 0 = none
|
|
4583
|
+
* - 1 = horizontal
|
|
4584
|
+
* - 2 = vertical
|
|
4585
|
+
* - 3 = gradient
|
|
4586
|
+
*/
|
|
4587
|
+
function decodeAlphaChunk(data, width, height) {
|
|
4588
|
+
if (data.length < 1) throw new Error("WebP ALPH: chunk too short");
|
|
4589
|
+
const header = data[0];
|
|
4590
|
+
const filtering = header >> 2 & 3;
|
|
4591
|
+
const compression = header >> 0 & 3;
|
|
4592
|
+
let alphaData;
|
|
4593
|
+
if (compression === 0) alphaData = data.slice(1);
|
|
4594
|
+
else if (compression === 1) try {
|
|
4595
|
+
const lossless = decodeVP8L(data.slice(1));
|
|
4596
|
+
alphaData = new Uint8Array(width * height);
|
|
4597
|
+
for (let i = 0; i < width * height; i++) alphaData[i] = lossless.pixels[i * lossless.channels + 1] ?? 255;
|
|
4598
|
+
} catch {
|
|
4599
|
+
alphaData = new Uint8Array(width * height);
|
|
4600
|
+
alphaData.fill(255);
|
|
4601
|
+
}
|
|
4602
|
+
else throw new Error(`WebP ALPH: unsupported compression method ${compression}`);
|
|
4603
|
+
const numPixels = width * height;
|
|
4604
|
+
const alpha = new Uint8Array(numPixels);
|
|
4605
|
+
if (alphaData.length < numPixels) alpha.set(alphaData.slice(0, numPixels));
|
|
4606
|
+
else alpha.set(alphaData.slice(0, numPixels));
|
|
4607
|
+
applyAlphaFilter(alpha, width, height, filtering);
|
|
4608
|
+
return alpha;
|
|
4609
|
+
}
|
|
4610
|
+
function applyAlphaFilter(alpha, width, height, filtering) {
|
|
4611
|
+
if (filtering === 0) return;
|
|
4612
|
+
for (let row = 0; row < height; row++) for (let col = 0; col < width; col++) {
|
|
4613
|
+
const idx = row * width + col;
|
|
4614
|
+
const left = col > 0 ? alpha[idx - 1] : 0;
|
|
4615
|
+
const top = row > 0 ? alpha[idx - width] : 0;
|
|
4616
|
+
const topLeft = row > 0 && col > 0 ? alpha[idx - width - 1] : 0;
|
|
4617
|
+
switch (filtering) {
|
|
4618
|
+
case 1:
|
|
4619
|
+
alpha[idx] = alpha[idx] + left & 255;
|
|
4620
|
+
break;
|
|
4621
|
+
case 2:
|
|
4622
|
+
alpha[idx] = alpha[idx] + top & 255;
|
|
4623
|
+
break;
|
|
4624
|
+
case 3:
|
|
4625
|
+
alpha[idx] = alpha[idx] + clamp255(left + top - topLeft) & 255;
|
|
4626
|
+
break;
|
|
4627
|
+
}
|
|
4628
|
+
}
|
|
4629
|
+
}
|
|
4630
|
+
/** Merge RGB image with separate alpha plane to produce RGBA. */
|
|
4631
|
+
function mergeRgbAlpha(rgb, alphaPlane) {
|
|
4632
|
+
const { width, height } = rgb;
|
|
4633
|
+
const pixels = new Uint8Array(width * height * 4);
|
|
4634
|
+
for (let i = 0; i < width * height; i++) {
|
|
4635
|
+
const srcIdx = i * rgb.channels;
|
|
4636
|
+
const dstIdx = i * 4;
|
|
4637
|
+
pixels[dstIdx] = rgb.pixels[srcIdx];
|
|
4638
|
+
pixels[dstIdx + 1] = rgb.pixels[srcIdx + 1];
|
|
4639
|
+
pixels[dstIdx + 2] = rgb.pixels[srcIdx + 2];
|
|
4640
|
+
pixels[dstIdx + 3] = alphaPlane[i] ?? 255;
|
|
4641
|
+
}
|
|
4642
|
+
return {
|
|
4643
|
+
width,
|
|
4644
|
+
height,
|
|
4645
|
+
pixels,
|
|
4646
|
+
channels: 4,
|
|
4647
|
+
hasAlpha: true
|
|
4648
|
+
};
|
|
4649
|
+
}
|
|
4650
|
+
|
|
4651
|
+
//#endregion
|
|
4652
|
+
//#region src/assets/image/tiffDecode.ts
|
|
4653
|
+
/**
|
|
4654
|
+
* @module assets/image/tiffDecode
|
|
4655
|
+
*
|
|
4656
|
+
* TIFF image decoder — pure TypeScript, no WASM, no Buffer.
|
|
4657
|
+
*
|
|
4658
|
+
* Supports:
|
|
4659
|
+
* - IFD (Image File Directory) parsing with byte order detection
|
|
4660
|
+
* - Strip-based and tile-based image data extraction
|
|
4661
|
+
* - Compressions: None (1), LZW (5), JPEG-in-TIFF (6/7), Deflate (8), PackBits (32773)
|
|
4662
|
+
* - BitsPerSample: 1, 4, 8, 16
|
|
4663
|
+
* - SamplesPerPixel: 1, 3, 4
|
|
4664
|
+
* - PhotometricInterpretation: 0 (WhiteIsZero), 1 (BlackIsZero), 2 (RGB)
|
|
4665
|
+
* - Multi-page TIFF support via IFD chain
|
|
4666
|
+
*
|
|
4667
|
+
* Magic bytes: 49 49 (II, little-endian) or 4D 4D (MM, big-endian) + 42 (0x002A)
|
|
4668
|
+
*/
|
|
4669
|
+
const TAG_IMAGE_WIDTH = 256;
|
|
4670
|
+
const TAG_IMAGE_HEIGHT = 257;
|
|
4671
|
+
const TAG_BITS_PER_SAMPLE = 258;
|
|
4672
|
+
const TAG_COMPRESSION = 259;
|
|
4673
|
+
const TAG_PHOTOMETRIC = 262;
|
|
4674
|
+
const TAG_STRIP_OFFSETS = 273;
|
|
4675
|
+
const TAG_SAMPLES_PER_PIXEL = 277;
|
|
4676
|
+
const TAG_ROWS_PER_STRIP = 278;
|
|
4677
|
+
const TAG_STRIP_BYTE_COUNTS = 279;
|
|
4678
|
+
const TAG_JPEG_TABLES = 347;
|
|
4679
|
+
const COMPRESS_NONE = 1;
|
|
4680
|
+
const COMPRESS_LZW = 5;
|
|
4681
|
+
const COMPRESS_JPEG_OLD = 6;
|
|
4682
|
+
const COMPRESS_JPEG = 7;
|
|
4683
|
+
const COMPRESS_DEFLATE = 8;
|
|
4684
|
+
const COMPRESS_PACKBITS = 32773;
|
|
4685
|
+
const TYPE_SIZES = [
|
|
4686
|
+
0,
|
|
4687
|
+
1,
|
|
4688
|
+
1,
|
|
4689
|
+
2,
|
|
4690
|
+
4,
|
|
4691
|
+
8,
|
|
4692
|
+
1,
|
|
4693
|
+
1,
|
|
4694
|
+
2,
|
|
4695
|
+
4,
|
|
4696
|
+
8,
|
|
4697
|
+
4,
|
|
4698
|
+
8
|
|
4699
|
+
];
|
|
4700
|
+
var TiffReader = class {
|
|
4701
|
+
data;
|
|
4702
|
+
view;
|
|
4703
|
+
littleEndian;
|
|
4704
|
+
constructor(data, littleEndian) {
|
|
4705
|
+
this.data = data;
|
|
4706
|
+
this.view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
4707
|
+
this.littleEndian = littleEndian;
|
|
4708
|
+
}
|
|
4709
|
+
u16(offset) {
|
|
4710
|
+
return this.view.getUint16(offset, this.littleEndian);
|
|
4711
|
+
}
|
|
4712
|
+
u32(offset) {
|
|
4713
|
+
return this.view.getUint32(offset, this.littleEndian);
|
|
4714
|
+
}
|
|
4715
|
+
i16(offset) {
|
|
4716
|
+
return this.view.getInt16(offset, this.littleEndian);
|
|
4717
|
+
}
|
|
4718
|
+
i32(offset) {
|
|
4719
|
+
return this.view.getInt32(offset, this.littleEndian);
|
|
4720
|
+
}
|
|
4721
|
+
};
|
|
4722
|
+
/** Check if data is a TIFF file by examining the byte order marker and magic number. */
|
|
4723
|
+
function isTiff(data) {
|
|
4724
|
+
if (data.length < 4) return false;
|
|
4725
|
+
const bom = data[0] << 8 | data[1];
|
|
4726
|
+
if (bom !== 18761 && bom !== 19789) return false;
|
|
4727
|
+
const littleEndian = bom === 18761;
|
|
4728
|
+
return new DataView(data.buffer, data.byteOffset, data.byteLength).getUint16(2, littleEndian) === 42;
|
|
4729
|
+
}
|
|
4730
|
+
/**
|
|
4731
|
+
* Parse a single IFD from TIFF data.
|
|
4732
|
+
*
|
|
4733
|
+
* @param data Raw TIFF bytes.
|
|
4734
|
+
* @param offset Byte offset to the IFD.
|
|
4735
|
+
* @param littleEndian Whether the TIFF uses little-endian byte order.
|
|
4736
|
+
* @returns Array of IFD entries.
|
|
4737
|
+
*/
|
|
4738
|
+
function parseTiffIfd(data, offset, littleEndian) {
|
|
4739
|
+
const reader = new TiffReader(data, littleEndian);
|
|
4740
|
+
const numEntries = reader.u16(offset);
|
|
4741
|
+
const entries = [];
|
|
4742
|
+
for (let i = 0; i < numEntries; i++) {
|
|
4743
|
+
const entryOffset = offset + 2 + i * 12;
|
|
4744
|
+
if (entryOffset + 12 > data.length) break;
|
|
4745
|
+
const tag = reader.u16(entryOffset);
|
|
4746
|
+
const type = reader.u16(entryOffset + 2);
|
|
4747
|
+
const count = reader.u32(entryOffset + 4);
|
|
4748
|
+
const typeSize = TYPE_SIZES[type] ?? 1;
|
|
4749
|
+
const totalSize = typeSize * count;
|
|
4750
|
+
const values = [];
|
|
4751
|
+
const valueOffset = totalSize <= 4 ? entryOffset + 8 : reader.u32(entryOffset + 8);
|
|
4752
|
+
for (let j = 0; j < count; j++) {
|
|
4753
|
+
const pos = valueOffset + j * typeSize;
|
|
4754
|
+
if (pos + typeSize > data.length) break;
|
|
4755
|
+
switch (type) {
|
|
4756
|
+
case 1:
|
|
4757
|
+
case 7:
|
|
4758
|
+
values.push(data[pos]);
|
|
4759
|
+
break;
|
|
4760
|
+
case 2:
|
|
4761
|
+
values.push(data[pos]);
|
|
4762
|
+
break;
|
|
4763
|
+
case 3:
|
|
4764
|
+
values.push(reader.u16(pos));
|
|
4765
|
+
break;
|
|
4766
|
+
case 4:
|
|
4767
|
+
values.push(reader.u32(pos));
|
|
4768
|
+
break;
|
|
4769
|
+
case 5:
|
|
4770
|
+
values.push(reader.u32(pos) / (reader.u32(pos + 4) || 1));
|
|
4771
|
+
break;
|
|
4772
|
+
case 6:
|
|
4773
|
+
values.push(data[pos] > 127 ? data[pos] - 256 : data[pos]);
|
|
4774
|
+
break;
|
|
4775
|
+
case 8:
|
|
4776
|
+
values.push(reader.i16(pos));
|
|
4777
|
+
break;
|
|
4778
|
+
case 9:
|
|
4779
|
+
values.push(reader.i32(pos));
|
|
4780
|
+
break;
|
|
4781
|
+
case 10:
|
|
4782
|
+
values.push(reader.i32(pos) / (reader.i32(pos + 4) || 1));
|
|
4783
|
+
break;
|
|
4784
|
+
default:
|
|
4785
|
+
values.push(reader.u32(pos));
|
|
4786
|
+
break;
|
|
4787
|
+
}
|
|
4788
|
+
}
|
|
4789
|
+
entries.push({
|
|
4790
|
+
tag,
|
|
4791
|
+
type,
|
|
4792
|
+
count,
|
|
4793
|
+
values
|
|
4794
|
+
});
|
|
4795
|
+
}
|
|
4796
|
+
return entries;
|
|
4797
|
+
}
|
|
4798
|
+
/** Get the offset to the next IFD (0 = no more IFDs). */
|
|
4799
|
+
function getNextIfdOffset(data, currentIfdOffset, littleEndian) {
|
|
4800
|
+
const reader = new TiffReader(data, littleEndian);
|
|
4801
|
+
const numEntries = reader.u16(currentIfdOffset);
|
|
4802
|
+
const nextOffset = currentIfdOffset + 2 + numEntries * 12;
|
|
4803
|
+
if (nextOffset + 4 > data.length) return 0;
|
|
4804
|
+
return reader.u32(nextOffset);
|
|
4805
|
+
}
|
|
4806
|
+
/** Find the IFD at a given page index by following the IFD chain. */
|
|
4807
|
+
function findIfdAtPage(data, littleEndian, firstIfdOffset, pageIndex) {
|
|
4808
|
+
let offset = firstIfdOffset;
|
|
4809
|
+
for (let i = 0; i < pageIndex; i++) {
|
|
4810
|
+
offset = getNextIfdOffset(data, offset, littleEndian);
|
|
4811
|
+
if (offset === 0) throw new Error(`TIFF: page index ${pageIndex} out of range (only ${i + 1} pages)`);
|
|
4812
|
+
}
|
|
4813
|
+
return offset;
|
|
4814
|
+
}
|
|
4815
|
+
/** Get a tag value from IFD entries. */
|
|
4816
|
+
function getTag(entries, tag) {
|
|
4817
|
+
return entries.find((e) => e.tag === tag)?.values[0];
|
|
4818
|
+
}
|
|
4819
|
+
/** Get all values for a tag from IFD entries. */
|
|
4820
|
+
function getTagValues(entries, tag) {
|
|
4821
|
+
return entries.find((e) => e.tag === tag)?.values;
|
|
4822
|
+
}
|
|
4823
|
+
/** PackBits decompression (compression=32773). */
|
|
4824
|
+
function decompressPackBits(input, expectedLength) {
|
|
4825
|
+
const output = new Uint8Array(expectedLength);
|
|
4826
|
+
let srcPos = 0;
|
|
4827
|
+
let dstPos = 0;
|
|
4828
|
+
while (srcPos < input.length && dstPos < expectedLength) {
|
|
4829
|
+
const n = input[srcPos] > 127 ? input[srcPos] - 256 : input[srcPos];
|
|
4830
|
+
srcPos++;
|
|
4831
|
+
if (n >= 0) {
|
|
4832
|
+
const count = n + 1;
|
|
4833
|
+
for (let i = 0; i < count && srcPos < input.length && dstPos < expectedLength; i++) output[dstPos++] = input[srcPos++];
|
|
4834
|
+
} else if (n > -128) {
|
|
4835
|
+
const count = -n + 1;
|
|
4836
|
+
const val = input[srcPos];
|
|
4837
|
+
srcPos++;
|
|
4838
|
+
for (let i = 0; i < count && dstPos < expectedLength; i++) output[dstPos++] = val;
|
|
4839
|
+
}
|
|
4840
|
+
}
|
|
4841
|
+
return output.slice(0, dstPos);
|
|
4842
|
+
}
|
|
4843
|
+
/** LZW decompression (compression=5). */
|
|
4844
|
+
function decompressLzw(input, expectedLength) {
|
|
4845
|
+
const output = new Uint8Array(expectedLength);
|
|
4846
|
+
let outPos = 0;
|
|
4847
|
+
const CLEAR_CODE = 256;
|
|
4848
|
+
const EOI_CODE = 257;
|
|
4849
|
+
let codeSize = 9;
|
|
4850
|
+
let nextCode = 258;
|
|
4851
|
+
let maxCode = 1 << codeSize;
|
|
4852
|
+
const dictPrefix = new Int32Array(4096);
|
|
4853
|
+
const dictSuffix = new Uint8Array(4096);
|
|
4854
|
+
const dictLength = new Uint16Array(4096);
|
|
4855
|
+
function resetDict() {
|
|
4856
|
+
codeSize = 9;
|
|
4857
|
+
nextCode = 258;
|
|
4858
|
+
maxCode = 1 << codeSize;
|
|
4859
|
+
for (let i = 0; i < 256; i++) {
|
|
4860
|
+
dictPrefix[i] = -1;
|
|
4861
|
+
dictSuffix[i] = i;
|
|
4862
|
+
dictLength[i] = 1;
|
|
4863
|
+
}
|
|
4864
|
+
}
|
|
4865
|
+
resetDict();
|
|
4866
|
+
let bitBuf = 0;
|
|
4867
|
+
let bitsAvail = 0;
|
|
4868
|
+
let bytePos = 0;
|
|
4869
|
+
function readCode() {
|
|
4870
|
+
while (bitsAvail < codeSize) if (bytePos < input.length) {
|
|
4871
|
+
bitBuf |= input[bytePos] << bitsAvail;
|
|
4872
|
+
bytePos++;
|
|
4873
|
+
bitsAvail += 8;
|
|
4874
|
+
} else return EOI_CODE;
|
|
4875
|
+
const code = bitBuf & (1 << codeSize) - 1;
|
|
4876
|
+
bitBuf >>>= codeSize;
|
|
4877
|
+
bitsAvail -= codeSize;
|
|
4878
|
+
return code;
|
|
4879
|
+
}
|
|
4880
|
+
function outputString(code) {
|
|
4881
|
+
const len = dictLength[code];
|
|
4882
|
+
let pos = outPos + len - 1;
|
|
4883
|
+
let c = code;
|
|
4884
|
+
while (c >= 0 && pos >= outPos) {
|
|
4885
|
+
if (pos < expectedLength) output[pos] = dictSuffix[c];
|
|
4886
|
+
pos--;
|
|
4887
|
+
c = dictPrefix[c];
|
|
4888
|
+
}
|
|
4889
|
+
outPos += len;
|
|
4890
|
+
}
|
|
4891
|
+
function firstByte(code) {
|
|
4892
|
+
let c = code;
|
|
4893
|
+
while (dictPrefix[c] >= 0) c = dictPrefix[c];
|
|
4894
|
+
return dictSuffix[c];
|
|
4895
|
+
}
|
|
4896
|
+
let oldCode = -1;
|
|
4897
|
+
while (outPos < expectedLength) {
|
|
4898
|
+
const code = readCode();
|
|
4899
|
+
if (code === EOI_CODE) break;
|
|
4900
|
+
if (code === CLEAR_CODE) {
|
|
4901
|
+
resetDict();
|
|
4902
|
+
oldCode = -1;
|
|
4903
|
+
continue;
|
|
4904
|
+
}
|
|
4905
|
+
if (oldCode === -1) {
|
|
4906
|
+
outputString(code);
|
|
4907
|
+
oldCode = code;
|
|
4908
|
+
continue;
|
|
4909
|
+
}
|
|
4910
|
+
if (code < nextCode) {
|
|
4911
|
+
outputString(code);
|
|
4912
|
+
if (nextCode < 4096) {
|
|
4913
|
+
dictPrefix[nextCode] = oldCode;
|
|
4914
|
+
dictSuffix[nextCode] = firstByte(code);
|
|
4915
|
+
dictLength[nextCode] = dictLength[oldCode] + 1;
|
|
4916
|
+
nextCode++;
|
|
4917
|
+
}
|
|
4918
|
+
} else {
|
|
4919
|
+
const fb = firstByte(oldCode);
|
|
4920
|
+
if (nextCode < 4096) {
|
|
4921
|
+
dictPrefix[nextCode] = oldCode;
|
|
4922
|
+
dictSuffix[nextCode] = fb;
|
|
4923
|
+
dictLength[nextCode] = dictLength[oldCode] + 1;
|
|
4924
|
+
nextCode++;
|
|
4925
|
+
}
|
|
4926
|
+
outputString(code);
|
|
4927
|
+
}
|
|
4928
|
+
if (nextCode >= maxCode && codeSize < 12) {
|
|
4929
|
+
codeSize++;
|
|
4930
|
+
maxCode = 1 << codeSize;
|
|
4931
|
+
}
|
|
4932
|
+
oldCode = code;
|
|
4933
|
+
}
|
|
4934
|
+
return output.slice(0, Math.min(outPos, expectedLength));
|
|
4935
|
+
}
|
|
4936
|
+
/** Deflate decompression (compression=8). */
|
|
4937
|
+
function decompressDeflate(input, expectedLength) {
|
|
4938
|
+
return (0, fflate.inflateSync)(input).slice(0, expectedLength);
|
|
4939
|
+
}
|
|
4940
|
+
/** Convert 1-bit data to 8-bit (0 or 255). */
|
|
4941
|
+
function normalize1bit(data, width, height, whiteIsZero) {
|
|
4942
|
+
const output = new Uint8Array(width * height);
|
|
4943
|
+
for (let row = 0; row < height; row++) for (let col = 0; col < width; col++) {
|
|
4944
|
+
const byteIdx = row * Math.ceil(width / 8) + (col >> 3);
|
|
4945
|
+
const bitIdx = 7 - (col & 7);
|
|
4946
|
+
const bit = data[byteIdx] >> bitIdx & 1;
|
|
4947
|
+
const val = whiteIsZero ? bit === 0 ? 255 : 0 : bit === 1 ? 255 : 0;
|
|
4948
|
+
output[row * width + col] = val;
|
|
4949
|
+
}
|
|
4950
|
+
return output;
|
|
4951
|
+
}
|
|
4952
|
+
/** Convert 4-bit data to 8-bit. */
|
|
4953
|
+
function normalize4bit(data, width, height, whiteIsZero) {
|
|
4954
|
+
const output = new Uint8Array(width * height);
|
|
4955
|
+
for (let row = 0; row < height; row++) for (let col = 0; col < width; col++) {
|
|
4956
|
+
const byteIdx = row * Math.ceil(width / 2) + (col >> 1);
|
|
4957
|
+
let val = ((col & 1) === 0 ? data[byteIdx] >> 4 & 15 : data[byteIdx] & 15) * 255 / 15 | 0;
|
|
4958
|
+
if (whiteIsZero) val = 255 - val;
|
|
4959
|
+
output[row * width + col] = val;
|
|
4960
|
+
}
|
|
4961
|
+
return output;
|
|
4962
|
+
}
|
|
4963
|
+
/** Convert 16-bit data to 8-bit by keeping the high byte. */
|
|
4964
|
+
function normalize16bit(data, numSamples, littleEndian) {
|
|
4965
|
+
const output = new Uint8Array(numSamples);
|
|
4966
|
+
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
4967
|
+
for (let i = 0; i < numSamples; i++) output[i] = view.getUint16(i * 2, littleEndian) >> 8 & 255;
|
|
4968
|
+
return output;
|
|
4969
|
+
}
|
|
4970
|
+
/**
|
|
4971
|
+
* Decode JPEG strip data. For JPEG-in-TIFF, each strip may be a complete
|
|
4972
|
+
* JPEG or the tables may be stored separately in the JPEGTables tag.
|
|
4973
|
+
*/
|
|
4974
|
+
function decodeJpegStrip(stripData, jpegTables, _width, stripHeight, samplesPerPixel) {
|
|
4975
|
+
let fullJpeg;
|
|
4976
|
+
if (jpegTables && jpegTables.length > 4) {
|
|
4977
|
+
const tableData = jpegTables.slice(2, jpegTables.length - 2);
|
|
4978
|
+
const stripBody = stripData.slice(2);
|
|
4979
|
+
fullJpeg = new Uint8Array(2 + tableData.length + stripBody.length);
|
|
4980
|
+
fullJpeg[0] = 255;
|
|
4981
|
+
fullJpeg[1] = 216;
|
|
4982
|
+
fullJpeg.set(tableData, 2);
|
|
4983
|
+
fullJpeg.set(stripBody, 2 + tableData.length);
|
|
4984
|
+
} else fullJpeg = stripData;
|
|
4985
|
+
throw new Error("TIFF JPEG-in-TIFF compression: JPEG strip decoding requires a JPEG decoder. This is supported when running with the JPEG WASM module initialized.");
|
|
4986
|
+
}
|
|
4987
|
+
/**
|
|
4988
|
+
* Get the number of pages in a multi-page TIFF.
|
|
4989
|
+
*
|
|
4990
|
+
* @param data Raw TIFF bytes.
|
|
4991
|
+
* @returns Number of IFDs (pages).
|
|
4992
|
+
*/
|
|
4993
|
+
function getTiffPageCount(data) {
|
|
4994
|
+
if (!isTiff(data)) throw new Error("TIFF: invalid TIFF header");
|
|
4995
|
+
const littleEndian = data[0] === 73;
|
|
4996
|
+
let offset = new TiffReader(data, littleEndian).u32(4);
|
|
4997
|
+
let count = 0;
|
|
4998
|
+
while (offset !== 0 && offset < data.length) {
|
|
4999
|
+
count++;
|
|
5000
|
+
offset = getNextIfdOffset(data, offset, littleEndian);
|
|
5001
|
+
}
|
|
5002
|
+
return count;
|
|
5003
|
+
}
|
|
5004
|
+
/**
|
|
5005
|
+
* Decode a specific page from a multi-page TIFF.
|
|
5006
|
+
*
|
|
5007
|
+
* @param data Raw TIFF bytes.
|
|
5008
|
+
* @param pageIndex 0-based page index.
|
|
5009
|
+
* @returns Decoded image.
|
|
5010
|
+
*/
|
|
5011
|
+
function decodeTiffPage(data, pageIndex) {
|
|
5012
|
+
return decodeTiff(data, { page: pageIndex });
|
|
5013
|
+
}
|
|
5014
|
+
/**
|
|
5015
|
+
* Decode all pages from a multi-page TIFF.
|
|
5016
|
+
*
|
|
5017
|
+
* @param data Raw TIFF bytes.
|
|
5018
|
+
* @returns Array of decoded images.
|
|
5019
|
+
*/
|
|
5020
|
+
function decodeTiffAll(data) {
|
|
5021
|
+
const pageCount = getTiffPageCount(data);
|
|
5022
|
+
const images = [];
|
|
5023
|
+
for (let i = 0; i < pageCount; i++) images.push(decodeTiffPage(data, i));
|
|
5024
|
+
return images;
|
|
5025
|
+
}
|
|
5026
|
+
/**
|
|
5027
|
+
* Decode a TIFF image.
|
|
5028
|
+
*
|
|
5029
|
+
* @param data Raw TIFF bytes.
|
|
5030
|
+
* @param options Decode options (page selection).
|
|
5031
|
+
* @returns Decoded image data.
|
|
5032
|
+
*/
|
|
5033
|
+
function decodeTiff(data, options) {
|
|
5034
|
+
if (!isTiff(data)) throw new Error("TIFF: invalid TIFF header (expected II or MM byte order marker + 42)");
|
|
5035
|
+
const littleEndian = data[0] === 73;
|
|
5036
|
+
const entries = parseTiffIfd(data, findIfdAtPage(data, littleEndian, new TiffReader(data, littleEndian).u32(4), options?.page ?? 0), littleEndian);
|
|
5037
|
+
const width = getTag(entries, TAG_IMAGE_WIDTH);
|
|
5038
|
+
const height = getTag(entries, TAG_IMAGE_HEIGHT);
|
|
5039
|
+
const compression = getTag(entries, TAG_COMPRESSION) ?? COMPRESS_NONE;
|
|
5040
|
+
const photometric = getTag(entries, TAG_PHOTOMETRIC) ?? 1;
|
|
5041
|
+
const bitsPerSample = getTag(entries, TAG_BITS_PER_SAMPLE) ?? 8;
|
|
5042
|
+
const samplesPerPixel = getTag(entries, TAG_SAMPLES_PER_PIXEL) ?? 1;
|
|
5043
|
+
const rowsPerStrip = getTag(entries, TAG_ROWS_PER_STRIP) ?? height ?? 0;
|
|
5044
|
+
const stripOffsets = getTagValues(entries, TAG_STRIP_OFFSETS);
|
|
5045
|
+
const stripByteCounts = getTagValues(entries, TAG_STRIP_BYTE_COUNTS);
|
|
5046
|
+
if (width === void 0 || height === void 0) throw new Error("TIFF: missing ImageWidth or ImageHeight tag");
|
|
5047
|
+
if (!stripOffsets || stripOffsets.length === 0) throw new Error("TIFF: missing StripOffsets tag");
|
|
5048
|
+
let jpegTables;
|
|
5049
|
+
const jpegTablesEntry = entries.find((e) => e.tag === TAG_JPEG_TABLES);
|
|
5050
|
+
if (jpegTablesEntry) {
|
|
5051
|
+
const tablesOffset = jpegTablesEntry.values[0];
|
|
5052
|
+
if (jpegTablesEntry.type === 7 && jpegTablesEntry.count > 4) jpegTables = data.slice(tablesOffset, tablesOffset + jpegTablesEntry.count);
|
|
5053
|
+
}
|
|
5054
|
+
const bytesPerRow = Math.ceil(width * samplesPerPixel * bitsPerSample / 8);
|
|
5055
|
+
const numStrips = stripOffsets.length;
|
|
5056
|
+
const rawData = new Uint8Array(height * bytesPerRow);
|
|
5057
|
+
let outOffset = 0;
|
|
5058
|
+
for (let strip = 0; strip < numStrips; strip++) {
|
|
5059
|
+
const stripOffset = stripOffsets[strip];
|
|
5060
|
+
const stripByteCount = stripByteCounts?.[strip] ?? bytesPerRow * rowsPerStrip;
|
|
5061
|
+
const stripRows = Math.min(rowsPerStrip, height - strip * rowsPerStrip);
|
|
5062
|
+
const expectedStripBytes = stripRows * bytesPerRow;
|
|
5063
|
+
if (stripOffset + stripByteCount > data.length) break;
|
|
5064
|
+
const compressedData = data.slice(stripOffset, stripOffset + stripByteCount);
|
|
5065
|
+
let decompressed;
|
|
5066
|
+
switch (compression) {
|
|
5067
|
+
case COMPRESS_NONE:
|
|
5068
|
+
decompressed = compressedData;
|
|
5069
|
+
break;
|
|
5070
|
+
case COMPRESS_PACKBITS:
|
|
5071
|
+
decompressed = decompressPackBits(compressedData, expectedStripBytes);
|
|
5072
|
+
break;
|
|
5073
|
+
case COMPRESS_LZW:
|
|
5074
|
+
decompressed = decompressLzw(compressedData, expectedStripBytes);
|
|
5075
|
+
break;
|
|
5076
|
+
case COMPRESS_DEFLATE:
|
|
5077
|
+
decompressed = decompressDeflate(compressedData, expectedStripBytes);
|
|
5078
|
+
break;
|
|
5079
|
+
case COMPRESS_JPEG:
|
|
5080
|
+
case COMPRESS_JPEG_OLD:
|
|
5081
|
+
decompressed = decodeJpegStrip(compressedData, jpegTables, width, stripRows, samplesPerPixel);
|
|
5082
|
+
break;
|
|
5083
|
+
default: throw new Error(`TIFF: unsupported compression type ${compression}`);
|
|
5084
|
+
}
|
|
5085
|
+
const copyLen = Math.min(decompressed.length, rawData.length - outOffset);
|
|
5086
|
+
rawData.set(decompressed.slice(0, copyLen), outOffset);
|
|
5087
|
+
outOffset += expectedStripBytes;
|
|
5088
|
+
}
|
|
5089
|
+
const whiteIsZero = photometric === 0;
|
|
5090
|
+
let pixels;
|
|
5091
|
+
if (bitsPerSample === 1 && samplesPerPixel === 1) pixels = normalize1bit(rawData, width, height, whiteIsZero);
|
|
5092
|
+
else if (bitsPerSample === 4 && samplesPerPixel === 1) pixels = normalize4bit(rawData, width, height, whiteIsZero);
|
|
5093
|
+
else if (bitsPerSample === 16) {
|
|
5094
|
+
pixels = normalize16bit(rawData, width * height * samplesPerPixel, littleEndian);
|
|
5095
|
+
if (whiteIsZero && samplesPerPixel === 1) for (let i = 0; i < pixels.length; i++) pixels[i] = 255 - pixels[i];
|
|
5096
|
+
} else if (bitsPerSample === 8) {
|
|
5097
|
+
pixels = rawData.slice(0, width * height * samplesPerPixel);
|
|
5098
|
+
if (whiteIsZero && samplesPerPixel === 1) for (let i = 0; i < pixels.length; i++) pixels[i] = 255 - pixels[i];
|
|
5099
|
+
} else throw new Error(`TIFF: unsupported BitsPerSample ${bitsPerSample}`);
|
|
5100
|
+
return {
|
|
5101
|
+
width,
|
|
5102
|
+
height,
|
|
5103
|
+
pixels,
|
|
5104
|
+
channels: samplesPerPixel === 1 ? 1 : samplesPerPixel === 3 ? 3 : samplesPerPixel === 4 ? 4 : 1,
|
|
5105
|
+
bitsPerSample
|
|
5106
|
+
};
|
|
5107
|
+
}
|
|
5108
|
+
|
|
3585
5109
|
//#endregion
|
|
3586
5110
|
//#region src/parser/parseError.ts
|
|
3587
5111
|
/**
|
|
@@ -4829,10 +6353,10 @@ async function tryLoadLibdeflate() {
|
|
|
4829
6353
|
if (libdeflateAttempted) return libdeflateEngine;
|
|
4830
6354
|
libdeflateAttempted = true;
|
|
4831
6355
|
try {
|
|
4832
|
-
const { LibdeflateWasm: LibdeflateCtor, initDeflateWasm } = await Promise.resolve().then(() => require("./libdeflateWasm-
|
|
6356
|
+
const { LibdeflateWasm: LibdeflateCtor, initDeflateWasm } = await Promise.resolve().then(() => require("./libdeflateWasm-DLw-I1CY.cjs")).then((n) => n.libdeflateWasm_exports);
|
|
4833
6357
|
let customBytes;
|
|
4834
6358
|
try {
|
|
4835
|
-
const { getWasmLoaderConfig } = await Promise.resolve().then(() => require("./loader-
|
|
6359
|
+
const { getWasmLoaderConfig } = await Promise.resolve().then(() => require("./loader-DYCH3n7d.cjs")).then((n) => n.loader_exports);
|
|
4836
6360
|
customBytes = getWasmLoaderConfig().moduleBytes?.["libdeflate"];
|
|
4837
6361
|
} catch {}
|
|
4838
6362
|
await initDeflateWasm(customBytes);
|
|
@@ -7889,7 +9413,7 @@ var PdfDocumentParser = class {
|
|
|
7889
9413
|
for (let i = 0; i + 1 < headerTokens.length && objEntries.length < n; i += 2) {
|
|
7890
9414
|
const objNum = parseInt(headerTokens[i], 10);
|
|
7891
9415
|
const offset = parseInt(headerTokens[i + 1], 10);
|
|
7892
|
-
if (!isNaN(objNum) && !isNaN(offset)) objEntries.push({
|
|
9416
|
+
if (!Number.isNaN(objNum) && !Number.isNaN(offset)) objEntries.push({
|
|
7893
9417
|
objNum,
|
|
7894
9418
|
offset
|
|
7895
9419
|
});
|
|
@@ -8171,7 +9695,7 @@ var PdfDocumentParser = class {
|
|
|
8171
9695
|
const name = key.startsWith("/") ? key.slice(1) : key;
|
|
8172
9696
|
if (name.startsWith(prefix)) {
|
|
8173
9697
|
const num = parseInt(name.slice(prefix.length), 10);
|
|
8174
|
-
return isNaN(num) ? 0 : num;
|
|
9698
|
+
return Number.isNaN(num) ? 0 : num;
|
|
8175
9699
|
}
|
|
8176
9700
|
return 0;
|
|
8177
9701
|
}).reduce((max, n) => Math.max(max, n), 0);
|
|
@@ -8885,12 +10409,12 @@ function parseXmpMetadata(xmpString) {
|
|
|
8885
10409
|
const createDate = extractSimpleValue(xmpString, "xmp:CreateDate");
|
|
8886
10410
|
if (createDate !== void 0) {
|
|
8887
10411
|
const parsed = new Date(createDate);
|
|
8888
|
-
if (!isNaN(parsed.getTime())) result.creationDate = parsed;
|
|
10412
|
+
if (!Number.isNaN(parsed.getTime())) result.creationDate = parsed;
|
|
8889
10413
|
}
|
|
8890
10414
|
const modifyDate = extractSimpleValue(xmpString, "xmp:ModifyDate");
|
|
8891
10415
|
if (modifyDate !== void 0) {
|
|
8892
10416
|
const parsed = new Date(modifyDate);
|
|
8893
|
-
if (!isNaN(parsed.getTime())) result.modDate = parsed;
|
|
10417
|
+
if (!Number.isNaN(parsed.getTime())) result.modDate = parsed;
|
|
8894
10418
|
}
|
|
8895
10419
|
return result;
|
|
8896
10420
|
}
|
|
@@ -10275,7 +11799,7 @@ const decoder$2 = new TextDecoder("latin1");
|
|
|
10275
11799
|
* The /ByteRange values are placeholders that will be updated after
|
|
10276
11800
|
* the final byte positions are known.
|
|
10277
11801
|
*/
|
|
10278
|
-
function buildSignatureDictString(placeholderSize, fieldName, signingDate) {
|
|
11802
|
+
function buildSignatureDictString(placeholderSize, fieldName, signingDate, mdpReference) {
|
|
10279
11803
|
const contentsHex = "0".repeat(placeholderSize * 2);
|
|
10280
11804
|
const byteRangePlaceholder = "0000000000 0000000000 0000000000 0000000000";
|
|
10281
11805
|
let dict = "";
|
|
@@ -10285,6 +11809,7 @@ function buildSignatureDictString(placeholderSize, fieldName, signingDate) {
|
|
|
10285
11809
|
dict += ` /ByteRange [${byteRangePlaceholder}]`;
|
|
10286
11810
|
dict += ` /Contents <${contentsHex}>`;
|
|
10287
11811
|
if (signingDate !== void 0) dict += ` /M (D:${formatPdfDate(signingDate)})`;
|
|
11812
|
+
if (mdpReference !== void 0) dict += mdpReference;
|
|
10288
11813
|
dict += " >>";
|
|
10289
11814
|
return dict;
|
|
10290
11815
|
}
|
|
@@ -10393,16 +11918,16 @@ function n$1(value) {
|
|
|
10393
11918
|
if (Number.isInteger(value)) return value.toString();
|
|
10394
11919
|
return value.toFixed(6).replace(/\.?0+$/, "");
|
|
10395
11920
|
}
|
|
10396
|
-
function prepareForSigning(pdfBytes, signatureFieldName, placeholderSize = 8192, appearance) {
|
|
11921
|
+
function prepareForSigning(pdfBytes, signatureFieldName, placeholderSize = 8192, appearance, mdpPermission, fieldLock) {
|
|
10397
11922
|
const pdfStr = decoder$2.decode(pdfBytes);
|
|
10398
11923
|
const startxrefIdx = pdfStr.lastIndexOf("startxref");
|
|
10399
11924
|
if (startxrefIdx === -1) throw new Error("Cannot find startxref in PDF — file may be corrupted");
|
|
10400
11925
|
const xrefOffsetMatch = pdfStr.slice(startxrefIdx + 9).trim().match(/^(\d+)/);
|
|
10401
11926
|
if (!xrefOffsetMatch) throw new Error("Cannot parse xref offset from startxref");
|
|
10402
11927
|
const prevXrefOffset = parseInt(xrefOffsetMatch[1], 10);
|
|
10403
|
-
const
|
|
10404
|
-
if (
|
|
10405
|
-
const originalSize = parseInt(
|
|
11928
|
+
const sizeMatches = [...pdfStr.matchAll(/\/Size\s+(\d+)/g)];
|
|
11929
|
+
if (sizeMatches.length === 0) throw new Error("Cannot find /Size in PDF trailer");
|
|
11930
|
+
const originalSize = parseInt(sizeMatches[sizeMatches.length - 1][1], 10);
|
|
10406
11931
|
const rootMatch = pdfStr.match(/\/Root\s+(\d+)\s+(\d+)\s+R/);
|
|
10407
11932
|
if (!rootMatch) throw new Error("Cannot find /Root in PDF trailer");
|
|
10408
11933
|
const rootObjNum = parseInt(rootMatch[1], 10);
|
|
@@ -10416,7 +11941,9 @@ function prepareForSigning(pdfBytes, signatureFieldName, placeholderSize = 8192,
|
|
|
10416
11941
|
apStreamObjNum = newSize;
|
|
10417
11942
|
newSize++;
|
|
10418
11943
|
}
|
|
10419
|
-
|
|
11944
|
+
let mdpReference;
|
|
11945
|
+
if (mdpPermission !== void 0 && mdpPermission >= 1 && mdpPermission <= 3) mdpReference = ` /Reference [<< /Type /SigRef /TransformMethod /DocMDP /TransformParams << /Type /TransformParams /P ${mdpPermission} /V /1.2 >> >>]`;
|
|
11946
|
+
const sigDictStr = buildSignatureDictString(placeholderSize, signatureFieldName, void 0, mdpReference);
|
|
10420
11947
|
let rectStr = "0 0 0 0";
|
|
10421
11948
|
if (appearance) {
|
|
10422
11949
|
const [x, y, w, h] = appearance.rect;
|
|
@@ -10424,6 +11951,14 @@ function prepareForSigning(pdfBytes, signatureFieldName, placeholderSize = 8192,
|
|
|
10424
11951
|
}
|
|
10425
11952
|
let sigFieldDict = `<< /Type /Annot /Subtype /Widget /FT /Sig /T (${signatureFieldName}) /V ${sigValueObjNum} 0 R /F 132 /Rect [${rectStr}]`;
|
|
10426
11953
|
if (appearance && apStreamObjNum >= 0) sigFieldDict += ` /AP << /N ${apStreamObjNum} 0 R >>`;
|
|
11954
|
+
if (fieldLock !== void 0) {
|
|
11955
|
+
sigFieldDict += ` /Lock << /Type /SigFieldLock /Action /${fieldLock.action}`;
|
|
11956
|
+
if (fieldLock.action !== "All" && fieldLock.fields && fieldLock.fields.length > 0) {
|
|
11957
|
+
const fieldEntries = fieldLock.fields.map((f) => `(${escapePdfString(f)})`).join(" ");
|
|
11958
|
+
sigFieldDict += ` /Fields [${fieldEntries}]`;
|
|
11959
|
+
}
|
|
11960
|
+
sigFieldDict += " >>";
|
|
11961
|
+
}
|
|
10427
11962
|
sigFieldDict += " >>";
|
|
10428
11963
|
let appendix = "\n";
|
|
10429
11964
|
const objOffsets = /* @__PURE__ */ new Map();
|
|
@@ -12129,6 +13664,99 @@ function parseJpegDimensions(data) {
|
|
|
12129
13664
|
throw new Error("Invalid JPEG: SOF marker not found");
|
|
12130
13665
|
}
|
|
12131
13666
|
|
|
13667
|
+
//#endregion
|
|
13668
|
+
//#region src/core/pageLabels.ts
|
|
13669
|
+
/** Map from user-facing style names to PDF /S name values. */
|
|
13670
|
+
const styleToPdfName = {
|
|
13671
|
+
decimal: "D",
|
|
13672
|
+
roman: "r",
|
|
13673
|
+
Roman: "R",
|
|
13674
|
+
alpha: "a",
|
|
13675
|
+
Alpha: "A"
|
|
13676
|
+
};
|
|
13677
|
+
/**
|
|
13678
|
+
* Symbol used to store page labels on the PdfDocument instance.
|
|
13679
|
+
* This avoids modifying the PdfDocument class while keeping state
|
|
13680
|
+
* associated with the document.
|
|
13681
|
+
*/
|
|
13682
|
+
const PAGE_LABELS_KEY = Symbol.for("modern-pdf-lib:pageLabels");
|
|
13683
|
+
/**
|
|
13684
|
+
* Set the page label ranges for the document.
|
|
13685
|
+
*
|
|
13686
|
+
* Each entry in the `labels` array defines a contiguous range of pages
|
|
13687
|
+
* that share a numbering style. Ranges must be sorted by `startPage`
|
|
13688
|
+
* in ascending order.
|
|
13689
|
+
*
|
|
13690
|
+
* @param doc The document to set page labels on.
|
|
13691
|
+
* @param labels An array of label range definitions.
|
|
13692
|
+
* @throws If `labels` is empty or ranges are not sorted.
|
|
13693
|
+
*/
|
|
13694
|
+
function setPageLabels(doc, labels) {
|
|
13695
|
+
if (labels.length === 0) throw new Error("Page labels array must not be empty");
|
|
13696
|
+
for (let i = 1; i < labels.length; i++) if (labels[i].startPage <= labels[i - 1].startPage) throw new Error(`Page label ranges must be sorted by startPage in ascending order. Got startPage ${labels[i].startPage} after ${labels[i - 1].startPage}.`);
|
|
13697
|
+
const store = doc;
|
|
13698
|
+
store[PAGE_LABELS_KEY] = labels.map((r) => ({ ...r }));
|
|
13699
|
+
}
|
|
13700
|
+
/**
|
|
13701
|
+
* Get the current page label ranges for the document.
|
|
13702
|
+
*
|
|
13703
|
+
* Returns `undefined` if no page labels have been set.
|
|
13704
|
+
*
|
|
13705
|
+
* @param doc The document to read page labels from.
|
|
13706
|
+
* @returns The page label ranges, or `undefined`.
|
|
13707
|
+
*/
|
|
13708
|
+
function getPageLabels(doc) {
|
|
13709
|
+
const labels = doc[PAGE_LABELS_KEY];
|
|
13710
|
+
if (labels === void 0) return void 0;
|
|
13711
|
+
return labels.map((r) => ({ ...r }));
|
|
13712
|
+
}
|
|
13713
|
+
/**
|
|
13714
|
+
* Remove all page labels from the document.
|
|
13715
|
+
*
|
|
13716
|
+
* @param doc The document to clear page labels from.
|
|
13717
|
+
*/
|
|
13718
|
+
function removePageLabels(doc) {
|
|
13719
|
+
const store = doc;
|
|
13720
|
+
store[PAGE_LABELS_KEY] = void 0;
|
|
13721
|
+
}
|
|
13722
|
+
/**
|
|
13723
|
+
* Build the `/PageLabels` number tree dictionary for inclusion in the
|
|
13724
|
+
* document catalog.
|
|
13725
|
+
*
|
|
13726
|
+
* This is called during document serialization (by the save pipeline)
|
|
13727
|
+
* to produce the PDF objects that represent the page label scheme.
|
|
13728
|
+
*
|
|
13729
|
+
* The number tree format (PDF 1.7 §7.9.7) uses a `/Nums` array of
|
|
13730
|
+
* alternating page-index / label-dict pairs:
|
|
13731
|
+
*
|
|
13732
|
+
* ```
|
|
13733
|
+
* /PageLabels << /Nums [
|
|
13734
|
+
* 0 << /S /r >>
|
|
13735
|
+
* 4 << /S /D /St 1 >>
|
|
13736
|
+
* ] >>
|
|
13737
|
+
* ```
|
|
13738
|
+
*
|
|
13739
|
+
* @param doc The document to read page label state from.
|
|
13740
|
+
* @returns The number tree entries as `[pageIndex, labelDict]` pairs,
|
|
13741
|
+
* or `undefined` if no labels are set.
|
|
13742
|
+
*
|
|
13743
|
+
* @internal
|
|
13744
|
+
*/
|
|
13745
|
+
function getPageLabelEntries(doc) {
|
|
13746
|
+
return doc[PAGE_LABELS_KEY];
|
|
13747
|
+
}
|
|
13748
|
+
/**
|
|
13749
|
+
* Map a {@link PageLabelStyle} to its PDF `/S` name value.
|
|
13750
|
+
*
|
|
13751
|
+
* @param style The user-facing style name.
|
|
13752
|
+
* @returns The PDF name value (without the leading `/`).
|
|
13753
|
+
*
|
|
13754
|
+
* @internal
|
|
13755
|
+
*/
|
|
13756
|
+
function styleToPdf(style) {
|
|
13757
|
+
return styleToPdfName[style];
|
|
13758
|
+
}
|
|
13759
|
+
|
|
12132
13760
|
//#endregion
|
|
12133
13761
|
//#region src/core/pdfDocument.ts
|
|
12134
13762
|
/**
|
|
@@ -12822,12 +14450,232 @@ var PdfDocument = class PdfDocument {
|
|
|
12822
14450
|
return imageRef;
|
|
12823
14451
|
}
|
|
12824
14452
|
/**
|
|
14453
|
+
* Embed a WebP image.
|
|
14454
|
+
*
|
|
14455
|
+
* WebP cannot be directly embedded in PDF. This method decodes the
|
|
14456
|
+
* WebP image to raw pixels (VP8/lossy, VP8L/lossless, or VP8+ALPH),
|
|
14457
|
+
* then embeds as a FlateDecode image XObject. If the WebP has
|
|
14458
|
+
* transparency, the alpha channel is embedded as a soft mask.
|
|
14459
|
+
*
|
|
14460
|
+
* @param webpData Raw WebP file bytes as a `Uint8Array` or `ArrayBuffer`.
|
|
14461
|
+
* @returns An {@link ImageRef}.
|
|
14462
|
+
*/
|
|
14463
|
+
embedWebP(webpData) {
|
|
14464
|
+
const decoded = decodeWebP(webpData instanceof ArrayBuffer ? new Uint8Array(webpData) : webpData);
|
|
14465
|
+
this.imageCounter++;
|
|
14466
|
+
const resourceName = `Im${this.imageCounter}`;
|
|
14467
|
+
const dict = new require_pdfObjects.PdfDict();
|
|
14468
|
+
dict.set("/Type", require_pdfObjects.PdfName.of("XObject"));
|
|
14469
|
+
dict.set("/Subtype", require_pdfObjects.PdfName.of("Image"));
|
|
14470
|
+
dict.set("/Width", require_pdfObjects.PdfNumber.of(decoded.width));
|
|
14471
|
+
dict.set("/Height", require_pdfObjects.PdfNumber.of(decoded.height));
|
|
14472
|
+
dict.set("/BitsPerComponent", require_pdfObjects.PdfNumber.of(8));
|
|
14473
|
+
if (decoded.hasAlpha) {
|
|
14474
|
+
const pixelCount = decoded.width * decoded.height;
|
|
14475
|
+
const rgb = new Uint8Array(pixelCount * 3);
|
|
14476
|
+
const alpha = new Uint8Array(pixelCount);
|
|
14477
|
+
for (let i = 0; i < pixelCount; i++) {
|
|
14478
|
+
const srcIdx = i * 4;
|
|
14479
|
+
const dstIdx = i * 3;
|
|
14480
|
+
rgb[dstIdx] = decoded.pixels[srcIdx];
|
|
14481
|
+
rgb[dstIdx + 1] = decoded.pixels[srcIdx + 1];
|
|
14482
|
+
rgb[dstIdx + 2] = decoded.pixels[srcIdx + 2];
|
|
14483
|
+
alpha[i] = decoded.pixels[srcIdx + 3];
|
|
14484
|
+
}
|
|
14485
|
+
const compressedRgb = (0, fflate.deflateSync)(rgb, { level: 6 });
|
|
14486
|
+
const compressedAlpha = (0, fflate.deflateSync)(alpha, { level: 6 });
|
|
14487
|
+
dict.set("/ColorSpace", require_pdfObjects.PdfName.of("DeviceRGB"));
|
|
14488
|
+
dict.set("/Filter", require_pdfObjects.PdfName.of("FlateDecode"));
|
|
14489
|
+
dict.set("/Length", require_pdfObjects.PdfNumber.of(compressedRgb.length));
|
|
14490
|
+
const smaskDict = new require_pdfObjects.PdfDict();
|
|
14491
|
+
smaskDict.set("/Type", require_pdfObjects.PdfName.of("XObject"));
|
|
14492
|
+
smaskDict.set("/Subtype", require_pdfObjects.PdfName.of("Image"));
|
|
14493
|
+
smaskDict.set("/Width", require_pdfObjects.PdfNumber.of(decoded.width));
|
|
14494
|
+
smaskDict.set("/Height", require_pdfObjects.PdfNumber.of(decoded.height));
|
|
14495
|
+
smaskDict.set("/BitsPerComponent", require_pdfObjects.PdfNumber.of(8));
|
|
14496
|
+
smaskDict.set("/ColorSpace", require_pdfObjects.PdfName.of("DeviceGray"));
|
|
14497
|
+
smaskDict.set("/Filter", require_pdfObjects.PdfName.of("FlateDecode"));
|
|
14498
|
+
smaskDict.set("/Length", require_pdfObjects.PdfNumber.of(compressedAlpha.length));
|
|
14499
|
+
const smaskStream = new require_pdfObjects.PdfStream(smaskDict, compressedAlpha);
|
|
14500
|
+
const smaskRef = this.registry.register(smaskStream);
|
|
14501
|
+
dict.set("/SMask", smaskRef);
|
|
14502
|
+
const stream = new require_pdfObjects.PdfStream(dict, compressedRgb);
|
|
14503
|
+
const ref = this.registry.register(stream);
|
|
14504
|
+
const w = decoded.width;
|
|
14505
|
+
const h = decoded.height;
|
|
14506
|
+
const imageRef = {
|
|
14507
|
+
name: resourceName,
|
|
14508
|
+
ref,
|
|
14509
|
+
width: w,
|
|
14510
|
+
height: h,
|
|
14511
|
+
scale(factor) {
|
|
14512
|
+
return {
|
|
14513
|
+
width: w * factor,
|
|
14514
|
+
height: h * factor
|
|
14515
|
+
};
|
|
14516
|
+
},
|
|
14517
|
+
scaleToFit(maxW, maxH) {
|
|
14518
|
+
const ratio = Math.min(maxW / w, maxH / h);
|
|
14519
|
+
return {
|
|
14520
|
+
width: w * ratio,
|
|
14521
|
+
height: h * ratio
|
|
14522
|
+
};
|
|
14523
|
+
}
|
|
14524
|
+
};
|
|
14525
|
+
this.embeddedImages.push(imageRef);
|
|
14526
|
+
return imageRef;
|
|
14527
|
+
}
|
|
14528
|
+
const compressedRgb = (0, fflate.deflateSync)(decoded.pixels, { level: 6 });
|
|
14529
|
+
dict.set("/ColorSpace", require_pdfObjects.PdfName.of("DeviceRGB"));
|
|
14530
|
+
dict.set("/Filter", require_pdfObjects.PdfName.of("FlateDecode"));
|
|
14531
|
+
dict.set("/Length", require_pdfObjects.PdfNumber.of(compressedRgb.length));
|
|
14532
|
+
const stream = new require_pdfObjects.PdfStream(dict, compressedRgb);
|
|
14533
|
+
const ref = this.registry.register(stream);
|
|
14534
|
+
const w = decoded.width;
|
|
14535
|
+
const h = decoded.height;
|
|
14536
|
+
const imageRef = {
|
|
14537
|
+
name: resourceName,
|
|
14538
|
+
ref,
|
|
14539
|
+
width: w,
|
|
14540
|
+
height: h,
|
|
14541
|
+
scale(factor) {
|
|
14542
|
+
return {
|
|
14543
|
+
width: w * factor,
|
|
14544
|
+
height: h * factor
|
|
14545
|
+
};
|
|
14546
|
+
},
|
|
14547
|
+
scaleToFit(maxW, maxH) {
|
|
14548
|
+
const ratio = Math.min(maxW / w, maxH / h);
|
|
14549
|
+
return {
|
|
14550
|
+
width: w * ratio,
|
|
14551
|
+
height: h * ratio
|
|
14552
|
+
};
|
|
14553
|
+
}
|
|
14554
|
+
};
|
|
14555
|
+
this.embeddedImages.push(imageRef);
|
|
14556
|
+
return imageRef;
|
|
14557
|
+
}
|
|
14558
|
+
/**
|
|
14559
|
+
* Embed a TIFF image.
|
|
14560
|
+
*
|
|
14561
|
+
* Decodes the TIFF image and creates a PDF image XObject with
|
|
14562
|
+
* FlateDecode compression. For multi-page TIFFs, a specific page
|
|
14563
|
+
* can be selected via options.
|
|
14564
|
+
*
|
|
14565
|
+
* @param tiffData Raw TIFF file bytes as a `Uint8Array` or `ArrayBuffer`.
|
|
14566
|
+
* @param options Options (e.g., `{ page: 0 }` for multi-page TIFFs).
|
|
14567
|
+
* @returns An {@link ImageRef}.
|
|
14568
|
+
*/
|
|
14569
|
+
embedTiff(tiffData, options) {
|
|
14570
|
+
const decoded = decodeTiff(tiffData instanceof ArrayBuffer ? new Uint8Array(tiffData) : tiffData, options?.page !== void 0 ? { page: options.page } : void 0);
|
|
14571
|
+
this.imageCounter++;
|
|
14572
|
+
const resourceName = `Im${this.imageCounter}`;
|
|
14573
|
+
const dict = new require_pdfObjects.PdfDict();
|
|
14574
|
+
dict.set("/Type", require_pdfObjects.PdfName.of("XObject"));
|
|
14575
|
+
dict.set("/Subtype", require_pdfObjects.PdfName.of("Image"));
|
|
14576
|
+
dict.set("/Width", require_pdfObjects.PdfNumber.of(decoded.width));
|
|
14577
|
+
dict.set("/Height", require_pdfObjects.PdfNumber.of(decoded.height));
|
|
14578
|
+
dict.set("/BitsPerComponent", require_pdfObjects.PdfNumber.of(8));
|
|
14579
|
+
if (decoded.channels === 4) {
|
|
14580
|
+
const pixelCount = decoded.width * decoded.height;
|
|
14581
|
+
const rgb = new Uint8Array(pixelCount * 3);
|
|
14582
|
+
const alpha = new Uint8Array(pixelCount);
|
|
14583
|
+
for (let i = 0; i < pixelCount; i++) {
|
|
14584
|
+
const srcIdx = i * 4;
|
|
14585
|
+
const dstIdx = i * 3;
|
|
14586
|
+
rgb[dstIdx] = decoded.pixels[srcIdx];
|
|
14587
|
+
rgb[dstIdx + 1] = decoded.pixels[srcIdx + 1];
|
|
14588
|
+
rgb[dstIdx + 2] = decoded.pixels[srcIdx + 2];
|
|
14589
|
+
alpha[i] = decoded.pixels[srcIdx + 3];
|
|
14590
|
+
}
|
|
14591
|
+
const compressedRgb = (0, fflate.deflateSync)(rgb, { level: 6 });
|
|
14592
|
+
const compressedAlpha = (0, fflate.deflateSync)(alpha, { level: 6 });
|
|
14593
|
+
dict.set("/ColorSpace", require_pdfObjects.PdfName.of("DeviceRGB"));
|
|
14594
|
+
dict.set("/Filter", require_pdfObjects.PdfName.of("FlateDecode"));
|
|
14595
|
+
dict.set("/Length", require_pdfObjects.PdfNumber.of(compressedRgb.length));
|
|
14596
|
+
const smaskDict = new require_pdfObjects.PdfDict();
|
|
14597
|
+
smaskDict.set("/Type", require_pdfObjects.PdfName.of("XObject"));
|
|
14598
|
+
smaskDict.set("/Subtype", require_pdfObjects.PdfName.of("Image"));
|
|
14599
|
+
smaskDict.set("/Width", require_pdfObjects.PdfNumber.of(decoded.width));
|
|
14600
|
+
smaskDict.set("/Height", require_pdfObjects.PdfNumber.of(decoded.height));
|
|
14601
|
+
smaskDict.set("/BitsPerComponent", require_pdfObjects.PdfNumber.of(8));
|
|
14602
|
+
smaskDict.set("/ColorSpace", require_pdfObjects.PdfName.of("DeviceGray"));
|
|
14603
|
+
smaskDict.set("/Filter", require_pdfObjects.PdfName.of("FlateDecode"));
|
|
14604
|
+
smaskDict.set("/Length", require_pdfObjects.PdfNumber.of(compressedAlpha.length));
|
|
14605
|
+
const smaskStream = new require_pdfObjects.PdfStream(smaskDict, compressedAlpha);
|
|
14606
|
+
const smaskRef = this.registry.register(smaskStream);
|
|
14607
|
+
dict.set("/SMask", smaskRef);
|
|
14608
|
+
const stream = new require_pdfObjects.PdfStream(dict, compressedRgb);
|
|
14609
|
+
const ref = this.registry.register(stream);
|
|
14610
|
+
const w = decoded.width;
|
|
14611
|
+
const h = decoded.height;
|
|
14612
|
+
const imageRef = {
|
|
14613
|
+
name: resourceName,
|
|
14614
|
+
ref,
|
|
14615
|
+
width: w,
|
|
14616
|
+
height: h,
|
|
14617
|
+
scale(factor) {
|
|
14618
|
+
return {
|
|
14619
|
+
width: w * factor,
|
|
14620
|
+
height: h * factor
|
|
14621
|
+
};
|
|
14622
|
+
},
|
|
14623
|
+
scaleToFit(maxW, maxH) {
|
|
14624
|
+
const ratio = Math.min(maxW / w, maxH / h);
|
|
14625
|
+
return {
|
|
14626
|
+
width: w * ratio,
|
|
14627
|
+
height: h * ratio
|
|
14628
|
+
};
|
|
14629
|
+
}
|
|
14630
|
+
};
|
|
14631
|
+
this.embeddedImages.push(imageRef);
|
|
14632
|
+
return imageRef;
|
|
14633
|
+
}
|
|
14634
|
+
const colorSpace = decoded.channels === 1 ? "DeviceGray" : "DeviceRGB";
|
|
14635
|
+
const compressed = (0, fflate.deflateSync)(decoded.pixels, { level: 6 });
|
|
14636
|
+
dict.set("/ColorSpace", require_pdfObjects.PdfName.of(colorSpace));
|
|
14637
|
+
dict.set("/Filter", require_pdfObjects.PdfName.of("FlateDecode"));
|
|
14638
|
+
dict.set("/Length", require_pdfObjects.PdfNumber.of(compressed.length));
|
|
14639
|
+
const stream = new require_pdfObjects.PdfStream(dict, compressed);
|
|
14640
|
+
const ref = this.registry.register(stream);
|
|
14641
|
+
const w = decoded.width;
|
|
14642
|
+
const h = decoded.height;
|
|
14643
|
+
const imageRef = {
|
|
14644
|
+
name: resourceName,
|
|
14645
|
+
ref,
|
|
14646
|
+
width: w,
|
|
14647
|
+
height: h,
|
|
14648
|
+
scale(factor) {
|
|
14649
|
+
return {
|
|
14650
|
+
width: w * factor,
|
|
14651
|
+
height: h * factor
|
|
14652
|
+
};
|
|
14653
|
+
},
|
|
14654
|
+
scaleToFit(maxW, maxH) {
|
|
14655
|
+
const ratio = Math.min(maxW / w, maxH / h);
|
|
14656
|
+
return {
|
|
14657
|
+
width: w * ratio,
|
|
14658
|
+
height: h * ratio
|
|
14659
|
+
};
|
|
14660
|
+
}
|
|
14661
|
+
};
|
|
14662
|
+
this.embeddedImages.push(imageRef);
|
|
14663
|
+
return imageRef;
|
|
14664
|
+
}
|
|
14665
|
+
/**
|
|
12825
14666
|
* Embed an image, auto-detecting the format from file headers.
|
|
12826
14667
|
*
|
|
12827
|
-
* Inspects the first bytes to determine
|
|
12828
|
-
* then delegates to
|
|
14668
|
+
* Inspects the first bytes to determine the image format (PNG, JPEG,
|
|
14669
|
+
* WebP, or TIFF), then delegates to the appropriate embedding method.
|
|
12829
14670
|
*
|
|
12830
|
-
*
|
|
14671
|
+
* Supported formats:
|
|
14672
|
+
* - **PNG**: `89 50 4E 47` — embedded via {@link embedPng}
|
|
14673
|
+
* - **JPEG**: `FF D8 FF` — embedded via {@link embedJpeg}
|
|
14674
|
+
* - **WebP**: `52 49 46 46` + `57 45 42 50` — embedded via {@link embedWebP}
|
|
14675
|
+
* - **TIFF LE**: `49 49 2A 00` — embedded via {@link embedTiff}
|
|
14676
|
+
* - **TIFF BE**: `4D 4D 00 2A` — embedded via {@link embedTiff}
|
|
14677
|
+
*
|
|
14678
|
+
* @param imageData Raw image file bytes (PNG, JPEG, WebP, or TIFF).
|
|
12831
14679
|
* @returns An {@link ImageRef} to pass to `page.drawImage()`.
|
|
12832
14680
|
* @throws If the image format cannot be detected.
|
|
12833
14681
|
*
|
|
@@ -12841,9 +14689,13 @@ var PdfDocument = class PdfDocument {
|
|
|
12841
14689
|
async embedImage(imageData) {
|
|
12842
14690
|
const data = imageData instanceof ArrayBuffer ? new Uint8Array(imageData) : imageData;
|
|
12843
14691
|
if (data.length < 4) throw new Error("Image data too short to detect format");
|
|
12844
|
-
|
|
12845
|
-
|
|
12846
|
-
|
|
14692
|
+
switch (detectImageFormat(data)) {
|
|
14693
|
+
case "png": return this.embedPng(data);
|
|
14694
|
+
case "jpeg": return this.embedJpeg(data);
|
|
14695
|
+
case "webp": return this.embedWebP(data);
|
|
14696
|
+
case "tiff": return this.embedTiff(data);
|
|
14697
|
+
default: throw new Error(`Unsupported image format. Expected PNG, JPEG, WebP, or TIFF, got ${data.subarray(0, 4).toHex().match(/.{2}/g)?.join(" ") ?? ""}.`);
|
|
14698
|
+
}
|
|
12847
14699
|
}
|
|
12848
14700
|
/**
|
|
12849
14701
|
* Embed pages from another PDF as Form XObjects.
|
|
@@ -13501,7 +15353,7 @@ var PdfDocument = class PdfDocument {
|
|
|
13501
15353
|
if ((options?.addDefaultPage ?? true) && this.getPageCount() === 0) this.addPage();
|
|
13502
15354
|
if ((options?.updateFieldAppearances ?? true) && this.form) for (const field of this.form.getFields()) field.generateAppearance();
|
|
13503
15355
|
const structure = this.buildStructure();
|
|
13504
|
-
return serializePdf(this.registry, structure, options);
|
|
15356
|
+
return serializePdf(this.registry, structure, options, this.encryptionHandler);
|
|
13505
15357
|
}
|
|
13506
15358
|
/**
|
|
13507
15359
|
* Serialize the document as a `ReadableStream<Uint8Array>`.
|
|
@@ -13677,6 +15529,21 @@ var PdfDocument = class PdfDocument {
|
|
|
13677
15529
|
catalogObj.set("/AA", aaDict);
|
|
13678
15530
|
}
|
|
13679
15531
|
}
|
|
15532
|
+
const pageLabelRanges = getPageLabelEntries(this);
|
|
15533
|
+
if (pageLabelRanges !== void 0 && pageLabelRanges.length > 0) {
|
|
15534
|
+
const numsArray = new require_pdfObjects.PdfArray();
|
|
15535
|
+
for (const range of pageLabelRanges) {
|
|
15536
|
+
numsArray.push(require_pdfObjects.PdfNumber.of(range.startPage));
|
|
15537
|
+
const labelDict = new require_pdfObjects.PdfDict();
|
|
15538
|
+
labelDict.set("/S", require_pdfObjects.PdfName.of(styleToPdf(range.style)));
|
|
15539
|
+
if (range.prefix !== void 0) labelDict.set("/P", require_pdfObjects.PdfString.literal(range.prefix));
|
|
15540
|
+
if (range.start !== void 0) labelDict.set("/St", require_pdfObjects.PdfNumber.of(range.start));
|
|
15541
|
+
numsArray.push(labelDict);
|
|
15542
|
+
}
|
|
15543
|
+
const pageLabelsDict = new require_pdfObjects.PdfDict();
|
|
15544
|
+
pageLabelsDict.set("/Nums", numsArray);
|
|
15545
|
+
catalogObj.set("/PageLabels", pageLabelsDict);
|
|
15546
|
+
}
|
|
13680
15547
|
}
|
|
13681
15548
|
if (this.originalBytes !== void 0) {
|
|
13682
15549
|
const rootRefs = [structure.catalogRef, structure.infoRef];
|
|
@@ -13937,6 +15804,48 @@ Object.defineProperty(exports, 'decodePermissions', {
|
|
|
13937
15804
|
return decodePermissions;
|
|
13938
15805
|
}
|
|
13939
15806
|
});
|
|
15807
|
+
Object.defineProperty(exports, 'decodeTiff', {
|
|
15808
|
+
enumerable: true,
|
|
15809
|
+
get: function () {
|
|
15810
|
+
return decodeTiff;
|
|
15811
|
+
}
|
|
15812
|
+
});
|
|
15813
|
+
Object.defineProperty(exports, 'decodeTiffAll', {
|
|
15814
|
+
enumerable: true,
|
|
15815
|
+
get: function () {
|
|
15816
|
+
return decodeTiffAll;
|
|
15817
|
+
}
|
|
15818
|
+
});
|
|
15819
|
+
Object.defineProperty(exports, 'decodeTiffPage', {
|
|
15820
|
+
enumerable: true,
|
|
15821
|
+
get: function () {
|
|
15822
|
+
return decodeTiffPage;
|
|
15823
|
+
}
|
|
15824
|
+
});
|
|
15825
|
+
Object.defineProperty(exports, 'decodeWebP', {
|
|
15826
|
+
enumerable: true,
|
|
15827
|
+
get: function () {
|
|
15828
|
+
return decodeWebP;
|
|
15829
|
+
}
|
|
15830
|
+
});
|
|
15831
|
+
Object.defineProperty(exports, 'detectImageFormat', {
|
|
15832
|
+
enumerable: true,
|
|
15833
|
+
get: function () {
|
|
15834
|
+
return detectImageFormat;
|
|
15835
|
+
}
|
|
15836
|
+
});
|
|
15837
|
+
Object.defineProperty(exports, 'detectKeyAlgorithm', {
|
|
15838
|
+
enumerable: true,
|
|
15839
|
+
get: function () {
|
|
15840
|
+
return detectKeyAlgorithm;
|
|
15841
|
+
}
|
|
15842
|
+
});
|
|
15843
|
+
Object.defineProperty(exports, 'detectNamedCurve', {
|
|
15844
|
+
enumerable: true,
|
|
15845
|
+
get: function () {
|
|
15846
|
+
return detectNamedCurve;
|
|
15847
|
+
}
|
|
15848
|
+
});
|
|
13940
15849
|
Object.defineProperty(exports, 'embedPageAsFormXObject', {
|
|
13941
15850
|
enumerable: true,
|
|
13942
15851
|
get: function () {
|
|
@@ -14015,6 +15924,12 @@ Object.defineProperty(exports, 'encodeUtf8String', {
|
|
|
14015
15924
|
return encodeUtf8String;
|
|
14016
15925
|
}
|
|
14017
15926
|
});
|
|
15927
|
+
Object.defineProperty(exports, 'extractIssuerAndSerial', {
|
|
15928
|
+
enumerable: true,
|
|
15929
|
+
get: function () {
|
|
15930
|
+
return extractIssuerAndSerial;
|
|
15931
|
+
}
|
|
15932
|
+
});
|
|
14018
15933
|
Object.defineProperty(exports, 'extractMetrics', {
|
|
14019
15934
|
enumerable: true,
|
|
14020
15935
|
get: function () {
|
|
@@ -14039,12 +15954,42 @@ Object.defineProperty(exports, 'getAttachments', {
|
|
|
14039
15954
|
return getAttachments;
|
|
14040
15955
|
}
|
|
14041
15956
|
});
|
|
15957
|
+
Object.defineProperty(exports, 'getImageFormatName', {
|
|
15958
|
+
enumerable: true,
|
|
15959
|
+
get: function () {
|
|
15960
|
+
return getImageFormatName;
|
|
15961
|
+
}
|
|
15962
|
+
});
|
|
15963
|
+
Object.defineProperty(exports, 'getPageLabels', {
|
|
15964
|
+
enumerable: true,
|
|
15965
|
+
get: function () {
|
|
15966
|
+
return getPageLabels;
|
|
15967
|
+
}
|
|
15968
|
+
});
|
|
14042
15969
|
Object.defineProperty(exports, 'getSignatures', {
|
|
14043
15970
|
enumerable: true,
|
|
14044
15971
|
get: function () {
|
|
14045
15972
|
return getSignatures;
|
|
14046
15973
|
}
|
|
14047
15974
|
});
|
|
15975
|
+
Object.defineProperty(exports, 'getSubtle', {
|
|
15976
|
+
enumerable: true,
|
|
15977
|
+
get: function () {
|
|
15978
|
+
return getSubtle;
|
|
15979
|
+
}
|
|
15980
|
+
});
|
|
15981
|
+
Object.defineProperty(exports, 'getSupportedFormats', {
|
|
15982
|
+
enumerable: true,
|
|
15983
|
+
get: function () {
|
|
15984
|
+
return getSupportedFormats;
|
|
15985
|
+
}
|
|
15986
|
+
});
|
|
15987
|
+
Object.defineProperty(exports, 'getTiffPageCount', {
|
|
15988
|
+
enumerable: true,
|
|
15989
|
+
get: function () {
|
|
15990
|
+
return getTiffPageCount;
|
|
15991
|
+
}
|
|
15992
|
+
});
|
|
14048
15993
|
Object.defineProperty(exports, 'isAccessible', {
|
|
14049
15994
|
enumerable: true,
|
|
14050
15995
|
get: function () {
|
|
@@ -14057,12 +16002,30 @@ Object.defineProperty(exports, 'isOpenTypeCFF', {
|
|
|
14057
16002
|
return isOpenTypeCFF;
|
|
14058
16003
|
}
|
|
14059
16004
|
});
|
|
16005
|
+
Object.defineProperty(exports, 'isTiff', {
|
|
16006
|
+
enumerable: true,
|
|
16007
|
+
get: function () {
|
|
16008
|
+
return isTiff;
|
|
16009
|
+
}
|
|
16010
|
+
});
|
|
14060
16011
|
Object.defineProperty(exports, 'isTrueType', {
|
|
14061
16012
|
enumerable: true,
|
|
14062
16013
|
get: function () {
|
|
14063
16014
|
return isTrueType;
|
|
14064
16015
|
}
|
|
14065
16016
|
});
|
|
16017
|
+
Object.defineProperty(exports, 'isWebP', {
|
|
16018
|
+
enumerable: true,
|
|
16019
|
+
get: function () {
|
|
16020
|
+
return isWebP;
|
|
16021
|
+
}
|
|
16022
|
+
});
|
|
16023
|
+
Object.defineProperty(exports, 'isWebPLossless', {
|
|
16024
|
+
enumerable: true,
|
|
16025
|
+
get: function () {
|
|
16026
|
+
return isWebPLossless;
|
|
16027
|
+
}
|
|
16028
|
+
});
|
|
14066
16029
|
Object.defineProperty(exports, 'loadPdf', {
|
|
14067
16030
|
enumerable: true,
|
|
14068
16031
|
get: function () {
|
|
@@ -14087,6 +16050,12 @@ Object.defineProperty(exports, 'parseDerTlv', {
|
|
|
14087
16050
|
return parseDerTlv;
|
|
14088
16051
|
}
|
|
14089
16052
|
});
|
|
16053
|
+
Object.defineProperty(exports, 'parseTiffIfd', {
|
|
16054
|
+
enumerable: true,
|
|
16055
|
+
get: function () {
|
|
16056
|
+
return parseTiffIfd;
|
|
16057
|
+
}
|
|
16058
|
+
});
|
|
14090
16059
|
Object.defineProperty(exports, 'parseViewerPreferences', {
|
|
14091
16060
|
enumerable: true,
|
|
14092
16061
|
get: function () {
|
|
@@ -14111,12 +16080,24 @@ Object.defineProperty(exports, 'rc4', {
|
|
|
14111
16080
|
return rc4;
|
|
14112
16081
|
}
|
|
14113
16082
|
});
|
|
16083
|
+
Object.defineProperty(exports, 'removePageLabels', {
|
|
16084
|
+
enumerable: true,
|
|
16085
|
+
get: function () {
|
|
16086
|
+
return removePageLabels;
|
|
16087
|
+
}
|
|
16088
|
+
});
|
|
14114
16089
|
Object.defineProperty(exports, 'serializePdf', {
|
|
14115
16090
|
enumerable: true,
|
|
14116
16091
|
get: function () {
|
|
14117
16092
|
return serializePdf;
|
|
14118
16093
|
}
|
|
14119
16094
|
});
|
|
16095
|
+
Object.defineProperty(exports, 'setPageLabels', {
|
|
16096
|
+
enumerable: true,
|
|
16097
|
+
get: function () {
|
|
16098
|
+
return setPageLabels;
|
|
16099
|
+
}
|
|
16100
|
+
});
|
|
14120
16101
|
Object.defineProperty(exports, 'sha256', {
|
|
14121
16102
|
enumerable: true,
|
|
14122
16103
|
get: function () {
|
|
@@ -14153,6 +16134,12 @@ Object.defineProperty(exports, 'summarizeIssues', {
|
|
|
14153
16134
|
return summarizeIssues;
|
|
14154
16135
|
}
|
|
14155
16136
|
});
|
|
16137
|
+
Object.defineProperty(exports, 'toBuffer', {
|
|
16138
|
+
enumerable: true,
|
|
16139
|
+
get: function () {
|
|
16140
|
+
return toBuffer;
|
|
16141
|
+
}
|
|
16142
|
+
});
|
|
14156
16143
|
Object.defineProperty(exports, 'verifyOwnerPassword', {
|
|
14157
16144
|
enumerable: true,
|
|
14158
16145
|
get: function () {
|
|
@@ -14177,4 +16164,4 @@ Object.defineProperty(exports, 'verifyUserPassword', {
|
|
|
14177
16164
|
return verifyUserPassword;
|
|
14178
16165
|
}
|
|
14179
16166
|
});
|
|
14180
|
-
//# sourceMappingURL=pdfDocument-
|
|
16167
|
+
//# sourceMappingURL=pdfDocument-D9uYNSb-.cjs.map
|