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
// Copyright 2025 the Parley Authors
// SPDX-License-Identifier: Apache-2.0 OR MIT
use core::fmt;
/// Generic font families, named after CSS.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[repr(u8)]
pub enum GenericFamily {
/// Glyphs have finishing strokes, flared or tapering ends, or have actual serifed endings.
Serif = 0,
/// Glyphs have stroke endings that are plain.
SansSerif = 1,
/// All glyphs have the same fixed width.
Monospace = 2,
/// 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.
Cursive = 3,
/// Decorative fonts with playful representations of characters.
Fantasy = 4,
/// Glyphs are taken from the default user interface font on a given platform.
SystemUi = 5,
/// The default user interface serif font.
UiSerif = 6,
/// The default user interface sans-serif font.
UiSansSerif = 7,
/// The default user interface monospace font.
UiMonospace = 8,
/// The default user interface font that has rounded features.
UiRounded = 9,
/// Fonts that are specifically designed to render emoji.
Emoji = 10,
/// 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.
Math = 11,
/// 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.
FangSong = 12,
// NOTICE: If a new value is added, be sure to modify `MAX_VALUE`.
}
impl GenericFamily {
/// Returns the maximum numeric value for known variants.
///
/// This is primarily intended for use in fixed-size maps keyed by `GenericFamily`.
pub const MAX_VALUE: u8 = Self::FangSong as u8;
/// Parses a generic family from a CSS generic family name.
///
/// ```
/// use parlance::GenericFamily;
///
/// assert_eq!(
/// GenericFamily::parse("sans-serif"),
/// Some(GenericFamily::SansSerif)
/// );
/// assert_eq!(GenericFamily::parse("SERIF"), Some(GenericFamily::Serif));
/// assert_eq!(GenericFamily::parse("Arial"), None);
/// ```
pub fn parse(s: &str) -> Option<Self> {
let s = s.trim();
// The longest generic family name is `ui-sans-serif` (13 bytes). Any
// input longer than this cannot match, and any shorter input is
// lowercased once into this fixed-size buffer so we only have to do a
// single case-sensitive comparison below.
const MAX_LEN: usize = "ui-sans-serif".len();
let bytes = s.as_bytes();
if bytes.len() > MAX_LEN {
return None;
}
let mut buf = [0_u8; MAX_LEN];
let buf = &mut buf[..bytes.len()];
buf.copy_from_slice(bytes);
buf.make_ascii_lowercase();
Some(match &*buf {
b"serif" => Self::Serif,
b"sans-serif" => Self::SansSerif,
b"monospace" => Self::Monospace,
b"cursive" => Self::Cursive,
b"fantasy" => Self::Fantasy,
b"system-ui" => Self::SystemUi,
b"ui-serif" => Self::UiSerif,
b"ui-sans-serif" => Self::UiSansSerif,
b"ui-monospace" => Self::UiMonospace,
b"ui-rounded" => Self::UiRounded,
b"emoji" => Self::Emoji,
b"math" => Self::Math,
b"fangsong" => Self::FangSong,
_ => return None,
})
}
/// Returns a slice containing all generic family variants.
pub const fn all() -> &'static [Self] {
&[
Self::SansSerif,
Self::Serif,
Self::Monospace,
Self::Cursive,
Self::Fantasy,
Self::SystemUi,
Self::UiSerif,
Self::UiSansSerif,
Self::UiMonospace,
Self::UiRounded,
Self::Emoji,
Self::Math,
Self::FangSong,
]
}
}
impl fmt::Display for GenericFamily {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let name = match self {
Self::Serif => "serif",
Self::SansSerif => "sans-serif",
Self::Monospace => "monospace",
Self::Cursive => "cursive",
Self::Fantasy => "fantasy",
Self::SystemUi => "system-ui",
Self::UiSerif => "ui-serif",
Self::UiSansSerif => "ui-sans-serif",
Self::UiMonospace => "ui-monospace",
Self::UiRounded => "ui-rounded",
Self::Emoji => "emoji",
Self::Math => "math",
Self::FangSong => "fangsong",
};
f.write_str(name)
}
}
#[cfg(test)]
mod tests {
use super::GenericFamily;
#[test]
fn parse_is_ascii_case_insensitive() {
assert_eq!(GenericFamily::parse("SERIF"), Some(GenericFamily::Serif));
assert_eq!(
GenericFamily::parse("Sans-Serif"),
Some(GenericFamily::SansSerif)
);
assert_eq!(
GenericFamily::parse("MONOSPACE"),
Some(GenericFamily::Monospace)
);
assert_eq!(
GenericFamily::parse("UI-Rounded"),
Some(GenericFamily::UiRounded)
);
assert_eq!(
GenericFamily::parse("FangSong"),
Some(GenericFamily::FangSong)
);
}
#[test]
fn parse_preserves_lowercase_trim_and_named_behavior() {
assert_eq!(GenericFamily::parse("serif"), Some(GenericFamily::Serif));
assert_eq!(
GenericFamily::parse(" SERIF "),
Some(GenericFamily::Serif)
);
assert_eq!(GenericFamily::parse("Arial"), None);
}
}