1pub mod json;
10pub mod layout;
11mod text;
12pub mod text_animation;
13
14use std::fmt;
15use std::path::{Path, PathBuf};
16use std::sync::Arc;
17
18use alpha2_text::{TextFont, TextRasterError};
19
20pub use layout::{
21 LAYOUT_WRAP_SLACK, LayoutBounds, NodeBounds, NodeRects, ResolvedLayout, layout_texts,
22 measure_layout, resolve, shape_texts,
23};
24pub use text::{
25 MAX_FONT_PIXELS, MAX_PADDING_PIXELS, MAX_STROKE_PIXELS, ShadowPixels, TextAnimation,
26 TextBounds, TextDraw, animated_text_key, font_pixels, layout_text_key, measure_text, rgba8,
27 shadow_pixels, shaped_key, stroke_pixels, text_draw, text_fill, text_key, text_request,
28};
29
30#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
34pub struct PictureSize {
35 width: u32,
36 height: u32,
37}
38
39#[derive(Copy, Clone, Debug, Eq, PartialEq)]
41#[non_exhaustive]
42pub enum PictureSizeError {
43 Empty,
45 TooLarge,
47}
48
49impl fmt::Display for PictureSizeError {
50 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
51 formatter.write_str(match self {
52 Self::Empty => "picture dimensions must be non-zero",
53 Self::TooLarge => "picture dimensions exceed the largest frame",
54 })
55 }
56}
57
58impl std::error::Error for PictureSizeError {}
59
60impl PictureSize {
61 pub const fn new(width: u32, height: u32) -> Result<Self, PictureSizeError> {
66 if width == 0 || height == 0 {
67 Err(PictureSizeError::Empty)
68 } else if width > alpha2_media::MAX_FRAME_DIMENSION
69 || height > alpha2_media::MAX_FRAME_DIMENSION
70 || (width as u64) * (height as u64) > alpha2_media::MAX_FRAME_PIXELS
71 {
72 Err(PictureSizeError::TooLarge)
73 } else {
74 Ok(Self { width, height })
75 }
76 }
77
78 #[must_use]
80 pub const fn width(self) -> u32 {
81 self.width
82 }
83
84 #[must_use]
86 pub const fn height(self) -> u32 {
87 self.height
88 }
89
90 #[must_use]
92 #[allow(clippy::cast_precision_loss)]
93 pub fn unit(self) -> f32 {
94 self.width.min(self.height) as f32
95 }
96}
97
98#[derive(Copy, Clone, Debug, PartialEq)]
100pub struct CanvasRect {
101 pub left: f32,
103 pub top: f32,
105 pub right: f32,
107 pub bottom: f32,
109}
110
111#[derive(Copy, Clone, Debug, PartialEq)]
114pub struct Baselines {
115 pub first: f32,
117 pub last: f32,
119}
120
121#[derive(Debug)]
123#[non_exhaustive]
124pub enum FontFileError {
125 Read {
127 path: PathBuf,
129 error: std::io::Error,
131 },
132 Invalid {
134 path: PathBuf,
136 face_index: u32,
138 error: TextRasterError,
140 },
141}
142
143impl fmt::Display for FontFileError {
144 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
145 match self {
146 Self::Read { path, error } => write!(formatter, "font {}: {error}", path.display()),
147 Self::Invalid {
148 path,
149 face_index,
150 error,
151 } => write!(
152 formatter,
153 "font {} face {face_index}: {error}",
154 path.display()
155 ),
156 }
157 }
158}
159
160impl std::error::Error for FontFileError {
161 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
162 match self {
163 Self::Read { error, .. } => Some(error),
164 Self::Invalid { error, .. } => Some(error),
165 }
166 }
167}
168
169#[derive(Clone, Debug)]
171pub struct FontFiles {
172 pub faces: Vec<TextFont>,
174 pub bytes: Vec<Arc<[u8]>>,
176}
177
178pub fn fonts_from_files(paths: &[impl AsRef<Path>]) -> Result<FontFiles, FontFileError> {
188 let mut fonts = Vec::with_capacity(paths.len());
189 let mut files = Vec::with_capacity(paths.len());
190 for path in paths {
191 let path = path.as_ref();
192 let bytes: Arc<[u8]> = std::fs::read(path)
193 .map_err(|error| FontFileError::Read {
194 path: path.to_owned(),
195 error,
196 })?
197 .into();
198 let family = path
199 .file_stem()
200 .map_or_else(String::new, |stem| stem.to_string_lossy().into_owned());
201 for face_index in 0..alpha2_text::face_count(&bytes) {
202 alpha2_text::check_font(&bytes, face_index).map_err(|error| {
203 FontFileError::Invalid {
204 path: path.to_owned(),
205 face_index,
206 error,
207 }
208 })?;
209 fonts.push(TextFont {
210 family: family.clone(),
211 bytes: Arc::clone(&bytes),
212 face_index,
213 });
214 }
215 files.push(bytes);
216 }
217 Ok(FontFiles {
218 faces: fonts,
219 bytes: files,
220 })
221}
222
223#[cfg(test)]
224mod tests;