| 1098 |
1098 |
|
// honoured by the generated stylesheet and ignored, silently and correctly, by
|
| 1099 |
1099 |
|
// the other two. A renderer that gains font control later reads
|
| 1100 |
1100 |
|
// [`Typography::resolve`] rather than the CSS, which is why the resolution is
|
| 1101 |
|
- |
// a method on the data and not a string-building detail.
|
|
1101 |
+ |
// a method on the data and not a string-building detail. Loading a file needs
|
|
1102 |
+ |
// one thing more than the stack — the family name and the source to load it
|
|
1103 |
+ |
// from — so [`Typography::faces`] is the same data read the other way, and
|
|
1104 |
+ |
// between them an egui or TUI surface can honour an override without a
|
|
1105 |
+ |
// stylesheet anywhere in the path. audiofiles is the first to do it.
|
| 1102 |
1106 |
|
// ============================================================================
|
| 1103 |
1107 |
|
|
| 1104 |
1108 |
|
/// A slot in the house font vocabulary — the unit an override replaces.
|
| 1192 |
1196 |
|
self
|
| 1193 |
1197 |
|
}
|
| 1194 |
1198 |
|
|
|
1199 |
+ |
/// The family name, as the stack has to spell it.
|
|
1200 |
+ |
///
|
|
1201 |
+ |
/// For a renderer that loads faces rather than emitting CSS this is the
|
|
1202 |
+ |
/// name it registers the file under, and reading it here is what keeps
|
|
1203 |
+ |
/// that name from being typed a second time.
|
|
1204 |
+ |
pub fn family(&self) -> &str {
|
|
1205 |
+ |
&self.family
|
|
1206 |
+ |
}
|
|
1207 |
+ |
|
|
1208 |
+ |
/// The sources, unresolved — bare filenames as they were declared, not
|
|
1209 |
+ |
/// joined to any base URL. A renderer loading from disk or from an
|
|
1210 |
+ |
/// `include_bytes!` wants the filename; only the CSS wants the URL.
|
|
1211 |
+ |
pub fn sources(&self) -> &[String] {
|
|
1212 |
+ |
&self.sources
|
|
1213 |
+ |
}
|
|
1214 |
+ |
|
| 1195 |
1215 |
|
fn css(&self, base: &str) -> String {
|
| 1196 |
1216 |
|
use std::fmt::Write as _;
|
| 1197 |
1217 |
|
|
| 1277 |
1297 |
|
pub fn stack(&self) -> &str {
|
| 1278 |
1298 |
|
&self.stack
|
| 1279 |
1299 |
|
}
|
|
1300 |
+ |
|
|
1301 |
+ |
/// The faces it ships, in declaration order.
|
|
1302 |
+ |
pub fn faces(&self) -> &[FontFace] {
|
|
1303 |
+ |
&self.faces
|
|
1304 |
+ |
}
|
| 1280 |
1305 |
|
}
|
| 1281 |
1306 |
|
|
| 1282 |
1307 |
|
/// The whole typography layer for one product: the house defaults, plus
|
| 1333 |
1358 |
|
.or_else(|| slot.house_default())
|
| 1334 |
1359 |
|
}
|
| 1335 |
1360 |
|
|
|
1361 |
+ |
/// The faces a product ships for `slot`, in declaration order, or an
|
|
1362 |
+ |
/// empty slice for a slot it did not override.
|
|
1363 |
+ |
///
|
|
1364 |
+ |
/// The other half of [`resolve`](Self::resolve), for a renderer that has
|
|
1365 |
+ |
/// to load a file rather than name a stack: `resolve` says which family
|
|
1366 |
+ |
/// wins, this says where the bytes come from and what to call them. The
|
|
1367 |
+ |
/// house faces are not here — they are the emitter's, and a renderer that
|
|
1368 |
+ |
/// wants them has them by other means.
|
|
1369 |
+ |
pub fn faces(&self, slot: FontSlot) -> &[FontFace] {
|
|
1370 |
+ |
self.overrides
|
|
1371 |
+ |
.iter()
|
|
1372 |
+ |
.find(|o| o.slot == slot)
|
|
1373 |
+ |
.map_or(&[], |o| o.faces())
|
|
1374 |
+ |
}
|
|
1375 |
+ |
|
| 1336 |
1376 |
|
/// The `@font-face` rules: the two house faces, then each override's.
|
| 1337 |
1377 |
|
pub fn font_face_css(&self) -> String {
|
| 1338 |
1378 |
|
let base = self.base_url.trim_end_matches('/');
|
| 3186 |
3226 |
|
assert!(faces.contains(" font-weight: 700;\n"));
|
| 3187 |
3227 |
|
}
|
| 3188 |
3228 |
|
|
|
3229 |
+ |
#[test]
|
|
3230 |
+ |
fn a_face_loading_renderer_reads_the_family_and_the_source_off_the_layer() {
|
|
3231 |
+ |
// The egui case, which has no stylesheet in the path at all: the
|
|
3232 |
+ |
// renderer registers the file under a name, and the name has to be
|
|
3233 |
+ |
// the one the stack spells or the two halves drift.
|
|
3234 |
+ |
let t = Typography::house("fonts").with_override(
|
|
3235 |
+ |
FontOverride::new(FontSlot::Display, "\"RecursiveMono\", monospace").with_face(
|
|
3236 |
+ |
FontFace::new("RecursiveMono", ["RecursiveMonoLnrSt-Bold.ttf"]).weight("700"),
|
|
3237 |
+ |
),
|
|
3238 |
+ |
);
|
|
3239 |
+ |
|
|
3240 |
+ |
let [face] = t.faces(FontSlot::Display) else {
|
|
3241 |
+ |
panic!("the display slot ships exactly one face");
|
|
3242 |
+ |
};
|
|
3243 |
+ |
assert_eq!(face.family(), "RecursiveMono");
|
|
3244 |
+ |
assert_eq!(face.sources(), ["RecursiveMonoLnrSt-Bold.ttf"]);
|
|
3245 |
+ |
assert!(
|
|
3246 |
+ |
t.resolve(FontSlot::Display)
|
|
3247 |
+ |
.unwrap()
|
|
3248 |
+ |
.contains(face.family())
|
|
3249 |
+ |
);
|
|
3250 |
+ |
}
|
|
3251 |
+ |
|
|
3252 |
+ |
#[test]
|
|
3253 |
+ |
fn a_source_is_read_back_unresolved_because_only_the_css_wants_a_url() {
|
|
3254 |
+ |
let t = Typography::house("/static/fonts").with_override(young_serif());
|
|
3255 |
+ |
assert_eq!(
|
|
3256 |
+ |
t.faces(FontSlot::Display)[0].sources(),
|
|
3257 |
+ |
["ysrf.woff2", "ysrf.ttf"]
|
|
3258 |
+ |
);
|
|
3259 |
+ |
// The same face, joined to the base, in the sheet.
|
|
3260 |
+ |
assert!(
|
|
3261 |
+ |
t.font_face_css()
|
|
3262 |
+ |
.contains("url(\"/static/fonts/ysrf.woff2\")")
|
|
3263 |
+ |
);
|
|
3264 |
+ |
}
|
|
3265 |
+ |
|
|
3266 |
+ |
#[test]
|
|
3267 |
+ |
fn a_slot_nobody_overrode_ships_no_faces_including_the_house_two() {
|
|
3268 |
+ |
let t = Typography::house("fonts").with_override(young_serif());
|
|
3269 |
+ |
assert!(t.faces(FontSlot::Mono).is_empty());
|
|
3270 |
+ |
assert!(t.faces(FontSlot::Sans).is_empty());
|
|
3271 |
+ |
assert_eq!(t.faces(FontSlot::Display).len(), 1);
|
|
3272 |
+ |
}
|
|
3273 |
+ |
|
| 3189 |
3274 |
|
#[test]
|
| 3190 |
3275 |
|
fn an_unrecognised_extension_gets_no_format_hint_rather_than_a_guessed_one() {
|
| 3191 |
3276 |
|
let t = Typography::house("fonts").with_override(
|