#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Style {
pub italic: bool,
pub weight: u16,
}
impl Style {
pub const REGULAR: Style = Style {
italic: false,
weight: 400,
};
pub fn regular() -> Self {
Self::REGULAR
}
pub fn italic() -> Self {
Self {
italic: true,
weight: 400,
}
}
#[must_use]
pub fn with_italic(mut self, italic: bool) -> Self {
self.italic = italic;
self
}
#[must_use]
pub fn with_weight(mut self, weight: u16) -> Self {
self.weight = weight.clamp(1, 1000);
self
}
}
impl Default for Style {
fn default() -> Self {
Self::REGULAR
}
}
pub const DEFAULT_SYNTHETIC_ITALIC_DEG: f32 = 12.0;
pub const ITALIC_ANGLE_EPSILON_DEG: f32 = 0.5;
pub const SYNTHETIC_BOLD_THRESHOLD: i32 = 200;
pub const SYNTHETIC_BOLD_PX_PER_WEIGHT_STEP_PER_PX: f32 = 0.0001;
pub fn synthetic_italic_shear(style: Style, face_italic_deg: f32) -> f32 {
if !style.italic {
return 0.0;
}
if face_italic_deg.abs() > ITALIC_ANGLE_EPSILON_DEG {
return 0.0;
}
DEFAULT_SYNTHETIC_ITALIC_DEG.to_radians().tan()
}
pub fn synthetic_bold_radius(style: Style, face_weight: u16, size_px: f32) -> f32 {
if size_px <= 0.0 || !size_px.is_finite() {
return 0.0;
}
let req = style.weight as i32;
let face = face_weight as i32;
if req <= face {
return 0.0;
}
let delta = req - face;
if delta < SYNTHETIC_BOLD_THRESHOLD {
return 0.0;
}
let raw = SYNTHETIC_BOLD_PX_PER_WEIGHT_STEP_PER_PX * size_px * delta as f32;
raw.max(1.0)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_is_regular_upright() {
assert_eq!(Style::default(), Style::REGULAR);
assert!(!Style::default().italic);
assert_eq!(Style::default().weight, 400);
}
#[test]
fn italic_builder_sets_flag() {
let s = Style::italic();
assert!(s.italic);
assert_eq!(s.weight, 400);
}
#[test]
fn weight_is_clamped() {
assert_eq!(Style::REGULAR.with_weight(0).weight, 1);
assert_eq!(Style::REGULAR.with_weight(2_000).weight, 1000);
assert_eq!(Style::REGULAR.with_weight(700).weight, 700);
}
#[test]
fn upright_request_yields_zero_shear() {
assert_eq!(synthetic_italic_shear(Style::REGULAR, 0.0), 0.0);
assert_eq!(synthetic_italic_shear(Style::REGULAR, -12.0), 0.0);
}
#[test]
fn italic_request_on_upright_face_yields_default_shear() {
let shear = synthetic_italic_shear(Style::italic(), 0.0);
let expected = DEFAULT_SYNTHETIC_ITALIC_DEG.to_radians().tan();
assert!(
(shear - expected).abs() < 1e-6,
"shear = {shear}, expected = {expected}"
);
assert!(shear > 0.20 && shear < 0.22, "shear = {shear}");
}
#[test]
fn italic_request_on_italic_face_yields_zero() {
assert_eq!(synthetic_italic_shear(Style::italic(), -12.0), 0.0);
assert_eq!(synthetic_italic_shear(Style::italic(), 8.0), 0.0);
}
#[test]
fn epsilon_band_still_synthesises() {
let shear = synthetic_italic_shear(Style::italic(), 0.3);
assert!(shear > 0.20);
}
#[test]
fn bold_radius_zero_when_request_at_or_below_face() {
assert_eq!(synthetic_bold_radius(Style::REGULAR, 400, 32.0), 0.0);
assert_eq!(synthetic_bold_radius(Style::REGULAR, 700, 32.0), 0.0);
let bold = Style::REGULAR.with_weight(700);
assert_eq!(synthetic_bold_radius(bold, 700, 32.0), 0.0);
let medium = Style::REGULAR.with_weight(500);
assert_eq!(synthetic_bold_radius(medium, 400, 32.0), 0.0);
}
#[test]
fn bold_radius_grows_with_weight_delta() {
let bold = Style::REGULAR.with_weight(700);
let black = Style::REGULAR.with_weight(900);
let r_bold = synthetic_bold_radius(bold, 400, 32.0);
let r_black = synthetic_bold_radius(black, 400, 32.0);
assert!(r_bold > 0.0);
assert!(r_black > r_bold);
}
#[test]
fn bold_radius_grows_with_size_in_unclamped_regime() {
let bold = Style::REGULAR.with_weight(700);
let r_med = synthetic_bold_radius(bold, 400, 64.0);
let r_huge = synthetic_bold_radius(bold, 400, 256.0);
assert!(r_med > 1.0, "64-px bold should exceed clamp: got {r_med}");
assert!(r_huge > r_med);
let ratio = r_huge / r_med;
assert!(
(ratio - 4.0).abs() < 1e-3,
"expected 4× ratio in unclamped regime, got {ratio}"
);
}
#[test]
fn bold_radius_clamp_kicks_in_at_small_sizes() {
let bold = Style::REGULAR.with_weight(700);
let r = synthetic_bold_radius(bold, 400, 16.0);
assert_eq!(r, 1.0, "small-size bold should clamp to 1.0 px, got {r}");
}
#[test]
fn bold_radius_invalid_size_returns_zero() {
let bold = Style::REGULAR.with_weight(700);
assert_eq!(synthetic_bold_radius(bold, 400, 0.0), 0.0);
assert_eq!(synthetic_bold_radius(bold, 400, -1.0), 0.0);
assert_eq!(synthetic_bold_radius(bold, 400, f32::NAN), 0.0);
}
}