use super::GlyphMapping;
#[derive(Debug, Clone)]
pub struct FontMetrics {
pub units_per_em: u16,
pub ascent: i16,
pub descent: i16,
pub line_gap: i16,
pub cap_height: i16,
pub x_height: i16,
}
impl Default for FontMetrics {
fn default() -> Self {
FontMetrics {
units_per_em: 1000,
ascent: 750,
descent: -250,
line_gap: 200,
cap_height: 700,
x_height: 500,
}
}
}
impl FontMetrics {
pub fn to_user_space(&self, value: i16, font_size: f32) -> f32 {
(value as f32 * font_size) / self.units_per_em as f32
}
pub fn line_height(&self, font_size: f32) -> f32 {
let total_height = self.ascent - self.descent + self.line_gap;
self.to_user_space(total_height, font_size)
}
pub fn get_ascent(&self, font_size: f32) -> f32 {
self.to_user_space(self.ascent, font_size)
}
pub fn get_descent(&self, font_size: f32) -> f32 {
self.to_user_space(-self.descent, font_size)
}
pub fn measure_text(
&self,
text: &str,
font_size: f32,
glyph_mapping: &GlyphMapping,
) -> TextMeasurement {
let mut width = 0.0;
let mut glyph_count = 0;
for ch in text.chars() {
if let Some(glyph_width) = glyph_mapping.get_char_width(ch) {
width += self.to_user_space(glyph_width as i16, font_size);
glyph_count += 1;
} else {
width += font_size * 0.6;
}
}
TextMeasurement {
width,
height: self.line_height(font_size),
ascent: self.get_ascent(font_size),
descent: self.get_descent(font_size),
glyph_count,
}
}
}
#[derive(Debug, Clone)]
pub struct TextMeasurement {
pub width: f32,
pub height: f32,
pub ascent: f32,
pub descent: f32,
pub glyph_count: usize,
}
impl TextMeasurement {
pub fn baseline_offset(&self) -> f32 {
self.ascent
}
pub fn bounding_box(&self) -> [f32; 4] {
[0.0, -self.descent, self.width, self.height]
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_font_metrics_conversion() {
let metrics = FontMetrics {
units_per_em: 1000,
ascent: 800,
descent: -200,
line_gap: 200,
cap_height: 700,
x_height: 500,
};
assert_eq!(metrics.to_user_space(1000, 12.0), 12.0);
assert_eq!(metrics.to_user_space(500, 12.0), 6.0);
assert_eq!(metrics.line_height(12.0), 14.4);
assert_eq!(metrics.get_ascent(12.0), 9.6); assert_eq!(metrics.get_descent(12.0), 2.4); }
#[test]
fn test_text_measurement() {
let metrics = FontMetrics {
units_per_em: 1000,
ascent: 800,
descent: -200,
line_gap: 200,
cap_height: 700,
x_height: 500,
};
let mut glyph_mapping = GlyphMapping::default();
for ch in "Hello".chars() {
let glyph_id = ch as u16;
glyph_mapping.add_mapping(ch, glyph_id);
glyph_mapping.set_glyph_width(glyph_id, 600); }
let measurement = metrics.measure_text("Hello", 12.0, &glyph_mapping);
assert_eq!(measurement.glyph_count, 5);
assert_eq!(measurement.width, 36.0); assert_eq!(measurement.height, 14.4);
}
#[test]
fn test_font_metrics_default() {
let metrics = FontMetrics::default();
assert_eq!(metrics.units_per_em, 1000);
assert_eq!(metrics.ascent, 750);
assert_eq!(metrics.descent, -250);
assert_eq!(metrics.line_gap, 200);
assert_eq!(metrics.cap_height, 700);
assert_eq!(metrics.x_height, 500);
}
#[test]
fn test_to_user_space_different_units() {
let metrics = FontMetrics {
units_per_em: 2048,
ascent: 1638,
descent: -410,
line_gap: 400,
cap_height: 1434,
x_height: 1024,
};
assert_eq!(metrics.to_user_space(2048, 24.0), 24.0);
assert_eq!(metrics.to_user_space(1024, 24.0), 12.0);
assert_eq!(metrics.to_user_space(512, 24.0), 6.0);
}
#[test]
fn test_text_measurement_missing_glyphs() {
let metrics = FontMetrics::default();
let glyph_mapping = GlyphMapping::default();
let measurement = metrics.measure_text("Test", 10.0, &glyph_mapping);
assert_eq!(measurement.glyph_count, 0);
assert_eq!(measurement.width, 24.0); }
#[test]
fn test_text_measurement_mixed_glyphs() {
let metrics = FontMetrics::default();
let mut glyph_mapping = GlyphMapping::default();
glyph_mapping.add_mapping('T', 1);
glyph_mapping.set_glyph_width(1, 700);
glyph_mapping.add_mapping('s', 2);
glyph_mapping.set_glyph_width(2, 500);
let measurement = metrics.measure_text("Test", 10.0, &glyph_mapping);
assert_eq!(measurement.glyph_count, 2);
assert_eq!(measurement.width, 7.0 + 6.0 + 5.0 + 6.0);
}
#[test]
fn test_text_measurement_baseline_offset() {
let metrics = FontMetrics {
units_per_em: 1000,
ascent: 800,
descent: -200,
line_gap: 200,
cap_height: 700,
x_height: 500,
};
let glyph_mapping = GlyphMapping::default();
let measurement = metrics.measure_text("", 12.0, &glyph_mapping);
assert_eq!(measurement.baseline_offset(), 9.6); assert_eq!(measurement.ascent, 9.6);
}
#[test]
fn test_text_measurement_bounding_box() {
let metrics = FontMetrics {
units_per_em: 1000,
ascent: 800,
descent: -200,
line_gap: 200,
cap_height: 700,
x_height: 500,
};
let mut glyph_mapping = GlyphMapping::default();
glyph_mapping.add_mapping('A', 1);
glyph_mapping.set_glyph_width(1, 1000);
let measurement = metrics.measure_text("A", 10.0, &glyph_mapping);
let bbox = measurement.bounding_box();
assert_eq!(bbox[0], 0.0); assert_eq!(bbox[1], -2.0); assert_eq!(bbox[2], 10.0); assert_eq!(bbox[3], 12.0); }
#[test]
fn test_line_height_zero_line_gap() {
let metrics = FontMetrics {
units_per_em: 1000,
ascent: 800,
descent: -200,
line_gap: 0,
cap_height: 700,
x_height: 500,
};
assert_eq!(metrics.line_height(10.0), 10.0); }
#[test]
fn test_negative_values() {
let metrics = FontMetrics {
units_per_em: 1000,
ascent: 800,
descent: -200,
line_gap: -100, cap_height: 700,
x_height: 500,
};
assert_eq!(metrics.line_height(10.0), 9.0); }
#[test]
fn test_very_small_font_size() {
let metrics = FontMetrics::default();
assert_eq!(metrics.to_user_space(1000, 0.1), 0.1);
assert_eq!(metrics.get_ascent(0.1), 0.075);
assert_eq!(metrics.get_descent(0.1), 0.025);
}
#[test]
fn test_very_large_font_size() {
let metrics = FontMetrics::default();
assert_eq!(metrics.to_user_space(1000, 1000.0), 1000.0);
assert_eq!(metrics.get_ascent(1000.0), 750.0);
assert_eq!(metrics.get_descent(1000.0), 250.0);
}
#[test]
fn test_empty_text_measurement() {
let metrics = FontMetrics::default();
let glyph_mapping = GlyphMapping::default();
let measurement = metrics.measure_text("", 12.0, &glyph_mapping);
assert_eq!(measurement.width, 0.0);
assert_eq!(measurement.glyph_count, 0);
assert_eq!(measurement.height, 14.4); }
#[test]
fn test_unicode_text_measurement() {
let metrics = FontMetrics::default();
let mut glyph_mapping = GlyphMapping::default();
glyph_mapping.add_mapping('€', 100);
glyph_mapping.set_glyph_width(100, 800);
glyph_mapping.add_mapping('™', 101);
glyph_mapping.set_glyph_width(101, 900);
let measurement = metrics.measure_text("€™", 10.0, &glyph_mapping);
assert_eq!(measurement.glyph_count, 2);
assert_eq!(measurement.width, 17.0); }
}