cross-image 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +606 -0
  3. package/esm/mod.d.ts +33 -0
  4. package/esm/mod.d.ts.map +1 -0
  5. package/esm/mod.js +31 -0
  6. package/esm/package.json +3 -0
  7. package/esm/src/formats/ascii.d.ts +27 -0
  8. package/esm/src/formats/ascii.d.ts.map +1 -0
  9. package/esm/src/formats/ascii.js +172 -0
  10. package/esm/src/formats/bmp.d.ts +19 -0
  11. package/esm/src/formats/bmp.d.ts.map +1 -0
  12. package/esm/src/formats/bmp.js +174 -0
  13. package/esm/src/formats/gif.d.ts +40 -0
  14. package/esm/src/formats/gif.d.ts.map +1 -0
  15. package/esm/src/formats/gif.js +385 -0
  16. package/esm/src/formats/jpeg.d.ts +18 -0
  17. package/esm/src/formats/jpeg.d.ts.map +1 -0
  18. package/esm/src/formats/jpeg.js +414 -0
  19. package/esm/src/formats/png.d.ts +33 -0
  20. package/esm/src/formats/png.d.ts.map +1 -0
  21. package/esm/src/formats/png.js +544 -0
  22. package/esm/src/formats/raw.d.ts +23 -0
  23. package/esm/src/formats/raw.d.ts.map +1 -0
  24. package/esm/src/formats/raw.js +98 -0
  25. package/esm/src/formats/tiff.d.ts +58 -0
  26. package/esm/src/formats/tiff.d.ts.map +1 -0
  27. package/esm/src/formats/tiff.js +791 -0
  28. package/esm/src/formats/webp.d.ts +22 -0
  29. package/esm/src/formats/webp.d.ts.map +1 -0
  30. package/esm/src/formats/webp.js +403 -0
  31. package/esm/src/image.d.ts +124 -0
  32. package/esm/src/image.d.ts.map +1 -0
  33. package/esm/src/image.js +320 -0
  34. package/esm/src/types.d.ts +167 -0
  35. package/esm/src/types.d.ts.map +1 -0
  36. package/esm/src/types.js +1 -0
  37. package/esm/src/utils/gif_decoder.d.ts +42 -0
  38. package/esm/src/utils/gif_decoder.d.ts.map +1 -0
  39. package/esm/src/utils/gif_decoder.js +374 -0
  40. package/esm/src/utils/gif_encoder.d.ts +29 -0
  41. package/esm/src/utils/gif_encoder.d.ts.map +1 -0
  42. package/esm/src/utils/gif_encoder.js +226 -0
  43. package/esm/src/utils/jpeg_decoder.d.ts +39 -0
  44. package/esm/src/utils/jpeg_decoder.d.ts.map +1 -0
  45. package/esm/src/utils/jpeg_decoder.js +580 -0
  46. package/esm/src/utils/jpeg_encoder.d.ts +33 -0
  47. package/esm/src/utils/jpeg_encoder.d.ts.map +1 -0
  48. package/esm/src/utils/jpeg_encoder.js +1017 -0
  49. package/esm/src/utils/lzw.d.ts +43 -0
  50. package/esm/src/utils/lzw.d.ts.map +1 -0
  51. package/esm/src/utils/lzw.js +309 -0
  52. package/esm/src/utils/resize.d.ts +9 -0
  53. package/esm/src/utils/resize.d.ts.map +1 -0
  54. package/esm/src/utils/resize.js +52 -0
  55. package/esm/src/utils/tiff_lzw.d.ts +44 -0
  56. package/esm/src/utils/tiff_lzw.d.ts.map +1 -0
  57. package/esm/src/utils/tiff_lzw.js +306 -0
  58. package/esm/src/utils/webp_decoder.d.ts +39 -0
  59. package/esm/src/utils/webp_decoder.d.ts.map +1 -0
  60. package/esm/src/utils/webp_decoder.js +493 -0
  61. package/esm/src/utils/webp_encoder.d.ts +72 -0
  62. package/esm/src/utils/webp_encoder.d.ts.map +1 -0
  63. package/esm/src/utils/webp_encoder.js +627 -0
  64. package/package.json +41 -0
  65. package/script/mod.d.ts +33 -0
  66. package/script/mod.d.ts.map +1 -0
  67. package/script/mod.js +43 -0
  68. package/script/package.json +3 -0
  69. package/script/src/formats/ascii.d.ts +27 -0
  70. package/script/src/formats/ascii.d.ts.map +1 -0
  71. package/script/src/formats/ascii.js +176 -0
  72. package/script/src/formats/bmp.d.ts +19 -0
  73. package/script/src/formats/bmp.d.ts.map +1 -0
  74. package/script/src/formats/bmp.js +178 -0
  75. package/script/src/formats/gif.d.ts +40 -0
  76. package/script/src/formats/gif.d.ts.map +1 -0
  77. package/script/src/formats/gif.js +389 -0
  78. package/script/src/formats/jpeg.d.ts +18 -0
  79. package/script/src/formats/jpeg.d.ts.map +1 -0
  80. package/script/src/formats/jpeg.js +451 -0
  81. package/script/src/formats/png.d.ts +33 -0
  82. package/script/src/formats/png.d.ts.map +1 -0
  83. package/script/src/formats/png.js +548 -0
  84. package/script/src/formats/raw.d.ts +23 -0
  85. package/script/src/formats/raw.d.ts.map +1 -0
  86. package/script/src/formats/raw.js +102 -0
  87. package/script/src/formats/tiff.d.ts +58 -0
  88. package/script/src/formats/tiff.d.ts.map +1 -0
  89. package/script/src/formats/tiff.js +795 -0
  90. package/script/src/formats/webp.d.ts +22 -0
  91. package/script/src/formats/webp.d.ts.map +1 -0
  92. package/script/src/formats/webp.js +440 -0
  93. package/script/src/image.d.ts +124 -0
  94. package/script/src/image.d.ts.map +1 -0
  95. package/script/src/image.js +324 -0
  96. package/script/src/types.d.ts +167 -0
  97. package/script/src/types.d.ts.map +1 -0
  98. package/script/src/types.js +2 -0
  99. package/script/src/utils/gif_decoder.d.ts +42 -0
  100. package/script/src/utils/gif_decoder.d.ts.map +1 -0
  101. package/script/src/utils/gif_decoder.js +378 -0
  102. package/script/src/utils/gif_encoder.d.ts +29 -0
  103. package/script/src/utils/gif_encoder.d.ts.map +1 -0
  104. package/script/src/utils/gif_encoder.js +230 -0
  105. package/script/src/utils/jpeg_decoder.d.ts +39 -0
  106. package/script/src/utils/jpeg_decoder.d.ts.map +1 -0
  107. package/script/src/utils/jpeg_decoder.js +584 -0
  108. package/script/src/utils/jpeg_encoder.d.ts +33 -0
  109. package/script/src/utils/jpeg_encoder.d.ts.map +1 -0
  110. package/script/src/utils/jpeg_encoder.js +1021 -0
  111. package/script/src/utils/lzw.d.ts +43 -0
  112. package/script/src/utils/lzw.d.ts.map +1 -0
  113. package/script/src/utils/lzw.js +314 -0
  114. package/script/src/utils/resize.d.ts +9 -0
  115. package/script/src/utils/resize.d.ts.map +1 -0
  116. package/script/src/utils/resize.js +56 -0
  117. package/script/src/utils/tiff_lzw.d.ts +44 -0
  118. package/script/src/utils/tiff_lzw.d.ts.map +1 -0
  119. package/script/src/utils/tiff_lzw.js +311 -0
  120. package/script/src/utils/webp_decoder.d.ts +39 -0
  121. package/script/src/utils/webp_decoder.d.ts.map +1 -0
  122. package/script/src/utils/webp_decoder.js +497 -0
  123. package/script/src/utils/webp_encoder.d.ts +72 -0
  124. package/script/src/utils/webp_encoder.d.ts.map +1 -0
  125. package/script/src/utils/webp_encoder.js +631 -0
