saudade 0.5.0

Classic looking retained-mode, cross-platform Rust GUI library
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
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;

use crate::geometry::Color;
use crate::painter::Painter;

/// Which face of a font to draw with.
///
/// saudade renders text in real OS-installed faces — when an app asks for
/// **bold** or *italic*, the host's actual bold / italic / bold-italic face of
/// the same family is rasterized, never a synthesized (smeared / sheared)
/// approximation of the regular face. A style the system has no face for falls
/// back to the nearest *real* face the family does provide (see
/// [`Font::load_sans`] and `resolve_style`).
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default)]
pub enum FontStyle {
    /// The upright, regular-weight face — what `Painter::text` uses.
    #[default]
    Regular = 0,
    /// Bold weight, upright.
    Bold = 1,
    /// Regular weight, italic (or oblique) slant.
    Italic = 2,
    /// Bold weight *and* italic slant.
    BoldItalic = 3,
}

impl FontStyle {
    /// The style for a `(bold, italic)` flag pair — handy when a renderer tracks
    /// the two emphases independently and needs the combined face.
    pub fn new(bold: bool, italic: bool) -> Self {
        match (bold, italic) {
            (false, false) => FontStyle::Regular,
            (true, false) => FontStyle::Bold,
            (false, true) => FontStyle::Italic,
            (true, true) => FontStyle::BoldItalic,
        }
    }

    /// Whether this style carries bold weight.
    pub fn is_bold(self) -> bool {
        matches!(self, FontStyle::Bold | FontStyle::BoldItalic)
    }

    /// Whether this style carries an italic / oblique slant.
    pub fn is_italic(self) -> bool {
        matches!(self, FontStyle::Italic | FontStyle::BoldItalic)
    }
}

/// Which family of font to draw with: a proportional sans-serif, a proportional
/// serif, or a fixed-width monospace. The styled draw / measure entry points take
/// one of these alongside a [`FontStyle`]; the plain `text` / `measure_text`
/// default to [`Sans`](FontFamily::Sans).
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default)]
pub enum FontFamily {
    /// Proportional sans-serif — the UI default.
    #[default]
    Sans,
    /// Proportional serif.
    Serif,
    /// Fixed-width monospace.
    Mono,
}

/// The set of font families a [`Painter`](crate::Painter) draws with — a
/// sans-serif, a serif, and a monospace face, each selectable per draw via
/// [`FontFamily`]. Any family may be absent (`None`): drawing or measuring in a
/// missing one is a no-op / zero. The painter borrows these for a paint pass;
/// the backend owns the `Font`s. Build it field-by-field (`FontSet { sans: ...,
/// ..Default::default() }`) or start from [`FontSet::default`] (all empty).
#[derive(Clone, Copy, Default)]
pub struct FontSet<'a> {
    /// Proportional sans-serif — the default for `text` / `measure_text`.
    pub sans: Option<&'a Font>,
    /// Proportional serif.
    pub serif: Option<&'a Font>,
    /// Fixed-width monospace — used by the text editors and by `text_styled`
    /// with [`FontFamily::Mono`].
    pub mono: Option<&'a Font>,
}

/// The number of distinct faces a [`Font`] can hold — one per [`FontStyle`].
const STYLE_COUNT: usize = 4;

/// Pick the [`FontStyle`] to actually draw for a requested one, given which
/// faces are present (`present[s as usize]`). The regular face is always loaded,
/// so this always resolves to a real, loaded face — we never fake an absent
/// bold/italic by transforming the regular glyph. A missing bold-italic prefers
/// to keep as much of the request as a real face allows (bold, then italic)
/// before giving up to regular.
fn resolve_style(present: [bool; STYLE_COUNT], style: FontStyle) -> FontStyle {
    use FontStyle::*;
    let has = |s: FontStyle| present[s as usize];
    match style {
        Regular => Regular,
        Bold => {
            if has(Bold) {
                Bold
            } else {
                Regular
            }
        }
        Italic => {
            if has(Italic) {
                Italic
            } else {
                Regular
            }
        }
        BoldItalic => {
            if has(BoldItalic) {
                BoldItalic
            } else if has(Bold) {
                Bold
            } else if has(Italic) {
                Italic
            } else {
                Regular
            }
        }
    }
}

