swf/
types.rs

1//! The data structures used in an Adobe SWF file.
2//!
3//! These structures are documented in the Adobe SWF File Format Specification
4//! version 19 (henceforth SWF19):
5//! <https://www.adobe.com/content/dam/acom/en/devnet/pdf/swf-file-format-spec.pdf>
6
7use crate::string::SwfStr;
8use bitflags::bitflags;
9use enum_map::Enum;
10use std::borrow::Cow;
11use std::fmt::{self, Display, Formatter};
12use std::num::NonZeroU8;
13use std::str::FromStr;
14
15mod bevel_filter;
16mod blur_filter;
17mod color;
18mod color_matrix_filter;
19mod color_transform;
20mod convolution_filter;
21mod drop_shadow_filter;
22mod fixed;
23mod glow_filter;
24mod gradient_filter;
25mod matrix;
26mod point;
27mod rectangle;
28mod twips;
29
30pub use bevel_filter::{BevelFilter, BevelFilterFlags};
31pub use blur_filter::{BlurFilter, BlurFilterFlags};
32pub use color::Color;
33pub use color_matrix_filter::ColorMatrixFilter;
34pub use color_transform::ColorTransform;
35pub use convolution_filter::{ConvolutionFilter, ConvolutionFilterFlags};
36pub use drop_shadow_filter::{DropShadowFilter, DropShadowFilterFlags};
37pub use fixed::{Fixed16, Fixed8};
38pub use glow_filter::{GlowFilter, GlowFilterFlags};
39pub use gradient_filter::{GradientFilter, GradientFilterFlags};
40pub use matrix::Matrix;
41pub use point::{Point, PointDelta};
42pub use rectangle::Rectangle;
43pub use twips::Twips;
44
45/// A complete header and tags in the SWF file.
46/// This is returned by the `swf::parse_swf` convenience method.
47#[derive(Debug)]
48pub struct Swf<'a> {
49    pub header: HeaderExt,
50    pub tags: Vec<Tag<'a>>,
51}
52
53/// Returned by `read::decompress_swf`.
54/// Owns the decompressed SWF data, which will be referenced when parsed by `parse_swf`.
55pub struct SwfBuf {
56    /// The parsed SWF header.
57    pub header: HeaderExt,
58
59    /// The decompressed SWF tag stream.
60    pub data: Vec<u8>,
61}
62
63/// The header of an SWF file.
64///
65/// Notably contains the compression format used by the rest of the SWF data.
66///
67/// [SWF19 p.27](https://www.adobe.com/content/dam/acom/en/devnet/pdf/swf-file-format-spec.pdf#page=27)
68#[derive(Clone, Debug, Eq, PartialEq)]
69pub struct Header {
70    pub compression: Compression,
71    pub version: u8,
72    pub stage_size: Rectangle<Twips>,
73    pub frame_rate: Fixed8,
74    pub num_frames: u16,
75}
76
77impl Header {
78    pub fn default_with_swf_version(version: u8) -> Self {
79        Self {
80            compression: Compression::None,
81            version,
82            stage_size: Default::default(),
83            frame_rate: Fixed8::ONE,
84            num_frames: 0,
85        }
86    }
87}
88
89/// The extended metadata of an SWF file.
90///
91/// This includes the SWF header data as well as metadata from the FileAttributes and
92/// SetBackgroundColor tags.
93///
94/// This metadata may not reflect the actual data inside a malformed SWF; for example,
95/// the root timeline my actually contain fewer frames than `HeaderExt::num_frames` if it is
96/// corrupted.
97#[derive(Clone, Debug)]
98pub struct HeaderExt {
99    pub(crate) header: Header,
100    pub(crate) file_attributes: FileAttributes,
101    pub(crate) background_color: Option<SetBackgroundColor>,
102    pub(crate) uncompressed_len: i32,
103}
104
105impl HeaderExt {
106    #[inline]
107    /// Returns the header for a dummy SWF file with the given SWF version.
108    pub fn default_with_swf_version(version: u8) -> Self {
109        Self {
110            header: Header::default_with_swf_version(version),
111            file_attributes: Default::default(),
112            background_color: None,
113            uncompressed_len: 0,
114        }
115    }
116
117    /// Returns the header for the error state movie stub which is used if no file
118    /// could be loaded or if the loaded content is no valid supported content.
119    pub fn default_error_header() -> Self {
120        Self {
121            header: Header::default_with_swf_version(0),
122            file_attributes: Default::default(),
123            background_color: None,
124            uncompressed_len: -1,
125        }
126    }
127
128    /// Returns the header for a loaded image (JPEG, GIF or PNG).
129    pub fn default_with_uncompressed_len(length: i32) -> Self {
130        let header = Header {
131            compression: Compression::None,
132            version: 0,
133            stage_size: Default::default(),
134            frame_rate: Fixed8::ONE,
135            num_frames: 1,
136        };
137        Self {
138            header,
139            file_attributes: Default::default(),
140            background_color: None,
141            uncompressed_len: length,
142        }
143    }
144
145    /// The background color of the SWF from the SetBackgroundColor tag.
146    ///
147    /// `None` will be returned if the SetBackgroundColor tag was not found.
148    #[inline]
149    pub fn background_color(&self) -> Option<Color> {
150        self.background_color
151    }
152
153    /// The compression format used by the SWF.
154    #[inline]
155    pub fn compression(&self) -> Compression {
156        self.header.compression
157    }
158
159    /// The frame rate of the SWF, in frames per second.
160    #[inline]
161    pub fn frame_rate(&self) -> Fixed8 {
162        self.header.frame_rate
163    }
164
165    /// Whether this SWF contains XMP metadata in a Metadata tag.
166    #[inline]
167    pub fn has_metadata(&self) -> bool {
168        self.file_attributes.contains(FileAttributes::HAS_METADATA)
169    }
170
171    /// Returns the basic SWF header.
172    #[inline]
173    pub fn swf_header(&self) -> &Header {
174        &self.header
175    }
176
177    /// Whether this SWF uses ActionScript 3.0 (AVM2).
178    #[inline]
179    pub fn is_action_script_3(&self) -> bool {
180        self.file_attributes
181            .contains(FileAttributes::IS_ACTION_SCRIPT_3)
182    }
183
184    /// The number of frames on the root timeline.
185    #[inline]
186    pub fn num_frames(&self) -> u16 {
187        self.header.num_frames
188    }
189
190    /// The stage dimensions of this SWF.
191    #[inline]
192    pub fn stage_size(&self) -> &Rectangle<Twips> {
193        &self.header.stage_size
194    }
195
196    /// The SWF version.
197    #[inline]
198    pub fn version(&self) -> u8 {
199        self.header.version
200    }
201
202    /// The length of the SWF after decompression.
203    #[inline]
204    pub fn uncompressed_len(&self) -> i32 {
205        self.uncompressed_len
206    }
207
208    /// Whether this SWF requests hardware acceleration to blit to the screen.
209    #[inline]
210    pub fn use_direct_blit(&self) -> bool {
211        self.file_attributes
212            .contains(FileAttributes::USE_DIRECT_BLIT)
213    }
214
215    /// Whether this SWF requests hardware acceleration for compositing.
216    #[inline]
217    pub fn use_gpu(&self) -> bool {
218        self.file_attributes.contains(FileAttributes::USE_GPU)
219    }
220
221    /// Whether this SWF should be placed in the network sandbox when run locally.
222    ///
223    /// SWFs in the network sandbox can only access network resources,  not local resources.
224    /// SWFs in the local sandbox can only access local resources, not network resources.
225    #[inline]
226    pub fn use_network_sandbox(&self) -> bool {
227        self.file_attributes
228            .contains(FileAttributes::USE_NETWORK_SANDBOX)
229    }
230}
231
232/// The compression format used internally by the SWF file.
233///
234/// The vast majority of SWFs will use zlib compression.
235/// [SWF19 p.27](https://www.adobe.com/content/dam/acom/en/devnet/pdf/swf-file-format-spec.pdf#page=27)
236#[derive(Clone, Copy, Debug, Eq, PartialEq)]
237pub enum Compression {
238    None,
239    Zlib,
240    Lzma,
241}
242
243#[derive(Clone, Copy, Debug, Eq, FromPrimitive, PartialEq)]
244pub enum Language {
245    Unknown = 0,
246    Latin = 1,
247    Japanese = 2,
248    Korean = 3,
249    SimplifiedChinese = 4,
250    TraditionalChinese = 5,
251}
252
253impl Language {
254    pub fn from_u8(n: u8) -> Option<Self> {
255        num_traits::FromPrimitive::from_u8(n)
256    }
257}
258
259bitflags! {
260    /// Flags that define various characteristic of an SWF file.
261    ///
262    /// [SWF19 pp.57-58 ClipEvent](https://www.adobe.com/content/dam/acom/en/devnet/pdf/swf-file-format-spec.pdf#page=47)
263    #[derive(Clone, Copy, Debug, PartialEq)]
264    pub struct FileAttributes: u8 {
265        /// Whether this SWF requests hardware acceleration to blit to the screen.
266        const USE_DIRECT_BLIT = 1 << 6;
267
268        /// Whether this SWF requests hardware acceleration for compositing.
269        const USE_GPU = 1 << 5;
270
271        /// Whether this SWF contains XMP metadata in a Metadata tag.
272        const HAS_METADATA = 1 << 4;
273
274        /// Whether this SWF uses ActionScript 3 (AVM2).
275        const IS_ACTION_SCRIPT_3 = 1 << 3;
276
277        /// Whether this SWF should be placed in the network sandbox when run locally.
278        ///
279        /// SWFs in the network sandbox can only access network resources, not local resources.
280        /// SWFs in the local sandbox can only access local resources, not network resources.
281        const USE_NETWORK_SANDBOX = 1 << 0;
282    }
283}
284
285impl Default for FileAttributes {
286    fn default() -> Self {
287        // The settings for SWF7 and earlier, which contain no FileAttributes tag.
288        Self::empty()
289    }
290}
291
292#[derive(Debug, Eq, PartialEq)]
293pub struct FrameLabel<'a> {
294    pub label: &'a SwfStr,
295    pub is_anchor: bool,
296}
297
298#[derive(Debug, Eq, PartialEq)]
299pub struct DefineSceneAndFrameLabelData<'a> {
300    pub scenes: Vec<FrameLabelData<'a>>,
301    pub frame_labels: Vec<FrameLabelData<'a>>,
302}
303
304#[derive(Debug, Eq, PartialEq)]
305pub struct FrameLabelData<'a> {
306    pub frame_num: u32,
307    pub label: &'a SwfStr,
308}
309
310pub type Depth = u16;
311pub type CharacterId = u16;
312
313#[derive(Debug, PartialEq)]
314pub struct PlaceObject<'a> {
315    pub version: u8,
316    pub action: PlaceObjectAction,
317    pub depth: Depth,
318    pub matrix: Option<Matrix>,
319    pub color_transform: Option<ColorTransform>,
320    pub ratio: Option<u16>,
321    pub name: Option<&'a SwfStr>,
322    pub clip_depth: Option<Depth>,
323    pub class_name: Option<&'a SwfStr>,
324    pub filters: Option<Vec<Filter>>,
325    pub background_color: Option<Color>,
326    pub blend_mode: Option<BlendMode>,
327    pub clip_actions: Option<Vec<ClipAction<'a>>>,
328    pub has_image: bool,
329    pub is_bitmap_cached: Option<bool>,
330    pub is_visible: Option<bool>,
331    pub amf_data: Option<&'a [u8]>,
332}
333
334bitflags! {
335    pub struct PlaceFlag: u16 {
336        const MOVE = 1 << 0;
337        const HAS_CHARACTER = 1 << 1;
338        const HAS_MATRIX = 1 << 2;
339        const HAS_COLOR_TRANSFORM = 1 << 3;
340        const HAS_RATIO = 1 << 4;
341        const HAS_NAME = 1 << 5;
342        const HAS_CLIP_DEPTH = 1 << 6;
343        const HAS_CLIP_ACTIONS = 1 << 7;
344
345        // PlaceObject3
346        const HAS_FILTER_LIST = 1 << 8;
347        const HAS_BLEND_MODE = 1 << 9;
348        const HAS_CACHE_AS_BITMAP = 1 << 10;
349        const HAS_CLASS_NAME = 1 << 11;
350        const HAS_IMAGE = 1 << 12;
351        const HAS_VISIBLE = 1 << 13;
352        const OPAQUE_BACKGROUND = 1 << 14;
353    }
354}
355
356#[derive(Clone, Copy, Debug, Eq, PartialEq)]
357pub enum PlaceObjectAction {
358    Place(CharacterId),
359    Modify,
360    Replace(CharacterId),
361}
362
363#[derive(Clone, Debug, PartialEq)]
364pub enum Filter {
365    DropShadowFilter(Box<DropShadowFilter>),
366    BlurFilter(Box<BlurFilter>),
367    GlowFilter(Box<GlowFilter>),
368    BevelFilter(Box<BevelFilter>),
369    GradientGlowFilter(Box<GradientFilter>),
370    ConvolutionFilter(Box<ConvolutionFilter>),
371    ColorMatrixFilter(Box<ColorMatrixFilter>),
372    GradientBevelFilter(Box<GradientFilter>),
373}
374
375#[derive(Default, Clone, Copy, Debug, Eq, FromPrimitive, PartialEq, Enum)]
376pub enum BlendMode {
377    #[default]
378    Normal = 0,
379    Layer = 2,
380    Multiply = 3,
381    Screen = 4,
382    Lighten = 5,
383    Darken = 6,
384    Difference = 7,
385    Add = 8,
386    Subtract = 9,
387    Invert = 10,
388    Alpha = 11,
389    Erase = 12,
390    Overlay = 13,
391    HardLight = 14,
392}
393
394impl BlendMode {
395    pub fn from_u8(n: u8) -> Option<Self> {
396        num_traits::FromPrimitive::from_u8(match n {
397            1 => 0,
398            n => n,
399        })
400    }
401}
402
403impl Display for BlendMode {
404    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
405        let s = match *self {
406            BlendMode::Normal => "normal",
407            BlendMode::Layer => "layer",
408            BlendMode::Multiply => "multiply",
409            BlendMode::Screen => "screen",
410            BlendMode::Lighten => "lighten",
411            BlendMode::Darken => "darken",
412            BlendMode::Difference => "difference",
413            BlendMode::Add => "add",
414            BlendMode::Subtract => "subtract",
415            BlendMode::Invert => "invert",
416            BlendMode::Alpha => "alpha",
417            BlendMode::Erase => "erase",
418            BlendMode::Overlay => "overlay",
419            BlendMode::HardLight => "hardlight",
420        };
421        f.write_str(s)
422    }
423}
424
425impl FromStr for BlendMode {
426    type Err = ();
427
428    fn from_str(s: &str) -> Result<Self, Self::Err> {
429        let mode = match s {
430            "normal" => BlendMode::Normal,
431            "layer" => BlendMode::Layer,
432            "multiply" => BlendMode::Multiply,
433            "screen" => BlendMode::Screen,
434            "lighten" => BlendMode::Lighten,
435            "darken" => BlendMode::Darken,
436            "difference" => BlendMode::Difference,
437            "add" => BlendMode::Add,
438            "subtract" => BlendMode::Subtract,
439            "invert" => BlendMode::Invert,
440            "alpha" => BlendMode::Alpha,
441            "erase" => BlendMode::Erase,
442            "overlay" => BlendMode::Overlay,
443            "hardlight" => BlendMode::HardLight,
444            _ => return Err(()),
445        };
446        Ok(mode)
447    }
448}
449
450/// An clip action (a.k.a. clip event) placed on a MovieClip instance.
451/// Created in the Flash IDE using `onClipEvent` or `on` blocks.
452///
453/// [SWF19 pp.37-38 ClipActionRecord](https://www.adobe.com/content/dam/acom/en/devnet/pdf/swf-file-format-spec.pdf#page=39)
454#[derive(Clone, Debug, Eq, PartialEq)]
455pub struct ClipAction<'a> {
456    pub events: ClipEventFlag,
457    pub key_code: Option<KeyCode>,
458    pub action_data: &'a [u8],
459}
460
461bitflags! {
462    /// An event that can be attached to a MovieClip instance using an `onClipEvent` or `on` block.
463    ///
464    /// [SWF19 pp.48-50 ClipEvent](https://www.adobe.com/content/dam/acom/en/devnet/pdf/swf-file-format-spec.pdf#page=50)
465    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
466    pub struct ClipEventFlag: u32 {
467        const LOAD            = 1 << 0;
468        const ENTER_FRAME     = 1 << 1;
469        const UNLOAD          = 1 << 2;
470        const MOUSE_MOVE      = 1 << 3;
471        const MOUSE_DOWN      = 1 << 4;
472        const MOUSE_UP        = 1 << 5;
473        const KEY_DOWN        = 1 << 6;
474        const KEY_UP          = 1 << 7;
475
476        // Added in SWF6, but not version-gated.
477        const DATA            = 1 << 8;
478        const INITIALIZE      = 1 << 9;
479        const PRESS           = 1 << 10;
480        const RELEASE         = 1 << 11;
481        const RELEASE_OUTSIDE = 1 << 12;
482        const ROLL_OVER       = 1 << 13;
483        const ROLL_OUT        = 1 << 14;
484        const DRAG_OVER       = 1 << 15;
485        const DRAG_OUT        = 1 << 16;
486        const KEY_PRESS       = 1 << 17;
487
488        // Construct was only added in SWF7, but it's not version-gated;
489        // Construct events will still fire in SWF6 in a v7+ player (#1424).
490        const CONSTRUCT       = 1 << 18;
491    }
492}
493
494/// A key code used in `ButtonAction` and `ClipAction` key press events.
495pub type KeyCode = u8;
496
497/// Represents a tag in an SWF file.
498///
499/// The SWF format is made up of a stream of tags. Each tag either
500/// defines a character (Graphic, Sound, MovieClip), or places/modifies
501/// an instance of these characters on the display list.
502///
503// [SWF19 p.29](https://www.adobe.com/content/dam/acom/en/devnet/pdf/swf-file-format-spec.pdf#page=29)
504#[derive(Debug, PartialEq)]
505pub enum Tag<'a> {
506    ExportAssets(ExportAssets<'a>),
507    ScriptLimits {
508        max_recursion_depth: u16,
509        timeout_in_seconds: u16,
510    },
511    ShowFrame,
512
513    Protect(Option<&'a SwfStr>),
514    CsmTextSettings(CsmTextSettings),
515    DebugId(DebugId),
516    DefineBinaryData(DefineBinaryData<'a>),
517    DefineBits {
518        id: CharacterId,
519        jpeg_data: &'a [u8],
520    },
521    DefineBitsJpeg2 {
522        id: CharacterId,
523        jpeg_data: &'a [u8],
524    },
525    DefineBitsJpeg3(DefineBitsJpeg3<'a>),
526    DefineBitsLossless(DefineBitsLossless<'a>),
527    DefineButton(Box<Button<'a>>),
528    DefineButton2(Box<Button<'a>>),
529    DefineButtonColorTransform(ButtonColorTransform),
530    DefineButtonSound(Box<ButtonSounds>),
531    DefineEditText(Box<EditText<'a>>),
532    DefineFont(Box<FontV1>),
533    DefineFont2(Box<Font<'a>>),
534    DefineFont4(Font4<'a>),
535    DefineFontAlignZones {
536        id: CharacterId,
537        thickness: FontThickness,
538        zones: Vec<FontAlignZone>,
539    },
540    DefineFontInfo(Box<FontInfo<'a>>),
541    DefineFontName {
542        id: CharacterId,
543        name: &'a SwfStr,
544        copyright_info: &'a SwfStr,
545    },
546    DefineMorphShape(Box<DefineMorphShape>),
547    DefineScalingGrid {
548        id: CharacterId,
549        splitter_rect: Rectangle<Twips>,
550    },
551    DefineShape(Shape),
552    DefineSound(Box<Sound<'a>>),
553    DefineSprite(Sprite<'a>),
554    DefineText(Box<Text>),
555    DefineText2(Box<Text>),
556    DefineVideoStream(DefineVideoStream),
557    DoAbc(&'a [u8]),
558    DoAbc2(DoAbc2<'a>),
559    DoAction(DoAction<'a>),
560    DoInitAction {
561        id: CharacterId,
562        action_data: &'a [u8],
563    },
564    EnableDebugger(&'a SwfStr),
565    EnableTelemetry {
566        password_hash: &'a [u8],
567    },
568    End,
569    Metadata(&'a SwfStr),
570    ImportAssets {
571        url: &'a SwfStr,
572        imports: Vec<ExportedAsset<'a>>,
573    },
574    JpegTables(JpegTables<'a>),
575    NameCharacter(NameCharacter<'a>),
576    SetBackgroundColor(SetBackgroundColor),
577    SetTabIndex {
578        depth: Depth,
579        tab_index: u16,
580    },
581    SoundStreamBlock(SoundStreamBlock<'a>),
582    SoundStreamHead(Box<SoundStreamHead>),
583    SoundStreamHead2(Box<SoundStreamHead>),
584    StartSound(StartSound),
585    StartSound2 {
586        class_name: &'a SwfStr,
587        sound_info: Box<SoundInfo>,
588    },
589    SymbolClass(Vec<SymbolClassLink<'a>>),
590    PlaceObject(Box<PlaceObject<'a>>),
591    RemoveObject(RemoveObject),
592    VideoFrame(VideoFrame<'a>),
593    FileAttributes(FileAttributes),
594
595    FrameLabel(FrameLabel<'a>),
596    DefineSceneAndFrameLabelData(DefineSceneAndFrameLabelData<'a>),
597
598    ProductInfo(ProductInfo),
599
600    Unknown {
601        tag_code: u16,
602        data: &'a [u8],
603    },
604}
605
606pub type ExportAssets<'a> = Vec<ExportedAsset<'a>>;
607
608#[derive(Clone, Debug, Eq, PartialEq)]
609pub struct ExportedAsset<'a> {
610    pub id: CharacterId,
611    pub name: &'a SwfStr,
612}
613
614#[derive(Clone, Debug, Eq, PartialEq)]
615pub struct RemoveObject {
616    pub depth: Depth,
617    pub character_id: Option<CharacterId>,
618}
619
620pub type SetBackgroundColor = Color;
621
622#[derive(Clone, Debug, Eq, PartialEq)]
623pub struct SymbolClassLink<'a> {
624    pub id: CharacterId,
625    pub class_name: &'a SwfStr,
626}
627
628#[derive(Clone, Debug, Eq, PartialEq)]
629pub struct ShapeContext {
630    pub swf_version: u8,
631    pub shape_version: u8,
632    pub num_fill_bits: u8,
633    pub num_line_bits: u8,
634}
635
636#[derive(Clone, Debug, Eq, PartialEq)]
637pub struct Shape {
638    pub version: u8,
639    pub id: CharacterId,
640    pub shape_bounds: Rectangle<Twips>,
641    pub edge_bounds: Rectangle<Twips>,
642    pub flags: ShapeFlag,
643    pub styles: ShapeStyles,
644    pub shape: Vec<ShapeRecord>,
645}
646
647bitflags! {
648    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
649    pub struct ShapeFlag: u8 {
650        const HAS_SCALING_STROKES     = 1 << 0;
651        const HAS_NON_SCALING_STROKES = 1 << 1;
652        const NON_ZERO_WINDING_RULE   = 1 << 2;
653    }
654}
655
656#[derive(Clone, Debug, Eq, PartialEq)]
657pub struct Sound<'a> {
658    pub id: CharacterId,
659    pub format: SoundFormat,
660    pub num_samples: u32,
661    pub data: &'a [u8],
662}
663
664#[derive(Clone, Debug, PartialEq)]
665pub struct SoundInfo {
666    pub event: SoundEvent,
667    pub in_sample: Option<u32>,
668    pub out_sample: Option<u32>,
669    pub num_loops: u16,
670    pub envelope: Option<SoundEnvelope>,
671}
672
673#[derive(Clone, Copy, Debug, Eq, FromPrimitive, PartialEq)]
674pub enum SoundEvent {
675    Event = 0,
676    Start = 1,
677    Stop = 2,
678}
679
680impl SoundEvent {
681    pub fn from_u8(n: u8) -> Option<Self> {
682        num_traits::FromPrimitive::from_u8(match n {
683            3 => 2,
684            n => n,
685        })
686    }
687}
688
689pub type SoundEnvelope = Vec<SoundEnvelopePoint>;
690
691#[derive(Clone, Debug, PartialEq)]
692pub struct SoundEnvelopePoint {
693    pub sample: u32,
694    pub left_volume: f32,
695    pub right_volume: f32,
696}
697
698#[derive(Clone, Debug, PartialEq)]
699pub struct StartSound {
700    pub id: CharacterId,
701    pub sound_info: Box<SoundInfo>,
702}
703
704#[derive(Debug, PartialEq)]
705pub struct Sprite<'a> {
706    pub id: CharacterId,
707    pub num_frames: u16,
708    pub tags: Vec<Tag<'a>>,
709}
710
711#[derive(Clone, Debug, Eq, PartialEq)]
712pub struct ShapeStyles {
713    pub fill_styles: Vec<FillStyle>,
714    pub line_styles: Vec<LineStyle>,
715}
716
717#[derive(Clone, Debug, Eq, PartialEq)]
718pub enum ShapeRecord {
719    StyleChange(Box<StyleChangeData>),
720    StraightEdge {
721        delta: PointDelta<Twips>,
722    },
723    CurvedEdge {
724        control_delta: PointDelta<Twips>,
725        anchor_delta: PointDelta<Twips>,
726    },
727}
728
729bitflags! {
730    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
731    pub struct ShapeRecordFlag: u8 {
732        const MOVE_TO      = 1 << 0;
733        const FILL_STYLE_0 = 1 << 1;
734        const FILL_STYLE_1 = 1 << 2;
735        const LINE_STYLE   = 1 << 3;
736        const NEW_STYLES   = 1 << 4;
737    }
738}
739
740#[derive(Clone, Debug, Eq, PartialEq)]
741pub struct StyleChangeData {
742    pub move_to: Option<Point<Twips>>,
743    pub fill_style_0: Option<u32>,
744    pub fill_style_1: Option<u32>,
745    pub line_style: Option<u32>,
746    pub new_styles: Option<ShapeStyles>,
747}
748
749#[derive(Clone, Debug, Eq, PartialEq)]
750pub enum FillStyle {
751    Color(Color),
752    LinearGradient(Gradient),
753    RadialGradient(Gradient),
754    FocalGradient {
755        gradient: Gradient,
756        focal_point: Fixed8,
757    },
758    Bitmap {
759        id: CharacterId,
760        matrix: Matrix,
761        is_smoothed: bool,
762        is_repeating: bool,
763    },
764}
765
766#[derive(Clone, Debug, Eq, PartialEq, Hash)]
767pub struct Gradient {
768    pub matrix: Matrix,
769    pub spread: GradientSpread,
770    pub interpolation: GradientInterpolation,
771    pub records: Vec<GradientRecord>,
772}
773
774#[derive(Clone, Copy, Debug, Eq, FromPrimitive, PartialEq, Enum, Hash)]
775pub enum GradientSpread {
776    Pad = 0,
777    Reflect = 1,
778    Repeat = 2,
779}
780
781impl GradientSpread {
782    pub fn from_u8(n: u8) -> Option<Self> {
783        num_traits::FromPrimitive::from_u8(match n {
784            // Per SWF19 p. 136, SpreadMode 3 is reserved.
785            // Flash treats it as pad mode.
786            3 => 0,
787            n => n,
788        })
789    }
790}
791
792#[derive(Clone, Copy, Debug, Eq, FromPrimitive, PartialEq, Hash)]
793pub enum GradientInterpolation {
794    Rgb = 0,
795    LinearRgb = 1,
796}
797
798impl GradientInterpolation {
799    pub fn from_u8(n: u8) -> Option<Self> {
800        num_traits::FromPrimitive::from_u8(match n {
801            // Per SWF19 p. 136, InterpolationMode 2 and 3 are reserved.
802            // Flash treats them as normal RGB mode interpolation.
803            2 | 3 => 0,
804            n => n,
805        })
806    }
807}
808
809#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Hash)]
810pub struct GradientRecord {
811    pub ratio: u8,
812    pub color: Color,
813}
814
815#[derive(Clone, Debug, Eq, PartialEq)]
816pub struct LineStyle {
817    pub(crate) width: Twips,
818    pub(crate) fill_style: FillStyle,
819    pub(crate) flags: LineStyleFlag,
820    pub(crate) miter_limit: Fixed8,
821}
822
823impl LineStyle {
824    #[inline]
825    pub fn new() -> LineStyle {
826        Default::default()
827    }
828
829    #[inline]
830    pub fn allow_close(&self) -> bool {
831        !self.flags.contains(LineStyleFlag::NO_CLOSE)
832    }
833
834    #[inline]
835    pub fn with_allow_close(mut self, val: bool) -> Self {
836        self.flags.set(LineStyleFlag::NO_CLOSE, !val);
837        self
838    }
839
840    #[inline]
841    pub fn allow_scale_x(&self) -> bool {
842        !self.flags.contains(LineStyleFlag::NO_H_SCALE)
843    }
844
845    #[inline]
846    pub fn with_allow_scale_x(mut self, val: bool) -> Self {
847        self.flags.set(LineStyleFlag::NO_H_SCALE, !val);
848        self
849    }
850
851    #[inline]
852    pub fn allow_scale_y(&self) -> bool {
853        !self.flags.contains(LineStyleFlag::NO_V_SCALE)
854    }
855
856    #[inline]
857    pub fn with_allow_scale_y(mut self, val: bool) -> Self {
858        self.flags.set(LineStyleFlag::NO_V_SCALE, !val);
859        self
860    }
861
862    #[inline]
863    pub fn is_pixel_hinted(&self) -> bool {
864        self.flags.contains(LineStyleFlag::PIXEL_HINTING)
865    }
866
867    #[inline]
868    pub fn with_is_pixel_hinted(mut self, val: bool) -> Self {
869        self.flags.set(LineStyleFlag::PIXEL_HINTING, val);
870        self
871    }
872
873    #[inline]
874    pub fn start_cap(&self) -> LineCapStyle {
875        let cap = (self.flags & LineStyleFlag::START_CAP_STYLE).bits() >> 6;
876        LineCapStyle::from_u8(cap as u8).unwrap()
877    }
878
879    #[inline]
880    pub fn with_start_cap(mut self, val: LineCapStyle) -> Self {
881        self.flags -= LineStyleFlag::START_CAP_STYLE;
882        self.flags |= LineStyleFlag::from_bits_retain((val as u16) << 6);
883        self
884    }
885
886    #[inline]
887    pub fn end_cap(&self) -> LineCapStyle {
888        let cap = (self.flags & LineStyleFlag::END_CAP_STYLE).bits() >> 8;
889        LineCapStyle::from_u8(cap as u8).unwrap()
890    }
891
892    #[inline]
893    pub fn with_end_cap(mut self, val: LineCapStyle) -> Self {
894        self.flags -= LineStyleFlag::END_CAP_STYLE;
895        self.flags |= LineStyleFlag::from_bits_retain((val as u16) << 8);
896        self
897    }
898
899    #[inline]
900    pub fn join_style(&self) -> LineJoinStyle {
901        match self.flags & LineStyleFlag::JOIN_STYLE {
902            LineStyleFlag::ROUND => LineJoinStyle::Round,
903            LineStyleFlag::BEVEL => LineJoinStyle::Bevel,
904            LineStyleFlag::MITER => LineJoinStyle::Miter(self.miter_limit),
905            _ => unreachable!(),
906        }
907    }
908
909    #[inline]
910    pub fn with_join_style(mut self, val: LineJoinStyle) -> Self {
911        self.flags -= LineStyleFlag::JOIN_STYLE;
912        self.flags |= match val {
913            LineJoinStyle::Round => LineStyleFlag::ROUND,
914            LineJoinStyle::Bevel => LineStyleFlag::BEVEL,
915            LineJoinStyle::Miter(miter_limit) => {
916                self.miter_limit = miter_limit;
917                LineStyleFlag::MITER
918            }
919        };
920        self
921    }
922
923    #[inline]
924    pub fn fill_style(&self) -> &FillStyle {
925        &self.fill_style
926    }
927
928    #[inline]
929    pub fn with_fill_style(mut self, val: FillStyle) -> Self {
930        self.flags
931            .set(LineStyleFlag::HAS_FILL, !matches!(val, FillStyle::Color(_)));
932        self.fill_style = val;
933        self
934    }
935
936    #[inline]
937    pub fn with_color(mut self, val: Color) -> Self {
938        self.flags.remove(LineStyleFlag::HAS_FILL);
939        self.fill_style = FillStyle::Color(val);
940        self
941    }
942
943    #[inline]
944    pub fn width(&self) -> Twips {
945        self.width
946    }
947
948    #[inline]
949    pub fn with_width(mut self, val: Twips) -> Self {
950        self.width = val;
951        self
952    }
953}
954
955impl Default for LineStyle {
956    #[inline]
957    fn default() -> Self {
958        // Hairline black stroke.
959        Self {
960            width: Twips::ZERO,
961            fill_style: FillStyle::Color(Color::BLACK),
962            flags: Default::default(),
963            miter_limit: Default::default(),
964        }
965    }
966}
967
968bitflags! {
969    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
970    pub struct LineStyleFlag: u16 {
971        // First byte.
972        const PIXEL_HINTING = 1 << 0;
973        const NO_V_SCALE = 1 << 1;
974        const NO_H_SCALE = 1 << 2;
975        const HAS_FILL = 1 << 3;
976        const JOIN_STYLE = 0b11 << 4;
977        const START_CAP_STYLE = 0b11 << 6;
978
979        // Second byte.
980        const END_CAP_STYLE = 0b11 << 8;
981        const NO_CLOSE = 1 << 10;
982
983        // JOIN_STYLE mask values.
984        const ROUND = 0b00 << 4;
985        const BEVEL = 0b01 << 4;
986        const MITER = 0b10 << 4;
987    }
988}
989
990impl Default for LineStyleFlag {
991    #[inline]
992    fn default() -> Self {
993        LineStyleFlag::empty()
994    }
995}
996
997#[derive(Default, Clone, Copy, Debug, Eq, FromPrimitive, PartialEq)]
998pub enum LineCapStyle {
999    #[default]
1000    Round = 0,
1001    None = 1,
1002    Square = 2,
1003}
1004
1005impl LineCapStyle {
1006    #[inline]
1007    pub fn from_u8(n: u8) -> Option<Self> {
1008        num_traits::FromPrimitive::from_u8(n)
1009    }
1010}
1011
1012#[derive(Default, Clone, Copy, Debug, Eq, PartialEq)]
1013pub enum LineJoinStyle {
1014    #[default]
1015    Round,
1016    Bevel,
1017    Miter(Fixed8),
1018}
1019
1020#[derive(Clone, Copy, Debug, Eq, FromPrimitive, PartialEq)]
1021pub enum AudioCompression {
1022    UncompressedUnknownEndian = 0,
1023    Adpcm = 1,
1024    Mp3 = 2,
1025    Uncompressed = 3,
1026    Nellymoser16Khz = 4,
1027    Nellymoser8Khz = 5,
1028    Nellymoser = 6,
1029    Aac = 10,
1030    Speex = 11,
1031}
1032
1033impl AudioCompression {
1034    pub fn from_u8(n: u8) -> Option<Self> {
1035        num_traits::FromPrimitive::from_u8(n)
1036    }
1037}
1038
1039#[derive(Clone, Debug, Eq, PartialEq)]
1040pub struct SoundFormat {
1041    pub compression: AudioCompression,
1042    pub sample_rate: u16,
1043    pub is_stereo: bool,
1044    pub is_16_bit: bool,
1045}
1046
1047#[derive(Clone, Debug, Eq, PartialEq)]
1048pub struct SoundStreamHead {
1049    pub stream_format: SoundFormat,
1050    pub playback_format: SoundFormat,
1051    pub num_samples_per_block: u16,
1052    pub latency_seek: i16,
1053}
1054
1055pub type SoundStreamBlock<'a> = &'a [u8];
1056
1057#[derive(Clone, Debug, PartialEq)]
1058pub struct Button<'a> {
1059    pub id: CharacterId,
1060    pub is_track_as_menu: bool,
1061    pub records: Vec<ButtonRecord>,
1062    pub actions: Vec<ButtonAction<'a>>,
1063}
1064
1065#[derive(Clone, Debug, PartialEq)]
1066pub struct ButtonRecord {
1067    pub states: ButtonState,
1068    pub id: CharacterId,
1069    pub depth: Depth,
1070    pub matrix: Matrix,
1071    pub color_transform: ColorTransform,
1072    pub filters: Vec<Filter>,
1073    pub blend_mode: BlendMode,
1074}
1075
1076bitflags! {
1077    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
1078    pub struct ButtonState: u8 {
1079        const UP       = 1 << 0;
1080        const OVER     = 1 << 1;
1081        const DOWN     = 1 << 2;
1082        const HIT_TEST = 1 << 3;
1083    }
1084}
1085
1086#[derive(Clone, Debug, Eq, PartialEq)]
1087pub struct ButtonColorTransform {
1088    pub id: CharacterId,
1089    pub color_transforms: Vec<ColorTransform>,
1090}
1091
1092#[derive(Clone, Debug, PartialEq)]
1093pub struct ButtonSounds {
1094    pub id: CharacterId,
1095    pub over_to_up_sound: Option<ButtonSound>,
1096    pub up_to_over_sound: Option<ButtonSound>,
1097    pub over_to_down_sound: Option<ButtonSound>,
1098    pub down_to_over_sound: Option<ButtonSound>,
1099}
1100
1101pub type ButtonSound = (CharacterId, SoundInfo);
1102
1103#[derive(Clone, Debug, Eq, PartialEq)]
1104pub struct ButtonAction<'a> {
1105    pub conditions: ButtonActionCondition,
1106    pub action_data: &'a [u8],
1107}
1108
1109impl ButtonAction<'_> {
1110    pub fn key_press(&self) -> Option<NonZeroU8> {
1111        NonZeroU8::new(((self.conditions & ButtonActionCondition::KEY_PRESS).bits() >> 9) as u8)
1112    }
1113}
1114
1115bitflags! {
1116    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
1117    pub struct ButtonActionCondition: u16 {
1118        const IDLE_TO_OVER_UP       = 1 << 0;
1119        const OVER_UP_TO_IDLE       = 1 << 1;
1120        const OVER_UP_TO_OVER_DOWN  = 1 << 2;
1121        const OVER_DOWN_TO_OVER_UP  = 1 << 3;
1122        const OVER_DOWN_TO_OUT_DOWN = 1 << 4;
1123        const OUT_DOWN_TO_OVER_DOWN = 1 << 5;
1124        const OUT_DOWN_TO_IDLE      = 1 << 6;
1125        const IDLE_TO_OVER_DOWN     = 1 << 7;
1126        const OVER_DOWN_TO_IDLE     = 1 << 8;
1127        const KEY_PRESS             = 0b1111111 << 9;
1128    }
1129}
1130
1131impl ButtonActionCondition {
1132    #[inline]
1133    pub fn from_key_code(key_code: u8) -> Self {
1134        Self::from_bits_retain((key_code as u16) << 9)
1135    }
1136
1137    /// Checks if the given test condition matches any of the conditions defined in self
1138    #[inline]
1139    pub fn matches(self, test_condition: ButtonActionCondition) -> bool {
1140        let self_key_press = (self & Self::KEY_PRESS).bits();
1141        let test_key_press = (test_condition & Self::KEY_PRESS).bits();
1142        let self_without_key = self & !Self::KEY_PRESS;
1143        let test_without_key = test_condition & !Self::KEY_PRESS;
1144
1145        self_without_key.contains(test_without_key)
1146            && (test_key_press == 0 || test_key_press == self_key_press)
1147    }
1148}
1149
1150#[derive(Clone, Debug, Eq, PartialEq)]
1151pub struct DefineMorphShape {
1152    pub version: u8,
1153    pub id: CharacterId,
1154    pub flags: DefineMorphShapeFlag,
1155    pub start: MorphShape,
1156    pub end: MorphShape,
1157}
1158
1159bitflags! {
1160    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
1161    pub struct DefineMorphShapeFlag: u8 {
1162        const HAS_SCALING_STROKES     = 1 << 0;
1163        const HAS_NON_SCALING_STROKES = 1 << 1;
1164    }
1165}
1166
1167#[derive(Clone, Debug, Eq, PartialEq)]
1168pub struct MorphShape {
1169    pub shape_bounds: Rectangle<Twips>,
1170    pub edge_bounds: Rectangle<Twips>,
1171    pub fill_styles: Vec<FillStyle>,
1172    pub line_styles: Vec<LineStyle>,
1173    pub shape: Vec<ShapeRecord>,
1174}
1175
1176#[derive(Clone, Debug, Eq, PartialEq)]
1177pub struct FontV1 {
1178    pub id: CharacterId,
1179    pub glyphs: Vec<Vec<ShapeRecord>>,
1180}
1181
1182#[derive(Clone, Debug, Eq, PartialEq)]
1183pub struct Font<'a> {
1184    pub version: u8,
1185    pub id: CharacterId,
1186    pub name: &'a SwfStr,
1187    pub language: Language,
1188    pub layout: Option<FontLayout>,
1189    pub glyphs: Vec<Glyph>,
1190    pub flags: FontFlag,
1191}
1192
1193bitflags! {
1194    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
1195    pub struct FontFlag: u8 {
1196        const IS_BOLD = 1 << 0;
1197        const IS_ITALIC = 1 << 1;
1198        const HAS_WIDE_CODES = 1 << 2;
1199        const HAS_WIDE_OFFSETS = 1 << 3;
1200        const IS_ANSI = 1 << 4;
1201        const IS_SMALL_TEXT = 1 << 5;
1202        const IS_SHIFT_JIS = 1 << 6;
1203        const HAS_LAYOUT = 1 << 7;
1204    }
1205}
1206
1207#[derive(Clone, Debug, Eq, PartialEq)]
1208pub struct Font4<'a> {
1209    pub id: CharacterId,
1210    pub is_italic: bool,
1211    pub is_bold: bool,
1212    pub name: &'a SwfStr,
1213    pub data: Option<&'a [u8]>,
1214}
1215
1216#[derive(Clone, Debug, Eq, PartialEq)]
1217pub struct Glyph {
1218    pub shape_records: Vec<ShapeRecord>,
1219    pub code: u16,
1220    pub advance: i16,
1221    pub bounds: Option<Rectangle<Twips>>,
1222}
1223
1224#[derive(Clone, Debug, Eq, PartialEq)]
1225pub struct FontLayout {
1226    pub ascent: u16,
1227    pub descent: u16,
1228    pub leading: i16,
1229    pub kerning: Vec<KerningRecord>,
1230}
1231
1232#[derive(Clone, Debug, Eq, PartialEq)]
1233pub struct KerningRecord {
1234    pub left_code: u16,
1235    pub right_code: u16,
1236    pub adjustment: Twips,
1237}
1238
1239#[derive(Clone, Debug, Eq, PartialEq)]
1240pub struct FontInfo<'a> {
1241    pub id: CharacterId,
1242    pub version: u8,
1243    pub name: &'a SwfStr,
1244    pub flags: FontInfoFlag,
1245    pub language: Language,
1246    pub code_table: Vec<u16>,
1247}
1248
1249bitflags! {
1250    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
1251    pub struct FontInfoFlag: u8 {
1252        const HAS_WIDE_CODES = 1 << 0;
1253        const IS_BOLD = 1 << 1;
1254        const IS_ITALIC = 1 << 2;
1255        const IS_SHIFT_JIS = 1 << 3;
1256        const IS_ANSI = 1 << 4;
1257        const IS_SMALL_TEXT = 1 << 5;
1258    }
1259}
1260
1261#[derive(Clone, Debug, Eq, PartialEq)]
1262pub struct DefineBinaryData<'a> {
1263    pub id: CharacterId,
1264    pub data: &'a [u8],
1265}
1266
1267#[derive(Clone, Debug, Eq, PartialEq)]
1268pub struct Text {
1269    pub id: CharacterId,
1270    pub bounds: Rectangle<Twips>,
1271    pub matrix: Matrix,
1272    pub records: Vec<TextRecord>,
1273}
1274
1275#[derive(Clone, Debug, Eq, PartialEq)]
1276pub struct TextRecord {
1277    pub font_id: Option<CharacterId>,
1278    pub color: Option<Color>,
1279    pub x_offset: Option<Twips>,
1280    pub y_offset: Option<Twips>,
1281    pub height: Option<Twips>,
1282    pub glyphs: Vec<GlyphEntry>,
1283}
1284
1285#[derive(Clone, Debug, Eq, PartialEq)]
1286pub struct GlyphEntry {
1287    pub index: u32,
1288    pub advance: i32,
1289}
1290
1291#[derive(Clone, Debug, Eq, PartialEq)]
1292pub struct EditText<'a> {
1293    pub(crate) id: CharacterId,
1294    pub(crate) bounds: Rectangle<Twips>,
1295    pub(crate) font_id: CharacterId,
1296    pub(crate) font_class: &'a SwfStr,
1297    pub(crate) height: Twips,
1298    pub(crate) color: Color,
1299    pub(crate) max_length: u16,
1300    pub(crate) layout: TextLayout,
1301    pub(crate) variable_name: &'a SwfStr,
1302    pub(crate) initial_text: &'a SwfStr,
1303    pub(crate) flags: EditTextFlag,
1304}
1305
1306impl<'a> EditText<'a> {
1307    #[inline]
1308    pub fn new() -> Self {
1309        Default::default()
1310    }
1311
1312    #[inline]
1313    pub fn bounds(&self) -> &Rectangle<Twips> {
1314        &self.bounds
1315    }
1316
1317    #[inline]
1318    pub fn with_bounds(mut self, bounds: Rectangle<Twips>) -> Self {
1319        self.bounds = bounds;
1320        self
1321    }
1322
1323    #[inline]
1324    pub fn id(&self) -> CharacterId {
1325        self.id
1326    }
1327
1328    #[inline]
1329    pub fn with_id(mut self, id: CharacterId) -> Self {
1330        self.id = id;
1331        self
1332    }
1333
1334    #[inline]
1335    pub fn font_id(&self) -> Option<CharacterId> {
1336        self.flags
1337            .contains(EditTextFlag::HAS_FONT)
1338            .then_some(self.font_id)
1339    }
1340
1341    #[inline]
1342    pub fn font_class(&self) -> Option<&'a SwfStr> {
1343        self.flags
1344            .contains(EditTextFlag::HAS_FONT_CLASS)
1345            .then_some(self.font_class)
1346    }
1347
1348    // The height of the font in twips.
1349    #[inline]
1350    pub fn height(&self) -> Option<Twips> {
1351        self.flags
1352            .intersects(EditTextFlag::HAS_FONT | EditTextFlag::HAS_FONT_CLASS)
1353            .then_some(self.height)
1354    }
1355
1356    #[inline]
1357    pub fn with_default_font(mut self) -> Self {
1358        self.flags -= EditTextFlag::HAS_FONT | EditTextFlag::HAS_FONT_CLASS;
1359        self
1360    }
1361
1362    #[inline]
1363    pub fn with_font_id(mut self, font_id: CharacterId, height: Twips) -> Self {
1364        self.flags |= EditTextFlag::HAS_FONT;
1365        self.flags -= EditTextFlag::HAS_FONT_CLASS;
1366        self.font_id = font_id;
1367        self.height = height;
1368        self
1369    }
1370
1371    #[inline]
1372    pub fn with_font_class(mut self, font_class: &'a SwfStr, height: Twips) -> Self {
1373        self.flags |= EditTextFlag::HAS_FONT_CLASS;
1374        self.flags -= EditTextFlag::HAS_FONT;
1375        self.font_class = font_class;
1376        self.height = height;
1377        self
1378    }
1379
1380    #[inline]
1381    pub fn color(&self) -> Option<&Color> {
1382        self.flags
1383            .contains(EditTextFlag::HAS_TEXT_COLOR)
1384            .then_some(&self.color)
1385    }
1386
1387    #[inline]
1388    pub fn with_color(mut self, color: Option<Color>) -> Self {
1389        if let Some(color) = color {
1390            self.flags |= EditTextFlag::HAS_TEXT_COLOR;
1391            self.color = color;
1392        } else {
1393            self.flags -= EditTextFlag::HAS_TEXT_COLOR;
1394        }
1395        self
1396    }
1397
1398    #[inline]
1399    pub fn max_length(&self) -> Option<u16> {
1400        self.flags
1401            .contains(EditTextFlag::HAS_MAX_LENGTH)
1402            .then_some(self.max_length)
1403    }
1404
1405    #[inline]
1406    pub fn with_max_length(mut self, max_length: Option<u16>) -> Self {
1407        if let Some(max_length) = max_length {
1408            self.flags |= EditTextFlag::HAS_MAX_LENGTH;
1409            self.max_length = max_length;
1410        } else {
1411            self.flags -= EditTextFlag::HAS_MAX_LENGTH;
1412        }
1413        self
1414    }
1415
1416    #[inline]
1417    pub fn layout(&self) -> Option<&TextLayout> {
1418        self.flags
1419            .contains(EditTextFlag::HAS_LAYOUT)
1420            .then_some(&self.layout)
1421    }
1422
1423    #[inline]
1424    pub fn with_layout(mut self, layout: Option<TextLayout>) -> Self {
1425        if let Some(layout) = layout {
1426            self.flags |= EditTextFlag::HAS_LAYOUT;
1427            self.layout = layout;
1428        } else {
1429            self.flags -= EditTextFlag::HAS_LAYOUT;
1430        }
1431        self
1432    }
1433
1434    #[inline]
1435    pub fn variable_name(&self) -> &'a SwfStr {
1436        self.variable_name
1437    }
1438
1439    #[inline]
1440    pub fn with_variable_name(mut self, variable_name: &'a SwfStr) -> Self {
1441        self.variable_name = variable_name;
1442        self
1443    }
1444
1445    #[inline]
1446    pub fn initial_text(&self) -> Option<&'a SwfStr> {
1447        self.flags
1448            .contains(EditTextFlag::HAS_TEXT)
1449            .then_some(self.initial_text)
1450    }
1451
1452    #[inline]
1453    pub fn with_initial_text(mut self, initial_text: Option<&'a SwfStr>) -> Self {
1454        if let Some(initial_text) = initial_text {
1455            self.flags |= EditTextFlag::HAS_TEXT;
1456            self.initial_text = initial_text;
1457        } else {
1458            self.flags -= EditTextFlag::HAS_TEXT;
1459        }
1460        self
1461    }
1462
1463    #[inline]
1464    pub fn flags(&self) -> EditTextFlag {
1465        self.flags
1466    }
1467
1468    #[inline]
1469    pub fn is_auto_size(&self) -> bool {
1470        self.flags.contains(EditTextFlag::AUTO_SIZE)
1471    }
1472
1473    #[inline]
1474    pub fn with_is_auto_size(mut self, value: bool) -> Self {
1475        self.flags.set(EditTextFlag::AUTO_SIZE, value);
1476        self
1477    }
1478
1479    #[inline]
1480    pub fn use_outlines(&self) -> bool {
1481        self.flags.contains(EditTextFlag::USE_OUTLINES)
1482    }
1483
1484    #[inline]
1485    pub fn with_use_outlines(mut self, value: bool) -> Self {
1486        self.flags.set(EditTextFlag::USE_OUTLINES, value);
1487        self
1488    }
1489
1490    #[inline]
1491    pub fn has_border(&self) -> bool {
1492        self.flags.contains(EditTextFlag::BORDER)
1493    }
1494
1495    #[inline]
1496    pub fn with_has_border(mut self, value: bool) -> Self {
1497        self.flags.set(EditTextFlag::BORDER, value);
1498        self
1499    }
1500
1501    #[inline]
1502    pub fn is_html(&self) -> bool {
1503        self.flags.contains(EditTextFlag::HTML)
1504    }
1505
1506    #[inline]
1507    pub fn with_is_html(mut self, value: bool) -> Self {
1508        self.flags.set(EditTextFlag::HTML, value);
1509        self
1510    }
1511
1512    #[inline]
1513    pub fn is_multiline(&self) -> bool {
1514        self.flags.contains(EditTextFlag::MULTILINE)
1515    }
1516
1517    #[inline]
1518    pub fn with_is_multiline(mut self, value: bool) -> Self {
1519        self.flags.set(EditTextFlag::MULTILINE, value);
1520        self
1521    }
1522
1523    #[inline]
1524    pub fn is_password(&self) -> bool {
1525        self.flags.contains(EditTextFlag::PASSWORD)
1526    }
1527
1528    #[inline]
1529    pub fn with_is_password(mut self, value: bool) -> Self {
1530        self.flags.set(EditTextFlag::PASSWORD, value);
1531        self
1532    }
1533
1534    #[inline]
1535    pub fn is_read_only(&self) -> bool {
1536        self.flags.contains(EditTextFlag::READ_ONLY)
1537    }
1538
1539    #[inline]
1540    pub fn with_is_read_only(mut self, value: bool) -> Self {
1541        self.flags.set(EditTextFlag::READ_ONLY, value);
1542        self
1543    }
1544
1545    #[inline]
1546    pub fn is_selectable(&self) -> bool {
1547        !self.flags.contains(EditTextFlag::NO_SELECT)
1548    }
1549
1550    #[inline]
1551    pub fn with_is_selectable(mut self, value: bool) -> Self {
1552        self.flags.set(EditTextFlag::NO_SELECT, !value);
1553        self
1554    }
1555
1556    #[inline]
1557    pub fn was_static(&self) -> bool {
1558        self.flags.contains(EditTextFlag::WAS_STATIC)
1559    }
1560
1561    #[inline]
1562    pub fn with_was_static(mut self, value: bool) -> Self {
1563        self.flags.set(EditTextFlag::WAS_STATIC, value);
1564        self
1565    }
1566
1567    #[inline]
1568    pub fn is_word_wrap(&self) -> bool {
1569        self.flags.contains(EditTextFlag::WORD_WRAP)
1570    }
1571
1572    #[inline]
1573    pub fn with_is_word_wrap(mut self, value: bool) -> Self {
1574        self.flags.set(EditTextFlag::WORD_WRAP, value);
1575        self
1576    }
1577}
1578
1579impl Default for EditText<'_> {
1580    fn default() -> Self {
1581        Self {
1582            id: Default::default(),
1583            bounds: Default::default(),
1584            font_id: Default::default(),
1585            font_class: Default::default(),
1586            height: Twips::ZERO,
1587            color: Color::BLACK,
1588            max_length: 0,
1589            layout: TextLayout {
1590                align: TextAlign::Left,
1591                left_margin: Twips::ZERO,
1592                right_margin: Twips::ZERO,
1593                indent: Twips::ZERO,
1594                leading: Twips::ZERO,
1595            },
1596            variable_name: Default::default(),
1597            initial_text: Default::default(),
1598            flags: EditTextFlag::empty(),
1599        }
1600    }
1601}
1602
1603bitflags! {
1604    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
1605    pub struct EditTextFlag: u16 {
1606        const HAS_FONT = 1 << 0;
1607        const HAS_MAX_LENGTH = 1 << 1;
1608        const HAS_TEXT_COLOR = 1 << 2;
1609        const READ_ONLY = 1 << 3;
1610        const PASSWORD = 1 << 4;
1611        const MULTILINE = 1 << 5;
1612        const WORD_WRAP = 1 << 6;
1613        const HAS_TEXT = 1 << 7;
1614
1615        const USE_OUTLINES = 1 << 8;
1616        const HTML = 1 << 9;
1617        const WAS_STATIC = 1 << 10;
1618        const BORDER = 1 << 11;
1619        const NO_SELECT = 1 << 12;
1620        const HAS_LAYOUT = 1 << 13;
1621        const AUTO_SIZE = 1 << 14;
1622        const HAS_FONT_CLASS = 1 << 15;
1623    }
1624}
1625
1626#[derive(Clone, Debug, Default, Eq, PartialEq)]
1627pub struct TextLayout {
1628    pub align: TextAlign,
1629    pub left_margin: Twips,
1630    pub right_margin: Twips,
1631    pub indent: Twips,
1632    pub leading: Twips,
1633}
1634
1635#[derive(Clone, Copy, Debug, Default, Eq, FromPrimitive, PartialEq)]
1636pub enum TextAlign {
1637    #[default]
1638    Left = 0,
1639    Right = 1,
1640    Center = 2,
1641    Justify = 3,
1642}
1643
1644impl TextAlign {
1645    pub fn from_u8(n: u8) -> Option<Self> {
1646        num_traits::FromPrimitive::from_u8(n)
1647    }
1648}
1649
1650#[derive(Clone, Debug, Eq, PartialEq)]
1651pub struct FontAlignZone {
1652    // TODO(Herschel): Read these as f16s.
1653    pub left: i16,
1654    pub width: i16,
1655    pub bottom: i16,
1656    pub height: i16,
1657}
1658
1659#[derive(Clone, Copy, Debug, Eq, FromPrimitive, PartialEq)]
1660pub enum FontThickness {
1661    Thin = 0,
1662    Medium = 1,
1663    Thick = 2,
1664}
1665
1666impl FontThickness {
1667    pub fn from_u8(n: u8) -> Option<Self> {
1668        num_traits::FromPrimitive::from_u8(n)
1669    }
1670}
1671
1672#[derive(Clone, Debug, PartialEq)]
1673pub struct CsmTextSettings {
1674    pub id: CharacterId,
1675    pub use_advanced_rendering: bool,
1676    pub grid_fit: TextGridFit,
1677    pub thickness: f32, // TODO(Herschel): 0.0 is default. Should be Option?
1678    pub sharpness: f32,
1679}
1680
1681#[derive(Clone, Copy, Debug, Eq, FromPrimitive, PartialEq)]
1682pub enum TextGridFit {
1683    None = 0,
1684    Pixel = 1,
1685    SubPixel = 2,
1686}
1687
1688impl TextGridFit {
1689    pub fn from_u8(n: u8) -> Option<Self> {
1690        num_traits::FromPrimitive::from_u8(n)
1691    }
1692}
1693
1694#[derive(Clone, Debug, Eq, PartialEq)]
1695pub struct DefineBitsLossless<'a> {
1696    pub version: u8,
1697    pub id: CharacterId,
1698    pub format: BitmapFormat,
1699    pub width: u16,
1700    pub height: u16,
1701    pub data: Cow<'a, [u8]>,
1702}
1703
1704#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1705pub enum BitmapFormat {
1706    ColorMap8 { num_colors: u8 },
1707    Rgb15,
1708    Rgb32,
1709}
1710
1711#[derive(Clone, Debug, Eq, PartialEq)]
1712pub struct DefineVideoStream {
1713    pub id: CharacterId,
1714    pub num_frames: u16,
1715    pub width: u16,
1716    pub height: u16,
1717    pub is_smoothed: bool,
1718    pub deblocking: VideoDeblocking,
1719    pub codec: VideoCodec,
1720}
1721
1722#[derive(Clone, Copy, Debug, Eq, FromPrimitive, PartialEq)]
1723pub enum VideoDeblocking {
1724    UseVideoPacketValue = 0,
1725    None = 1,
1726    Level1 = 2,
1727    Level2 = 3,
1728    Level3 = 4,
1729    Level4 = 5,
1730}
1731
1732impl VideoDeblocking {
1733    pub fn from_u8(n: u8) -> Option<Self> {
1734        num_traits::FromPrimitive::from_u8(n)
1735    }
1736}
1737
1738#[derive(Clone, Copy, Debug, Eq, FromPrimitive, PartialEq)]
1739pub enum VideoCodec {
1740    None = 0,
1741    H263 = 2,
1742    ScreenVideo = 3,
1743    Vp6 = 4,
1744    Vp6WithAlpha = 5,
1745    ScreenVideoV2 = 6,
1746    H264 = 7,
1747}
1748
1749impl VideoCodec {
1750    pub fn from_u8(n: u8) -> Option<Self> {
1751        num_traits::FromPrimitive::from_u8(n)
1752    }
1753}
1754
1755#[derive(Clone, Debug, Eq, PartialEq)]
1756pub struct VideoFrame<'a> {
1757    pub stream_id: CharacterId,
1758    pub frame_num: u16,
1759    pub data: &'a [u8],
1760}
1761
1762#[derive(Clone, Debug, Eq, PartialEq)]
1763pub struct DefineBitsJpeg3<'a> {
1764    pub id: CharacterId,
1765    pub version: u8,
1766    pub deblocking: Fixed8,
1767    pub data: &'a [u8],
1768    pub alpha_data: &'a [u8],
1769}
1770
1771#[derive(Clone, Debug, Eq, PartialEq)]
1772pub struct DoAbc2<'a> {
1773    pub flags: DoAbc2Flag,
1774    pub name: &'a SwfStr,
1775    pub data: &'a [u8],
1776}
1777
1778bitflags! {
1779    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
1780    pub struct DoAbc2Flag: u32 {
1781        const LAZY_INITIALIZE = 1 << 0;
1782    }
1783}
1784
1785pub type DoAction<'a> = &'a [u8];
1786
1787pub type JpegTables<'a> = &'a [u8];
1788
1789/// Contains information about the software used to generate the SWF.
1790///
1791/// Not documented in the SWF19 reference. Emitted by mxmlc.
1792/// See <http://wahlers.com.br/claus/blog/undocumented-swf-tags-written-by-mxmlc/>
1793#[derive(Clone, Debug, Eq, PartialEq)]
1794pub struct ProductInfo {
1795    pub product_id: u32,
1796    pub edition: u32,
1797    pub major_version: u8,
1798    pub minor_version: u8,
1799    pub build_number: u64,
1800    pub compilation_date: u64,
1801}
1802
1803/// `DebugId` is a UUID written to debug SWFs and used by the Flash Debugger.
1804pub type DebugId = [u8; 16];
1805
1806/// An undocumented and unused tag to set the instance name of a character.
1807/// This seems to have no effect in the official Flash Player.
1808/// Superseded by the PlaceObject2 tag.
1809#[derive(Clone, Debug, Eq, PartialEq)]
1810pub struct NameCharacter<'a> {
1811    pub id: CharacterId,
1812    pub name: &'a SwfStr,
1813}
1814
1815#[cfg(test)]
1816mod tests {
1817    use super::ButtonActionCondition;
1818
1819    #[test]
1820    fn button_conditions_match() {
1821        assert!(ButtonActionCondition::OVER_DOWN_TO_OVER_UP
1822            .matches(ButtonActionCondition::OVER_DOWN_TO_OVER_UP));
1823
1824        assert!(!ButtonActionCondition::OVER_DOWN_TO_OVER_UP
1825            .matches(ButtonActionCondition::IDLE_TO_OVER_UP));
1826
1827        assert!((ButtonActionCondition::OVER_DOWN_TO_OVER_UP
1828            | ButtonActionCondition::IDLE_TO_OVER_UP)
1829            .matches(ButtonActionCondition::IDLE_TO_OVER_UP));
1830
1831        assert!((ButtonActionCondition::OVER_DOWN_TO_OVER_UP
1832            | ButtonActionCondition::from_key_code(3))
1833        .matches(ButtonActionCondition::OVER_DOWN_TO_OVER_UP));
1834
1835        assert!((ButtonActionCondition::OVER_DOWN_TO_OVER_UP
1836            | ButtonActionCondition::from_key_code(3))
1837        .matches(ButtonActionCondition::from_key_code(3)));
1838
1839        assert!(!(ButtonActionCondition::OVER_DOWN_TO_OVER_UP
1840            | ButtonActionCondition::from_key_code(3))
1841        .matches(ButtonActionCondition::from_key_code(1)));
1842    }
1843}