max / quasi-type
- Co-Authored-By
- Claude Opus 5 (1M context) <noreply@anthropic.com>
1 file changed,
+53 insertions,
-0 deletions
| @@ -284,3 +284,56 @@ | |||
| 284 | 284 | faces, | |
| 285 | 285 | }) | |
| 286 | 286 | } | |
| 287 | + | ||
| 288 | + | /// Cut slots straight into a directory a web server serves, as woff2. | |
| 289 | + | /// | |
| 290 | + | /// `slots` pairs a slot id with the filename it lands under, and the filename | |
| 291 | + | /// is the caller's because it is half of an agreement this crate cannot see: | |
| 292 | + | /// `makeover` emits the `@font-face` that fetches these, and its | |
| 293 | + | /// `WEBFONT_MONO_FILE` / `WEBFONT_SANS_FILE` are the other half. makeover | |
| 294 | + | /// cannot call this — it is on crates.io and this crate is `publish = false` — | |
| 295 | + | /// so the two meet in a consumer's build script, and this is the part of that | |
| 296 | + | /// meeting worth having once instead of three times. | |
| 297 | + | /// | |
| 298 | + | /// woff2 only. Every browser that can run a Make Creative frontend has | |
| 299 | + | /// supported it since 2018, so a ttf beside it is two to three times the bytes | |
| 300 | + | /// for a case that does not arrive. | |
| 301 | + | /// | |
| 302 | + | /// The licence text travels as `OFL-<Family>.txt` beside each face, which is | |
| 303 | + | /// what OFL requires of a build that ships the face. | |
| 304 | + | /// | |
| 305 | + | /// Nothing is rewritten if it is already byte-identical. A build script that | |
| 306 | + | /// rewrites its outputs every run bumps their mtimes, and everything watching | |
| 307 | + | /// those files then believes the fonts moved. | |
| 308 | + | /// | |
| 309 | + | /// # Errors | |
| 310 | + | /// | |
| 311 | + | /// If a slot cannot be cut, or a file cannot be written. | |
| 312 | + | pub fn cut_web( | |
| 313 | + | dir: &std::path::Path, | |
| 314 | + | cache: &std::path::Path, | |
| 315 | + | offline: bool, | |
| 316 | + | slots: &[(&str, &str)], | |
| 317 | + | ) -> Result<(), Error> { | |
| 318 | + | std::fs::create_dir_all(dir).map_err(|e| Error::Io(dir.to_path_buf(), e))?; | |
| 319 | + | for (slot_id, filename) in slots { | |
| 320 | + | let cut = cut(slot_id, cache, offline)?; | |
| 321 | + | let face = cut | |
| 322 | + | .faces | |
| 323 | + | .first() | |
| 324 | + | .ok_or_else(|| Error::Pins(format!("slot `{slot_id}` cut no faces")))?; | |
| 325 | + | write_if_changed(&dir.join(filename), &face.woff2)?; | |
| 326 | + | write_if_changed( | |
| 327 | + | &dir.join(format!("OFL-{}.txt", cut.family.replace(' ', ""))), | |
| 328 | + | &cut.license, | |
| 329 | + | )?; | |
| 330 | + | } | |
| 331 | + | Ok(()) | |
| 332 | + | } | |
| 333 | + | ||
| 334 | + | fn write_if_changed(path: &std::path::Path, bytes: &[u8]) -> Result<(), Error> { | |
| 335 | + | if std::fs::read(path).is_ok_and(|existing| existing == bytes) { | |
| 336 | + | return Ok(()); | |
| 337 | + | } | |
| 338 | + | std::fs::write(path, bytes).map_err(|e| Error::Io(path.to_path_buf(), e)) | |
| 339 | + | } |