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