/// A loaded font family, ready for glyph rasterization in any of its
/// [`FontStyle`]s.
///
/// saudade owns no bundled bitmap font: we ask the host OS via fontdb for a
/// reasonable proportional sans-serif (MS Sans Serif on Windows, Tahoma /
/// Liberation Sans / DejaVu Sans elsewhere) and rasterize on demand with
/// fontdue. Glyph alpha is blended into the framebuffer. For emphasis we load
/// the family's *real* bold, italic, and bold-italic faces alongside the regular
/// one — bold text is the system's bold face, not the regular face smeared a
/// pixel wider. The bold/italic faces are optional: a family the host ships
/// without them simply falls back to the regular face (see `resolve_style`).
///
/// Rasterizing an outline into a coverage bitmap is expensive, and a
/// retained-mode window repaints the *entire* visible text on every frame —
/// every scroll notch, every drag-resize step. So both the rasterized glyph
/// bitmaps and the per-glyph advance widths are memoized, keyed by the glyph,
/// the exact pixel size requested, *and* the style. After the first frame the
/// working set (the handful of characters actually on screen, at one or two
/// sizes) is all cache hits, turning each subsequent frame from "rasterize N
/// glyphs" into "blend N cached bitmaps". The caches use interior mutability so
/// drawing can stay `&self` (the painter only ever holds a shared reference to
/// the font).
///
/// The glyph bitmaps are the memory-heavy half, so that cache is LRU-bounded
/// to [`GLYPH_CACHE_CAP`] entries: an app that cycles through many sizes (a
/// smooth zoom) or a large character range (CJK) keeps only the most recently
/// drawn glyphs instead of growing without limit. The advance cache holds a
/// single `f32` per entry, so it stays an unbounded plain map.
pub struct Font {
    /// The faces, indexed by `FontStyle as usize`. Slot 0 (`Regular`) is always
    /// `Some`; the bold / italic / bold-italic slots are `Some` only when the
    /// host actually provides that face.
    faces: [Option<fontdue::Font>; STYLE_COUNT],
    /// Rasterized glyphs, keyed by `(char, physical-size bits, style)`. The
    /// bitmaps are wrapped in `Rc` so a lookup can hand back a cheap clone and
    /// release the cache borrow before the (longer-lived) blend loop runs.
    /// LRU-bounded. The `style` in the key is always a *resolved* style, so two
    /// requests that fall back to the same real face share a cache entry.
    glyphs: RefCell<LruCache<GlyphKey, Rc<Glyph>>>,
    /// Per-glyph advance widths, keyed by `(char, size bits, style)`. Feeds both
    /// text measurement and the editor's caret-offset table; far cheaper than a
    /// full rasterize when only the advance is needed.
    advances: RefCell<HashMap<GlyphKey, f32>>,
}

/// Cache key: a glyph at a physical size in a resolved style.
type GlyphKey = (char, u32, FontStyle);

/// Upper bound on the number of distinct rasterized glyphs kept in memory at
/// once. The on-screen working set is a few hundred at most (printable ASCII
/// across one or two sizes and styles), so this leaves generous headroom while
/// still capping memory when an app renders text at many sizes or over a wide
/// script.
const GLYPH_CACHE_CAP: usize = 1024;

/// One rasterized glyph: fontdue's metrics plus its coverage bitmap.
struct Glyph {
    metrics: fontdue::Metrics,
    bitmap: Vec<u8>,
}

/// A small least-recently-used cache: a plain map plus a monotonic access
/// "clock" stamped on every entry. On overflow the entry with the oldest stamp
/// is evicted. Eviction scans the map (O(capacity)), but it only happens on a
/// miss that fills the cache — and a glyph rasterization, the thing a miss
/// triggers, dwarfs that scan — so the simplicity is worth more than an
/// intrusive-list O(1) variant here.
struct LruCache<K, V> {
    entries: HashMap<K, (V, u64)>,
    clock: u64,
    capacity: usize,
}

