cranpose_render_common/
font_layout.rs1use ab_glyph::{Font, Glyph, OutlinedGlyph, Point, PxScale, ScaleFont, point};
2
3#[derive(Debug, Clone, Copy, PartialEq)]
4pub struct FontVerticalMetrics {
5 pub ascent: f32,
6 pub descent: f32,
7 pub natural_line_height: f32,
8 pub line_gap: f32,
11}
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub struct GlyphPixelBounds {
15 pub min_x: i32,
16 pub min_y: i32,
17 pub max_x: i32,
18 pub max_y: i32,
19}
20
21impl GlyphPixelBounds {
22 pub fn width(self) -> usize {
23 self.max_x.saturating_sub(self.min_x) as usize
24 }
25
26 pub fn height(self) -> usize {
27 self.max_y.saturating_sub(self.min_y) as usize
28 }
29}
30
31pub fn vertical_metrics(font: &impl Font, font_size: f32) -> FontVerticalMetrics {
32 let scaled_font = font.as_scaled(PxScale::from(font_size));
33 let ascent = scaled_font.ascent();
34 let descent = scaled_font.descent();
35 FontVerticalMetrics {
36 ascent,
37 descent,
38 natural_line_height: (ascent - descent).ceil(),
39 line_gap: scaled_font.line_gap(),
40 }
41}
42
43pub fn layout_line_glyphs(
44 font: &impl Font,
45 text: &str,
46 font_size: f32,
47 origin: Point,
48) -> Vec<Glyph> {
49 let scale = PxScale::from(font_size);
50 let scaled_font = font.as_scaled(scale);
51 let mut caret_x = origin.x;
52 let mut previous = None;
53 let mut glyphs = Vec::with_capacity(text.chars().count());
54
55 for ch in text.chars() {
56 let glyph_id = scaled_font.glyph_id(ch);
57 if let Some(previous_id) = previous {
58 caret_x += scaled_font.kern(previous_id, glyph_id);
59 }
60 glyphs.push(glyph_id.with_scale_and_position(scale, point(caret_x, origin.y)));
61 caret_x += scaled_font.h_advance(glyph_id);
62 previous = Some(glyph_id);
63 }
64
65 glyphs
66}
67
68pub fn line_advance_width(font: &impl Font, text: &str, font_size: f32) -> f32 {
69 let scale = PxScale::from(font_size);
70 let scaled_font = font.as_scaled(scale);
71 let mut width = 0.0;
72 let mut previous = None;
73
74 for ch in text.chars() {
75 let glyph_id = scaled_font.glyph_id(ch);
76 if let Some(previous_id) = previous {
77 width += scaled_font.kern(previous_id, glyph_id);
78 }
79 width += scaled_font.h_advance(glyph_id);
80 previous = Some(glyph_id);
81 }
82
83 width.max(0.0)
84}
85
86pub fn align_glyph_to_pixel_grid(mut glyph: Glyph, static_text_motion: bool) -> Glyph {
87 if static_text_motion {
88 glyph.position.x = glyph.position.x.round();
89 glyph.position.y = glyph.position.y.round();
90 }
91 glyph
92}
93
94pub fn glyph_pixel_bounds(font: &impl Font, glyph: &Glyph) -> Option<GlyphPixelBounds> {
95 let outlined = font.outline_glyph(glyph.clone())?;
96 Some(pixel_bounds_from_outlined(&outlined))
97}
98
99pub(crate) fn pixel_bounds_from_outlined(outlined: &OutlinedGlyph) -> GlyphPixelBounds {
100 let bounds = outlined.px_bounds();
101 GlyphPixelBounds {
102 min_x: bounds.min.x as i32,
103 min_y: bounds.min.y as i32,
104 max_x: bounds.max.x as i32,
105 max_y: bounds.max.y as i32,
106 }
107}
108
109#[cfg(test)]
110#[path = "tests/font_layout_tests.rs"]
111mod tests;