swash/
attributes.rs

1//! Basic font attributes: stretch, weight and style.
2
3use super::internal::{head::Os2, RawFont};
4use super::{tag_from_bytes, FontRef, Setting, Tag};
5
6use core::fmt;
7use core::hash::{Hash, Hasher};
8
9// Variations that apply to attributes.
10const WDTH: Tag = tag_from_bytes(b"wdth");
11const WGHT: Tag = tag_from_bytes(b"wght");
12const SLNT: Tag = tag_from_bytes(b"slnt");
13const ITAL: Tag = tag_from_bytes(b"ital");
14
15/// Primary attributes for font classification: stretch, weight and style.
16///
17/// This struct is created by the [`attributes`](FontRef::attributes) method on [`FontRef`].
18#[derive(Copy, Clone)]
19pub struct Attributes(pub u32);
20
21impl Attributes {
22    /// Creates new font attributes from the specified stretch, weight and
23    /// style.
24    pub const fn new(stretch: Stretch, weight: Weight, style: Style) -> Self {
25        let stretch = stretch.0 as u32 & 0x1FF;
26        let weight = weight.0 as u32 & 0x3FF;
27        let style = style.pack();
28        Self(style | weight << 9 | stretch << 19)
29    }
30
31    /// Extracts the attributes from the specified font.
32    pub fn from_font<'a>(font: &FontRef<'a>) -> Self {
33        let mut attrs = Self::from_os2(font.os2().as_ref());
34        let mut var_bits = 0;
35        for var in font.variations() {
36            match var.tag() {
37                WDTH => var_bits |= 1,
38                WGHT => var_bits |= 2,
39                SLNT => var_bits |= 4,
40                ITAL => var_bits |= 8,
41                _ => {}
42            }
43        }
44        attrs.0 |= var_bits << 28;
45        attrs
46    }
47
48    pub(crate) fn from_os2(os2: Option<&Os2>) -> Self {
49        if let Some(os2) = os2 {
50            let flags = os2.selection_flags();
51            let style = if flags.italic() {
52                Style::Italic
53            } else if flags.oblique() {
54                Style::Oblique(ObliqueAngle::default())
55            } else {
56                Style::Normal
57            };
58            let weight = Weight(os2.weight_class() as u16);
59            let stretch = Stretch::from_raw(os2.width_class() as u16);
60            Self::new(stretch, weight, style)
61        } else {
62            Self::default()
63        }
64    }
65
66    /// Returns the stretch attribute.
67    pub fn stretch(&self) -> Stretch {
68        Stretch((self.0 >> 19 & 0x1FF) as u16)
69    }
70
71    /// Returns the weight attribute.
72    pub fn weight(&self) -> Weight {
73        Weight((self.0 >> 9 & 0x3FF) as u16)
74    }
75
76    /// Returns the style attribute.
77    pub fn style(&self) -> Style {
78        Style::unpack(self.0 & 0x1FF)
79    }
80
81    /// Returns a tuple containing all attributes.
82    pub fn parts(&self) -> (Stretch, Weight, Style) {
83        (self.stretch(), self.weight(), self.style())
84    }
85
86    /// Returns true if the font has variations corresponding to primary
87    /// attributes.
88    pub fn has_variations(&self) -> bool {
89        (self.0 >> 28) != 0
90    }
91
92    /// Returns true if the font has a variation for the stretch attribute.
93    pub fn has_stretch_variation(&self) -> bool {
94        let var_bits = self.0 >> 28;
95        var_bits & 1 != 0
96    }
97
98    /// Returns true if the font has a variation for the weight attribute.
99    pub fn has_weight_variation(&self) -> bool {
100        let var_bits = self.0 >> 28;
101        var_bits & 2 != 0
102    }
103
104    /// Returns true if the font has a variation for the oblique style
105    /// attribute.
106    pub fn has_oblique_variation(&self) -> bool {
107        let var_bits = self.0 >> 28;
108        var_bits & 4 != 0
109    }
110
111    /// Returns true if the font has a variation for the italic style
112    /// attribute.
113    pub fn has_italic_variation(&self) -> bool {
114        let var_bits = self.0 >> 28;
115        var_bits & 8 != 0
116    }
117
118    /// Returns a synthesis analysis based on the requested attributes with
119    /// respect to this set of attributes.
120    pub fn synthesize(&self, requested: Attributes) -> Synthesis {
121        let mut synth = Synthesis::default();
122        if self.0 << 4 == requested.0 << 4 {
123            return synth;
124        }
125        let mut len = 0usize;
126        if self.has_stretch_variation() {
127            let stretch = self.stretch();
128            let req_stretch = requested.stretch();
129            if stretch != requested.stretch() {
130                synth.vars[len] = Setting {
131                    tag: WDTH,
132                    value: req_stretch.to_percentage(),
133                };
134                len += 1;
135            }
136        }
137        let (weight, req_weight) = (self.weight(), requested.weight());
138        if weight != req_weight {
139            if self.has_weight_variation() {
140                synth.vars[len] = Setting {
141                    tag: WGHT,
142                    value: req_weight.0 as f32,
143                };
144                len += 1;
145            } else if req_weight > weight {
146                synth.embolden = true;
147            }
148        }
149        let (style, req_style) = (self.style(), requested.style());
150        if style != req_style {
151            match req_style {
152                Style::Normal => {}
153                Style::Italic => {
154                    if style == Style::Normal {
155                        if self.has_italic_variation() {
156                            synth.vars[len] = Setting {
157                                tag: ITAL,
158                                value: 1.,
159                            };
160                            len += 1;
161                        } else if self.has_oblique_variation() {
162                            synth.vars[len] = Setting {
163                                tag: SLNT,
164                                value: 14.,
165                            };
166                            len += 1;
167                        } else {
168                            synth.skew = 14;
169                        }
170                    }
171                }
172                Style::Oblique(angle) => {
173                    if style == Style::Normal {
174                        let degrees = angle.to_degrees();
175                        if self.has_oblique_variation() {
176                            synth.vars[len] = Setting {
177                                tag: SLNT,
178                                value: degrees,
179                            };
180                            len += 1;
181                        } else if self.has_italic_variation() && degrees > 0. {
182                            synth.vars[len] = Setting {
183                                tag: ITAL,
184                                value: 1.,
185                            };
186                            len += 1;
187                        } else {
188                            synth.skew = degrees as i8;
189                        }
190                    }
191                }
192            }
193        }
194        synth.len = len as u8;
195        synth
196    }
197}
198
199impl Default for Attributes {
200    fn default() -> Self {
201        Self::new(Stretch::NORMAL, Weight::NORMAL, Style::Normal)
202    }
203}
204
205impl PartialEq for Attributes {
206    fn eq(&self, other: &Self) -> bool {
207        self.0 << 4 == other.0 << 4
208    }
209}
210
211impl Eq for Attributes {}
212
213impl Hash for Attributes {
214    fn hash<H: Hasher>(&self, state: &mut H) {
215        (self.0 << 4).hash(state);
216    }
217}
218
219impl fmt::Display for Attributes {
220    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
221        let mut space = "";
222        let (stretch, weight, style) = self.parts();
223        if style == Style::Normal && weight == Weight::NORMAL && stretch == Stretch::NORMAL {
224            return write!(f, "regular");
225        }
226        if stretch != Stretch::NORMAL {
227            write!(f, "{}", stretch)?;
228            space = " ";
229        }
230        if style != Style::Normal {
231            write!(f, "{}{}", space, style)?;
232            space = " ";
233        }
234        if weight != Weight::NORMAL {
235            write!(f, "{}{}", space, weight)?;
236        }
237        Ok(())
238    }
239}
240
241impl fmt::Debug for Attributes {
242    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
243        write!(f, "{:?}", self.parts())?;
244        if self.has_stretch_variation() {
245            write!(f, "+wdth")?;
246        }
247        if self.has_weight_variation() {
248            write!(f, "+wght")?;
249        }
250        if self.has_italic_variation() {
251            write!(f, "+ital")?;
252        }
253        if self.has_oblique_variation() {
254            write!(f, "+slnt")?;
255        }
256        Ok(())
257    }
258}
259
260impl From<Stretch> for Attributes {
261    fn from(s: Stretch) -> Self {
262        Self::new(s, Weight::default(), Style::default())
263    }
264}
265
266impl From<Weight> for Attributes {
267    fn from(w: Weight) -> Self {
268        Self::new(Stretch::default(), w, Style::default())
269    }
270}
271
272impl From<Style> for Attributes {
273    fn from(s: Style) -> Self {
274        Self::new(Stretch::default(), Weight::default(), s)
275    }
276}
277
278impl From<()> for Attributes {
279    fn from(_: ()) -> Self {
280        Self::default()
281    }
282}
283
284impl From<(Stretch, Weight, Style)> for Attributes {
285    fn from(parts: (Stretch, Weight, Style)) -> Self {
286        Self::new(parts.0, parts.1, parts.2)
287    }
288}
289
290/// Angle of an oblique style in degrees from -90 to 90.
291#[derive(Copy, Clone, PartialEq, Eq, Debug)]
292pub struct ObliqueAngle(pub(crate) u8);
293
294impl ObliqueAngle {
295    /// Creates a new oblique angle from degrees.
296    pub fn from_degrees(degrees: f32) -> Self {
297        let a = degrees.clamp(-90., 90.) + 90.;
298        Self(a as u8)
299    }
300
301    /// Creates a new oblique angle from radians.
302    pub fn from_radians(radians: f32) -> Self {
303        let degrees = radians * 180. / core::f32::consts::PI;
304        Self::from_degrees(degrees)
305    }
306
307    /// Creates a new oblique angle from gradians.
308    pub fn from_gradians(gradians: f32) -> Self {
309        Self::from_degrees(gradians / 400. * 360.)
310    }
311
312    /// Creates a new oblique angle from turns.
313    pub fn from_turns(turns: f32) -> Self {
314        Self::from_degrees(turns * 360.)
315    }
316
317    /// Returns the oblique angle in degrees.
318    pub fn to_degrees(self) -> f32 {
319        self.0 as f32 - 90.
320    }
321}
322
323impl Default for ObliqueAngle {
324    fn default() -> Self {
325        Self::from_degrees(14.)
326    }
327}
328
329/// Visual style or 'slope' of a font.
330#[derive(Copy, Clone, PartialEq, Eq, Debug)]
331pub enum Style {
332    Normal,
333    Italic,
334    Oblique(ObliqueAngle),
335}
336
337impl Style {
338    /// Parses a style from a CSS style value.
339    pub fn parse(mut s: &str) -> Option<Self> {
340        s = s.trim();
341        Some(match s {
342            "normal" => Self::Normal,
343            "italic" => Self::Italic,
344            "oblique" => Self::Oblique(ObliqueAngle::from_degrees(14.)),
345            _ => {
346                if s.starts_with("oblique ") {
347                    s = s.get(8..)?;
348                    if s.ends_with("deg") {
349                        s = s.get(..s.len() - 3)?;
350                        if let Ok(a) = s.trim().parse::<f32>() {
351                            return Some(Self::Oblique(ObliqueAngle::from_degrees(a)));
352                        }
353                    } else if s.ends_with("grad") {
354                        s = s.get(..s.len() - 4)?;
355                        if let Ok(a) = s.trim().parse::<f32>() {
356                            return Some(Self::Oblique(ObliqueAngle::from_gradians(a)));
357                        }
358                    } else if s.ends_with("rad") {
359                        s = s.get(..s.len() - 3)?;
360                        if let Ok(a) = s.trim().parse::<f32>() {
361                            return Some(Self::Oblique(ObliqueAngle::from_radians(a)));
362                        }
363                    } else if s.ends_with("turn") {
364                        s = s.get(..s.len() - 4)?;
365                        if let Ok(a) = s.trim().parse::<f32>() {
366                            return Some(Self::Oblique(ObliqueAngle::from_turns(a)));
367                        }
368                    }
369                    return Some(Self::Oblique(ObliqueAngle::default()));
370                }
371                return None;
372            }
373        })
374    }
375
376    /// Creates a new oblique style with the specified angle
377    /// in degrees.
378    pub fn from_degrees(degrees: f32) -> Self {
379        Self::Oblique(ObliqueAngle::from_degrees(degrees))
380    }
381
382    /// Returns the angle of the style in degrees.
383    pub fn to_degrees(self) -> f32 {
384        match self {
385            Self::Italic => 14.,
386            Self::Oblique(angle) => angle.to_degrees(),
387            _ => 0.,
388        }
389    }
390
391    fn unpack(bits: u32) -> Self {
392        if bits & 1 != 0 {
393            Self::Oblique(ObliqueAngle((bits >> 1) as u8))
394        } else if bits == 0b110 {
395            Self::Italic
396        } else {
397            Self::Normal
398        }
399    }
400
401    const fn pack(&self) -> u32 {
402        match self {
403            Self::Normal => 0b10,
404            Self::Italic => 0b110,
405            Self::Oblique(angle) => 1 | (angle.0 as u32) << 1,
406        }
407    }
408}
409
410impl fmt::Display for Style {
411    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
412        write!(
413            f,
414            "{}",
415            match self {
416                Self::Normal => "normal",
417                Self::Italic => "italic",
418                Self::Oblique(angle) => {
419                    let degrees = angle.to_degrees();
420                    if degrees == 14. {
421                        "oblique"
422                    } else {
423                        return write!(f, "oblique({}deg)", degrees);
424                    }
425                }
426            }
427        )
428    }
429}
430
431impl Default for Style {
432    fn default() -> Self {
433        Self::Normal
434    }
435}
436
437/// Visual weight class of a font on a scale from 1 to 1000.
438#[derive(Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Hash, Debug)]
439pub struct Weight(pub u16);
440
441impl Weight {
442    pub const THIN: Weight = Weight(100);
443    pub const EXTRA_LIGHT: Weight = Weight(200);
444    pub const LIGHT: Weight = Weight(300);
445    pub const NORMAL: Weight = Weight(400);
446    pub const MEDIUM: Weight = Weight(500);
447    pub const SEMI_BOLD: Weight = Weight(600);
448    pub const BOLD: Weight = Weight(700);
449    pub const EXTRA_BOLD: Weight = Weight(800);
450    pub const BLACK: Weight = Weight(900);
451
452    /// Parses a CSS style font weight attribute.
453    pub fn parse(s: &str) -> Option<Self> {
454        let s = s.trim();
455        Some(match s {
456            "normal" => Self::NORMAL,
457            "bold" => Self::BOLD,
458            _ => Self(s.parse::<u32>().ok()?.clamp(1, 1000) as u16),
459        })
460    }
461}
462
463impl fmt::Display for Weight {
464    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
465        let s = match *self {
466            Self::THIN => "thin",
467            Self::EXTRA_LIGHT => "extra-light",
468            Self::LIGHT => "light",
469            Self::NORMAL => "normal",
470            Self::MEDIUM => "medium",
471            Self::SEMI_BOLD => "semi-bold",
472            Self::BOLD => "bold",
473            Self::EXTRA_BOLD => "extra-bold",
474            Self::BLACK => "black",
475            _ => "",
476        };
477        if s.is_empty() {
478            write!(f, "{}", self.0)
479        } else {
480            write!(f, "{}", s)
481        }
482    }
483}
484
485impl Default for Weight {
486    fn default() -> Self {
487        Self::NORMAL
488    }
489}
490
491/// Visual width of a font-- a relative change from the normal aspect
492/// ratio.
493#[derive(Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Hash)]
494pub struct Stretch(pub(crate) u16);
495
496impl Stretch {
497    pub const ULTRA_CONDENSED: Self = Self(0);
498    pub const EXTRA_CONDENSED: Self = Self(25);
499    pub const CONDENSED: Self = Self(50);
500    pub const SEMI_CONDENSED: Self = Self(75);
501    pub const NORMAL: Self = Self(100);
502    pub const SEMI_EXPANDED: Self = Self(125);
503    pub const EXPANDED: Self = Self(150);
504    pub const EXTRA_EXPANDED: Self = Self(200);
505    pub const ULTRA_EXPANDED: Self = Self(300);
506
507    /// Creates a stretch attribute from a percentage. The value will be
508    /// clamped at half percentage increments between 50% and 200%,
509    /// inclusive.
510    pub fn from_percentage(percentage: f32) -> Self {
511        let value = ((percentage.clamp(50., 200.) - 50.) * 2.) as u16;
512        Self(value)
513    }
514
515    /// Converts the stretch value to a percentage.
516    pub fn to_percentage(self) -> f32 {
517        (self.0 as f32) * 0.5 + 50.
518    }
519
520    /// Returns true if the stretch is normal.
521    pub fn is_normal(self) -> bool {
522        self == Self::NORMAL
523    }
524
525    /// Returns true if the stretch is condensed (less than normal).
526    pub fn is_condensed(self) -> bool {
527        self < Self::NORMAL
528    }
529
530    /// Returns true if the stretch is expanded (greater than normal).
531    pub fn is_expanded(self) -> bool {
532        self > Self::NORMAL
533    }
534
535    /// Parses the stretch from a CSS style keyword or a percentage value.
536    pub fn parse(s: &str) -> Option<Self> {
537        let s = s.trim();
538        Some(match s {
539            "ultra-condensed" => Self::ULTRA_CONDENSED,
540            "extra-condensed" => Self::EXTRA_CONDENSED,
541            "condensed" => Self::CONDENSED,
542            "semi-condensed" => Self::SEMI_CONDENSED,
543            "normal" => Self::NORMAL,
544            "semi-expanded" => Self::SEMI_EXPANDED,
545            "extra-expanded" => Self::EXTRA_EXPANDED,
546            "ultra-expanded" => Self::ULTRA_EXPANDED,
547            _ => {
548                if s.ends_with('%') {
549                    let p = s.get(..s.len() - 1)?.parse::<f32>().ok()?;
550                    return Some(Self::from_percentage(p));
551                }
552                return None;
553            }
554        })
555    }
556
557    /// Returns the raw value of the stretch attribute.
558    pub fn raw(self) -> u16 {
559        self.0
560    }
561
562    pub(crate) fn from_raw(raw: u16) -> Self {
563        match raw {
564            1 => Self::ULTRA_CONDENSED,
565            2 => Self::EXTRA_CONDENSED,
566            3 => Self::CONDENSED,
567            4 => Self::SEMI_CONDENSED,
568            5 => Self::NORMAL,
569            6 => Self::SEMI_EXPANDED,
570            7 => Self::EXPANDED,
571            8 => Self::EXTRA_EXPANDED,
572            9 => Self::ULTRA_EXPANDED,
573            _ => Self::NORMAL,
574        }
575    }
576}
577
578impl fmt::Display for Stretch {
579    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
580        write!(
581            f,
582            "{}",
583            match *self {
584                Self::ULTRA_CONDENSED => "ultra-condensed",
585                Self::EXTRA_CONDENSED => "extra-condensed",
586                Self::CONDENSED => "condensed",
587                Self::SEMI_CONDENSED => "semi-condensed",
588                Self::NORMAL => "normal",
589                Self::SEMI_EXPANDED => "semi-expanded",
590                Self::EXPANDED => "expanded",
591                Self::EXTRA_EXPANDED => "extra-expanded",
592                Self::ULTRA_EXPANDED => "ultra-expanded",
593                _ => {
594                    return write!(f, "{}%", self.to_percentage());
595                }
596            }
597        )
598    }
599}
600
601impl fmt::Debug for Stretch {
602    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
603        write!(f, "Stretch({})", self.to_percentage())
604    }
605}
606
607impl Default for Stretch {
608    fn default() -> Self {
609        Self::NORMAL
610    }
611}
612
613/// Synthesis suggestions for mismatched font attributes.
614///
615/// This is generated by the [`synthesize`](Attributes::synthesize) method on
616/// [`Attributes`].
617#[derive(Copy, Clone, Default, Debug)]
618pub struct Synthesis {
619    vars: [Setting<f32>; 4],
620    len: u8,
621    embolden: bool,
622    skew: i8,
623}
624
625impl Synthesis {
626    #[doc(hidden)]
627    pub fn new(variations: impl Iterator<Item = Setting<f32>>, embolden: bool, skew: f32) -> Self {
628        let mut synth = Self {
629            embolden,
630            skew: skew as i8,
631            ..Default::default()
632        };
633        for (i, setting) in variations.take(4).enumerate() {
634            synth.vars[i] = setting;
635            synth.len = i as u8 + 1;
636        }
637        synth
638    }
639
640    /// Returns true if any synthesis suggestions are available.
641    pub fn any(&self) -> bool {
642        self.len != 0 || self.embolden || self.skew != 0
643    }
644
645    /// Returns the variations that should be applied to match the requested
646    /// attributes.
647    pub fn variations(&self) -> &[Setting<f32>] {
648        &self.vars[..self.len as usize]
649    }
650
651    /// Returns true if the scaler should apply a faux bold.
652    pub fn embolden(&self) -> bool {
653        self.embolden
654    }
655
656    /// Returns a skew angle for faux italic/oblique, if requested.
657    pub fn skew(&self) -> Option<f32> {
658        if self.skew != 0 {
659            Some(self.skew as f32)
660        } else {
661            None
662        }
663    }
664}
665
666impl PartialEq for Synthesis {
667    fn eq(&self, other: &Self) -> bool {
668        if self.len != other.len {
669            return false;
670        }
671        if self.len != 0 && self.variations() != other.variations() {
672            return false;
673        }
674        self.embolden == other.embolden && self.skew == other.skew
675    }
676}