use super::value::{Color, YcbcrMatrix};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub(crate) enum TrackType {
#[default]
Unknown,
Ass,
Ssa,
}
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct Style {
pub name: String,
pub font_name: String,
pub font_size: f64,
pub primary_colour: Color,
pub secondary_colour: Color,
pub outline_colour: Color,
pub back_colour: Color,
pub bold: i32,
pub italic: i32,
pub underline: i32,
pub strike_out: i32,
pub scale_x: f64,
pub scale_y: f64,
pub spacing: f64,
pub angle: f64,
pub border_style: i32,
pub outline: f64,
pub shadow: f64,
pub alignment: i32,
pub margin_l: i32,
pub margin_r: i32,
pub margin_v: i32,
pub encoding: i32,
pub justify: i32,
pub blur: f64,
}
impl Style {
pub(super) fn zeroed() -> Self {
Self {
name: String::new(),
font_name: String::new(),
font_size: 0.0,
primary_colour: Color::default(),
secondary_colour: Color::default(),
outline_colour: Color::default(),
back_colour: Color::default(),
bold: 0,
italic: 0,
underline: 0,
strike_out: 0,
scale_x: 100.0,
scale_y: 100.0,
spacing: 0.0,
angle: 0.0,
border_style: 0,
outline: 0.0,
shadow: 0.0,
alignment: 0,
margin_l: 0,
margin_r: 0,
margin_v: 0,
encoding: 0,
justify: 0,
blur: 0.0,
}
}
pub(crate) fn builtin_default() -> Self {
Self {
name: "Default".to_string(),
font_name: "Arial".to_string(),
font_size: 18.0,
primary_colour: Color::from_packed(0xffff_ff00), secondary_colour: Color::from_packed(0x00ff_ff00), outline_colour: Color::from_packed(0x0000_0000), back_colour: Color::from_packed(0x0000_0080), bold: 200,
scale_x: 1.0,
scale_y: 1.0,
border_style: 1,
outline: 2.0,
shadow: 3.0,
alignment: 2, margin_l: 20,
margin_r: 20,
margin_v: 20,
..Self::zeroed()
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct Event {
pub read_order: i32,
pub layer: i32,
pub start_ms: i64,
pub duration_ms: i64,
pub style: usize,
pub name: String,
pub margin_l: i32,
pub margin_r: i32,
pub margin_v: i32,
pub effect: String,
pub text: String,
}
impl Event {
pub(super) fn zeroed() -> Self {
Self {
read_order: 0,
layer: 0,
start_ms: 0,
duration_ms: 0,
style: 0,
name: String::new(),
margin_l: 0,
margin_r: 0,
margin_v: 0,
effect: String::new(),
text: String::new(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct EmbeddedFont {
pub filename: String,
pub data: Vec<u8>,
}
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct Script {
pub track_type: TrackType,
pub play_res_x: i32,
pub play_res_y: i32,
pub layout_res_x: i32,
pub layout_res_y: i32,
pub timer: f64,
pub wrap_style: i32,
pub scaled_border_and_shadow: bool,
pub kerning: bool,
pub language: Option<String>,
pub ycbcr_matrix: YcbcrMatrix,
pub styles: Vec<Style>,
pub default_style: usize,
pub events: Vec<Event>,
pub fonts: Vec<EmbeddedFont>,
}
impl Script {
pub(super) fn new() -> Self {
Self {
track_type: TrackType::Unknown,
play_res_x: 0,
play_res_y: 0,
layout_res_x: 0,
layout_res_y: 0,
timer: 0.0,
wrap_style: 0,
scaled_border_and_shadow: false,
kerning: false,
language: None,
ycbcr_matrix: YcbcrMatrix::Default,
styles: vec![Style::builtin_default()],
default_style: 0,
events: Vec::new(),
fonts: Vec::new(),
}
}
pub(crate) fn lookup_style(&self, name: &str) -> usize {
let name = name.trim_start_matches('*');
let name = if name.eq_ignore_ascii_case("Default") {
"Default"
} else {
name
};
for (index, style) in self.styles.iter().enumerate().rev() {
if style.name == name {
return index;
}
}
log::warn!(
"ass parser: no style named '{name}' found, using '{}'",
self.styles[self.default_style].name
);
self.default_style
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn builtin_default_matches_set_default_style() {
let style = Style::builtin_default();
assert_eq!(style.name, "Default");
assert_eq!(style.font_name, "Arial");
assert_eq!(style.font_size, 18.0);
assert_eq!(style.primary_colour.packed(), 0xffff_ff00);
assert_eq!(style.secondary_colour.packed(), 0x00ff_ff00);
assert_eq!(style.outline_colour.packed(), 0x0000_0000);
assert_eq!(style.back_colour.packed(), 0x0000_0080);
assert_eq!(style.bold, 200);
assert_eq!((style.scale_x, style.scale_y), (1.0, 1.0));
assert_eq!(style.border_style, 1);
assert_eq!((style.outline, style.shadow), (2.0, 3.0));
assert_eq!(style.alignment, 2);
assert_eq!(
(style.margin_l, style.margin_r, style.margin_v),
(20, 20, 20)
);
assert_eq!((style.italic, style.underline, style.strike_out), (0, 0, 0));
assert_eq!((style.encoding, style.justify, style.blur), (0, 0, 0.0));
}
#[test]
fn lookup_style_matches_ass_lookup_style() {
let mut script = Script::new();
script.styles.push(Style {
name: "Sign".to_string(),
..Style::zeroed()
});
script.styles.push(Style {
name: "Default".to_string(),
..Style::zeroed()
});
script.default_style = 2;
script.styles.push(Style {
name: "Sign".to_string(),
font_name: "Later".to_string(),
..Style::zeroed()
});
assert_eq!(script.lookup_style("Sign"), 3);
assert_eq!(script.lookup_style("**Sign"), 3);
assert_eq!(script.lookup_style("DEFAULT"), 2);
assert_eq!(script.lookup_style("sign"), script.default_style);
assert_eq!(script.lookup_style("Missing"), 2);
}
}