Skip to main content

kas_text/fonts/
resolver.rs

1// Licensed under the Apache License, Version 2.0 (the "License");
2// you may not use this file except in compliance with the License.
3// You may obtain a copy of the License in the LICENSE-APACHE file or at:
4//     https://www.apache.org/licenses/LICENSE-2.0
5
6//! KAS Rich-Text library — font resolver
7//!
8//! Many items are copied from font-kit to avoid any public dependency.
9
10use super::{FontStyle, FontWeight, FontWidth};
11use fontique::{
12    Attributes, Collection, FamilyId, GenericFamily, QueryFamily, QueryFont, QueryStatus, Script,
13    SourceCache,
14};
15use log::debug;
16#[cfg(feature = "serde")]
17use serde::{Deserialize, Serialize};
18use std::collections::HashMap;
19use std::collections::hash_map::Entry;
20use std::hash::{BuildHasher, Hash};
21
22/// A tool to resolve a single font face given a family and style
23pub struct Resolver {
24    collection: Collection,
25    cache: SourceCache,
26    /// Cached family selectors:
27    families: HashMap<FamilySelector, FamilySet>,
28}
29
30impl Resolver {
31    pub(crate) fn new() -> Self {
32        Resolver {
33            collection: Collection::new(Default::default()),
34            cache: SourceCache::new(Default::default()),
35            families: HashMap::new(),
36        }
37    }
38
39    /// Get a font family name from an id
40    pub fn font_family(&mut self, id: FamilyId) -> Option<&str> {
41        self.collection.family_name(id)
42    }
43
44    /// Get a font family name for some generic font family
45    pub fn font_family_from_generic(&mut self, generic: GenericFamily) -> Option<&str> {
46        let id = self.collection.generic_families(generic).next()?;
47        self.collection.family_name(id)
48    }
49
50    /// Construct a [`FamilySelector`] for the given `families`
51    pub fn select_families<I, F>(&mut self, families: I) -> FamilySelector
52    where
53        I: IntoIterator<Item = F>,
54        F: Into<FamilyName>,
55    {
56        let set = FamilySet(families.into_iter().map(|f| f.into()).collect());
57        let hash = self.families.hasher().hash_one(&set);
58        let sel = FamilySelector(hash | (1 << 63));
59
60        match self.families.entry(sel) {
61            Entry::Vacant(entry) => {
62                entry.insert(set);
63            }
64            Entry::Occupied(entry) => {
65                // Unlikely but possible case:
66                log::warn!(
67                    "Resolver::select_families: hash collision for family selector {set:?} and {:?}",
68                    entry.get()
69                );
70                // TODO: inject a random value into the FamilySet and rehash?
71            }
72        }
73
74        sel
75    }
76
77    /// Resolve families from a [`FamilySelector`]
78    ///
79    /// Returns an empty [`Vec`] on error.
80    pub fn resolve_families(&self, selector: &FamilySelector) -> Vec<FamilyName> {
81        if let Some(gf) = selector.as_generic() {
82            vec![FamilyName::Generic(gf)]
83        } else if let Some(set) = self.families.get(selector) {
84            set.0.clone()
85        } else {
86            vec![]
87        }
88    }
89}
90
91/// A family name
92#[derive(Clone, Debug, Eq, PartialEq, Hash)]
93#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
94pub enum FamilyName {
95    /// A family named with a `String`
96    Named(String),
97    /// A generic family
98    #[cfg_attr(feature = "serde", serde(with = "remote::GenericFamily"))]
99    Generic(GenericFamily),
100}
101
102impl From<GenericFamily> for FamilyName {
103    fn from(gf: GenericFamily) -> Self {
104        FamilyName::Generic(gf)
105    }
106}
107
108impl<'a> From<&'a FamilyName> for QueryFamily<'a> {
109    fn from(family: &'a FamilyName) -> Self {
110        match family {
111            FamilyName::Named(name) => QueryFamily::Named(name),
112            FamilyName::Generic(gf) => QueryFamily::Generic(*gf),
113        }
114    }
115}
116
117#[derive(Clone, Debug, PartialEq, Eq, Hash)]
118struct FamilySet(Vec<FamilyName>);
119
120/// A (cached) family selector
121///
122/// This may be constructed directly for some generic families; for other
123/// families use [`Resolver::select_families`].
124///
125/// This is a small, `Copy` type (a newtype over `u64`).
126#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
127pub struct FamilySelector(u64);
128
129impl FamilySelector {
130    /// Glyphs have finishing strokes, flared or tapering ends, or have actual serifed endings.
131    pub const SERIF: FamilySelector = FamilySelector(0);
132
133    /// Glyphs have stroke endings that are plain.
134    pub const SANS_SERIF: FamilySelector = FamilySelector(1);
135
136    /// All glyphs have the same fixed width.
137    pub const MONOSPACE: FamilySelector = FamilySelector(2);
138
139    /// Glyphs in cursive fonts generally have either joining strokes or other cursive characteristics beyond those of italic typefaces. The glyphs are partially or completely connected, and the result looks more like handwritten pen or brush writing than printed letter work.
140    pub const CURSIVE: FamilySelector = FamilySelector(3);
141
142    /// Glyphs are taken from the default user interface font on a given platform.
143    pub const SYSTEM_UI: FamilySelector = FamilySelector(5);
144
145    /// Fonts that are specifically designed to render emoji.
146    pub const EMOJI: FamilySelector = FamilySelector(10);
147
148    /// This is for the particular stylistic concerns of representing mathematics: superscript and subscript, brackets that cross several lines, nesting expressions, and double struck glyphs with distinct meanings.
149    pub const MATH: FamilySelector = FamilySelector(11);
150
151    /// A particular style of Chinese characters that are between serif-style Song and cursive-style Kai forms. This style is often used for government documents.
152    pub const FANG_SONG: FamilySelector = FamilySelector(12);
153
154    /// Convert to a [`GenericFamily`] where possible
155    pub fn as_generic(self) -> Option<GenericFamily> {
156        match self.0 {
157            0 => Some(GenericFamily::Serif),
158            1 => Some(GenericFamily::SansSerif),
159            2 => Some(GenericFamily::Monospace),
160            3 => Some(GenericFamily::Cursive),
161            5 => Some(GenericFamily::SystemUi),
162            10 => Some(GenericFamily::Emoji),
163            11 => Some(GenericFamily::Math),
164            12 => Some(GenericFamily::FangSong),
165            _ => None,
166        }
167    }
168
169    /// Get a CSS-style generic family name where possible
170    pub fn generic_name(self) -> Option<&'static str> {
171        Some(match self.as_generic()? {
172            GenericFamily::Serif => "serif",
173            GenericFamily::SansSerif => "sans-serif",
174            GenericFamily::Monospace => "monospace",
175            GenericFamily::Cursive => "cursive",
176            GenericFamily::SystemUi => "system-ui",
177            GenericFamily::Emoji => "emoji",
178            GenericFamily::Math => "math",
179            GenericFamily::FangSong => "fangsong",
180            _ => return None,
181        })
182    }
183
184    /// Parse a CSS-style family descriptor
185    pub fn parse_generic(name: &str) -> Option<Self> {
186        Some(match name.trim() {
187            "serif" => Self::SERIF,
188            "sans-serif" => Self::SANS_SERIF,
189            "monospace" => Self::MONOSPACE,
190            "cursive" => Self::CURSIVE,
191            "system-ui" => Self::SYSTEM_UI,
192            "emoji" => Self::EMOJI,
193            "math" => Self::MATH,
194            "fangsong" => Self::FANG_SONG,
195            _ => return None,
196        })
197    }
198}
199
200/// Default-constructs to [`FamilySelector::SYSTEM_UI`].
201impl Default for FamilySelector {
202    fn default() -> Self {
203        FamilySelector::SYSTEM_UI
204    }
205}
206
207/// A font face selection tool
208///
209/// This tool selects a font according to the given criteria from available
210/// system fonts. Selection criteria are based on CSS.
211///
212/// This can be converted [from](From) a [`FamilySelector`], selecting the
213/// default styles.
214#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash)]
215pub struct FontSelector {
216    /// Family selector
217    pub family: FamilySelector,
218    /// Weight
219    pub weight: FontWeight,
220    /// Width
221    pub width: FontWidth,
222    /// Italic / oblique style
223    pub style: FontStyle,
224}
225
226impl FontSelector {
227    /// Hard-coded emoji font selector
228    pub const EMOJI: Self = FontSelector {
229        family: FamilySelector::EMOJI,
230        weight: FontWeight::NORMAL,
231        width: FontWidth::NORMAL,
232        style: FontStyle::Normal,
233    };
234
235    /// Synonym for default
236    ///
237    /// Without further parametrization, this will select a generic sans-serif
238    /// font which should be suitable for most uses.
239    #[inline]
240    pub fn new() -> Self {
241        FontSelector::default()
242    }
243
244    /// Resolve font faces for each matching font
245    ///
246    /// All font faces matching steps 1-4 will be returned through the `add_face` closure.
247    pub(crate) fn select<F>(&self, resolver: &mut Resolver, script: Script, add_face: F)
248    where
249        F: FnMut(&QueryFont) -> QueryStatus,
250    {
251        let mut query = resolver.collection.query(&mut resolver.cache);
252        if let Some(gf) = self.family.as_generic() {
253            debug!(
254                "select: Script::{:?}, GenericFamily::{:?}, {:?}, {:?}, {:?}",
255                &script, gf, &self.weight, &self.width, &self.style
256            );
257
258            query.set_families([gf]);
259        } else if let Some(set) = resolver.families.get(&self.family) {
260            debug!(
261                "select: Script::{:?}, {:?}, {:?}, {:?}, {:?}",
262                &script, set, &self.weight, &self.width, &self.style
263            );
264
265            query.set_families(set.0.iter());
266        }
267
268        query.set_attributes(Attributes {
269            width: self.width.into(),
270            style: self.style.into(),
271            weight: self.weight.into(),
272        });
273
274        query.set_fallbacks(script);
275
276        query.matches_with(add_face);
277    }
278
279    /// Format CSS-style
280    ///
281    /// This is similar to the CSS `font` property, though it does not support
282    /// size or variant or using relative or global values. Examples:
283    ///
284    /// - `system-ui`
285    /// - `italic bold expanded sans-serif`
286    /// - `oblique 10deg 500 175% monospace`
287    /// - `300 cursive`
288    ///
289    /// Weight, width and style will be omitted if normal. Family is required
290    /// and must be a single generic name.
291    ///
292    /// Will return `None` if [`Self::family`] is not one of the generic
293    /// families supported by [`FamilySelector`].
294    pub fn format_css(&self) -> Option<String> {
295        let family = self.family.generic_name()?;
296        let mut s = String::new();
297        if self.style != FontStyle::Normal {
298            s.push_str(&format!("{} ", self.style));
299        }
300        if self.weight != FontWeight::NORMAL {
301            s.push_str(&format!("{} ", self.weight));
302        }
303        if self.width != FontWidth::NORMAL {
304            s.push_str(&format!("{} ", self.width));
305        }
306        s.push_str(family);
307        Some(s)
308    }
309
310    /// Parse a CSS-style selector
311    ///
312    /// Format support is similar to [`Self::format_css`].
313    ///
314    /// Does not (yet) support non-generic font families.
315    /// TODO: write a nicer parser with real error detection!
316    pub fn parse_css(s: &str) -> Option<Self> {
317        let mut weight = FontWeight::NORMAL;
318        let mut width = FontWidth::NORMAL;
319        let mut style = FontStyle::Normal;
320        let mut last_is_oblique = false;
321        for part in s.split_ascii_whitespace() {
322            if last_is_oblique {
323                // Special case: oblique may be followed by a numeric specifier in degrees
324                if part.ends_with("deg") {
325                    style = FontStyle::parse(&format!("oblique {part}"))
326                        .expect("failed to parse oblique angle");
327                }
328                last_is_oblique = false;
329            } else if let Some(v) = FontStyle::parse(part) {
330                style = v;
331                if style == FontStyle::Oblique(None) {
332                    last_is_oblique = true;
333                }
334            } else if let Some(v) = FontWeight::parse(part) {
335                weight = v;
336            } else if let Some(v) = FontWidth::parse(part) {
337                width = v;
338            } else {
339                let family = FamilySelector::parse_generic(part)?;
340                return Some(FontSelector {
341                    family,
342                    weight,
343                    width,
344                    style,
345                });
346            }
347        }
348        None
349    }
350}
351
352impl From<FamilySelector> for FontSelector {
353    #[inline]
354    fn from(family: FamilySelector) -> Self {
355        FontSelector {
356            family,
357            ..Default::default()
358        }
359    }
360}
361
362// See: https://serde.rs/remote-derive.html
363#[cfg(feature = "serde")]
364mod remote {
365    use serde::{Deserialize, Serialize};
366
367    #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)]
368    #[repr(u8)]
369    #[serde(remote = "fontique::GenericFamily")]
370    pub enum GenericFamily {
371        Serif = 0,
372        SansSerif = 1,
373        Monospace = 2,
374        Cursive = 3,
375        Fantasy = 4,
376        SystemUi = 5,
377        UiSerif = 6,
378        UiSansSerif = 7,
379        UiMonospace = 8,
380        UiRounded = 9,
381        Emoji = 10,
382        Math = 11,
383        FangSong = 12,
384    }
385}
386
387#[cfg(feature = "serde")]
388mod serde_impls {
389    use super::*;
390    use serde::{de, ser};
391    use std::fmt;
392
393    impl ser::Serialize for FamilySelector {
394        fn serialize<S: ser::Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
395            if let Some(name) = self.generic_name() {
396                ser.serialize_str(name)
397            } else {
398                Err(ser::Error::custom(
399                    "unable to serialize non-generic family selectors",
400                ))
401            }
402        }
403    }
404
405    impl<'de> de::Deserialize<'de> for FamilySelector {
406        fn deserialize<D: de::Deserializer<'de>>(de: D) -> Result<FamilySelector, D::Error> {
407            struct Visitor;
408            impl<'de> de::Visitor<'de> for Visitor {
409                type Value = FamilySelector;
410
411                fn expecting(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
412                    write!(fmt, "a generic family name")
413                }
414
415                fn visit_str<E: de::Error>(self, s: &str) -> Result<FamilySelector, E> {
416                    // TODO: support non-generic font families
417                    FamilySelector::parse_generic(s)
418                        .ok_or_else(|| de::Error::invalid_value(de::Unexpected::Str(s), &self))
419                }
420            }
421
422            de.deserialize_str(Visitor)
423        }
424    }
425
426    impl ser::Serialize for FontSelector {
427        fn serialize<S: ser::Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
428            if let Some(s) = self.format_css() {
429                ser.serialize_str(&s)
430            } else {
431                Err(ser::Error::custom(
432                    "unable to serialize non-generic family selectors",
433                ))
434            }
435        }
436    }
437
438    impl<'de> de::Deserialize<'de> for FontSelector {
439        fn deserialize<D: de::Deserializer<'de>>(de: D) -> Result<FontSelector, D::Error> {
440            struct Visitor;
441            impl<'de> de::Visitor<'de> for Visitor {
442                type Value = FontSelector;
443
444                fn expecting(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
445                    write!(fmt, "a CSS-style font selector")
446                }
447
448                fn visit_str<E: de::Error>(self, s: &str) -> Result<FontSelector, E> {
449                    FontSelector::parse_css(s)
450                        .ok_or_else(|| de::Error::invalid_value(de::Unexpected::Str(s), &self))
451                }
452            }
453
454            de.deserialize_str(Visitor)
455        }
456    }
457}