font-index 0.1.2

Static index for system fonts
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
//! Font index.

use super::index_data::*;
use super::types::*;
use super::{
    fallback::Fallbacks,
    types::{FamilyId, GenericFamily},
};
use crate::util::{
    fxhash::FxHashMap,
    string::{LowercaseString, SmallString},
};
use std::path::Path;
use swash::text::{Cjk, Script};
use swash::{Attributes, CacheKey};

/// Type alias for signatures to distinguish between inherent and
/// requested attributes.
pub type RequestedAttributes = Attributes;

#[derive(Default)]
pub struct BaseIndex {
    pub family_map: FxHashMap<SmallString, FamilyId>,
    pub fonts: Vec<FontData>,
    pub sources: Vec<SourceData>,
}

pub struct StaticIndex {
    pub base: BaseIndex,
    pub families: Vec<FamilyData>,
    pub script_map: FxHashMap<Script, Fallbacks>,
    pub cjk: [Fallbacks; 5],
    pub generic: [Option<FamilyId>; 13],
}

impl Default for StaticIndex {
    fn default() -> Self {
        let fallbacks = Fallbacks::new();
        Self {
            base: BaseIndex::default(),
            families: Vec::new(),
            script_map: Default::default(),
            cjk: [fallbacks; 5],
            generic: [None; 13],
        }
    }
}

impl StaticIndex {
    pub fn setup_default_fallbacks(&mut self) {
        use super::system::*;
        use Cjk::*;
        use Script::*;
        match OS {
            Os::Windows => {
                // Simplified Chinese
                self.cjk[Simplified as usize] =
                    self.find_fallbacks(&["microsoft yahei", "simsun", "simsun-extb"]);
                // Traditional Chinese
                self.cjk[Traditional as usize] =
                    self.find_fallbacks(&["microsoft jhenghei", "pmingliu", "pmingliu-extb"]);
                self.cjk[Cjk::None as usize] = self.cjk[Traditional as usize];
                // Japanese
                self.cjk[Japanese as usize] = self.find_fallbacks(&[
                    "meiryo",
                    "yu gothic",
                    "microsoft yahei",
                    "simsun",
                    "simsun-extb",
                ]);
                // Korean
                self.cjk[Korean as usize] = self.find_fallbacks(&[
                    "malgun gothic",
                    "gulim",
                    "microsoft yahei",
                    "simsun",
                    "simsun-extb",
                ]);
                self.map_script(Latin, &["times new roman"]);
                self.map_script(Arabic, &["tahoma", "segoe ui"]);
                self.map_script(Armenian, &["segoe ui", "sylfaen"]);
                self.map_script(Bengali, &["nirmala ui", "vrinda"]);
                self.map_script(Brahmi, &["segoe ui historic"]);
                self.map_script(Braille, &["segoe ui symbol"]);
                self.map_script(Buginese, &["leelawadee ui"]);
                self.map_script(CanadianAboriginal, &["gadugi", "euphemia"]);
                self.map_script(Carian, &["segoe ui historic"]);
                self.map_script(Devanagari, &["nirmala ui", "mangal"]);
                self.map_script(Hebrew, &["david", "segoe ui", "calibri"]);
                self.map_script(Hangul, &["malgun gothic", "gulim"]);
                self.map_script(Myanmar, &["myanmar text"]);
                self.map_script(Malayalam, &["nirmala ui", "kartika"]);
                self.map_script(Han, &["microsoft yahei", "simsun", "simsun-extb"]);
                self.map_script(
                    Hiragana,
                    &["meiryo", "yu gothic", "ms pgothic", "microsoft yahei"],
                );
                self.map_script(
                    Katakana,
                    &["meiryo", "yu gothic", "ms pgothic", "microsoft yahei"],
                );
                self.map_script(Kharoshthi, &["segoe ui historic"]);
                self.map_script(
                    Khmer,
                    &[
                        "leelawadee ui",
                        "khmer ui",
                        "khmer os",
                        "moolboran",
                        "daunpenh",
                    ],
                );
                self.map_script(
                    Lao,
                    &[
                        "leelawadee ui",
                        "lao ui",
                        "dokchampa",
                        "saysettha ot",
                        "phetsarath ot",
                        "code2000",
                    ],
                );
                self.map_script(Lisu, &["segoe ui"]);
                self.map_script(
                    Syriac,
                    &["estrangelo edessa", "estrangelo nisibin", "code2000"],
                );
                self.map_script(Thai, &["tahoma", "leelawadee ui", "leelawadee"]);
                self.map_script(
                    Tibetan,
                    &["microsoft himalaya", "jomolhari", "tibetan machine uni"],
                );
                self.map_script(Vai, &["ebrima"]);
                self.map_script(Yi, &["microsoft yi baiti", "nuosu sil", "code2000"]);
            }
            Os::MacOs => {
                // Simplified Chinese
                self.cjk[Simplified as usize] = self.find_fallbacks(&["pingfang sc"]);
                // Traditional Chinese
                self.cjk[Traditional as usize] = self.find_fallbacks(&["pingfang tc"]);
                self.cjk[Cjk::None as usize] = self.cjk[Traditional as usize];
                // Japanese
                self.cjk[Japanese as usize] =
                    self.find_fallbacks(&["hiragino kaku gothic pron w3"]);
                // Korean
                self.cjk[Korean as usize] = self.find_fallbacks(&["apple sd gothic neo"]);
                self.map_script(Latin, &["times", "times new roman"]);
                self.map_script(Arabic, &["geeza pro"]);
                self.map_script(
                    Devanagari,
                    &[
                        "itf devanagari",
                        "kohinoor devanagari",
                        "devanagari sangam mn",
                        "devanagari mt",
                    ],
                );
                self.map_script(Bengali, &[]);
                self.map_script(Myanmar, &["noto sans myanmar", "myanmar mn"]);
                self.map_script(Malayalam, &["malayalam mn"]);
                self.map_script(Hebrew, &["lucida grande", "arial hebrew"]);
            }
            _ => {
                self.map_script(
                    Latin,
                    &[
                        "liberation sans",
                        "dejavu sans",
                        "ubuntu",
                        "source sans pro",
                    ],
                );
                self.map_script(Arabic, &["noto sans arabic"]);
                self.map_script(Hebrew, &["noto sans hebrew", "noto serif hebrew"]);
                self.map_script(Bengali, &["noto sans bengali", "noto serif bengali"]);
                self.map_script(
                    Devanagari,
                    &["noto sans devanagari", "noto serif devanagari"],
                );
                self.map_script(Malayalam, &["noto sans malayalam", "noto serif malayalam"]);
                self.map_script(Myanmar, &["noto sans myanmar", "noto serif myanmar"]);
            }
        }
    }