impl<K: Eq + std::hash::Hash + Copy, V: Clone> LruCache<K, V> {
    fn new(capacity: usize) -> Self {
        Self {
            entries: HashMap::new(),
            clock: 0,
            capacity: capacity.max(1),
        }
    }

    /// Fetch a value, marking it most-recently-used. Returns a clone so the
    /// caller doesn't hold a borrow of the cache.
    fn get(&mut self, key: &K) -> Option<V> {
        self.clock += 1;
        let stamp = self.clock;
        let slot = self.entries.get_mut(key)?;
        slot.1 = stamp;
        Some(slot.0.clone())
    }

    /// Insert (or overwrite) a value as most-recently-used, evicting the
    /// least-recently-used entry first if a *new* key would exceed capacity.
    fn insert(&mut self, key: K, value: V) {
        self.clock += 1;
        if self.entries.len() >= self.capacity
            && !self.entries.contains_key(&key)
            && let Some(lru) = self
                .entries
                .iter()
                .min_by_key(|(_, (_, stamp))| *stamp)
                .map(|(k, _)| *k)
        {
            self.entries.remove(&lru);
        }
        self.entries.insert(key, (value, self.clock));
    }
}

impl Font {
    /// Build a font from just a regular face. Bold / italic slots start empty;
    /// attach them with [`with_bold_bytes`](Self::with_bold_bytes) and friends,
    /// or let the system loader fill them in.
    fn from_face(regular: fontdue::Font) -> Self {
        Self {
            faces: [Some(regular), None, None, None],
            glyphs: RefCell::new(LruCache::new(GLYPH_CACHE_CAP)),
            advances: RefCell::new(HashMap::new()),
        }
    }

    /// Try to load a system sans-serif font, with its bold / italic / bold-italic
    /// faces. Returns `None` if no candidate family could be loaded — text
    /// drawing then becomes a no-op.
    pub fn load_sans() -> Option<Self> {
        const SANS_FAMILIES: &[&str] = &[
            "MS Sans Serif",
            "Microsoft Sans Serif",
            "Tahoma",
            "Segoe UI",
            "Arial",
            "Helvetica",
            "Geneva",
            "DejaVu Sans",
            "Liberation Sans",
        ];
        load_family_chain(SANS_FAMILIES, false)
    }

    /// Try to load a system serif font, with its bold / italic / bold-italic
    /// faces. Walks the classic Win 3.1 / Office serif down to modern Linux
    /// replacements. Returns `None` if no candidate family could be loaded.
    pub fn load_serif() -> Option<Self> {
        const SERIF_FAMILIES: &[&str] = &[
            "Times New Roman",
            "Times",
            "MS Serif",
            "Georgia",
            "Liberation Serif",
            "DejaVu Serif",
            "Noto Serif",
        ];
        load_family_chain(SERIF_FAMILIES, false)
    }

    /// Try to load a fixed-width font (with its styled faces) for plain-text
    /// editors / code displays. Walks the same set of fallbacks Notepad and
    /// friends used through the nineties down to modern Linux replacements.
    pub fn load_monospace() -> Option<Self> {
        const MONO_FAMILIES: &[&str] = &[
            "Lucida Console",
            "Consolas",
            "Courier New",
            "Courier",
            "Liberation Mono",
            "DejaVu Sans Mono",
            "Menlo",
            "Monaco",
        ];
        load_family_chain(MONO_FAMILIES, true)
    }

    /// Load a font family from an in-memory TTF/OTF byte buffer, used as its
    /// regular (sans) face. Use this when you need deterministic glyph output
    /// independent of the host's installed fonts — for example, snapshot tests
    /// that bundle the font they render with via `include_bytes!`. The result
    /// has only a regular face; attach the emphasis faces with
    /// [`with_bold_bytes`](Self::with_bold_bytes),
    /// [`with_italic_bytes`](Self::with_italic_bytes), and
    /// [`with_bold_italic_bytes`](Self::with_bold_italic_bytes).
    pub fn from_sans_bytes(data: Vec<u8>) -> Option<Self> {
        fontdue::Font::from_bytes(data, fontdue::FontSettings::default())
            .ok()
            .map(Self::from_face)
    }

