mirror of
https://github.com/typst/typst
synced 2025-05-13 12:36:23 +08:00
227 lines
5.9 KiB
Rust
227 lines
5.9 KiB
Rust
use std::fmt::{self, Debug, Formatter};
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
/// Properties that distinguish a font from other fonts in the same family.
|
|
#[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
|
#[derive(Serialize, Deserialize)]
|
|
pub struct FontVariant {
|
|
/// The style of the font (normal / italic / oblique).
|
|
pub style: FontStyle,
|
|
/// How heavy the font is (100 - 900).
|
|
pub weight: FontWeight,
|
|
/// How condensed or expanded the font is (0.5 - 2.0).
|
|
pub stretch: FontStretch,
|
|
}
|
|
|
|
impl FontVariant {
|
|
/// Create a variant from its three components.
|
|
pub fn new(style: FontStyle, weight: FontWeight, stretch: FontStretch) -> Self {
|
|
Self { style, weight, stretch }
|
|
}
|
|
}
|
|
|
|
impl Debug for FontVariant {
|
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
write!(f, "{:?}-{:?}-{:?}", self.style, self.weight, self.stretch)
|
|
}
|
|
}
|
|
|
|
/// The style of a font.
|
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
|
#[derive(Serialize, Deserialize)]
|
|
#[serde(rename_all = "kebab-case")]
|
|
pub enum FontStyle {
|
|
/// The default style.
|
|
Normal,
|
|
/// A cursive style.
|
|
Italic,
|
|
/// A slanted style.
|
|
Oblique,
|
|
}
|
|
|
|
impl FontStyle {
|
|
/// The conceptual distance between the styles, expressed as a number.
|
|
pub fn distance(self, other: Self) -> u16 {
|
|
if self == other {
|
|
0
|
|
} else if self != Self::Normal && other != Self::Normal {
|
|
1
|
|
} else {
|
|
2
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Default for FontStyle {
|
|
fn default() -> Self {
|
|
Self::Normal
|
|
}
|
|
}
|
|
|
|
/// The weight of a font.
|
|
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
|
#[derive(Serialize, Deserialize)]
|
|
#[serde(transparent)]
|
|
pub struct FontWeight(u16);
|
|
|
|
impl FontWeight {
|
|
/// Thin weight (100).
|
|
pub const THIN: Self = Self(100);
|
|
|
|
/// Extra light weight (200).
|
|
pub const EXTRALIGHT: Self = Self(200);
|
|
|
|
/// Light weight (300).
|
|
pub const LIGHT: Self = Self(300);
|
|
|
|
/// Regular weight (400).
|
|
pub const REGULAR: Self = Self(400);
|
|
|
|
/// Medium weight (500).
|
|
pub const MEDIUM: Self = Self(500);
|
|
|
|
/// Semibold weight (600).
|
|
pub const SEMIBOLD: Self = Self(600);
|
|
|
|
/// Bold weight (700).
|
|
pub const BOLD: Self = Self(700);
|
|
|
|
/// Extrabold weight (800).
|
|
pub const EXTRABOLD: Self = Self(800);
|
|
|
|
/// Black weight (900).
|
|
pub const BLACK: Self = Self(900);
|
|
|
|
/// Create a font weight from a number between 100 and 900, clamping it if
|
|
/// necessary.
|
|
pub fn from_number(weight: u16) -> Self {
|
|
Self(weight.max(100).min(900))
|
|
}
|
|
|
|
/// The number between 100 and 900.
|
|
pub fn to_number(self) -> u16 {
|
|
self.0
|
|
}
|
|
|
|
/// Add (or remove) weight, saturating at the boundaries of 100 and 900.
|
|
pub fn thicken(self, delta: i16) -> Self {
|
|
Self((self.0 as i16).saturating_add(delta).max(100).min(900) as u16)
|
|
}
|
|
|
|
/// The absolute number distance between this and another font weight.
|
|
pub fn distance(self, other: Self) -> u16 {
|
|
(self.0 as i16 - other.0 as i16).unsigned_abs()
|
|
}
|
|
}
|
|
|
|
impl Default for FontWeight {
|
|
fn default() -> Self {
|
|
Self::REGULAR
|
|
}
|
|
}
|
|
|
|
impl Debug for FontWeight {
|
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
write!(f, "{}", self.0)
|
|
}
|
|
}
|
|
|
|
/// The width of a font.
|
|
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
|
#[derive(Serialize, Deserialize)]
|
|
#[serde(transparent)]
|
|
pub struct FontStretch(u16);
|
|
|
|
impl FontStretch {
|
|
/// Ultra-condensed stretch (50%).
|
|
pub const ULTRA_CONDENSED: Self = Self(500);
|
|
|
|
/// Extra-condensed stretch weight (62.5%).
|
|
pub const EXTRA_CONDENSED: Self = Self(625);
|
|
|
|
/// Condensed stretch (75%).
|
|
pub const CONDENSED: Self = Self(750);
|
|
|
|
/// Semi-condensed stretch (87.5%).
|
|
pub const SEMI_CONDENSED: Self = Self(875);
|
|
|
|
/// Normal stretch (100%).
|
|
pub const NORMAL: Self = Self(1000);
|
|
|
|
/// Semi-expanded stretch (112.5%).
|
|
pub const SEMI_EXPANDED: Self = Self(1125);
|
|
|
|
/// Expanded stretch (125%).
|
|
pub const EXPANDED: Self = Self(1250);
|
|
|
|
/// Extra-expanded stretch (150%).
|
|
pub const EXTRA_EXPANDED: Self = Self(1500);
|
|
|
|
/// Ultra-expanded stretch (200%).
|
|
pub const ULTRA_EXPANDED: Self = Self(2000);
|
|
|
|
/// Create a font stretch from a ratio between 0.5 and 2.0, clamping it if
|
|
/// necessary.
|
|
pub fn from_ratio(ratio: f32) -> Self {
|
|
Self((ratio.max(0.5).min(2.0) * 1000.0) as u16)
|
|
}
|
|
|
|
/// Create a font stretch from an OpenType-style number between 1 and 9,
|
|
/// clamping it if necessary.
|
|
pub fn from_number(stretch: u16) -> Self {
|
|
match stretch {
|
|
0 | 1 => Self::ULTRA_CONDENSED,
|
|
2 => Self::EXTRA_CONDENSED,
|
|
3 => Self::CONDENSED,
|
|
4 => Self::SEMI_CONDENSED,
|
|
5 => Self::NORMAL,
|
|
6 => Self::SEMI_EXPANDED,
|
|
7 => Self::EXPANDED,
|
|
8 => Self::EXTRA_EXPANDED,
|
|
_ => Self::ULTRA_EXPANDED,
|
|
}
|
|
}
|
|
|
|
/// The ratio between 0.5 and 2.0 corresponding to this stretch.
|
|
pub fn to_ratio(self) -> f32 {
|
|
self.0 as f32 / 1000.0
|
|
}
|
|
|
|
/// The absolute ratio distance between this and another font stretch.
|
|
pub fn distance(self, other: Self) -> f32 {
|
|
(self.to_ratio() - other.to_ratio()).abs()
|
|
}
|
|
}
|
|
|
|
impl Default for FontStretch {
|
|
fn default() -> Self {
|
|
Self::NORMAL
|
|
}
|
|
}
|
|
|
|
impl Debug for FontStretch {
|
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
write!(f, "{}%", 100.0 * self.to_ratio())
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_font_weight_distance() {
|
|
let d = |a, b| FontWeight(a).distance(FontWeight(b));
|
|
assert_eq!(d(500, 200), 300);
|
|
assert_eq!(d(500, 500), 0);
|
|
assert_eq!(d(500, 900), 400);
|
|
assert_eq!(d(10, 100), 90);
|
|
}
|
|
|
|
#[test]
|
|
fn test_font_stretch_debug() {
|
|
assert_eq!(format!("{:?}", FontStretch::EXPANDED), "125%")
|
|
}
|
|
}
|