@aglyn/aglyn 1.0.0-beta.197 → 1.0.0-beta.199

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/package.json +11 -11
  2. package/src/lib/app-utils/docs-help.generated.d.ts +1 -1
  3. package/src/lib/app-utils/docs-help.generated.js +1 -0
  4. package/src/lib/app-utils/docs-help.generated.js.map +1 -1
  5. package/src/lib/app-utils/docs-index.generated.js +3 -3
  6. package/src/lib/app-utils/docs-index.generated.js.map +1 -1
  7. package/src/lib/app-utils/media-embedded-fields.d.ts +471 -0
  8. package/src/lib/app-utils/media-embedded-fields.js +619 -0
  9. package/src/lib/app-utils/media-embedded-fields.js.map +1 -0
  10. package/src/lib/app-utils/media-embedded-metadata/exif.d.ts +160 -0
  11. package/src/lib/app-utils/media-embedded-metadata/exif.js +1267 -0
  12. package/src/lib/app-utils/media-embedded-metadata/exif.js.map +1 -0
  13. package/src/lib/app-utils/media-embedded-metadata/gif.d.ts +44 -0
  14. package/src/lib/app-utils/media-embedded-metadata/gif.js +134 -0
  15. package/src/lib/app-utils/media-embedded-metadata/gif.js.map +1 -0
  16. package/src/lib/app-utils/media-embedded-metadata/heif.d.ts +23 -0
  17. package/src/lib/app-utils/media-embedded-metadata/heif.js +354 -0
  18. package/src/lib/app-utils/media-embedded-metadata/heif.js.map +1 -0
  19. package/src/lib/app-utils/media-embedded-metadata/icc.d.ts +22 -0
  20. package/src/lib/app-utils/media-embedded-metadata/icc.js +85 -0
  21. package/src/lib/app-utils/media-embedded-metadata/icc.js.map +1 -0
  22. package/src/lib/app-utils/media-embedded-metadata/image-blocks.d.ts +145 -0
  23. package/src/lib/app-utils/media-embedded-metadata/image-blocks.js +200 -0
  24. package/src/lib/app-utils/media-embedded-metadata/image-blocks.js.map +1 -0
  25. package/src/lib/app-utils/media-embedded-metadata/index.d.ts +93 -0
  26. package/src/lib/app-utils/media-embedded-metadata/index.js +744 -0
  27. package/src/lib/app-utils/media-embedded-metadata/index.js.map +1 -0
  28. package/src/lib/app-utils/media-embedded-metadata/iptc.d.ts +59 -0
  29. package/src/lib/app-utils/media-embedded-metadata/iptc.js +529 -0
  30. package/src/lib/app-utils/media-embedded-metadata/iptc.js.map +1 -0
  31. package/src/lib/app-utils/media-embedded-metadata/jpeg.d.ts +43 -0
  32. package/src/lib/app-utils/media-embedded-metadata/jpeg.js +456 -0
  33. package/src/lib/app-utils/media-embedded-metadata/jpeg.js.map +1 -0
  34. package/src/lib/app-utils/media-embedded-metadata/mp4.d.ts +37 -0
  35. package/src/lib/app-utils/media-embedded-metadata/mp4.js +621 -0
  36. package/src/lib/app-utils/media-embedded-metadata/mp4.js.map +1 -0
  37. package/src/lib/app-utils/media-embedded-metadata/ooxml.d.ts +131 -0
  38. package/src/lib/app-utils/media-embedded-metadata/ooxml.js +1058 -0
  39. package/src/lib/app-utils/media-embedded-metadata/ooxml.js.map +1 -0
  40. package/src/lib/app-utils/media-embedded-metadata/pdf-objects.d.ts +223 -0
  41. package/src/lib/app-utils/media-embedded-metadata/pdf-objects.js +936 -0
  42. package/src/lib/app-utils/media-embedded-metadata/pdf-objects.js.map +1 -0
  43. package/src/lib/app-utils/media-embedded-metadata/pdf.d.ts +82 -0
  44. package/src/lib/app-utils/media-embedded-metadata/pdf.js +1167 -0
  45. package/src/lib/app-utils/media-embedded-metadata/pdf.js.map +1 -0
  46. package/src/lib/app-utils/media-embedded-metadata/png.d.ts +41 -0
  47. package/src/lib/app-utils/media-embedded-metadata/png.js +512 -0
  48. package/src/lib/app-utils/media-embedded-metadata/png.js.map +1 -0
  49. package/src/lib/app-utils/media-embedded-metadata/svg.d.ts +30 -0
  50. package/src/lib/app-utils/media-embedded-metadata/svg.js +154 -0
  51. package/src/lib/app-utils/media-embedded-metadata/svg.js.map +1 -0
  52. package/src/lib/app-utils/media-embedded-metadata/tiff.d.ts +40 -0
  53. package/src/lib/app-utils/media-embedded-metadata/tiff.js +158 -0
  54. package/src/lib/app-utils/media-embedded-metadata/tiff.js.map +1 -0
  55. package/src/lib/app-utils/media-embedded-metadata/types.d.ts +60 -0
  56. package/src/lib/app-utils/media-embedded-metadata/types.js +32 -0
  57. package/src/lib/app-utils/media-embedded-metadata/types.js.map +1 -0
  58. package/src/lib/app-utils/media-embedded-metadata/webm.d.ts +26 -0
  59. package/src/lib/app-utils/media-embedded-metadata/webm.js +417 -0
  60. package/src/lib/app-utils/media-embedded-metadata/webm.js.map +1 -0
  61. package/src/lib/app-utils/media-embedded-metadata/webp.d.ts +37 -0
  62. package/src/lib/app-utils/media-embedded-metadata/webp.js +272 -0
  63. package/src/lib/app-utils/media-embedded-metadata/webp.js.map +1 -0
  64. package/src/lib/app-utils/media-embedded-metadata/xml.d.ts +231 -0
  65. package/src/lib/app-utils/media-embedded-metadata/xml.js +598 -0
  66. package/src/lib/app-utils/media-embedded-metadata/xml.js.map +1 -0
  67. package/src/lib/app-utils/media-embedded-metadata/xmp.d.ts +100 -0
  68. package/src/lib/app-utils/media-embedded-metadata/xmp.js +1475 -0
  69. package/src/lib/app-utils/media-embedded-metadata/xmp.js.map +1 -0
  70. package/src/lib/app-utils/media-embedded-metadata/zip.d.ts +93 -0
  71. package/src/lib/app-utils/media-embedded-metadata/zip.js +531 -0
  72. package/src/lib/app-utils/media-embedded-metadata/zip.js.map +1 -0
  73. package/src/lib/app-utils/name-search.d.ts +12 -0
  74. package/src/lib/app-utils/name-search.js +11 -0
  75. package/src/lib/app-utils/name-search.js.map +1 -1
  76. package/src/lib/foundation/definitions/platform.types.d.ts +11 -0
  77. package/src/lib/foundation/definitions/platform.types.js.map +1 -1
  78. package/src/server.d.ts +1 -0
  79. package/src/server.js +7 -0
  80. package/src/server.js.map +1 -1