    /// Attach a bold face from an in-memory TTF/OTF buffer (builder style).
    /// A buffer that fails to parse leaves the slot empty (bold then falls back
    /// to the regular face). Pairs with [`from_sans_bytes`](Self::from_sans_bytes)
    /// for deterministic, font-bundling snapshot tests.
    pub fn with_bold_bytes(self, data: Vec<u8>) -> Self {
        self.with_style_bytes(FontStyle::Bold, data)
    }

    /// Attach an italic (or oblique) face from an in-memory buffer. See
    /// [`with_bold_bytes`](Self::with_bold_bytes).
    pub fn with_italic_bytes(self, data: Vec<u8>) -> Self {
        self.with_style_bytes(FontStyle::Italic, data)
    }

    /// Attach a bold-italic face from an in-memory buffer. See
    /// [`with_bold_bytes`](Self::with_bold_bytes).
    pub fn with_bold_italic_bytes(self, data: Vec<u8>) -> Self {
        self.with_style_bytes(FontStyle::BoldItalic, data)
    }

    fn with_style_bytes(mut self, style: FontStyle, data: Vec<u8>) -> Self {
        if let Ok(face) = fontdue::Font::from_bytes(data, fontdue::FontSettings::default()) {
            self.faces[style as usize] = Some(face);
        }
        self
    }

    /// Which styles have a face loaded — drives [`resolve_style`].
    fn presence(&self) -> [bool; STYLE_COUNT] {
        std::array::from_fn(|i| self.faces[i].is_some())
    }

    /// The face to rasterize a requested `style` with. Always returns a loaded
    /// face: an absent bold/italic resolves down to one that exists (ultimately
    /// the always-present regular face).
    fn face(&self, style: FontStyle) -> (&fontdue::Font, FontStyle) {
        let resolved = resolve_style(self.presence(), style);
        // `resolve_style` only ever returns a style whose slot is populated.
        (
            self.faces[resolved as usize]
                .as_ref()
                .expect("resolved face is loaded"),
            resolved,
        )
    }

    /// Cached advance width of a single glyph at `size` pixels in `style`. The
    /// first call for a `(char, size, style)` triple asks fontdue; the rest are
    /// map lookups.
    fn advance(&self, ch: char, size: f32, style: FontStyle) -> f32 {
        let (face, resolved) = self.face(style);
        let key = (ch, size.to_bits(), resolved);
        if let Some(a) = self.advances.borrow().get(&key) {
            return *a;
        }
        let a = face.metrics(ch, size).advance_width;
        self.advances.borrow_mut().insert(key, a);
        a
    }

    /// Cached rasterization of a single glyph at `size_phys` physical pixels in
    /// `style`. Returns a shared handle so the caller can drop the cache borrow
    /// before iterating the bitmap.
    fn glyph(&self, ch: char, size_phys: f32, style: FontStyle) -> Rc<Glyph> {
        let (face, resolved) = self.face(style);
        let key = (ch, size_phys.to_bits(), resolved);
        if let Some(g) = self.glyphs.borrow_mut().get(&key) {
            return g;
        }
        let (metrics, bitmap) = face.rasterize(ch, size_phys);
        let g = Rc::new(Glyph { metrics, bitmap });
        self.glyphs.borrow_mut().insert(key, g.clone());
        g
    }

    /// Measure a single line of regular-weight text at the given pixel size.
    /// Returns (advance width, em height). See [`measure_styled`](Self::measure_styled).
    pub fn measure(&self, text: &str, size: f32) -> (f32, f32) {
        self.measure_styled(text, size, FontStyle::Regular)
    }

