@opengeni/artifact-tool 0.2.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 (203) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +104 -0
  3. package/dist/bounded-zip.d.ts +28 -0
  4. package/dist/chunk-4BSENMAE.js +39 -0
  5. package/dist/chunk-4BSENMAE.js.map +1 -0
  6. package/dist/chunk-4FRFNNBM.js +276 -0
  7. package/dist/chunk-4FRFNNBM.js.map +1 -0
  8. package/dist/chunk-5XIA6KHZ.js +49 -0
  9. package/dist/chunk-5XIA6KHZ.js.map +1 -0
  10. package/dist/chunk-66CT2ZRP.js +2229 -0
  11. package/dist/chunk-66CT2ZRP.js.map +1 -0
  12. package/dist/chunk-72SYDU4O.js +319 -0
  13. package/dist/chunk-72SYDU4O.js.map +1 -0
  14. package/dist/chunk-B2ZJRZDA.js +279 -0
  15. package/dist/chunk-B2ZJRZDA.js.map +1 -0
  16. package/dist/chunk-DCTLTNPW.js +2458 -0
  17. package/dist/chunk-DCTLTNPW.js.map +1 -0
  18. package/dist/chunk-FGZM4FQ6.js +473 -0
  19. package/dist/chunk-FGZM4FQ6.js.map +1 -0
  20. package/dist/chunk-HTJBLBDE.js +591 -0
  21. package/dist/chunk-HTJBLBDE.js.map +1 -0
  22. package/dist/chunk-IYFXZQ34.js +5306 -0
  23. package/dist/chunk-IYFXZQ34.js.map +1 -0
  24. package/dist/chunk-J6N7IR44.js +172 -0
  25. package/dist/chunk-J6N7IR44.js.map +1 -0
  26. package/dist/chunk-N2IHNWBS.js +1002 -0
  27. package/dist/chunk-N2IHNWBS.js.map +1 -0
  28. package/dist/chunk-NAG44K67.js +549 -0
  29. package/dist/chunk-NAG44K67.js.map +1 -0
  30. package/dist/chunk-O24KTEGP.js +33 -0
  31. package/dist/chunk-O24KTEGP.js.map +1 -0
  32. package/dist/chunk-PI2AZVGK.js +2074 -0
  33. package/dist/chunk-PI2AZVGK.js.map +1 -0
  34. package/dist/chunk-Q2FFHEY7.js +40 -0
  35. package/dist/chunk-Q2FFHEY7.js.map +1 -0
  36. package/dist/chunk-RBGQADXA.js +57 -0
  37. package/dist/chunk-RBGQADXA.js.map +1 -0
  38. package/dist/chunk-U5G2BKM2.js +613 -0
  39. package/dist/chunk-U5G2BKM2.js.map +1 -0
  40. package/dist/chunk-VOJHMSM3.js +39 -0
  41. package/dist/chunk-VOJHMSM3.js.map +1 -0
  42. package/dist/chunk-VUNV25KB.js +16 -0
  43. package/dist/chunk-VUNV25KB.js.map +1 -0
  44. package/dist/chunk-XIFUNVBO.js +101 -0
  45. package/dist/chunk-XIFUNVBO.js.map +1 -0
  46. package/dist/chunk-ZCJYRJGB.js +489 -0
  47. package/dist/chunk-ZCJYRJGB.js.map +1 -0
  48. package/dist/chunk-ZZXPEE4S.js +2260 -0
  49. package/dist/chunk-ZZXPEE4S.js.map +1 -0
  50. package/dist/document-docx-api.d.ts +49 -0
  51. package/dist/document-docx-codec.d.ts +9 -0
  52. package/dist/document-docx-codec.js +4765 -0
  53. package/dist/document-docx-codec.js.map +1 -0
  54. package/dist/document-docx-import.d.ts +261 -0
  55. package/dist/document-docx-packer.d.ts +18 -0
  56. package/dist/document-docx-state.d.ts +12 -0
  57. package/dist/document-render.d.ts +3 -0
  58. package/dist/document-render.js +492 -0
  59. package/dist/document-render.js.map +1 -0
  60. package/dist/document.d.ts +432 -0
  61. package/dist/document.js +37 -0
  62. package/dist/document.js.map +1 -0
  63. package/dist/errors.d.ts +23 -0
  64. package/dist/file-blob.d.ts +11 -0
  65. package/dist/index.d.ts +4 -0
  66. package/dist/index.js +186 -0
  67. package/dist/index.js.map +1 -0
  68. package/dist/inspection.d.ts +12 -0
  69. package/dist/kernel.d.ts +101 -0
  70. package/dist/lib-GQLPACFK.js +9746 -0
  71. package/dist/lib-GQLPACFK.js.map +1 -0
  72. package/dist/materializer-cli-entry.d.ts +2 -0
  73. package/dist/materializer-cli-entry.js +29 -0
  74. package/dist/materializer-cli-entry.js.map +1 -0
  75. package/dist/materializer-cli.d.ts +24 -0
  76. package/dist/materializer-cli.js +24 -0
  77. package/dist/materializer-cli.js.map +1 -0
  78. package/dist/native-raster.d.ts +10 -0
  79. package/dist/native.d.ts +129 -0
  80. package/dist/native.js +31 -0
  81. package/dist/native.js.map +1 -0
  82. package/dist/office-import.d.ts +48 -0
  83. package/dist/office-import.js +154 -0
  84. package/dist/office-import.js.map +1 -0
  85. package/dist/presentation-pptx-api.d.ts +60 -0
  86. package/dist/presentation-pptx-import.d.ts +5 -0
  87. package/dist/presentation-pptx-state-digest-765CZZED.js +10 -0
  88. package/dist/presentation-pptx-state-digest-765CZZED.js.map +1 -0
  89. package/dist/presentation-pptx-state-digest.d.ts +3 -0
  90. package/dist/presentation-pptx-state.d.ts +4 -0
  91. package/dist/presentation-pptx-xml.d.ts +29 -0
  92. package/dist/presentation-pptx.d.ts +5 -0
  93. package/dist/presentation-pptx.js +2365 -0
  94. package/dist/presentation-pptx.js.map +1 -0
  95. package/dist/presentation-render.d.ts +29 -0
  96. package/dist/presentation-render.js +375 -0
  97. package/dist/presentation-render.js.map +1 -0
  98. package/dist/presentation.d.ts +565 -0
  99. package/dist/presentation.js +71 -0
  100. package/dist/presentation.js.map +1 -0
  101. package/dist/production-composite.d.ts +68 -0
  102. package/dist/production-document.d.ts +2 -0
  103. package/dist/production-document.js +176 -0
  104. package/dist/production-document.js.map +1 -0
  105. package/dist/production-native-codecs.d.ts +94 -0
  106. package/dist/production-presentation.d.ts +2 -0
  107. package/dist/production-presentation.js +176 -0
  108. package/dist/production-presentation.js.map +1 -0
  109. package/dist/production-sha256.d.ts +2 -0
  110. package/dist/production-spreadsheet-native.d.ts +12 -0
  111. package/dist/production-spreadsheet.d.ts +2 -0
  112. package/dist/production-spreadsheet.js +176 -0
  113. package/dist/production-spreadsheet.js.map +1 -0
  114. package/dist/production.d.ts +96 -0
  115. package/dist/raster-image.d.ts +34 -0
  116. package/dist/reference.d.ts +17 -0
  117. package/dist/reference.js +574 -0
  118. package/dist/reference.js.map +1 -0
  119. package/dist/runtime-cli-entry.d.ts +2 -0
  120. package/dist/runtime-cli-entry.js +21 -0
  121. package/dist/runtime-cli-entry.js.map +1 -0
  122. package/dist/runtime-cli.d.ts +76 -0
  123. package/dist/runtime-cli.js +26 -0
  124. package/dist/runtime-cli.js.map +1 -0
  125. package/dist/runtime-development.d.ts +73 -0
  126. package/dist/runtime-development.js +30 -0
  127. package/dist/runtime-development.js.map +1 -0
  128. package/dist/runtime-receipt.d.ts +19 -0
  129. package/dist/runtime.d.ts +355 -0
  130. package/dist/runtime.js +42 -0
  131. package/dist/runtime.js.map +1 -0
  132. package/dist/snapshot.d.ts +26 -0
  133. package/dist/snapshot.js +61 -0
  134. package/dist/snapshot.js.map +1 -0
  135. package/dist/spreadsheet-address.d.ts +18 -0
  136. package/dist/spreadsheet-file.d.ts +17 -0
  137. package/dist/spreadsheet-formula.d.ts +67 -0
  138. package/dist/spreadsheet-image.d.ts +17 -0
  139. package/dist/spreadsheet-render.d.ts +5 -0
  140. package/dist/spreadsheet-render.js +752 -0
  141. package/dist/spreadsheet-render.js.map +1 -0
  142. package/dist/spreadsheet-types.d.ts +155 -0
  143. package/dist/spreadsheet-xlsx-api.d.ts +88 -0
  144. package/dist/spreadsheet-xlsx-codec.d.ts +16 -0
  145. package/dist/spreadsheet-xlsx-codec.js +16 -0
  146. package/dist/spreadsheet-xlsx-codec.js.map +1 -0
  147. package/dist/spreadsheet-xlsx-state.d.ts +16 -0
  148. package/dist/spreadsheet.d.ts +724 -0
  149. package/dist/spreadsheet.js +67 -0
  150. package/dist/spreadsheet.js.map +1 -0
  151. package/package.json +129 -0
  152. package/src/bounded-zip.ts +450 -0
  153. package/src/document-docx-api.ts +64 -0
  154. package/src/document-docx-codec.ts +2253 -0
  155. package/src/document-docx-import.ts +3814 -0
  156. package/src/document-docx-packer.ts +72 -0
  157. package/src/document-docx-state.ts +59 -0
  158. package/src/document-render.ts +615 -0
  159. package/src/document.ts +2800 -0
  160. package/src/errors.ts +45 -0
  161. package/src/file-blob.ts +43 -0
  162. package/src/index.ts +4 -0
  163. package/src/inspection.ts +50 -0
  164. package/src/kernel.ts +525 -0
  165. package/src/materializer-cli-entry.ts +11 -0
  166. package/src/materializer-cli.ts +743 -0
  167. package/src/native-raster.ts +141 -0
  168. package/src/native.ts +390 -0
  169. package/src/office-import.ts +243 -0
  170. package/src/presentation-pptx-api.ts +107 -0
  171. package/src/presentation-pptx-import.ts +2065 -0
  172. package/src/presentation-pptx-state-digest.ts +46 -0
  173. package/src/presentation-pptx-state.ts +18 -0
  174. package/src/presentation-pptx-xml.ts +473 -0
  175. package/src/presentation-pptx.ts +447 -0
  176. package/src/presentation-render.ts +532 -0
  177. package/src/presentation.ts +2599 -0
  178. package/src/production-composite.ts +805 -0
  179. package/src/production-document.ts +2 -0
  180. package/src/production-native-codecs.ts +1141 -0
  181. package/src/production-presentation.ts +2 -0
  182. package/src/production-sha256.ts +150 -0
  183. package/src/production-spreadsheet-native.ts +686 -0
  184. package/src/production-spreadsheet.ts +2 -0
  185. package/src/production.ts +1022 -0
  186. package/src/raster-image.ts +586 -0
  187. package/src/reference.ts +17 -0
  188. package/src/runtime-cli-entry.ts +13 -0
  189. package/src/runtime-cli.ts +670 -0
  190. package/src/runtime-development.ts +689 -0
  191. package/src/runtime-receipt.ts +169 -0
  192. package/src/runtime.ts +1463 -0
  193. package/src/snapshot.ts +88 -0
  194. package/src/spreadsheet-address.ts +90 -0
  195. package/src/spreadsheet-file.ts +71 -0
  196. package/src/spreadsheet-formula.ts +1212 -0
  197. package/src/spreadsheet-image.ts +242 -0
  198. package/src/spreadsheet-render.ts +985 -0
  199. package/src/spreadsheet-types.ts +171 -0
  200. package/src/spreadsheet-xlsx-api.ts +123 -0
  201. package/src/spreadsheet-xlsx-codec.ts +3063 -0
  202. package/src/spreadsheet-xlsx-state.ts +191 -0
  203. package/src/spreadsheet.ts +5195 -0
