| 331 |
331 |
|
Ok(())
|
| 332 |
332 |
|
}
|
| 333 |
333 |
|
|
|
334 |
+ |
/// One slot, cut and written for a native consumer.
|
|
335 |
+ |
///
|
|
336 |
+ |
/// Carries what a `build.rs` has to export beside the file itself. A native
|
|
337 |
+ |
/// renderer registers a face by family and style and picks a weight to draw at,
|
|
338 |
+ |
/// and none of the three can be assumed from the filename: the style is the
|
|
339 |
+ |
/// base's default instance, and Atkinson Mono's is `ExtraLight` rather than
|
|
340 |
+ |
/// `Regular`.
|
|
341 |
+ |
pub struct NativeFace {
|
|
342 |
+ |
/// The slot id that was cut, as it appears in the pins.
|
|
343 |
+ |
pub slot: String,
|
|
344 |
+ |
/// The output family, e.g. `Quasi Mono`.
|
|
345 |
+ |
pub family: String,
|
|
346 |
+ |
/// The style of the face that was written.
|
|
347 |
+ |
pub style: String,
|
|
348 |
+ |
/// The weight to draw at unless the consumer says otherwise: the variable
|
|
349 |
+ |
/// axis default where there is an axis, and 400 where there is not.
|
|
350 |
+ |
pub default_weight: f32,
|
|
351 |
+ |
/// The ttf that was written.
|
|
352 |
+ |
pub path: std::path::PathBuf,
|
|
353 |
+ |
/// The licence text that was written beside it.
|
|
354 |
+ |
pub license_path: std::path::PathBuf,
|
|
355 |
+ |
}
|
|
356 |
+ |
|
|
357 |
+ |
/// Cut slots into a directory a native build points at, as ttf.
|
|
358 |
+ |
///
|
|
359 |
+ |
/// The native counterpart of [`cut_web`], and the same agreement in a different
|
|
360 |
+ |
/// shape: `cut_web` writes woff2 for a browser to fetch, this writes ttf for a
|
|
361 |
+ |
/// font loader to register. `slots` pairs a slot id with the filename it lands
|
|
362 |
+ |
/// under, the filename being the caller's for the same reason as there — it is
|
|
363 |
+ |
/// half of an agreement this crate cannot see, and the other half lives in
|
|
364 |
+ |
/// whatever the consumer does with `OUT_DIR`.
|
|
365 |
+ |
///
|
|
366 |
+ |
/// ttf rather than woff2: nothing native decodes woff2 without being told how,
|
|
367 |
+ |
/// and `cut` already returns both, so this is packaging rather than a second
|
|
368 |
+ |
/// pipeline.
|
|
369 |
+ |
///
|
|
370 |
+ |
/// This exists because the house tier has to reach native renderers without
|
|
371 |
+ |
/// going through `makeover`. `makeover::FontSlot::house_face` names the *web*
|
|
372 |
+ |
/// copies, and a native consumer cuts its own; before this it copied about
|
|
373 |
+ |
/// thirty lines out of `shop/crates/shop-font/build.rs` to do it, which is a
|
|
374 |
+ |
/// second caller doing its own file naming and its own weight defaulting.
|
|
375 |
+ |
///
|
|
376 |
+ |
/// The licence text travels as `OFL-<Family>.txt` beside each face, which is
|
|
377 |
+ |
/// what OFL requires of a build that ships the face.
|
|
378 |
+ |
///
|
|
379 |
+ |
/// Nothing is rewritten if it is already byte-identical, for the reason
|
|
380 |
+ |
/// [`cut_web`] gives: a build script that rewrites its outputs every run bumps
|
|
381 |
+ |
/// their mtimes, and cargo then rebuilds everything watching them.
|
|
382 |
+ |
///
|
|
383 |
+ |
/// # Errors
|
|
384 |
+ |
///
|
|
385 |
+ |
/// If a slot cannot be cut, or a file cannot be written.
|
|
386 |
+ |
pub fn cut_native(
|
|
387 |
+ |
dir: &std::path::Path,
|
|
388 |
+ |
cache: &std::path::Path,
|
|
389 |
+ |
offline: bool,
|
|
390 |
+ |
slots: &[(&str, &str)],
|
|
391 |
+ |
) -> Result<Vec<NativeFace>, Error> {
|
|
392 |
+ |
std::fs::create_dir_all(dir).map_err(|e| Error::Io(dir.to_path_buf(), e))?;
|
|
393 |
+ |
let mut written = Vec::with_capacity(slots.len());
|
|
394 |
+ |
for (slot_id, filename) in slots {
|
|
395 |
+ |
let cut = cut(slot_id, cache, offline)?;
|
|
396 |
+ |
let face = cut
|
|
397 |
+ |
.faces
|
|
398 |
+ |
.first()
|
|
399 |
+ |
.ok_or_else(|| Error::Pins(format!("slot `{slot_id}` cut no faces")))?;
|
|
400 |
+ |
let path = dir.join(filename);
|
|
401 |
+ |
let license_path = dir.join(format!("OFL-{}.txt", cut.family.replace(' ', "")));
|
|
402 |
+ |
write_if_changed(&path, &face.ttf)?;
|
|
403 |
+ |
write_if_changed(&license_path, &cut.license)?;
|
|
404 |
+ |
written.push(NativeFace {
|
|
405 |
+ |
slot: (*slot_id).to_string(),
|
|
406 |
+ |
family: cut.family.clone(),
|
|
407 |
+ |
style: face.style.clone(),
|
|
408 |
+ |
default_weight: face.variation.as_ref().map_or(400.0, |axis| axis.default),
|
|
409 |
+ |
path,
|
|
410 |
+ |
license_path,
|
|
411 |
+ |
});
|
|
412 |
+ |
}
|
|
413 |
+ |
Ok(written)
|
|
414 |
+ |
}
|
|
415 |
+ |
|
| 334 |
416 |
|
fn write_if_changed(path: &std::path::Path, bytes: &[u8]) -> Result<(), Error> {
|
| 335 |
417 |
|
if std::fs::read(path).is_ok_and(|existing| existing == bytes) {
|
| 336 |
418 |
|
return Ok(());
|