    /// Measure a single line of text drawn in `style` at the given pixel size.
    /// Returns (advance width, em height). The advance is summed from the
    /// per-glyph cache, so repeated measurements of the same text cost only map
    /// lookups. Bold and italic faces have their own metrics, so measuring with
    /// the same style the text is drawn in keeps wrapping pixel-accurate.
    pub fn measure_styled(&self, text: &str, size: f32, style: FontStyle) -> (f32, f32) {
        let width: f32 = text.chars().map(|ch| self.advance(ch, size, style)).sum();
        // The font's em height is more visually correct than max glyph height
        // when laying out lines of text. We use size as a proxy and pad a
        // little so descenders fit.
        (width, size * 1.2)
    }

    /// Cumulative caret x-offsets for `text` at `size` pixels in `style`.
    /// `out[i]` is the logical-pixel x where the caret sits *before* character
    /// `i`; `out[len]` is the end of the string. A single O(n) pass over the
    /// per-glyph advance cache — the value at each step is the running advance
    /// sum, ceiled, which matches the pixel width
    /// [`measure_styled`](Self::measure_styled) reports for the corresponding
    /// prefix. Replaces the editor's old O(n²) prefix-remeasure.
    pub fn cumulative_widths(&self, text: &str, size: f32, style: FontStyle) -> Vec<i32> {
        let mut out = Vec::with_capacity(text.len() + 1);
        let mut acc = 0.0_f32;
        out.push(0);
        for ch in text.chars() {
            acc += self.advance(ch, size, style);
            out.push(acc.ceil() as i32);
        }
        out
    }

    /// Draw one line of text in `style` at *physical* pixel coordinates. The
    /// caller (Painter::text) has already multiplied logical coords and font
    /// size by the DPI scale, so glyphs are rasterized once at their final
    /// on-screen pixel size — no resampling, no upscale blur.
    ///
    /// Glyphs are pulled from the rasterization cache, and any that fall
    /// entirely outside the painter's horizontal clip are skipped: the pen only
    /// advances rightward, so once a glyph starts past the clip's right edge the
    /// rest of the line is off-screen and the loop stops. This keeps a long line
    /// (a 500-column Markdown row) from blending hundreds of invisible glyphs.
    #[allow(clippy::too_many_arguments)]
    pub(crate) fn draw_phys(
        &self,
        painter: &mut Painter,
        text: &str,
        x: f32,
        y: f32,
        size_phys: f32,
        color: Color,
        style: FontStyle,
    ) -> f32 {
        let baseline = y + size_phys;
        let (clip_lo, clip_hi) = painter.glyph_clip_x();
        let mut pen_x = x;
        for ch in text.chars() {
            let glyph = self.glyph(ch, size_phys, style);
            let metrics = &glyph.metrics;
            let glyph_x = pen_x + metrics.xmin as f32;
            // Everything from here rightward is past the visible span.
            if glyph_x >= clip_hi as f32 {
                break;
            }
            // This glyph ends before the visible span — advance past it without
            // blending (matters when content is scrolled left of the origin).
            if glyph_x + metrics.width as f32 <= clip_lo as f32 {
                pen_x += metrics.advance_width;
                continue;
            }
            let glyph_y = baseline - metrics.ymin as f32 - metrics.height as f32;
            for row in 0..metrics.height {
                let dy = glyph_y as i32 + row as i32;
                let src_row = row * metrics.width;
                for col in 0..metrics.width {
                    let alpha = glyph.bitmap[src_row + col];
                    if alpha == 0 {
                        continue;
                    }
                    let dx = glyph_x as i32 + col as i32;
                    painter.blend_pixel_phys(dx, dy, color, alpha);
                }
            }
            pen_x += metrics.advance_width;
        }
        pen_x
    }
}

fn load_face(db: &fontdb::Database, id: fontdb::ID) -> Option<fontdue::Font> {
    let mut data: Option<Vec<u8>> = None;
    db.with_face_data(id, |bytes, _| data = Some(bytes.to_vec()));
    let data = data?;
    fontdue::Font::from_bytes(data, fontdue::FontSettings::default()).ok()
}

