use crate::vt::{Config, FONT_SIZE_STEPS, Metrics, Section, split};
#[must_use]
pub fn auto_font_size(sections: &[Section], config: &Config, metrics: &Metrics<'_>) -> f32 {
if crate::geom::width(config.plate) <= 0.0 {
return 0.0;
}
let span = if config.multi_line {
FONT_SIZE_STEPS.len() / 4
} else {
FONT_SIZE_STEPS.len()
};
let steps = FONT_SIZE_STEPS.get(..span).unwrap_or(&FONT_SIZE_STEPS);
let first_too_big = steps
.iter()
.position(|size| is_bigger(sections, config, metrics, f32::from(*size)));
let chosen = match first_too_big {
None => steps.last().copied(),
Some(0) => steps.first().copied(),
Some(index) => steps.get(index - 1).copied(),
};
chosen.map_or(0.0, f32::from)
}
#[must_use]
pub fn is_bigger(
sections: &[Section],
config: &Config,
metrics: &Metrics<'_>,
font_size: f32,
) -> bool {
let (mut width, mut height) = (0.0_f32, 0.0_f32);
for section in sections {
let mut probe = section.clone();
let size = split::split_at_size(&mut probe, config, metrics, false, font_size);
width = crate::geom::width(size).max(width);
height += crate::geom::height(size);
if crate::geom::is_float_bigger(width, crate::geom::width(config.plate))
|| crate::geom::is_float_bigger(height, crate::geom::height(config.plate))
{
return true;
}
}
false
}
#[cfg(test)]
mod tests {
use super::auto_font_size;
use crate::geom;
use crate::vt::{Config, Section, Word, stub};
fn sections(text: &str) -> Vec<Section> {
vec![Section {
words: text
.chars()
.map(|ch| Word {
ch: ch as u32,
x: 0.0,
y: 0.0,
tail: 0.0,
is_rtl: false,
})
.collect(),
lines: Vec::new(),
rect: kurbo::Rect::ZERO,
}]
}
#[test]
fn a_plate_with_no_width_answers_zero() {
let config = Config {
plate: geom::rect(0.0, 0.0, 0.0, 100.0),
..Config::default()
};
assert!(auto_font_size(§ions("hi"), &config, &stub::metrics()).abs() < f32::EPSILON);
}
#[test]
fn a_plate_that_fits_even_the_largest_step_gets_the_largest() {
let config = Config {
plate: geom::rect(0.0, 0.0, 100_000.0, 100_000.0),
..Config::default()
};
assert!(
(auto_font_size(§ions("hi"), &config, &stub::metrics()) - 144.0).abs()
< f32::EPSILON
);
}
#[test]
fn a_plate_too_small_for_anything_gets_the_smallest_candidate() {
let config = Config {
plate: geom::rect(0.0, 0.0, 0.001, 0.001),
..Config::default()
};
assert!(
(auto_font_size(§ions("hi"), &config, &stub::metrics()) - 4.0).abs() < f32::EPSILON
);
}
#[test]
fn a_multi_line_field_never_sizes_above_twelve() {
let config = Config {
plate: geom::rect(0.0, 0.0, 100_000.0, 100_000.0),
multi_line: true,
..Config::default()
};
assert!(
(auto_font_size(§ions("hi"), &config, &stub::metrics()) - 12.0).abs()
< f32::EPSILON
);
}
#[test]
fn the_answer_is_the_largest_step_that_still_fits() {
for (plate, expected) in [
(geom::rect(0.0, 0.0, 0.6, 100.0), 30.0),
(geom::rect(0.0, 0.0, 0.2, 100.0), 10.0),
(geom::rect(0.0, 0.0, 10_000.0, 0.1), 8.0),
] {
let config = Config {
plate,
..Config::default()
};
let chosen = auto_font_size(§ions("hi"), &config, &stub::metrics());
assert!(
(chosen - expected).abs() < f32::EPSILON,
"{plate:?}: got {chosen}, wanted {expected}"
);
}
}
}