use crate::font::FontAttrs;
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct FontSynthesis {
pub embolden: f32,
pub skew: f32,
}
const FAKE_ITALIC_SKEW: f32 = -0.25;
const FAKE_BOLD_MIN_WEIGHT: u16 = 600;
const FAKE_BOLD_MIN_GAIN: u16 = 200;
const FAKE_BOLD_WIDTHS: [(f32, f32); 2] = [(9.0, 1.0 / 24.0), (36.0, 1.0 / 32.0)];
const EMBOLDEN_CORNER_REACH: f32 = 5.76;
impl FontSynthesis {
pub const NONE: FontSynthesis = FontSynthesis {
embolden: 0.0,
skew: 0.0,
};
pub fn needed(wanted: FontAttrs, face: FontAttrs, size: f32) -> FontSynthesis {
let fake_bold = wanted.weight >= FAKE_BOLD_MIN_WEIGHT
&& wanted.weight.saturating_sub(face.weight) >= FAKE_BOLD_MIN_GAIN;
let fake_italic = wanted.italic && !face.italic;
FontSynthesis {
embolden: if fake_bold {
fake_bold_width(size)
} else {
0.0
},
skew: if fake_italic { FAKE_ITALIC_SKEW } else { 0.0 },
}
}
pub fn is_none(&self) -> bool {
self.embolden == 0.0 && self.skew == 0.0
}
pub(crate) fn edge_growth(&self, size: f32) -> f32 {
self.embolden * size / 2.0
}
pub fn ink_box(
&self,
(x_min, y_min, x_max, y_max): (f32, f32, f32, f32),
size: f32,
) -> (f32, f32, f32, f32) {
let reach = self.edge_growth(size) * EMBOLDEN_CORNER_REACH;
let (x_min, y_min, x_max, y_max) =
(x_min - reach, y_min - reach, x_max + reach, y_max + reach);
let (low, high) = (-self.skew * y_min, -self.skew * y_max);
(x_min + low.min(high), y_min, x_max + low.max(high), y_max)
}
}
fn fake_bold_width(size: f32) -> f32 {
let [(small_size, small_width), (large_size, large_width)] = FAKE_BOLD_WIDTHS;
let along = ((size - small_size) / (large_size - small_size)).clamp(0.0, 1.0);
small_width + (large_width - small_width) * along
}
#[cfg(test)]
mod tests {
use super::*;
fn attrs(weight: u16, italic: bool) -> FontAttrs {
FontAttrs {
weight,
italic,
..FontAttrs::default()
}
}
#[test]
fn fakes_only_what_skia_fakes() {
let regular = attrs(400, false);
let at = |wanted: FontAttrs, face: FontAttrs| FontSynthesis::needed(wanted, face, 16.0);
assert!(at(attrs(600, false), regular).embolden > 0.0);
assert!(at(attrs(700, false), attrs(500, false)).embolden > 0.0);
assert_eq!(
at(attrs(500, false), attrs(300, false)),
FontSynthesis::NONE
);
assert_eq!(
at(attrs(700, false), attrs(600, false)),
FontSynthesis::NONE
);
assert_eq!(
at(attrs(400, false), attrs(700, false)),
FontSynthesis::NONE
);
assert_eq!(at(attrs(400, true), regular).skew, -0.25);
assert_eq!(at(attrs(400, true), attrs(400, true)), FontSynthesis::NONE);
assert_eq!(at(attrs(400, false), attrs(400, true)), FontSynthesis::NONE);
}
#[test]
fn fake_bold_width_eases_from_a_24th_to_a_32nd() {
assert_eq!(fake_bold_width(6.0), 1.0 / 24.0);
assert_eq!(fake_bold_width(9.0), 1.0 / 24.0);
assert_eq!(fake_bold_width(36.0), 1.0 / 32.0);
assert_eq!(fake_bold_width(72.0), 1.0 / 32.0);
let middle = fake_bold_width(22.5);
assert!((middle - (1.0 / 24.0 + 1.0 / 32.0) / 2.0).abs() < 1e-6);
}
}