@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.
- package/package.json +11 -11
- package/src/lib/app-utils/docs-help.generated.d.ts +1 -1
- package/src/lib/app-utils/docs-help.generated.js +1 -0
- package/src/lib/app-utils/docs-help.generated.js.map +1 -1
- package/src/lib/app-utils/docs-index.generated.js +3 -3
- package/src/lib/app-utils/docs-index.generated.js.map +1 -1
- package/src/lib/app-utils/media-embedded-fields.d.ts +471 -0
- package/src/lib/app-utils/media-embedded-fields.js +619 -0
- package/src/lib/app-utils/media-embedded-fields.js.map +1 -0
- package/src/lib/app-utils/media-embedded-metadata/exif.d.ts +160 -0
- package/src/lib/app-utils/media-embedded-metadata/exif.js +1267 -0
- package/src/lib/app-utils/media-embedded-metadata/exif.js.map +1 -0
- package/src/lib/app-utils/media-embedded-metadata/gif.d.ts +44 -0
- package/src/lib/app-utils/media-embedded-metadata/gif.js +134 -0
- package/src/lib/app-utils/media-embedded-metadata/gif.js.map +1 -0
- package/src/lib/app-utils/media-embedded-metadata/heif.d.ts +23 -0
- package/src/lib/app-utils/media-embedded-metadata/heif.js +354 -0
- package/src/lib/app-utils/media-embedded-metadata/heif.js.map +1 -0
- package/src/lib/app-utils/media-embedded-metadata/icc.d.ts +22 -0
- package/src/lib/app-utils/media-embedded-metadata/icc.js +85 -0
- package/src/lib/app-utils/media-embedded-metadata/icc.js.map +1 -0
- package/src/lib/app-utils/media-embedded-metadata/image-blocks.d.ts +145 -0
- package/src/lib/app-utils/media-embedded-metadata/image-blocks.js +200 -0
- package/src/lib/app-utils/media-embedded-metadata/image-blocks.js.map +1 -0
- package/src/lib/app-utils/media-embedded-metadata/index.d.ts +93 -0
- package/src/lib/app-utils/media-embedded-metadata/index.js +744 -0
- package/src/lib/app-utils/media-embedded-metadata/index.js.map +1 -0
- package/src/lib/app-utils/media-embedded-metadata/iptc.d.ts +59 -0
- package/src/lib/app-utils/media-embedded-metadata/iptc.js +529 -0
- package/src/lib/app-utils/media-embedded-metadata/iptc.js.map +1 -0
- package/src/lib/app-utils/media-embedded-metadata/jpeg.d.ts +43 -0
- package/src/lib/app-utils/media-embedded-metadata/jpeg.js +456 -0
- package/src/lib/app-utils/media-embedded-metadata/jpeg.js.map +1 -0
- package/src/lib/app-utils/media-embedded-metadata/mp4.d.ts +37 -0
- package/src/lib/app-utils/media-embedded-metadata/mp4.js +621 -0
- package/src/lib/app-utils/media-embedded-metadata/mp4.js.map +1 -0
- package/src/lib/app-utils/media-embedded-metadata/ooxml.d.ts +131 -0
- package/src/lib/app-utils/media-embedded-metadata/ooxml.js +1058 -0
- package/src/lib/app-utils/media-embedded-metadata/ooxml.js.map +1 -0
- package/src/lib/app-utils/media-embedded-metadata/pdf-objects.d.ts +223 -0
- package/src/lib/app-utils/media-embedded-metadata/pdf-objects.js +936 -0
- package/src/lib/app-utils/media-embedded-metadata/pdf-objects.js.map +1 -0
- package/src/lib/app-utils/media-embedded-metadata/pdf.d.ts +82 -0
- package/src/lib/app-utils/media-embedded-metadata/pdf.js +1167 -0
- package/src/lib/app-utils/media-embedded-metadata/pdf.js.map +1 -0
- package/src/lib/app-utils/media-embedded-metadata/png.d.ts +41 -0
- package/src/lib/app-utils/media-embedded-metadata/png.js +512 -0
- package/src/lib/app-utils/media-embedded-metadata/png.js.map +1 -0
- package/src/lib/app-utils/media-embedded-metadata/svg.d.ts +30 -0
- package/src/lib/app-utils/media-embedded-metadata/svg.js +154 -0
- package/src/lib/app-utils/media-embedded-metadata/svg.js.map +1 -0
- package/src/lib/app-utils/media-embedded-metadata/tiff.d.ts +40 -0
- package/src/lib/app-utils/media-embedded-metadata/tiff.js +158 -0
- package/src/lib/app-utils/media-embedded-metadata/tiff.js.map +1 -0
- package/src/lib/app-utils/media-embedded-metadata/types.d.ts +60 -0
- package/src/lib/app-utils/media-embedded-metadata/types.js +32 -0
- package/src/lib/app-utils/media-embedded-metadata/types.js.map +1 -0
- package/src/lib/app-utils/media-embedded-metadata/webm.d.ts +26 -0
- package/src/lib/app-utils/media-embedded-metadata/webm.js +417 -0
- package/src/lib/app-utils/media-embedded-metadata/webm.js.map +1 -0
- package/src/lib/app-utils/media-embedded-metadata/webp.d.ts +37 -0
- package/src/lib/app-utils/media-embedded-metadata/webp.js +272 -0
- package/src/lib/app-utils/media-embedded-metadata/webp.js.map +1 -0
- package/src/lib/app-utils/media-embedded-metadata/xml.d.ts +231 -0
- package/src/lib/app-utils/media-embedded-metadata/xml.js +598 -0
- package/src/lib/app-utils/media-embedded-metadata/xml.js.map +1 -0
- package/src/lib/app-utils/media-embedded-metadata/xmp.d.ts +100 -0
- package/src/lib/app-utils/media-embedded-metadata/xmp.js +1475 -0
- package/src/lib/app-utils/media-embedded-metadata/xmp.js.map +1 -0
- package/src/lib/app-utils/media-embedded-metadata/zip.d.ts +93 -0
- package/src/lib/app-utils/media-embedded-metadata/zip.js +531 -0
- package/src/lib/app-utils/media-embedded-metadata/zip.js.map +1 -0
- package/src/lib/app-utils/name-search.d.ts +12 -0
- package/src/lib/app-utils/name-search.js +11 -0
- package/src/lib/app-utils/name-search.js.map +1 -1
- package/src/lib/foundation/definitions/platform.types.d.ts +11 -0
- package/src/lib/foundation/definitions/platform.types.js.map +1 -1
- package/src/server.d.ts +1 -0
- package/src/server.js +7 -0
- package/src/server.js.map +1 -1
|
@@ -0,0 +1,512 @@
|
|
|
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
|
+
* PNG metadata blocks: text chunks, `eXIf`, XMP and the ICC profile, read
|
|
18
|
+
* and rewritten without touching the image (AGL-3331).
|
|
19
|
+
*
|
|
20
|
+
* A PNG is an 8-byte signature and a run of chunks (PNG 3rd edition, 5.3):
|
|
21
|
+
* a 4-byte length, a 4-byte type, the data, and a CRC-32 over type and data.
|
|
22
|
+
* The writer rebuilds only the metadata chunks it changes, with fresh CRCs,
|
|
23
|
+
* and copies every other chunk — IHDR, PLTE, every IDAT, APNG's frames, and
|
|
24
|
+
* any chunk it has never heard of — through byte for byte.
|
|
25
|
+
*
|
|
26
|
+
* The blocks:
|
|
27
|
+
*
|
|
28
|
+
* - `tEXt` (Latin-1), `zTXt` (zlib-compressed Latin-1) and `iTXt` (UTF-8,
|
|
29
|
+
* optionally compressed, with a language tag and a translated keyword),
|
|
30
|
+
* 11.3.3. Every one of them is reported as text except the XMP one.
|
|
31
|
+
* - XMP is the `iTXt` chunk with keyword `XML:com.adobe.xmp` (XMP
|
|
32
|
+
* Specification Part 3, 1.1.5); it is read from any of the three types
|
|
33
|
+
* and always written back as an uncompressed `iTXt`, which is what that
|
|
34
|
+
* section asks for so that packet scanners can find it.
|
|
35
|
+
* - `eXIf` holds EXIF as a bare TIFF structure (11.3.3.6), and must come
|
|
36
|
+
* before the first IDAT.
|
|
37
|
+
* - `iCCP` holds a zlib-compressed ICC profile (11.3.2.3). Read-only.
|
|
38
|
+
* - ImageMagick's legacy `Raw profile type exif` / `APP1` / `xmp` text
|
|
39
|
+
* chunks hold the same payloads as hex. They are read only when the
|
|
40
|
+
* modern chunk is absent, and a write that replaces or removes EXIF or
|
|
41
|
+
* XMP removes them too, so no stale copy is left for a reader to fall back
|
|
42
|
+
* on — a GPS removal that left the legacy EXIF behind would not be one.
|
|
43
|
+
*/ import { deflateSync, inflateSync } from "zlib";
|
|
44
|
+
import { EmbeddedWriteError } from "./types.js";
|
|
45
|
+
import { FILE_DAMAGED, METADATA_TOO_LARGE, asciiBytes, bytesAt, concatBytes, crc32, latin1Decode, latin1Encode, readAscii, readU32BE, trimNul, utf8Decode, utf8Encode, writeU32BE } from "./image-blocks.js";
|
|
46
|
+
const SIGNATURE = Uint8Array.from([
|
|
47
|
+
0x89,
|
|
48
|
+
0x50,
|
|
49
|
+
0x4e,
|
|
50
|
+
0x47,
|
|
51
|
+
0x0d,
|
|
52
|
+
0x0a,
|
|
53
|
+
0x1a,
|
|
54
|
+
0x0a
|
|
55
|
+
]);
|
|
56
|
+
const XMP_KEYWORD = 'XML:com.adobe.xmp';
|
|
57
|
+
const RAW_PROFILE = /^Raw profile type (\S+)$/i;
|
|
58
|
+
const EXIF_HEADER = asciiBytes('Exif\0\0');
|
|
59
|
+
const XMP_APP1_ID = asciiBytes('http://ns.adobe.com/xap/1.0/\0');
|
|
60
|
+
/** A chunk's length is at most 2^31 − 1 (PNG 3rd edition, 5.3). */ const MAX_CHUNK_LENGTH = 0x7fffffff;
|
|
61
|
+
/** Chunk count past which a file is not an image; the walk stops. */ const MAX_CHUNKS = 1 << 20;
|
|
62
|
+
/** How many text chunks are decoded; past this they are ignored. */ const MAX_TEXT_CHUNKS = 4096;
|
|
63
|
+
/**
|
|
64
|
+
* The most a compressed chunk may inflate to. zlib data can expand a
|
|
65
|
+
* thousandfold, so a small hostile chunk could otherwise ask for gigabytes.
|
|
66
|
+
*/ const MAX_INFLATED = 32 * 1024 * 1024;
|
|
67
|
+
/** A chunk type is four ASCII letters (5.4). */ function isChunkType(bytes, offset) {
|
|
68
|
+
for(let i = offset; i < offset + 4; i += 1){
|
|
69
|
+
var _bytes_i;
|
|
70
|
+
const c = (_bytes_i = bytes[i]) != null ? _bytes_i : 0;
|
|
71
|
+
if (!(c >= 0x41 && c <= 0x5a || c >= 0x61 && c <= 0x7a)) return false;
|
|
72
|
+
}
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
/** Walks the chunks up to IEND. `null` when the signature is not PNG's. */ function walkPng(bytes) {
|
|
76
|
+
if (!bytesAt(bytes, 0, SIGNATURE)) return null;
|
|
77
|
+
const chunks = [];
|
|
78
|
+
let offset = SIGNATURE.length;
|
|
79
|
+
while(offset + 12 <= bytes.length && chunks.length < MAX_CHUNKS){
|
|
80
|
+
const length = readU32BE(bytes, offset);
|
|
81
|
+
const end = offset + 12 + length;
|
|
82
|
+
if (length > MAX_CHUNK_LENGTH || end > bytes.length) break;
|
|
83
|
+
if (!isChunkType(bytes, offset + 4)) break;
|
|
84
|
+
const type = readAscii(bytes, offset + 4, 4);
|
|
85
|
+
chunks.push({
|
|
86
|
+
type,
|
|
87
|
+
start: offset,
|
|
88
|
+
dataStart: offset + 8,
|
|
89
|
+
dataEnd: offset + 8 + length,
|
|
90
|
+
end
|
|
91
|
+
});
|
|
92
|
+
offset = end;
|
|
93
|
+
if (type === 'IEND') return {
|
|
94
|
+
chunks,
|
|
95
|
+
complete: true,
|
|
96
|
+
end: offset
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
chunks,
|
|
101
|
+
complete: false,
|
|
102
|
+
end: offset
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
/** zlib-inflates, or `null` for a corrupt or oversized stream. */ function inflate(data) {
|
|
106
|
+
try {
|
|
107
|
+
return new Uint8Array(inflateSync(data, {
|
|
108
|
+
maxOutputLength: MAX_INFLATED
|
|
109
|
+
}));
|
|
110
|
+
} catch (unused) {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* The keyword of a text chunk and where its remainder starts: 1 to 79
|
|
116
|
+
* bytes, NUL-terminated (11.3.3.2).
|
|
117
|
+
*/ function readKeyword(data) {
|
|
118
|
+
const nul = data.subarray(0, 80).indexOf(0);
|
|
119
|
+
if (nul < 1) return null;
|
|
120
|
+
return {
|
|
121
|
+
keyword: latin1Decode(data.subarray(0, nul)),
|
|
122
|
+
rest: nul + 1
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
/** Decodes a `tEXt`, `zTXt` or `iTXt` chunk, or `null` if it is malformed. */ function decodeText(type, data) {
|
|
126
|
+
const head = readKeyword(data);
|
|
127
|
+
if (!head) return null;
|
|
128
|
+
const { keyword, rest } = head;
|
|
129
|
+
if (type === 'tEXt') {
|
|
130
|
+
return {
|
|
131
|
+
chunk: {
|
|
132
|
+
keyword,
|
|
133
|
+
type,
|
|
134
|
+
text: latin1Decode(data.subarray(rest))
|
|
135
|
+
},
|
|
136
|
+
compressed: false
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
if (type === 'zTXt') {
|
|
140
|
+
// One compression method byte, which must be 0 (zlib deflate).
|
|
141
|
+
if (data[rest] !== 0) return null;
|
|
142
|
+
const text = inflate(data.subarray(rest + 1));
|
|
143
|
+
return text ? {
|
|
144
|
+
chunk: {
|
|
145
|
+
keyword,
|
|
146
|
+
type,
|
|
147
|
+
text: latin1Decode(text)
|
|
148
|
+
},
|
|
149
|
+
compressed: true
|
|
150
|
+
} : null;
|
|
151
|
+
}
|
|
152
|
+
if (type === 'iTXt') {
|
|
153
|
+
// Compression flag, compression method, language\0, translated\0, text.
|
|
154
|
+
const flag = data[rest];
|
|
155
|
+
const method = data[rest + 1];
|
|
156
|
+
if (flag !== 0 && flag !== 1 || method !== 0) return null;
|
|
157
|
+
const langEnd = data.indexOf(0, rest + 2);
|
|
158
|
+
if (langEnd < 0) return null;
|
|
159
|
+
const translatedEnd = data.indexOf(0, langEnd + 1);
|
|
160
|
+
if (translatedEnd < 0) return null;
|
|
161
|
+
const body = data.subarray(translatedEnd + 1);
|
|
162
|
+
const text = flag === 1 ? inflate(body) : body;
|
|
163
|
+
if (!text) return null;
|
|
164
|
+
const chunk = {
|
|
165
|
+
keyword,
|
|
166
|
+
type,
|
|
167
|
+
text: utf8Decode(text)
|
|
168
|
+
};
|
|
169
|
+
const language = latin1Decode(data.subarray(rest + 2, langEnd));
|
|
170
|
+
const translatedKeyword = utf8Decode(data.subarray(langEnd + 1, translatedEnd));
|
|
171
|
+
if (language) chunk.language = language;
|
|
172
|
+
if (translatedKeyword) chunk.translatedKeyword = translatedKeyword;
|
|
173
|
+
return {
|
|
174
|
+
chunk,
|
|
175
|
+
compressed: flag === 1
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Decodes an ImageMagick raw profile: `\n<name>\n<length>\n` then the bytes
|
|
182
|
+
* as hex, wrapped at 72 columns (ImageMagick `coders/png.c`). `null` when
|
|
183
|
+
* the header is malformed or the hex is short or not hex.
|
|
184
|
+
*/ function decodeRawProfile(text) {
|
|
185
|
+
const match = /^\s*\S+\s*\n\s*(\d{1,10})\s*\n/.exec(text);
|
|
186
|
+
if (!match) return null;
|
|
187
|
+
const length = Number(match[1]);
|
|
188
|
+
if (!length || length > MAX_INFLATED) return null;
|
|
189
|
+
const out = new Uint8Array(length);
|
|
190
|
+
let nibble = -1;
|
|
191
|
+
let written = 0;
|
|
192
|
+
for(let i = match[0].length; i < text.length && written < length; i += 1){
|
|
193
|
+
const c = text.charCodeAt(i);
|
|
194
|
+
let value;
|
|
195
|
+
if (c >= 0x30 && c <= 0x39) value = c - 0x30;
|
|
196
|
+
else if (c >= 0x61 && c <= 0x66) value = c - 0x57;
|
|
197
|
+
else if (c >= 0x41 && c <= 0x46) value = c - 0x37;
|
|
198
|
+
else if (c === 0x0a || c === 0x0d || c === 0x20 || c === 0x09) continue;
|
|
199
|
+
else return null;
|
|
200
|
+
if (nibble < 0) {
|
|
201
|
+
nibble = value;
|
|
202
|
+
} else {
|
|
203
|
+
out[written] = nibble << 4 | value;
|
|
204
|
+
written += 1;
|
|
205
|
+
nibble = -1;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return written === length ? out : null;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Which block a legacy raw profile holds, with its payload unwrapped. `APP1`
|
|
212
|
+
* is ImageMagick's name for a JPEG APP1 payload, which is EXIF or XMP by its
|
|
213
|
+
* own prefix.
|
|
214
|
+
*/ function legacyProfile(keyword, text) {
|
|
215
|
+
var _RAW_PROFILE_exec_, _RAW_PROFILE_exec;
|
|
216
|
+
const name = (_RAW_PROFILE_exec = RAW_PROFILE.exec(keyword)) == null ? void 0 : (_RAW_PROFILE_exec_ = _RAW_PROFILE_exec[1]) == null ? void 0 : _RAW_PROFILE_exec_.toLowerCase();
|
|
217
|
+
if (name !== 'exif' && name !== 'xmp' && name !== 'app1') return null;
|
|
218
|
+
const payload = decodeRawProfile(text);
|
|
219
|
+
if (!payload) return null;
|
|
220
|
+
if (bytesAt(payload, 0, EXIF_HEADER)) {
|
|
221
|
+
return {
|
|
222
|
+
kind: 'exif',
|
|
223
|
+
payload: payload.subarray(EXIF_HEADER.length)
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
if (bytesAt(payload, 0, XMP_APP1_ID)) {
|
|
227
|
+
return {
|
|
228
|
+
kind: 'xmp',
|
|
229
|
+
payload: payload.subarray(XMP_APP1_ID.length)
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
if (name === 'exif') return {
|
|
233
|
+
kind: 'exif',
|
|
234
|
+
payload
|
|
235
|
+
};
|
|
236
|
+
if (name === 'xmp') return {
|
|
237
|
+
kind: 'xmp',
|
|
238
|
+
payload
|
|
239
|
+
};
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
/** Strips an `Exif\0\0` prefix some writers put in front of the TIFF bytes. */ function bareTiff(exif) {
|
|
243
|
+
return bytesAt(exif, 0, EXIF_HEADER) ? exif.subarray(EXIF_HEADER.length) : exif;
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Reads the metadata blocks of a PNG. Returns `null` when the signature is
|
|
247
|
+
* not PNG's; a damaged file yields what came before the damage.
|
|
248
|
+
*/ export function readPngBlocks(bytes) {
|
|
249
|
+
const layout = walkPng(bytes);
|
|
250
|
+
if (!layout) return null;
|
|
251
|
+
const blocks = {};
|
|
252
|
+
const pngText = [];
|
|
253
|
+
let legacyExif;
|
|
254
|
+
let legacyXmp;
|
|
255
|
+
let decoded = 0;
|
|
256
|
+
for (const chunk of layout.chunks){
|
|
257
|
+
var _decodeText;
|
|
258
|
+
const data = bytes.subarray(chunk.dataStart, chunk.dataEnd);
|
|
259
|
+
if (chunk.type === 'eXIf') {
|
|
260
|
+
if (!blocks.exif) blocks.exif = bareTiff(data).slice();
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
if (chunk.type === 'iCCP') {
|
|
264
|
+
if (blocks.icc) continue;
|
|
265
|
+
// Profile name\0, compression method (0), zlib data.
|
|
266
|
+
const head = readKeyword(data);
|
|
267
|
+
if (!head || data[head.rest] !== 0) continue;
|
|
268
|
+
const icc = inflate(data.subarray(head.rest + 1));
|
|
269
|
+
if (icc) blocks.icc = icc;
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
if (chunk.type !== 'tEXt' && chunk.type !== 'zTXt' && chunk.type !== 'iTXt') {
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
if (decoded >= MAX_TEXT_CHUNKS) continue;
|
|
276
|
+
decoded += 1;
|
|
277
|
+
const text = (_decodeText = decodeText(chunk.type, data)) == null ? void 0 : _decodeText.chunk;
|
|
278
|
+
if (!text) continue;
|
|
279
|
+
if (text.keyword === XMP_KEYWORD) {
|
|
280
|
+
if (blocks.xmp === undefined) blocks.xmp = trimNul(text.text);
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
if (RAW_PROFILE.test(text.keyword)) {
|
|
284
|
+
// A hex-encoded binary profile, never text a person would read.
|
|
285
|
+
const legacy = legacyProfile(text.keyword, text.text);
|
|
286
|
+
if ((legacy == null ? void 0 : legacy.kind) === 'exif') legacyExif != null ? legacyExif : legacyExif = legacy.payload;
|
|
287
|
+
if ((legacy == null ? void 0 : legacy.kind) === 'xmp') legacyXmp != null ? legacyXmp : legacyXmp = legacy.payload;
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
pngText.push(text);
|
|
291
|
+
}
|
|
292
|
+
if (!blocks.exif && legacyExif) blocks.exif = legacyExif;
|
|
293
|
+
if (blocks.xmp === undefined && legacyXmp) {
|
|
294
|
+
blocks.xmp = trimNul(utf8Decode(legacyXmp));
|
|
295
|
+
}
|
|
296
|
+
if (pngText.length) blocks.pngText = pngText;
|
|
297
|
+
return blocks;
|
|
298
|
+
}
|
|
299
|
+
/** Builds a chunk: length, type, data, CRC-32 over type and data. */ function buildChunk(type, data) {
|
|
300
|
+
if (data.length > MAX_CHUNK_LENGTH) throw new EmbeddedWriteError(METADATA_TOO_LARGE);
|
|
301
|
+
const out = new Uint8Array(12 + data.length);
|
|
302
|
+
const typeBytes = asciiBytes(type);
|
|
303
|
+
writeU32BE(out, 0, data.length);
|
|
304
|
+
out.set(typeBytes, 4);
|
|
305
|
+
out.set(data, 8);
|
|
306
|
+
writeU32BE(out, 8 + data.length, crc32(typeBytes, data));
|
|
307
|
+
return out;
|
|
308
|
+
}
|
|
309
|
+
/** An uncompressed or compressed `iTXt` chunk. */ function iTXtChunk(keyword, text, options = {}) {
|
|
310
|
+
var _latin1Encode, _options_language, _options_translatedKeyword;
|
|
311
|
+
const body = utf8Encode(text);
|
|
312
|
+
return buildChunk('iTXt', concatBytes([
|
|
313
|
+
(_latin1Encode = latin1Encode(keyword)) != null ? _latin1Encode : new Uint8Array(0),
|
|
314
|
+
Uint8Array.from([
|
|
315
|
+
0,
|
|
316
|
+
options.compressed ? 1 : 0,
|
|
317
|
+
0
|
|
318
|
+
]),
|
|
319
|
+
asciiBytes((_options_language = options.language) != null ? _options_language : ''),
|
|
320
|
+
Uint8Array.from([
|
|
321
|
+
0
|
|
322
|
+
]),
|
|
323
|
+
utf8Encode((_options_translatedKeyword = options.translatedKeyword) != null ? _options_translatedKeyword : ''),
|
|
324
|
+
Uint8Array.from([
|
|
325
|
+
0
|
|
326
|
+
]),
|
|
327
|
+
options.compressed ? new Uint8Array(deflateSync(body)) : body
|
|
328
|
+
]));
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* The chunk for a text value. The type of the chunk it replaces is kept
|
|
332
|
+
* where the value allows it — a `zTXt` stays compressed, an `iTXt` keeps its
|
|
333
|
+
* compression and language — and Latin-1 text becomes `tEXt` otherwise.
|
|
334
|
+
* Text outside Latin-1 has to be `iTXt` (11.3.3.3 vs 11.3.3.5).
|
|
335
|
+
*/ function textChunk(keyword, text, previous) {
|
|
336
|
+
var _latin1Encode;
|
|
337
|
+
const latin1 = latin1Encode(text);
|
|
338
|
+
const key = (_latin1Encode = latin1Encode(keyword)) != null ? _latin1Encode : new Uint8Array(0);
|
|
339
|
+
const type = previous == null ? void 0 : previous.chunk.type;
|
|
340
|
+
if (type === 'iTXt' || !latin1) {
|
|
341
|
+
var _ref;
|
|
342
|
+
return iTXtChunk(keyword, text, {
|
|
343
|
+
compressed: (_ref = previous == null ? void 0 : previous.compressed) != null ? _ref : false,
|
|
344
|
+
language: previous == null ? void 0 : previous.chunk.language,
|
|
345
|
+
translatedKeyword: previous == null ? void 0 : previous.chunk.translatedKeyword
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
if (type === 'zTXt') {
|
|
349
|
+
return buildChunk('zTXt', concatBytes([
|
|
350
|
+
key,
|
|
351
|
+
Uint8Array.from([
|
|
352
|
+
0,
|
|
353
|
+
0
|
|
354
|
+
]),
|
|
355
|
+
new Uint8Array(deflateSync(latin1))
|
|
356
|
+
]));
|
|
357
|
+
}
|
|
358
|
+
return buildChunk('tEXt', concatBytes([
|
|
359
|
+
key,
|
|
360
|
+
Uint8Array.from([
|
|
361
|
+
0
|
|
362
|
+
]),
|
|
363
|
+
latin1
|
|
364
|
+
]));
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* A keyword the PNG spec accepts (11.3.3.2): 1 to 79 printable Latin-1
|
|
368
|
+
* characters, no leading, trailing or consecutive spaces.
|
|
369
|
+
*/ function isValidKeyword(keyword) {
|
|
370
|
+
if (keyword.length < 1 || keyword.length > 79) return false;
|
|
371
|
+
if (/^ | $| {2}/.test(keyword)) return false;
|
|
372
|
+
for(let i = 0; i < keyword.length; i += 1){
|
|
373
|
+
const c = keyword.charCodeAt(i);
|
|
374
|
+
if (!(c >= 0x20 && c <= 0x7e || c >= 0xa1 && c <= 0xff)) return false;
|
|
375
|
+
}
|
|
376
|
+
return true;
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Writes an update into a PNG and returns the new file.
|
|
380
|
+
*
|
|
381
|
+
* - `exif` replaces the `eXIf` chunk (moving it before IDAT if an old file
|
|
382
|
+
* had it after), or inserts one immediately before the first IDAT.
|
|
383
|
+
* - `xmp` replaces the XMP text chunk in place as an uncompressed `iTXt`,
|
|
384
|
+
* or inserts one before the first IDAT.
|
|
385
|
+
* - `pngText` sets or removes text chunks by keyword. A set replaces the
|
|
386
|
+
* first chunk with that keyword and removes the rest; a new keyword is
|
|
387
|
+
* inserted before the first IDAT.
|
|
388
|
+
* - Replacing or removing EXIF or XMP also removes the legacy raw-profile
|
|
389
|
+
* copy of it.
|
|
390
|
+
*
|
|
391
|
+
* Every chunk it does not rewrite, and every byte after IEND, is copied
|
|
392
|
+
* verbatim. Throws `EmbeddedWriteError` for a file that is not a PNG or that
|
|
393
|
+
* ends before IEND, for an IRB (PNG has no place for one), for a keyword the
|
|
394
|
+
* PNG spec does not allow, and for text containing NUL.
|
|
395
|
+
*/ export function writePngBlocks(bytes, update) {
|
|
396
|
+
var _update_pngText;
|
|
397
|
+
var _update_irb;
|
|
398
|
+
const layout = walkPng(bytes);
|
|
399
|
+
if (!layout) throw new EmbeddedWriteError('This file is not a PNG image.');
|
|
400
|
+
if (!layout.complete) throw new EmbeddedWriteError(FILE_DAMAGED);
|
|
401
|
+
if ((_update_irb = update.irb) == null ? void 0 : _update_irb.length) {
|
|
402
|
+
throw new EmbeddedWriteError('A PNG has no place for Photoshop or IPTC metadata.');
|
|
403
|
+
}
|
|
404
|
+
const pieces = layout.chunks.map((chunk)=>{
|
|
405
|
+
const piece = {
|
|
406
|
+
bytes: bytes.subarray(chunk.start, chunk.end),
|
|
407
|
+
kind: 'other',
|
|
408
|
+
type: chunk.type
|
|
409
|
+
};
|
|
410
|
+
if (chunk.type === 'eXIf') piece.kind = 'exif';
|
|
411
|
+
if (chunk.type === 'tEXt' || chunk.type === 'zTXt' || chunk.type === 'iTXt') {
|
|
412
|
+
var _ref;
|
|
413
|
+
var _readKeyword;
|
|
414
|
+
const data = bytes.subarray(chunk.dataStart, chunk.dataEnd);
|
|
415
|
+
const text = decodeText(chunk.type, data);
|
|
416
|
+
const keyword = (_ref = text == null ? void 0 : text.chunk.keyword) != null ? _ref : (_readKeyword = readKeyword(data)) == null ? void 0 : _readKeyword.keyword;
|
|
417
|
+
if (keyword === XMP_KEYWORD) {
|
|
418
|
+
piece.kind = 'xmp';
|
|
419
|
+
} else if (keyword && RAW_PROFILE.test(keyword)) {
|
|
420
|
+
const legacy = text && legacyProfile(keyword, text.chunk.text);
|
|
421
|
+
if (legacy) piece.kind = legacy.kind === 'exif' ? 'legacyExif' : 'legacyXmp';
|
|
422
|
+
} else if (text) {
|
|
423
|
+
piece.kind = 'text';
|
|
424
|
+
piece.text = text;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
return piece;
|
|
428
|
+
});
|
|
429
|
+
const remove = (predicate)=>{
|
|
430
|
+
for(let i = pieces.length - 1; i >= 0; i -= 1){
|
|
431
|
+
const piece = pieces[i];
|
|
432
|
+
if (piece && predicate(piece)) pieces.splice(i, 1);
|
|
433
|
+
}
|
|
434
|
+
};
|
|
435
|
+
/** Before the first IDAT — or the APNG `fcTL` that introduces it. */ const imageStart = ()=>{
|
|
436
|
+
const index = pieces.findIndex((p)=>p.type === 'IDAT' || p.type === 'fcTL');
|
|
437
|
+
return index >= 0 ? index : pieces.findIndex((p)=>p.type === 'IEND');
|
|
438
|
+
};
|
|
439
|
+
const inserted = [];
|
|
440
|
+
if (update.exif !== undefined) {
|
|
441
|
+
const tiff = update.exif && update.exif.length ? bareTiff(update.exif) : null;
|
|
442
|
+
const at = pieces.findIndex((p)=>p.kind === 'exif');
|
|
443
|
+
const inPlace = tiff && at >= 0 && at < imageStart();
|
|
444
|
+
remove((p)=>p.kind === 'legacyExif');
|
|
445
|
+
if (inPlace) {
|
|
446
|
+
const first = pieces.findIndex((p)=>p.kind === 'exif');
|
|
447
|
+
const piece = pieces[first];
|
|
448
|
+
if (piece) piece.bytes = buildChunk('eXIf', tiff);
|
|
449
|
+
remove((p)=>p.kind === 'exif' && p !== piece);
|
|
450
|
+
} else {
|
|
451
|
+
remove((p)=>p.kind === 'exif');
|
|
452
|
+
if (tiff) inserted.push({
|
|
453
|
+
bytes: buildChunk('eXIf', tiff),
|
|
454
|
+
kind: 'exif',
|
|
455
|
+
type: 'eXIf'
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
if (update.xmp !== undefined) {
|
|
460
|
+
remove((p)=>p.kind === 'legacyXmp');
|
|
461
|
+
const chunk = update.xmp ? iTXtChunk(XMP_KEYWORD, update.xmp) : null;
|
|
462
|
+
const first = pieces.find((p)=>p.kind === 'xmp');
|
|
463
|
+
remove((p)=>p.kind === 'xmp' && (p !== first || !chunk));
|
|
464
|
+
if (chunk && first) first.bytes = chunk;
|
|
465
|
+
else if (chunk) inserted.push({
|
|
466
|
+
bytes: chunk,
|
|
467
|
+
kind: 'xmp',
|
|
468
|
+
type: 'iTXt'
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
for (const [keyword, value] of Object.entries((_update_pngText = update.pngText) != null ? _update_pngText : {})){
|
|
472
|
+
var _ref;
|
|
473
|
+
if (value === undefined) continue;
|
|
474
|
+
const first = pieces.find((p)=>{
|
|
475
|
+
var _p_text;
|
|
476
|
+
return p.kind === 'text' && ((_p_text = p.text) == null ? void 0 : _p_text.chunk.keyword) === keyword;
|
|
477
|
+
});
|
|
478
|
+
// A keyword the file already carries round-trips as it is, even one an
|
|
479
|
+
// older writer spelled against the rules; a NEW keyword must follow
|
|
480
|
+
// them (11.3.3.2).
|
|
481
|
+
if (!first && !isValidKeyword(keyword) || latin1Encode(keyword) === null || keyword === XMP_KEYWORD || RAW_PROFILE.test(keyword)) {
|
|
482
|
+
throw new EmbeddedWriteError(`"${keyword}" cannot be used as a PNG text keyword.`);
|
|
483
|
+
}
|
|
484
|
+
if (value !== null && value.includes('\0')) {
|
|
485
|
+
throw new EmbeddedWriteError('PNG text cannot contain a NUL character.');
|
|
486
|
+
}
|
|
487
|
+
remove((p)=>{
|
|
488
|
+
var _p_text;
|
|
489
|
+
return p.kind === 'text' && ((_p_text = p.text) == null ? void 0 : _p_text.chunk.keyword) === keyword && (p !== first || value === null);
|
|
490
|
+
});
|
|
491
|
+
if (value === null) continue;
|
|
492
|
+
const chunk = textChunk(keyword, value, (_ref = first == null ? void 0 : first.text) != null ? _ref : null);
|
|
493
|
+
if (first) {
|
|
494
|
+
first.bytes = chunk;
|
|
495
|
+
} else {
|
|
496
|
+
inserted.push({
|
|
497
|
+
bytes: chunk,
|
|
498
|
+
kind: 'text',
|
|
499
|
+
type: 'tEXt'
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
const at = imageStart();
|
|
504
|
+
pieces.splice(at >= 0 ? at : pieces.length, 0, ...inserted);
|
|
505
|
+
return concatBytes([
|
|
506
|
+
SIGNATURE,
|
|
507
|
+
...pieces.map((piece)=>piece.bytes),
|
|
508
|
+
bytes.subarray(layout.end)
|
|
509
|
+
]);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
//# sourceMappingURL=png.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/aglyn/src/lib/app-utils/media-embedded-metadata/png.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 * PNG metadata blocks: text chunks, `eXIf`, XMP and the ICC profile, read\n * and rewritten without touching the image (AGL-3331).\n *\n * A PNG is an 8-byte signature and a run of chunks (PNG 3rd edition, 5.3):\n * a 4-byte length, a 4-byte type, the data, and a CRC-32 over type and data.\n * The writer rebuilds only the metadata chunks it changes, with fresh CRCs,\n * and copies every other chunk — IHDR, PLTE, every IDAT, APNG's frames, and\n * any chunk it has never heard of — through byte for byte.\n *\n * The blocks:\n *\n * - `tEXt` (Latin-1), `zTXt` (zlib-compressed Latin-1) and `iTXt` (UTF-8,\n * optionally compressed, with a language tag and a translated keyword),\n * 11.3.3. Every one of them is reported as text except the XMP one.\n * - XMP is the `iTXt` chunk with keyword `XML:com.adobe.xmp` (XMP\n * Specification Part 3, 1.1.5); it is read from any of the three types\n * and always written back as an uncompressed `iTXt`, which is what that\n * section asks for so that packet scanners can find it.\n * - `eXIf` holds EXIF as a bare TIFF structure (11.3.3.6), and must come\n * before the first IDAT.\n * - `iCCP` holds a zlib-compressed ICC profile (11.3.2.3). Read-only.\n * - ImageMagick's legacy `Raw profile type exif` / `APP1` / `xmp` text\n * chunks hold the same payloads as hex. They are read only when the\n * modern chunk is absent, and a write that replaces or removes EXIF or\n * XMP removes them too, so no stale copy is left for a reader to fall back\n * on — a GPS removal that left the legacy EXIF behind would not be one.\n */\n\nimport { deflateSync, inflateSync } from 'zlib'\nimport { EmbeddedWriteError } from './types'\nimport {\n FILE_DAMAGED,\n METADATA_TOO_LARGE,\n asciiBytes,\n bytesAt,\n concatBytes,\n crc32,\n latin1Decode,\n latin1Encode,\n readAscii,\n readU32BE,\n trimNul,\n utf8Decode,\n utf8Encode,\n writeU32BE,\n type ImageBlockUpdate,\n type ImageBlocks,\n type PngTextChunk,\n} from './image-blocks'\n\nconst SIGNATURE = Uint8Array.from([\n 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a,\n])\nconst XMP_KEYWORD = 'XML:com.adobe.xmp'\nconst RAW_PROFILE = /^Raw profile type (\\S+)$/i\nconst EXIF_HEADER = asciiBytes('Exif\\0\\0')\nconst XMP_APP1_ID = asciiBytes('http://ns.adobe.com/xap/1.0/\\0')\n\n/** A chunk's length is at most 2^31 − 1 (PNG 3rd edition, 5.3). */\nconst MAX_CHUNK_LENGTH = 0x7fffffff\n/** Chunk count past which a file is not an image; the walk stops. */\nconst MAX_CHUNKS = 1 << 20\n/** How many text chunks are decoded; past this they are ignored. */\nconst MAX_TEXT_CHUNKS = 4096\n/**\n * The most a compressed chunk may inflate to. zlib data can expand a\n * thousandfold, so a small hostile chunk could otherwise ask for gigabytes.\n */\nconst MAX_INFLATED = 32 * 1024 * 1024\n\ninterface PngChunk {\n type: string\n /** Offset of the length field. */\n start: number\n dataStart: number\n dataEnd: number\n /** One past the CRC. */\n end: number\n}\n\ninterface PngLayout {\n chunks: PngChunk[]\n /** True when the walk reached IEND. */\n complete: boolean\n /** Where the chunk walk ended: after IEND, or at the damage. */\n end: number\n}\n\n/** A chunk type is four ASCII letters (5.4). */\nfunction isChunkType(bytes: Uint8Array, offset: number): boolean {\n for (let i = offset; i < offset + 4; i += 1) {\n const c = bytes[i] ?? 0\n if (!((c >= 0x41 && c <= 0x5a) || (c >= 0x61 && c <= 0x7a))) return false\n }\n return true\n}\n\n/** Walks the chunks up to IEND. `null` when the signature is not PNG's. */\nfunction walkPng(bytes: Uint8Array): PngLayout | null {\n if (!bytesAt(bytes, 0, SIGNATURE)) return null\n const chunks: PngChunk[] = []\n let offset = SIGNATURE.length\n while (offset + 12 <= bytes.length && chunks.length < MAX_CHUNKS) {\n const length = readU32BE(bytes, offset)\n const end = offset + 12 + length\n if (length > MAX_CHUNK_LENGTH || end > bytes.length) break\n if (!isChunkType(bytes, offset + 4)) break\n const type = readAscii(bytes, offset + 4, 4)\n chunks.push({\n type,\n start: offset,\n dataStart: offset + 8,\n dataEnd: offset + 8 + length,\n end,\n })\n offset = end\n if (type === 'IEND') return { chunks, complete: true, end: offset }\n }\n return { chunks, complete: false, end: offset }\n}\n\n/** zlib-inflates, or `null` for a corrupt or oversized stream. */\nfunction inflate(data: Uint8Array): Uint8Array | null {\n try {\n return new Uint8Array(inflateSync(data, { maxOutputLength: MAX_INFLATED }))\n } catch {\n return null\n }\n}\n\n/**\n * The keyword of a text chunk and where its remainder starts: 1 to 79\n * bytes, NUL-terminated (11.3.3.2).\n */\nfunction readKeyword(\n data: Uint8Array,\n): { keyword: string; rest: number } | null {\n const nul = data.subarray(0, 80).indexOf(0)\n if (nul < 1) return null\n return { keyword: latin1Decode(data.subarray(0, nul)), rest: nul + 1 }\n}\n\n/** A decoded text chunk, and whether it was stored compressed. */\ninterface DecodedText {\n chunk: PngTextChunk\n compressed: boolean\n}\n\n/** Decodes a `tEXt`, `zTXt` or `iTXt` chunk, or `null` if it is malformed. */\nfunction decodeText(type: string, data: Uint8Array): DecodedText | null {\n const head = readKeyword(data)\n if (!head) return null\n const { keyword, rest } = head\n if (type === 'tEXt') {\n return {\n chunk: { keyword, type, text: latin1Decode(data.subarray(rest)) },\n compressed: false,\n }\n }\n if (type === 'zTXt') {\n // One compression method byte, which must be 0 (zlib deflate).\n if (data[rest] !== 0) return null\n const text = inflate(data.subarray(rest + 1))\n return text\n ? { chunk: { keyword, type, text: latin1Decode(text) }, compressed: true }\n : null\n }\n if (type === 'iTXt') {\n // Compression flag, compression method, language\\0, translated\\0, text.\n const flag = data[rest]\n const method = data[rest + 1]\n if ((flag !== 0 && flag !== 1) || method !== 0) return null\n const langEnd = data.indexOf(0, rest + 2)\n if (langEnd < 0) return null\n const translatedEnd = data.indexOf(0, langEnd + 1)\n if (translatedEnd < 0) return null\n const body = data.subarray(translatedEnd + 1)\n const text = flag === 1 ? inflate(body) : body\n if (!text) return null\n const chunk: PngTextChunk = { keyword, type, text: utf8Decode(text) }\n const language = latin1Decode(data.subarray(rest + 2, langEnd))\n const translatedKeyword = utf8Decode(\n data.subarray(langEnd + 1, translatedEnd),\n )\n if (language) chunk.language = language\n if (translatedKeyword) chunk.translatedKeyword = translatedKeyword\n return { chunk, compressed: flag === 1 }\n }\n return null\n}\n\n/**\n * Decodes an ImageMagick raw profile: `\\n<name>\\n<length>\\n` then the bytes\n * as hex, wrapped at 72 columns (ImageMagick `coders/png.c`). `null` when\n * the header is malformed or the hex is short or not hex.\n */\nfunction decodeRawProfile(text: string): Uint8Array | null {\n const match = /^\\s*\\S+\\s*\\n\\s*(\\d{1,10})\\s*\\n/.exec(text)\n if (!match) return null\n const length = Number(match[1])\n if (!length || length > MAX_INFLATED) return null\n const out = new Uint8Array(length)\n let nibble = -1\n let written = 0\n for (let i = match[0].length; i < text.length && written < length; i += 1) {\n const c = text.charCodeAt(i)\n let value: number\n if (c >= 0x30 && c <= 0x39) value = c - 0x30\n else if (c >= 0x61 && c <= 0x66) value = c - 0x57\n else if (c >= 0x41 && c <= 0x46) value = c - 0x37\n else if (c === 0x0a || c === 0x0d || c === 0x20 || c === 0x09) continue\n else return null\n if (nibble < 0) {\n nibble = value\n } else {\n out[written] = (nibble << 4) | value\n written += 1\n nibble = -1\n }\n }\n return written === length ? out : null\n}\n\ntype LegacyKind = 'exif' | 'xmp'\n\n/**\n * Which block a legacy raw profile holds, with its payload unwrapped. `APP1`\n * is ImageMagick's name for a JPEG APP1 payload, which is EXIF or XMP by its\n * own prefix.\n */\nfunction legacyProfile(\n keyword: string,\n text: string,\n): { kind: LegacyKind; payload: Uint8Array } | null {\n const name = RAW_PROFILE.exec(keyword)?.[1]?.toLowerCase()\n if (name !== 'exif' && name !== 'xmp' && name !== 'app1') return null\n const payload = decodeRawProfile(text)\n if (!payload) return null\n if (bytesAt(payload, 0, EXIF_HEADER)) {\n return { kind: 'exif', payload: payload.subarray(EXIF_HEADER.length) }\n }\n if (bytesAt(payload, 0, XMP_APP1_ID)) {\n return { kind: 'xmp', payload: payload.subarray(XMP_APP1_ID.length) }\n }\n if (name === 'exif') return { kind: 'exif', payload }\n if (name === 'xmp') return { kind: 'xmp', payload }\n return null\n}\n\n/** Strips an `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 PNG. Returns `null` when the signature is\n * not PNG's; a damaged file yields what came before the damage.\n */\nexport function readPngBlocks(bytes: Uint8Array): ImageBlocks | null {\n const layout = walkPng(bytes)\n if (!layout) return null\n const blocks: ImageBlocks = {}\n const pngText: PngTextChunk[] = []\n let legacyExif: Uint8Array | undefined\n let legacyXmp: Uint8Array | undefined\n let decoded = 0\n for (const chunk of layout.chunks) {\n const data = bytes.subarray(chunk.dataStart, chunk.dataEnd)\n if (chunk.type === 'eXIf') {\n if (!blocks.exif) blocks.exif = bareTiff(data).slice()\n continue\n }\n if (chunk.type === 'iCCP') {\n if (blocks.icc) continue\n // Profile name\\0, compression method (0), zlib data.\n const head = readKeyword(data)\n if (!head || data[head.rest] !== 0) continue\n const icc = inflate(data.subarray(head.rest + 1))\n if (icc) blocks.icc = icc\n continue\n }\n if (\n chunk.type !== 'tEXt' &&\n chunk.type !== 'zTXt' &&\n chunk.type !== 'iTXt'\n ) {\n continue\n }\n if (decoded >= MAX_TEXT_CHUNKS) continue\n decoded += 1\n const text = decodeText(chunk.type, data)?.chunk\n if (!text) continue\n if (text.keyword === XMP_KEYWORD) {\n if (blocks.xmp === undefined) blocks.xmp = trimNul(text.text)\n continue\n }\n if (RAW_PROFILE.test(text.keyword)) {\n // A hex-encoded binary profile, never text a person would read.\n const legacy = legacyProfile(text.keyword, text.text)\n if (legacy?.kind === 'exif') legacyExif ??= legacy.payload\n if (legacy?.kind === 'xmp') legacyXmp ??= legacy.payload\n continue\n }\n pngText.push(text)\n }\n if (!blocks.exif && legacyExif) blocks.exif = legacyExif\n if (blocks.xmp === undefined && legacyXmp) {\n blocks.xmp = trimNul(utf8Decode(legacyXmp))\n }\n if (pngText.length) blocks.pngText = pngText\n return blocks\n}\n\n/** Builds a chunk: length, type, data, CRC-32 over type and data. */\nfunction buildChunk(type: string, data: Uint8Array): Uint8Array {\n if (data.length > MAX_CHUNK_LENGTH)\n throw new EmbeddedWriteError(METADATA_TOO_LARGE)\n const out = new Uint8Array(12 + data.length)\n const typeBytes = asciiBytes(type)\n writeU32BE(out, 0, data.length)\n out.set(typeBytes, 4)\n out.set(data, 8)\n writeU32BE(out, 8 + data.length, crc32(typeBytes, data))\n return out\n}\n\n/** An uncompressed or compressed `iTXt` chunk. */\nfunction iTXtChunk(\n keyword: string,\n text: string,\n options: {\n compressed?: boolean\n language?: string\n translatedKeyword?: string\n } = {},\n): Uint8Array {\n const body = utf8Encode(text)\n return buildChunk(\n 'iTXt',\n concatBytes([\n latin1Encode(keyword) ?? new Uint8Array(0),\n Uint8Array.from([0, options.compressed ? 1 : 0, 0]),\n asciiBytes(options.language ?? ''),\n Uint8Array.from([0]),\n utf8Encode(options.translatedKeyword ?? ''),\n Uint8Array.from([0]),\n options.compressed ? new Uint8Array(deflateSync(body)) : body,\n ]),\n )\n}\n\n/**\n * The chunk for a text value. The type of the chunk it replaces is kept\n * where the value allows it — a `zTXt` stays compressed, an `iTXt` keeps its\n * compression and language — and Latin-1 text becomes `tEXt` otherwise.\n * Text outside Latin-1 has to be `iTXt` (11.3.3.3 vs 11.3.3.5).\n */\nfunction textChunk(\n keyword: string,\n text: string,\n previous: DecodedText | null,\n): Uint8Array {\n const latin1 = latin1Encode(text)\n const key = latin1Encode(keyword) ?? new Uint8Array(0)\n const type = previous?.chunk.type\n if (type === 'iTXt' || !latin1) {\n return iTXtChunk(keyword, text, {\n compressed: previous?.compressed ?? false,\n language: previous?.chunk.language,\n translatedKeyword: previous?.chunk.translatedKeyword,\n })\n }\n if (type === 'zTXt') {\n return buildChunk(\n 'zTXt',\n concatBytes([\n key,\n Uint8Array.from([0, 0]),\n new Uint8Array(deflateSync(latin1)),\n ]),\n )\n }\n return buildChunk('tEXt', concatBytes([key, Uint8Array.from([0]), latin1]))\n}\n\n/**\n * A keyword the PNG spec accepts (11.3.3.2): 1 to 79 printable Latin-1\n * characters, no leading, trailing or consecutive spaces.\n */\nfunction isValidKeyword(keyword: string): boolean {\n if (keyword.length < 1 || keyword.length > 79) return false\n if (/^ | $| {2}/.test(keyword)) return false\n for (let i = 0; i < keyword.length; i += 1) {\n const c = keyword.charCodeAt(i)\n if (!((c >= 0x20 && c <= 0x7e) || (c >= 0xa1 && c <= 0xff))) return false\n }\n return true\n}\n\ntype PieceKind = 'exif' | 'xmp' | 'legacyExif' | 'legacyXmp' | 'text' | 'other'\n\ninterface Piece {\n bytes: Uint8Array\n kind: PieceKind\n type: string\n /** Text chunks: the decoded chunk, for its keyword, type and language. */\n text?: DecodedText\n}\n\n/**\n * Writes an update into a PNG and returns the new file.\n *\n * - `exif` replaces the `eXIf` chunk (moving it before IDAT if an old file\n * had it after), or inserts one immediately before the first IDAT.\n * - `xmp` replaces the XMP text chunk in place as an uncompressed `iTXt`,\n * or inserts one before the first IDAT.\n * - `pngText` sets or removes text chunks by keyword. A set replaces the\n * first chunk with that keyword and removes the rest; a new keyword is\n * inserted before the first IDAT.\n * - Replacing or removing EXIF or XMP also removes the legacy raw-profile\n * copy of it.\n *\n * Every chunk it does not rewrite, and every byte after IEND, is copied\n * verbatim. Throws `EmbeddedWriteError` for a file that is not a PNG or that\n * ends before IEND, for an IRB (PNG has no place for one), for a keyword the\n * PNG spec does not allow, and for text containing NUL.\n */\nexport function writePngBlocks(\n bytes: Uint8Array,\n update: ImageBlockUpdate,\n): Uint8Array {\n const layout = walkPng(bytes)\n if (!layout) throw new EmbeddedWriteError('This file is not a PNG image.')\n if (!layout.complete) throw new EmbeddedWriteError(FILE_DAMAGED)\n if (update.irb?.length) {\n throw new EmbeddedWriteError(\n 'A PNG has no place for Photoshop or IPTC metadata.',\n )\n }\n\n const pieces: Piece[] = layout.chunks.map((chunk) => {\n const piece: Piece = {\n bytes: bytes.subarray(chunk.start, chunk.end),\n kind: 'other',\n type: chunk.type,\n }\n if (chunk.type === 'eXIf') piece.kind = 'exif'\n if (\n chunk.type === 'tEXt' ||\n chunk.type === 'zTXt' ||\n chunk.type === 'iTXt'\n ) {\n const data = bytes.subarray(chunk.dataStart, chunk.dataEnd)\n const text = decodeText(chunk.type, data)\n const keyword = text?.chunk.keyword ?? readKeyword(data)?.keyword\n if (keyword === XMP_KEYWORD) {\n piece.kind = 'xmp'\n } else if (keyword && RAW_PROFILE.test(keyword)) {\n const legacy = text && legacyProfile(keyword, text.chunk.text)\n if (legacy)\n piece.kind = legacy.kind === 'exif' ? 'legacyExif' : 'legacyXmp'\n } else if (text) {\n piece.kind = 'text'\n piece.text = text\n }\n }\n return piece\n })\n\n const remove = (predicate: (piece: Piece) => boolean) => {\n for (let i = pieces.length - 1; i >= 0; i -= 1) {\n const piece = pieces[i]\n if (piece && predicate(piece)) pieces.splice(i, 1)\n }\n }\n /** Before the first IDAT — or the APNG `fcTL` that introduces it. */\n const imageStart = () => {\n const index = pieces.findIndex(\n (p) => p.type === 'IDAT' || p.type === 'fcTL',\n )\n return index >= 0 ? index : pieces.findIndex((p) => p.type === 'IEND')\n }\n const inserted: Piece[] = []\n\n if (update.exif !== undefined) {\n const tiff =\n update.exif && update.exif.length ? bareTiff(update.exif) : null\n const at = pieces.findIndex((p) => p.kind === 'exif')\n const inPlace = tiff && at >= 0 && at < imageStart()\n remove((p) => p.kind === 'legacyExif')\n if (inPlace) {\n const first = pieces.findIndex((p) => p.kind === 'exif')\n const piece = pieces[first]\n if (piece) piece.bytes = buildChunk('eXIf', tiff)\n remove((p) => p.kind === 'exif' && p !== piece)\n } else {\n remove((p) => p.kind === 'exif')\n if (tiff)\n inserted.push({\n bytes: buildChunk('eXIf', tiff),\n kind: 'exif',\n type: 'eXIf',\n })\n }\n }\n\n if (update.xmp !== undefined) {\n remove((p) => p.kind === 'legacyXmp')\n const chunk = update.xmp ? iTXtChunk(XMP_KEYWORD, update.xmp) : null\n const first = pieces.find((p) => p.kind === 'xmp')\n remove((p) => p.kind === 'xmp' && (p !== first || !chunk))\n if (chunk && first) first.bytes = chunk\n else if (chunk) inserted.push({ bytes: chunk, kind: 'xmp', type: 'iTXt' })\n }\n\n for (const [keyword, value] of Object.entries(update.pngText ?? {})) {\n if (value === undefined) continue\n const first = pieces.find(\n (p) => p.kind === 'text' && p.text?.chunk.keyword === keyword,\n )\n // A keyword the file already carries round-trips as it is, even one an\n // older writer spelled against the rules; a NEW keyword must follow\n // them (11.3.3.2).\n if (\n (!first && !isValidKeyword(keyword)) ||\n latin1Encode(keyword) === null ||\n keyword === XMP_KEYWORD ||\n RAW_PROFILE.test(keyword)\n ) {\n throw new EmbeddedWriteError(\n `\"${keyword}\" cannot be used as a PNG text keyword.`,\n )\n }\n if (value !== null && value.includes('\\0')) {\n throw new EmbeddedWriteError('PNG text cannot contain a NUL character.')\n }\n remove(\n (p) =>\n p.kind === 'text' &&\n p.text?.chunk.keyword === keyword &&\n (p !== first || value === null),\n )\n if (value === null) continue\n const chunk = textChunk(keyword, value, first?.text ?? null)\n if (first) {\n first.bytes = chunk\n } else {\n inserted.push({ bytes: chunk, kind: 'text', type: 'tEXt' })\n }\n }\n\n const at = imageStart()\n pieces.splice(at >= 0 ? at : pieces.length, 0, ...inserted)\n\n return concatBytes([\n SIGNATURE,\n ...pieces.map((piece) => piece.bytes),\n bytes.subarray(layout.end),\n ])\n}\n"],"names":["deflateSync","inflateSync","EmbeddedWriteError","FILE_DAMAGED","METADATA_TOO_LARGE","asciiBytes","bytesAt","concatBytes","crc32","latin1Decode","latin1Encode","readAscii","readU32BE","trimNul","utf8Decode","utf8Encode","writeU32BE","SIGNATURE","Uint8Array","from","XMP_KEYWORD","RAW_PROFILE","EXIF_HEADER","XMP_APP1_ID","MAX_CHUNK_LENGTH","MAX_CHUNKS","MAX_TEXT_CHUNKS","MAX_INFLATED","isChunkType","bytes","offset","i","c","walkPng","chunks","length","end","type","push","start","dataStart","dataEnd","complete","inflate","data","maxOutputLength","readKeyword","nul","subarray","indexOf","keyword","rest","decodeText","head","chunk","text","compressed","flag","method","langEnd","translatedEnd","body","language","translatedKeyword","decodeRawProfile","match","exec","Number","out","nibble","written","charCodeAt","value","legacyProfile","name","toLowerCase","payload","kind","bareTiff","exif","readPngBlocks","layout","blocks","pngText","legacyExif","legacyXmp","decoded","slice","icc","xmp","undefined","test","legacy","buildChunk","typeBytes","set","iTXtChunk","options","textChunk","previous","latin1","key","isValidKeyword","writePngBlocks","update","irb","pieces","map","piece","remove","predicate","splice","imageStart","index","findIndex","p","inserted","tiff","at","inPlace","first","find","Object","entries","includes"],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
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
|
+
import type { EmbeddedCandidate } from './types';
|
|
18
|
+
/** Past this, the file is not read at all. */
|
|
19
|
+
export declare const SVG_READ_MAX_BYTES: number;
|
|
20
|
+
/**
|
|
21
|
+
* Read an SVG's title, description and embedded XMP.
|
|
22
|
+
*
|
|
23
|
+
* Null when the bytes are over {@link SVG_READ_MAX_BYTES}, are not a
|
|
24
|
+
* well-formed XML document whose root is `<svg>`, or carry a DOCTYPE
|
|
25
|
+
* internal subset or an entity declaration.
|
|
26
|
+
*/
|
|
27
|
+
export declare function readSvg(bytes: Uint8Array): {
|
|
28
|
+
candidates: EmbeddedCandidate[];
|
|
29
|
+
xmp: string | null;
|
|
30
|
+
} | null;
|