Skip to main content

max / quasi-type

29.9 KB · 745 lines History Blame Raw
1 //! Assembling the output face: base tables in, a `Quasi <Slot>` face out.
2 //!
3 //! The base's own glyphs are copied as bytes and never recompiled. Plex Mono is
4 //! hinted (`cvt `, `fpgm`, `prep`), and round-tripping 1,207 glyphs through a
5 //! builder to add seven risks changing a face nobody asked us to change. So
6 //! `glyf` is spliced: base bytes, then ours, with `loca` rebuilt over the pair.
7
8 use std::collections::BTreeMap;
9
10 use read_fonts::types::{GlyphId, Tag};
11 use read_fonts::{FontRef, TableProvider, TopLevelTable};
12 use write_fonts::FontBuilder;
13 use write_fonts::tables::cmap::Cmap;
14 use write_fonts::tables::glyf::{Glyph, SimpleGlyph};
15 use write_fonts::tables::name::{Name, NameRecord};
16 use write_fonts::tables::post::Post;
17 use write_fonts::types::{Fixed, NameId, Version16Dot16};
18
19 use crate::Error;
20 use crate::base::{self, BaseParams, Variation};
21 use crate::draw;
22 use crate::manifest::{GlyphSpec, Purpose};
23 use crate::pins::Base;
24 use crate::vary;
25
26 /// What the pipeline stamps into a face's `name` table.
27 pub struct Identity<'a> {
28 pub family: &'a str,
29 pub style: &'a str,
30 pub version: &'a str,
31 /// `[set] version` from the glyph manifest, stated in the description on
32 /// its own rather than only folded into `version`.
33 ///
34 /// A cut face is a composition of a base and a glyph set, and the set
35 /// versions independently of the base and of this pipeline. Two faces over
36 /// the same base carrying different sets are different fonts, so "which set
37 /// is this face carrying" has to be answerable from the file. It was not:
38 /// `version` is `{set}.{base}`, and nothing in the face says where the
39 /// boundary falls, so `1.2.5.0` is unreadable without knowing that Plex Mono
40 /// happened to be at 2.5.0.
41 pub set_version: u32,
42 pub base: &'a Base,
43 }
44
45 /// A face, and what went into it.
46 pub struct Built {
47 pub bytes: Vec<u8>,
48 pub added: Vec<(u32, String)>,
49 pub params: BaseParams,
50 /// The axis the face carries, when its base had one. A cut keeps the axis
51 /// rather than instancing it away, so this is the base's `fvar` unchanged.
52 pub variation: Option<Variation>,
53 /// Generated cell primitives the base already drew, so it kept its own.
54 /// Reported rather than silent: "167 marks" and "167 marks, 160 of which
55 /// the base already had" are very different builds.
56 pub kept_by_base: Vec<u32>,
57 }
58
59 /// Tables the pipeline rebuilds. Everything else is copied verbatim.
60 const REBUILT: [Tag; 8] = [
61 Tag::new(b"glyf"),
62 Tag::new(b"loca"),
63 Tag::new(b"cmap"),
64 Tag::new(b"hmtx"),
65 Tag::new(b"hhea"),
66 Tag::new(b"maxp"),
67 Tag::new(b"name"),
68 Tag::new(b"post"),
69 ];
70
71 /// Tables that do not survive modification.
72 ///
73 /// `DSIG` signs the bytes we just changed, so keeping it would ship a signature
74 /// that fails to verify. Dropping it is what every font tool does here.
75 const DROPPED: [Tag; 1] = [Tag::new(b"DSIG")];
76
77 pub fn build(base_bytes: &[u8], glyphs: &[&GlyphSpec], id: &Identity<'_>) -> Result<Built, Error> {
78 let font =
79 FontRef::new(base_bytes).map_err(|e| Error::Font(format!("base is unreadable: {e}")))?;
80 let variation = base::variation(base_bytes)?;
81 if let Some(axis) = &variation {
82 check_default_instance(id, axis)?;
83 }
84 // A variable base is measured at its axis default rather than out of `glyf`,
85 // which is the same location by definition and the same numbers for a static
86 // face. The other masters are measured the same way, one location each.
87 let params = match &variation {
88 Some(axis) => base::measure_at(base_bytes, axis, axis.default_master())?,
89 None => base::measure(base_bytes)?,
90 };
91 let masters: Vec<(f32, BaseParams)> = variation
92 .iter()
93 .flat_map(|axis| {
94 axis.delta_masters()
95 .into_iter()
96 .map(|at| Ok((at.peak, base::measure_at(base_bytes, axis, at)?)))
97 .collect::<Vec<_>>()
98 })
99 .collect::<Result<_, Error>>()?;
100 let mut mappings = base::mappings(base_bytes)?;
101 // The reference glyph, `+`: what the recipes measure their band against and
102 // what `quasi-type params` reads the cell off. See `extend_hvar`.
103 let reference = *mappings
104 .get(&0x2B)
105 .ok_or_else(|| Error::Font("base has no `+`, so no mark can be measured".into()))?;
106
107 let head = font.head().map_err(missing("head"))?;
108 let maxp = font.maxp().map_err(missing("maxp"))?;
109 let hhea = font.hhea().map_err(missing("hhea"))?;
110 let base_glyph_count = maxp.num_glyphs();
111
112 // --- the new glyphs, compiled ------------------------------------------
113
114 let mut appended: Vec<(GlyphId, &GlyphSpec, Vec<u8>, Bounds)> = Vec::new();
115 let mut variations: Vec<Vec<u8>> = Vec::new();
116 let mut skipped: Vec<u32> = Vec::new();
117 for spec in glyphs {
118 if let Some(existing) = mappings.get(&spec.codepoint) {
119 // Which of the two this is decides the answer. See `Purpose`.
120 if spec.purpose == Purpose::Coverage {
121 skipped.push(spec.codepoint);
122 continue;
123 }
124 return Err(Error::AlreadyDrawn {
125 codepoint: spec.codepoint,
126 base: id.base.family.clone(),
127 gid: existing.to_u32(),
128 });
129 }
130 let simple = compile_drawing(spec, &params)?;
131 let bounds = Bounds::of(&simple);
132 if variation.is_some() {
133 // The same recipe at another location, which is what makes a master
134 // a measurement rather than a second drawing.
135 let varied = vary::Varied {
136 default: simple.clone(),
137 masters: masters
138 .iter()
139 .map(|(peak, at)| Ok((*peak, compile_drawing(spec, at)?)))
140 .collect::<Result<_, Error>>()?,
141 };
142 variations.push(match varied.deltas(&spec.name)? {
143 Some(deltas) => vary::compile(deltas)?,
144 // A zero-length entry, which is how `gvar` says "this glyph does
145 // not vary". Every appended glyph needs one so the offsets stay
146 // in step with the glyph ids.
147 None => Vec::new(),
148 });
149 }
150 let bytes = write_fonts::dump_table(&Glyph::Simple(simple))
151 .map_err(|e| Error::Draw(format!("{}: {e}", spec.name)))?;
152 // Counted off what has actually been appended rather than off the
153 // loop index, which stopped being the same number the moment a glyph
154 // could be skipped.
155 let gid = GlyphId::from(base_glyph_count + appended.len() as u16);
156 mappings.insert(spec.codepoint, gid);
157 appended.push((gid, spec, bytes, bounds));
158 }
159
160 let glyph_count = base_glyph_count + appended.len() as u16;
161
162 // --- glyf and loca ------------------------------------------------------
163
164 let base_glyf = table_bytes(&font, Tag::new(b"glyf"))?;
165 let base_loca = read_loca(&font, base_glyph_count)?;
166 // `loca`'s last entry is the end of the glyph data, which can sit short of
167 // the padded table length. Appending from the table's end instead would
168 // leave a gap the offsets do not describe.
169 let mut glyf = base_glyf[..*base_loca.last().unwrap() as usize].to_vec();
170 let mut loca = base_loca;
171 for (_, _, bytes, _) in &appended {
172 glyf.extend_from_slice(bytes);
173 // Long-format offsets need no alignment, but keeping glyphs on a
174 // four-byte boundary matches what every other tool writes.
175 while glyf.len() % 4 != 0 {
176 glyf.push(0);
177 }
178 loca.push(glyf.len() as u32);
179 }
180 let loca_bytes: Vec<u8> = loca.iter().flat_map(|o| o.to_be_bytes()).collect();
181
182 // --- hmtx and hhea ------------------------------------------------------
183
184 let hmtx_bytes = rebuild_hmtx(
185 &font,
186 base_glyph_count,
187 hhea.number_of_h_metrics(),
188 params.advance,
189 &appended,
190 )?;
191 let mut hhea_bytes = table_bytes(&font, Tag::new(b"hhea"))?.to_vec();
192 write_u16(&mut hhea_bytes, 34, glyph_count);
193
194 // --- maxp ---------------------------------------------------------------
195
196 let mut maxp_bytes = table_bytes(&font, Tag::new(b"maxp"))?.to_vec();
197 write_u16(&mut maxp_bytes, 4, glyph_count);
198 if maxp_bytes.len() >= 8 {
199 let points = appended
200 .iter()
201 .map(|(_, _, _, b)| b.points)
202 .max()
203 .unwrap_or(0);
204 let contours = appended
205 .iter()
206 .map(|(_, _, _, b)| b.contours)
207 .max()
208 .unwrap_or(0);
209 bump_u16(&mut maxp_bytes, 6, points);
210 bump_u16(&mut maxp_bytes, 8, contours);
211 }
212
213 // --- head ---------------------------------------------------------------
214
215 let mut head_bytes = table_bytes(&font, Tag::new(b"head"))?.to_vec();
216 // Long offsets unconditionally: a spliced `glyf` can cross the 128KB the
217 // short format reaches, and converting up front means one code path.
218 write_i16(&mut head_bytes, 50, 1);
219 let mut bbox = (head.x_min(), head.y_min(), head.x_max(), head.y_max());
220 for (_, _, _, b) in &appended {
221 bbox.0 = bbox.0.min(b.x_min);
222 bbox.1 = bbox.1.min(b.y_min);
223 bbox.2 = bbox.2.max(b.x_max);
224 bbox.3 = bbox.3.max(b.y_max);
225 }
226 write_i16(&mut head_bytes, 36, bbox.0);
227 write_i16(&mut head_bytes, 38, bbox.1);
228 write_i16(&mut head_bytes, 40, bbox.2);
229 write_i16(&mut head_bytes, 42, bbox.3);
230 // `modified` is left exactly as the base wrote it. A build stamped with the
231 // wall clock is a build that differs from itself, and the done condition
232 // here is a byte-identical face from a clean checkout.
233
234 // --- OS/2 ---------------------------------------------------------------
235
236 let mut os2_bytes = table_bytes(&font, Tag::new(b"OS/2"))?.to_vec();
237 let first = mappings.keys().copied().min().unwrap_or(0);
238 let last = mappings.keys().copied().max().unwrap_or(0);
239 write_u16(&mut os2_bytes, 64, u16::try_from(first).unwrap_or(0xFFFF));
240 write_u16(&mut os2_bytes, 66, u16::try_from(last).unwrap_or(0xFFFF));
241
242 // --- cmap, name, post ---------------------------------------------------
243
244 let cmap = Cmap::from_mappings(
245 mappings
246 .iter()
247 .filter_map(|(cp, gid)| char::from_u32(*cp).map(|c| (c, *gid))),
248 )
249 .map_err(|e| Error::Font(format!("cmap: {e}")))?;
250
251 let name = name_table(id, variation.as_ref(), &font);
252 // `post` 3.0: the base ships 2.0 with a name per glyph, and a 2.0 table has
253 // to carry exactly `numGlyphs` entries. Extending it would mean inventing
254 // names for the seven and rewriting the base's, and nothing on any target
255 // reads glyph names.
256 let post = Post {
257 version: Version16Dot16::VERSION_3_0,
258 italic_angle: Fixed::from_f64(0.0),
259 underline_position: font
260 .post()
261 .map(|p| p.underline_position())
262 .unwrap_or_default(),
263 underline_thickness: font
264 .post()
265 .map(|p| p.underline_thickness())
266 .unwrap_or_default(),
267 is_fixed_pitch: font.post().map_or(0, |p| p.is_fixed_pitch()),
268 ..Default::default()
269 };
270
271 // --- assemble -----------------------------------------------------------
272
273 let mut builder = FontBuilder::new();
274 builder.add_raw(Tag::new(b"glyf"), glyf);
275 builder.add_raw(Tag::new(b"loca"), loca_bytes);
276 builder.add_raw(Tag::new(b"hmtx"), hmtx_bytes);
277 builder.add_raw(Tag::new(b"hhea"), hhea_bytes);
278 builder.add_raw(Tag::new(b"maxp"), maxp_bytes);
279 builder.add_raw(Tag::new(b"head"), head_bytes);
280 builder.add_raw(Tag::new(b"OS/2"), os2_bytes);
281 builder
282 .add_table(&cmap)
283 .map_err(|e| Error::Font(format!("cmap: {e}")))?;
284 builder
285 .add_table(&name)
286 .map_err(|e| Error::Font(format!("name: {e}")))?;
287 builder
288 .add_table(&post)
289 .map_err(|e| Error::Font(format!("post: {e}")))?;
290
291 // --- gvar ---------------------------------------------------------------
292
293 let gvar_tag = Tag::new(b"gvar");
294 if variation.is_some() {
295 let base_gvar = table_bytes(&font, gvar_tag).map_err(|_| {
296 Error::Font(
297 "the base has an `fvar` axis and no `gvar`, so its own glyphs do not \
298 vary. Nothing here can tell what a mark should do on an axis the base \
299 does not use."
300 .into(),
301 )
302 })?;
303 let axis_count = u16::try_from(variation.iter().len()).unwrap_or(1);
304 builder.add_raw(gvar_tag, vary::splice(base_gvar, &variations, axis_count)?);
305 }
306
307 // --- HVAR ---------------------------------------------------------------
308
309 let hvar_tag = Tag::new(b"HVAR");
310 let has_hvar = font
311 .table_directory()
312 .table_records()
313 .iter()
314 .any(|r| r.tag() == hvar_tag);
315 if has_hvar {
316 builder.add_raw(hvar_tag, extend_hvar(&font, reference, appended.len())?);
317 }
318
319 // Everything the pipeline does not touch is carried across as bytes, minus
320 // the tables that modification invalidates.
321 for record in font.table_directory().table_records() {
322 let tag = record.tag();
323 if REBUILT.contains(&tag)
324 || DROPPED.contains(&tag)
325 || tag == Tag::new(b"head")
326 || tag == Tag::new(b"OS/2")
327 || tag == hvar_tag
328 || (variation.is_some() && tag == gvar_tag)
329 {
330 continue;
331 }
332 let data = table_bytes(&font, tag)?;
333 builder.add_raw(tag, data.to_vec());
334 }
335
336 Ok(Built {
337 bytes: builder.build(),
338 added: appended
339 .iter()
340 .map(|(_, spec, _, _)| (spec.codepoint, spec.name.clone()))
341 .collect(),
342 params,
343 variation,
344 kept_by_base: skipped,
345 })
346 }
347
348 /// One mark, drawn against one set of measurements.
349 fn compile_drawing(spec: &GlyphSpec, params: &BaseParams) -> Result<SimpleGlyph, Error> {
350 let path = draw::draw(&spec.shape, params).to_bezpath();
351 SimpleGlyph::from_bezpath(&path).map_err(|e| Error::Draw(format!("{}: {e:?}", spec.name)))
352 }
353
354 /// The declared style has to be the style of the base's default instance.
355 ///
356 /// This is the ExtraLight trap, made mechanical. Atkinson Mono's `fvar` default
357 /// is `wght` 200 and its own `name` table reads `Atkinson Hyperlegible Mono
358 /// ExtraLight`, so a face cut from it and labelled `Regular` would be a file
359 /// that says one weight and draws another — and every naive `@font-face` and
360 /// every `fc-match` would take it at its word.
361 fn check_default_instance(id: &Identity<'_>, axis: &Variation) -> Result<(), Error> {
362 if id.style == axis.default_style {
363 return Ok(());
364 }
365 Err(Error::DefaultInstance {
366 declared: id.style.to_owned(),
367 actual: axis.default_style.clone(),
368 tag: axis.tag.clone(),
369 at: axis.default,
370 })
371 }
372
373 /// THE ADVANCE DECISION cutting the body slot from Atkinson Hyperlegible Next:
374 /// **a mark takes the reference glyph's advance, and answers the axis by
375 /// pointing at that glyph's own `HVAR` delta set.**
376 ///
377 /// The reference glyph is `+`, which is already what `quasi-type params` reads
378 /// the cell off and what the recipes measure their band against. On a monospace
379 /// base that is the cell and nothing changes. On a proportional one it is the
380 /// width the base itself gives the symbols these marks are drawn to sit among,
381 /// which is the honest answer to "how wide is a triangle in a face where glyphs
382 /// have their own widths": as wide as the base sets its own symbols.
383 ///
384 /// The two rejected options are worth recording. Refitting each mark's own
385 /// drawn width per master is the truthful-in-principle answer and needs
386 /// variation data this pipeline would have to invent — new `HVAR` regions for
387 /// a number that lands within a pixel of the reference glyph's anyway. Pinning
388 /// a fixed advance that holds still across the axis is what the mono does for
389 /// free, and on a proportional base it is wrong by a little at the heavy end,
390 /// where Next narrows `+` from 606 units to 595.
391 ///
392 /// What this rules out is the accident: `HVAR`'s advance map is indexed by glyph
393 /// id, and a glyph appended past its end picks up **whatever the last entry
394 /// says**, which is one arbitrary glyph's width response. So the entries are
395 /// written rather than left to fall off the end.
396 fn extend_hvar(font: &FontRef<'_>, reference: GlyphId, added: usize) -> Result<Vec<u8>, Error> {
397 let bytes = table_bytes(font, Tag::new(b"HVAR"))?.to_vec();
398 if added == 0 || bytes.len() < 20 {
399 return Ok(bytes);
400 }
401 let regions = font
402 .hvar()
403 .ok()
404 .and_then(|hvar| hvar.item_variation_store().ok())
405 .and_then(|store| store.variation_region_list().ok())
406 .map_or(0, |list| list.region_count());
407 // No regions is a table that describes no variation, which is what a
408 // monospace face ships. Nothing to point at, and nothing to get wrong.
409 if regions == 0 {
410 return Ok(bytes);
411 }
412
413 let read_u32 = |at: usize| u32::from_be_bytes(bytes[at..at + 4].try_into().unwrap()) as usize;
414 let map_offset = read_u32(8);
415 if map_offset == 0 {
416 return Err(Error::Font(
417 "the base's advances vary and its `HVAR` carries no advance-width mapping, so \
418 delta sets are indexed by glyph id directly and an appended glyph indexes past \
419 the end of the data. Nothing here can say what its advance should do."
420 .into(),
421 ));
422 }
423 let map = &bytes[map_offset..];
424 let (entry_format, count, header) = match map[0] {
425 0 => (
426 map[1],
427 u16::from_be_bytes([map[2], map[3]]) as usize,
428 4usize,
429 ),
430 1 => (
431 map[1],
432 u32::from_be_bytes([map[2], map[3], map[4], map[5]]) as usize,
433 6usize,
434 ),
435 other => {
436 return Err(Error::Font(format!(
437 "the base's `HVAR` advance mapping is format {other}, which this pipeline \
438 does not know how to extend"
439 )));
440 }
441 };
442 let entry_size = ((entry_format & 0x30) >> 4) as usize + 1;
443 let reference = reference.to_u32() as usize;
444 // A glyph inside the map has its own entry; one past the end already means
445 // "the last entry", which is the rule this whole function exists to stop
446 // relying on by accident.
447 let at = header + reference.min(count.saturating_sub(1)) * entry_size;
448 let entry = map
449 .get(at..at + entry_size)
450 .ok_or_else(|| Error::Font("the base's `HVAR` advance mapping is short".into()))?
451 .to_vec();
452
453 // Rebuilt rather than patched in place: the four offsets in the header are
454 // into the table, so growing one blob has to move the ones after it. Laying
455 // them out in offset order and recomputing is shorter than deciding which
456 // of the four happened to be last.
457 let mut blobs: Vec<(usize, usize)> = (0..4)
458 .map(|i| read_u32(4 + i * 4))
459 .filter(|&off| off != 0)
460 .collect::<std::collections::BTreeSet<_>>()
461 .into_iter()
462 .map(|start| (start, 0))
463 .collect();
464 for i in 0..blobs.len() {
465 let end = blobs.get(i + 1).map_or(bytes.len(), |(next, _)| *next);
466 blobs[i].1 = end;
467 }
468
469 let mut out = bytes[..20].to_vec();
470 let mut moved: Vec<(usize, usize)> = Vec::new();
471 for (start, end) in blobs {
472 let new_start = out.len();
473 if start == map_offset {
474 let mut extended = bytes[start..end].to_vec();
475 match extended[0] {
476 0 => extended[2..4].copy_from_slice(&((count + added) as u16).to_be_bytes()),
477 _ => extended[2..6].copy_from_slice(&((count + added) as u32).to_be_bytes()),
478 }
479 // The map's entries run to the end of its blob, so the copies go
480 // where the last entry already sits.
481 let tail = header + count * entry_size;
482 extended.truncate(tail);
483 for _ in 0..added {
484 extended.extend_from_slice(&entry);
485 }
486 out.extend_from_slice(&extended);
487 } else {
488 out.extend_from_slice(&bytes[start..end]);
489 }
490 moved.push((start, new_start));
491 }
492 for i in 0..4 {
493 let old = read_u32(4 + i * 4);
494 let new = moved
495 .iter()
496 .find(|(from, _)| *from == old)
497 .map_or(0, |(_, to)| *to);
498 out[4 + i * 4..8 + i * 4].copy_from_slice(&(new as u32).to_be_bytes());
499 }
500 Ok(out)
501 }
502
503 struct Bounds {
504 x_min: i16,
505 y_min: i16,
506 x_max: i16,
507 y_max: i16,
508 points: u16,
509 contours: u16,
510 }
511
512 impl Bounds {
513 fn of(glyph: &SimpleGlyph) -> Self {
514 let bbox = glyph.bbox;
515 Self {
516 x_min: bbox.x_min,
517 y_min: bbox.y_min,
518 x_max: bbox.x_max,
519 y_max: bbox.y_max,
520 points: glyph.contours.iter().map(|c| c.len() as u16).sum(),
521 contours: glyph.contours.len() as u16,
522 }
523 }
524 }
525
526 fn missing(tag: &'static str) -> impl Fn(read_fonts::ReadError) -> Error {
527 move |e| Error::Font(format!("base has no readable `{tag}`: {e}"))
528 }
529
530 fn table_bytes<'a>(font: &FontRef<'a>, tag: Tag) -> Result<&'a [u8], Error> {
531 font.table_data(tag)
532 .map(|d| d.as_bytes())
533 .ok_or_else(|| Error::Font(format!("base has no `{tag}` table")))
534 }
535
536 fn read_loca(font: &FontRef<'_>, glyph_count: u16) -> Result<Vec<u32>, Error> {
537 let loca = font.loca(None).map_err(missing("loca"))?;
538 (0..=glyph_count as usize)
539 .map(|i| {
540 loca.get_raw(i)
541 .ok_or_else(|| Error::Font(format!("base `loca` is short at {i}")))
542 })
543 .collect()
544 }
545
546 /// Rebuild `hmtx` with an entry per appended glyph.
547 ///
548 /// A base may store fewer `longHorMetrics` than glyphs, with the tail carrying
549 /// left side bearings only. Expanding to one metric per glyph costs two bytes
550 /// each and removes the special case; monospace faces are already full-length.
551 fn rebuild_hmtx(
552 font: &FontRef<'_>,
553 base_glyph_count: u16,
554 number_of_h_metrics: u16,
555 advance: u16,
556 appended: &[(GlyphId, &crate::manifest::GlyphSpec, Vec<u8>, Bounds)],
557 ) -> Result<Vec<u8>, Error> {
558 let hmtx = font.hmtx().map_err(missing("hmtx"))?;
559 let raw = table_bytes(font, Tag::new(b"hmtx"))?;
560 let mut out = Vec::with_capacity(raw.len() + appended.len() * 4);
561
562 let last_advance = hmtx
563 .advance(GlyphId::from(number_of_h_metrics.saturating_sub(1)))
564 .ok_or_else(|| Error::Font("base `hmtx` carries no metrics".into()))?;
565
566 for gid in 0..base_glyph_count {
567 let advance = hmtx.advance(GlyphId::from(gid)).unwrap_or(last_advance);
568 let lsb = side_bearing(raw, number_of_h_metrics, gid);
569 out.extend_from_slice(&advance.to_be_bytes());
570 out.extend_from_slice(&lsb.to_be_bytes());
571 }
572 for (_, _, _, bounds) in appended {
573 // The reference glyph's advance, which on a monospace base is the cell
574 // and on a proportional one is what the base sets its own symbols on.
575 // See `extend_hvar` for the decision and for how it answers the axis.
576 // The left side bearing has to match the outline's own x_min or hinting
577 // and layout disagree.
578 out.extend_from_slice(&advance.to_be_bytes());
579 out.extend_from_slice(&bounds.x_min.to_be_bytes());
580 }
581 Ok(out)
582 }
583
584 fn side_bearing(raw: &[u8], number_of_h_metrics: u16, gid: u16) -> i16 {
585 let offset = if gid < number_of_h_metrics {
586 gid as usize * 4 + 2
587 } else {
588 number_of_h_metrics as usize * 4 + (gid - number_of_h_metrics) as usize * 2
589 };
590 raw.get(offset..offset + 2)
591 .map_or(0, |b| i16::from_be_bytes([b[0], b[1]]))
592 }
593
594 fn name_table(id: &Identity<'_>, variation: Option<&Variation>, base: &FontRef<'_>) -> Name {
595 let full = format!("{} {}", id.family, id.style);
596 let postscript = format!(
597 "{}-{}",
598 id.family.replace(' ', ""),
599 id.style.replace(' ', "")
600 );
601 // OFL 1.1 requires the base's copyright and licence to travel with a
602 // modified build, and asks that the lineage be stated. It goes here rather
603 // than in the family name, which a Reserved Font Name would forbid.
604 //
605 // The glyph set version is stated alongside the base because a cut face is
606 // a composition of the two and the set moves on its own schedule. Naming
607 // both is what makes "does this installed face have the octants" a question
608 // about the file rather than about which build produced it.
609 let description = format!(
610 "Derived from {} {}, by drawing quasi house glyph set v{} into it. \
611 The letterforms are unmodified.",
612 id.base.family, id.base.version, id.set_version
613 );
614 // A variable face is named the way its own default instance forces. The
615 // family a naive consumer reads (name 1) can only carry the four RIBBI
616 // styles, so a default instance of ExtraLight goes into the family name and
617 // leaves `Regular` in the subfamily, with the honest pair in the typographic
618 // records. That is exactly what upstream does with the same face, and it is
619 // what keeps `Quasi Mono` one family in a menu instead of seven.
620 let ribbi = matches!(id.style, "Regular" | "Italic" | "Bold" | "Bold Italic");
621 let (family, subfamily) = if variation.is_some() && !ribbi {
622 (full.clone(), "Regular".to_owned())
623 } else {
624 (id.family.to_owned(), id.style.to_owned())
625 };
626
627 let mut records = vec![
628 record(NameId::COPYRIGHT_NOTICE, &id.base.copyright),
629 record(NameId::FAMILY_NAME, &family),
630 record(NameId::SUBFAMILY_NAME, &subfamily),
631 // Names both halves of the composition rather than the packed version,
632 // so the record that exists to identify one exact build identifies it
633 // without a decoder ring.
634 record(
635 NameId::UNIQUE_ID,
636 &format!(
637 "{full}; house glyph set v{}; {} {}",
638 id.set_version, id.base.family, id.base.version
639 ),
640 ),
641 record(NameId::FULL_NAME, &full),
642 record(NameId::VERSION_STRING, &format!("Version {}", id.version)),
643 record(NameId::POSTSCRIPT_NAME, &postscript),
644 record(NameId::DESCRIPTION, &description),
645 record(NameId::DESIGNER, &id.base.designer),
646 record(
647 NameId::LICENSE_DESCRIPTION,
648 "This Font Software is licensed under the SIL Open Font License, Version 1.1. \
649 This license is available with a FAQ at https://openfontlicense.org",
650 ),
651 record(NameId::LICENSE_URL, "https://openfontlicense.org"),
652 ];
653 if variation.is_some() {
654 // The typographic pair is where the face states what it really is, and
655 // name 25 is the stem every instance's PostScript name is built from.
656 // Without it, an application generating one for `wght` 700 would build
657 // it out of the base's prefix and hand back the upstream's name.
658 records.push(record(NameId::TYPOGRAPHIC_FAMILY_NAME, id.family));
659 records.push(record(NameId::TYPOGRAPHIC_SUBFAMILY_NAME, id.style));
660 records.push(record(
661 NameId::VARIATIONS_POSTSCRIPT_NAME_PREFIX,
662 &id.family.replace(' ', ""),
663 ));
664 }
665
666 let mut all = Vec::with_capacity(records.len() * 2);
667 for (name_id, value) in records {
668 // Windows/Unicode BMP, English (US), which is the pair every consumer
669 // reads, plus the Macintosh Roman record older tools still look for.
670 all.push(NameRecord::new(3, 1, 0x0409, name_id, value.clone().into()));
671 all.push(NameRecord::new(1, 0, 0, name_id, value.into()));
672 }
673 all.extend(inherited_names(base));
674 all.sort_by_key(|r| (r.platform_id, r.encoding_id, r.language_id, r.name_id));
675 all.dedup_by_key(|r| (r.platform_id, r.encoding_id, r.language_id, r.name_id));
676 Name::new(all)
677 }
678
679 /// The name records above 255, carried across from the base unchanged.
680 ///
681 /// These are not descriptive text: they are the strings other tables point at
682 /// by number. `fvar` names its axis and every named instance that way, `STAT`
683 /// names its axis values, and `GSUB` labels its stylistic sets. Rebuilding the
684 /// table from the ten standard records alone left all of those dangling — the
685 /// static cut has been shipping Plex Mono's stylistic sets with no names since
686 /// the first build, and a variable cut would lose the names of its own seven
687 /// weights.
688 ///
689 /// They are not rewritten, only kept. "Weight", "ExtraLight" and "alternate
690 /// lowercase l" describe the base's design rather than our packaging of it.
691 fn inherited_names(base: &FontRef<'_>) -> Vec<NameRecord> {
692 let Ok(name) = base.name() else {
693 return Vec::new();
694 };
695 let data = name.string_data();
696 name.name_record()
697 .iter()
698 .filter(|record| record.name_id().to_u16() >= 256)
699 .filter_map(|record| {
700 let value: String = record.string(data).ok()?.chars().collect();
701 Some(NameRecord::new(
702 record.platform_id(),
703 record.encoding_id(),
704 record.language_id(),
705 record.name_id(),
706 value.into(),
707 ))
708 })
709 .collect()
710 }
711
712 fn record(name_id: NameId, value: &str) -> (NameId, String) {
713 (name_id, value.to_owned())
714 }
715
716 fn write_u16(bytes: &mut [u8], offset: usize, value: u16) {
717 if let Some(slot) = bytes.get_mut(offset..offset + 2) {
718 slot.copy_from_slice(&value.to_be_bytes());
719 }
720 }
721
722 fn write_i16(bytes: &mut [u8], offset: usize, value: i16) {
723 if let Some(slot) = bytes.get_mut(offset..offset + 2) {
724 slot.copy_from_slice(&value.to_be_bytes());
725 }
726 }
727
728 fn bump_u16(bytes: &mut [u8], offset: usize, value: u16) {
729 let current = bytes
730 .get(offset..offset + 2)
731 .map_or(0, |b| u16::from_be_bytes([b[0], b[1]]));
732 write_u16(bytes, offset, current.max(value));
733 }
734
735 /// The codepoints a built face maps, for the coverage assertion.
736 pub fn coverage(bytes: &[u8]) -> Result<BTreeMap<u32, GlyphId>, Error> {
737 base::mappings(bytes)
738 }
739
740 /// `head`'s tag, re-exported so the CLI can name it without importing
741 /// read-fonts.
742 pub fn head_tag() -> Tag {
743 read_fonts::tables::head::Head::TAG
744 }
745