/// The weight at or above which we consider a face "bold". OS/2 weight classes
/// put Regular at 400 and Bold at 700; SemiBold (600) and up read as bold enough
/// to serve as the bold face, while Medium (500) does not.
const BOLD_WEIGHT_THRESHOLD: u16 = 600;

/// Query `db` for a `family` face in the requested weight/style and load it —
/// but only if the face fontdb hands back *actually* carries that emphasis.
///
/// fontdb's `query` runs the CSS font-matching algorithm, which returns the
/// closest available face even when nothing matches: ask a family with no italic
/// for its italic and you get the upright face back. Accepting that would let a
/// regular face masquerade as bold or italic — exactly the synthesized-looking
/// fakery we want to avoid — so we re-read the matched face's own weight/style
/// and reject a mismatch. The caller then leaves that slot empty and falls back
/// to a real face at draw time.
fn query_verified(
    db: &fontdb::Database,
    family: &str,
    weight: fontdb::Weight,
    style: fontdb::Style,
) -> Option<fontdue::Font> {
    let query = fontdb::Query {
        families: &[fontdb::Family::Name(family)],
        weight,
        stretch: fontdb::Stretch::Normal,
        style,
    };
    let id = db.query(&query)?;
    let info = db.face(id)?;

    let want_bold = weight.0 >= BOLD_WEIGHT_THRESHOLD;
    let want_slanted = style != fontdb::Style::Normal;
    let is_bold = info.weight.0 >= BOLD_WEIGHT_THRESHOLD;
    let is_slanted = info.style != fontdb::Style::Normal;
    if is_bold != want_bold || is_slanted != want_slanted {
        return None;
    }

    load_face(db, id)
}

/// Build a [`Font`] for `family`, loading its regular face plus whichever of the
/// bold / italic / bold-italic faces the host actually ships. Returns `None`
/// when the family has no usable regular face.
fn load_styled_family(db: &fontdb::Database, family: &str) -> Option<Font> {
    let regular = query_verified(db, family, fontdb::Weight::NORMAL, fontdb::Style::Normal)?;
    let mut font = Font::from_face(regular);
    let mut attach = |style: FontStyle, weight, slant| {
        if let Some(face) = query_verified(db, family, weight, slant) {
            font.faces[style as usize] = Some(face);
        }
    };
    attach(FontStyle::Bold, fontdb::Weight::BOLD, fontdb::Style::Normal);
    attach(
        FontStyle::Italic,
        fontdb::Weight::NORMAL,
        fontdb::Style::Italic,
    );
    attach(
        FontStyle::BoldItalic,
        fontdb::Weight::BOLD,
        fontdb::Style::Italic,
    );
    Some(font)
}

/// Search `db` for the first family name in `families` that resolves to a
/// loadable regular face, returning that family with all of its emphasis faces.
/// When `monospace_fallback` is true, after exhausting the named families the
/// search also accepts any face whose record claims monospace — useful so we
/// don't accidentally drop into a proportional font when none of the well-known
/// mono families are installed. The fallback faces are regular-only (no styled
/// variants); a named family is the path that carries bold/italic.
fn load_family_chain(families: &[&str], monospace_fallback: bool) -> Option<Font> {
    let mut db = fontdb::Database::new();
    db.load_system_fonts();
    // fontdb's fontconfig loader hardcodes /etc/fonts/fonts.conf, but the
    // FreeBSD port installs fontconfig at /usr/local/etc/fonts/fonts.conf —
    // so on a stock FreeBSD desktop load_system_fonts ends up with zero
    // faces. Fall back to the conventional ports font directory.
    if db.faces().next().is_none() {
        db.load_fonts_dir("/usr/local/share/fonts");
    }

    for family in families {
        if let Some(font) = load_styled_family(&db, family) {
            return Some(font);
        }
    }

    if monospace_fallback {
        for face in db.faces() {
            if face.monospaced
                && let Some(font) = load_face(&db, face.id)
            {
                return Some(Font::from_face(font));
            }
        }
    }

    // Last-ditch: any face we can find. Better something than nothing.
    for face in db.faces() {
        if let Some(font) = load_face(&db, face.id) {
            return Some(Font::from_face(font));
        }
    }

    None
}

