1pub mod json;
12pub mod layout;
13mod text;
14
15use std::fmt;
16use std::path::{Path, PathBuf};
17use std::sync::Arc;
18
19use alpha2_text::{TextFont, TextRasterError};
20
21pub use layout::{
22 LAYOUT_WRAP_SLACK, LayoutBounds, NodeBounds, NodeRects, ResolvedLayout, layout_texts,
23 measure_layout, resolve, shape_texts,
24};
25pub use text::{
26 MAX_FONT_PIXELS, MAX_OUTLINE_PIXELS, MAX_PADDING_PIXELS, TextBounds, layout_text_key,
27 measure_text, rgba8, shaped_key, 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> {
67 if width == 0 || height == 0 {
68 Err(PictureSizeError::Empty)
69 } else if width > alpha2_media::MAX_FRAME_DIMENSION
70 || height > alpha2_media::MAX_FRAME_DIMENSION
71 || (width as u64) * (height as u64) > alpha2_media::MAX_FRAME_PIXELS
72 {
73 Err(PictureSizeError::TooLarge)
74 } else {
75 Ok(Self { width, height })
76 }
77 }
78
79 #[must_use]
81 pub const fn width(self) -> u32 {
82 self.width
83 }
84
85 #[must_use]
87 pub const fn height(self) -> u32 {
88 self.height
89 }
90
91 #[must_use]
94 #[allow(clippy::cast_precision_loss)]
95 pub fn unit(self) -> f32 {
96 self.width.min(self.height) as f32
97 }
98}
99
100#[derive(Copy, Clone, Debug, PartialEq)]
102pub struct CanvasRect {
103 pub left: f32,
105 pub top: f32,
107 pub right: f32,
109 pub bottom: f32,
111}
112
113#[derive(Copy, Clone, Debug, PartialEq)]
118pub struct Baselines {
119 pub first: f32,
121 pub last: f32,
123}
124
125#[derive(Debug)]
127#[non_exhaustive]
128pub enum FontFileError {
129 Read {
131 path: PathBuf,
133 error: std::io::Error,
135 },
136 Invalid {
138 path: PathBuf,
140 face_index: u32,
142 error: TextRasterError,
144 },
145}
146
147impl fmt::Display for FontFileError {
148 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
149 match self {
150 Self::Read { path, error } => write!(formatter, "font {}: {error}", path.display()),
151 Self::Invalid {
152 path,
153 face_index,
154 error,
155 } => write!(
156 formatter,
157 "font {} face {face_index}: {error}",
158 path.display()
159 ),
160 }
161 }
162}
163
164impl std::error::Error for FontFileError {
165 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
166 match self {
167 Self::Read { error, .. } => Some(error),
168 Self::Invalid { error, .. } => Some(error),
169 }
170 }
171}
172
173#[derive(Clone, Debug)]
176pub struct FontFiles {
177 pub faces: Vec<TextFont>,
179 pub bytes: Vec<Arc<[u8]>>,
181}
182
183pub fn fonts_from_files(paths: &[impl AsRef<Path>]) -> Result<FontFiles, FontFileError> {
193 let mut fonts = Vec::with_capacity(paths.len());
194 let mut files = Vec::with_capacity(paths.len());
195 for path in paths {
196 let path = path.as_ref();
197 let bytes: Arc<[u8]> = std::fs::read(path)
198 .map_err(|error| FontFileError::Read {
199 path: path.to_owned(),
200 error,
201 })?
202 .into();
203 let family = path
204 .file_stem()
205 .map_or_else(String::new, |stem| stem.to_string_lossy().into_owned());
206 for face_index in 0..alpha2_text::face_count(&bytes) {
207 alpha2_text::check_font(&bytes, face_index).map_err(|error| {
208 FontFileError::Invalid {
209 path: path.to_owned(),
210 face_index,
211 error,
212 }
213 })?;
214 fonts.push(TextFont {
215 family: family.clone(),
216 bytes: Arc::clone(&bytes),
217 face_index,
218 });
219 }
220 files.push(bytes);
221 }
222 Ok(FontFiles {
223 faces: fonts,
224 bytes: files,
225 })
226}
227
228#[cfg(test)]
229mod tests;