ipx 3.1.0 → 3.1.2

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.
@@ -0,0 +1,1685 @@
1
+ 'use strict';
2
+
3
+ const defu = require('defu');
4
+ const ufo = require('ufo');
5
+ const h3 = require('h3');
6
+ const destr = require('destr');
7
+ const acceptNegotiator = require('@fastify/accept-negotiator');
8
+ const getEtag = require('etag');
9
+ const pathe = require('pathe');
10
+ const ofetch = require('ofetch');
11
+
12
+ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
13
+
14
+ const destr__default = /*#__PURE__*/_interopDefaultCompat(destr);
15
+ const getEtag__default = /*#__PURE__*/_interopDefaultCompat(getEtag);
16
+
17
+ const Handlers = {
18
+ __proto__: null,
19
+ get b () { return b; },
20
+ get background () { return background; },
21
+ get blur () { return blur; },
22
+ get crop () { return crop; },
23
+ get enlarge () { return enlarge; },
24
+ get extend () { return extend; },
25
+ get extract () { return extract; },
26
+ get fit () { return fit; },
27
+ get flatten () { return flatten; },
28
+ get flip () { return flip; },
29
+ get flop () { return flop; },
30
+ get gamma () { return gamma; },
31
+ get grayscale () { return grayscale; },
32
+ get h () { return h; },
33
+ get height () { return height; },
34
+ get kernel () { return kernel; },
35
+ get median () { return median; },
36
+ get modulate () { return modulate; },
37
+ get negate () { return negate; },
38
+ get normalize () { return normalize; },
39
+ get pos () { return pos; },
40
+ get position () { return position; },
41
+ get q () { return q; },
42
+ get quality () { return quality; },
43
+ get resize () { return resize; },
44
+ get rotate () { return rotate; },
45
+ get s () { return s; },
46
+ get sharpen () { return sharpen; },
47
+ get threshold () { return threshold; },
48
+ get tint () { return tint; },
49
+ get trim () { return trim; },
50
+ get w () { return w; },
51
+ get width () { return width; }
52
+ };
53
+
54
+ const decoder = new TextDecoder();
55
+ const toUTF8String = (input, start = 0, end = input.length) => decoder.decode(input.slice(start, end));
56
+ const toHexString = (input, start = 0, end = input.length) => input.slice(start, end).reduce((memo, i) => memo + ("0" + i.toString(16)).slice(-2), "");
57
+ const readInt16LE = (input, offset = 0) => {
58
+ const val = input[offset] + input[offset + 1] * 256;
59
+ return val >= 2 ** 15 ? val - 2 ** 16 : val;
60
+ };
61
+ const readUInt16BE = (input, offset = 0) => input[offset] * 256 + input[offset + 1];
62
+ const readUInt16LE = (input, offset = 0) => input[offset] + input[offset + 1] * 256;
63
+ const readUInt24LE = (input, offset = 0) => input[offset] + input[offset + 1] * 256 + input[offset + 2] * 2 ** 16;
64
+ const readInt32LE = (input, offset = 0) => {
65
+ const val = readUInt32LE(input, offset);
66
+ return val >= 2 ** 31 ? val - 2 ** 32 : val;
67
+ };
68
+ const readUInt32BE = (input, offset = 0) => input[offset] * 2 ** 24 + input[offset + 1] * 2 ** 16 + input[offset + 2] * 256 + input[offset + 3];
69
+ const readUInt32LE = (input, offset = 0) => input[offset] + input[offset + 1] * 256 + input[offset + 2] * 2 ** 16 + input[offset + 3] * 2 ** 24;
70
+ const methods = {
71
+ readUInt16BE,
72
+ readUInt16LE,
73
+ readUInt32BE,
74
+ readUInt32LE
75
+ };
76
+ function readUInt(input, bits, offset, isBigEndian) {
77
+ offset = offset || 0;
78
+ const endian = isBigEndian ? "BE" : "LE";
79
+ const methodName = "readUInt" + bits + endian;
80
+ return methods[methodName](input, offset);
81
+ }
82
+ const encoder = new TextEncoder();
83
+ const toTagCode = (tag) => readUInt32BE(encoder.encode(tag));
84
+ const BOX_HEADER_SIZE = 8;
85
+ function findBox(input, boxName, startOffset = 0, endOffset = input.length) {
86
+ const boxType = toTagCode(boxName);
87
+ let offset = startOffset;
88
+ while (offset + BOX_HEADER_SIZE <= endOffset) {
89
+ const size = readUInt32BE(input, offset);
90
+ if (size < BOX_HEADER_SIZE || offset + size > endOffset) return;
91
+ if (readUInt32BE(input, offset + 4) === boxType) return {
92
+ offset,
93
+ size
94
+ };
95
+ offset += size;
96
+ }
97
+ }
98
+ const CORE_HEADER_SIZE = 12;
99
+ const BMP = {
100
+ validate: (input) => toUTF8String(input, 0, 2) === "BM",
101
+ calculate: (input) => {
102
+ if (readUInt32LE(input, 14) === CORE_HEADER_SIZE) return {
103
+ height: readUInt16LE(input, 20),
104
+ width: readUInt16LE(input, 18)
105
+ };
106
+ return {
107
+ height: Math.abs(readInt32LE(input, 22)),
108
+ width: readInt32LE(input, 18)
109
+ };
110
+ }
111
+ };
112
+ const TYPE_ICON = 1;
113
+ const SIZE_HEADER$1 = 6;
114
+ const SIZE_IMAGE_ENTRY = 16;
115
+ function getSizeFromOffset(input, offset) {
116
+ const value = input[offset];
117
+ return value === 0 ? 256 : value;
118
+ }
119
+ function getImageSize$1(input, imageIndex) {
120
+ const offset = SIZE_HEADER$1 + imageIndex * SIZE_IMAGE_ENTRY;
121
+ return {
122
+ height: getSizeFromOffset(input, offset + 1),
123
+ width: getSizeFromOffset(input, offset)
124
+ };
125
+ }
126
+ const ICO = {
127
+ validate(input) {
128
+ const reserved = readUInt16LE(input, 0);
129
+ const imageCount = readUInt16LE(input, 4);
130
+ if (reserved !== 0 || imageCount === 0) return false;
131
+ return readUInt16LE(input, 2) === TYPE_ICON;
132
+ },
133
+ calculate(input) {
134
+ const nbImages = readUInt16LE(input, 4);
135
+ if (SIZE_HEADER$1 + nbImages * SIZE_IMAGE_ENTRY > input.length) throw new TypeError("Invalid ICO");
136
+ const imageSize = getImageSize$1(input, 0);
137
+ if (nbImages === 1) return imageSize;
138
+ const imgs = [imageSize];
139
+ for (let imageIndex = 1; imageIndex < nbImages; imageIndex += 1) imgs.push(getImageSize$1(input, imageIndex));
140
+ return {
141
+ height: imageSize.height,
142
+ images: imgs,
143
+ width: imageSize.width
144
+ };
145
+ }
146
+ };
147
+ const TYPE_CURSOR = 2;
148
+ const CUR = {
149
+ validate(input) {
150
+ const reserved = readUInt16LE(input, 0);
151
+ const imageCount = readUInt16LE(input, 4);
152
+ if (reserved !== 0 || imageCount === 0) return false;
153
+ return readUInt16LE(input, 2) === TYPE_CURSOR;
154
+ },
155
+ calculate: (input) => ICO.calculate(input)
156
+ };
157
+ const DDS = {
158
+ validate: (input) => readUInt32LE(input, 0) === 542327876,
159
+ calculate: (input) => ({
160
+ height: readUInt32LE(input, 12),
161
+ width: readUInt32LE(input, 16)
162
+ })
163
+ };
164
+ const gifRegexp = /^GIF8[79]a/;
165
+ const GIF = {
166
+ validate: (input) => gifRegexp.test(toUTF8String(input, 0, 6)),
167
+ calculate: (input) => ({
168
+ height: readUInt16LE(input, 8),
169
+ width: readUInt16LE(input, 6)
170
+ })
171
+ };
172
+ const FTYP = toTagCode("ftyp");
173
+ const ISPE = toTagCode("ispe");
174
+ const CLAP = toTagCode("clap");
175
+ function larger(largest, image) {
176
+ return !largest || image.width * image.height > largest.width * largest.height ? image : largest;
177
+ }
178
+ function readLargestImageSize(input) {
179
+ const metaBox = findBox(input, "meta");
180
+ const iprpBox = metaBox && findBox(input, "iprp", metaBox.offset + 12, metaBox.offset + metaBox.size);
181
+ const ipcoBox = iprpBox && findBox(input, "ipco", iprpBox.offset + 8, iprpBox.offset + iprpBox.size);
182
+ if (!ipcoBox) throw new TypeError("Invalid HEIF, no ipco box found");
183
+ let largest;
184
+ let image;
185
+ const end = ipcoBox.offset + ipcoBox.size;
186
+ let offset = ipcoBox.offset + 8;
187
+ while (offset + 8 <= end) {
188
+ const size = readUInt32BE(input, offset);
189
+ if (size < 8 || offset + size > end) throw new TypeError("Invalid HEIF, corrupt ipco box");
190
+ const type = readUInt32BE(input, offset + 4);
191
+ if (type === ISPE) {
192
+ if (size < 20) throw new TypeError("Invalid HEIF, corrupt ispe box");
193
+ if (image) largest = larger(largest, image);
194
+ image = {
195
+ width: readUInt32BE(input, offset + 12),
196
+ height: readUInt32BE(input, offset + 16)
197
+ };
198
+ }
199
+ if (type === CLAP && size >= 24 && image) {
200
+ const widthD = readUInt32BE(input, offset + 12);
201
+ const heightD = readUInt32BE(input, offset + 20);
202
+ if (widthD > 0 && heightD > 0) {
203
+ image.width = Math.round(readUInt32BE(input, offset + 8) / widthD);
204
+ image.height = Math.round(readUInt32BE(input, offset + 16) / heightD);
205
+ }
206
+ }
207
+ offset += size;
208
+ }
209
+ if (!image) throw new TypeError("Invalid HEIF, no ispe box found");
210
+ return larger(largest, image);
211
+ }
212
+ const AVIF_BRANDS = new Set(["avif", "avis"].map((brand) => toTagCode(brand)));
213
+ function detectHeifType(input) {
214
+ const ftypSize = readUInt32BE(input, 0);
215
+ if (readUInt32BE(input, 4) !== FTYP || ftypSize < 12 || ftypSize > input.length) return;
216
+ if (AVIF_BRANDS.has(readUInt32BE(input, 8))) return "avif";
217
+ const majorBrand = toUTF8String(input, 8, 12);
218
+ if ([
219
+ "mif1",
220
+ "msf1",
221
+ "miaf"
222
+ ].includes(majorBrand)) {
223
+ for (let offset = 16; offset + 4 <= ftypSize; offset += 4) if (AVIF_BRANDS.has(readUInt32BE(input, offset))) return "avif";
224
+ }
225
+ return [
226
+ "heic",
227
+ "heix",
228
+ "hevc",
229
+ "hevx",
230
+ "mif1",
231
+ "msf1"
232
+ ].includes(majorBrand) ? "heic" : void 0;
233
+ }
234
+ const HEIC = {
235
+ validate: (input) => detectHeifType(input) === "heic",
236
+ calculate: (input) => readLargestImageSize(input)
237
+ };
238
+ const SIZE_HEADER = 8;
239
+ const FILE_LENGTH_OFFSET = 4;
240
+ const ENTRY_LENGTH_OFFSET = 4;
241
+ const ICON_TYPE_SIZE = {
242
+ ICON: 32,
243
+ "ICN#": 32,
244
+ "icm#": 16,
245
+ icm4: 16,
246
+ icm8: 16,
247
+ "ics#": 16,
248
+ ics4: 16,
249
+ ics8: 16,
250
+ is32: 16,
251
+ s8mk: 16,
252
+ icp4: 16,
253
+ ic04: 16,
254
+ icsb: 18,
255
+ sb24: 24,
256
+ icl4: 32,
257
+ icl8: 32,
258
+ il32: 32,
259
+ l8mk: 32,
260
+ icp5: 32,
261
+ ic05: 32,
262
+ ic11: 32,
263
+ icsB: 36,
264
+ ich4: 48,
265
+ ich8: 48,
266
+ ih32: 48,
267
+ h8mk: 48,
268
+ SB24: 48,
269
+ icp6: 64,
270
+ ic12: 64,
271
+ it32: 128,
272
+ t8mk: 128,
273
+ ic07: 128,
274
+ ic08: 256,
275
+ ic13: 256,
276
+ ic09: 512,
277
+ ic14: 512,
278
+ ic10: 1024
279
+ };
280
+ const ICON_TYPES = new Map(Object.keys(ICON_TYPE_SIZE).map((type) => [toTagCode(type), type]));
281
+ function readImageHeader(input, imageOffset) {
282
+ const imageLengthOffset = imageOffset + ENTRY_LENGTH_OFFSET;
283
+ if (imageLengthOffset + 4 > input.length) throw new TypeError("Invalid ICNS");
284
+ const entryLength = readUInt32BE(input, imageLengthOffset);
285
+ if (entryLength < 8) throw new TypeError("Invalid ICNS");
286
+ return [ICON_TYPES.get(readUInt32BE(input, imageOffset)), entryLength];
287
+ }
288
+ function getImageSize(type) {
289
+ const size = ICON_TYPE_SIZE[type];
290
+ return {
291
+ width: size,
292
+ height: size,
293
+ type
294
+ };
295
+ }
296
+ const ICNS = {
297
+ validate: (input) => toUTF8String(input, 0, 4) === "icns",
298
+ calculate(input) {
299
+ const inputLength = input.length;
300
+ const fileLength = readUInt32BE(input, FILE_LENGTH_OFFSET);
301
+ let imageOffset = SIZE_HEADER;
302
+ const images = [];
303
+ const seenTypes = /* @__PURE__ */ new Set();
304
+ while (imageOffset < fileLength && imageOffset < inputLength) {
305
+ const [type, entryLength] = readImageHeader(input, imageOffset);
306
+ imageOffset += entryLength;
307
+ if (type !== void 0 && !seenTypes.has(type)) {
308
+ seenTypes.add(type);
309
+ images.push(getImageSize(type));
310
+ }
311
+ }
312
+ if (images.length === 0) throw new TypeError("Invalid ICNS, no icons found");
313
+ if (images.length === 1) return images[0];
314
+ return {
315
+ height: images[0].height,
316
+ images,
317
+ width: images[0].width
318
+ };
319
+ }
320
+ };
321
+ const J2C = {
322
+ validate: (input) => readUInt32BE(input, 0) === 4283432785,
323
+ calculate: (input) => ({
324
+ height: readUInt32BE(input, 12) - readUInt32BE(input, 20),
325
+ width: readUInt32BE(input, 8) - readUInt32BE(input, 16)
326
+ })
327
+ };
328
+ const JP2 = {
329
+ validate(input) {
330
+ if (toUTF8String(input, 4, 8) !== "jP ") return false;
331
+ return findBox(input, "ftyp") !== void 0;
332
+ },
333
+ calculate(input) {
334
+ const jp2hBox = findBox(input, "jp2h");
335
+ const ihdrBox = jp2hBox && findBox(input, "ihdr", jp2hBox.offset + 8, jp2hBox.offset + jp2hBox.size);
336
+ if (!ihdrBox || ihdrBox.size < 16) throw new TypeError("Invalid JPEG 2000, no ihdr box found");
337
+ return {
338
+ height: readUInt32BE(input, ihdrBox.offset + 8),
339
+ width: readUInt32BE(input, ihdrBox.offset + 12)
340
+ };
341
+ }
342
+ };
343
+ const EXIF_MARKER = toTagCode("Exif");
344
+ const APP1_DATA_SIZE_BYTES = 2;
345
+ const EXIF_HEADER_BYTES = 6;
346
+ const BIG_ENDIAN_BYTE_ALIGN = 19789;
347
+ const LITTLE_ENDIAN_BYTE_ALIGN = 18761;
348
+ const TIFF_MAGIC = 42;
349
+ const TIFF_HEADER_BYTES = 8;
350
+ const IDF_ENTRY_BYTES = 12;
351
+ const NUM_DIRECTORY_ENTRIES_BYTES = 2;
352
+ function isEXIF(input) {
353
+ return readUInt32BE(input, 2) === EXIF_MARKER;
354
+ }
355
+ function extractSize(input, index) {
356
+ return {
357
+ height: readUInt16BE(input, index),
358
+ width: readUInt16BE(input, index + 2)
359
+ };
360
+ }
361
+ function extractOrientation(exifBlock, isBigEndian) {
362
+ if (readUInt(exifBlock, 16, 8, isBigEndian) !== TIFF_MAGIC) return;
363
+ const idfOffset = readUInt(exifBlock, 32, 10, isBigEndian);
364
+ const offset = EXIF_HEADER_BYTES + idfOffset;
365
+ if (!(idfOffset >= TIFF_HEADER_BYTES && offset + NUM_DIRECTORY_ENTRIES_BYTES <= exifBlock.length)) return;
366
+ const idfDirectoryEntries = readUInt(exifBlock, 16, offset, isBigEndian);
367
+ for (let directoryEntryNumber = 0; directoryEntryNumber < idfDirectoryEntries; directoryEntryNumber++) {
368
+ const start = offset + NUM_DIRECTORY_ENTRIES_BYTES + directoryEntryNumber * IDF_ENTRY_BYTES;
369
+ const end = start + IDF_ENTRY_BYTES;
370
+ if (start > exifBlock.length) return;
371
+ const block = exifBlock.slice(start, end);
372
+ if (readUInt(block, 16, 0, isBigEndian) === 274) {
373
+ if (readUInt(block, 16, 2, isBigEndian) !== 3) return;
374
+ if (readUInt(block, 32, 4, isBigEndian) !== 1) return;
375
+ return readUInt(block, 16, 8, isBigEndian);
376
+ }
377
+ }
378
+ }
379
+ function validateExifBlock(input, index) {
380
+ const exifBlock = input.slice(APP1_DATA_SIZE_BYTES, index);
381
+ const byteAlign = readUInt16BE(exifBlock, EXIF_HEADER_BYTES);
382
+ const isBigEndian = byteAlign === BIG_ENDIAN_BYTE_ALIGN;
383
+ if (isBigEndian || byteAlign === LITTLE_ENDIAN_BYTE_ALIGN) return extractOrientation(exifBlock, isBigEndian);
384
+ }
385
+ function validateInput(input, index) {
386
+ if (index > input.length) throw new TypeError("Corrupt JPG, exceeded buffer limits");
387
+ }
388
+ function findMarker(input, index) {
389
+ let marker = input.indexOf(255, index);
390
+ while (marker !== -1) {
391
+ while (input[marker + 1] === 255) marker++;
392
+ if (input[marker + 1] !== 0) return marker;
393
+ marker = input.indexOf(255, marker + 2);
394
+ }
395
+ throw new TypeError("Invalid JPG, marker table corrupted");
396
+ }
397
+ const JPG = {
398
+ validate: (input) => toHexString(input, 0, 2) === "ffd8",
399
+ calculate(input) {
400
+ input = input.subarray(2);
401
+ let orientation;
402
+ let next;
403
+ while (input.length > 0) {
404
+ const marker = findMarker(input, 0);
405
+ next = input[marker + 1];
406
+ if (next >= 192 && next <= 195 || next >= 201 && next <= 203) {
407
+ const size = extractSize(input, marker + 5);
408
+ if (!orientation) return size;
409
+ return {
410
+ height: size.height,
411
+ orientation,
412
+ width: size.width
413
+ };
414
+ }
415
+ if (next === 218) break;
416
+ input = input.subarray(marker + 2);
417
+ const i = readUInt16BE(input, 0);
418
+ validateInput(input, i);
419
+ if (isEXIF(input)) orientation = validateExifBlock(input, i);
420
+ input = input.subarray(i);
421
+ }
422
+ throw new TypeError("Invalid JPG, no size found");
423
+ }
424
+ };
425
+ function createBitReader(input) {
426
+ let bitOffset = 16;
427
+ return (length) => {
428
+ let result = 0;
429
+ for (let i = 0; i < length; i++, bitOffset++) {
430
+ const byte = input[bitOffset >> 3];
431
+ if (byte === void 0) throw new TypeError("Invalid JXL, truncated codestream");
432
+ result += (byte >> (bitOffset & 7) & 1) * 2 ** i;
433
+ }
434
+ return result;
435
+ };
436
+ }
437
+ const RATIOS = [
438
+ [1, 1],
439
+ [12, 10],
440
+ [4, 3],
441
+ [3, 2],
442
+ [16, 9],
443
+ [5, 4],
444
+ [2, 1]
445
+ ];
446
+ function calculateCodestream(input) {
447
+ const readBits = createBitReader(input);
448
+ const readSize = (isSmall) => isSmall ? 8 * (1 + readBits(5)) : 1 + readBits([
449
+ 9,
450
+ 13,
451
+ 18,
452
+ 30
453
+ ][readBits(2)]);
454
+ const isSmall = readBits(1) === 1;
455
+ const height = readSize(isSmall);
456
+ const ratio = readBits(3);
457
+ if (ratio === 0) return {
458
+ height,
459
+ width: readSize(isSmall)
460
+ };
461
+ const [numerator, denominator] = RATIOS[ratio - 1];
462
+ return {
463
+ height,
464
+ width: Math.floor(height * numerator / denominator)
465
+ };
466
+ }
467
+ const CODESTREAM_PREFIX_BYTES = 32;
468
+ const JXLC = toTagCode("jxlc");
469
+ const JXLP = toTagCode("jxlp");
470
+ function extractCodestream(input) {
471
+ const parts = [];
472
+ let length = 0;
473
+ let offset = 0;
474
+ while (offset + 8 <= input.length && length < CODESTREAM_PREFIX_BYTES) {
475
+ let size = readUInt32BE(input, offset);
476
+ const type = readUInt32BE(input, offset + 4);
477
+ let headerSize = 8;
478
+ if (size === 1) {
479
+ if (offset + 16 > input.length) break;
480
+ size = readUInt32BE(input, offset + 8) * 2 ** 32 + readUInt32BE(input, offset + 12);
481
+ headerSize = 16;
482
+ } else if (size === 0) size = input.length - offset;
483
+ if (type === JXLC || type === JXLP) {
484
+ const start = offset + headerSize + (type === JXLP ? 4 : 0);
485
+ if (size < start - offset) throw new TypeError(`Invalid JXL, corrupt ${type === JXLP ? "jxlp" : "jxlc"} box`);
486
+ const end = Math.min(offset + size, input.length, start + CODESTREAM_PREFIX_BYTES - length);
487
+ if (end > start) {
488
+ parts.push(input.subarray(start, end));
489
+ length += end - start;
490
+ }
491
+ if (type === JXLC) break;
492
+ }
493
+ if (size < headerSize) throw new TypeError("Invalid JXL, corrupt box");
494
+ offset += size;
495
+ }
496
+ const codestream = new Uint8Array(length);
497
+ let position = 0;
498
+ for (const part of parts) {
499
+ codestream.set(part, position);
500
+ position += part.length;
501
+ }
502
+ return codestream;
503
+ }
504
+ const isCodestream = (input) => toHexString(input, 0, 2) === "ff0a";
505
+ const JXL = {
506
+ validate: (input) => {
507
+ if (isCodestream(input)) return true;
508
+ if (toUTF8String(input, 4, 8) !== "JXL ") return false;
509
+ const ftypBox = findBox(input, "ftyp");
510
+ return ftypBox !== void 0 && toUTF8String(input, ftypBox.offset + 8, ftypBox.offset + 12) === "jxl ";
511
+ },
512
+ calculate: (input) => {
513
+ const codestream = isCodestream(input) ? input : extractCodestream(input);
514
+ if (!isCodestream(codestream)) throw new TypeError("Invalid JXL, no codestream found");
515
+ return calculateCodestream(codestream);
516
+ }
517
+ };
518
+ const LITTLE_ENDIAN = 67305985;
519
+ const BIG_ENDIAN = 16909060;
520
+ const KTX = {
521
+ validate: (input) => {
522
+ const signature = toUTF8String(input, 1, 7);
523
+ return signature === "KTX 11" || signature === "KTX 20";
524
+ },
525
+ calculate: (input) => {
526
+ if (input[5] === 50) return {
527
+ height: readUInt32LE(input, 24),
528
+ width: readUInt32LE(input, 20)
529
+ };
530
+ const endianness = readUInt32LE(input, 12);
531
+ if (endianness !== LITTLE_ENDIAN && endianness !== BIG_ENDIAN) throw new TypeError("Invalid KTX, unknown endianness");
532
+ const isBigEndian = endianness === BIG_ENDIAN;
533
+ return {
534
+ height: readUInt(input, 32, 40, isBigEndian),
535
+ width: readUInt(input, 32, 36, isBigEndian)
536
+ };
537
+ }
538
+ };
539
+ const pngSignature = "PNG\r\n\n";
540
+ const pngImageHeaderChunkName = "IHDR";
541
+ const pngFriedChunkName = "CgBI";
542
+ const PNG = {
543
+ validate(input) {
544
+ if (pngSignature === toUTF8String(input, 1, 8)) {
545
+ let chunkName = toUTF8String(input, 12, 16);
546
+ if (chunkName === pngFriedChunkName) chunkName = toUTF8String(input, 28, 32);
547
+ if (chunkName !== pngImageHeaderChunkName) throw new TypeError("Invalid PNG");
548
+ return true;
549
+ }
550
+ return false;
551
+ },
552
+ calculate(input) {
553
+ if (toUTF8String(input, 12, 16) === pngFriedChunkName) return {
554
+ height: readUInt32BE(input, 36),
555
+ width: readUInt32BE(input, 32)
556
+ };
557
+ return {
558
+ height: readUInt32BE(input, 20),
559
+ width: readUInt32BE(input, 16)
560
+ };
561
+ }
562
+ };
563
+ const PNMTypes = {
564
+ P1: "pbm/ascii",
565
+ P2: "pgm/ascii",
566
+ P3: "ppm/ascii",
567
+ P4: "pbm",
568
+ P5: "pgm",
569
+ P6: "ppm",
570
+ P7: "pam",
571
+ PF: "pfm",
572
+ Pf: "pfm"
573
+ };
574
+ const isLineBreak = (byte) => byte === 10 || byte === 13;
575
+ const isWhitespace = (byte) => byte === 32 || byte >= 9 && byte <= 13;
576
+ const toInteger = (value = "") => /^\d+$/.test(value) ? Number(value) : NaN;
577
+ const PAMLine = /^\s*(width|height|endhdr)(?!\S)\s*(\S*)/i;
578
+ const handlers = {
579
+ default: (input) => {
580
+ const [width, height] = readHeader(input, "tokens");
581
+ return {
582
+ height: toInteger(height),
583
+ width: toInteger(width)
584
+ };
585
+ },
586
+ pam: (input) => {
587
+ const size = {};
588
+ for (const line of readHeader(input, "lines")) {
589
+ const [, tag = "", value] = PAMLine.exec(line) || [];
590
+ const key = tag.toLowerCase();
591
+ if (key === "width" || key === "height") size[key] = toInteger(value);
592
+ if (key === "endhdr" || "width" in size && "height" in size) break;
593
+ }
594
+ return {
595
+ height: size.height,
596
+ width: size.width
597
+ };
598
+ }
599
+ };
600
+ function* readHeader(input, unit) {
601
+ const isSeparator = unit === "lines" ? isLineBreak : isWhitespace;
602
+ const values = unit === "lines" ? /[^\n\r]+/g : /[^\t-\r ]+/g;
603
+ let start = 2;
604
+ while (start < input.length) {
605
+ let end = start;
606
+ let cut = start;
607
+ let inComment = false;
608
+ while (end < input.length && (end - start < 1024 || cut === start)) {
609
+ const byte = input[end++];
610
+ inComment = byte === 35 || inComment && !isLineBreak(byte);
611
+ if (!inComment && isSeparator(byte)) cut = end;
612
+ }
613
+ if (end === input.length) cut = end;
614
+ yield* toUTF8String(input, start, cut).replace(/#[^\n\r]*/g, "\n").match(values) || [];
615
+ start = cut;
616
+ }
617
+ }
618
+ const PNM = {
619
+ validate: (input) => toUTF8String(input, 0, 2) in PNMTypes,
620
+ calculate(input) {
621
+ const signature = toUTF8String(input, 0, 2);
622
+ const type = PNMTypes[signature];
623
+ return (handlers[type] || handlers.default)(input);
624
+ }
625
+ };
626
+ const PSD = {
627
+ validate: (input) => toUTF8String(input, 0, 4) === "8BPS",
628
+ calculate: (input) => ({
629
+ height: readUInt32BE(input, 14),
630
+ width: readUInt32BE(input, 18)
631
+ })
632
+ };
633
+ const svgReg = /<svg\s([^"'>]|"[^"]*"|'[^']*')*>/;
634
+ const commentReg = /<!--[\s\S]*?(?:-->|$)/g;
635
+ const extractorRegExps = {
636
+ height: /\sheight=(["'])([^%]+?)\1/,
637
+ viewbox: /\sviewbox=(["'])([\s\S]+?)\1/i,
638
+ width: /\swidth=(["'])([^%]+?)\1/
639
+ };
640
+ const TAG_START = 60;
641
+ const TAG_END = 62;
642
+ const SVG_TAG = [
643
+ TAG_START,
644
+ 115,
645
+ 118,
646
+ 103
647
+ ];
648
+ const DOUBLE_QUOTE = 34;
649
+ const SINGLE_QUOTE = 39;
650
+ const unicodeSpaceReg = /^(?:\u00C2\u00A0|\u00E1\u009A\u0080|\u00E2\u0080[\u0080-\u008A\u00A8\u00A9\u00AF]|\u00E2\u0081\u009F|\u00E3\u0080\u0080|\u00EF\u00BB\u00BF)/;
651
+ function isSpaceAt(input, offset) {
652
+ const byte = input[offset];
653
+ if (byte < 128) return byte === 32 || byte >= 9 && byte <= 13;
654
+ const bytes = String.fromCharCode(byte, input[offset + 1], input[offset + 2]);
655
+ return unicodeSpaceReg.test(bytes);
656
+ }
657
+ const isCommentAt = (input, offset) => input[offset] === TAG_START && offset + 3 < input.length && input[offset + 1] === 33 && input[offset + 2] === 45 && input[offset + 3] === 45;
658
+ function skipComment(input, offset) {
659
+ for (let end = offset + 4; end + 2 < input.length; end++) if (input[end] === 45 && input[end + 1] === 45 && input[end + 2] === TAG_END) return end + 3;
660
+ return input.length;
661
+ }
662
+ function extractRoot(input) {
663
+ const length = input.length;
664
+ let start = 0;
665
+ let offset = 0;
666
+ let matched = 0;
667
+ while (offset < length) {
668
+ const byte = input[offset];
669
+ if (byte === TAG_START && isCommentAt(input, offset)) {
670
+ offset = skipComment(input, offset);
671
+ continue;
672
+ }
673
+ if (matched === SVG_TAG.length && isSpaceAt(input, offset)) break;
674
+ if (byte === TAG_START) {
675
+ start = offset;
676
+ matched = 1;
677
+ } else if (matched < SVG_TAG.length && byte === SVG_TAG[matched]) matched++;
678
+ else matched = 0;
679
+ offset++;
680
+ }
681
+ let quote = 0;
682
+ for (offset++; offset < length; offset++) {
683
+ const byte = input[offset];
684
+ if (byte === TAG_START && isCommentAt(input, offset)) {
685
+ offset = skipComment(input, offset) - 1;
686
+ continue;
687
+ }
688
+ if (quote) {
689
+ if (byte === quote) quote = 0;
690
+ } else if (byte === DOUBLE_QUOTE || byte === SINGLE_QUOTE) quote = byte;
691
+ else if (byte === TAG_END) break;
692
+ }
693
+ if (offset >= length) return;
694
+ try {
695
+ return toUTF8String(input, start, offset + 1).replace(commentReg, "");
696
+ } catch {
697
+ return;
698
+ }
699
+ }
700
+ const INCH_CM = 2.54;
701
+ const units = {
702
+ in: 96,
703
+ cm: 96 / INCH_CM,
704
+ em: 16,
705
+ ex: 8,
706
+ m: 96 / INCH_CM * 100,
707
+ mm: 96 / INCH_CM / 10,
708
+ pc: 16,
709
+ pt: 96 / 72,
710
+ px: 1
711
+ };
712
+ const unitsReg = new RegExp(`^([0-9.]+(?:e\\d+)?)(${Object.keys(units).join("|")})?$`);
713
+ function parseLength(len) {
714
+ const m = unitsReg.exec(len);
715
+ if (!m) return;
716
+ return Number(m[1]) * (units[m[2]] || 1);
717
+ }
718
+ function parseViewbox(viewbox) {
719
+ const bounds = viewbox.trim().split(/[\s,]+/, 4);
720
+ return {
721
+ height: parseLength(bounds[3]),
722
+ width: parseLength(bounds[2])
723
+ };
724
+ }
725
+ function parseAttributes(root) {
726
+ const width = root.match(extractorRegExps.width);
727
+ const height = root.match(extractorRegExps.height);
728
+ const viewbox = root.match(extractorRegExps.viewbox);
729
+ return {
730
+ height: height && Math.round(parseLength(height[2])),
731
+ viewbox: viewbox && parseViewbox(viewbox[2]),
732
+ width: width && Math.round(parseLength(width[2]))
733
+ };
734
+ }
735
+ function calculateByDimensions(attrs) {
736
+ return {
737
+ height: attrs.height,
738
+ width: attrs.width
739
+ };
740
+ }
741
+ function toPixels(length) {
742
+ return Math.max(1, Math.round(length));
743
+ }
744
+ function calculateByViewbox(attrs, viewbox) {
745
+ const vbWidth = viewbox.width;
746
+ const vbHeight = viewbox.height;
747
+ if (attrs.width) return {
748
+ height: toPixels(attrs.width * vbHeight / vbWidth),
749
+ width: attrs.width
750
+ };
751
+ if (attrs.height) return {
752
+ height: attrs.height,
753
+ width: toPixels(attrs.height * vbWidth / vbHeight)
754
+ };
755
+ return {
756
+ height: toPixels(vbHeight),
757
+ width: toPixels(vbWidth)
758
+ };
759
+ }
760
+ const SVG = {
761
+ validate: (input) => svgReg.test(toUTF8String(input, 0, 1e3)),
762
+ calculate(input) {
763
+ const root = extractRoot(input);
764
+ if (root) {
765
+ const attrs = parseAttributes(root);
766
+ if (attrs.width && attrs.height) return calculateByDimensions(attrs);
767
+ if (attrs.viewbox?.width && attrs.viewbox.height) return calculateByViewbox(attrs, attrs.viewbox);
768
+ }
769
+ throw new TypeError("Invalid SVG");
770
+ }
771
+ };
772
+ const TGA = {
773
+ validate(input) {
774
+ return readUInt16LE(input, 0) === 0 && readUInt16LE(input, 4) === 0;
775
+ },
776
+ calculate(input) {
777
+ return {
778
+ height: readUInt16LE(input, 14),
779
+ width: readUInt16LE(input, 12)
780
+ };
781
+ }
782
+ };
783
+ const TAG_WIDTH = 256;
784
+ const TAG_HEIGHT = 257;
785
+ const TYPE_SHORT = 3;
786
+ const TYPE_LONG = 4;
787
+ const TYPE_LONG8 = 16;
788
+ const MAX_ENTRIES = 4096;
789
+ function readUInt64(input, offset, isBigEndian) {
790
+ const high = readUInt(input, 32, offset + (isBigEndian ? 0 : 4), isBigEndian);
791
+ const low = readUInt(input, 32, offset + (isBigEndian ? 4 : 0), isBigEndian);
792
+ return high * 2 ** 32 + low;
793
+ }
794
+ function readValue(input, type, offset, isBigEndian, isBigTiff) {
795
+ switch (type) {
796
+ case TYPE_SHORT: return readUInt(input, 16, offset, isBigEndian);
797
+ case TYPE_LONG: return readUInt(input, 32, offset, isBigEndian);
798
+ case TYPE_LONG8: {
799
+ if (!isBigTiff) return;
800
+ const value = readUInt64(input, offset, isBigEndian);
801
+ if (!Number.isSafeInteger(value)) throw new TypeError("Invalid Tiff. Value too large");
802
+ return value;
803
+ }
804
+ }
805
+ }
806
+ const signatures = /* @__PURE__ */ new Set([
807
+ "49492a00",
808
+ "4d4d002a",
809
+ "49492b00",
810
+ "4d4d002b"
811
+ ]);
812
+ const TIFF = {
813
+ validate: (input) => signatures.has(toHexString(input, 0, 4)),
814
+ calculate(input) {
815
+ const isBigEndian = toUTF8String(input, 0, 2) === "MM";
816
+ const isBigTiff = readUInt(input, 16, 2, isBigEndian) === 43;
817
+ let entryCount;
818
+ let entriesOffset;
819
+ let entrySize;
820
+ if (isBigTiff) {
821
+ const byteSize = readUInt(input, 16, 4, isBigEndian);
822
+ const reserved = readUInt(input, 16, 6, isBigEndian);
823
+ if (byteSize !== 8 || reserved !== 0) throw new TypeError("Invalid BigTIFF header");
824
+ const ifdOffset = readUInt64(input, 8, isBigEndian);
825
+ if (!(ifdOffset + 8 <= input.length)) throw new TypeError("Invalid Tiff. IFD offset out of bounds");
826
+ entryCount = readUInt64(input, ifdOffset, isBigEndian);
827
+ entriesOffset = ifdOffset + 8;
828
+ entrySize = 20;
829
+ } else {
830
+ const ifdOffset = readUInt(input, 32, 4, isBigEndian);
831
+ if (!(ifdOffset + 2 <= input.length)) throw new TypeError("Invalid Tiff. IFD offset out of bounds");
832
+ entryCount = readUInt(input, 16, ifdOffset, isBigEndian);
833
+ entriesOffset = ifdOffset + 2;
834
+ entrySize = 12;
835
+ }
836
+ const tags = {};
837
+ for (let index = 0; index < Math.min(entryCount, MAX_ENTRIES); index++) {
838
+ const offset = entriesOffset + index * entrySize;
839
+ if (offset + entrySize > input.length) break;
840
+ const code = readUInt(input, 16, offset, isBigEndian);
841
+ const type = readUInt(input, 16, offset + 2, isBigEndian);
842
+ if ((isBigTiff ? readUInt64(input, offset + 4, isBigEndian) : readUInt(input, 32, offset + 4, isBigEndian)) === 1) tags[code] = readValue(input, type, offset + (isBigTiff ? 12 : 8), isBigEndian, isBigTiff);
843
+ if (tags[TAG_WIDTH] && tags[TAG_HEIGHT]) break;
844
+ }
845
+ const width = tags[TAG_WIDTH];
846
+ const height = tags[TAG_HEIGHT];
847
+ if (!width || !height) throw new TypeError("Invalid Tiff. Missing tags");
848
+ return {
849
+ height,
850
+ width
851
+ };
852
+ }
853
+ };
854
+ function calculateExtended(input) {
855
+ return {
856
+ height: 1 + readUInt24LE(input, 7),
857
+ width: 1 + readUInt24LE(input, 4)
858
+ };
859
+ }
860
+ function calculateLossless(input) {
861
+ return {
862
+ height: 1 + ((input[4] & 15) << 10 | input[3] << 2 | (input[2] & 192) >> 6),
863
+ width: 1 + ((input[2] & 63) << 8 | input[1])
864
+ };
865
+ }
866
+ function calculateLossy(input) {
867
+ return {
868
+ height: readInt16LE(input, 8) & 16383,
869
+ width: readInt16LE(input, 6) & 16383
870
+ };
871
+ }
872
+ const typeHandlers = {
873
+ bmp: BMP,
874
+ cur: CUR,
875
+ dds: DDS,
876
+ gif: GIF,
877
+ heic: HEIC,
878
+ icns: ICNS,
879
+ ico: ICO,
880
+ j2c: J2C,
881
+ jp2: JP2,
882
+ jpg: JPG,
883
+ jxl: JXL,
884
+ ktx: KTX,
885
+ png: PNG,
886
+ pnm: PNM,
887
+ psd: PSD,
888
+ svg: SVG,
889
+ tga: TGA,
890
+ tiff: TIFF,
891
+ webp: {
892
+ validate(input) {
893
+ const riffHeader = toUTF8String(input, 0, 4) === "RIFF";
894
+ const webpHeader = toUTF8String(input, 8, 12) === "WEBP";
895
+ const vp8Header = toUTF8String(input, 12, 15) === "VP8";
896
+ return riffHeader && webpHeader && vp8Header;
897
+ },
898
+ calculate(input) {
899
+ if (input.length < 30) throw new TypeError("Invalid WebP, truncated input");
900
+ const chunkHeader = toUTF8String(input, 12, 16);
901
+ input = input.slice(20, 30);
902
+ if (chunkHeader === "VP8X") {
903
+ const extendedHeader = input[0];
904
+ const validStart = (extendedHeader & 192) === 0;
905
+ const validEnd = (extendedHeader & 1) === 0;
906
+ if (validStart && validEnd) return calculateExtended(input);
907
+ else throw new TypeError("Invalid WebP");
908
+ }
909
+ if (chunkHeader === "VP8 " && input[0] !== 47) return calculateLossy(input);
910
+ const signature = toHexString(input, 3, 6);
911
+ if (chunkHeader === "VP8L" && signature !== "9d012a") return calculateLossless(input);
912
+ throw new TypeError("Invalid WebP");
913
+ }
914
+ },
915
+ avif: {
916
+ validate: (input) => detectHeifType(input) === "avif",
917
+ calculate: (input) => HEIC.calculate(input)
918
+ }
919
+ };
920
+ const keys = Object.keys(typeHandlers);
921
+ const firstBytes = {
922
+ 56: "psd",
923
+ 66: "bmp",
924
+ 68: "dds",
925
+ 71: "gif",
926
+ 73: "tiff",
927
+ 77: "tiff",
928
+ 82: "webp",
929
+ 105: "icns",
930
+ 137: "png",
931
+ 255: "jpg"
932
+ };
933
+ function detector(input) {
934
+ const byte = input[0];
935
+ if (byte in firstBytes) {
936
+ const type = firstBytes[byte];
937
+ if (type && typeHandlers[type].validate(input)) return type;
938
+ }
939
+ return keys.find((key) => typeHandlers[key].validate(input));
940
+ }
941
+ function imageMeta(input) {
942
+ if (!(input instanceof Uint8Array)) throw new TypeError("Input should be a Uint8Array");
943
+ const type = detector(input);
944
+ if (type !== void 0 && type in typeHandlers) {
945
+ const size = typeHandlers[type].calculate(input);
946
+ if (size !== void 0) {
947
+ if (size.images && size.images.length > 1) {
948
+ for (const image of size.images) if (image.width * image.height > size.width * size.height) {
949
+ size.width = image.width;
950
+ size.height = image.height;
951
+ }
952
+ }
953
+ if (!Number.isSafeInteger(size.width) || !Number.isSafeInteger(size.height)) throw new TypeError(`Invalid ${type}, truncated or corrupt input`);
954
+ if (size.width <= 0 || size.height < 0) throw new TypeError(`Invalid ${type}, zero or negative size`);
955
+ size.type = type;
956
+ return size;
957
+ }
958
+ }
959
+ throw new TypeError(`Unsupported file type: ${type}`);
960
+ }
961
+
962
+ function VArg(argument) {
963
+ return destr__default(argument);
964
+ }
965
+ function parseArgs(arguments_, mappers) {
966
+ const vargs = arguments_.split("_");
967
+ return mappers.map((v, index) => v(vargs[index]));
968
+ }
969
+ function getHandler(key) {
970
+ return Handlers[key];
971
+ }
972
+ function applyHandler(context, pipe, handler, argumentsString) {
973
+ const arguments_ = handler.args ? parseArgs(argumentsString, handler.args) : [];
974
+ return handler.apply(context, pipe, ...arguments_);
975
+ }
976
+ function clampDimensionsPreservingAspectRatio(sourceDimensions, desiredDimensions) {
977
+ const desiredAspectRatio = desiredDimensions.width / desiredDimensions.height;
978
+ let { width, height } = desiredDimensions;
979
+ if (sourceDimensions.width && width > sourceDimensions.width) {
980
+ width = sourceDimensions.width;
981
+ height = Math.round(sourceDimensions.width / desiredAspectRatio);
982
+ }
983
+ if (sourceDimensions.height && height > sourceDimensions.height) {
984
+ height = sourceDimensions.height;
985
+ width = Math.round(sourceDimensions.height * desiredAspectRatio);
986
+ }
987
+ return { width, height };
988
+ }
989
+
990
+ const quality = {
991
+ args: [VArg],
992
+ order: -1,
993
+ apply: (context, _pipe, quality2) => {
994
+ context.quality = quality2;
995
+ }
996
+ };
997
+ const fit = {
998
+ args: [VArg],
999
+ order: -1,
1000
+ apply: (context, _pipe, fit2) => {
1001
+ context.fit = fit2;
1002
+ }
1003
+ };
1004
+ const position = {
1005
+ args: [VArg],
1006
+ order: -1,
1007
+ apply: (context, _pipe, position2) => {
1008
+ context.position = position2;
1009
+ }
1010
+ };
1011
+ const HEX_RE = /^([\da-f]{2})([\da-f]{2})([\da-f]{2})$/i;
1012
+ const SHORTHEX_RE = /^([\da-f])([\da-f])([\da-f])$/i;
1013
+ const background = {
1014
+ args: [VArg],
1015
+ order: -1,
1016
+ apply: (context, _pipe, background2) => {
1017
+ background2 = String(background2);
1018
+ if (!background2.startsWith("#") && (HEX_RE.test(background2) || SHORTHEX_RE.test(background2))) {
1019
+ background2 = "#" + background2;
1020
+ }
1021
+ context.background = background2;
1022
+ }
1023
+ };
1024
+ const enlarge = {
1025
+ args: [],
1026
+ apply: (context) => {
1027
+ context.enlarge = true;
1028
+ }
1029
+ };
1030
+ const kernel = {
1031
+ args: [VArg],
1032
+ apply: (context, _pipe, kernel2) => {
1033
+ context.kernel = kernel2;
1034
+ }
1035
+ };
1036
+ const width = {
1037
+ args: [VArg],
1038
+ apply: (context, pipe, width2) => {
1039
+ return pipe.resize(width2, void 0, {
1040
+ withoutEnlargement: !context.enlarge
1041
+ });
1042
+ }
1043
+ };
1044
+ const height = {
1045
+ args: [VArg],
1046
+ apply: (context, pipe, height2) => {
1047
+ return pipe.resize(void 0, height2, {
1048
+ withoutEnlargement: !context.enlarge
1049
+ });
1050
+ }
1051
+ };
1052
+ const resize = {
1053
+ args: [VArg, VArg, VArg],
1054
+ apply: (context, pipe, size) => {
1055
+ let [width2, height2] = String(size).split("x").map(Number);
1056
+ if (!width2) {
1057
+ return;
1058
+ }
1059
+ if (!height2) {
1060
+ height2 = width2;
1061
+ }
1062
+ if (!context.enlarge) {
1063
+ const clamped = clampDimensionsPreservingAspectRatio(context.meta, {
1064
+ width: width2,
1065
+ height: height2
1066
+ });
1067
+ width2 = clamped.width;
1068
+ height2 = clamped.height;
1069
+ }
1070
+ return pipe.resize(width2, height2, {
1071
+ fit: context.fit,
1072
+ position: context.position,
1073
+ background: context.background,
1074
+ kernel: context.kernel
1075
+ });
1076
+ }
1077
+ };
1078
+ const trim = {
1079
+ args: [VArg],
1080
+ apply: (_context, pipe, threshold2) => {
1081
+ return pipe.trim(threshold2);
1082
+ }
1083
+ };
1084
+ const extend = {
1085
+ args: [VArg, VArg, VArg, VArg],
1086
+ apply: (context, pipe, top, right, bottom, left) => {
1087
+ return pipe.extend({
1088
+ top,
1089
+ left,
1090
+ bottom,
1091
+ right,
1092
+ background: context.background
1093
+ });
1094
+ }
1095
+ };
1096
+ const extract = {
1097
+ args: [VArg, VArg, VArg, VArg],
1098
+ apply: (_context, pipe, left, top, width2, height2) => {
1099
+ return pipe.extract({
1100
+ left,
1101
+ top,
1102
+ width: width2,
1103
+ height: height2
1104
+ });
1105
+ }
1106
+ };
1107
+ const rotate = {
1108
+ args: [VArg],
1109
+ apply: (context, pipe, angel) => {
1110
+ return pipe.rotate(angel, {
1111
+ background: context.background
1112
+ });
1113
+ }
1114
+ };
1115
+ const flip = {
1116
+ args: [],
1117
+ apply: (_context, pipe) => {
1118
+ return pipe.flip();
1119
+ }
1120
+ };
1121
+ const flop = {
1122
+ args: [],
1123
+ apply: (_context, pipe) => {
1124
+ return pipe.flop();
1125
+ }
1126
+ };
1127
+ const sharpen = {
1128
+ args: [VArg, VArg, VArg],
1129
+ apply: (_context, pipe, sigma, flat, jagged) => {
1130
+ return pipe.sharpen(sigma, flat, jagged);
1131
+ }
1132
+ };
1133
+ const median = {
1134
+ args: [VArg, VArg, VArg],
1135
+ apply: (_context, pipe, size) => {
1136
+ return pipe.median(size);
1137
+ }
1138
+ };
1139
+ const blur = {
1140
+ args: [VArg, VArg, VArg],
1141
+ apply: (_context, pipe, sigma) => {
1142
+ return pipe.blur(sigma);
1143
+ }
1144
+ };
1145
+ const flatten = {
1146
+ args: [VArg, VArg, VArg],
1147
+ apply: (context, pipe) => {
1148
+ return pipe.flatten({
1149
+ background: context.background
1150
+ });
1151
+ }
1152
+ };
1153
+ const gamma = {
1154
+ args: [VArg, VArg, VArg],
1155
+ apply: (_context, pipe, gamma2, gammaOut) => {
1156
+ return pipe.gamma(gamma2, gammaOut);
1157
+ }
1158
+ };
1159
+ const negate = {
1160
+ args: [VArg, VArg, VArg],
1161
+ apply: (_context, pipe) => {
1162
+ return pipe.negate();
1163
+ }
1164
+ };
1165
+ const normalize = {
1166
+ args: [VArg, VArg, VArg],
1167
+ apply: (_context, pipe) => {
1168
+ return pipe.normalize();
1169
+ }
1170
+ };
1171
+ const threshold = {
1172
+ args: [VArg],
1173
+ apply: (_context, pipe, threshold2) => {
1174
+ return pipe.threshold(threshold2);
1175
+ }
1176
+ };
1177
+ const modulate = {
1178
+ args: [VArg],
1179
+ apply: (_context, pipe, brightness, saturation, hue) => {
1180
+ return pipe.modulate({
1181
+ brightness,
1182
+ saturation,
1183
+ hue
1184
+ });
1185
+ }
1186
+ };
1187
+ const tint = {
1188
+ args: [VArg],
1189
+ apply: (_context, pipe, rgb) => {
1190
+ return pipe.tint(rgb);
1191
+ }
1192
+ };
1193
+ const grayscale = {
1194
+ args: [VArg],
1195
+ apply: (_context, pipe) => {
1196
+ return pipe.grayscale();
1197
+ }
1198
+ };
1199
+ const crop = extract;
1200
+ const q = quality;
1201
+ const b = background;
1202
+ const w = width;
1203
+ const h = height;
1204
+ const s = resize;
1205
+ const pos = position;
1206
+
1207
+ function getEnv(name) {
1208
+ return name in process.env ? destr__default(process.env[name]) : void 0;
1209
+ }
1210
+ function cachedPromise(function_) {
1211
+ let p;
1212
+ return (...arguments_) => {
1213
+ if (p) {
1214
+ return p;
1215
+ }
1216
+ p = Promise.resolve(function_(...arguments_));
1217
+ return p;
1218
+ };
1219
+ }
1220
+
1221
+ const SUPPORTED_FORMATS = /* @__PURE__ */ new Set([
1222
+ "jpeg",
1223
+ "png",
1224
+ "webp",
1225
+ "avif",
1226
+ "tiff",
1227
+ "heif",
1228
+ "gif",
1229
+ "heic"
1230
+ ]);
1231
+ function createIPX(userOptions) {
1232
+ const options = defu.defu(userOptions, {
1233
+ alias: getEnv("IPX_ALIAS") || {},
1234
+ maxAge: getEnv("IPX_MAX_AGE") ?? 60,
1235
+ sharpOptions: {
1236
+ jpegProgressive: true
1237
+ }
1238
+ });
1239
+ options.alias = Object.fromEntries(
1240
+ Object.entries(options.alias || {}).map((e) => [
1241
+ ufo.withLeadingSlash(e[0]),
1242
+ e[1]
1243
+ ])
1244
+ );
1245
+ const getSharp = cachedPromise(async () => {
1246
+ return await import('sharp').then(
1247
+ (r) => r.default || r
1248
+ );
1249
+ });
1250
+ const getSVGO = cachedPromise(async () => {
1251
+ const { optimize } = await import('svgo');
1252
+ return { optimize };
1253
+ });
1254
+ return function ipx(id, modifiers = {}, opts = {}) {
1255
+ if (!id) {
1256
+ throw h3.createError({
1257
+ statusCode: 400,
1258
+ statusText: `IPX_MISSING_ID`,
1259
+ message: `Resource id is missing`
1260
+ });
1261
+ }
1262
+ id = ufo.hasProtocol(id) ? id : ufo.withLeadingSlash(id);
1263
+ for (const base in options.alias) {
1264
+ if (id.startsWith(base)) {
1265
+ id = ufo.joinURL(options.alias[base], id.slice(base.length));
1266
+ }
1267
+ }
1268
+ const storage = ufo.hasProtocol(id) ? options.httpStorage || options.storage : options.storage || options.httpStorage;
1269
+ if (!storage) {
1270
+ throw h3.createError({
1271
+ statusCode: 500,
1272
+ statusText: `IPX_NO_STORAGE`,
1273
+ message: "No storage configured!"
1274
+ });
1275
+ }
1276
+ const getSourceMeta = cachedPromise(async () => {
1277
+ const sourceMeta = await storage.getMeta(id, opts);
1278
+ if (!sourceMeta) {
1279
+ throw h3.createError({
1280
+ statusCode: 404,
1281
+ statusText: `IPX_RESOURCE_NOT_FOUND`,
1282
+ message: `Resource not found: ${id}`
1283
+ });
1284
+ }
1285
+ const _maxAge = sourceMeta.maxAge ?? options.maxAge;
1286
+ return {
1287
+ maxAge: typeof _maxAge === "string" ? Number.parseInt(_maxAge) : _maxAge,
1288
+ mtime: sourceMeta.mtime ? new Date(sourceMeta.mtime) : void 0
1289
+ };
1290
+ });
1291
+ const getSourceData = cachedPromise(async () => {
1292
+ const sourceData = await storage.getData(id, opts);
1293
+ if (!sourceData) {
1294
+ throw h3.createError({
1295
+ statusCode: 404,
1296
+ statusText: `IPX_RESOURCE_NOT_FOUND`,
1297
+ message: `Resource not found: ${id}`
1298
+ });
1299
+ }
1300
+ return Buffer.from(sourceData);
1301
+ });
1302
+ const process = cachedPromise(async () => {
1303
+ const sourceData = await getSourceData();
1304
+ let imageMeta$1;
1305
+ try {
1306
+ imageMeta$1 = imageMeta(sourceData);
1307
+ } catch {
1308
+ throw h3.createError({
1309
+ statusCode: 400,
1310
+ statusText: `IPX_INVALID_IMAGE`,
1311
+ message: `Cannot parse image metadata: ${id}`
1312
+ });
1313
+ }
1314
+ let mFormat = modifiers.f || modifiers.format;
1315
+ if (mFormat === "jpg") {
1316
+ mFormat = "jpeg";
1317
+ }
1318
+ const format = mFormat && SUPPORTED_FORMATS.has(mFormat) ? mFormat : SUPPORTED_FORMATS.has(imageMeta$1.type || "") ? imageMeta$1.type : "jpeg";
1319
+ if (imageMeta$1.type === "svg" && !mFormat) {
1320
+ if (options.svgo === false) {
1321
+ return {
1322
+ data: sourceData,
1323
+ format: "svg+xml",
1324
+ meta: imageMeta$1
1325
+ };
1326
+ } else {
1327
+ const { optimize } = await getSVGO();
1328
+ const svg = optimize(sourceData.toString("utf8"), {
1329
+ ...options.svgo,
1330
+ plugins: ["removeScripts", ...options.svgo?.plugins || []]
1331
+ }).data;
1332
+ return {
1333
+ data: svg,
1334
+ format: "svg+xml",
1335
+ meta: imageMeta$1
1336
+ };
1337
+ }
1338
+ }
1339
+ const animated = modifiers.animated !== void 0 || modifiers.a !== void 0 || format === "gif";
1340
+ const Sharp = await getSharp();
1341
+ let sharp = Sharp(sourceData, { animated, ...options.sharpOptions });
1342
+ Object.assign(
1343
+ sharp.options,
1344
+ options.sharpOptions
1345
+ );
1346
+ const handlers = Object.entries(modifiers).map(([name, arguments_]) => ({
1347
+ handler: getHandler(name),
1348
+ name,
1349
+ args: arguments_
1350
+ })).filter((h) => h.handler).sort((a, b) => {
1351
+ const aKey = (a.handler.order || a.name || "").toString();
1352
+ const bKey = (b.handler.order || b.name || "").toString();
1353
+ return aKey.localeCompare(bKey);
1354
+ });
1355
+ const handlerContext = { meta: imageMeta$1 };
1356
+ for (const h of handlers) {
1357
+ sharp = applyHandler(handlerContext, sharp, h.handler, h.args) || sharp;
1358
+ }
1359
+ if (SUPPORTED_FORMATS.has(format || "")) {
1360
+ sharp = sharp.toFormat(format, {
1361
+ quality: handlerContext.quality
1362
+ });
1363
+ }
1364
+ const processedImage = await sharp.toBuffer();
1365
+ return {
1366
+ data: processedImage,
1367
+ format,
1368
+ meta: imageMeta$1
1369
+ };
1370
+ });
1371
+ return {
1372
+ getSourceMeta,
1373
+ process
1374
+ };
1375
+ };
1376
+ }
1377
+
1378
+ const MODIFIER_SEP = /[&,]/g;
1379
+ const MODIFIER_VAL_SEP = /[:=_]/;
1380
+ function createIPXH3Handler(ipx) {
1381
+ const _handler = async (event) => {
1382
+ const [modifiersString = "", ...idSegments] = event.path.slice(
1383
+ 1
1384
+ /* leading slash */
1385
+ ).split("/");
1386
+ const id = safeString(ufo.decode(idSegments.join("/")));
1387
+ if (!modifiersString) {
1388
+ throw h3.createError({
1389
+ statusCode: 400,
1390
+ statusText: `IPX_MISSING_MODIFIERS`,
1391
+ message: `Modifiers are missing: ${id}`
1392
+ });
1393
+ }
1394
+ if (!id || id === "/") {
1395
+ throw h3.createError({
1396
+ statusCode: 400,
1397
+ statusText: `IPX_MISSING_ID`,
1398
+ message: `Resource id is missing: ${event.path}`
1399
+ });
1400
+ }
1401
+ const modifiers = /* @__PURE__ */ Object.create(null);
1402
+ if (modifiersString !== "_") {
1403
+ for (const p of modifiersString.split(MODIFIER_SEP)) {
1404
+ const [key, ...values] = p.split(MODIFIER_VAL_SEP);
1405
+ modifiers[safeString(key)] = values.map((v) => safeString(ufo.decode(v))).join("_");
1406
+ }
1407
+ }
1408
+ const mFormat = modifiers.f || modifiers.format;
1409
+ if (mFormat === "auto") {
1410
+ const acceptHeader = h3.getRequestHeader(event, "accept") || "";
1411
+ const animated = modifiers.animated ?? modifiers.a;
1412
+ const autoFormat = autoDetectFormat(
1413
+ acceptHeader,
1414
+ // #234 "animated" param adds {animated: ''} to the modifiers
1415
+ // TODO: fix modifiers to normalized to boolean
1416
+ !!animated || animated === ""
1417
+ );
1418
+ delete modifiers.f;
1419
+ delete modifiers.format;
1420
+ if (autoFormat) {
1421
+ modifiers.format = autoFormat;
1422
+ h3.appendResponseHeader(event, "vary", "Accept");
1423
+ }
1424
+ }
1425
+ const img = ipx(id, modifiers);
1426
+ const sourceMeta = await img.getSourceMeta();
1427
+ sendResponseHeaderIfNotSet(
1428
+ event,
1429
+ "content-security-policy",
1430
+ "default-src 'none'"
1431
+ );
1432
+ if (sourceMeta.mtime) {
1433
+ sendResponseHeaderIfNotSet(
1434
+ event,
1435
+ "last-modified",
1436
+ sourceMeta.mtime.toUTCString()
1437
+ );
1438
+ const _ifModifiedSince = h3.getRequestHeader(event, "if-modified-since");
1439
+ if (_ifModifiedSince && new Date(_ifModifiedSince) >= sourceMeta.mtime) {
1440
+ h3.setResponseStatus(event, 304);
1441
+ return h3.send(event);
1442
+ }
1443
+ }
1444
+ const { data, format } = await img.process();
1445
+ if (typeof sourceMeta.maxAge === "number") {
1446
+ sendResponseHeaderIfNotSet(
1447
+ event,
1448
+ "cache-control",
1449
+ `max-age=${+sourceMeta.maxAge}, public, s-maxage=${+sourceMeta.maxAge}`
1450
+ );
1451
+ }
1452
+ const etag = getEtag__default(data);
1453
+ sendResponseHeaderIfNotSet(event, "etag", etag);
1454
+ if (etag && h3.getRequestHeader(event, "if-none-match") === etag) {
1455
+ h3.setResponseStatus(event, 304);
1456
+ return h3.send(event);
1457
+ }
1458
+ if (format) {
1459
+ sendResponseHeaderIfNotSet(event, "content-type", `image/${format}`);
1460
+ }
1461
+ return data;
1462
+ };
1463
+ return h3.defineEventHandler(async (event) => {
1464
+ try {
1465
+ return await _handler(event);
1466
+ } catch (_error) {
1467
+ const error = h3.createError(_error);
1468
+ h3.setResponseStatus(event, error.statusCode, error.statusMessage);
1469
+ return {
1470
+ error: {
1471
+ message: `[${error.statusCode}] [${error.statusMessage || "IPX_ERROR"}] ${error.message}`
1472
+ }
1473
+ };
1474
+ }
1475
+ });
1476
+ }
1477
+ function createIPXH3App(ipx) {
1478
+ const app = h3.createApp({ debug: true });
1479
+ app.use(createIPXH3Handler(ipx));
1480
+ return app;
1481
+ }
1482
+ function createIPXWebServer(ipx) {
1483
+ return h3.toWebHandler(createIPXH3App(ipx));
1484
+ }
1485
+ function createIPXNodeServer(ipx) {
1486
+ return h3.toNodeListener(createIPXH3App(ipx));
1487
+ }
1488
+ function createIPXPlainServer(ipx) {
1489
+ return h3.toPlainHandler(createIPXH3App(ipx));
1490
+ }
1491
+ function sendResponseHeaderIfNotSet(event, name, value) {
1492
+ if (!h3.getResponseHeader(event, name)) {
1493
+ h3.setResponseHeader(event, name, value);
1494
+ }
1495
+ }
1496
+ function autoDetectFormat(acceptHeader, animated) {
1497
+ if (animated) {
1498
+ const acceptMime2 = acceptNegotiator.negotiate(acceptHeader, ["image/webp", "image/gif"]);
1499
+ return acceptMime2?.split("/")[1] || "gif";
1500
+ }
1501
+ const acceptMime = acceptNegotiator.negotiate(acceptHeader, [
1502
+ "image/avif",
1503
+ "image/webp",
1504
+ "image/jpeg",
1505
+ "image/png",
1506
+ "image/tiff",
1507
+ "image/heif",
1508
+ "image/gif"
1509
+ ]);
1510
+ return acceptMime?.split("/")[1] || "jpeg";
1511
+ }
1512
+ function safeString(input) {
1513
+ return JSON.stringify(input).replace(/^"|"$/g, "").replace(/\\+/g, "\\").replace(/\\"/g, '"');
1514
+ }
1515
+
1516
+ function ipxFSStorage(_options = {}) {
1517
+ const dirs = resolveDirs(_options.dir);
1518
+ const maxAge = _options.maxAge || getEnv("IPX_FS_MAX_AGE");
1519
+ const _getFS = cachedPromise(
1520
+ () => import('node:fs/promises').catch(() => {
1521
+ throw h3.createError({
1522
+ statusCode: 500,
1523
+ statusText: `IPX_FILESYSTEM_ERROR`,
1524
+ message: `Failed to resolve filesystem module`
1525
+ });
1526
+ })
1527
+ );
1528
+ const resolveFile = async (id) => {
1529
+ const fs = await _getFS();
1530
+ for (const dir of dirs) {
1531
+ const filePath = pathe.join(dir, id);
1532
+ if (!isValidPath(filePath) || !filePath.startsWith(dir + "/")) {
1533
+ throw h3.createError({
1534
+ statusCode: 403,
1535
+ statusText: `IPX_FORBIDDEN_PATH`,
1536
+ message: `Forbidden path: ${id}`
1537
+ });
1538
+ }
1539
+ try {
1540
+ const stats = await fs.stat(filePath);
1541
+ if (!stats.isFile()) {
1542
+ continue;
1543
+ }
1544
+ return {
1545
+ stats,
1546
+ read: () => fs.readFile(filePath)
1547
+ };
1548
+ } catch (error) {
1549
+ if (error.code === "ENOENT") {
1550
+ continue;
1551
+ }
1552
+ throw h3.createError({
1553
+ statusCode: 403,
1554
+ statusText: `IPX_FORBIDDEN_FILE`,
1555
+ message: `Cannot access file: ${id}`
1556
+ });
1557
+ }
1558
+ }
1559
+ throw h3.createError({
1560
+ statusCode: 404,
1561
+ statusText: `IPX_FILE_NOT_FOUND`,
1562
+ message: `File not found: ${id}`
1563
+ });
1564
+ };
1565
+ return {
1566
+ name: "ipx:node-fs",
1567
+ async getMeta(id) {
1568
+ const { stats } = await resolveFile(id);
1569
+ return {
1570
+ mtime: stats.mtime,
1571
+ maxAge
1572
+ };
1573
+ },
1574
+ async getData(id) {
1575
+ const { read } = await resolveFile(id);
1576
+ return read();
1577
+ }
1578
+ };
1579
+ }
1580
+ const isWindows = process.platform === "win32";
1581
+ function isValidPath(fp) {
1582
+ if (isWindows) {
1583
+ fp = fp.slice(pathe.parse(fp).root.length);
1584
+ }
1585
+ if (/["*:<>?|]/.test(fp)) {
1586
+ return false;
1587
+ }
1588
+ return true;
1589
+ }
1590
+ function resolveDirs(dirs) {
1591
+ if (!dirs || !Array.isArray(dirs)) {
1592
+ const dir = pathe.resolve(dirs || getEnv("IPX_FS_DIR") || ".");
1593
+ return [dir];
1594
+ }
1595
+ return dirs.map((dirs2) => pathe.resolve(dirs2));
1596
+ }
1597
+
1598
+ const HTTP_RE = /^https?:\/\//;
1599
+ function ipxHttpStorage(_options = {}) {
1600
+ const allowAllDomains = _options.allowAllDomains ?? getEnv("IPX_HTTP_ALLOW_ALL_DOMAINS") ?? false;
1601
+ let _domains = _options.domains || getEnv("IPX_HTTP_DOMAINS") || [];
1602
+ const defaultMaxAge = _options.maxAge || getEnv("IPX_HTTP_MAX_AGE") || 300;
1603
+ const fetchOptions = _options.fetchOptions || getEnv("IPX_HTTP_FETCH_OPTIONS") || {};
1604
+ if (typeof _domains === "string") {
1605
+ _domains = _domains.split(",").map((s) => s.trim());
1606
+ }
1607
+ const domains = new Set(
1608
+ _domains.map((d) => {
1609
+ if (!HTTP_RE.test(d)) {
1610
+ d = "http://" + d;
1611
+ }
1612
+ return new URL(d).hostname;
1613
+ }).filter(Boolean)
1614
+ );
1615
+ function validateId(id) {
1616
+ const url = new URL(decodeURIComponent(id));
1617
+ if (!url.hostname) {
1618
+ throw h3.createError({
1619
+ statusCode: 403,
1620
+ statusText: `IPX_MISSING_HOSTNAME`,
1621
+ message: `Hostname is missing: ${id}`
1622
+ });
1623
+ }
1624
+ if (!allowAllDomains && !domains.has(url.hostname)) {
1625
+ throw h3.createError({
1626
+ statusCode: 403,
1627
+ statusText: `IPX_FORBIDDEN_HOST`,
1628
+ message: `Forbidden host: ${url.hostname}`
1629
+ });
1630
+ }
1631
+ return url.toString();
1632
+ }
1633
+ function parseResponse(response) {
1634
+ let maxAge = defaultMaxAge;
1635
+ if (_options.ignoreCacheControl !== true) {
1636
+ const _cacheControl = response.headers.get("cache-control");
1637
+ if (_cacheControl) {
1638
+ const m = _cacheControl.match(/max-age=(\d+)/);
1639
+ if (m && m[1]) {
1640
+ maxAge = Number.parseInt(m[1]);
1641
+ }
1642
+ }
1643
+ }
1644
+ let mtime;
1645
+ const _lastModified = response.headers.get("last-modified");
1646
+ if (_lastModified) {
1647
+ mtime = new Date(_lastModified);
1648
+ }
1649
+ return { maxAge, mtime };
1650
+ }
1651
+ return {
1652
+ name: "ipx:http",
1653
+ async getMeta(id) {
1654
+ const url = validateId(id);
1655
+ try {
1656
+ const response = await ofetch.ofetch.raw(url, {
1657
+ ...fetchOptions,
1658
+ method: "HEAD"
1659
+ });
1660
+ const { maxAge, mtime } = parseResponse(response);
1661
+ return { mtime, maxAge };
1662
+ } catch {
1663
+ return {};
1664
+ }
1665
+ },
1666
+ async getData(id) {
1667
+ const url = validateId(id);
1668
+ const response = await ofetch.ofetch(url, {
1669
+ ...fetchOptions,
1670
+ method: "GET",
1671
+ responseType: "arrayBuffer"
1672
+ });
1673
+ return response;
1674
+ }
1675
+ };
1676
+ }
1677
+
1678
+ exports.createIPX = createIPX;
1679
+ exports.createIPXH3App = createIPXH3App;
1680
+ exports.createIPXH3Handler = createIPXH3Handler;
1681
+ exports.createIPXNodeServer = createIPXNodeServer;
1682
+ exports.createIPXPlainServer = createIPXPlainServer;
1683
+ exports.createIPXWebServer = createIPXWebServer;
1684
+ exports.ipxFSStorage = ipxFSStorage;
1685
+ exports.ipxHttpStorage = ipxHttpStorage;