xref: /aosp_15_r20/external/cronet/third_party/rust/chromium_crates_io/vendor/skrifa-0.15.5/src/attribute.rs (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 //! Primary attributes typically used for font classification and selection.
2 
3 use read_fonts::{
4     tables::{
5         head::{Head, MacStyle},
6         os2::{Os2, SelectionFlags},
7         post::Post,
8     },
9     TableProvider,
10 };
11 
12 /// Stretch, style and weight attributes of a font.
13 ///
14 /// Variable fonts may contain axes that modify these attributes. The
15 /// [new](Self::new) method on this type returns values for the default
16 /// instance.
17 ///
18 /// These are derived from values in the
19 /// [OS/2](https://learn.microsoft.com/en-us/typography/opentype/spec/os2) if
20 /// available. Otherwise, they are retrieved from the
21 /// [head](https://learn.microsoft.com/en-us/typography/opentype/spec/head)
22 /// table.
23 #[derive(Copy, Clone, PartialEq, Debug, Default)]
24 pub struct Attributes {
25     pub stretch: Stretch,
26     pub style: Style,
27     pub weight: Weight,
28 }
29 
30 impl Attributes {
31     /// Extracts the stretch, style and weight attributes for the default
32     /// instance of the given font.
new<'a>(font: &impl TableProvider<'a>) -> Self33     pub fn new<'a>(font: &impl TableProvider<'a>) -> Self {
34         if let Ok(os2) = font.os2() {
35             // Prefer values from the OS/2 table if it exists. We also use
36             // the post table to extract the angle for oblique styles.
37             Self::from_os2_post(os2, font.post().ok())
38         } else if let Ok(head) = font.head() {
39             // Otherwise, fall back to the macStyle field of the head table.
40             Self::from_head(head)
41         } else {
42             Self::default()
43         }
44     }
45 
from_os2_post(os2: Os2, post: Option<Post>) -> Self46     fn from_os2_post(os2: Os2, post: Option<Post>) -> Self {
47         let stretch = Stretch::from_width_class(os2.us_width_class());
48         // Bits 1 and 9 of the fsSelection field signify italic and
49         // oblique, respectively.
50         // See: <https://learn.microsoft.com/en-us/typography/opentype/spec/os2#fsselection>
51         let fs_selection = os2.fs_selection();
52         let style = if fs_selection.contains(SelectionFlags::ITALIC) {
53             Style::Italic
54         } else if fs_selection.contains(SelectionFlags::OBLIQUE) {
55             let angle = post.map(|post| post.italic_angle().to_f64() as f32);
56             Style::Oblique(angle)
57         } else {
58             Style::Normal
59         };
60         // The usWeightClass field is specified with a 1-1000 range, but
61         // we don't clamp here because variable fonts could potentially
62         // have a value outside of that range.
63         // See <https://learn.microsoft.com/en-us/typography/opentype/spec/os2#usweightclass>
64         let weight = Weight(os2.us_weight_class() as f32);
65         Self {
66             stretch,
67             style,
68             weight,
69         }
70     }
71 
from_head(head: Head) -> Self72     fn from_head(head: Head) -> Self {
73         let mac_style = head.mac_style();
74         let style = mac_style
75             .contains(MacStyle::ITALIC)
76             .then_some(Style::Italic)
77             .unwrap_or_default();
78         let weight = mac_style
79             .contains(MacStyle::BOLD)
80             .then_some(Weight::BOLD)
81             .unwrap_or_default();
82         Self {
83             stretch: Stretch::default(),
84             style,
85             weight,
86         }
87     }
88 }
89 
90 /// Visual width of a font-- a relative change from the normal aspect
91 /// ratio, typically in the range 0.5 to 2.0.
92 ///
93 /// In variable fonts, this can be controlled with the `wdth` axis.
94 ///
95 /// See <https://fonts.google.com/knowledge/glossary/width>
96 #[derive(Copy, Clone, PartialEq, PartialOrd, Debug)]
97 pub struct Stretch(f32);
98 
99 impl Stretch {
100     /// Width that is 50% of normal.
101     pub const ULTRA_CONDENSED: Self = Self(0.5);
102 
103     /// Width that is 62.5% of normal.
104     pub const EXTRA_CONDENSED: Self = Self(0.625);
105 
106     /// Width that is 75% of normal.
107     pub const CONDENSED: Self = Self(0.75);
108 
109     /// Width that is 87.5% of normal.
110     pub const SEMI_CONDENSED: Self = Self(0.875);
111 
112     /// Width that is 100% of normal.
113     pub const NORMAL: Self = Self(1.0);
114 
115     /// Width that is 112.5% of normal.
116     pub const SEMI_EXPANDED: Self = Self(1.125);
117 
118     /// Width that is 125% of normal.
119     pub const EXPANDED: Self = Self(1.25);
120 
121     /// Width that is 150% of normal.
122     pub const EXTRA_EXPANDED: Self = Self(1.5);
123 
124     /// Width that is 200% of normal.
125     pub const ULTRA_EXPANDED: Self = Self(2.0);
126 }
127 
128 impl Stretch {
129     /// Creates a new stretch attribute with the given ratio.
new(ratio: f32) -> Self130     pub fn new(ratio: f32) -> Self {
131         Self(ratio)
132     }
133 
134     /// Creates a new stretch attribute from the
135     /// [usWidthClass](<https://learn.microsoft.com/en-us/typography/opentype/spec/os2#uswidthclass>)
136     /// field of the OS/2 table.
from_width_class(width_class: u16) -> Self137     fn from_width_class(width_class: u16) -> Self {
138         // The specified range is 1-9 and Skia simply clamps out of range
139         // values. We follow.
140         // See <https://skia.googlesource.com/skia/+/21b7538fe0757d8cda31598bc9e5a6d0b4b54629/include/core/SkFontStyle.h#52>
141         match width_class {
142             0..=1 => Stretch::ULTRA_CONDENSED,
143             2 => Stretch::EXTRA_CONDENSED,
144             3 => Stretch::CONDENSED,
145             4 => Stretch::SEMI_CONDENSED,
146             5 => Stretch::NORMAL,
147             6 => Stretch::SEMI_EXPANDED,
148             7 => Stretch::EXPANDED,
149             8 => Stretch::EXTRA_EXPANDED,
150             _ => Stretch::ULTRA_EXPANDED,
151         }
152     }
153 
154     /// Returns the stretch attribute as a ratio.
155     ///
156     /// This is a linear scaling factor with 1.0 being "normal" width.
ratio(self) -> f32157     pub fn ratio(self) -> f32 {
158         self.0
159     }
160 
161     /// Returns the stretch attribute as a percentage value.
162     ///
163     /// This is generally the value associated with the `wdth` axis.
percentage(self) -> f32164     pub fn percentage(self) -> f32 {
165         self.0 * 100.0
166     }
167 }
168 
169 impl Default for Stretch {
default() -> Self170     fn default() -> Self {
171         Self::NORMAL
172     }
173 }
174 
175 /// Visual style or 'slope' of a font.
176 ///
177 /// In variable fonts, this can be controlled with the `ital`
178 /// and `slnt` axes for italic and oblique styles, respectively.
179 ///
180 /// See <https://fonts.google.com/knowledge/glossary/style>
181 #[derive(Copy, Clone, PartialEq, Default, Debug)]
182 pub enum Style {
183     /// An upright or "roman" style.
184     #[default]
185     Normal,
186     /// Generally a slanted style, originally based on semi-cursive forms.
187     /// This often has a different structure from the normal style.
188     Italic,
189     /// Oblique (or slanted) style with an optional angle in degrees,
190     /// counter-clockwise from the vertical.
191     Oblique(Option<f32>),
192 }
193 
194 /// Visual weight class of a font, typically on a scale from 1.0 to 1000.0.
195 ///
196 /// In variable fonts, this can be controlled with the `wght` axis.
197 ///
198 /// See <https://fonts.google.com/knowledge/glossary/weight>
199 #[derive(Copy, Clone, PartialEq, PartialOrd, Debug)]
200 pub struct Weight(f32);
201 
202 impl Weight {
203     /// Weight value of 100.
204     pub const THIN: Self = Self(100.0);
205 
206     /// Weight value of 200.
207     pub const EXTRA_LIGHT: Self = Self(200.0);
208 
209     /// Weight value of 300.
210     pub const LIGHT: Self = Self(300.0);
211 
212     /// Weight value of 350.
213     pub const SEMI_LIGHT: Self = Self(350.0);
214 
215     /// Weight value of 400.
216     pub const NORMAL: Self = Self(400.0);
217 
218     /// Weight value of 500.
219     pub const MEDIUM: Self = Self(500.0);
220 
221     /// Weight value of 600.
222     pub const SEMI_BOLD: Self = Self(600.0);
223 
224     /// Weight value of 700.
225     pub const BOLD: Self = Self(700.0);
226 
227     /// Weight value of 800.
228     pub const EXTRA_BOLD: Self = Self(800.0);
229 
230     /// Weight value of 900.
231     pub const BLACK: Self = Self(900.0);
232 
233     /// Weight value of 950.
234     pub const EXTRA_BLACK: Self = Self(950.0);
235 }
236 
237 impl Weight {
238     /// Creates a new weight attribute with the given value.
new(weight: f32) -> Self239     pub fn new(weight: f32) -> Self {
240         Self(weight)
241     }
242 
243     /// Returns the underlying weight value.
value(self) -> f32244     pub fn value(self) -> f32 {
245         self.0
246     }
247 }
248 
249 impl Default for Weight {
default() -> Self250     fn default() -> Self {
251         Self::NORMAL
252     }
253 }
254 
255 #[cfg(test)]
256 mod tests {
257     use super::*;
258     use crate::prelude::*;
259 
260     #[test]
missing_os2()261     fn missing_os2() {
262         let font = FontRef::new(font_test_data::CMAP12_FONT1).unwrap();
263         let attrs = font.attributes();
264         assert_eq!(attrs.stretch, Stretch::NORMAL);
265         assert_eq!(attrs.style, Style::Italic);
266         assert_eq!(attrs.weight, Weight::BOLD);
267     }
268 
269     #[test]
so_stylish()270     fn so_stylish() {
271         let font = FontRef::new(font_test_data::CMAP14_FONT1).unwrap();
272         let attrs = font.attributes();
273         assert_eq!(attrs.stretch, Stretch::SEMI_CONDENSED);
274         assert_eq!(attrs.style, Style::Oblique(Some(-14.0)));
275         assert_eq!(attrs.weight, Weight::EXTRA_BOLD);
276     }
277 }
278