    pub fn setup_default_generic(&mut self) {
        use super::system::*;
        use GenericFamily::*;
        match OS {
            Os::Windows => {
                self.generic[SansSerif as usize] = self.find_family(&["arial"]);
                self.generic[Serif as usize] = self.find_family(&["times new roman"]);
                self.generic[Monospace as usize] = self.find_family(&["courier new"]);
                self.generic[Fantasy as usize] = self.find_family(&["impact"]);
                self.generic[Cursive as usize] = self.find_family(&["comic sans ms"]);
                self.generic[SystemUI as usize] = self.find_family(&["segoe ui"]);
                self.generic[Emoji as usize] = self.find_family(&["segoe ui emoji"]);
            }
            Os::MacOs => {
                self.generic[SansSerif as usize] = self.find_family(&["helvetica"]);
                self.generic[Serif as usize] = self.find_family(&["times"]);
                self.generic[Monospace as usize] = self.find_family(&["courier"]);
                self.generic[Fantasy as usize] = self.find_family(&["papyrus"]);
                self.generic[Cursive as usize] = self.find_family(&["apple chancery"]);
                self.generic[SystemUI as usize] = self.find_family(&["system font", "helvetica"]);
                self.generic[Emoji as usize] = self.find_family(&["apple color emoji"]);
            }
            Os::Ios => {
                self.generic[SansSerif as usize] = self.find_family(&["helvetica"]);
                self.generic[Serif as usize] = self.find_family(&["times new roman"]);
                self.generic[Monospace as usize] = self.find_family(&["courier"]);
                self.generic[Fantasy as usize] = self.find_family(&["papyrus"]);
                self.generic[Cursive as usize] = self.find_family(&["snell roundhand"]);
                self.generic[SystemUI as usize] = self.find_family(&["system font", "helvetica"]);
                self.generic[Emoji as usize] = self.find_family(&["apple color emoji"]);
            }
            Os::Android => {
                self.generic[SansSerif as usize] = self.find_family(&["roboto"]);
                self.generic[Serif as usize] = self.find_family(&["noto serif", "droid serif"]);
                self.generic[Monospace as usize] = self.find_family(&["droid sans mono"]);
                self.generic[Fantasy as usize] = self.find_family(&["noto serif"]);
                self.generic[Cursive as usize] = self.find_family(&["dancing script"]);
                self.generic[SystemUI as usize] = self.find_family(&["roboto"]);
                self.generic[Emoji as usize] = self.find_family(&["noto color emoji"]);
            }
            Os::Unix | Os::Other => {
                self.generic[SansSerif as usize] =
                    self.find_family(&["liberation sans", "dejavu sans"]);
                self.generic[Serif as usize] = self.find_family(&[
                    "liberation serif",
                    "dejavu serif",
                    "noto serif",
                    "times new roman",
                ]);
                self.generic[Monospace as usize] = self.find_family(&["dejavu sans mono"]);
                self.generic[Fantasy as usize] =
                    self.find_family(&["liberation serif", "dejavu serif"]);
                self.generic[Cursive as usize] =
                    self.find_family(&["liberation serif", "dejavu serif"]);
                self.generic[SystemUI as usize] =
                    self.find_family(&["liberation sans", "dejavu sans"]);
                self.generic[Emoji as usize] = self.find_family(&["noto color emoji", "emoji one"]);
            }
        }
    }

