use crate::style::computed::FontFamily;
static HELVETICA_WIDTHS: [u16; 95] = [
278, 278, 355, 556, 556, 889, 667, 191, 333, 333, 389, 584, 278, 333, 278, 278, 556, 556, 556, 556, 556, 556, 556, 556, 556, 556, 278, 278, 584, 584, 584, 556, 1015, 667, 667, 722, 722, 667, 611, 778, 722, 278, 500, 667, 556, 833, 722, 778, 667, 778, 722, 667, 611, 722, 667, 944, 667, 667, 611, 278, 278, 278, 469, 556, 333, 556, 556, 500, 556, 556, 278, 556, 556, 222, 222, 500, 222, 833, 556, 556, 556, 556, 333, 500, 278, 556, 500, 722, 500, 500, 500, 334, 260, 334, 584, ];
static HELVETICA_BOLD_WIDTHS: [u16; 95] = [
278, 333, 474, 556, 556, 889, 722, 238, 333, 333, 389, 584, 278, 333, 278, 278, 556, 556, 556, 556, 556, 556, 556, 556, 556, 556, 333, 333, 584, 584, 584, 611, 975, 722, 722, 722, 722, 667, 611, 778, 722, 278, 556, 722, 611, 833, 722, 778, 667, 778, 722, 667, 611, 722, 667, 944, 667, 667, 611, 333, 278, 333, 584, 556, 333, 556, 611, 556, 611, 556, 333, 611, 611, 278, 278, 556, 278, 889, 611, 611, 611, 611, 389, 556, 333, 611, 556, 778, 556, 556, 500, 389, 280, 389, 584, ];
const DEFAULT_WIDTH: u16 = 556;
pub(crate) fn ascender_ratio(font_family: &FontFamily) -> f32 {
match font_family {
FontFamily::Helvetica | FontFamily::Custom(_) => 0.718,
FontFamily::TimesRoman => 0.683,
FontFamily::Courier => 0.629,
}
}
pub(crate) fn descender_ratio(font_family: &FontFamily) -> f32 {
match font_family {
FontFamily::Helvetica | FontFamily::Custom(_) => 0.207,
FontFamily::TimesRoman => 0.217,
FontFamily::Courier => 0.157,
}
}
const COURIER_WIDTH: u16 = 600;
pub(crate) fn char_width(ch: char, font_size: f32, font_family: &FontFamily, bold: bool) -> f32 {
let afm = match font_family {
FontFamily::Courier => COURIER_WIDTH,
FontFamily::Helvetica | FontFamily::Custom(_) => helvetica_char_afm(ch, bold),
FontFamily::TimesRoman => {
helvetica_char_afm(ch, bold)
}
};
afm as f32 / 1000.0 * font_size
}
pub(crate) fn str_width(s: &str, font_size: f32, font_family: &FontFamily, bold: bool) -> f32 {
s.chars()
.map(|c| char_width(c, font_size, font_family, bold))
.sum()
}
fn helvetica_char_afm(ch: char, bold: bool) -> u16 {
let code = ch as u32;
if (32..=126).contains(&code) {
let idx = (code - 32) as usize;
if bold {
HELVETICA_BOLD_WIDTHS[idx]
} else {
HELVETICA_WIDTHS[idx]
}
} else {
DEFAULT_WIDTH
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn helvetica_space_width() {
let w = char_width(' ', 10.0, &FontFamily::Helvetica, false);
assert!((w - 2.78).abs() < 0.01);
}
#[test]
fn helvetica_bold_a_wider_than_regular() {
let regular = char_width('A', 12.0, &FontFamily::Helvetica, false);
let bold = char_width('A', 12.0, &FontFamily::Helvetica, true);
assert!(bold > regular);
}
#[test]
fn courier_fixed_width() {
let w1 = char_width('i', 10.0, &FontFamily::Courier, false);
let w2 = char_width('W', 10.0, &FontFamily::Courier, false);
assert!((w1 - w2).abs() < f32::EPSILON);
}
#[test]
fn str_width_hello() {
let w = str_width("Hello", 10.0, &FontFamily::Helvetica, false);
assert!((w - 22.78).abs() < 0.01);
}
#[test]
fn non_ascii_uses_default() {
let w = char_width('\u{00E9}', 10.0, &FontFamily::Helvetica, false);
assert!((w - 5.56).abs() < 0.01);
}
#[test]
fn helvetica_uppercase_wider() {
let w_upper = char_width('W', 12.0, &FontFamily::Helvetica, false);
let w_lower = char_width('i', 12.0, &FontFamily::Helvetica, false);
assert!(
w_upper > w_lower,
"W ({w_upper}) should be wider than i ({w_lower})"
);
}
#[test]
fn ascender_ratio_helvetica() {
let r = ascender_ratio(&FontFamily::Helvetica);
assert!((r - 0.718).abs() < f32::EPSILON);
}
#[test]
fn descender_ratio_helvetica() {
let r = descender_ratio(&FontFamily::Helvetica);
assert!((r - 0.207).abs() < f32::EPSILON);
}
#[test]
fn ascender_plus_descender_less_than_one() {
for family in &[
FontFamily::Helvetica,
FontFamily::TimesRoman,
FontFamily::Courier,
] {
let sum = ascender_ratio(family) + descender_ratio(family);
assert!(
sum < 1.0,
"ascender + descender should be < 1.0 em for {family:?}"
);
}
}
#[test]
fn bold_wider_than_regular() {
let regular = char_width('b', 12.0, &FontFamily::Helvetica, false);
let bold = char_width('b', 12.0, &FontFamily::Helvetica, true);
assert!(
bold > regular,
"Bold 'b' ({bold}) should be wider than regular 'b' ({regular})"
);
}
}