@@ -0,0 +1,385 @@
1
+ import { GIFDecoder } from "../utils/gif_decoder.js";
2
+ import { GIFEncoder } from "../utils/gif_encoder.js";
3
+ /**
4
+ * GIF format handler
5
+ * Now includes pure-JS implementation with custom LZW compression/decompression
6
+ *
7
+ * Features:
8
+ * - LZW compression/decompression
9
+ * - Color quantization and palette generation for encoding
10
+ * - Interlacing support
11
+ * - Transparency support
12
+ * - Multi-frame animation support (decoding and encoding)
13
+ * - Falls back to runtime APIs when pure-JS fails
14
+ */
15
+ export class GIFFormat {
16
+ constructor() {
17
+ Object.defineProperty(this, "name", {
18
+ enumerable: true,
19
+ configurable: true,
20
+ writable: true,
21
+ value: "gif"
22
+ });
23
+ Object.defineProperty(this, "mimeType", {
24
+ enumerable: true,
25
+ configurable: true,
26
+ writable: true,
27
+ value: "image/gif"
28
+ });
29
+ }
30
+ supportsMultipleFrames() {
31
+ return true;
32
+ }
33
+ canDecode(data) {
34
+ // GIF signature: "GIF87a" or "GIF89a"
35
+ return data.length >= 6 &&
36
+ data[0] === 0x47 && data[1] === 0x49 && data[2] === 0x46 && // "GIF"
37
+ data[3] === 0x38 && // "8"
38
+ (data[4] === 0x37 || data[4] === 0x39) && // "7" or "9"
39
+ data[5] === 0x61; // "a"
40
+ }
41
+ async decode(data) {
42
+ if (!this.canDecode(data)) {
43
+ throw new Error("Invalid GIF signature");
44
+ }
45
+ // Try pure-JS decoder first
46
+ try {
47
+ const decoder = new GIFDecoder(data);
48
+ const result = decoder.decode();
49
+ // Extract metadata from comment extensions
50
+ const metadata = this.extractMetadata(data);
51
+ return {
52
+ width: result.width,
53
+ height: result.height,
54
+ data: result.data,
55
+ metadata: Object.keys(metadata).length > 0 ? metadata : undefined,
56
+ };
57
+ }
58
+ catch (error) {
59
+ // Fall back to runtime decoder if pure-JS fails
60
+ console.warn("Pure-JS GIF decoder failed, falling back to runtime:", error);
61
+ let pos = 6; // Skip "GIF89a" or "GIF87a"
62
+ const width = this.readUint16LE(data, pos);
63
+ pos += 2;
64
+ const height = this.readUint16LE(data, pos);
65
+ const rgba = await this.decodeUsingRuntime(data, width, height);
66
+ const metadata = this.extractMetadata(data);
67
+ return {
68
+ width,
69
+ height,
70
+ data: rgba,
71
+ metadata: Object.keys(metadata).length > 0 ? metadata : undefined,
72
+ };
73
+ }
74
+ }
75
+ extractMetadata(data) {
76
+ const metadata = {};
77
+ let pos = 6; // Skip "GIF89a" or "GIF87a"
78
+ // Read logical screen descriptor
79
+ pos += 4; // Skip width and height
80
+ const packed = data[pos++];
81
+ const hasGlobalColorTable = (packed & 0x80) !== 0;
82
+ const globalColorTableSize = 2 << (packed & 0x07);
83
+ pos++; // background color
84
+ pos++; // aspect ratio
85
+ // Skip global color table if present
86
+ if (hasGlobalColorTable) {
87
+ pos += globalColorTableSize * 3;
88
+ }
89
+ // Parse extensions
90
+ while (pos < data.length) {
91
+ if (data[pos] === 0x21) { // Extension
92
+ const label = data[pos + 1];
93
+ pos += 2;
94
+ if (label === 0xfe) { // Comment Extension
95
+ const comment = this.readDataSubBlocks(data, pos);
96
+ if (comment.text) {
97
+ this.parseComment(comment.text, metadata);
98
+ }
99
+ pos = comment.endPos;
100
+ }
101
+ else if (label === 0xff) { // Application Extension
102
+ const appData = this.readDataSubBlocks(data, pos);
103
+ if (appData.text && appData.text.startsWith("XMP DataXMP") ||
104
+ appData.text.includes("<?xpacket")) {
105
+ this.parseXMP(appData.text, metadata);
106
+ }
107
+ pos = appData.endPos;
108
+ }
109
+ else {
110
+ // Skip other extensions
111
+ while (pos < data.length && data[pos] !== 0) {
112
+ const blockSize = data[pos++];
113
+ pos += blockSize;
114
+ }
115
+ pos++; // Skip block terminator
116
+ }
117
+ }
118
+ else if (data[pos] === 0x3b) { // Trailer
119
+ break;
120
+ }
121
+ else {
122
+ pos++;
123
+ }
124
+ }
125
+ return metadata;
126
+ }
127
+ async encode(imageData) {
128
+ const { width, height, data, metadata } = imageData;
129
+ // Try pure-JS encoder first
130
+ try {
131
+ const encoder = new GIFEncoder(width, height, data);
132
+ const encoded = encoder.encode();
133
+ // Inject metadata if present
134
+ if (metadata && Object.keys(metadata).length > 0) {
135
+ const injected = this.injectMetadata(encoded, metadata);
136
+ return injected;
137
+ }
138
+ return encoded;
139
+ }
140
+ catch (error) {
141
+ // Fall back to runtime encoding if pure-JS fails
142
+ console.warn("Pure-JS GIF encoder failed, falling back to runtime:", error);
143
+ if (typeof OffscreenCanvas !== "undefined") {
144
+ try {
145
+ const canvas = new OffscreenCanvas(width, height);
146
+ const ctx = canvas.getContext("2d");
147
+ if (!ctx)
148
+ throw new Error("Could not get canvas context");
149
+ const imgData = ctx.createImageData(width, height);
150
+ const imgDataData = new Uint8ClampedArray(data);
151
+ imgData.data.set(imgDataData);
152
+ ctx.putImageData(imgData, 0, 0);
153
+ const blob = await canvas.convertToBlob({
154
+ type: "image/gif",
155
+ });
156
+ const arrayBuffer = await blob.arrayBuffer();
157
+ const encoded = new Uint8Array(arrayBuffer);
158
+ // Inject metadata if present
159
+ if (metadata && Object.keys(metadata).length > 0) {
160
+ const injected = this.injectMetadata(encoded, metadata);
161
+ return injected;
162
+ }
163
+ return encoded;
164
+ }
165
+ catch (runtimeError) {
166
+ throw new Error(`GIF encoding failed: ${runtimeError}`);
167
+ }
168
+ }
169
+ throw new Error("GIF encoding requires pure-JS support or OffscreenCanvas API");
170
+ }
171
+ }
172
+ /**
173
+ * Decode all frames from an animated GIF
174
+ */
175
+ decodeFrames(data) {
176
+ if (!this.canDecode(data)) {
177
+ throw new Error("Invalid GIF signature");
178
+ }
179
+ try {
180
+ const decoder = new GIFDecoder(data);
181
+ const result = decoder.decodeAllFrames();
182
+ // Extract metadata from comment extensions
183
+ const metadata = this.extractMetadata(data);
184
+ return Promise.resolve({
185
+ width: result.width,
186
+ height: result.height,
187
+ frames: result.frames.map((frame) => ({
188
+ width: frame.width,
189
+ height: frame.height,
190
+ data: frame.data,
191
+ frameMetadata: {
192
+ left: frame.left,
193
+ top: frame.top,
194
+ // Convert GIF delay from centiseconds (1/100s) to milliseconds
195
+ delay: frame.delay * 10,
196
+ disposal: this.mapDisposalMethod(frame.disposal),
197
+ },
198
+ })),
199
+ metadata: Object.keys(metadata).length > 0 ? metadata : undefined,
200
+ });
201
+ }
202
+ catch (error) {
203
+ throw new Error(`GIF multi-frame decoding failed: ${error}`);
204
+ }
205
+ }
206
+ /**
207
+ * Encode multi-frame image data to animated GIF
208
+ * Note: Currently not implemented, will encode only first frame
209
+ */
210
+ encodeFrames(imageData, _options) {
211
+ // For now, just encode the first frame using the existing encoder
212
+ // Full multi-frame encoding would require a more complex GIFEncoder
213
+ if (imageData.frames.length === 0) {
214
+ throw new Error("No frames to encode");
215
+ }
216
+ const firstFrame = imageData.frames[0];
217
+ const singleFrameData = {
218
+ width: firstFrame.width,
219
+ height: firstFrame.height,
220
+ data: firstFrame.data,
221
+ metadata: imageData.metadata,
222
+ };
223
+ return this.encode(singleFrameData);
224
+ }
225
+ mapDisposalMethod(disposal) {
226
+ switch (disposal) {
227
+ case 0:
228
+ case 1:
229
+ return "none";
230
+ case 2:
231
+ return "background";
232
+ case 3:
233
+ return "previous";
234
+ default:
235
+ return "none";
236
+ }
237
+ }
238
+ readUint16LE(data, offset) {
239
+ return data[offset] | (data[offset + 1] << 8);
240
+ }
241
+ async decodeUsingRuntime(data, _width, _height) {
242
+ // Try to use ImageDecoder API if available (Deno, modern browsers)
243
+ if (typeof ImageDecoder !== "undefined") {
244
+ try {
245
+ const decoder = new ImageDecoder({ data, type: "image/gif" });
246
+ const result = await decoder.decode();
247
+ const bitmap = result.image;
248
+ // Create a canvas to extract pixel data
249
+ const canvas = new OffscreenCanvas(bitmap.displayWidth, bitmap.displayHeight);
250
+ const ctx = canvas.getContext("2d");
251
+ if (!ctx)
252
+ throw new Error("Could not get canvas context");
253
+ ctx.drawImage(bitmap, 0, 0);
254
+ const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
255
+ bitmap.close();
256
+ return new Uint8Array(imageData.data.buffer);
257
+ }
258
+ catch (error) {
259
+ throw new Error(`GIF decoding failed: ${error}`);
260
+ }
261
+ }
262
+ throw new Error("GIF decoding requires ImageDecoder API or equivalent runtime support");
263
+ }
264
+ // Metadata parsing and injection methods
265
+ readDataSubBlocks(data, pos) {
266
+ const blocks = [];
267
+ while (pos < data.length) {
268
+ const blockSize = data[pos++];
269
+ if (blockSize === 0)
270
+ break;
271
+ for (let i = 0; i < blockSize && pos < data.length; i++) {
272
+ blocks.push(data[pos++]);
273
+ }
274
+ }
275
+ return {
276
+ text: new TextDecoder().decode(new Uint8Array(blocks)),
277
+ endPos: pos,
278
+ };
279
+ }
280
+ parseComment(comment, metadata) {
281
+ // Try to parse structured comments like "Title: xxx" or JSON
282
+ const lines = comment.split("\n");
283
+ for (const line of lines) {
284
+ const colonIdx = line.indexOf(":");
285
+ if (colonIdx > 0) {
286
+ const key = line.slice(0, colonIdx).trim().toLowerCase();
287
+ const value = line.slice(colonIdx + 1).trim();
288
+ switch (key) {
289
+ case "title":
290
+ metadata.title = value;
291
+ break;
292
+ case "description":
293
+ metadata.description = value;
294
+ break;
295
+ case "author":
296
+ case "artist":
297
+ metadata.author = value;
298
+ break;
299
+ case "copyright":
300
+ metadata.copyright = value;
301
+ break;
302
+ }
303
+ }
304
+ }
305
+ // If no structured data, use entire comment as description
306
+ if (!metadata.title && !metadata.description && !metadata.author &&
307
+ !metadata.copyright) {
308
+ metadata.description = comment.trim();
309
+ }
310
+ }
311
+ parseXMP(xmpStr, metadata) {
312
+ // Simple XMP parsing for common fields
313
+ try {
314
+ const titleMatch = xmpStr.match(/<dc:title[^>]*>([^<]+)<\/dc:title>/);
315
+ if (titleMatch)
316
+ metadata.title = titleMatch[1].trim();
317
+ const descMatch = xmpStr.match(/<dc:description[^>]*>([^<]+)<\/dc:description>/);
318
+ if (descMatch)
319
+ metadata.description = descMatch[1].trim();
320
+ const creatorMatch = xmpStr.match(/<dc:creator[^>]*>([^<]+)<\/dc:creator>/);
321
+ if (creatorMatch)
322
+ metadata.author = creatorMatch[1].trim();
323
+ const rightsMatch = xmpStr.match(/<dc:rights[^>]*>([^<]+)<\/dc:rights>/);
324
+ if (rightsMatch)
325
+ metadata.copyright = rightsMatch[1].trim();
326
+ }
327
+ catch (_e) {
328
+ // Ignore XMP parsing errors
329
+ }
330
+ }
331
+ injectMetadata(gifData, metadata) {
332
+ // GIF structure: Header + Logical Screen Descriptor + [Global Color Table] + Data + Trailer
333
+ // We'll inject a Comment Extension after the Logical Screen Descriptor
334
+ const result = [];
335
+ // Copy header and logical screen descriptor
336
+ let pos = 0;
337
+ for (let i = 0; i < 13; i++) {
338
+ result.push(gifData[pos++]);
339
+ }
340
+ // Check if there's a global color table
341
+ const packed = gifData[10];
342
+ const hasGlobalColorTable = (packed & 0x80) !== 0;
343
+ const globalColorTableSize = 2 << (packed & 0x07);
344
+ // Copy global color table if present
345
+ if (hasGlobalColorTable) {
346
+ for (let i = 0; i < globalColorTableSize * 3; i++) {
347
+ result.push(gifData[pos++]);
348
+ }
349
+ }
350
+ // Inject Comment Extension with metadata
351
+ const commentText = this.createCommentText(metadata);
352
+ if (commentText) {
353
+ result.push(0x21); // Extension Introducer
354
+ result.push(0xfe); // Comment Label
355
+ // Write comment in sub-blocks (max 255 bytes per block)
356
+ const commentBytes = new TextEncoder().encode(commentText);
357
+ for (let i = 0; i < commentBytes.length; i += 255) {
358
+ const blockSize = Math.min(255, commentBytes.length - i);
359
+ result.push(blockSize);
360
+ for (let j = 0; j < blockSize; j++) {
361
+ result.push(commentBytes[i + j]);
362
+ }
363
+ }
364
+ result.push(0); // Block Terminator
365
+ }
366
+ // Copy rest of the GIF data
367
+ while (pos < gifData.length) {
368
+ result.push(gifData[pos++]);
369
+ }
370
+ return new Uint8Array(result);
371
+ }
372
+ createCommentText(metadata) {
373
+ const parts = [];
374
+ if (metadata.title)
375
+ parts.push(`Title: ${metadata.title}`);
376
+ if (metadata.description) {
377
+ parts.push(`Description: ${metadata.description}`);
378
+ }
379
+ if (metadata.author)
380
+ parts.push(`Author: ${metadata.author}`);
381
+ if (metadata.copyright)
382
+ parts.push(`Copyright: ${metadata.copyright}`);
383
+ return parts.length > 0 ? parts.join("\n") : null;
384
+ }
385
+ }
@@ -0,0 +1,18 @@
1
+ import type { ImageData, ImageFormat } from "../types.js";
2
+ /**
3
+ * JPEG format handler
4
+ * Implements a basic JPEG decoder and encoder
5
+ */
6
+ export declare class JPEGFormat implements ImageFormat {
7
+ readonly name = "jpeg";
8
+ readonly mimeType = "image/jpeg";
9
+ canDecode(data: Uint8Array): boolean;
10
+ decode(data: Uint8Array): Promise<ImageData>;
11
+ encode(imageData: ImageData): Promise<Uint8Array>;
12
+ private injectMetadata;
13
+ private decodeUsingRuntime;
14
+ private parseJFIF;
15
+ private parseEXIF;
16
+ private createEXIFData;
17
+ }
18
+ //# sourceMappingURL=jpeg.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jpeg.d.ts","sourceRoot":"","sources":["../../../src/src/formats/jpeg.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAiB,MAAM,aAAa,CAAC;AAKzE;;;GAGG;AACH,qBAAa,UAAW,YAAW,WAAW;IAC5C,QAAQ,CAAC,IAAI,UAAU;IACvB,QAAQ,CAAC,QAAQ,gBAAgB;IAEjC,SAAS,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO;IAM9B,MAAM,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC;IA2E5C,MAAM,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC;IAgDvD,OAAO,CAAC,cAAc;YA+CR,kBAAkB;IAgDhC,OAAO,CAAC,SAAS;IAkCjB,OAAO,CAAC,SAAS;IAyIjB,OAAO,CAAC,cAAc;CAgHvB"}