    pub fn emoji_family(&self) -> Option<FamilyId> {
        self.generic[GenericFamily::Emoji as usize]
    }

    pub fn fallbacks(&self, script: Script, cjk: Cjk) -> &[FamilyId] {
        if script == Script::Han {
            self.cjk[cjk as usize].get()
        } else {
            self.script_map.get(&script).map(|f| f.get()).unwrap_or(&[])
        }
    }

    fn map_script(&mut self, script: Script, families: &[&str]) {
        let fallbacks = self.find_fallbacks(families);
        if fallbacks.len() != 0 {
            self.script_map.insert(script, fallbacks);
        }
    }

    fn find_family(&self, families: &[&str]) -> Option<FamilyId> {
        for family in families {
            if let Some(id) = self.base.family_map.get(*family) {
                return Some(*id);
            }
        }
        None
    }

    fn find_fallbacks(&self, families: &[&str]) -> Fallbacks {
        let mut fallbacks = Fallbacks::new();
        for family in families {
            if let Some(id) = self.base.family_map.get(*family) {
                if !fallbacks.push(*id) {
                    break;
                }
            }
        }
        fallbacks
    }
}

impl StaticIndex {
    /// Returns a font entry that matches the specified family and
    /// attributes.
    pub fn query<'a>(
        &'a self,
        family: impl Into<FamilyKey<'a>>,
        attributes: impl Into<Attributes>,
    ) -> Option<FontEntry<'a>> {
        let family = self.family_by_key(family)?;
        let attrs = attributes.into();
        let font_id = family.data.query(attrs)?;
        let data = self.base.fonts.get(font_id.to_usize())?;
        Some(FontEntry {
            index: &self.base,
            family: family.data,
            data,
        })
    }

    /// Returns a font family entry for the specified family key.
    pub fn family_by_key<'a>(&'a self, key: impl Into<FamilyKey<'a>>) -> Option<FamilyEntry<'a>> {
        match key.into() {
            FamilyKey::Id(id) => self.family_by_id(id),
            FamilyKey::Name(name) => self.family_by_name(name),
            FamilyKey::Generic(generic) => {
                self.family_by_id(self.generic.get(generic as usize).copied()??)
            }
        }
    }

    /// Returns a font family entry for the specified name.
    pub fn family_by_name<'a>(&'a self, name: &str) -> Option<FamilyEntry<'a>> {
        let mut s = LowercaseString::new();
        let name = s.get(name)?;
        let id = if let Some(generic) = GenericFamily::parse(name) {
            self.generic.get(generic as usize).copied()??
        } else {
            *self.base.family_map.get(name)?
        };

        self.family_by_id(id)
    }

    /// Returns a font family entry for the specified identifier.
    pub fn family_by_id<'a>(&'a self, id: FamilyId) -> Option<FamilyEntry<'a>> {
        let data = self.families.get(id.to_usize())?;
        Some(FamilyEntry {
            index: &self.base,
            data,
        })
    }

    /// Returns a font entry for the specified identifier.
    pub fn font_by_id<'a>(&'a self, id: FontId) -> Option<FontEntry<'a>> {
        let data = self.base.fonts.get(id.to_usize())?;
        let family = self.families.get(data.family.to_usize())?;
        Some(FontEntry {
            index: &self.base,
            family,
            data,
        })
    }
}

