1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
use std::num::NonZeroU32;

use crate::{reader::AseReader, user_data::UserData, AsepriteParseError, Result};

/// A tag is a grouping of one or more frames.
///
/// Tag ranges may overlap each other. Tag names are _not_ guaranteed to be
/// unique.
#[derive(Debug, Clone)]
pub struct Tag {
    name: String,
    from_frame: u16,
    to_frame: u16,
    repeat: u16,
    animation_direction: AnimationDirection,
    pub(crate) user_data: Option<UserData>,
}

impl Tag {
    /// Tag name. May not be unique among all tags.
    pub fn name(&self) -> &str {
        &self.name
    }

    /// First frame included in the tag.
    pub fn from_frame(&self) -> u32 {
        self.from_frame as u32
    }

    /// Last frame included in the tag.
    pub fn to_frame(&self) -> u32 {
        self.to_frame as u32
    }

    /// See [AnimationDirection] for details.
    pub fn animation_direction(&self) -> AnimationDirection {
        self.animation_direction
    }

    /// Repeat count included in the tag.
    ///
    /// `None` if unspecified/not enabled (UI shows infinity symbol).
    pub fn repeat(&self) -> Option<NonZeroU32> {
        NonZeroU32::new(self.repeat as u32)
    }

    /// Returns the user data for the tag, if any exists.
    pub fn user_data(&self) -> Option<&UserData> {
        self.user_data.as_ref()
    }

    pub(crate) fn set_user_data(&mut self, user_data: UserData) {
        self.user_data = Some(user_data);
    }
}

/// Describes how the tag's frames should be animated.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AnimationDirection {
    /// Start at `from_frame` and count up to `to_frame`.
    Forward,
    /// Start at `to_frame` and count down to `from_frame`.
    Reverse,
    /// Start at `from_frame`, count up to `to_frame`, then back down to `from_frame`.
    PingPong,
}

pub(crate) fn parse_chunk(data: &[u8]) -> Result<Vec<Tag>> {
    let mut reader = AseReader::new(data);

    let num_tags = reader.word()?;
    reader.skip_reserved(8)?;

    let mut result = Vec::with_capacity(num_tags as usize);

    for _tag in 0..num_tags {
        let from_frame = reader.word()?;
        let to_frame = reader.word()?;
        let anim_dir = reader.byte()?;
        let repeat = reader.word()?;
        reader.skip_reserved(6)?;
        let _color = reader.dword()?;
        let name = reader.string()?;
        let animation_direction = parse_animation_direction(anim_dir)?;
        result.push(Tag {
            name,
            from_frame,
            to_frame,
            animation_direction,
            repeat,
            user_data: None,
        });
    }

    Ok(result)
}

fn parse_animation_direction(id: u8) -> Result<AnimationDirection> {
    match id {
        0 => Ok(AnimationDirection::Forward),
        1 => Ok(AnimationDirection::Reverse),
        2 => Ok(AnimationDirection::PingPong),
        _ => Err(AsepriteParseError::InvalidInput(format!(
            "Unknown animation direction: {}",
            id
        ))),
    }
}