#[cfg(test)]
mod tests {
    use super::*;

    impl<K: Eq + std::hash::Hash + Copy, V: Clone> LruCache<K, V> {
        fn len(&self) -> usize {
            self.entries.len()
        }
        fn contains(&self, key: &K) -> bool {
            self.entries.contains_key(key)
        }
    }

    #[test]
    fn evicts_the_least_recently_used_entry() {
        let mut cache: LruCache<i32, i32> = LruCache::new(2);
        cache.insert(1, 10);
        cache.insert(2, 20);
        // Touch key 1 so key 2 becomes the least-recently-used.
        assert_eq!(cache.get(&1), Some(10));
        // Inserting a third key overflows capacity → evict key 2, keep 1 and 3.
        cache.insert(3, 30);
        assert_eq!(cache.len(), 2);
        assert!(cache.contains(&1));
        assert!(!cache.contains(&2), "the untouched entry is evicted");
        assert!(cache.contains(&3));
    }

    #[test]
    fn overwriting_an_existing_key_never_evicts() {
        let mut cache: LruCache<i32, i32> = LruCache::new(2);
        cache.insert(1, 10);
        cache.insert(2, 20);
        // Re-inserting an existing key updates in place — the cache is full but
        // the key already lives there, so nothing is evicted.
        cache.insert(1, 11);
        assert_eq!(cache.len(), 2);
        assert_eq!(cache.get(&1), Some(11));
        assert!(cache.contains(&2));
    }

    #[test]
    fn a_miss_returns_none() {
        let mut cache: LruCache<i32, i32> = LruCache::new(4);
        assert_eq!(cache.get(&99), None);
    }

    #[test]
    fn font_style_from_flags() {
        assert_eq!(FontStyle::new(false, false), FontStyle::Regular);
        assert_eq!(FontStyle::new(true, false), FontStyle::Bold);
        assert_eq!(FontStyle::new(false, true), FontStyle::Italic);
        assert_eq!(FontStyle::new(true, true), FontStyle::BoldItalic);

        assert!(FontStyle::Bold.is_bold() && !FontStyle::Bold.is_italic());
        assert!(FontStyle::Italic.is_italic() && !FontStyle::Italic.is_bold());
        assert!(FontStyle::BoldItalic.is_bold() && FontStyle::BoldItalic.is_italic());
        assert!(!FontStyle::Regular.is_bold() && !FontStyle::Regular.is_italic());
    }

    #[test]
    fn resolve_uses_the_real_face_when_present() {
        let all = [true, true, true, true];
        assert_eq!(resolve_style(all, FontStyle::Bold), FontStyle::Bold);
        assert_eq!(resolve_style(all, FontStyle::Italic), FontStyle::Italic);
        assert_eq!(
            resolve_style(all, FontStyle::BoldItalic),
            FontStyle::BoldItalic
        );
    }

    #[test]
    fn resolve_falls_back_to_regular_when_a_face_is_missing() {
        // Only the regular face is loaded — every request resolves to it, never
        // to a synthesized style.
        let only_regular = [true, false, false, false];
        for style in [
            FontStyle::Regular,
            FontStyle::Bold,
            FontStyle::Italic,
            FontStyle::BoldItalic,
        ] {
            assert_eq!(resolve_style(only_regular, style), FontStyle::Regular);
        }
    }

    #[test]
    fn resolve_bold_italic_prefers_the_closest_real_face() {
        // No bold-italic face, but a bold one exists: keep the weight.
        let no_bi_has_bold = [true, true, false, false];
        assert_eq!(
            resolve_style(no_bi_has_bold, FontStyle::BoldItalic),
            FontStyle::Bold
        );
        // No bold-italic and no bold, but an italic exists: keep the slant.
        let no_bi_has_italic = [true, false, true, false];
        assert_eq!(
            resolve_style(no_bi_has_italic, FontStyle::BoldItalic),
            FontStyle::Italic
        );
    }
}