/// Font family entry in a library.
#[derive(Copy, Clone)]
pub struct FamilyEntry<'a> {
    index: &'a BaseIndex,
    data: &'a FamilyData,
}

impl<'a> FamilyEntry<'a> {
    /// Returns the family identifier.
    pub fn id(&self) -> FamilyId {
        self.data.id
    }

    /// Returns the name of the family.
    pub fn name(&self) -> &str {
        self.data.name.as_str()
    }

    /// Returns an iterator over the fonts in the family.
    pub fn fonts(&'a self) -> impl Iterator<Item = FontEntry<'a>> + 'a {
        self.data.fonts.iter().filter_map(move |f| {
            let data = self.index.fonts.get(f.id.to_usize())?;
            Some(FontEntry {
                index: self.index,
                family: self.data,
                data,
            })
        })
    }
}

/// Font entry in a library.
#[derive(Copy, Clone)]
pub struct FontEntry<'a> {
    index: &'a BaseIndex,
    family: &'a FamilyData,
    data: &'a FontData,
}

impl<'a> FontEntry<'a> {
    /// Returns the font identifier.
    pub fn id(&self) -> FontId {
        self.data.id
    }

    /// Returns the font source.
    pub fn source(&self) -> SourceEntry<'a> {
        SourceEntry {
            index: self.index,
            data: &self.index.sources[self.data.source.to_usize()],
        }
    }

    /// Returns the index of the font in the source.
    pub fn index(&self) -> u32 {
        self.data.index
    }

    /// Returns the offset to the font table directory in the source.
    pub fn offset(&self) -> u32 {
        self.data.offset
    }

    /// Returns the family entry.
    pub fn family(&self) -> FamilyEntry<'a> {
        FamilyEntry {
            index: self.index,
            data: self.family,
        }
    }

    /// Returns the family name.
    pub fn family_name(&self) -> &str {
        self.family.name.as_str()
    }

    /// Returns the font attributes.
    pub fn attributes(&self) -> Attributes {
        self.data.attributes
    }

    pub fn cache_key(&self) -> CacheKey {
        self.data.key
    }

    pub fn selector(
        &self,
        attrs: RequestedAttributes,
    ) -> (FontId, Attributes, RequestedAttributes) {
        (self.data.id, self.data.attributes, attrs)
    }
}

/// Source entry in a library.
#[derive(Copy, Clone)]
pub struct SourceEntry<'a> {
    pub index: &'a BaseIndex,
    data: &'a SourceData,
}

impl<'a> SourceEntry<'a> {
    /// Returns the source identifier.
    pub fn id(&self) -> SourceId {
        self.data.id
    }

    /// Returns the path of the source, if it is represented by a file.
    pub fn path(&self) -> Option<&Path> {
        match &self.data.kind {
            SourceKind::Memory(..) => None,
            SourceKind::File(data) => Some(&data.path),
        }
    }
}