use alpha2_time::FrameRate;
use serde::{Deserialize, Serialize};
use crate::{MAX_TEXT_CONTENT_BYTES, MediaKind, Rgba};
pub const MAX_SEQUENCE_FRAMES: usize = 100_000;
pub const MAX_PROVIDER_DOCUMENT_BYTES: usize = 256 * 1024 * 1024;
pub const MAX_LOTTIE_DOCUMENT_BYTES: usize = 16 * 1024 * 1024;
pub const MAX_SVG_DOCUMENT_BYTES: usize = 32 * 1024 * 1024;
pub const MAX_LATEX_BYTES: usize = MAX_TEXT_CONTENT_BYTES;
pub const MAX_LATEX_FONT_SIZE: f32 = 1_024.0;
pub const MAX_PDF_DOCUMENT_BYTES: usize = 256 * 1024 * 1024;
pub const MAX_PDF_PAGES: u32 = 10_000;
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(tag = "format", rename_all = "snake_case", from = "ProviderSourceWire")]
#[non_exhaustive]
pub enum ProviderSource {
Svg,
AnimatedImage,
Lottie {
#[serde(default, skip_serializing_if = "Option::is_none")]
animation: Option<String>,
},
ImageSequence {
frames: Vec<Option<String>>,
frame_rate: FrameRate,
#[serde(default)]
start_number: u64,
#[serde(default)]
missing: MissingFrames,
#[serde(default, skip_serializing_if = "Option::is_none")]
primaries: Option<LinearPrimaries>,
},
Exr {
#[serde(default, skip_serializing_if = "Option::is_none")]
primaries: Option<LinearPrimaries>,
},
Latex {
latex: String,
font_size: f32,
#[serde(default = "white")]
color: Rgba,
#[serde(default = "display_style")]
display: bool,
},
Pdf {
#[serde(default = "first_page")]
page: u32,
#[serde(default = "white")]
background: Rgba,
},
}
#[derive(Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "schema", schemars(rename = "ProviderSource"))]
#[serde(tag = "format", rename_all = "snake_case", deny_unknown_fields)]
pub(crate) enum ProviderSourceWire {
Svg {},
AnimatedImage {},
Lottie {
#[serde(default, skip_serializing_if = "Option::is_none")]
animation: Option<String>,
},
ImageSequence {
frames: Vec<Option<String>>,
frame_rate: FrameRate,
#[serde(default)]
start_number: u64,
#[serde(default)]
missing: MissingFrames,
#[serde(default, skip_serializing_if = "Option::is_none")]
primaries: Option<LinearPrimaries>,
},
Exr {
#[serde(default, skip_serializing_if = "Option::is_none")]
primaries: Option<LinearPrimaries>,
},
Latex {
latex: String,
font_size: f32,
#[serde(default = "white")]
color: Rgba,
#[serde(default = "display_style")]
display: bool,
},
Pdf {
#[serde(default = "first_page")]
page: u32,
#[serde(default = "white")]
background: Rgba,
},
}
impl From<ProviderSourceWire> for ProviderSource {
fn from(wire: ProviderSourceWire) -> Self {
match wire {
ProviderSourceWire::Svg {} => Self::Svg,
ProviderSourceWire::AnimatedImage {} => Self::AnimatedImage,
ProviderSourceWire::Lottie { animation } => Self::Lottie { animation },
ProviderSourceWire::ImageSequence {
frames,
frame_rate,
start_number,
missing,
primaries,
} => Self::ImageSequence {
frames,
frame_rate,
start_number,
missing,
primaries,
},
ProviderSourceWire::Exr { primaries } => Self::Exr { primaries },
ProviderSourceWire::Latex {
latex,
font_size,
color,
display,
} => Self::Latex {
latex,
font_size,
color,
display,
},
ProviderSourceWire::Pdf { page, background } => Self::Pdf { page, background },
}
}
}
const fn white() -> Rgba {
Rgba::new(1.0, 1.0, 1.0, 1.0)
}
const fn display_style() -> bool {
true
}
const fn first_page() -> u32 {
1
}
#[derive(Copy, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case")]
pub enum LinearPrimaries {
Bt709,
Bt2020,
P3D65,
AcesAp0,
AcesAp1,
}
#[derive(Copy, Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case")]
pub enum MissingFrames {
#[default]
Reject,
Hold,
Blank,
}
impl ProviderSource {
#[must_use]
pub const fn kind(&self) -> MediaKind {
match self {
Self::Svg | Self::Exr { .. } | Self::Latex { .. } | Self::Pdf { .. } => {
MediaKind::Image
}
Self::AnimatedImage | Self::Lottie { .. } | Self::ImageSequence { .. } => {
MediaKind::Video
}
}
}
#[must_use]
pub const fn reads_file(&self) -> bool {
!matches!(self, Self::Latex { .. })
}
#[must_use]
pub fn is_valid(&self) -> bool {
match self {
Self::Lottie {
animation: Some(animation),
} => return !animation.is_empty(),
Self::Latex {
latex,
font_size,
color,
..
} => {
return !latex.is_empty()
&& latex.len() <= MAX_LATEX_BYTES
&& *font_size > 0.0
&& *font_size <= MAX_LATEX_FONT_SIZE
&& color.is_valid();
}
Self::Pdf { page, background } => {
return (1..=MAX_PDF_PAGES).contains(page) && background.is_valid();
}
_ => {}
}
let Self::ImageSequence {
frames, missing, ..
} = self
else {
return true;
};
let present = frames.iter().flatten().collect::<Vec<_>>();
let unique = present
.iter()
.collect::<std::collections::HashSet<_>>()
.len()
== present.len();
!frames.is_empty()
&& frames.len() <= MAX_SEQUENCE_FRAMES
&& frames[0].is_some()
&& unique
&& present.iter().all(|locator| !locator.is_empty())
&& (present.len() == frames.len() || *missing != MissingFrames::Reject)
}
}