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