1use std::collections::HashMap;
2use std::sync::{Arc, Mutex};
3
4use zintl_render_math::{
5 Alignment, InLogicalScale, InPhysicalScale, LogicalPixels, LogicalPixelsRect, PhysicalPixels,
6 PhysicalPixelsF, PhysicalPixelsFPoint, PhysicalPixelsFRect, PhysicalPixelsFSize,
7 PhysicalPixelsRect, PhysicalPixelsSize, ScaleFactor,
8};
9
10use crate::texture::Atlas;
11use ab_glyph::{Font as _, ScaleFont};
12
13#[derive(Clone, Copy, Debug, Default, PartialEq)]
15pub struct GlyphRect {
16 pub width: PhysicalPixelsF,
18 pub height: PhysicalPixelsF,
20 pub bounds: PhysicalPixelsFRect,
22 pub texture_bounds: PhysicalPixelsRect,
24}
25
26#[derive(Clone, Copy, Debug, Default, PartialEq)]
28pub struct Glyph {
29 pub id: ab_glyph::GlyphId,
31 pub rect: GlyphRect,
32}
33
34#[derive(Clone, Debug)]
36pub struct Font {
37 pub ab_font: ab_glyph::FontArc,
38 pub type_face: String,
39 pub atlas: Arc<Mutex<Atlas>>,
41 pub scale: PhysicalPixelsF,
43 pub height: PhysicalPixelsF,
44 pub ascent: PhysicalPixelsF,
46 pub descent: PhysicalPixelsF,
48 pub line_gap: PhysicalPixelsF,
50 pub glyphs: Arc<Mutex<HashMap<char, Glyph>>>,
52}
53
54impl Font {
55 pub fn new(
56 ab_font: ab_glyph::FontArc,
57 type_face: String,
58 scale: LogicalPixels,
59 scale_factor: ScaleFactor,
60 ) -> Self {
61 let scale_physical: PhysicalPixels = scale.in_physical_scale(&scale_factor);
62 let atlas = Atlas::new(
64 scale_physical.max(1024.into()),
65 scale_physical.max(32.into()),
66 );
67 let scale: PhysicalPixelsF = scale_physical.into();
68 let scaled = ab_font.as_scaled(scale.value());
70 let height: PhysicalPixelsF = scaled.height().into();
71 let line_gap: PhysicalPixelsF = scaled.line_gap().into();
72 let ascent: PhysicalPixelsF = scaled.ascent().into();
73 let descent: PhysicalPixelsF = scaled.descent().into();
74
75 Font {
76 ab_font,
77 type_face,
78 atlas: Mutex::new(atlas).into(),
79 scale,
80 height,
81 ascent,
82 descent,
83 line_gap,
84 glyphs: Mutex::new(HashMap::new()).into(),
85 }
86 }
87
88 pub fn get_glyph(&self, c: char) -> Glyph {
89 if let Some(glyph) = self.glyphs.lock().unwrap().get(&c) {
91 return *glyph;
92 }
93
94 let atlas = &mut self.atlas.lock().unwrap();
95 let id = self.ab_font.glyph_id(c);
97
98 if id.0 == 0 {
99 return Glyph::default();
100 }
101
102 let scaled = self.ab_font.as_scaled(self.scale.value());
103 let h_advance: PhysicalPixelsF = scaled.h_advance(id).into();
104
105 let g = id.with_scale_and_position(
106 self.scale.value(),
107 ab_glyph::Point {
108 x: 0.0,
109 y: scaled.ascent(),
110 },
111 );
112
113 let rect = {
114 if let Some(g) = self.ab_font.outline_glyph(g) {
115 let px_bounds = g.px_bounds();
116
117 let px_width: PhysicalPixels = (px_bounds.width() as u32).into();
119 let px_height: PhysicalPixels = (px_bounds.height() as u32).into();
120 let (texture_bounds, atlas_width, pixels) = atlas.create_image(px_width, px_height);
122 g.draw(|x, y, c| {
123 if c == 0.0 {
124 return; }
126 let start = (texture_bounds.min.y + y) * atlas_width * 4
127 + (texture_bounds.min.x + x) * 4;
128 let start = start.value() as usize;
129 pixels[start] = ((1. - c) * 255.) as u8;
130 pixels[start + 1] = ((1. - c) * 255.) as u8;
131 pixels[start + 2] = ((1. - c) * 255.) as u8;
132 pixels[start + 3] = (c * 255.) as u8;
133 });
134
135 GlyphRect {
136 width: h_advance,
137 height: self.height,
138 bounds: PhysicalPixelsFRect {
139 min: PhysicalPixelsFPoint::new(
140 px_bounds.min.x.into(),
141 px_bounds.min.y.into(),
142 ),
143 max: PhysicalPixelsFPoint::new(
144 px_bounds.max.x.into(),
145 px_bounds.max.y.into(),
146 ),
147 },
148 texture_bounds,
149 }
150 } else {
151 return Glyph::default();
152 }
153 };
154
155 let glyph = Glyph { id, rect };
156
157 self.glyphs.lock().unwrap().insert(c, glyph);
160
161 glyph
162 }
163
164 pub fn kern(&self, left: &Glyph, right: &Glyph) -> PhysicalPixelsF {
165 let scaled = self.ab_font.as_scaled(self.scale.value());
166 scaled.kern(left.id, right.id).into()
167 }
168
169 pub fn get_atlas_pixels(&self) -> Vec<u8> {
170 self.atlas.lock().unwrap().pixels()
171 }
172
173 pub fn get_atlas_size(&self) -> PhysicalPixelsSize {
174 let atlas = self.atlas.lock().unwrap();
175 PhysicalPixelsSize::new(atlas.width, atlas.height)
176 }
177}
178
179#[derive(Clone, Debug, Default, PartialEq, Hash, Eq)]
180pub struct FontProperties {
181 pub name: String,
182 pub scale_string: String,
184}
185
186#[derive(Clone, Debug)]
187pub struct Typecase {
188 pub fonts: HashMap<String, ab_glyph::FontArc>,
189 pub sized_fonts: HashMap<FontProperties, Font>,
190 pub scale_factor: ScaleFactor,
191}
192
193impl Typecase {
194 pub fn new(scale_factor: ScaleFactor) -> Self {
195 Typecase {
196 fonts: HashMap::new(),
197 sized_fonts: HashMap::new(),
198 scale_factor,
199 }
200 }
201
202 pub fn load_font(&mut self, name: String, data: Vec<u8>) {
203 let font = ab_glyph::FontVec::try_from_vec(data)
204 .map(ab_glyph::FontArc::from)
205 .unwrap();
206 self.fonts.insert(name.clone(), font.clone());
207 }
208
209 pub fn get_font(&mut self, font: FontProperties) -> Option<&Font> {
210 let f = self.sized_fonts.entry(font.clone()).or_insert({
211 if let Some(ab_font) = self.fonts.get(&font.name) {
212 let scale = font
213 .scale_string
214 .parse::<f32>()
215 .expect("Invalid scale string");
216 let new_font = Font::new(
217 ab_font.clone(),
218 font.name.clone(),
219 scale.into(),
220 self.scale_factor,
221 );
222 new_font
223 } else {
224 return None;
225 }
226 });
227 Some(f)
228 }
229}
230
231#[derive(Clone, Debug)]
232pub struct PositionedGlyph {
233 pub glyph: Glyph,
234 pub rect: PhysicalPixelsFRect,
235}
236
237#[derive(Clone, Copy, Debug, Default, PartialEq)]
238pub enum TextAlignment {
239 #[default]
240 Left,
241 Center,
242 Right,
243}
244
245#[derive(Clone, Debug)]
247pub struct Galley {
248 pub glyphs: Vec<PositionedGlyph>,
249 pub rect: LogicalPixelsRect,
250}
251
252#[derive(Clone, Debug)]
253pub struct Typesetter {}
254
255impl Typesetter {
256 pub fn new() -> Self {
257 Typesetter {}
258 }
259
260 pub fn compose(
262 &self,
263 text: &str,
264 font: &Font,
265 bounds: LogicalPixelsRect,
266 _text_alignment: TextAlignment,
267 alignment: Alignment,
268 scale_factor: &ScaleFactor,
269 ) -> Galley {
270 let mut glyphs = Vec::new();
271 let mut cursor = PhysicalPixelsFPoint::zero();
272 let mut size: PhysicalPixelsFSize = PhysicalPixelsFSize::default();
273 let mut last_glyph_id = None;
274 for c in text.chars() {
275 let glyph = font.get_glyph(c);
276 if let Some(last) = last_glyph_id {
277 cursor.x += font.kern(&last, &glyph);
278 }
279 if size.width < glyph.rect.height {
280 size.height = glyph.rect.height;
281 }
282 glyphs.push(PositionedGlyph {
283 glyph,
284 rect: PhysicalPixelsFRect::with_size(
285 cursor,
286 PhysicalPixelsFSize::new(glyph.rect.width, glyph.rect.height),
287 ),
288 });
289 last_glyph_id = Some(glyph);
290 cursor.x += glyph.rect.width;
291 size.width += glyph.rect.width;
292 }
293
294 Galley {
295 glyphs,
296 rect: alignment.align_size(bounds, size.in_logical_scale(&scale_factor)),
297 }
298 }
299}
300
301#[cfg(test)]
302mod tests {
303 use super::*;
304 use zintl_render_math::{LogicalPixelsPoint, ScaleFactor};
305
306 #[test]
307 fn galley_logical_rect() {
308 let scale_factor = ScaleFactor::new(1.0, 1.5);
309 let font = Font::new(
310 ab_glyph::FontArc::try_from_slice(include_bytes!(
311 "../../../assets/inter/Inter-Regular.ttf"
312 ))
313 .unwrap(),
314 "Inter".to_string(),
315 16.0.into(),
316 scale_factor,
317 );
318 let typesetter = Typesetter::new();
319 let bounds = LogicalPixelsRect::new(
320 LogicalPixelsPoint::new(0.0.into(), 0.0.into()),
321 LogicalPixelsPoint::new(100.0.into(), 100.0.into()),
322 );
323 let galley = typesetter.compose(
324 "Hello",
325 &font,
326 bounds,
327 TextAlignment::Left,
328 Alignment::TopLeft,
329 &scale_factor,
330 );
331 assert_eq!(galley.rect.height(), 16.0.into());
332 assert_eq!(galley.glyphs[0].glyph.rect.height, (16.0 * 1.5).into());
333 }
334}