@@ -0,0 +1,586 @@
1
+ export type RasterImageContentType = "image/png" | "image/jpeg" | "image/gif" | "image/webp";
2
+
3
+ export type RasterImageMetadata = {
4
+ contentType: RasterImageContentType;
5
+ width: number;
6
+ height: number;
7
+ };
8
+
9
+ /** Internal validation failure. Public model boundaries translate this to their typed error. */
10
+ export class RasterImageValidationError extends Error {
11
+ readonly name = "RasterImageValidationError";
12
+
13
+ get detail(): string {
14
+ return this.message;
15
+ }
16
+ }
17
+
18
+ /** Decode already-syntax-checked base64 without forcing a large intermediary string when supported. */
19
+ export function decodeRasterBase64(encoded: string): Uint8Array<ArrayBuffer> {
20
+ const native = (
21
+ Uint8Array as typeof Uint8Array & {
22
+ fromBase64?: (value: string) => Uint8Array<ArrayBuffer>;
23
+ }
24
+ ).fromBase64;
25
+ if (native) return native(encoded);
26
+
27
+ const binary = atob(encoded);
28
+ const bytes = new Uint8Array(binary.length);
29
+ for (let index = 0; index < binary.length; index += 1) {
30
+ bytes[index] = binary.charCodeAt(index);
31
+ }
32
+ return bytes;
33
+ }
34
+
35
+ /** Encode bytes using the zero-copy native primitive where available, with a bounded fallback. */
36
+ export function encodeRasterBase64(bytes: Uint8Array): string {
37
+ const native = (bytes as Uint8Array & { toBase64?: () => string }).toBase64;
38
+ if (native) return native.call(bytes);
39
+
40
+ const chunkSize = 0x8000;
41
+ let binary = "";
42
+ for (let offset = 0; offset < bytes.length; offset += chunkSize) {
43
+ binary += String.fromCharCode(...bytes.subarray(offset, offset + chunkSize));
44
+ }
45
+ return btoa(binary);
46
+ }
47
+
48
+ const RASTER_CONTENT_TYPES = new Set<RasterImageContentType>([
49
+ "image/png",
50
+ "image/jpeg",
51
+ "image/gif",
52
+ "image/webp",
53
+ ]);
54
+
55
+ /** Normalize one supported raster MIME without admitting active image formats. */
56
+ export function normalizeRasterContentType(value: unknown): RasterImageContentType {
57
+ if (typeof value !== "string") throw invalid("content type must be a string");
58
+ const normalized = value.toLowerCase();
59
+ if (!RASTER_CONTENT_TYPES.has(normalized as RasterImageContentType)) {
60
+ throw invalid("content type must be image/png, image/jpeg, image/gif, or image/webp");
61
+ }
62
+ return normalized as RasterImageContentType;
63
+ }
64
+
65
+ /** Validate bytes and, when supplied, bind the declared MIME to their signature. */
66
+ export function validateRasterImageBytes(
67
+ bytes: Uint8Array,
68
+ declaredType?: unknown,
69
+ ): RasterImageContentType {
70
+ const detected = inspectRasterImage(bytes).contentType;
71
+ if (declaredType !== undefined && normalizeRasterContentType(declaredType) !== detected) {
72
+ throw invalid("content type does not match the image bytes");
73
+ }
74
+ return detected;
75
+ }
76
+
77
+ /** Strictly decode and canonicalize an inline raster source under a byte cap. */
78
+ export function canonicalizeRasterDataUrl(
79
+ value: unknown,
80
+ maximumBytes: number,
81
+ ): Readonly<{
82
+ contentType: RasterImageContentType;
83
+ canonical: string;
84
+ byteLength: number;
85
+ }> {
86
+ if (typeof value !== "string" || value.length > Math.ceil((maximumBytes * 4) / 3) + 128) {
87
+ throw invalid("data URL must be a bounded string");
88
+ }
89
+ const match = /^data:([^;,]+);base64,([A-Za-z0-9+/]*={0,2})$/i.exec(value);
90
+ if (!match) throw invalid("data URL must contain inline base64 raster bytes");
91
+ const declaredType = normalizeRasterContentType(match[1]!);
92
+ const encoded = match[2]!;
93
+ if (
94
+ encoded.length === 0 ||
95
+ encoded.length % 4 !== 0 ||
96
+ !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(encoded)
97
+ ) {
98
+ throw invalid("data URL contains malformed base64");
99
+ }
100
+ let bytes: Uint8Array;
101
+ try {
102
+ bytes = decodeRasterBase64(encoded);
103
+ } catch {
104
+ throw invalid("data URL contains malformed base64");
105
+ }
106
+ if (bytes.byteLength > maximumBytes) {
107
+ throw invalid(`decoded image exceeds ${maximumBytes} bytes`);
108
+ }
109
+ const contentType = validateRasterImageBytes(bytes, declaredType);
110
+ return Object.freeze({
111
+ contentType,
112
+ canonical: `data:${contentType};base64,${encodeRasterBase64(bytes)}`,
113
+ byteLength: bytes.byteLength,
114
+ });
115
+ }
116
+
117
+ /** Count UTF-8 bytes without allocating an encoded copy; returns as soon as the cap is crossed. */
118
+ export function boundedUtf8ByteLength(value: string, maximum: number): number {
119
+ let bytes = 0;
120
+ for (let index = 0; index < value.length; index += 1) {
121
+ const codePoint = value.codePointAt(index)!;
122
+ if (codePoint <= 0x7f) bytes += 1;
123
+ else if (codePoint <= 0x7ff) bytes += 2;
124
+ else if (codePoint <= 0xffff) bytes += 3;
125
+ else {
126
+ bytes += 4;
127
+ index += 1;
128
+ }
129
+ if (bytes > maximum) return bytes;
130
+ }
131
+ return bytes;
132
+ }
133
+
134
+ const MAX_RASTER_DIMENSION = 16_384;
135
+ const MAX_RASTER_PIXELS = 16_777_216;
136
+ const MAX_ANIMATION_FRAMES = 500;
137
+ const MAX_ANIMATION_DECODED_PIXELS = 67_108_864;
138
+
139
+ /**
140
+ * Structurally validates supported raster bytes before a native/browser decoder sees them.
141
+ *
142
+ * Callers must separately bound the encoded byte length. This parser bounds decoded canvas,
143
+ * animation frame count, and cumulative animated pixels while walking each container once.
144
+ */
145
+ export function inspectRasterImage(bytes: Uint8Array): RasterImageMetadata {
146
+ if (
147
+ bytes.length >= 8 &&
148
+ bytes[0] === 0x89 &&
149
+ bytes[1] === 0x50 &&
150
+ bytes[2] === 0x4e &&
151
+ bytes[3] === 0x47 &&
152
+ bytes[4] === 0x0d &&
153
+ bytes[5] === 0x0a &&
154
+ bytes[6] === 0x1a &&
155
+ bytes[7] === 0x0a
156
+ ) {
157
+ return parsePngHeader(bytes);
158
+ }
159
+ if (bytes.length >= 3 && bytes[0] === 0xff && bytes[1] === 0xd8 && bytes[2] === 0xff) {
160
+ return parseJpegHeader(bytes);
161
+ }
162
+ if (bytes.length >= 6) {
163
+ const signature = ascii(bytes, 0, 6);
164
+ if (signature === "GIF87a" || signature === "GIF89a") return parseGifHeader(bytes);
165
+ }
166
+ if (bytes.length >= 12 && ascii(bytes, 0, 4) === "RIFF" && ascii(bytes, 8, 4) === "WEBP") {
167
+ return parseWebpHeader(bytes);
168
+ }
169
+ throw invalid("does not have a supported PNG, JPEG, GIF, or WebP signature");
170
+ }
171
+
172
+ function parsePngHeader(bytes: Uint8Array): RasterImageMetadata {
173
+ if (bytes.length < 45 || readU32Be(bytes, 8) !== 13 || ascii(bytes, 12, 4) !== "IHDR") {
174
+ throw malformed("PNG has no complete leading IHDR chunk");
175
+ }
176
+ const width = readU32Be(bytes, 16);
177
+ const height = readU32Be(bytes, 20);
178
+ assertRasterDimensions(width, height);
179
+
180
+ const bitDepth = bytes[24]!;
181
+ const colorType = bytes[25]!;
182
+ const allowedDepths: Readonly<Record<number, readonly number[]>> = {
183
+ 0: [1, 2, 4, 8, 16],
184
+ 2: [8, 16],
185
+ 3: [1, 2, 4, 8],
186
+ 4: [8, 16],
187
+ 6: [8, 16],
188
+ };
189
+ if (
190
+ !allowedDepths[colorType]?.includes(bitDepth) ||
191
+ bytes[26] !== 0 ||
192
+ bytes[27] !== 0 ||
193
+ (bytes[28] !== 0 && bytes[28] !== 1)
194
+ ) {
195
+ throw malformed("PNG IHDR fields are invalid");
196
+ }
197
+ if (pngCrc32(bytes, 12, 29) !== readU32Be(bytes, 29)) {
198
+ throw malformed("PNG IHDR checksum is invalid");
199
+ }
200
+
201
+ let offset = 33;
202
+ let sawIdat = false;
203
+ let declaredFrames: number | undefined;
204
+ let frameCount = 0;
205
+ let animationPixels = 0;
206
+ while (offset < bytes.length) {
207
+ if (offset + 12 > bytes.length) throw malformed("PNG chunk is truncated");
208
+ const length = readU32Be(bytes, offset);
209
+ if (length > bytes.length - offset - 12) {
210
+ throw malformed("PNG chunk length exceeds the input");
211
+ }
212
+ const chunkType = ascii(bytes, offset + 4, 4);
213
+ if (!/^[A-Za-z]{4}$/.test(chunkType)) {
214
+ throw malformed("PNG chunk type is invalid");
215
+ }
216
+ if (chunkType === "IHDR") throw malformed("PNG contains duplicate IHDR");
217
+ if (chunkType === "IDAT") sawIdat = true;
218
+ if (chunkType === "acTL") {
219
+ if (length !== 8 || declaredFrames !== undefined) {
220
+ throw malformed("APNG animation control is invalid");
221
+ }
222
+ declaredFrames = readU32Be(bytes, offset + 8);
223
+ if (declaredFrames === 0 || declaredFrames > MAX_ANIMATION_FRAMES) {
224
+ throw invalid(`animation frame limit exceeded (maximum ${MAX_ANIMATION_FRAMES})`);
225
+ }
226
+ }
227
+ if (chunkType === "fcTL") {
228
+ if (length !== 26 || declaredFrames === undefined) {
229
+ throw malformed("APNG frame control is invalid");
230
+ }
231
+ const frameWidth = readU32Be(bytes, offset + 12);
232
+ const frameHeight = readU32Be(bytes, offset + 16);
233
+ const frameX = readU32Be(bytes, offset + 20);
234
+ const frameY = readU32Be(bytes, offset + 24);
235
+ assertRasterDimensions(frameWidth, frameHeight);
236
+ if (frameX + frameWidth > width || frameY + frameHeight > height) {
237
+ throw malformed("APNG frame exceeds its canvas dimensions");
238
+ }
239
+ frameCount += 1;
240
+ animationPixels = addAnimationPixels(animationPixels, frameWidth, frameHeight, frameCount);
241
+ }
242
+ const next = offset + 12 + length;
243
+ if (chunkType === "IEND") {
244
+ if (
245
+ length !== 0 ||
246
+ !sawIdat ||
247
+ next !== bytes.length ||
248
+ (declaredFrames !== undefined && frameCount !== declaredFrames)
249
+ ) {
250
+ throw malformed("PNG terminal chunks are invalid");
251
+ }
252
+ return { contentType: "image/png", width, height };
253
+ }
254
+ offset = next;
255
+ }
256
+ throw malformed("PNG has no terminal IEND chunk");
257
+ }
258
+
259
+ function parseJpegHeader(bytes: Uint8Array): RasterImageMetadata {
260
+ if (
261
+ bytes.length < 12 ||
262
+ bytes[0] !== 0xff ||
263
+ bytes[1] !== 0xd8 ||
264
+ bytes.at(-2) !== 0xff ||
265
+ bytes.at(-1) !== 0xd9
266
+ ) {
267
+ throw malformed("JPEG framing is incomplete");
268
+ }
269
+ let offset = 2;
270
+ let dimensions: RasterImageMetadata | undefined;
271
+ while (offset < bytes.length - 2) {
272
+ if (bytes[offset] !== 0xff) throw malformed("JPEG marker prefix is invalid");
273
+ while (offset < bytes.length && bytes[offset] === 0xff) offset += 1;
274
+ if (offset >= bytes.length) throw malformed("JPEG marker is truncated");
275
+ const marker = bytes[offset++]!;
276
+ if (marker === 0x00) throw malformed("JPEG contains an escaped marker in headers");
277
+ if (marker === 0xd8 || marker === 0x01 || (marker >= 0xd0 && marker <= 0xd7)) continue;
278
+ if (marker === 0xd9) throw malformed("JPEG ended before image data");
279
+ if (offset + 2 > bytes.length) throw malformed("JPEG segment is truncated");
280
+ const length = readU16Be(bytes, offset);
281
+ if (length < 2 || length > bytes.length - offset) {
282
+ throw malformed("JPEG segment length exceeds the input");
283
+ }
284
+ if (marker === 0xda) {
285
+ if (!dimensions) {
286
+ throw malformed("JPEG has no supported frame header before image data");
287
+ }
288
+ const scanComponents = bytes[offset + 2];
289
+ if (
290
+ scanComponents === undefined ||
291
+ scanComponents === 0 ||
292
+ scanComponents > 4 ||
293
+ length !== 6 + 2 * scanComponents
294
+ ) {
295
+ throw malformed("JPEG scan header is invalid");
296
+ }
297
+ return dimensions;
298
+ }
299
+ if (isJpegStartOfFrame(marker)) {
300
+ if (dimensions) throw malformed("JPEG contains duplicate frame headers");
301
+ if (length < 8) throw malformed("JPEG frame header is too short");
302
+ const height = readU16Be(bytes, offset + 3);
303
+ const width = readU16Be(bytes, offset + 5);
304
+ const components = bytes[offset + 7]!;
305
+ if (components === 0 || components > 4 || length !== 8 + 3 * components) {
306
+ throw malformed("JPEG frame component table is invalid");
307
+ }
308
+ assertRasterDimensions(width, height);
309
+ dimensions = { contentType: "image/jpeg", width, height };
310
+ }
311
+ offset += length;
312
+ }
313
+ throw malformed("JPEG has no complete scan data");
314
+ }
315
+
316
+ function isJpegStartOfFrame(marker: number): boolean {
317
+ return (
318
+ (marker >= 0xc0 && marker <= 0xc3) ||
319
+ (marker >= 0xc5 && marker <= 0xc7) ||
320
+ (marker >= 0xc9 && marker <= 0xcb) ||
321
+ (marker >= 0xcd && marker <= 0xcf)
322
+ );
323
+ }
324
+
325
+ function parseGifHeader(bytes: Uint8Array): RasterImageMetadata {
326
+ if (bytes.length < 14) throw malformed("GIF logical screen descriptor is truncated");
327
+ const width = readU16Le(bytes, 6);
328
+ const height = readU16Le(bytes, 8);
329
+ assertRasterDimensions(width, height);
330
+ const packed = bytes[10]!;
331
+ let offset = 13;
332
+ if ((packed & 0x80) !== 0) {
333
+ const tableBytes = 3 * 2 ** ((packed & 0x07) + 1);
334
+ if (tableBytes > bytes.length - offset) {
335
+ throw malformed("GIF global color table is truncated");
336
+ }
337
+ offset += tableBytes;
338
+ }
339
+ let sawImage = false;
340
+ let frameCount = 0;
341
+ let animationPixels = 0;
342
+ while (offset < bytes.length) {
343
+ const marker = bytes[offset++]!;
344
+ if (marker === 0x3b) {
345
+ if (!sawImage || offset !== bytes.length) {
346
+ throw malformed("GIF trailer or image data is invalid");
347
+ }
348
+ return { contentType: "image/gif", width, height };
349
+ }
350
+ if (marker === 0x21) {
351
+ if (offset >= bytes.length) throw malformed("GIF extension is truncated");
352
+ offset += 1;
353
+ offset = skipGifSubBlocks(bytes, offset);
354
+ continue;
355
+ }
356
+ if (marker !== 0x2c || offset + 9 > bytes.length) {
357
+ throw malformed("GIF block structure is invalid");
358
+ }
359
+ const frameLeft = readU16Le(bytes, offset);
360
+ const frameTop = readU16Le(bytes, offset + 2);
361
+ const frameWidth = readU16Le(bytes, offset + 4);
362
+ const frameHeight = readU16Le(bytes, offset + 6);
363
+ assertRasterDimensions(frameWidth, frameHeight);
364
+ if (frameLeft + frameWidth > width || frameTop + frameHeight > height) {
365
+ throw malformed("GIF frame exceeds its canvas dimensions");
366
+ }
367
+ const framePacked = bytes[offset + 8]!;
368
+ offset += 9;
369
+ if ((framePacked & 0x80) !== 0) {
370
+ const tableBytes = 3 * 2 ** ((framePacked & 0x07) + 1);
371
+ if (tableBytes > bytes.length - offset) {
372
+ throw malformed("GIF local color table is truncated");
373
+ }
374
+ offset += tableBytes;
375
+ }
376
+ if (offset >= bytes.length) {
377
+ throw malformed("GIF LZW header is missing");
378
+ }
379
+ const minimumCodeSize = bytes[offset++]!;
380
+ if (minimumCodeSize < 2 || minimumCodeSize > 8) {
381
+ throw malformed("GIF LZW header is invalid");
382
+ }
383
+ offset = skipGifSubBlocks(bytes, offset);
384
+ frameCount += 1;
385
+ animationPixels = addAnimationPixels(animationPixels, frameWidth, frameHeight, frameCount);
386
+ sawImage = true;
387
+ }
388
+ throw malformed("GIF has no trailer");
389
+ }
390
+
391
+ function skipGifSubBlocks(bytes: Uint8Array, start: number): number {
392
+ let offset = start;
393
+ while (offset < bytes.length) {
394
+ const length = bytes[offset++]!;
395
+ if (length === 0) return offset;
396
+ if (length > bytes.length - offset) {
397
+ throw malformed("GIF data sub-block is truncated");
398
+ }
399
+ offset += length;
400
+ }
401
+ throw malformed("GIF data sub-block has no terminator");
402
+ }
403
+
404
+ function parseWebpHeader(bytes: Uint8Array): RasterImageMetadata {
405
+ if (bytes.length < 30 || readU32Le(bytes, 4) + 8 !== bytes.length) {
406
+ throw malformed("WebP RIFF container length is invalid");
407
+ }
408
+ let offset = 12;
409
+ let dimensions: { width: number; height: number } | undefined;
410
+ let extended = false;
411
+ let sawImage = false;
412
+ let frameCount = 0;
413
+ let animationPixels = 0;
414
+ while (offset < bytes.length) {
415
+ if (offset + 8 > bytes.length) throw malformed("WebP chunk is truncated");
416
+ const type = ascii(bytes, offset, 4);
417
+ const length = readU32Le(bytes, offset + 4);
418
+ const dataOffset = offset + 8;
419
+ if (length > bytes.length - dataOffset) {
420
+ throw malformed("WebP chunk length exceeds the input");
421
+ }
422
+ if (type === "VP8X") {
423
+ if (offset !== 12 || dimensions || length !== 10) {
424
+ throw malformed("WebP VP8X header placement or length is invalid");
425
+ }
426
+ dimensions = {
427
+ width: readU24Le(bytes, dataOffset + 4) + 1,
428
+ height: readU24Le(bytes, dataOffset + 7) + 1,
429
+ };
430
+ assertRasterDimensions(dimensions.width, dimensions.height);
431
+ extended = true;
432
+ } else if (type === "VP8 ") {
433
+ if (
434
+ length < 10 ||
435
+ bytes[dataOffset + 3] !== 0x9d ||
436
+ bytes[dataOffset + 4] !== 0x01 ||
437
+ bytes[dataOffset + 5] !== 0x2a
438
+ ) {
439
+ throw malformed("WebP VP8 frame header is invalid");
440
+ }
441
+ const frame = {
442
+ width: readU16Le(bytes, dataOffset + 6) & 0x3fff,
443
+ height: readU16Le(bytes, dataOffset + 8) & 0x3fff,
444
+ };
445
+ assertRasterDimensions(frame.width, frame.height);
446
+ if (dimensions && (frame.width > dimensions.width || frame.height > dimensions.height)) {
447
+ throw malformed("WebP frame exceeds its canvas dimensions");
448
+ }
449
+ dimensions ??= frame;
450
+ frameCount += 1;
451
+ animationPixels = addAnimationPixels(animationPixels, frame.width, frame.height, frameCount);
452
+ sawImage = true;
453
+ } else if (type === "VP8L") {
454
+ if (length < 5 || bytes[dataOffset] !== 0x2f) {
455
+ throw malformed("WebP VP8L frame header is invalid");
456
+ }
457
+ const b1 = bytes[dataOffset + 1]!;
458
+ const b2 = bytes[dataOffset + 2]!;
459
+ const b3 = bytes[dataOffset + 3]!;
460
+ const b4 = bytes[dataOffset + 4]!;
461
+ const frame = {
462
+ width: 1 + b1 + ((b2 & 0x3f) << 8),
463
+ height: 1 + (b2 >> 6) + (b3 << 2) + ((b4 & 0x0f) << 10),
464
+ };
465
+ assertRasterDimensions(frame.width, frame.height);
466
+ if (dimensions && (frame.width > dimensions.width || frame.height > dimensions.height)) {
467
+ throw malformed("WebP frame exceeds its canvas dimensions");
468
+ }
469
+ dimensions ??= frame;
470
+ frameCount += 1;
471
+ animationPixels = addAnimationPixels(animationPixels, frame.width, frame.height, frameCount);
472
+ sawImage = true;
473
+ } else if (type === "ANMF") {
474
+ if (!extended || !dimensions || length < 24) {
475
+ throw malformed("WebP animation frame header is invalid");
476
+ }
477
+ const frameX = readU24Le(bytes, dataOffset) * 2;
478
+ const frameY = readU24Le(bytes, dataOffset + 3) * 2;
479
+ const frameWidth = readU24Le(bytes, dataOffset + 6) + 1;
480
+ const frameHeight = readU24Le(bytes, dataOffset + 9) + 1;
481
+ assertRasterDimensions(frameWidth, frameHeight);
482
+ if (frameX + frameWidth > dimensions.width || frameY + frameHeight > dimensions.height) {
483
+ throw malformed("WebP animation frame exceeds its canvas dimensions");
484
+ }
485
+ frameCount += 1;
486
+ animationPixels = addAnimationPixels(animationPixels, frameWidth, frameHeight, frameCount);
487
+ sawImage = true;
488
+ }
489
+ const paddedLength = length + (length & 1);
490
+ if (paddedLength > bytes.length - dataOffset) {
491
+ throw malformed("WebP chunk padding is truncated");
492
+ }
493
+ offset = dataOffset + paddedLength;
494
+ }
495
+ if (offset !== bytes.length || !dimensions || !sawImage) {
496
+ throw malformed("WebP has no complete image payload");
497
+ }
498
+ return { contentType: "image/webp", ...dimensions };
499
+ }
500
+
501
+ function assertRasterDimensions(width: number, height: number): void {
502
+ if (!Number.isInteger(width) || !Number.isInteger(height) || width <= 0 || height <= 0) {
503
+ throw invalid("image dimensions must be positive integers");
504
+ }
505
+ if (width > MAX_RASTER_DIMENSION || height > MAX_RASTER_DIMENSION) {
506
+ throw invalid(
507
+ `image dimension limit exceeded (${width}x${height}; maximum ${MAX_RASTER_DIMENSION})`,
508
+ );
509
+ }
510
+ if (width > Math.floor(MAX_RASTER_PIXELS / height)) {
511
+ throw invalid(
512
+ `decoded pixel limit exceeded (${width}x${height}; maximum ${MAX_RASTER_PIXELS})`,
513
+ );
514
+ }
515
+ }
516
+
517
+ function addAnimationPixels(
518
+ current: number,
519
+ width: number,
520
+ height: number,
521
+ frames: number,
522
+ ): number {
523
+ if (frames > MAX_ANIMATION_FRAMES) {
524
+ throw invalid(`animation frame limit exceeded (maximum ${MAX_ANIMATION_FRAMES})`);
525
+ }
526
+ const framePixels = width * height;
527
+ if (current > MAX_ANIMATION_DECODED_PIXELS - framePixels) {
528
+ throw invalid(
529
+ `animation decoded pixel limit exceeded (maximum ${MAX_ANIMATION_DECODED_PIXELS})`,
530
+ );
531
+ }
532
+ return current + framePixels;
533
+ }
534
+
535
+ function malformed(detail: string): RasterImageValidationError {
536
+ return invalid(`malformed raster image: ${detail}`);
537
+ }
538
+
539
+ function invalid(detail: string): RasterImageValidationError {
540
+ return new RasterImageValidationError(detail);
541
+ }
542
+
543
+ function readU16Be(bytes: Uint8Array, offset: number): number {
544
+ return bytes[offset]! * 0x100 + bytes[offset + 1]!;
545
+ }
546
+
547
+ function readU16Le(bytes: Uint8Array, offset: number): number {
548
+ return bytes[offset]! + bytes[offset + 1]! * 0x100;
549
+ }
550
+
551
+ function readU24Le(bytes: Uint8Array, offset: number): number {
552
+ return bytes[offset]! + bytes[offset + 1]! * 0x100 + bytes[offset + 2]! * 0x1_0000;
553
+ }
554
+
555
+ function readU32Be(bytes: Uint8Array, offset: number): number {
556
+ return (
557
+ bytes[offset]! * 0x1_000000 +
558
+ bytes[offset + 1]! * 0x1_0000 +
559
+ bytes[offset + 2]! * 0x100 +
560
+ bytes[offset + 3]!
561
+ );
562
+ }
563
+
564
+ function readU32Le(bytes: Uint8Array, offset: number): number {
565
+ return (
566
+ bytes[offset]! +
567
+ bytes[offset + 1]! * 0x100 +
568
+ bytes[offset + 2]! * 0x1_0000 +
569
+ bytes[offset + 3]! * 0x1_000000
570
+ );
571
+ }
572
+
573
+ function ascii(bytes: Uint8Array, offset: number, length: number): string {
574
+ return String.fromCharCode(...bytes.subarray(offset, offset + length));
575
+ }
576
+
577
+ function pngCrc32(bytes: Uint8Array, start: number, end: number): number {
578
+ let crc = 0xffff_ffff;
579
+ for (let index = start; index < end; index += 1) {
580
+ crc ^= bytes[index]!;
581
+ for (let bit = 0; bit < 8; bit += 1) {
582
+ crc = (crc >>> 1) ^ (crc & 1 ? 0xedb8_8320 : 0);
583
+ }
584
+ }
585
+ return (crc ^ 0xffff_ffff) >>> 0;
586
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * TypeScript artifact models used for API conformance, codec development, and
3
+ * deterministic tests. They are not a production fallback for the Rust
4
+ * native/WASM kernel.
5
+ *
6
+ * Production Node/Bun spreadsheet editing uses the native session facade.
7
+ * Browser editing uses `@opengeni/sdk/editable-artifacts`, which owns a
8
+ * dedicated Worker and the paired WASM kernel.
9
+ */
10
+ export * from "./document";
11
+ export * from "./kernel";
12
+ export * from "./presentation";
13
+ export * from "./spreadsheet";
14
+ export { SpreadsheetFile } from "./spreadsheet-file";
15
+ export { Document as ReferenceDocument } from "./document";
16
+ export { Presentation as ReferencePresentation } from "./presentation";
17
+ export { Workbook as ReferenceWorkbook } from "./spreadsheet";
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { ArtifactRuntimeError } from "./runtime";
4
+ import { runConfiguredArtifactRuntimeCli } from "./runtime-development";
5
+
6
+ try {
7
+ process.stdout.write(await runConfiguredArtifactRuntimeCli(process.argv.slice(2)));
8
+ } catch (error) {
9
+ const code = error instanceof ArtifactRuntimeError ? error.code : "ARTIFACT_RUNTIME_UNAVAILABLE";
10
+ const message = error instanceof Error ? error.message : String(error);
11
+ process.stderr.write(`${JSON.stringify({ error: { code, message } })}\n`);
12
+ process.exitCode = 1;
13
+ }