| //! Primary attributes typically used for font classification and selection. |
| |
| use read_fonts::{ |
| tables::{ |
| head::{Head, MacStyle}, |
| os2::{Os2, SelectionFlags}, |
| post::Post, |
| }, |
| FontRef, TableProvider, |
| }; |
| |
| /// Stretch, style and weight attributes of a font. |
| /// |
| /// Variable fonts may contain axes that modify these attributes. The |
| /// [new](Self::new) method on this type returns values for the default |
| /// instance. |
| /// |
| /// These are derived from values in the |
| /// [OS/2](https://learn.microsoft.com/en-us/typography/opentype/spec/os2) if |
| /// available. Otherwise, they are retrieved from the |
| /// [head](https://learn.microsoft.com/en-us/typography/opentype/spec/head) |
| /// table. |
| #[derive(Copy, Clone, PartialEq, Debug, Default)] |
| pub struct Attributes { |
| pub stretch: Stretch, |
| pub style: Style, |
| pub weight: Weight, |
| } |
| |
| impl Attributes { |
| /// Extracts the stretch, style and weight attributes for the default |
| /// instance of the given font. |
| pub fn new(font: &FontRef) -> Self { |
| if let Ok(os2) = font.os2() { |
| // Prefer values from the OS/2 table if it exists. We also use |
| // the post table to extract the angle for oblique styles. |
| Self::from_os2_post(os2, font.post().ok()) |
| } else if let Ok(head) = font.head() { |
| // Otherwise, fall back to the macStyle field of the head table. |
| Self::from_head(head) |
| } else { |
| Self::default() |
| } |
| } |
| |
| fn from_os2_post(os2: Os2, post: Option<Post>) -> Self { |
| let stretch = Stretch::from_width_class(os2.us_width_class()); |
| // Bits 1 and 9 of the fsSelection field signify italic and |
| // oblique, respectively. |
| // See: <https://learn.microsoft.com/en-us/typography/opentype/spec/os2#fsselection> |
| let fs_selection = os2.fs_selection(); |
| let style = if fs_selection.contains(SelectionFlags::ITALIC) { |
| Style::Italic |
| } else if fs_selection.contains(SelectionFlags::OBLIQUE) { |
| let angle = post.map(|post| post.italic_angle().to_f64() as f32); |
| Style::Oblique(angle) |
| } else { |
| Style::Normal |
| }; |
| // The usWeightClass field is specified with a 1-1000 range, but |
| // we don't clamp here because variable fonts could potentially |
| // have a value outside of that range. |
| // See <https://learn.microsoft.com/en-us/typography/opentype/spec/os2#usweightclass> |
| let weight = Weight(os2.us_weight_class() as f32); |
| Self { |
| stretch, |
| style, |
| weight, |
| } |
| } |
| |
| fn from_head(head: Head) -> Self { |
| let mac_style = head.mac_style(); |
| let style = mac_style |
| .contains(MacStyle::ITALIC) |
| .then_some(Style::Italic) |
| .unwrap_or_default(); |
| let weight = mac_style |
| .contains(MacStyle::BOLD) |
| .then_some(Weight::BOLD) |
| .unwrap_or_default(); |
| Self { |
| stretch: Stretch::default(), |
| style, |
| weight, |
| } |
| } |
| } |
| |
| /// Visual width of a font-- a relative change from the normal aspect |
| /// ratio, typically in the range 0.5 to 2.0. |
| /// |
| /// In variable fonts, this can be controlled with the `wdth` axis. |
| /// |
| /// See <https://fonts.google.com/knowledge/glossary/width> |
| #[derive(Copy, Clone, PartialEq, PartialOrd, Debug)] |
| pub struct Stretch(f32); |
| |
| impl Stretch { |
| /// Width that is 50% of normal. |
| pub const ULTRA_CONDENSED: Self = Self(0.5); |
| |
| /// Width that is 62.5% of normal. |
| pub const EXTRA_CONDENSED: Self = Self(0.625); |
| |
| /// Width that is 75% of normal. |
| pub const CONDENSED: Self = Self(0.75); |
| |
| /// Width that is 87.5% of normal. |
| pub const SEMI_CONDENSED: Self = Self(0.875); |
| |
| /// Width that is 100% of normal. |
| pub const NORMAL: Self = Self(1.0); |
| |
| /// Width that is 112.5% of normal. |
| pub const SEMI_EXPANDED: Self = Self(1.125); |
| |
| /// Width that is 125% of normal. |
| pub const EXPANDED: Self = Self(1.25); |
| |
| /// Width that is 150% of normal. |
| pub const EXTRA_EXPANDED: Self = Self(1.5); |
| |
| /// Width that is 200% of normal. |
| pub const ULTRA_EXPANDED: Self = Self(2.0); |
| } |
| |
| impl Stretch { |
| /// Creates a new stretch attribute with the given ratio. |
| pub const fn new(ratio: f32) -> Self { |
| Self(ratio) |
| } |
| |
| /// Creates a new stretch attribute from the |
| /// [usWidthClass](<https://learn.microsoft.com/en-us/typography/opentype/spec/os2#uswidthclass>) |
| /// field of the OS/2 table. |
| fn from_width_class(width_class: u16) -> Self { |
| // The specified range is 1-9 and Skia simply clamps out of range |
| // values. We follow. |
| // See <https://skia.googlesource.com/skia/+/21b7538fe0757d8cda31598bc9e5a6d0b4b54629/include/core/SkFontStyle.h#52> |
| match width_class { |
| 0..=1 => Stretch::ULTRA_CONDENSED, |
| 2 => Stretch::EXTRA_CONDENSED, |
| 3 => Stretch::CONDENSED, |
| 4 => Stretch::SEMI_CONDENSED, |
| 5 => Stretch::NORMAL, |
| 6 => Stretch::SEMI_EXPANDED, |
| 7 => Stretch::EXPANDED, |
| 8 => Stretch::EXTRA_EXPANDED, |
| _ => Stretch::ULTRA_EXPANDED, |
| } |
| } |
| |
| /// Returns the stretch attribute as a ratio. |
| /// |
| /// This is a linear scaling factor with 1.0 being "normal" width. |
| pub const fn ratio(self) -> f32 { |
| self.0 |
| } |
| |
| /// Returns the stretch attribute as a percentage value. |
| /// |
| /// This is generally the value associated with the `wdth` axis. |
| pub fn percentage(self) -> f32 { |
| self.0 * 100.0 |
| } |
| } |
| |
| impl Default for Stretch { |
| fn default() -> Self { |
| Self::NORMAL |
| } |
| } |
| |
| /// Visual style or 'slope' of a font. |
| /// |
| /// In variable fonts, this can be controlled with the `ital` |
| /// and `slnt` axes for italic and oblique styles, respectively. |
| /// |
| /// See <https://fonts.google.com/knowledge/glossary/style> |
| #[derive(Copy, Clone, PartialEq, Default, Debug)] |
| pub enum Style { |
| /// An upright or "roman" style. |
| #[default] |
| Normal, |
| /// Generally a slanted style, originally based on semi-cursive forms. |
| /// This often has a different structure from the normal style. |
| Italic, |
| /// Oblique (or slanted) style with an optional angle in degrees, |
| /// counter-clockwise from the vertical. |
| Oblique(Option<f32>), |
| } |
| |
| /// Visual weight class of a font, typically on a scale from 1.0 to 1000.0. |
| /// |
| /// In variable fonts, this can be controlled with the `wght` axis. |
| /// |
| /// See <https://fonts.google.com/knowledge/glossary/weight> |
| #[derive(Copy, Clone, PartialEq, PartialOrd, Debug)] |
| pub struct Weight(f32); |
| |
| impl Weight { |
| /// Weight value of 100. |
| pub const THIN: Self = Self(100.0); |
| |
| /// Weight value of 200. |
| pub const EXTRA_LIGHT: Self = Self(200.0); |
| |
| /// Weight value of 300. |
| pub const LIGHT: Self = Self(300.0); |
| |
| /// Weight value of 350. |
| pub const SEMI_LIGHT: Self = Self(350.0); |
| |
| /// Weight value of 400. |
| pub const NORMAL: Self = Self(400.0); |
| |
| /// Weight value of 500. |
| pub const MEDIUM: Self = Self(500.0); |
| |
| /// Weight value of 600. |
| pub const SEMI_BOLD: Self = Self(600.0); |
| |
| /// Weight value of 700. |
| pub const BOLD: Self = Self(700.0); |
| |
| /// Weight value of 800. |
| pub const EXTRA_BOLD: Self = Self(800.0); |
| |
| /// Weight value of 900. |
| pub const BLACK: Self = Self(900.0); |
| |
| /// Weight value of 950. |
| pub const EXTRA_BLACK: Self = Self(950.0); |
| } |
| |
| impl Weight { |
| /// Creates a new weight attribute with the given value. |
| pub const fn new(weight: f32) -> Self { |
| Self(weight) |
| } |
| |
| /// Returns the underlying weight value. |
| pub const fn value(self) -> f32 { |
| self.0 |
| } |
| } |
| |
| impl Default for Weight { |
| fn default() -> Self { |
| Self::NORMAL |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| use crate::prelude::*; |
| |
| #[test] |
| fn missing_os2() { |
| let font = FontRef::new(font_test_data::CMAP12_FONT1).unwrap(); |
| let attrs = font.attributes(); |
| assert_eq!(attrs.stretch, Stretch::NORMAL); |
| assert_eq!(attrs.style, Style::Italic); |
| assert_eq!(attrs.weight, Weight::BOLD); |
| } |
| |
| #[test] |
| fn so_stylish() { |
| let font = FontRef::new(font_test_data::CMAP14_FONT1).unwrap(); |
| let attrs = font.attributes(); |
| assert_eq!(attrs.stretch, Stretch::SEMI_CONDENSED); |
| assert_eq!(attrs.style, Style::Oblique(Some(-14.0))); |
| assert_eq!(attrs.weight, Weight::EXTRA_BOLD); |
| } |
| } |