@swf2es/format 0.0.0-stage → 0.1.1

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 (94) hide show
  1. package/LICENSE +202 -0
  2. package/LICENSE.third-party +35 -0
  3. package/README.md +22 -2
  4. package/dist/abc-names.d.ts +3 -0
  5. package/dist/abc-names.d.ts.map +1 -0
  6. package/dist/abc-names.js +127 -0
  7. package/dist/abc-names.js.map +1 -0
  8. package/dist/actions.d.ts +106 -0
  9. package/dist/actions.d.ts.map +1 -0
  10. package/dist/actions.js +223 -0
  11. package/dist/actions.js.map +1 -0
  12. package/dist/air-descriptor.d.ts +11 -0
  13. package/dist/air-descriptor.d.ts.map +1 -0
  14. package/dist/air-descriptor.js +64 -0
  15. package/dist/air-descriptor.js.map +1 -0
  16. package/dist/api-versions.d.ts +12 -0
  17. package/dist/api-versions.d.ts.map +1 -0
  18. package/dist/api-versions.js +23 -0
  19. package/dist/api-versions.js.map +1 -0
  20. package/dist/bitmap.d.ts +34 -0
  21. package/dist/bitmap.d.ts.map +1 -0
  22. package/dist/bitmap.js +200 -0
  23. package/dist/bitmap.js.map +1 -0
  24. package/dist/button.d.ts +39 -0
  25. package/dist/button.d.ts.map +1 -0
  26. package/dist/button.js +61 -0
  27. package/dist/button.js.map +1 -0
  28. package/dist/compression.d.ts +47 -0
  29. package/dist/compression.d.ts.map +1 -0
  30. package/dist/compression.js +201 -0
  31. package/dist/compression.js.map +1 -0
  32. package/dist/display.d.ts +106 -0
  33. package/dist/display.d.ts.map +1 -0
  34. package/dist/display.js +306 -0
  35. package/dist/display.js.map +1 -0
  36. package/dist/filters.d.ts +66 -0
  37. package/dist/filters.d.ts.map +1 -0
  38. package/dist/filters.js +137 -0
  39. package/dist/filters.js.map +1 -0
  40. package/dist/font.d.ts +60 -0
  41. package/dist/font.d.ts.map +1 -0
  42. package/dist/font.js +169 -0
  43. package/dist/font.js.map +1 -0
  44. package/dist/index.d.ts +27 -0
  45. package/dist/index.d.ts.map +1 -0
  46. package/dist/index.js +27 -0
  47. package/dist/index.js.map +1 -0
  48. package/dist/pbj.d.ts +178 -0
  49. package/dist/pbj.d.ts.map +1 -0
  50. package/dist/pbj.js +485 -0
  51. package/dist/pbj.js.map +1 -0
  52. package/dist/shape.d.ts +152 -0
  53. package/dist/shape.d.ts.map +1 -0
  54. package/dist/shape.js +412 -0
  55. package/dist/shape.js.map +1 -0
  56. package/dist/sound.d.ts +71 -0
  57. package/dist/sound.d.ts.map +1 -0
  58. package/dist/sound.js +92 -0
  59. package/dist/sound.js.map +1 -0
  60. package/dist/swf.d.ts +77 -0
  61. package/dist/swf.d.ts.map +1 -0
  62. package/dist/swf.js +164 -0
  63. package/dist/swf.js.map +1 -0
  64. package/dist/tags.d.ts +57 -0
  65. package/dist/tags.d.ts.map +1 -0
  66. package/dist/tags.js +59 -0
  67. package/dist/tags.js.map +1 -0
  68. package/dist/text.d.ts +75 -0
  69. package/dist/text.d.ts.map +1 -0
  70. package/dist/text.js +136 -0
  71. package/dist/text.js.map +1 -0
  72. package/dist/zip.d.ts +17 -0
  73. package/dist/zip.d.ts.map +1 -0
  74. package/dist/zip.js +92 -0
  75. package/dist/zip.js.map +1 -0
  76. package/package.json +33 -3
  77. package/src/abc-names.ts +138 -0
  78. package/src/actions.ts +319 -0
  79. package/src/air-descriptor.ts +84 -0
  80. package/src/api-versions.ts +27 -0
  81. package/src/bitmap.ts +259 -0
  82. package/src/button.ts +99 -0
  83. package/src/compression.ts +243 -0
  84. package/src/display.ts +419 -0
  85. package/src/filters.ts +206 -0
  86. package/src/font.ts +238 -0
  87. package/src/index.ts +151 -0
  88. package/src/pbj.ts +651 -0
  89. package/src/shape.ts +618 -0
  90. package/src/sound.ts +173 -0
  91. package/src/swf.ts +234 -0
  92. package/src/tags.ts +58 -0
  93. package/src/text.ts +213 -0
  94. package/src/zip.ts +135 -0
