#[derive(Clone, Copy, Debug, PartialEq)]
pub struct TextStyleToken {
pub size: f32,
pub line_height: f32,
pub letter_spacing: f32,
pub weight: u16,
}
impl TextStyleToken {
pub const fn new(size: f32, line_height: f32, letter_spacing: f32, weight: u16) -> Self {
Self {
size,
line_height,
letter_spacing,
weight,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct TypographyScale {
pub display: TextStyleToken,
pub headline: TextStyleToken,
pub title: TextStyleToken,
pub body: TextStyleToken,
pub caption: TextStyleToken,
pub overline: TextStyleToken,
}
impl Default for TypographyScale {
fn default() -> Self {
Self {
display: TextStyleToken::new(32.0, 40.0, -0.5, 700),
headline: TextStyleToken::new(24.0, 32.0, -0.25, 600),
title: TextStyleToken::new(18.0, 24.0, 0.0, 600),
body: TextStyleToken::new(14.0, 20.0, 0.0, 400),
caption: TextStyleToken::new(12.0, 16.0, 0.2, 400),
overline: TextStyleToken::new(10.0, 14.0, 1.0, 500),
}
}
}
impl TypographyScale {
pub fn roles_descending(&self) -> [TextStyleToken; 6] {
[
self.display,
self.headline,
self.title,
self.body,
self.caption,
self.overline,
]
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sizes_are_monotonic_descending() {
let s = TypographyScale::default();
let roles = s.roles_descending();
for w in roles.windows(2) {
assert!(
w[0].size >= w[1].size,
"scale must not increase as it descends"
);
}
assert!(s.caption.size < s.body.size);
assert!(s.body.size < s.title.size);
assert!(s.title.size < s.headline.size);
assert!(s.headline.size < s.display.size);
}
#[test]
fn line_height_at_least_size() {
let s = TypographyScale::default();
for role in s.roles_descending() {
assert!(
role.line_height >= role.size,
"line height must cover the glyph size"
);
}
}
}