modern-pdf-lib 0.37.0 → 0.38.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 +3 -3
- package/dist/browser.cjs +15 -1
- package/dist/browser.d.cts +2 -2
- package/dist/browser.d.mts +2 -2
- package/dist/browser.mjs +2 -2
- package/dist/{index-n0WBuf3b.d.mts → index-CDg5gwYg.d.mts} +414 -4
- package/dist/index-CDg5gwYg.d.mts.map +1 -0
- package/dist/{index-BnS4WpNK.d.cts → index-CNtfRlyP.d.cts} +414 -4
- package/dist/index-CNtfRlyP.d.cts.map +1 -0
- package/dist/index.cjs +15 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +2 -2
- package/dist/{src-CbPUzRbg.cjs → src-B0gmgVEs.cjs} +884 -19
- package/dist/{src-DGxzt-jG.mjs → src-DlRTrvWg.mjs} +801 -20
- package/package.json +1 -1
- package/dist/index-BnS4WpNK.d.cts.map +0 -1
- package/dist/index-n0WBuf3b.d.mts.map +0 -1
|
@@ -9599,7 +9599,7 @@ function readU16$1(data, offset, le) {
|
|
|
9599
9599
|
* Read a 32-bit unsigned integer.
|
|
9600
9600
|
* @internal
|
|
9601
9601
|
*/
|
|
9602
|
-
function readU32$
|
|
9602
|
+
function readU32$2(data, offset, le) {
|
|
9603
9603
|
return getView(data).getUint32(offset, le);
|
|
9604
9604
|
}
|
|
9605
9605
|
/**
|
|
@@ -9614,8 +9614,8 @@ function parseIfd(data, offset, le) {
|
|
|
9614
9614
|
if (entryOffset + 12 > data.length) break;
|
|
9615
9615
|
const tag = readU16$1(data, entryOffset, le);
|
|
9616
9616
|
const type = readU16$1(data, entryOffset + 2, le);
|
|
9617
|
-
const cnt = readU32$
|
|
9618
|
-
const valueOrOffset = readU32$
|
|
9617
|
+
const cnt = readU32$2(data, entryOffset + 4, le);
|
|
9618
|
+
const valueOrOffset = readU32$2(data, entryOffset + 8, le);
|
|
9619
9619
|
entries.push({
|
|
9620
9620
|
tag,
|
|
9621
9621
|
type,
|
|
@@ -9653,7 +9653,7 @@ function readOffsetArray(data, entry, le) {
|
|
|
9653
9653
|
const pos = offset + i * elementSize;
|
|
9654
9654
|
if (pos + elementSize > data.length) break;
|
|
9655
9655
|
if (entry.type === 3) values.push(readU16$1(data, pos, le));
|
|
9656
|
-
else values.push(readU32$
|
|
9656
|
+
else values.push(readU32$2(data, pos, le));
|
|
9657
9657
|
}
|
|
9658
9658
|
return values;
|
|
9659
9659
|
}
|
|
@@ -9662,13 +9662,13 @@ function readOffsetArray(data, entry, le) {
|
|
|
9662
9662
|
* @internal
|
|
9663
9663
|
*/
|
|
9664
9664
|
function findIfdOffset(data, le, page) {
|
|
9665
|
-
let ifdOffset = readU32$
|
|
9665
|
+
let ifdOffset = readU32$2(data, 4, le);
|
|
9666
9666
|
for (let i = 0; i < page; i++) {
|
|
9667
9667
|
if (ifdOffset === 0 || ifdOffset + 2 > data.length) throw new Error(`TIFF page ${page} not found (only ${i} pages available)`);
|
|
9668
9668
|
const entryCount = readU16$1(data, ifdOffset, le);
|
|
9669
9669
|
const nextIfdOffset = ifdOffset + 2 + entryCount * 12;
|
|
9670
9670
|
if (nextIfdOffset + 4 > data.length) throw new Error(`TIFF page ${page} not found (only ${i + 1} pages available)`);
|
|
9671
|
-
ifdOffset = readU32$
|
|
9671
|
+
ifdOffset = readU32$2(data, nextIfdOffset, le);
|
|
9672
9672
|
if (ifdOffset === 0) throw new Error(`TIFF page ${page} not found (only ${i + 1} pages available)`);
|
|
9673
9673
|
}
|
|
9674
9674
|
return ifdOffset;
|
|
@@ -9725,7 +9725,7 @@ function canDirectEmbed(data) {
|
|
|
9725
9725
|
if (!isLE && !isBE) return false;
|
|
9726
9726
|
const le = isLE;
|
|
9727
9727
|
if (readU16$1(data, 2, le) !== 42) return false;
|
|
9728
|
-
const ifdOffset = readU32$
|
|
9728
|
+
const ifdOffset = readU32$2(data, 4, le);
|
|
9729
9729
|
if (ifdOffset === 0 || ifdOffset + 2 > data.length) return false;
|
|
9730
9730
|
const compression = getTag(parseIfd(data, ifdOffset, le), TAG_COMPRESSION, data, le) ?? 1;
|
|
9731
9731
|
return compression === COMPRESSION_NONE || compression === COMPRESSION_DEFLATE || compression === COMPRESSION_DEFLATE_PKZIP || compression === COMPRESSION_JPEG || compression === COMPRESSION_OLD_JPEG;
|
|
@@ -25147,7 +25147,7 @@ function readU16(data, offset) {
|
|
|
25147
25147
|
/**
|
|
25148
25148
|
* Read a 32-bit big-endian unsigned integer.
|
|
25149
25149
|
*/
|
|
25150
|
-
function readU32(data, offset) {
|
|
25150
|
+
function readU32$1(data, offset) {
|
|
25151
25151
|
return (data[offset] << 24 | data[offset + 1] << 16 | data[offset + 2] << 8 | data[offset + 3]) >>> 0;
|
|
25152
25152
|
}
|
|
25153
25153
|
/**
|
|
@@ -25158,7 +25158,7 @@ function findCodestreamStart(data) {
|
|
|
25158
25158
|
if (data.length >= 12 && data[0] === 0 && data[1] === 0 && data[2] === 0 && data[3] === 12 && data[4] === 106 && data[5] === 80) {
|
|
25159
25159
|
let offset = 0;
|
|
25160
25160
|
while (offset + 8 <= data.length) {
|
|
25161
|
-
const boxLen = readU32(data, offset);
|
|
25161
|
+
const boxLen = readU32$1(data, offset);
|
|
25162
25162
|
if (String.fromCharCode(data[offset + 4], data[offset + 5], data[offset + 6], data[offset + 7]) === "jp2c") return offset + 8;
|
|
25163
25163
|
const advance = boxLen === 0 ? data.length - offset : boxLen < 8 ? 8 : boxLen;
|
|
25164
25164
|
offset += advance;
|
|
@@ -25184,14 +25184,14 @@ function parseTileInfo(data) {
|
|
|
25184
25184
|
if (sizOffset < 0) throw new Error("JPEG2000 Tiles: SIZ marker (0xFF51) not found");
|
|
25185
25185
|
const base = sizOffset + 2;
|
|
25186
25186
|
if (base + 38 > data.length) throw new Error("JPEG2000 Tiles: SIZ marker segment is truncated");
|
|
25187
|
-
const xsiz = readU32(data, base + 4);
|
|
25188
|
-
const ysiz = readU32(data, base + 8);
|
|
25189
|
-
const xosiz = readU32(data, base + 12);
|
|
25190
|
-
const yosiz = readU32(data, base + 16);
|
|
25191
|
-
const xtsiz = readU32(data, base + 20);
|
|
25192
|
-
const ytsiz = readU32(data, base + 24);
|
|
25193
|
-
const xtosiz = readU32(data, base + 28);
|
|
25194
|
-
const ytosiz = readU32(data, base + 32);
|
|
25187
|
+
const xsiz = readU32$1(data, base + 4);
|
|
25188
|
+
const ysiz = readU32$1(data, base + 8);
|
|
25189
|
+
const xosiz = readU32$1(data, base + 12);
|
|
25190
|
+
const yosiz = readU32$1(data, base + 16);
|
|
25191
|
+
const xtsiz = readU32$1(data, base + 20);
|
|
25192
|
+
const ytsiz = readU32$1(data, base + 24);
|
|
25193
|
+
const xtosiz = readU32$1(data, base + 28);
|
|
25194
|
+
const ytosiz = readU32$1(data, base + 32);
|
|
25195
25195
|
const csiz = readU16(data, base + 36);
|
|
25196
25196
|
const imageWidth = xsiz - xosiz;
|
|
25197
25197
|
const imageHeight = ysiz - yosiz;
|
|
@@ -25231,7 +25231,7 @@ function findTileParts(data, csStart) {
|
|
|
25231
25231
|
if (offset + 12 > data.length) break;
|
|
25232
25232
|
const lsot = readU16(data, offset + 2);
|
|
25233
25233
|
const isot = readU16(data, offset + 4);
|
|
25234
|
-
const psot = readU32(data, offset + 6);
|
|
25234
|
+
const psot = readU32$1(data, offset + 6);
|
|
25235
25235
|
let sodOffset = offset + 2 + lsot;
|
|
25236
25236
|
while (sodOffset + 1 < data.length) {
|
|
25237
25237
|
if (data[sodOffset] === 255 && data[sodOffset + 1] === 147) break;
|
|
@@ -31386,6 +31386,787 @@ function rgbToLab(r, g, b) {
|
|
|
31386
31386
|
];
|
|
31387
31387
|
}
|
|
31388
31388
|
//#endregion
|
|
31389
|
+
//#region src/assets/image/nextGenImageDetect.ts
|
|
31390
|
+
/** AVIF file-level brands (AV1 Image File Format §4). */
|
|
31391
|
+
const AVIF_BRANDS = /* @__PURE__ */ new Set(["avif", "avis"]);
|
|
31392
|
+
/** HEVC-coded HEIF brands (ISO/IEC 23008-12 Annex B). */
|
|
31393
|
+
const HEIC_BRANDS = /* @__PURE__ */ new Set([
|
|
31394
|
+
"heic",
|
|
31395
|
+
"heix",
|
|
31396
|
+
"heim",
|
|
31397
|
+
"heis",
|
|
31398
|
+
"hevc",
|
|
31399
|
+
"hevx"
|
|
31400
|
+
]);
|
|
31401
|
+
/** Generic (codec-agnostic) HEIF brands (ISO/IEC 23008-12 Annex B). */
|
|
31402
|
+
const HEIF_BRANDS = /* @__PURE__ */ new Set(["mif1", "msf1"]);
|
|
31403
|
+
/** Read a 4-byte big-endian unsigned integer at `offset`. */
|
|
31404
|
+
function readU32(bytes, offset) {
|
|
31405
|
+
const b0 = bytes[offset] ?? 0;
|
|
31406
|
+
const b1 = bytes[offset + 1] ?? 0;
|
|
31407
|
+
const b2 = bytes[offset + 2] ?? 0;
|
|
31408
|
+
const b3 = bytes[offset + 3] ?? 0;
|
|
31409
|
+
return b0 * 16777216 + (b1 << 16 | b2 << 8 | b3);
|
|
31410
|
+
}
|
|
31411
|
+
/** Decode a 4-byte FourCC at `offset` as an ASCII string. */
|
|
31412
|
+
function readFourCC(bytes, offset) {
|
|
31413
|
+
return String.fromCharCode(bytes[offset] ?? 0, bytes[offset + 1] ?? 0, bytes[offset + 2] ?? 0, bytes[offset + 3] ?? 0);
|
|
31414
|
+
}
|
|
31415
|
+
/**
|
|
31416
|
+
* Parse an ISOBMFF box header starting at `offset` (ISO/IEC 14496-12 §4.2).
|
|
31417
|
+
*
|
|
31418
|
+
* Handles the standard 32-bit `size`, the 64-bit `largesize` (`size == 1`),
|
|
31419
|
+
* and the to-EOF form (`size == 0`). Returns `null` on a malformed / truncated
|
|
31420
|
+
* header.
|
|
31421
|
+
*/
|
|
31422
|
+
function parseBoxHeader(bytes, offset, end) {
|
|
31423
|
+
if (offset + 8 > end) return null;
|
|
31424
|
+
let size = readU32(bytes, offset);
|
|
31425
|
+
const type = readFourCC(bytes, offset + 4);
|
|
31426
|
+
let headerLen = 8;
|
|
31427
|
+
if (size === 1) {
|
|
31428
|
+
if (offset + 16 > end) return null;
|
|
31429
|
+
const high = readU32(bytes, offset + 8);
|
|
31430
|
+
const low = readU32(bytes, offset + 12);
|
|
31431
|
+
size = high * 4294967296 + low;
|
|
31432
|
+
headerLen = 16;
|
|
31433
|
+
} else if (size === 0) size = end - offset;
|
|
31434
|
+
const boxEnd = offset + size;
|
|
31435
|
+
if (size < headerLen || boxEnd > end) return null;
|
|
31436
|
+
return {
|
|
31437
|
+
type,
|
|
31438
|
+
contentStart: offset + headerLen,
|
|
31439
|
+
boxEnd
|
|
31440
|
+
};
|
|
31441
|
+
}
|
|
31442
|
+
/**
|
|
31443
|
+
* Find a direct child box of a given `type` within [start, end).
|
|
31444
|
+
* Returns the matching {@link BoxHeader} or `null`.
|
|
31445
|
+
*/
|
|
31446
|
+
function findChildBox(bytes, start, end, type) {
|
|
31447
|
+
let cursor = start;
|
|
31448
|
+
while (cursor + 8 <= end) {
|
|
31449
|
+
const header = parseBoxHeader(bytes, cursor, end);
|
|
31450
|
+
if (header === null) return null;
|
|
31451
|
+
if (header.type === type) return header;
|
|
31452
|
+
if (header.boxEnd <= cursor) return null;
|
|
31453
|
+
cursor = header.boxEnd;
|
|
31454
|
+
}
|
|
31455
|
+
return null;
|
|
31456
|
+
}
|
|
31457
|
+
/** Verify a buffer begins with the given byte sequence. */
|
|
31458
|
+
function startsWith(bytes, sig) {
|
|
31459
|
+
if (bytes.length < sig.length) return false;
|
|
31460
|
+
for (let i = 0; i < sig.length; i++) if (bytes[i] !== sig[i]) return false;
|
|
31461
|
+
return true;
|
|
31462
|
+
}
|
|
31463
|
+
/** JPEG XL bare-codestream signature `FF 0A` (ISO/IEC 18181-1). */
|
|
31464
|
+
const JXL_CODESTREAM_SIG = [255, 10];
|
|
31465
|
+
/**
|
|
31466
|
+
* JPEG XL ISOBMFF container signature box:
|
|
31467
|
+
* `00 00 00 0C 'JXL ' 0D 0A 87 0A` (ISO/IEC 18181-1 / 18181-2).
|
|
31468
|
+
*/
|
|
31469
|
+
const JXL_CONTAINER_SIG = [
|
|
31470
|
+
0,
|
|
31471
|
+
0,
|
|
31472
|
+
0,
|
|
31473
|
+
12,
|
|
31474
|
+
74,
|
|
31475
|
+
88,
|
|
31476
|
+
76,
|
|
31477
|
+
32,
|
|
31478
|
+
13,
|
|
31479
|
+
10,
|
|
31480
|
+
135,
|
|
31481
|
+
10
|
|
31482
|
+
];
|
|
31483
|
+
/**
|
|
31484
|
+
* Inspect an ISOBMFF `ftyp` box and classify its brand set into a next-gen
|
|
31485
|
+
* format. Precedence: AVIF > HEIC > HEIF. Returns `null` if no brand matches.
|
|
31486
|
+
*/
|
|
31487
|
+
function classifyFtypBrands(bytes, ftyp) {
|
|
31488
|
+
const brands = [];
|
|
31489
|
+
if (ftyp.contentStart + 4 <= ftyp.boxEnd) brands.push(readFourCC(bytes, ftyp.contentStart));
|
|
31490
|
+
for (let off = ftyp.contentStart + 8; off + 4 <= ftyp.boxEnd; off += 4) brands.push(readFourCC(bytes, off));
|
|
31491
|
+
if (brands.some((b) => AVIF_BRANDS.has(b))) return "avif";
|
|
31492
|
+
if (brands.some((b) => HEIC_BRANDS.has(b))) return "heic";
|
|
31493
|
+
if (brands.some((b) => HEIF_BRANDS.has(b))) return "heif";
|
|
31494
|
+
return null;
|
|
31495
|
+
}
|
|
31496
|
+
/** Locate the top-level `ftyp` box (it must be the first box). */
|
|
31497
|
+
function findFtyp(bytes) {
|
|
31498
|
+
if (bytes.length < 8 || readFourCC(bytes, 4) !== "ftyp") return null;
|
|
31499
|
+
return parseBoxHeader(bytes, 0, bytes.length);
|
|
31500
|
+
}
|
|
31501
|
+
/**
|
|
31502
|
+
* Detect a next-generation image format from raw bytes.
|
|
31503
|
+
*
|
|
31504
|
+
* Recognizes AVIF / HEIC / HEIF via the ISOBMFF `ftyp` brand set, and JPEG XL
|
|
31505
|
+
* via either the bare codestream signature (`FF 0A`) or the container
|
|
31506
|
+
* signature box (`00 00 00 0C 'JXL ' 0D 0A 87 0A`).
|
|
31507
|
+
*
|
|
31508
|
+
* @param bytes Raw image file bytes.
|
|
31509
|
+
* @returns The detected {@link NextGenFormat}, or `null` if unrecognized.
|
|
31510
|
+
*/
|
|
31511
|
+
function detectNextGenFormat(bytes) {
|
|
31512
|
+
if (startsWith(bytes, JXL_CODESTREAM_SIG)) return "jpegxl";
|
|
31513
|
+
if (startsWith(bytes, JXL_CONTAINER_SIG)) return "jpegxl";
|
|
31514
|
+
const ftyp = findFtyp(bytes);
|
|
31515
|
+
if (ftyp === null) return null;
|
|
31516
|
+
return classifyFtypBrands(bytes, ftyp);
|
|
31517
|
+
}
|
|
31518
|
+
/**
|
|
31519
|
+
* Walk an ISOBMFF box tree to `meta` > `iprp` > `ipco` and read the first
|
|
31520
|
+
* `ispe` (dimensions) and `pixi` (bit depth) property boxes.
|
|
31521
|
+
*/
|
|
31522
|
+
function probeIsobmffDimensions(bytes) {
|
|
31523
|
+
const result = {};
|
|
31524
|
+
const ftyp = findFtyp(bytes);
|
|
31525
|
+
if (ftyp === null) return result;
|
|
31526
|
+
const meta = findChildBox(bytes, ftyp.boxEnd, bytes.length, "meta");
|
|
31527
|
+
if (meta === null) return result;
|
|
31528
|
+
const metaChildren = meta.contentStart + 4;
|
|
31529
|
+
if (metaChildren > meta.boxEnd) return result;
|
|
31530
|
+
const iprp = findChildBox(bytes, metaChildren, meta.boxEnd, "iprp");
|
|
31531
|
+
if (iprp === null) return result;
|
|
31532
|
+
const ipco = findChildBox(bytes, iprp.contentStart, iprp.boxEnd, "ipco");
|
|
31533
|
+
if (ipco === null) return result;
|
|
31534
|
+
const ispe = findChildBox(bytes, ipco.contentStart, ipco.boxEnd, "ispe");
|
|
31535
|
+
if (ispe !== null && ispe.contentStart + 12 <= ispe.boxEnd) {
|
|
31536
|
+
result.width = readU32(bytes, ispe.contentStart + 4);
|
|
31537
|
+
result.height = readU32(bytes, ispe.contentStart + 8);
|
|
31538
|
+
}
|
|
31539
|
+
const pixi = findChildBox(bytes, ipco.contentStart, ipco.boxEnd, "pixi");
|
|
31540
|
+
if (pixi !== null && pixi.contentStart + 5 <= pixi.boxEnd) {
|
|
31541
|
+
const numChannels = bytes[pixi.contentStart + 4] ?? 0;
|
|
31542
|
+
if (numChannels > 0 && pixi.contentStart + 5 + numChannels <= pixi.boxEnd) result.bitDepth = bytes[pixi.contentStart + 5] ?? 0;
|
|
31543
|
+
}
|
|
31544
|
+
return result;
|
|
31545
|
+
}
|
|
31546
|
+
const REASON_AVIF = "AVIF detected; pure-JS AV1 decoding is not bundled — decode/convert to PNG or JPEG, or register a WASM decoder.";
|
|
31547
|
+
const REASON_HEIC = "HEIC detected; pure-JS HEVC decoding is not bundled — decode/convert to PNG or JPEG, or register a WASM decoder.";
|
|
31548
|
+
const REASON_HEIF = "HEIF detected; pure-JS HEVC/AV1 decoding is not bundled — decode/convert to PNG or JPEG, or register a WASM decoder.";
|
|
31549
|
+
const REASON_JXL = "JPEG XL detected; pure-JS JPEG XL decoding is not bundled — decode/convert to PNG or JPEG, or register a WASM decoder.";
|
|
31550
|
+
/** Map a detected format to its non-decodable explanation. */
|
|
31551
|
+
function reasonFor(format) {
|
|
31552
|
+
switch (format) {
|
|
31553
|
+
case "avif": return REASON_AVIF;
|
|
31554
|
+
case "heic": return REASON_HEIC;
|
|
31555
|
+
case "heif": return REASON_HEIF;
|
|
31556
|
+
case "jpegxl": return REASON_JXL;
|
|
31557
|
+
}
|
|
31558
|
+
}
|
|
31559
|
+
/**
|
|
31560
|
+
* Probe a next-generation image for container metadata WITHOUT decoding pixels.
|
|
31561
|
+
*
|
|
31562
|
+
* For AVIF / HEIC / HEIF, walks the ISOBMFF box tree
|
|
31563
|
+
* (`meta` > `iprp` > `ipco` > `ispe`/`pixi`) to recover width, height, and
|
|
31564
|
+
* bit depth when present. For JPEG XL, the format is identified but dimensions
|
|
31565
|
+
* are not parsed (the `SizeHeader` is a packed-bit codestream field and is
|
|
31566
|
+
* intentionally scoped out here); only `format` is returned.
|
|
31567
|
+
*
|
|
31568
|
+
* The returned {@link NextGenImageInfo} **always** has `decodable: false`.
|
|
31569
|
+
* No pixel data is ever produced.
|
|
31570
|
+
*
|
|
31571
|
+
* @param bytes Raw image file bytes.
|
|
31572
|
+
* @returns A {@link NextGenImageInfo}, or `null` if not a next-gen image.
|
|
31573
|
+
*/
|
|
31574
|
+
function probeNextGenImage(bytes) {
|
|
31575
|
+
const format = detectNextGenFormat(bytes);
|
|
31576
|
+
if (format === null) return null;
|
|
31577
|
+
const info = {
|
|
31578
|
+
format,
|
|
31579
|
+
decodable: false,
|
|
31580
|
+
reason: reasonFor(format)
|
|
31581
|
+
};
|
|
31582
|
+
if (format === "jpegxl") return info;
|
|
31583
|
+
const dims = probeIsobmffDimensions(bytes);
|
|
31584
|
+
if (dims.width !== void 0) info.width = dims.width;
|
|
31585
|
+
if (dims.height !== void 0) info.height = dims.height;
|
|
31586
|
+
if (dims.bitDepth !== void 0) info.bitDepth = dims.bitDepth;
|
|
31587
|
+
return info;
|
|
31588
|
+
}
|
|
31589
|
+
//#endregion
|
|
31590
|
+
//#region src/assets/image/imageDecoderRegistry.ts
|
|
31591
|
+
/**
|
|
31592
|
+
* The single, module-scoped decoder registry, keyed by normalized format name.
|
|
31593
|
+
*/
|
|
31594
|
+
const registry = /* @__PURE__ */ new Map();
|
|
31595
|
+
/**
|
|
31596
|
+
* Normalize a format key: trim surrounding whitespace and lowercase it, so that
|
|
31597
|
+
* `'AVIF'`, `' avif '`, and `'avif'` all map to the same entry.
|
|
31598
|
+
*
|
|
31599
|
+
* @param format The caller-supplied format key.
|
|
31600
|
+
* @returns The normalized key.
|
|
31601
|
+
*/
|
|
31602
|
+
function normalizeFormat(format) {
|
|
31603
|
+
return format.trim().toLowerCase();
|
|
31604
|
+
}
|
|
31605
|
+
/**
|
|
31606
|
+
* Register a decoder for a given image format.
|
|
31607
|
+
*
|
|
31608
|
+
* If a decoder is already registered for the (normalized) format, it is
|
|
31609
|
+
* replaced.
|
|
31610
|
+
*
|
|
31611
|
+
* @param format Format key, e.g. `'avif'`, `'heic'`, `'jpegxl'`. The key is
|
|
31612
|
+
* normalized (trimmed + lowercased) before storage.
|
|
31613
|
+
* @param decoder The {@link ImageDecoder} that turns encoded bytes into RGBA.
|
|
31614
|
+
*/
|
|
31615
|
+
function registerImageDecoder(format, decoder) {
|
|
31616
|
+
registry.set(normalizeFormat(format), decoder);
|
|
31617
|
+
}
|
|
31618
|
+
/**
|
|
31619
|
+
* Remove the decoder registered for a given format, if any.
|
|
31620
|
+
*
|
|
31621
|
+
* This is a no-op when no decoder is registered for the (normalized) format.
|
|
31622
|
+
*
|
|
31623
|
+
* @param format Format key; normalized (trimmed + lowercased) before lookup.
|
|
31624
|
+
*/
|
|
31625
|
+
function unregisterImageDecoder(format) {
|
|
31626
|
+
registry.delete(normalizeFormat(format));
|
|
31627
|
+
}
|
|
31628
|
+
/**
|
|
31629
|
+
* Report whether a decoder is currently registered for a given format.
|
|
31630
|
+
*
|
|
31631
|
+
* @param format Format key; normalized (trimmed + lowercased) before lookup.
|
|
31632
|
+
* @returns `true` if a decoder is registered, otherwise `false`.
|
|
31633
|
+
*/
|
|
31634
|
+
function hasImageDecoder(format) {
|
|
31635
|
+
return registry.has(normalizeFormat(format));
|
|
31636
|
+
}
|
|
31637
|
+
/**
|
|
31638
|
+
* Get the decoder registered for a given format, if any.
|
|
31639
|
+
*
|
|
31640
|
+
* @param format Format key; normalized (trimmed + lowercased) before lookup.
|
|
31641
|
+
* @returns The registered {@link ImageDecoder}, or `undefined` if none.
|
|
31642
|
+
*/
|
|
31643
|
+
function getImageDecoder(format) {
|
|
31644
|
+
return registry.get(normalizeFormat(format));
|
|
31645
|
+
}
|
|
31646
|
+
/**
|
|
31647
|
+
* Validate that a value returned by a decoder is a well-formed
|
|
31648
|
+
* {@link DecodedRasterImage}.
|
|
31649
|
+
*
|
|
31650
|
+
* Throws a descriptive error (naming the format) if the result is malformed:
|
|
31651
|
+
* not an object, non-positive-integer dimensions, a non-`Uint8Array` `rgba`, or
|
|
31652
|
+
* an `rgba` length that does not equal `width * height * 4`.
|
|
31653
|
+
*
|
|
31654
|
+
* @param format Normalized format key, used only for error messages.
|
|
31655
|
+
* @param result The value returned by the decoder.
|
|
31656
|
+
* @returns The same value, narrowed to {@link DecodedRasterImage}.
|
|
31657
|
+
*/
|
|
31658
|
+
function validateDecodedResult(format, result) {
|
|
31659
|
+
if (result === null || typeof result !== "object") throw new Error(`Image decoder for format "${format}" returned a malformed result: expected an object with { width, height, rgba }, got ${typeof result}.`);
|
|
31660
|
+
const { width, height, rgba } = result;
|
|
31661
|
+
if (typeof width !== "number" || !Number.isInteger(width) || width <= 0) throw new Error(`Image decoder for format "${format}" returned a malformed result: width must be a positive integer, got ${String(width)}.`);
|
|
31662
|
+
if (typeof height !== "number" || !Number.isInteger(height) || height <= 0) throw new Error(`Image decoder for format "${format}" returned a malformed result: height must be a positive integer, got ${String(height)}.`);
|
|
31663
|
+
if (!(rgba instanceof Uint8Array)) throw new Error(`Image decoder for format "${format}" returned a malformed result: rgba must be a Uint8Array, got ${rgba === null ? "null" : typeof rgba}.`);
|
|
31664
|
+
const expected = width * height * 4;
|
|
31665
|
+
if (rgba.length !== expected) throw new Error(`Image decoder for format "${format}" returned a malformed result: rgba length ${rgba.length} does not match width*height*4 = ${width}*${height}*4 = ${expected}.`);
|
|
31666
|
+
return {
|
|
31667
|
+
width,
|
|
31668
|
+
height,
|
|
31669
|
+
rgba
|
|
31670
|
+
};
|
|
31671
|
+
}
|
|
31672
|
+
/**
|
|
31673
|
+
* Decode raw image bytes using a previously-registered decoder for `format`.
|
|
31674
|
+
*
|
|
31675
|
+
* Looks up the decoder by (normalized) format key, awaits its result, validates
|
|
31676
|
+
* the result shape (`rgba.length === width * height * 4`, positive-integer
|
|
31677
|
+
* dimensions, `Uint8Array` pixel buffer), and returns it.
|
|
31678
|
+
*
|
|
31679
|
+
* This function does **not** decode anything itself — it dispatches to the
|
|
31680
|
+
* consumer-supplied {@link ImageDecoder}. If no decoder is registered for the
|
|
31681
|
+
* format, it throws an error naming the format and explaining how to register
|
|
31682
|
+
* one via {@link registerImageDecoder}.
|
|
31683
|
+
*
|
|
31684
|
+
* @param format Format key, e.g. `'avif'`; normalized before lookup.
|
|
31685
|
+
* @param bytes Raw, encoded image bytes, forwarded verbatim to the decoder.
|
|
31686
|
+
* @returns A validated {@link DecodedRasterImage}.
|
|
31687
|
+
* @throws If no decoder is registered for the format, or if the
|
|
31688
|
+
* registered decoder returns a malformed result.
|
|
31689
|
+
*/
|
|
31690
|
+
async function decodeRegisteredImage(format, bytes) {
|
|
31691
|
+
const normalized = normalizeFormat(format);
|
|
31692
|
+
const decoder = registry.get(normalized);
|
|
31693
|
+
if (decoder === void 0) throw new Error(`No image decoder registered for format "${normalized}". This library does not bundle a decoder for this format; supply one with registerImageDecoder(${JSON.stringify(normalized)}, decoder), where decoder turns the encoded bytes into RGBA8888 ({ width, height, rgba }).`);
|
|
31694
|
+
return validateDecodedResult(normalized, await decoder(bytes));
|
|
31695
|
+
}
|
|
31696
|
+
//#endregion
|
|
31697
|
+
//#region src/assets/image/svgFilters.ts
|
|
31698
|
+
/** Clamp a number to the closed interval [0, 255] and round to an integer. */
|
|
31699
|
+
function clamp8(v) {
|
|
31700
|
+
if (v <= 0) return 0;
|
|
31701
|
+
if (v >= 255) return 255;
|
|
31702
|
+
return Math.round(v);
|
|
31703
|
+
}
|
|
31704
|
+
/** Allocate a transparent-black buffer of the given dimensions. */
|
|
31705
|
+
function allocBuffer(width, height) {
|
|
31706
|
+
return {
|
|
31707
|
+
width,
|
|
31708
|
+
height,
|
|
31709
|
+
rgba: new Uint8Array(width * height * 4)
|
|
31710
|
+
};
|
|
31711
|
+
}
|
|
31712
|
+
/** Throw if two buffers differ in dimensions (an op needs them equal). */
|
|
31713
|
+
function assertSameSize(a, b) {
|
|
31714
|
+
if (a.width !== b.width || a.height !== b.height) throw new Error(`svgFilters: buffer size mismatch (${a.width}x${a.height} vs ${b.width}x${b.height})`);
|
|
31715
|
+
}
|
|
31716
|
+
/**
|
|
31717
|
+
* Convert a straight RGBA8888 buffer to a premultiplied Float32Array in the
|
|
31718
|
+
* 0..1 range, channel order R,G,B,A. Premultiplying = colour × alpha.
|
|
31719
|
+
*/
|
|
31720
|
+
function toPremultipliedF32(src) {
|
|
31721
|
+
const n = src.width * src.height;
|
|
31722
|
+
const out = new Float32Array(n * 4);
|
|
31723
|
+
for (let i = 0; i < n; i++) {
|
|
31724
|
+
const a = src.rgba[i * 4 + 3] / 255;
|
|
31725
|
+
out[i * 4 + 0] = src.rgba[i * 4 + 0] / 255 * a;
|
|
31726
|
+
out[i * 4 + 1] = src.rgba[i * 4 + 1] / 255 * a;
|
|
31727
|
+
out[i * 4 + 2] = src.rgba[i * 4 + 2] / 255 * a;
|
|
31728
|
+
out[i * 4 + 3] = a;
|
|
31729
|
+
}
|
|
31730
|
+
return out;
|
|
31731
|
+
}
|
|
31732
|
+
/**
|
|
31733
|
+
* Convert a premultiplied Float32Array (0..1) back to a straight RGBA8888
|
|
31734
|
+
* RasterBuffer, un-premultiplying (colour ÷ alpha) and clamping.
|
|
31735
|
+
*/
|
|
31736
|
+
function fromPremultipliedF32(buf, width, height) {
|
|
31737
|
+
const out = allocBuffer(width, height);
|
|
31738
|
+
const n = width * height;
|
|
31739
|
+
for (let i = 0; i < n; i++) {
|
|
31740
|
+
const a = buf[i * 4 + 3];
|
|
31741
|
+
if (a <= 0) {
|
|
31742
|
+
out.rgba[i * 4 + 0] = 0;
|
|
31743
|
+
out.rgba[i * 4 + 1] = 0;
|
|
31744
|
+
out.rgba[i * 4 + 2] = 0;
|
|
31745
|
+
out.rgba[i * 4 + 3] = 0;
|
|
31746
|
+
} else {
|
|
31747
|
+
out.rgba[i * 4 + 0] = clamp8(buf[i * 4 + 0] / a * 255);
|
|
31748
|
+
out.rgba[i * 4 + 1] = clamp8(buf[i * 4 + 1] / a * 255);
|
|
31749
|
+
out.rgba[i * 4 + 2] = clamp8(buf[i * 4 + 2] / a * 255);
|
|
31750
|
+
out.rgba[i * 4 + 3] = clamp8(a * 255);
|
|
31751
|
+
}
|
|
31752
|
+
}
|
|
31753
|
+
return out;
|
|
31754
|
+
}
|
|
31755
|
+
/**
|
|
31756
|
+
* Produce a buffer filled with a single constant colour.
|
|
31757
|
+
*
|
|
31758
|
+
* Implements `feFlood` (SVG 1.1 §15.12): every pixel is set to the given
|
|
31759
|
+
* straight RGBA8888 colour.
|
|
31760
|
+
*
|
|
31761
|
+
* @param width Output width in pixels (must be > 0).
|
|
31762
|
+
* @param height Output height in pixels (must be > 0).
|
|
31763
|
+
* @param rgba The fill colour as `[R, G, B, A]`, each 0..255.
|
|
31764
|
+
* @returns A new buffer of `width × height` filled with `rgba`.
|
|
31765
|
+
*/
|
|
31766
|
+
function feFlood(width, height, rgba) {
|
|
31767
|
+
if (width <= 0 || height <= 0 || !Number.isInteger(width) || !Number.isInteger(height)) throw new Error(`svgFilters.feFlood: invalid dimensions ${width}x${height}`);
|
|
31768
|
+
const out = allocBuffer(width, height);
|
|
31769
|
+
const r = clamp8(rgba[0]);
|
|
31770
|
+
const g = clamp8(rgba[1]);
|
|
31771
|
+
const b = clamp8(rgba[2]);
|
|
31772
|
+
const a = clamp8(rgba[3]);
|
|
31773
|
+
const n = width * height;
|
|
31774
|
+
for (let i = 0; i < n; i++) {
|
|
31775
|
+
out.rgba[i * 4 + 0] = r;
|
|
31776
|
+
out.rgba[i * 4 + 1] = g;
|
|
31777
|
+
out.rgba[i * 4 + 2] = b;
|
|
31778
|
+
out.rgba[i * 4 + 3] = a;
|
|
31779
|
+
}
|
|
31780
|
+
return out;
|
|
31781
|
+
}
|
|
31782
|
+
/**
|
|
31783
|
+
* Apply a 4×5 colour matrix to every pixel.
|
|
31784
|
+
*
|
|
31785
|
+
* Implements `feColorMatrix` with `type="matrix"` (SVG 1.1 §15.10). The 20
|
|
31786
|
+
* values are row-major:
|
|
31787
|
+
*
|
|
31788
|
+
* ```
|
|
31789
|
+
* | R' | | m0 m1 m2 m3 m4 | | R |
|
|
31790
|
+
* | G' | | m5 m6 m7 m8 m9 | | G |
|
|
31791
|
+
* | B' | = | m10 m11 m12 m13 m14 | · | B |
|
|
31792
|
+
* | A' | | m15 m16 m17 m18 m19 | | A |
|
|
31793
|
+
* | 1 | ( the implicit identity row ) | 1 |
|
|
31794
|
+
* ```
|
|
31795
|
+
*
|
|
31796
|
+
* Channels are normalised to 0..1, the matrix is applied to the **straight**
|
|
31797
|
+
* (un-premultiplied) `[R, G, B, A, 1]` vector, then the result is scaled back
|
|
31798
|
+
* to 0..255 with clamping.
|
|
31799
|
+
*
|
|
31800
|
+
* @param src Source buffer.
|
|
31801
|
+
* @param matrix Exactly 20 numbers (4 rows × 5 columns).
|
|
31802
|
+
* @returns A new buffer with the matrix applied.
|
|
31803
|
+
* @throws If `matrix` does not contain exactly 20 values.
|
|
31804
|
+
*/
|
|
31805
|
+
function feColorMatrix(src, matrix) {
|
|
31806
|
+
if (matrix.length !== 20) throw new Error(`svgFilters.feColorMatrix: matrix must have 20 values, got ${matrix.length}`);
|
|
31807
|
+
const m = matrix;
|
|
31808
|
+
const out = allocBuffer(src.width, src.height);
|
|
31809
|
+
const n = src.width * src.height;
|
|
31810
|
+
for (let i = 0; i < n; i++) {
|
|
31811
|
+
const r = src.rgba[i * 4 + 0] / 255;
|
|
31812
|
+
const g = src.rgba[i * 4 + 1] / 255;
|
|
31813
|
+
const b = src.rgba[i * 4 + 2] / 255;
|
|
31814
|
+
const a = src.rgba[i * 4 + 3] / 255;
|
|
31815
|
+
const nr = m[0] * r + m[1] * g + m[2] * b + m[3] * a + m[4];
|
|
31816
|
+
const ng = m[5] * r + m[6] * g + m[7] * b + m[8] * a + m[9];
|
|
31817
|
+
const nb = m[10] * r + m[11] * g + m[12] * b + m[13] * a + m[14];
|
|
31818
|
+
const na = m[15] * r + m[16] * g + m[17] * b + m[18] * a + m[19];
|
|
31819
|
+
out.rgba[i * 4 + 0] = clamp8(nr * 255);
|
|
31820
|
+
out.rgba[i * 4 + 1] = clamp8(ng * 255);
|
|
31821
|
+
out.rgba[i * 4 + 2] = clamp8(nb * 255);
|
|
31822
|
+
out.rgba[i * 4 + 3] = clamp8(na * 255);
|
|
31823
|
+
}
|
|
31824
|
+
return out;
|
|
31825
|
+
}
|
|
31826
|
+
/**
|
|
31827
|
+
* Apply the `saturate` shorthand colour matrix.
|
|
31828
|
+
*
|
|
31829
|
+
* Implements `feColorMatrix type="saturate"` (SVG 1.1 §15.10). The luma
|
|
31830
|
+
* coefficients are the spec's exact constants 0.213 (R), 0.715 (G),
|
|
31831
|
+
* 0.072 (B). `s = 1` is the identity; `s = 0` fully desaturates each pixel to
|
|
31832
|
+
* its luma (so R = G = B); `s > 1` over-saturates.
|
|
31833
|
+
*
|
|
31834
|
+
* The spec saturate matrix (rows R, G, B; alpha untouched):
|
|
31835
|
+
* ```
|
|
31836
|
+
* | 0.213+0.787s 0.715-0.715s 0.072-0.072s 0 0 |
|
|
31837
|
+
* | 0.213-0.213s 0.715+0.285s 0.072-0.072s 0 0 |
|
|
31838
|
+
* | 0.213-0.213s 0.715-0.715s 0.072+0.928s 0 0 |
|
|
31839
|
+
* | 0 0 0 1 0 |
|
|
31840
|
+
* ```
|
|
31841
|
+
*
|
|
31842
|
+
* @param src Source buffer.
|
|
31843
|
+
* @param s Saturation factor (0 = greyscale, 1 = identity, >1 = boosted).
|
|
31844
|
+
* @returns A new buffer with the saturate matrix applied.
|
|
31845
|
+
*/
|
|
31846
|
+
function feColorMatrixSaturate(src, s) {
|
|
31847
|
+
const rl = .213;
|
|
31848
|
+
const gl = .715;
|
|
31849
|
+
const bl = .072;
|
|
31850
|
+
return feColorMatrix(src, [
|
|
31851
|
+
rl + .787 * s,
|
|
31852
|
+
gl - gl * s,
|
|
31853
|
+
bl - bl * s,
|
|
31854
|
+
0,
|
|
31855
|
+
0,
|
|
31856
|
+
rl - rl * s,
|
|
31857
|
+
gl + .285 * s,
|
|
31858
|
+
bl - bl * s,
|
|
31859
|
+
0,
|
|
31860
|
+
0,
|
|
31861
|
+
rl - rl * s,
|
|
31862
|
+
gl - gl * s,
|
|
31863
|
+
bl + .928 * s,
|
|
31864
|
+
0,
|
|
31865
|
+
0,
|
|
31866
|
+
0,
|
|
31867
|
+
0,
|
|
31868
|
+
0,
|
|
31869
|
+
1,
|
|
31870
|
+
0
|
|
31871
|
+
]);
|
|
31872
|
+
}
|
|
31873
|
+
/**
|
|
31874
|
+
* Shift the image by an integer offset, leaving exposed edges transparent.
|
|
31875
|
+
*
|
|
31876
|
+
* Implements `feOffset` (SVG 1.1 §15.15). Pixels are copied to
|
|
31877
|
+
* `(x + dx, y + dy)`; destinations that fall outside the buffer are dropped
|
|
31878
|
+
* and exposed areas remain transparent black. `dx` / `dy` are rounded to the
|
|
31879
|
+
* nearest integer pixel.
|
|
31880
|
+
*
|
|
31881
|
+
* @param src Source buffer.
|
|
31882
|
+
* @param dx Horizontal offset in pixels (positive = right).
|
|
31883
|
+
* @param dy Vertical offset in pixels (positive = down).
|
|
31884
|
+
* @returns A new buffer of the same size with the shifted content.
|
|
31885
|
+
*/
|
|
31886
|
+
function feOffset(src, dx, dy) {
|
|
31887
|
+
const out = allocBuffer(src.width, src.height);
|
|
31888
|
+
const idx = Math.round(dx);
|
|
31889
|
+
const idy = Math.round(dy);
|
|
31890
|
+
const { width, height } = src;
|
|
31891
|
+
for (let y = 0; y < height; y++) {
|
|
31892
|
+
const sy = y + idy;
|
|
31893
|
+
if (sy < 0 || sy >= height) continue;
|
|
31894
|
+
for (let x = 0; x < width; x++) {
|
|
31895
|
+
const sx = x + idx;
|
|
31896
|
+
if (sx < 0 || sx >= width) continue;
|
|
31897
|
+
const di = (sy * width + sx) * 4;
|
|
31898
|
+
const si = (y * width + x) * 4;
|
|
31899
|
+
out.rgba[di + 0] = src.rgba[si + 0];
|
|
31900
|
+
out.rgba[di + 1] = src.rgba[si + 1];
|
|
31901
|
+
out.rgba[di + 2] = src.rgba[si + 2];
|
|
31902
|
+
out.rgba[di + 3] = src.rgba[si + 3];
|
|
31903
|
+
}
|
|
31904
|
+
}
|
|
31905
|
+
return out;
|
|
31906
|
+
}
|
|
31907
|
+
/**
|
|
31908
|
+
* Compute the box size `d` for the three-box-blur approximation.
|
|
31909
|
+
*
|
|
31910
|
+
* SVG 1.1 §15.17: `d = floor(s · 3 · √(2π) / 4 + 0.5)`.
|
|
31911
|
+
*/
|
|
31912
|
+
function boxBlurD(stdDev) {
|
|
31913
|
+
return Math.floor(stdDev * 3 * Math.sqrt(2 * Math.PI) / 4 + .5);
|
|
31914
|
+
}
|
|
31915
|
+
/**
|
|
31916
|
+
* One horizontal box blur of the given (odd) length, premultiplied float
|
|
31917
|
+
* data, with an asymmetric left/right radius to support the even-`d` rule.
|
|
31918
|
+
* The window covers offsets `[-leftRadius, +rightRadius]` inclusive. Edge
|
|
31919
|
+
* samples are clamped (extend-edge) rather than treated as transparent so a
|
|
31920
|
+
* flat region stays flat.
|
|
31921
|
+
*/
|
|
31922
|
+
function boxBlurH(data, width, height, leftRadius, rightRadius) {
|
|
31923
|
+
const out = new Float32Array(data.length);
|
|
31924
|
+
const windowLen = leftRadius + rightRadius + 1;
|
|
31925
|
+
for (let y = 0; y < height; y++) {
|
|
31926
|
+
const row = y * width;
|
|
31927
|
+
for (let c = 0; c < 4; c++) {
|
|
31928
|
+
let sum = 0;
|
|
31929
|
+
for (let k = -leftRadius; k <= rightRadius; k++) {
|
|
31930
|
+
const cx = k < 0 ? 0 : k >= width ? width - 1 : k;
|
|
31931
|
+
sum += data[(row + cx) * 4 + c];
|
|
31932
|
+
}
|
|
31933
|
+
for (let x = 0; x < width; x++) {
|
|
31934
|
+
out[(row + x) * 4 + c] = sum / windowLen;
|
|
31935
|
+
const dropX = x - leftRadius;
|
|
31936
|
+
const addX = x + rightRadius + 1;
|
|
31937
|
+
const dc = dropX < 0 ? 0 : dropX >= width ? width - 1 : dropX;
|
|
31938
|
+
const ac = addX < 0 ? 0 : addX >= width ? width - 1 : addX;
|
|
31939
|
+
sum += data[(row + ac) * 4 + c] - data[(row + dc) * 4 + c];
|
|
31940
|
+
}
|
|
31941
|
+
}
|
|
31942
|
+
}
|
|
31943
|
+
return out;
|
|
31944
|
+
}
|
|
31945
|
+
/** One vertical box blur — the transpose of {@link boxBlurH}. */
|
|
31946
|
+
function boxBlurV(data, width, height, topRadius, bottomRadius) {
|
|
31947
|
+
const out = new Float32Array(data.length);
|
|
31948
|
+
const windowLen = topRadius + bottomRadius + 1;
|
|
31949
|
+
for (let x = 0; x < width; x++) for (let c = 0; c < 4; c++) {
|
|
31950
|
+
let sum = 0;
|
|
31951
|
+
for (let k = -topRadius; k <= bottomRadius; k++) {
|
|
31952
|
+
const cy = k < 0 ? 0 : k >= height ? height - 1 : k;
|
|
31953
|
+
sum += data[(cy * width + x) * 4 + c];
|
|
31954
|
+
}
|
|
31955
|
+
for (let y = 0; y < height; y++) {
|
|
31956
|
+
out[(y * width + x) * 4 + c] = sum / windowLen;
|
|
31957
|
+
const dropY = y - topRadius;
|
|
31958
|
+
const addY = y + bottomRadius + 1;
|
|
31959
|
+
const dc = dropY < 0 ? 0 : dropY >= height ? height - 1 : dropY;
|
|
31960
|
+
const ac = addY < 0 ? 0 : addY >= height ? height - 1 : addY;
|
|
31961
|
+
sum += data[(ac * width + x) * 4 + c] - data[(dc * width + x) * 4 + c];
|
|
31962
|
+
}
|
|
31963
|
+
}
|
|
31964
|
+
return out;
|
|
31965
|
+
}
|
|
31966
|
+
/**
|
|
31967
|
+
* Apply the three successive box blurs that approximate a Gaussian along one
|
|
31968
|
+
* axis, following the SVG 1.1 §15.17 odd/even centring rule for box size `d`.
|
|
31969
|
+
*
|
|
31970
|
+
* - `d` odd: three box blurs of size `d`, centred on the output pixel
|
|
31971
|
+
* (`radius = (d-1)/2` each side).
|
|
31972
|
+
* - `d` even: two box blurs of size `d` (one offset left-of-centre, one
|
|
31973
|
+
* right-of-centre) plus one box blur of size `d + 1` centred.
|
|
31974
|
+
* - `d <= 1`: identity (no perceivable blur on this axis).
|
|
31975
|
+
*/
|
|
31976
|
+
function threeBoxBlurAxis(data, width, height, d, horizontal) {
|
|
31977
|
+
if (d <= 1) return data;
|
|
31978
|
+
const blur = horizontal ? boxBlurH : boxBlurV;
|
|
31979
|
+
if (d % 2 === 1) {
|
|
31980
|
+
const r = (d - 1) / 2;
|
|
31981
|
+
let cur = blur(data, width, height, r, r);
|
|
31982
|
+
cur = blur(cur, width, height, r, r);
|
|
31983
|
+
cur = blur(cur, width, height, r, r);
|
|
31984
|
+
return cur;
|
|
31985
|
+
}
|
|
31986
|
+
const half = d / 2;
|
|
31987
|
+
let cur = blur(data, width, height, half, half - 1);
|
|
31988
|
+
cur = blur(cur, width, height, half - 1, half);
|
|
31989
|
+
cur = blur(cur, width, height, half, half);
|
|
31990
|
+
return cur;
|
|
31991
|
+
}
|
|
31992
|
+
/**
|
|
31993
|
+
* Approximate a Gaussian blur via three successive box blurs.
|
|
31994
|
+
*
|
|
31995
|
+
* Implements `feGaussianBlur` (SVG 1.1 §15.17). The blur is performed in
|
|
31996
|
+
* **premultiplied** colour space (the correct space for averaging colours
|
|
31997
|
+
* with varying alpha) and un-premultiplied on output. A standard deviation of
|
|
31998
|
+
* 0 on an axis is a no-op for that axis.
|
|
31999
|
+
*
|
|
32000
|
+
* The per-axis box size is `d = floor(s · 3 · √(2π) / 4 + 0.5)` with the
|
|
32001
|
+
* spec's odd/even centring rule.
|
|
32002
|
+
*
|
|
32003
|
+
* @param src Source buffer.
|
|
32004
|
+
* @param stdDevX Standard deviation along X (>= 0).
|
|
32005
|
+
* @param stdDevY Standard deviation along Y; defaults to `stdDevX`.
|
|
32006
|
+
* @returns A new, blurred buffer of the same size.
|
|
32007
|
+
* @throws If either standard deviation is negative.
|
|
32008
|
+
*/
|
|
32009
|
+
function feGaussianBlur(src, stdDevX, stdDevY) {
|
|
32010
|
+
const sx = stdDevX;
|
|
32011
|
+
const sy = stdDevY ?? stdDevX;
|
|
32012
|
+
if (sx < 0 || sy < 0 || Number.isNaN(sx) || Number.isNaN(sy)) throw new Error(`svgFilters.feGaussianBlur: standard deviation must be >= 0 (got ${sx}, ${sy})`);
|
|
32013
|
+
if (sx === 0 && sy === 0) return {
|
|
32014
|
+
width: src.width,
|
|
32015
|
+
height: src.height,
|
|
32016
|
+
rgba: src.rgba.slice()
|
|
32017
|
+
};
|
|
32018
|
+
let data = toPremultipliedF32(src);
|
|
32019
|
+
if (sx > 0) data = threeBoxBlurAxis(data, src.width, src.height, boxBlurD(sx), true);
|
|
32020
|
+
if (sy > 0) data = threeBoxBlurAxis(data, src.width, src.height, boxBlurD(sy), false);
|
|
32021
|
+
return fromPremultipliedF32(data, src.width, src.height);
|
|
32022
|
+
}
|
|
32023
|
+
/**
|
|
32024
|
+
* Composite two buffers with a Porter-Duff operator.
|
|
32025
|
+
*
|
|
32026
|
+
* Implements `feComposite` with operators `over`, `in`, `out`, `atop`, `xor`
|
|
32027
|
+
* (SVG 1.1 §15.13; Porter & Duff, SIGGRAPH 1984). The general form on
|
|
32028
|
+
* premultiplied colour is:
|
|
32029
|
+
*
|
|
32030
|
+
* ```
|
|
32031
|
+
* cr = ca · Fa + cb · Fb
|
|
32032
|
+
* ar = aa · Fa + ab · Fb
|
|
32033
|
+
* ```
|
|
32034
|
+
*
|
|
32035
|
+
* with coefficients (`aa`, `ab` = source/destination alpha):
|
|
32036
|
+
*
|
|
32037
|
+
* | op | Fa | Fb |
|
|
32038
|
+
* | ---- | --------- | --------- |
|
|
32039
|
+
* | over | 1 | 1 − aa |
|
|
32040
|
+
* | in | ab | 0 |
|
|
32041
|
+
* | out | 1 − ab | 0 |
|
|
32042
|
+
* | atop | ab | 1 − aa |
|
|
32043
|
+
* | xor | 1 − ab | 1 − aa |
|
|
32044
|
+
*
|
|
32045
|
+
* Here `a` is the source (foreground) and `b` is the destination
|
|
32046
|
+
* (background). Both buffers must be the same size.
|
|
32047
|
+
*
|
|
32048
|
+
* @param a Source (foreground) buffer.
|
|
32049
|
+
* @param b Destination (background) buffer.
|
|
32050
|
+
* @param op Porter-Duff operator.
|
|
32051
|
+
* @returns A new composited buffer of the same size.
|
|
32052
|
+
* @throws If the buffers differ in size.
|
|
32053
|
+
*/
|
|
32054
|
+
function feComposite(a, b, op) {
|
|
32055
|
+
assertSameSize(a, b);
|
|
32056
|
+
const out = allocBuffer(a.width, a.height);
|
|
32057
|
+
const n = a.width * a.height;
|
|
32058
|
+
for (let i = 0; i < n; i++) {
|
|
32059
|
+
const aa = a.rgba[i * 4 + 3] / 255;
|
|
32060
|
+
const ab = b.rgba[i * 4 + 3] / 255;
|
|
32061
|
+
const ar = a.rgba[i * 4 + 0] / 255 * aa;
|
|
32062
|
+
const ag = a.rgba[i * 4 + 1] / 255 * aa;
|
|
32063
|
+
const abl = a.rgba[i * 4 + 2] / 255 * aa;
|
|
32064
|
+
const br = b.rgba[i * 4 + 0] / 255 * ab;
|
|
32065
|
+
const bg = b.rgba[i * 4 + 1] / 255 * ab;
|
|
32066
|
+
const bb = b.rgba[i * 4 + 2] / 255 * ab;
|
|
32067
|
+
let fa;
|
|
32068
|
+
let fb;
|
|
32069
|
+
switch (op) {
|
|
32070
|
+
case "over":
|
|
32071
|
+
fa = 1;
|
|
32072
|
+
fb = 1 - aa;
|
|
32073
|
+
break;
|
|
32074
|
+
case "in":
|
|
32075
|
+
fa = ab;
|
|
32076
|
+
fb = 0;
|
|
32077
|
+
break;
|
|
32078
|
+
case "out":
|
|
32079
|
+
fa = 1 - ab;
|
|
32080
|
+
fb = 0;
|
|
32081
|
+
break;
|
|
32082
|
+
case "atop":
|
|
32083
|
+
fa = ab;
|
|
32084
|
+
fb = 1 - aa;
|
|
32085
|
+
break;
|
|
32086
|
+
case "xor":
|
|
32087
|
+
fa = 1 - ab;
|
|
32088
|
+
fb = 1 - aa;
|
|
32089
|
+
break;
|
|
32090
|
+
default: throw new Error(`svgFilters.feComposite: unknown operator ${String(op)}`);
|
|
32091
|
+
}
|
|
32092
|
+
const cr = ar * fa + br * fb;
|
|
32093
|
+
const cg = ag * fa + bg * fb;
|
|
32094
|
+
const cb = abl * fa + bb * fb;
|
|
32095
|
+
const car = aa * fa + ab * fb;
|
|
32096
|
+
if (car <= 0) {
|
|
32097
|
+
out.rgba[i * 4 + 0] = 0;
|
|
32098
|
+
out.rgba[i * 4 + 1] = 0;
|
|
32099
|
+
out.rgba[i * 4 + 2] = 0;
|
|
32100
|
+
out.rgba[i * 4 + 3] = 0;
|
|
32101
|
+
} else {
|
|
32102
|
+
out.rgba[i * 4 + 0] = clamp8(cr / car * 255);
|
|
32103
|
+
out.rgba[i * 4 + 1] = clamp8(cg / car * 255);
|
|
32104
|
+
out.rgba[i * 4 + 2] = clamp8(cb / car * 255);
|
|
32105
|
+
out.rgba[i * 4 + 3] = clamp8(car * 255);
|
|
32106
|
+
}
|
|
32107
|
+
}
|
|
32108
|
+
return out;
|
|
32109
|
+
}
|
|
32110
|
+
/**
|
|
32111
|
+
* Blend two buffers with one of the basic SVG 1.1 blend modes.
|
|
32112
|
+
*
|
|
32113
|
+
* Implements `feBlend` (SVG 1.1 §15.11). The spec defines the result on
|
|
32114
|
+
* **premultiplied** colour with `qa` = source alpha, `qb` = destination
|
|
32115
|
+
* alpha:
|
|
32116
|
+
*
|
|
32117
|
+
* - normal: `cr = (1 − qa)·cb + ca`
|
|
32118
|
+
* - multiply: `cr = (1 − qa)·cb + (1 − qb)·ca + ca·cb`
|
|
32119
|
+
* - screen: `cr = cb + ca − ca·cb`
|
|
32120
|
+
* - darken: `cr = min((1 − qa)·cb + ca, (1 − qb)·ca + cb)`
|
|
32121
|
+
* - lighten: `cr = max((1 − qa)·cb + ca, (1 − qb)·ca + cb)`
|
|
32122
|
+
*
|
|
32123
|
+
* with the common result alpha `ar = 1 − (1 − qa)·(1 − qb)`. Inputs `a`
|
|
32124
|
+
* (source) and `b` (destination) must be the same size.
|
|
32125
|
+
*
|
|
32126
|
+
* @param a Source (top) buffer.
|
|
32127
|
+
* @param b Destination (bottom) buffer.
|
|
32128
|
+
* @param mode Blend mode.
|
|
32129
|
+
* @returns A new blended buffer of the same size.
|
|
32130
|
+
* @throws If the buffers differ in size.
|
|
32131
|
+
*/
|
|
32132
|
+
function feBlend(a, b, mode) {
|
|
32133
|
+
assertSameSize(a, b);
|
|
32134
|
+
const out = allocBuffer(a.width, a.height);
|
|
32135
|
+
const n = a.width * a.height;
|
|
32136
|
+
for (let i = 0; i < n; i++) {
|
|
32137
|
+
const qa = a.rgba[i * 4 + 3] / 255;
|
|
32138
|
+
const qb = b.rgba[i * 4 + 3] / 255;
|
|
32139
|
+
const ar = 1 - (1 - qa) * (1 - qb);
|
|
32140
|
+
out.rgba[i * 4 + 3] = clamp8(ar * 255);
|
|
32141
|
+
for (let c = 0; c < 3; c++) {
|
|
32142
|
+
const ca = a.rgba[i * 4 + c] / 255 * qa;
|
|
32143
|
+
const cb = b.rgba[i * 4 + c] / 255 * qb;
|
|
32144
|
+
let cr;
|
|
32145
|
+
switch (mode) {
|
|
32146
|
+
case "normal":
|
|
32147
|
+
cr = (1 - qa) * cb + ca;
|
|
32148
|
+
break;
|
|
32149
|
+
case "multiply":
|
|
32150
|
+
cr = (1 - qa) * cb + (1 - qb) * ca + ca * cb;
|
|
32151
|
+
break;
|
|
32152
|
+
case "screen":
|
|
32153
|
+
cr = cb + ca - ca * cb;
|
|
32154
|
+
break;
|
|
32155
|
+
case "darken":
|
|
32156
|
+
cr = Math.min((1 - qa) * cb + ca, (1 - qb) * ca + cb);
|
|
32157
|
+
break;
|
|
32158
|
+
case "lighten":
|
|
32159
|
+
cr = Math.max((1 - qa) * cb + ca, (1 - qb) * ca + cb);
|
|
32160
|
+
break;
|
|
32161
|
+
default: throw new Error(`svgFilters.feBlend: unknown mode ${String(mode)}`);
|
|
32162
|
+
}
|
|
32163
|
+
if (ar <= 0) out.rgba[i * 4 + c] = 0;
|
|
32164
|
+
else out.rgba[i * 4 + c] = clamp8(cr / ar * 255);
|
|
32165
|
+
}
|
|
32166
|
+
}
|
|
32167
|
+
return out;
|
|
32168
|
+
}
|
|
32169
|
+
//#endregion
|
|
31389
32170
|
//#region src/render/matrix.ts
|
|
31390
32171
|
/** The identity transform. */
|
|
31391
32172
|
function identity() {
|
|
@@ -36113,6 +36894,12 @@ Object.defineProperty(exports, "dataMatrixToOperators", {
|
|
|
36113
36894
|
return dataMatrixToOperators;
|
|
36114
36895
|
}
|
|
36115
36896
|
});
|
|
36897
|
+
Object.defineProperty(exports, "decodeRegisteredImage", {
|
|
36898
|
+
enumerable: true,
|
|
36899
|
+
get: function() {
|
|
36900
|
+
return decodeRegisteredImage;
|
|
36901
|
+
}
|
|
36902
|
+
});
|
|
36116
36903
|
Object.defineProperty(exports, "decodeTile", {
|
|
36117
36904
|
enumerable: true,
|
|
36118
36905
|
get: function() {
|
|
@@ -36149,6 +36936,12 @@ Object.defineProperty(exports, "detectModifications", {
|
|
|
36149
36936
|
return detectModifications;
|
|
36150
36937
|
}
|
|
36151
36938
|
});
|
|
36939
|
+
Object.defineProperty(exports, "detectNextGenFormat", {
|
|
36940
|
+
enumerable: true,
|
|
36941
|
+
get: function() {
|
|
36942
|
+
return detectNextGenFormat;
|
|
36943
|
+
}
|
|
36944
|
+
});
|
|
36152
36945
|
Object.defineProperty(exports, "detectTransparency", {
|
|
36153
36946
|
enumerable: true,
|
|
36154
36947
|
get: function() {
|
|
@@ -36377,6 +37170,48 @@ Object.defineProperty(exports, "extractXmpMetadata", {
|
|
|
36377
37170
|
return extractXmpMetadata;
|
|
36378
37171
|
}
|
|
36379
37172
|
});
|
|
37173
|
+
Object.defineProperty(exports, "feBlend", {
|
|
37174
|
+
enumerable: true,
|
|
37175
|
+
get: function() {
|
|
37176
|
+
return feBlend;
|
|
37177
|
+
}
|
|
37178
|
+
});
|
|
37179
|
+
Object.defineProperty(exports, "feColorMatrix", {
|
|
37180
|
+
enumerable: true,
|
|
37181
|
+
get: function() {
|
|
37182
|
+
return feColorMatrix;
|
|
37183
|
+
}
|
|
37184
|
+
});
|
|
37185
|
+
Object.defineProperty(exports, "feColorMatrixSaturate", {
|
|
37186
|
+
enumerable: true,
|
|
37187
|
+
get: function() {
|
|
37188
|
+
return feColorMatrixSaturate;
|
|
37189
|
+
}
|
|
37190
|
+
});
|
|
37191
|
+
Object.defineProperty(exports, "feComposite", {
|
|
37192
|
+
enumerable: true,
|
|
37193
|
+
get: function() {
|
|
37194
|
+
return feComposite;
|
|
37195
|
+
}
|
|
37196
|
+
});
|
|
37197
|
+
Object.defineProperty(exports, "feFlood", {
|
|
37198
|
+
enumerable: true,
|
|
37199
|
+
get: function() {
|
|
37200
|
+
return feFlood;
|
|
37201
|
+
}
|
|
37202
|
+
});
|
|
37203
|
+
Object.defineProperty(exports, "feGaussianBlur", {
|
|
37204
|
+
enumerable: true,
|
|
37205
|
+
get: function() {
|
|
37206
|
+
return feGaussianBlur;
|
|
37207
|
+
}
|
|
37208
|
+
});
|
|
37209
|
+
Object.defineProperty(exports, "feOffset", {
|
|
37210
|
+
enumerable: true,
|
|
37211
|
+
get: function() {
|
|
37212
|
+
return feOffset;
|
|
37213
|
+
}
|
|
37214
|
+
});
|
|
36380
37215
|
Object.defineProperty(exports, "findChangedObjects", {
|
|
36381
37216
|
enumerable: true,
|
|
36382
37217
|
get: function() {
|
|
@@ -36593,6 +37428,12 @@ Object.defineProperty(exports, "getFieldValue", {
|
|
|
36593
37428
|
return getFieldValue;
|
|
36594
37429
|
}
|
|
36595
37430
|
});
|
|
37431
|
+
Object.defineProperty(exports, "getImageDecoder", {
|
|
37432
|
+
enumerable: true,
|
|
37433
|
+
get: function() {
|
|
37434
|
+
return getImageDecoder;
|
|
37435
|
+
}
|
|
37436
|
+
});
|
|
36596
37437
|
Object.defineProperty(exports, "getLinearizationInfo", {
|
|
36597
37438
|
enumerable: true,
|
|
36598
37439
|
get: function() {
|
|
@@ -36629,6 +37470,12 @@ Object.defineProperty(exports, "h", {
|
|
|
36629
37470
|
return h;
|
|
36630
37471
|
}
|
|
36631
37472
|
});
|
|
37473
|
+
Object.defineProperty(exports, "hasImageDecoder", {
|
|
37474
|
+
enumerable: true,
|
|
37475
|
+
get: function() {
|
|
37476
|
+
return hasImageDecoder;
|
|
37477
|
+
}
|
|
37478
|
+
});
|
|
36632
37479
|
Object.defineProperty(exports, "hasLtvData", {
|
|
36633
37480
|
enumerable: true,
|
|
36634
37481
|
get: function() {
|
|
@@ -36893,6 +37740,12 @@ Object.defineProperty(exports, "preflightPdfA", {
|
|
|
36893
37740
|
return preflightPdfA;
|
|
36894
37741
|
}
|
|
36895
37742
|
});
|
|
37743
|
+
Object.defineProperty(exports, "probeNextGenImage", {
|
|
37744
|
+
enumerable: true,
|
|
37745
|
+
get: function() {
|
|
37746
|
+
return probeNextGenImage;
|
|
37747
|
+
}
|
|
37748
|
+
});
|
|
36896
37749
|
Object.defineProperty(exports, "processBatch", {
|
|
36897
37750
|
enumerable: true,
|
|
36898
37751
|
get: function() {
|
|
@@ -36983,6 +37836,12 @@ Object.defineProperty(exports, "registerEmbeddedFile", {
|
|
|
36983
37836
|
return registerEmbeddedFile;
|
|
36984
37837
|
}
|
|
36985
37838
|
});
|
|
37839
|
+
Object.defineProperty(exports, "registerImageDecoder", {
|
|
37840
|
+
enumerable: true,
|
|
37841
|
+
get: function() {
|
|
37842
|
+
return registerImageDecoder;
|
|
37843
|
+
}
|
|
37844
|
+
});
|
|
36986
37845
|
Object.defineProperty(exports, "removeAllBookmarks", {
|
|
36987
37846
|
enumerable: true,
|
|
36988
37847
|
get: function() {
|
|
@@ -37307,6 +38166,12 @@ Object.defineProperty(exports, "truncateText", {
|
|
|
37307
38166
|
return truncateText;
|
|
37308
38167
|
}
|
|
37309
38168
|
});
|
|
38169
|
+
Object.defineProperty(exports, "unregisterImageDecoder", {
|
|
38170
|
+
enumerable: true,
|
|
38171
|
+
get: function() {
|
|
38172
|
+
return unregisterImageDecoder;
|
|
38173
|
+
}
|
|
38174
|
+
});
|
|
37310
38175
|
Object.defineProperty(exports, "upcAToOperators", {
|
|
37311
38176
|
enumerable: true,
|
|
37312
38177
|
get: function() {
|
|
@@ -37458,4 +38323,4 @@ Object.defineProperty(exports, "xyzToRgb", {
|
|
|
37458
38323
|
}
|
|
37459
38324
|
});
|
|
37460
38325
|
|
|
37461
|
-
//# sourceMappingURL=src-
|
|
38326
|
+
//# sourceMappingURL=src-B0gmgVEs.cjs.map
|