#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TextAlignment {
Left,
Center,
Right,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FontWeight {
Normal,
Bold,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CaptionPlacement {
BottomCenter,
TopCenter,
Custom { x_pct: u8, y_pct: u8 },
}
#[derive(Debug, Clone)]
pub struct CaptionStyleConfig {
pub font_size_px: u32,
pub font_family: &'static str,
pub font_weight: FontWeight,
pub text_color: &'static str,
pub background_color: &'static str,
pub background_opacity: f32,
pub text_shadow: &'static str,
pub alignment: TextAlignment,
pub placement: CaptionPlacement,
pub contrast_ratio: f32,
pub max_lines: u8,
pub max_chars_per_line: u8,
pub preset_name: &'static str,
}
pub struct CaptionStyle;
impl CaptionStyle {
#[must_use]
pub fn netflix() -> CaptionStyleConfig {
CaptionStyleConfig {
font_size_px: 40,
font_family: "Arial, Helvetica, sans-serif",
font_weight: FontWeight::Normal,
text_color: "#FFFFFF",
background_color: "#000000",
background_opacity: 0.8,
text_shadow: "0 0 4px #000000",
alignment: TextAlignment::Center,
placement: CaptionPlacement::BottomCenter,
contrast_ratio: 7.0,
max_lines: 2,
max_chars_per_line: 42,
preset_name: "Netflix",
}
}
#[must_use]
pub fn bbc() -> CaptionStyleConfig {
CaptionStyleConfig {
font_size_px: 38,
font_family: "BBC Reith Sans, Arial, sans-serif",
font_weight: FontWeight::Normal,
text_color: "#FFFFFF",
background_color: "#000000",
background_opacity: 0.75,
text_shadow: "",
alignment: TextAlignment::Center,
placement: CaptionPlacement::BottomCenter,
contrast_ratio: 5.0,
max_lines: 2,
max_chars_per_line: 37,
preset_name: "BBC",
}
}
#[must_use]
pub fn wcag() -> CaptionStyleConfig {
CaptionStyleConfig {
font_size_px: 36,
font_family: "Arial, Helvetica, sans-serif",
font_weight: FontWeight::Bold,
text_color: "#FFFFFF",
background_color: "#000000",
background_opacity: 1.0,
text_shadow: "1px 1px 2px #000000, -1px -1px 2px #000000",
alignment: TextAlignment::Center,
placement: CaptionPlacement::BottomCenter,
contrast_ratio: 7.1,
max_lines: 3,
max_chars_per_line: 40,
preset_name: "WCAG",
}
}
#[must_use]
pub fn apple_tv() -> CaptionStyleConfig {
CaptionStyleConfig {
font_size_px: 40,
font_family: "Helvetica Neue, Arial, sans-serif",
font_weight: FontWeight::Normal,
text_color: "#FFFFFF",
background_color: "#000000",
background_opacity: 0.0,
text_shadow: "0 2px 4px rgba(0,0,0,0.9)",
alignment: TextAlignment::Center,
placement: CaptionPlacement::BottomCenter,
contrast_ratio: 4.5,
max_lines: 2,
max_chars_per_line: 40,
preset_name: "AppleTV",
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_netflix_preset_font_size() {
let p = CaptionStyle::netflix();
assert_eq!(p.font_size_px, 40);
}
#[test]
fn test_netflix_preset_max_lines() {
let p = CaptionStyle::netflix();
assert_eq!(p.max_lines, 2);
}
#[test]
fn test_bbc_text_color_white() {
let p = CaptionStyle::bbc();
assert_eq!(p.text_color, "#FFFFFF");
}
#[test]
fn test_bbc_max_chars_per_line() {
let p = CaptionStyle::bbc();
assert_eq!(p.max_chars_per_line, 37);
}
#[test]
fn test_wcag_contrast_ratio_aaaa() {
let p = CaptionStyle::wcag();
assert!(
p.contrast_ratio >= 7.0,
"WCAG preset must meet AAA contrast: {}",
p.contrast_ratio
);
}
#[test]
fn test_wcag_fully_opaque_background() {
let p = CaptionStyle::wcag();
assert!((p.background_opacity - 1.0).abs() < f32::EPSILON);
}
#[test]
fn test_all_presets_have_names() {
for preset in [
CaptionStyle::netflix(),
CaptionStyle::bbc(),
CaptionStyle::wcag(),
CaptionStyle::apple_tv(),
] {
assert!(!preset.preset_name.is_empty());
}
}
#[test]
fn test_all_presets_bottom_centred() {
for preset in [
CaptionStyle::netflix(),
CaptionStyle::bbc(),
CaptionStyle::wcag(),
] {
assert_eq!(preset.placement, CaptionPlacement::BottomCenter);
}
}
#[test]
fn test_contrast_ratio_positive() {
for preset in [
CaptionStyle::netflix(),
CaptionStyle::bbc(),
CaptionStyle::wcag(),
] {
assert!(preset.contrast_ratio > 0.0);
}
}
}