@@ -0,0 +1,272 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ /**
17
+ * WebP metadata blocks: the `EXIF`, `XMP ` and `ICCP` chunks, read and
18
+ * rewritten without touching the image (AGL-3331).
19
+ *
20
+ * A WebP is a RIFF file (RFC 9649, section 2): `RIFF`, a little-endian
21
+ * size, `WEBP`, then chunks of a FourCC, a little-endian size and the data,
22
+ * padded with one zero byte to an even length. A simple file holds a single
23
+ * `VP8 ` (lossy) or `VP8L` (lossless) chunk; metadata needs the extended
24
+ * format (2.7), which opens with a `VP8X` chunk whose flags announce the
25
+ * optional chunks, in this order:
26
+ *
27
+ * VP8X, ICCP, ANIM, (ALPH + VP8 | VP8L | ANMF…), EXIF, XMP
28
+ *
29
+ * The writer rebuilds only the `EXIF` and `XMP ` chunks and the `VP8X`
30
+ * flags byte; every image, frame, alpha and unknown chunk is copied byte for
31
+ * byte. Adding metadata to a simple file converts it to the extended format
32
+ * by prepending a `VP8X` whose canvas is read from the bitstream header —
33
+ * the image chunk itself is unchanged.
34
+ */ import { EmbeddedWriteError } from "./types.js";
35
+ import { FILE_DAMAGED, METADATA_TOO_LARGE, asciiBytes, bytesAt, concatBytes, readAscii, readU16LE, readU32LE, trimNul, utf8Decode, utf8Encode, writeU32LE } from "./image-blocks.js";
36
+ const RIFF = asciiBytes('RIFF');
37
+ const WEBP = asciiBytes('WEBP');
38
+ const EXIF_HEADER = asciiBytes('Exif\0\0');
39
+ /** VP8X flag bits (RFC 9649, 2.7): ICC, alpha, EXIF, XMP, animation. */ const FLAG_ICC = 0x20;
40
+ const FLAG_ALPHA = 0x10;
41
+ const FLAG_EXIF = 0x08;
42
+ const FLAG_XMP = 0x04;
43
+ /** A RIFF size field is 32 bits; the file tops out at 2^32 − 2 bytes. */ const MAX_RIFF_SIZE = 0xfffffffe - 8;
44
+ /** Chunk count past which a file is not an image; the walk stops. */ const MAX_CHUNKS = 1 << 20;
45
+ /** Chunks that make up the image, after which EXIF and XMP belong. */ const IMAGE_CHUNKS = new Set([
46
+ 'VP8X',
47
+ 'ICCP',
48
+ 'ANIM',
49
+ 'ANMF',
50
+ 'ALPH',
51
+ 'VP8 ',
52
+ 'VP8L'
53
+ ]);
54
+ /** Walks the RIFF chunks. `null` when the bytes are not `RIFF….WEBP`. */ function walkWebp(bytes) {
55
+ if (!bytesAt(bytes, 0, RIFF) || !bytesAt(bytes, 8, WEBP)) return null;
56
+ const declared = 8 + readU32LE(bytes, 4);
57
+ const riffEnd = Math.min(declared, bytes.length);
58
+ const chunks = [];
59
+ let offset = 12;
60
+ let complete = declared <= bytes.length;
61
+ while(offset + 8 <= riffEnd && chunks.length < MAX_CHUNKS){
62
+ const size = readU32LE(bytes, offset + 4);
63
+ const dataEnd = offset + 8 + size;
64
+ if (dataEnd > riffEnd) {
65
+ complete = false;
66
+ break;
67
+ }
68
+ const end = Math.min(dataEnd + (size & 1), riffEnd);
69
+ chunks.push({
70
+ fourcc: readAscii(bytes, offset, 4),
71
+ start: offset,
72
+ dataStart: offset + 8,
73
+ size,
74
+ end
75
+ });
76
+ offset = end;
77
+ }
78
+ // Bytes left inside the RIFF payload that are not a whole chunk.
79
+ if (offset < riffEnd) complete = false;
80
+ return {
81
+ chunks,
82
+ riffEnd,
83
+ complete
84
+ };
85
+ }
86
+ /** Strips the `Exif\0\0` prefix some writers put in front of the TIFF bytes. */ function bareTiff(exif) {
87
+ return bytesAt(exif, 0, EXIF_HEADER) ? exif.subarray(EXIF_HEADER.length) : exif;
88
+ }
89
+ /**
90
+ * Reads the metadata blocks of a WebP. Returns `null` when the bytes are not
91
+ * a WebP; a damaged file yields what came before the damage.
92
+ */ export function readWebpBlocks(bytes) {
93
+ const layout = walkWebp(bytes);
94
+ if (!layout) return null;
95
+ const blocks = {};
96
+ for (const chunk of layout.chunks){
97
+ const data = bytes.subarray(chunk.dataStart, chunk.dataStart + chunk.size);
98
+ if (chunk.fourcc === 'EXIF' && !blocks.exif) blocks.exif = bareTiff(data).slice();
99
+ if (chunk.fourcc === 'XMP ' && blocks.xmp === undefined) {
100
+ blocks.xmp = trimNul(utf8Decode(data));
101
+ }
102
+ if (chunk.fourcc === 'ICCP' && !blocks.icc) blocks.icc = data.slice();
103
+ }
104
+ return blocks;
105
+ }
106
+ /** Builds a chunk: FourCC, little-endian size, data, and the pad byte. */ function buildChunk(fourcc, data) {
107
+ if (data.length > MAX_RIFF_SIZE) throw new EmbeddedWriteError(METADATA_TOO_LARGE);
108
+ const out = new Uint8Array(8 + data.length + (data.length & 1));
109
+ out.set(asciiBytes(fourcc), 0);
110
+ writeU32LE(out, 4, data.length);
111
+ out.set(data, 8);
112
+ return out;
113
+ }
114
+ /**
115
+ * The canvas size and alpha of a simple file's image chunk, for the `VP8X`
116
+ * a conversion adds, or `null` when the bitstream header is unreadable.
117
+ *
118
+ * - `VP8 `: a 3-byte frame tag whose low bit is 0 on a key frame, the start
119
+ * code `9D 01 2A`, then 14-bit width and height (RFC 6386, 9.1).
120
+ * - `VP8L`: the signature `0x2F`, then 14 bits of width − 1, 14 of
121
+ * height − 1 and the alpha hint (RFC 9649, section 3, the lossless header).
122
+ */ function simpleCanvas(bytes, chunk) {
123
+ const data = bytes.subarray(chunk.dataStart, chunk.dataStart + chunk.size);
124
+ if (chunk.fourcc === 'VP8 ') {
125
+ var _data_;
126
+ if (data.length < 10 || ((_data_ = data[0]) != null ? _data_ : 1) & 1) return null;
127
+ if (data[3] !== 0x9d || data[4] !== 0x01 || data[5] !== 0x2a) return null;
128
+ const width = readU16LE(data, 6) & 0x3fff;
129
+ const height = readU16LE(data, 8) & 0x3fff;
130
+ return width && height ? {
131
+ width,
132
+ height,
133
+ alpha: false
134
+ } : null;
135
+ }
136
+ if (chunk.fourcc === 'VP8L') {
137
+ if (data.length < 5 || data[0] !== 0x2f) return null;
138
+ const bits = readU32LE(data, 1);
139
+ return {
140
+ width: (bits & 0x3fff) + 1,
141
+ height: (bits >>> 14 & 0x3fff) + 1,
142
+ alpha: (bits >>> 28 & 1) === 1
143
+ };
144
+ }
145
+ return null;
146
+ }
147
+ /** A `VP8X` chunk (RFC 9649, 2.7): flags, 3 reserved bytes, canvas − 1. */ function vp8xChunk(flags, width, height) {
148
+ const data = new Uint8Array(10);
149
+ data[0] = flags;
150
+ data[4] = width - 1 & 0xff;
151
+ data[5] = width - 1 >>> 8 & 0xff;
152
+ data[6] = width - 1 >>> 16 & 0xff;
153
+ data[7] = height - 1 & 0xff;
154
+ data[8] = height - 1 >>> 8 & 0xff;
155
+ data[9] = height - 1 >>> 16 & 0xff;
156
+ return buildChunk('VP8X', data);
157
+ }
158
+ /**
159
+ * Writes an update into a WebP and returns the new file.
160
+ *
161
+ * An existing `EXIF` or `XMP ` chunk is replaced in place (later duplicates
162
+ * removed); a new one goes after the image — EXIF before any existing XMP,
163
+ * XMP after EXIF — as the extended format orders them. `null` removes every
164
+ * copy. The `VP8X` EXIF and XMP flags are set to match what the file then
165
+ * holds; ICC, alpha and animation flags are left as they were.
166
+ *
167
+ * Throws `EmbeddedWriteError` for a file that is not a WebP or is truncated,
168
+ * for a layout that is neither simple nor extended, for a simple file whose
169
+ * bitstream header cannot give the canvas size a conversion needs, and for
170
+ * an IRB or PNG text update.
171
+ */ export function writeWebpBlocks(bytes, update) {
172
+ var _update_irb;
173
+ const layout = walkWebp(bytes);
174
+ if (!layout) throw new EmbeddedWriteError('This file is not a WebP image.');
175
+ if (!layout.complete) throw new EmbeddedWriteError(FILE_DAMAGED);
176
+ if ((_update_irb = update.irb) == null ? void 0 : _update_irb.length) {
177
+ throw new EmbeddedWriteError('A WebP has no place for Photoshop or IPTC metadata.');
178
+ }
179
+ if (update.pngText && Object.values(update.pngText).some((value)=>value !== null)) {
180
+ throw new EmbeddedWriteError('PNG text fields can only be written to a PNG.');
181
+ }
182
+ const first = layout.chunks[0];
183
+ const extended = (first == null ? void 0 : first.fourcc) === 'VP8X';
184
+ if (!first || !extended && first.fourcc !== 'VP8 ' && first.fourcc !== 'VP8L') {
185
+ throw new EmbeddedWriteError(FILE_DAMAGED);
186
+ }
187
+ const pieces = layout.chunks.map((chunk)=>{
188
+ const expected = 8 + chunk.size + (chunk.size & 1);
189
+ const original = bytes.subarray(chunk.start, chunk.end);
190
+ // A last chunk missing its pad byte gets one, or anything after it
191
+ // would start on an odd offset.
192
+ const padded = original.length < expected ? concatBytes([
193
+ original,
194
+ new Uint8Array(1)
195
+ ]) : original;
196
+ return {
197
+ fourcc: chunk.fourcc,
198
+ bytes: padded
199
+ };
200
+ });
201
+ const lastImage = ()=>{
202
+ for(let i = pieces.length - 1; i >= 0; i -= 1){
203
+ var _ref;
204
+ var _pieces_i;
205
+ if (IMAGE_CHUNKS.has((_ref = (_pieces_i = pieces[i]) == null ? void 0 : _pieces_i.fourcc) != null ? _ref : '')) return i + 1;
206
+ }
207
+ return pieces.length;
208
+ };
209
+ const indexOf = (fourcc)=>pieces.findIndex((p)=>p.fourcc === fourcc);
210
+ const place = (fourcc, data, anchor)=>{
211
+ const chunk = data && data.length ? buildChunk(fourcc, data) : null;
212
+ const at = indexOf(fourcc);
213
+ for(let i = pieces.length - 1; i > at; i -= 1){
214
+ var _pieces_i;
215
+ if (((_pieces_i = pieces[i]) == null ? void 0 : _pieces_i.fourcc) === fourcc) pieces.splice(i, 1);
216
+ }
217
+ const existing = pieces[at];
218
+ if (existing && chunk) existing.bytes = chunk;
219
+ else if (existing) pieces.splice(at, 1);
220
+ else if (chunk) pieces.splice(anchor(), 0, {
221
+ fourcc,
222
+ bytes: chunk
223
+ });
224
+ };
225
+ if (update.exif !== undefined) {
226
+ place('EXIF', update.exif && bareTiff(update.exif), ()=>{
227
+ const xmp = indexOf('XMP ');
228
+ return xmp >= lastImage() ? xmp : lastImage();
229
+ });
230
+ }
231
+ if (update.xmp !== undefined) {
232
+ place('XMP ', update.xmp ? utf8Encode(update.xmp) : null, ()=>{
233
+ const exif = indexOf('EXIF');
234
+ return exif >= 0 ? Math.max(exif + 1, lastImage()) : lastImage();
235
+ });
236
+ }
237
+ const hasExif = indexOf('EXIF') >= 0;
238
+ const hasXmp = indexOf('XMP ') >= 0;
239
+ const vp8x = pieces[0];
240
+ if (extended && vp8x) {
241
+ var _chunk_;
242
+ const chunk = vp8x.bytes.slice();
243
+ let flags = (_chunk_ = chunk[8]) != null ? _chunk_ : 0;
244
+ flags = hasExif ? flags | FLAG_EXIF : flags & ~FLAG_EXIF;
245
+ flags = hasXmp ? flags | FLAG_XMP : flags & ~FLAG_XMP;
246
+ chunk[8] = flags;
247
+ vp8x.bytes = chunk;
248
+ } else if (hasExif || hasXmp) {
249
+ const canvas = simpleCanvas(bytes, first);
250
+ if (!canvas) throw new EmbeddedWriteError(FILE_DAMAGED);
251
+ let flags = (hasExif ? FLAG_EXIF : 0) | (hasXmp ? FLAG_XMP : 0);
252
+ if (canvas.alpha) flags |= FLAG_ALPHA;
253
+ if (indexOf('ICCP') >= 0) flags |= FLAG_ICC;
254
+ pieces.unshift({
255
+ fourcc: 'VP8X',
256
+ bytes: vp8xChunk(flags, canvas.width, canvas.height)
257
+ });
258
+ }
259
+ const body = concatBytes(pieces.map((piece)=>piece.bytes));
260
+ if (body.length + 4 > MAX_RIFF_SIZE) throw new EmbeddedWriteError(METADATA_TOO_LARGE);
261
+ const header = new Uint8Array(12);
262
+ header.set(RIFF, 0);
263
+ writeU32LE(header, 4, body.length + 4);
264
+ header.set(WEBP, 8);
265
+ return concatBytes([
266
+ header,
267
+ body,
268
+ bytes.subarray(layout.riffEnd)
269
+ ]);
270
+ }
271
+
272
+ //# sourceMappingURL=webp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/aglyn/src/lib/app-utils/media-embedded-metadata/webp.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * WebP metadata blocks: the `EXIF`, `XMP ` and `ICCP` chunks, read and\n * rewritten without touching the image (AGL-3331).\n *\n * A WebP is a RIFF file (RFC 9649, section 2): `RIFF`, a little-endian\n * size, `WEBP`, then chunks of a FourCC, a little-endian size and the data,\n * padded with one zero byte to an even length. A simple file holds a single\n * `VP8 ` (lossy) or `VP8L` (lossless) chunk; metadata needs the extended\n * format (2.7), which opens with a `VP8X` chunk whose flags announce the\n * optional chunks, in this order:\n *\n * VP8X, ICCP, ANIM, (ALPH + VP8 | VP8L | ANMF…), EXIF, XMP\n *\n * The writer rebuilds only the `EXIF` and `XMP ` chunks and the `VP8X`\n * flags byte; every image, frame, alpha and unknown chunk is copied byte for\n * byte. Adding metadata to a simple file converts it to the extended format\n * by prepending a `VP8X` whose canvas is read from the bitstream header —\n * the image chunk itself is unchanged.\n */\n\nimport { EmbeddedWriteError } from './types'\nimport {\n FILE_DAMAGED,\n METADATA_TOO_LARGE,\n asciiBytes,\n bytesAt,\n concatBytes,\n readAscii,\n readU16LE,\n readU32LE,\n trimNul,\n utf8Decode,\n utf8Encode,\n writeU32LE,\n type ImageBlockUpdate,\n type ImageBlocks,\n} from './image-blocks'\n\nconst RIFF = asciiBytes('RIFF')\nconst WEBP = asciiBytes('WEBP')\nconst EXIF_HEADER = asciiBytes('Exif\\0\\0')\n\n/** VP8X flag bits (RFC 9649, 2.7): ICC, alpha, EXIF, XMP, animation. */\nconst FLAG_ICC = 0x20\nconst FLAG_ALPHA = 0x10\nconst FLAG_EXIF = 0x08\nconst FLAG_XMP = 0x04\n\n/** A RIFF size field is 32 bits; the file tops out at 2^32 − 2 bytes. */\nconst MAX_RIFF_SIZE = 0xfffffffe - 8\n/** Chunk count past which a file is not an image; the walk stops. */\nconst MAX_CHUNKS = 1 << 20\n\n/** Chunks that make up the image, after which EXIF and XMP belong. */\nconst IMAGE_CHUNKS = new Set([\n 'VP8X',\n 'ICCP',\n 'ANIM',\n 'ANMF',\n 'ALPH',\n 'VP8 ',\n 'VP8L',\n])\n\ninterface RiffChunk {\n fourcc: string\n start: number\n dataStart: number\n size: number\n /** One past the data and its pad byte (clamped to the RIFF payload). */\n end: number\n}\n\ninterface WebpLayout {\n chunks: RiffChunk[]\n /** One past the RIFF payload the header declares, clamped to the file. */\n riffEnd: number\n /** False when a chunk runs past the RIFF payload or the file. */\n complete: boolean\n}\n\n/** Walks the RIFF chunks. `null` when the bytes are not `RIFF….WEBP`. */\nfunction walkWebp(bytes: Uint8Array): WebpLayout | null {\n if (!bytesAt(bytes, 0, RIFF) || !bytesAt(bytes, 8, WEBP)) return null\n const declared = 8 + readU32LE(bytes, 4)\n const riffEnd = Math.min(declared, bytes.length)\n const chunks: RiffChunk[] = []\n let offset = 12\n let complete = declared <= bytes.length\n while (offset + 8 <= riffEnd && chunks.length < MAX_CHUNKS) {\n const size = readU32LE(bytes, offset + 4)\n const dataEnd = offset + 8 + size\n if (dataEnd > riffEnd) {\n complete = false\n break\n }\n const end = Math.min(dataEnd + (size & 1), riffEnd)\n chunks.push({\n fourcc: readAscii(bytes, offset, 4),\n start: offset,\n dataStart: offset + 8,\n size,\n end,\n })\n offset = end\n }\n // Bytes left inside the RIFF payload that are not a whole chunk.\n if (offset < riffEnd) complete = false\n return { chunks, riffEnd, complete }\n}\n\n/** Strips the `Exif\\0\\0` prefix some writers put in front of the TIFF bytes. */\nfunction bareTiff(exif: Uint8Array): Uint8Array {\n return bytesAt(exif, 0, EXIF_HEADER)\n ? exif.subarray(EXIF_HEADER.length)\n : exif\n}\n\n/**\n * Reads the metadata blocks of a WebP. Returns `null` when the bytes are not\n * a WebP; a damaged file yields what came before the damage.\n */\nexport function readWebpBlocks(bytes: Uint8Array): ImageBlocks | null {\n const layout = walkWebp(bytes)\n if (!layout) return null\n const blocks: ImageBlocks = {}\n for (const chunk of layout.chunks) {\n const data = bytes.subarray(chunk.dataStart, chunk.dataStart + chunk.size)\n if (chunk.fourcc === 'EXIF' && !blocks.exif)\n blocks.exif = bareTiff(data).slice()\n if (chunk.fourcc === 'XMP ' && blocks.xmp === undefined) {\n blocks.xmp = trimNul(utf8Decode(data))\n }\n if (chunk.fourcc === 'ICCP' && !blocks.icc) blocks.icc = data.slice()\n }\n return blocks\n}\n\n/** Builds a chunk: FourCC, little-endian size, data, and the pad byte. */\nfunction buildChunk(fourcc: string, data: Uint8Array): Uint8Array {\n if (data.length > MAX_RIFF_SIZE)\n throw new EmbeddedWriteError(METADATA_TOO_LARGE)\n const out = new Uint8Array(8 + data.length + (data.length & 1))\n out.set(asciiBytes(fourcc), 0)\n writeU32LE(out, 4, data.length)\n out.set(data, 8)\n return out\n}\n\n/**\n * The canvas size and alpha of a simple file's image chunk, for the `VP8X`\n * a conversion adds, or `null` when the bitstream header is unreadable.\n *\n * - `VP8 `: a 3-byte frame tag whose low bit is 0 on a key frame, the start\n * code `9D 01 2A`, then 14-bit width and height (RFC 6386, 9.1).\n * - `VP8L`: the signature `0x2F`, then 14 bits of width − 1, 14 of\n * height − 1 and the alpha hint (RFC 9649, section 3, the lossless header).\n */\nfunction simpleCanvas(\n bytes: Uint8Array,\n chunk: RiffChunk,\n): { width: number; height: number; alpha: boolean } | null {\n const data = bytes.subarray(chunk.dataStart, chunk.dataStart + chunk.size)\n if (chunk.fourcc === 'VP8 ') {\n if (data.length < 10 || (data[0] ?? 1) & 1) return null\n if (data[3] !== 0x9d || data[4] !== 0x01 || data[5] !== 0x2a) return null\n const width = readU16LE(data, 6) & 0x3fff\n const height = readU16LE(data, 8) & 0x3fff\n return width && height ? { width, height, alpha: false } : null\n }\n if (chunk.fourcc === 'VP8L') {\n if (data.length < 5 || data[0] !== 0x2f) return null\n const bits = readU32LE(data, 1)\n return {\n width: (bits & 0x3fff) + 1,\n height: ((bits >>> 14) & 0x3fff) + 1,\n alpha: ((bits >>> 28) & 1) === 1,\n }\n }\n return null\n}\n\n/** A `VP8X` chunk (RFC 9649, 2.7): flags, 3 reserved bytes, canvas − 1. */\nfunction vp8xChunk(flags: number, width: number, height: number): Uint8Array {\n const data = new Uint8Array(10)\n data[0] = flags\n data[4] = (width - 1) & 0xff\n data[5] = ((width - 1) >>> 8) & 0xff\n data[6] = ((width - 1) >>> 16) & 0xff\n data[7] = (height - 1) & 0xff\n data[8] = ((height - 1) >>> 8) & 0xff\n data[9] = ((height - 1) >>> 16) & 0xff\n return buildChunk('VP8X', data)\n}\n\ninterface Piece {\n fourcc: string\n bytes: Uint8Array\n}\n\n/**\n * Writes an update into a WebP and returns the new file.\n *\n * An existing `EXIF` or `XMP ` chunk is replaced in place (later duplicates\n * removed); a new one goes after the image — EXIF before any existing XMP,\n * XMP after EXIF — as the extended format orders them. `null` removes every\n * copy. The `VP8X` EXIF and XMP flags are set to match what the file then\n * holds; ICC, alpha and animation flags are left as they were.\n *\n * Throws `EmbeddedWriteError` for a file that is not a WebP or is truncated,\n * for a layout that is neither simple nor extended, for a simple file whose\n * bitstream header cannot give the canvas size a conversion needs, and for\n * an IRB or PNG text update.\n */\nexport function writeWebpBlocks(\n bytes: Uint8Array,\n update: ImageBlockUpdate,\n): Uint8Array {\n const layout = walkWebp(bytes)\n if (!layout) throw new EmbeddedWriteError('This file is not a WebP image.')\n if (!layout.complete) throw new EmbeddedWriteError(FILE_DAMAGED)\n if (update.irb?.length) {\n throw new EmbeddedWriteError(\n 'A WebP has no place for Photoshop or IPTC metadata.',\n )\n }\n if (\n update.pngText &&\n Object.values(update.pngText).some((value) => value !== null)\n ) {\n throw new EmbeddedWriteError(\n 'PNG text fields can only be written to a PNG.',\n )\n }\n\n const first = layout.chunks[0]\n const extended = first?.fourcc === 'VP8X'\n if (\n !first ||\n (!extended && first.fourcc !== 'VP8 ' && first.fourcc !== 'VP8L')\n ) {\n throw new EmbeddedWriteError(FILE_DAMAGED)\n }\n\n const pieces: Piece[] = layout.chunks.map((chunk) => {\n const expected = 8 + chunk.size + (chunk.size & 1)\n const original = bytes.subarray(chunk.start, chunk.end)\n // A last chunk missing its pad byte gets one, or anything after it\n // would start on an odd offset.\n const padded =\n original.length < expected\n ? concatBytes([original, new Uint8Array(1)])\n : original\n return { fourcc: chunk.fourcc, bytes: padded }\n })\n\n const lastImage = () => {\n for (let i = pieces.length - 1; i >= 0; i -= 1) {\n if (IMAGE_CHUNKS.has(pieces[i]?.fourcc ?? '')) return i + 1\n }\n return pieces.length\n }\n const indexOf = (fourcc: string) =>\n pieces.findIndex((p) => p.fourcc === fourcc)\n\n const place = (\n fourcc: string,\n data: Uint8Array | null,\n anchor: () => number,\n ) => {\n const chunk = data && data.length ? buildChunk(fourcc, data) : null\n const at = indexOf(fourcc)\n for (let i = pieces.length - 1; i > at; i -= 1) {\n if (pieces[i]?.fourcc === fourcc) pieces.splice(i, 1)\n }\n const existing = pieces[at]\n if (existing && chunk) existing.bytes = chunk\n else if (existing) pieces.splice(at, 1)\n else if (chunk) pieces.splice(anchor(), 0, { fourcc, bytes: chunk })\n }\n\n if (update.exif !== undefined) {\n place('EXIF', update.exif && bareTiff(update.exif), () => {\n const xmp = indexOf('XMP ')\n return xmp >= lastImage() ? xmp : lastImage()\n })\n }\n if (update.xmp !== undefined) {\n place('XMP ', update.xmp ? utf8Encode(update.xmp) : null, () => {\n const exif = indexOf('EXIF')\n return exif >= 0 ? Math.max(exif + 1, lastImage()) : lastImage()\n })\n }\n\n const hasExif = indexOf('EXIF') >= 0\n const hasXmp = indexOf('XMP ') >= 0\n const vp8x = pieces[0]\n if (extended && vp8x) {\n const chunk = vp8x.bytes.slice()\n let flags = chunk[8] ?? 0\n flags = hasExif ? flags | FLAG_EXIF : flags & ~FLAG_EXIF\n flags = hasXmp ? flags | FLAG_XMP : flags & ~FLAG_XMP\n chunk[8] = flags\n vp8x.bytes = chunk\n } else if (hasExif || hasXmp) {\n const canvas = simpleCanvas(bytes, first)\n if (!canvas) throw new EmbeddedWriteError(FILE_DAMAGED)\n let flags = (hasExif ? FLAG_EXIF : 0) | (hasXmp ? FLAG_XMP : 0)\n if (canvas.alpha) flags |= FLAG_ALPHA\n if (indexOf('ICCP') >= 0) flags |= FLAG_ICC\n pieces.unshift({\n fourcc: 'VP8X',\n bytes: vp8xChunk(flags, canvas.width, canvas.height),\n })\n }\n\n const body = concatBytes(pieces.map((piece) => piece.bytes))\n if (body.length + 4 > MAX_RIFF_SIZE)\n throw new EmbeddedWriteError(METADATA_TOO_LARGE)\n const header = new Uint8Array(12)\n header.set(RIFF, 0)\n writeU32LE(header, 4, body.length + 4)\n header.set(WEBP, 8)\n return concatBytes([header, body, bytes.subarray(layout.riffEnd)])\n}\n"],"names":["EmbeddedWriteError","FILE_DAMAGED","METADATA_TOO_LARGE","asciiBytes","bytesAt","concatBytes","readAscii","readU16LE","readU32LE","trimNul","utf8Decode","utf8Encode","writeU32LE","RIFF","WEBP","EXIF_HEADER","FLAG_ICC","FLAG_ALPHA","FLAG_EXIF","FLAG_XMP","MAX_RIFF_SIZE","MAX_CHUNKS","IMAGE_CHUNKS","Set","walkWebp","bytes","declared","riffEnd","Math","min","length","chunks","offset","complete","size","dataEnd","end","push","fourcc","start","dataStart","bareTiff","exif","subarray","readWebpBlocks","layout","blocks","chunk","data","slice","xmp","undefined","icc","buildChunk","out","Uint8Array","set","simpleCanvas","width","height","alpha","bits","vp8xChunk","flags","writeWebpBlocks","update","irb","pngText","Object","values","some","value","first","extended","pieces","map","expected","original","padded","lastImage","i","has","indexOf","findIndex","p","place","anchor","at","splice","existing","max","hasExif","hasXmp","vp8x","canvas","unshift","body","piece","header"],"mappings":"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"}
@@ -0,0 +1,231 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ /**
18
+ * A minimal, FAITHFUL XML DOM for editing XMP packets in place (AGL-3331).
19
+ *
20
+ * ## Why not a general XML library
21
+ *
22
+ * An XMP packet written by Lightroom or Photoshop carries structures this
23
+ * module does not understand — `xmpMM:History`, `crs:*` tone curves,
24
+ * `mwg-rs` face regions, contact-info structs — and an edit of one caption
25
+ * must leave every one of them exactly as it was. A conventional DOM
26
+ * normalizes on the way in (quote style, attribute whitespace, entity
27
+ * spelling, `<a/>` versus `<a></a>`), so a round trip through it rewrites
28
+ * the whole packet. This one keeps the raw text of everything it parses
29
+ * next to the decoded value, and {@link serializeXml} writes the raw text
30
+ * back, so `serializeXml(parseXml(s)) === s` for any input it accepts and
31
+ * only the nodes an edit touched come out different.
32
+ *
33
+ * ## What it refuses
34
+ *
35
+ * The input is hostile: it arrives inside an uploaded file. There is NO DTD
36
+ * support at all — `<!DOCTYPE`, and with it `<!ENTITY`, external entities
37
+ * and entity expansion ("billion laughs"), are unparseable, as is any named
38
+ * entity other than the five XML predefines (XML 1.0 §4.6). Depth, node
39
+ * count and input length are bounded. Everything that is not well-formed
40
+ * returns `null`; nothing here throws on input.
41
+ *
42
+ * It is deliberately lenient in one direction only: content outside the
43
+ * root element (the `<?xpacket?>` wrappers, trailing padding, a BOM) is
44
+ * kept as nodes of the document rather than rejected, because XMP packets
45
+ * live in exactly that space and a fragment handed over from an SVG's
46
+ * `<metadata>` may have several top-level elements.
47
+ */
48
+ /** The `xml:` prefix is bound by definition (Namespaces in XML 1.0 §3). */
49
+ export declare const XML_NAMESPACE = "http://www.w3.org/XML/1998/namespace";
50
+ /** The `xmlns:` prefix is bound by definition (Namespaces in XML 1.0 §3). */
51
+ export declare const XMLNS_NAMESPACE = "http://www.w3.org/2000/xmlns/";
52
+ /** Default parse bounds. XMP packets are kilobytes; these are generous. */
53
+ export declare const XML_MAX_DEPTH = 64;
54
+ export declare const XML_MAX_NODES = 50000;
55
+ export declare const XML_MAX_LENGTH: number;
56
+ export interface XmlLimits {
57
+ /** Element nesting depth; the root element is depth 1. */
58
+ maxDepth?: number;
59
+ /** Elements, attributes, text, CDATA, comments and PIs together. */
60
+ maxNodes?: number;
61
+ /** Input length in UTF-16 code units. */
62
+ maxLength?: number;
63
+ }
64
+ export interface XmlDocument {
65
+ type: 'document';
66
+ children: XmlNode[];
67
+ }
68
+ export type XmlNode = XmlElement | XmlText | XmlCData | XmlComment | XmlProcessingInstruction;
69
+ export type XmlParent = XmlDocument | XmlElement;
70
+ export interface XmlElement {
71
+ type: 'element';
72
+ /** The qualified name as written, prefix included. */
73
+ name: string;
74
+ /** In source order; order is part of what a round trip preserves. */
75
+ attributes: XmlAttribute[];
76
+ children: XmlNode[];
77
+ parent: XmlParent | null;
78
+ /**
79
+ * Written as `<a/>`. Only honored while the element has no children, so
80
+ * appending to a self-closed element serializes it as `<a>…</a>`.
81
+ */
82
+ selfClosing: boolean;
83
+ /** Whitespace after the last attribute (or the name), before `>`/`/>`. */
84
+ tagTail: string;
85
+ /** Whitespace between the name and `>` of the end tag: `</a >`. */
86
+ endTagTail: string;
87
+ }
88
+ export interface XmlAttribute {
89
+ /** The qualified name as written. */
90
+ name: string;
91
+ /**
92
+ * The decoded, normalized value (XML 1.0 §3.3.3): entity and character
93
+ * references resolved, literal tabs and newlines turned into spaces.
94
+ */
95
+ value: string;
96
+ /** Exactly what was between the quotes. Serialized as-is. */
97
+ raw: string;
98
+ quote: '"' | "'";
99
+ /** Whitespace before the name — a space, or a newline and an indent. */
100
+ leading: string;
101
+ /** The `=` with whatever whitespace the author put around it. */
102
+ equals: string;
103
+ }
104
+ export interface XmlText {
105
+ type: 'text';
106
+ /** Decoded (references resolved, line ends normalized, XML 1.0 §2.11). */
107
+ value: string;
108
+ /** Exactly as written. Serialized as-is. */
109
+ raw: string;
110
+ }
111
+ export interface XmlCData {
112
+ type: 'cdata';
113
+ value: string;
114
+ }
115
+ export interface XmlComment {
116
+ type: 'comment';
117
+ value: string;
118
+ }
119
+ export interface XmlProcessingInstruction {
120
+ type: 'pi';
121
+ target: string;
122
+ /** Everything after the target up to `?>`, leading whitespace included. */
123
+ data: string;
124
+ }
125
+ /**
126
+ * Decode the raw text of a text node or an attribute value.
127
+ *
128
+ * Applies the normalizations a conforming parser applies, in the order the
129
+ * spec gives them: line ends first (§2.11, CRLF and lone CR become LF),
130
+ * then — for attributes only — each literal whitespace character becomes a
131
+ * space (§3.3.3), then references are replaced. A character produced by a
132
+ * reference is NOT normalized, which is how `&#xA;` survives in an
133
+ * attribute. Returns null for an undeclared entity, a bare `&` or a
134
+ * reference to a character XML cannot hold.
135
+ */
136
+ export declare function decodeXmlText(raw: string, attribute?: boolean): string | null;
137
+ /**
138
+ * Escape a value for a text node. `>` is escaped too so a value can never
139
+ * spell `]]>`, and CR is written as a reference because a parser would
140
+ * otherwise normalize it away (§2.11).
141
+ */
142
+ export declare function escapeXmlText(value: string): string;
143
+ /**
144
+ * Escape a value for an attribute delimited by `quote`. Tab, LF and CR are
145
+ * written as references so attribute-value normalization (§3.3.3) does not
146
+ * turn them into spaces on the next read.
147
+ */
148
+ export declare function escapeXmlAttribute(value: string, quote?: '"' | "'"): string;
149
+ /**
150
+ * Whether a string holds a character that XML 1.0 cannot represent at all,
151
+ * not even as a reference (§2.2): C0 controls other than tab/LF/CR, U+FFFE,
152
+ * U+FFFF and unpaired surrogates. A writer must refuse such a value rather
153
+ * than emit a packet no reader will parse.
154
+ */
155
+ export declare function hasNonXmlCharacters(value: string): boolean;
156
+ /**
157
+ * Parse `input` into a faithful DOM, or return null when it is not
158
+ * well-formed, declares a DTD, or exceeds a bound. Never throws.
159
+ *
160
+ * The parser is iterative — an explicit stack, not recursion — so a
161
+ * hostile nesting depth costs a counter check, not the call stack.
162
+ */
163
+ export declare function parseXml(input: string, limits?: XmlLimits): XmlDocument | null;
164
+ /** Serialize a document or node back to text, raw spellings preserved. */
165
+ export declare function serializeXml(node: XmlDocument | XmlNode): string;
166
+ /** A text node whose raw spelling is the escaped value. */
167
+ export declare function createText(value: string): XmlText;
168
+ /**
169
+ * A whitespace-only text node written verbatim — indentation copied from
170
+ * the document, where escaping a CR would turn `\r\n` into `&#xD;\n`.
171
+ * Anything that is not XML whitespace is dropped.
172
+ */
173
+ export declare function createWhitespace(raw: string): XmlText;
174
+ /** A new element; attributes are `[name, value]` pairs, space-separated. */
175
+ export declare function createElement(name: string, attributes?: ReadonlyArray<readonly [string, string]>, children?: XmlNode[], quote?: '"' | "'"): XmlElement;
176
+ /** Insert nodes at `index`, adopting any elements among them. */
177
+ export declare function insertChildren(parent: XmlParent, index: number, nodes: XmlNode[]): void;
178
+ /** Replace every child of `parent`. */
179
+ export declare function replaceChildren(parent: XmlParent, nodes: XmlNode[]): void;
180
+ /** Remove one child. Returns false when it was not a child of `parent`. */
181
+ export declare function removeChild(parent: XmlParent, node: XmlNode): boolean;
182
+ export declare function getAttribute(element: XmlElement, name: string): XmlAttribute | undefined;
183
+ /**
184
+ * Set an attribute's value. An existing attribute keeps its position,
185
+ * quote style and surrounding whitespace; a new one is appended with
186
+ * `leading` before it.
187
+ */
188
+ export declare function setAttribute(element: XmlElement, name: string, value: string, options?: {
189
+ leading?: string;
190
+ quote?: '"' | "'";
191
+ index?: number;
192
+ }): XmlAttribute;
193
+ export declare function removeAttribute(element: XmlElement, name: string): boolean;
194
+ /** Replace an element's content with one text node (none for ''). */
195
+ export declare function setTextContent(element: XmlElement, value: string): void;
196
+ /** The decoded text of an element's direct text and CDATA children. */
197
+ export declare function textContent(element: XmlElement): string;
198
+ export declare function elementChildren(parent: XmlParent): XmlElement[];
199
+ /** Whether a text node is nothing but XML whitespace. */
200
+ export declare function isWhitespaceText(node: XmlNode | undefined): node is XmlText;
201
+ /**
202
+ * Visit every element in document order, iteratively. Return `false` from
203
+ * `visit` to skip an element's descendants.
204
+ */
205
+ export declare function walkElements(root: XmlParent, visit: (element: XmlElement) => boolean | void): void;
206
+ /** `dc:title` → `{ prefix: 'dc', local: 'title' }`; no colon → prefix ''. */
207
+ export declare function splitQName(name: string): {
208
+ prefix: string;
209
+ local: string;
210
+ };
211
+ /**
212
+ * The namespace URI `prefix` is bound to at `element`, through the nearest
213
+ * `xmlns:prefix` (or, for `''`, `xmlns`) on it or an ancestor. Null when
214
+ * unbound — or, for the default namespace, explicitly undeclared (§6.2).
215
+ */
216
+ export declare function resolvePrefix(element: XmlElement, prefix: string): string | null;
217
+ /** An element's namespace URI: its prefix's, or the default namespace. */
218
+ export declare function elementNamespace(element: XmlElement): string | null;
219
+ /**
220
+ * An attribute's namespace URI. An unprefixed attribute is in NO namespace
221
+ * — the default namespace does not apply to attributes (§6.2).
222
+ */
223
+ export declare function attributeNamespace(element: XmlElement, attr: XmlAttribute): string | null;
224
+ /** Whether an attribute is a namespace declaration. */
225
+ export declare function isNamespaceDeclaration(attr: XmlAttribute): boolean;
226
+ /**
227
+ * A prefix bound to `uri` in scope at `element` and not shadowed by a
228
+ * nearer declaration of the same prefix, or null. The default namespace is
229
+ * never returned: an attribute cannot use it.
230
+ */
231
+ export declare function prefixForNamespace(element: XmlElement, uri: string): string | null;