const AVG_CHAR_WIDTH_RATIO: f64 = 0.6;
pub fn approx_width(text: &str, font_size: f64, letter_spacing: f64) -> f64 {
let advance = font_size * AVG_CHAR_WIDTH_RATIO;
let step = (advance + letter_spacing).abs();
text.split('\n')
.map(|line| match line.chars().count() {
0 => 0.0,
n => advance + (n as f64 - 1.0) * step,
})
.fold(0.0_f64, f64::max)
}
const LINE_LEADING: f64 = 1.2;
const SINGLE_LINE_EM: f64 = 1.0;
pub fn approx_height(text: &str, font_size: f64, line_spacing: f64) -> f64 {
let line_count = text.split('\n').count().max(1);
if line_count == 1 {
return SINGLE_LINE_EM * font_size;
}
let leading = LINE_LEADING * font_size;
let step = (leading + line_spacing).abs();
leading + (line_count as f64 - 1.0) * step
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn single_line_width_scales_with_chars_and_size() {
let w = approx_width("hello", 13.0, 0.0);
assert!((w - 39.0).abs() < 0.01, "got {}", w);
}
#[test]
fn multi_line_picks_widest() {
let w = approx_width("hi\nhello", 10.0, 0.0);
assert!((w - 30.0).abs() < 0.01, "got {}", w);
}
#[test]
fn letter_spacing_widens_by_the_gaps_only() {
assert!((approx_width("hello", 13.0, 4.0) - 55.0).abs() < 0.01);
assert!((approx_width("x", 13.0, 4.0) - approx_width("x", 13.0, 0.0)).abs() < 0.01);
}
#[test]
fn height_grows_with_lines() {
let h1 = approx_height("a", 10.0, 0.0);
let h2 = approx_height("a\nb", 10.0, 0.0);
assert!((h1 - 10.0).abs() < 0.01, "got {h1}");
assert!((h2 - 24.0).abs() < 0.01, "got {h2}");
}
#[test]
fn line_spacing_adds_between_lines_only() {
assert!((approx_height("a\nb", 10.0, 6.0) - 30.0).abs() < 0.01);
assert!((approx_height("a", 10.0, 6.0) - approx_height("a", 10.0, 0.0)).abs() < 0.01);
}
#[test]
fn negative_letter_spacing_collapses_then_grows_back() {
let advance = 9.0;
assert!((approx_width("hello", 15.0, -9.0) - advance).abs() < 0.01);
assert!((approx_width("hello", 15.0, -18.0) - 45.0).abs() < 0.01);
assert!(approx_width("hello", 15.0, -1000.0) >= advance);
}
#[test]
fn negative_line_spacing_collapses_then_grows_back() {
let leading = 12.0;
assert!((approx_height("a\nb\nc", 10.0, -12.0) - leading).abs() < 0.01);
assert!(approx_height("a\nb\nc", 10.0, -1000.0) >= leading);
}
}