package/src/display.ts ADDED
@@ -0,0 +1,419 @@
1
+ // The display list's tags (SWF specification, chapter 3), and the ones that
2
+ // tie a SWF's classes to its symbols: PlaceObject 1 to 4, RemoveObject 1
3
+ // and 2, FrameLabel, DefineSprite, DefineScalingGrid, SymbolClass and
4
+ // DoABC. Filters are kept as their bytes for now, and clip actions are the
5
+ // SWF's action blocks (actions.ts), which a place gives by their indices.
6
+ import type { ActionList } from "./actions.js";
7
+ import { type ColorTransform, type Matrix, readColorTransform, readMatrix } from "./shape.js";
8
+ import { type Rect, readTags, SwfReader, type Tag } from "./swf.js";
9
+ import {
10
+ DoABC,
11
+ PlaceObject,
12
+ PlaceObject3,
13
+ PlaceObject4,
14
+ RemoveObject,
15
+ RemoveObject2,
16
+ } from "./tags.js";
17
+
18
+ /**
19
+ * A place: a new character at a depth, or a move of the one there, with
20
+ * what it sets. Fields it does not set are null or undefined.
21
+ */
22
+ export interface Place {
23
+ depth: number;
24
+ /** Whether it changes the character already at the depth, rather than placing one. */
25
+ move: boolean;
26
+ character: number | null;
27
+ matrix: Matrix | null;
28
+ colorTransform: ColorTransform | null;
29
+ ratio: number | null;
30
+ name: string | null;
31
+ clipDepth: number | null;
32
+ /** PlaceObject3's class name, for a character made by a class. */
33
+ className: string | null;
34
+ /** PlaceObject3's HasImage: the character is a bitmap, as Flash Pro marks one it places. */
35
+ hasImage: boolean;
36
+ blendMode: number | null;
37
+ cacheAsBitmap: boolean | null;
38
+ visible: boolean | null;
39
+ opaqueBackground: number | null;
40
+ /** The filter list's bytes, undecoded; null without one. */
41
+ filters: Uint8Array | null;
42
+ /** PlaceObject4's trailing AMF3 value, undecoded; null without one. */
43
+ metaData: Uint8Array | null;
44
+ /**
45
+ * The indices of its AVM1 clip actions in the SWF's ActionList; null
46
+ * without them, or when read without the list.
47
+ */
48
+ actions: number[] | null;
49
+ /**
50
+ * The field of all events of clip actions that run past the tag, which
51
+ * Flash cannot read: it stops the SWF as the first of them comes; 0 for
52
+ * none (ActionList.corrupt).
53
+ */
54
+ corrupt: number;
55
+ }
56
+
57
+ export function readString(r: SwfReader): string {
58
+ const start = r.pos;
59
+ while (r.pos < r.end && r.bytes[r.pos] !== 0) {
60
+ r.pos++;
61
+ }
62
+
63
+ const s = utf8(r.bytes.subarray(start, r.pos));
64
+ r.pos++;
65
+ return s;
66
+ }
67
+
68
+ /**
69
+ * UTF-8 as SWF 6 and later write strings, read as avmplus' Utf8ToUtf16 does
70
+ * (fromUtf8 in the runtime): a malformed or overlong sequence is no
71
+ * sequence, and its first byte stands for itself.
72
+ */
73
+ export function utf8(bytes: Uint8Array): string {
74
+ let s = "";
75
+ const length = bytes.length;
76
+
77
+ for (let i = 0; i < length; ) {
78
+ const b = bytes[i++];
79
+ // ASCII, and a continuation byte on its own, stand for themselves.
80
+ if (b < 0xc0) {
81
+ s += String.fromCharCode(b);
82
+ continue;
83
+ }
84
+
85
+ const n = b < 0xe0 ? 1 : b < 0xf0 ? 2 : 3;
86
+ const minimum = n === 1 ? 0x80 : n === 2 ? 0x800 : 0x10000;
87
+ let c = b & (n === 1 ? 0x1f : n === 2 ? 0x0f : 0x07);
88
+ let ok = i + n <= length;
89
+ for (let k = 0; ok && k < n; k++) {
90
+ const next = bytes[i + k];
91
+ ok = (next & 0xc0) === 0x80;
92
+ c = (c << 6) | (next & 0x3f);
93
+ }
94
+
95
+ if (!ok || c < minimum) {
96
+ s += String.fromCharCode(b);
97
+ continue;
98
+ }
99
+
100
+ i += n;
101
+ if (n === 3) {
102
+ // A surrogate pair however large the value, as avmplus makes one;
103
+ // fromCodePoint would refuse one past U+10FFFF.
104
+ const u = c - 0x10000;
105
+ s += String.fromCharCode(0xd800 + ((u >> 10) & 0x3ff), 0xdc00 + (u & 0x3ff));
106
+ } else {
107
+ s += String.fromCharCode(c);
108
+ }
109
+ }
110
+
111
+ return s;
112
+ }
113
+
114
+ /**
115
+ * A PlaceObject 1 to 4 tag's body; given the SWF's
116
+ * action list, with the indices of its clip actions.
117
+ */
118
+ export function readPlace(bytes: Uint8Array, tag: Tag, actions?: ActionList): Place {
119
+ const { place } = parsePlace(bytes, tag);
120
+ place.actions = actions?.byTag.get(tag.offset) ?? null;
121
+ place.corrupt = actions?.corruptEvents.get(tag.offset) ?? 0;
122
+ return place;
123
+ }
124
+
125
+ /** Where a PlaceObject 2 to 4's clip actions start, -1 without them, and its depth. */
126
+ export function clipActionsOffset(bytes: Uint8Array, tag: Tag): { depth: number; offset: number } {
127
+ const { place, clipActions } = parsePlace(bytes, tag);
128
+ return { depth: place.depth, offset: clipActions };
129
+ }
130
+
131
+ function parsePlace(bytes: Uint8Array, tag: Tag): { place: Place; clipActions: number } {
132
+ const r = new SwfReader(bytes, tag.offset, tag.offset + tag.length);
133
+ const place: Place = {
134
+ depth: 0,
135
+ move: false,
136
+ character: null,
137
+ matrix: null,
138
+ colorTransform: null,
139
+ ratio: null,
140
+ name: null,
141
+ clipDepth: null,
142
+ className: null,
143
+ hasImage: false,
144
+ blendMode: null,
145
+ cacheAsBitmap: null,
146
+ visible: null,
147
+ opaqueBackground: null,
148
+ filters: null,
149
+ metaData: null,
150
+ actions: null,
151
+ corrupt: 0,
152
+ };
153
+
154
+ if (tag.code === PlaceObject) {
155
+ place.character = r.u16();
156
+ place.depth = r.u16();
157
+ place.matrix = readMatrix(r);
158
+ if (r.pos < r.end) {
159
+ place.colorTransform = readColorTransform(r, false);
160
+ }
161
+
162
+ return { place, clipActions: -1 };
163
+ }
164
+
165
+ const flags = r.u8();
166
+ const flags2 = tag.code === PlaceObject3 || tag.code === PlaceObject4 ? r.u8() : 0;
167
+ place.depth = r.u16();
168
+ place.move = (flags & 0x01) !== 0;
169
+
170
+ // SWF19 has a class name follow HasImage with a character; Flash reads
171
+ // one with HasImage only without a character, as Ruffle's reader notes.
172
+ const hasImage = (flags2 & 0x10) !== 0;
173
+ place.hasImage = hasImage;
174
+ if (flags2 & 0x08 || (hasImage && !(flags & 0x02))) {
175
+ place.className = readString(r);
176
+ }
177
+
178
+ if (flags & 0x02) {
179
+ place.character = r.u16();
180
+ }
181
+
182
+ if (flags & 0x04) {
183
+ place.matrix = readMatrix(r);
184
+ }
185
+
186
+ if (flags & 0x08) {
187
+ place.colorTransform = readColorTransform(r, true);
188
+ }
189
+
190
+ if (flags & 0x10) {
191
+ place.ratio = r.u16();
192
+ }
193
+
194
+ if (flags & 0x20) {
195
+ place.name = readString(r);
196
+ }
197
+
198
+ if (flags & 0x40) {
199
+ place.clipDepth = r.u16();
200
+ }
201
+
202
+ if (flags2 & 0x01) {
203
+ place.filters = readFilterBytes(r);
204
+ }
205
+
206
+ if (flags2 & 0x02) {
207
+ place.blendMode = r.u8();
208
+ }
209
+
210
+ if (flags2 & 0x04) {
211
+ place.cacheAsBitmap = r.u8() !== 0;
212
+ }
213
+
214
+ if (flags2 & 0x20) {
215
+ place.visible = r.u8() !== 0;
216
+ }
217
+
218
+ // The background follows its own flag, not HasVisible as SWF19 lays it out.
219
+ if (flags2 & 0x40) {
220
+ const a = r.u8();
221
+ const rgb = (r.u8() << 16) | (r.u8() << 8) | r.u8();
222
+ place.opaqueBackground = ((a << 24) | rgb) >>> 0;
223
+ }
224
+
225
+ const clipActions = flags & 0x80 && r.pos < r.end ? r.pos : -1;
226
+ if (tag.code === PlaceObject4) {
227
+ if (clipActions >= 0) {
228
+ // PlaceObject4 postdates SWF 6: its clip event flags are always 32 bits.
229
+ r.u16();
230
+ r.u32();
231
+ while (r.pos < r.end) {
232
+ const events = r.u32();
233
+ if (events === 0) {
234
+ break;
235
+ }
236
+
237
+ const length = r.u32();
238
+ // Flash still consumes keyPress's key code when its record says length 0.
239
+ r.pos += events & (1 << 17) ? Math.max(1, length) : length;
240
+ }
241
+ }
242
+
243
+ if (r.pos < r.end) {
244
+ place.metaData = bytes.subarray(r.pos, r.end);
245
+ }
246
+ }
247
+
248
+ return { place, clipActions };
249
+ }
250
+
251
+ /** Filter sizes by id, as FILTER's variants lay them out (6 and 7 have variable length). */
252
+ export function readFilterBytes(r: SwfReader): Uint8Array {
253
+ const start = r.pos;
254
+ const count = r.u8();
255
+
256
+ for (let i = 0; i < count && !r.overrun; i++) {
257
+ const id = r.u8();
258
+ switch (id) {
259
+ case 0:
260
+ r.pos += 23; // DropShadow
261
+ break;
262
+ case 1:
263
+ r.pos += 9; // Blur
264
+ break;
265
+ case 2:
266
+ r.pos += 15; // Glow
267
+ break;
268
+ case 3:
269
+ r.pos += 27; // Bevel
270
+ break;
271
+ case 4:
272
+ case 7: {
273
+ const stops = r.u8(); // GradientGlow, GradientBevel
274
+ r.pos += stops * 5 + 19;
275
+ break;
276
+ }
277
+ case 5: {
278
+ const x = r.u8(); // Convolution
279
+ const y = r.u8();
280
+ r.pos += 8 + x * y * 4 + 5;
281
+ break;
282
+ }
283
+ case 6:
284
+ r.pos += 80; // ColorMatrix
285
+ break;
286
+ }
287
+ }
288
+
289
+ return r.bytes.subarray(start, Math.min(r.pos, r.end));
290
+ }
291
+
292
+ /** A RemoveObject's or RemoveObject2's depth. */
293
+ export function readRemove(bytes: Uint8Array, tag: Tag): number {
294
+ const r = new SwfReader(bytes, tag.offset, tag.offset + tag.length);
295
+
296
+ if (tag.code === RemoveObject) {
297
+ r.u16();
298
+ } else if (tag.code !== RemoveObject2) {
299
+ throw new Error(`tag ${tag.code} is not a remove`);
300
+ }
301
+
302
+ return r.u16();
303
+ }
304
+
305
+ export function readFrameLabel(bytes: Uint8Array, tag: Tag): string {
306
+ return readString(new SwfReader(bytes, tag.offset, tag.offset + tag.length));
307
+ }
308
+
309
+ /** A root's scenes and frame labels, each frame counted from 0 the first. */
310
+ export interface SceneData {
311
+ scenes: { frame: number; name: string }[];
312
+ labels: { frame: number; name: string }[];
313
+ }
314
+
315
+ /** An EncodedU32: seven bits a byte, low first, while the top bit is set. */
316
+ function encodedU32(r: SwfReader): number {
317
+ let value = 0;
318
+ for (let shift = 0; shift < 35; shift += 7) {
319
+ const b = r.u8();
320
+ value += (b & 0x7f) * 2 ** shift;
321
+ if (!(b & 0x80)) {
322
+ break;
323
+ }
324
+ }
325
+
326
+ return value >>> 0;
327
+ }
328
+
329
+ /** DefineSceneAndFrameLabelData; a count past the tag's end stops where its bytes do. */
330
+ export function readSceneData(bytes: Uint8Array, tag: Tag): SceneData {
331
+ const r = new SwfReader(bytes, tag.offset, tag.offset + tag.length);
332
+ const list = () => {
333
+ const out: { frame: number; name: string }[] = [];
334
+ const count = encodedU32(r);
335
+ for (let i = 0; i < count && !r.overrun; i++) {
336
+ const frame = encodedU32(r);
337
+ const name = readString(r);
338
+ if (!r.overrun) {
339
+ out.push({ frame, name });
340
+ }
341
+ }
342
+
343
+ return out;
344
+ };
345
+ const scenes = list();
346
+
347
+ return { scenes, labels: list() };
348
+ }
349
+
350
+ /** A DefineSprite's id, frame count and its own tags. */
351
+ export function readSprite(
352
+ bytes: Uint8Array,
353
+ tag: Tag,
354
+ ): { id: number; frameCount: number; tags: Tag[] } {
355
+ const r = new SwfReader(bytes, tag.offset, tag.offset + tag.length);
356
+ const id = r.u16();
357
+ const frameCount = r.u16();
358
+ return { id, frameCount, tags: readTags(bytes, r.pos, tag.offset + tag.length).tags };
359
+ }
360
+
361
+ /** DefineScalingGrid: the sprite or button it is for, and its grid, in twips, in the character's space. */
362
+ export function readScalingGrid(bytes: Uint8Array, tag: Tag): { id: number; grid: Rect } {
363
+ const r = new SwfReader(bytes, tag.offset, tag.offset + tag.length);
364
+ const id = r.u16();
365
+ return { id, grid: r.rect() };
366
+ }
367
+
368
+ /** DefineBinaryData: a character's bytes, after its id and 4 reserved bytes, which a ByteArray subclass SymbolClass binds to it holds. */
369
+ export function readBinaryData(bytes: Uint8Array, tag: Tag): { id: number; data: Uint8Array } {
370
+ const r = new SwfReader(bytes, tag.offset, tag.offset + tag.length);
371
+ const id = r.u16();
372
+ r.u32();
373
+ return {
374
+ id,
375
+ data: bytes.subarray(Math.min(r.pos, tag.offset + tag.length), tag.offset + tag.length),
376
+ };
377
+ }
378
+
379
+ /** SymbolClass's links, including multiple classes of one character; id 0 is the document class. */
380
+ export function readSymbolClass(bytes: Uint8Array, tag: Tag): [number, string][] {
381
+ const r = new SwfReader(bytes, tag.offset, tag.offset + tag.length);
382
+ const count = r.u16();
383
+ const symbols: [number, string][] = [];
384
+
385
+ for (let i = 0; i < count && !r.overrun; i++) {
386
+ const id = r.u16();
387
+ symbols.push([id, readString(r)]);
388
+ }
389
+
390
+ return symbols;
391
+ }
392
+
393
+ /** A DoABC's or DoABC2's ABC bytes, its name, and whether it is to be run lazily (kDoAbcLazyInitializeFlag). */
394
+ export function readDoAbc(
395
+ bytes: Uint8Array,
396
+ tag: Tag,
397
+ ): { name: string; lazy: boolean; abc: Uint8Array } {
398
+ const r = new SwfReader(bytes, tag.offset, tag.offset + tag.length);
399
+ if (tag.code === DoABC) {
400
+ return { name: "", lazy: false, abc: bytes.subarray(tag.offset, tag.offset + tag.length) };
401
+ }
402
+
403
+ const flags = r.u32();
404
+ const name = readString(r);
405
+ return { name, lazy: (flags & 1) !== 0, abc: bytes.subarray(r.pos, tag.offset + tag.length) };
406
+ }
407
+
408
+ /** ExportAssets: the characters it names, each id with its name, in tag order. */
409
+ export function readExports(bytes: Uint8Array, tag: Tag): { id: number; name: string }[] {
410
+ const r = new SwfReader(bytes, tag.offset, tag.offset + tag.length);
411
+ const exports: { id: number; name: string }[] = [];
412
+ const n = r.end - r.pos >= 2 ? r.u16() : 0;
413
+ for (let i = 0; i < n && r.end - r.pos >= 2; i++) {
414
+ const id = r.u16();
415
+ exports.push({ id, name: readString(r) });
416
+ }
417
+
418
+ return exports;
419
+ }
package/src/filters.ts ADDED
@@ -0,0 +1,206 @@
1
+ // The filters a PlaceObject3 gives a display object, as the SWF stores
2
+ // them: FILTERLIST's records, with their fixed-point and float fields.
3
+ import { SwfReader } from "./swf.js";
4
+
5
+ /** A colour of a filter: RGB and alpha 0 to 255, as RGBA stores them. */
6
+ export interface FilterColor {
7
+ rgb: number;
8
+ alpha: number;
9
+ }
10
+
11
+ export type SwfFilter =
12
+ | {
13
+ type: "dropShadow";
14
+ color: FilterColor;
15
+ blurX: number;
16
+ blurY: number;
17
+ /** In radians. */
18
+ angle: number;
19
+ distance: number;
20
+ strength: number;
21
+ inner: boolean;
22
+ knockout: boolean;
23
+ /** The object drawn with its shadow: false hides it. */
24
+ composite: boolean;
25
+ passes: number;
26
+ }
27
+ | { type: "blur"; blurX: number; blurY: number; passes: number }
28
+ | {
29
+ type: "glow";
30
+ color: FilterColor;
31
+ blurX: number;
32
+ blurY: number;
33
+ strength: number;
34
+ inner: boolean;
35
+ knockout: boolean;
36
+ composite: boolean;
37
+ passes: number;
38
+ }
39
+ | {
40
+ type: "bevel" | "gradientGlow" | "gradientBevel";
41
+ /** A bevel's shadow and highlight, in that order; a gradient's stops. */
42
+ colors: FilterColor[];
43
+ ratios: number[];
44
+ blurX: number;
45
+ blurY: number;
46
+ angle: number;
47
+ distance: number;
48
+ strength: number;
49
+ inner: boolean;
50
+ knockout: boolean;
51
+ composite: boolean;
52
+ onTop: boolean;
53
+ passes: number;
54
+ }
55
+ | {
56
+ type: "convolution";
57
+ matrixX: number;
58
+ matrixY: number;
59
+ divisor: number;
60
+ bias: number;
61
+ matrix: number[];
62
+ color: FilterColor;
63
+ clamp: boolean;
64
+ preserveAlpha: boolean;
65
+ }
66
+ | { type: "colorMatrix"; matrix: number[] };
67
+
68
+ function color(r: SwfReader): FilterColor {
69
+ const red = r.u8();
70
+ const green = r.u8();
71
+ const blue = r.u8();
72
+ return { rgb: (red << 16) | (green << 8) | blue, alpha: r.u8() };
73
+ }
74
+
75
+ const fixed = (r: SwfReader) => (r.u32() | 0) / 65536;
76
+ const fixed8 = (r: SwfReader) => ((r.u16() << 16) >> 16) / 256;
77
+
78
+ function float(r: SwfReader): number {
79
+ const v = new DataView(new ArrayBuffer(4));
80
+ v.setUint32(0, r.u32());
81
+ return v.getFloat32(0);
82
+ }
83
+
84
+ /** A FILTERLIST's filters, as far as the bytes go. */
85
+ export function readFilters(bytes: Uint8Array): SwfFilter[] {
86
+ const r = new SwfReader(bytes, 0, bytes.length);
87
+ const count = r.u8();
88
+ const filters: SwfFilter[] = [];
89
+ for (let i = 0; i < count && !r.overrun; i++) {
90
+ const id = r.u8();
91
+ let filter: SwfFilter | null = null;
92
+ switch (id) {
93
+ case 0: {
94
+ const c = color(r);
95
+ const [blurX, blurY, angle, distance] = [fixed(r), fixed(r), fixed(r), fixed(r)];
96
+ const strength = fixed8(r);
97
+ const flags = r.u8();
98
+ filter = {
99
+ type: "dropShadow",
100
+ color: c,
101
+ blurX,
102
+ blurY,
103
+ angle,
104
+ distance,
105
+ strength,
106
+ inner: (flags & 0x80) !== 0,
107
+ knockout: (flags & 0x40) !== 0,
108
+ composite: (flags & 0x20) !== 0,
109
+ passes: flags & 0x1f,
110
+ };
111
+ break;
112
+ }
113
+ case 1: {
114
+ const [blurX, blurY] = [fixed(r), fixed(r)];
115
+ filter = { type: "blur", blurX, blurY, passes: r.u8() >> 3 };
116
+ break;
117
+ }
118
+ case 2: {
119
+ const c = color(r);
120
+ const [blurX, blurY] = [fixed(r), fixed(r)];
121
+ const strength = fixed8(r);
122
+ const flags = r.u8();
123
+ filter = {
124
+ type: "glow",
125
+ color: c,
126
+ blurX,
127
+ blurY,
128
+ strength,
129
+ inner: (flags & 0x80) !== 0,
130
+ knockout: (flags & 0x40) !== 0,
131
+ composite: (flags & 0x20) !== 0,
132
+ passes: flags & 0x1f,
133
+ };
134
+ break;
135
+ }
136
+ case 3:
137
+ case 4:
138
+ case 7: {
139
+ let colors: FilterColor[];
140
+ let ratios: number[];
141
+ if (id === 3) {
142
+ // The highlight, then the shadow: the SWF spec has them the other way, adl this.
143
+ const highlight = color(r);
144
+ colors = [color(r), highlight];
145
+ ratios = [];
146
+ } else {
147
+ const n = r.u8();
148
+ colors = Array.from({ length: n }, () => color(r));
149
+ ratios = Array.from({ length: n }, () => r.u8());
150
+ }
151
+
152
+ const [blurX, blurY, angle, distance] = [fixed(r), fixed(r), fixed(r), fixed(r)];
153
+ const strength = fixed8(r);
154
+ const flags = r.u8();
155
+ filter = {
156
+ type: id === 3 ? "bevel" : id === 4 ? "gradientGlow" : "gradientBevel",
157
+ colors,
158
+ ratios,
159
+ blurX,
160
+ blurY,
161
+ angle,
162
+ distance,
163
+ strength,
164
+ inner: (flags & 0x80) !== 0,
165
+ knockout: (flags & 0x40) !== 0,
166
+ composite: (flags & 0x20) !== 0,
167
+ onTop: (flags & 0x10) !== 0,
168
+ passes: flags & 0x0f,
169
+ };
170
+ break;
171
+ }
172
+ case 5: {
173
+ const matrixX = r.u8();
174
+ const matrixY = r.u8();
175
+ const divisor = float(r);
176
+ const bias = float(r);
177
+ const matrix = Array.from({ length: matrixX * matrixY }, () => float(r));
178
+ const c = color(r);
179
+ const flags = r.u8();
180
+ filter = {
181
+ type: "convolution",
182
+ matrixX,
183
+ matrixY,
184
+ divisor,
185
+ bias,
186
+ matrix,
187
+ color: c,
188
+ clamp: (flags & 0x02) !== 0,
189
+ preserveAlpha: (flags & 0x01) !== 0,
190
+ };
191
+ break;
192
+ }
193
+ case 6:
194
+ filter = { type: "colorMatrix", matrix: Array.from({ length: 20 }, () => float(r)) };
195
+ break;
196
+ }
197
+
198
+ if (!filter || r.overrun) {
199
+ break;
200
+ }
201
+
202
+ filters.push(filter);
203
+ }
204
+
205
+ return filters;
206
+ }