pub mod json;
pub mod layout;
mod text;
pub mod text_animation;
use std::fmt;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use alpha2_text::{TextFont, TextRasterError};
pub use layout::{
LAYOUT_WRAP_SLACK, LayoutBounds, NodeBounds, NodeRects, ResolvedLayout, layout_texts,
measure_layout, resolve, shape_texts,
};
pub use text::{
MAX_FONT_PIXELS, MAX_PADDING_PIXELS, MAX_STROKE_PIXELS, ShadowPixels, TextAnimation,
TextBounds, TextDraw, animated_text_key, font_pixels, layout_text_key, measure_text, rgba8,
shadow_pixels, shaped_key, stroke_pixels, text_draw, text_fill, text_key, text_request,
};
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
pub struct PictureSize {
width: u32,
height: u32,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum PictureSizeError {
Empty,
TooLarge,
}
impl fmt::Display for PictureSizeError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(match self {
Self::Empty => "picture dimensions must be non-zero",
Self::TooLarge => "picture dimensions exceed the largest frame",
})
}
}
impl std::error::Error for PictureSizeError {}
impl PictureSize {
pub const fn new(width: u32, height: u32) -> Result<Self, PictureSizeError> {
if width == 0 || height == 0 {
Err(PictureSizeError::Empty)
} else if width > alpha2_media::MAX_FRAME_DIMENSION
|| height > alpha2_media::MAX_FRAME_DIMENSION
|| (width as u64) * (height as u64) > alpha2_media::MAX_FRAME_PIXELS
{
Err(PictureSizeError::TooLarge)
} else {
Ok(Self { width, height })
}
}
#[must_use]
pub const fn width(self) -> u32 {
self.width
}
#[must_use]
pub const fn height(self) -> u32 {
self.height
}
#[must_use]
#[allow(clippy::cast_precision_loss)]
pub fn unit(self) -> f32 {
self.width.min(self.height) as f32
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct CanvasRect {
pub left: f32,
pub top: f32,
pub right: f32,
pub bottom: f32,
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Baselines {
pub first: f32,
pub last: f32,
}
#[derive(Debug)]
#[non_exhaustive]
pub enum FontFileError {
Read {
path: PathBuf,
error: std::io::Error,
},
Invalid {
path: PathBuf,
face_index: u32,
error: TextRasterError,
},
}
impl fmt::Display for FontFileError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Read { path, error } => write!(formatter, "font {}: {error}", path.display()),
Self::Invalid {
path,
face_index,
error,
} => write!(
formatter,
"font {} face {face_index}: {error}",
path.display()
),
}
}
}
impl std::error::Error for FontFileError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Read { error, .. } => Some(error),
Self::Invalid { error, .. } => Some(error),
}
}
}
#[derive(Clone, Debug)]
pub struct FontFiles {
pub faces: Vec<TextFont>,
pub bytes: Vec<Arc<[u8]>>,
}
pub fn fonts_from_files(paths: &[impl AsRef<Path>]) -> Result<FontFiles, FontFileError> {
let mut fonts = Vec::with_capacity(paths.len());
let mut files = Vec::with_capacity(paths.len());
for path in paths {
let path = path.as_ref();
let bytes: Arc<[u8]> = std::fs::read(path)
.map_err(|error| FontFileError::Read {
path: path.to_owned(),
error,
})?
.into();
let family = path
.file_stem()
.map_or_else(String::new, |stem| stem.to_string_lossy().into_owned());
for face_index in 0..alpha2_text::face_count(&bytes) {
alpha2_text::check_font(&bytes, face_index).map_err(|error| {
FontFileError::Invalid {
path: path.to_owned(),
face_index,
error,
}
})?;
fonts.push(TextFont {
family: family.clone(),
bytes: Arc::clone(&bytes),
face_index,
});
}
files.push(bytes);
}
Ok(FontFiles {
faces: fonts,
bytes: files,
})
}
#[cfg(test)]
mod tests;