//! Assembling the output face: base tables in, a `Quasi ` face out. //! //! The base's own glyphs are copied as bytes and never recompiled. Plex Mono is //! hinted (`cvt `, `fpgm`, `prep`), and round-tripping 1,207 glyphs through a //! builder to add seven risks changing a face nobody asked us to change. So //! `glyf` is spliced: base bytes, then ours, with `loca` rebuilt over the pair. use std::collections::BTreeMap; use read_fonts::types::{GlyphId, Tag}; use read_fonts::{FontRef, TableProvider, TopLevelTable}; use write_fonts::FontBuilder; use write_fonts::tables::cmap::Cmap; use write_fonts::tables::glyf::{Glyph, SimpleGlyph}; use write_fonts::tables::name::{Name, NameRecord}; use write_fonts::tables::post::Post; use write_fonts::types::{Fixed, NameId, Version16Dot16}; use crate::Error; use crate::base::{self, BaseParams, Variation}; use crate::draw; use crate::manifest::{GlyphSpec, Purpose}; use crate::pins::Base; use crate::vary; /// What the pipeline stamps into a face's `name` table. pub struct Identity<'a> { pub family: &'a str, pub style: &'a str, pub version: &'a str, /// `[set] version` from the glyph manifest, stated in the description on /// its own rather than only folded into `version`. /// /// A cut face is a composition of a base and a glyph set, and the set /// versions independently of the base and of this pipeline. Two faces over /// the same base carrying different sets are different fonts, so "which set /// is this face carrying" has to be answerable from the file. It was not: /// `version` is `{set}.{base}`, and nothing in the face says where the /// boundary falls, so `1.2.5.0` is unreadable without knowing that Plex Mono /// happened to be at 2.5.0. pub set_version: u32, pub base: &'a Base, } /// A face, and what went into it. pub struct Built { pub bytes: Vec, pub added: Vec<(u32, String)>, pub params: BaseParams, /// The axis the face carries, when its base had one. A cut keeps the axis /// rather than instancing it away, so this is the base's `fvar` unchanged. pub variation: Option, /// Generated cell primitives the base already drew, so it kept its own. /// Reported rather than silent: "167 marks" and "167 marks, 160 of which /// the base already had" are very different builds. pub kept_by_base: Vec, } /// Tables the pipeline rebuilds. Everything else is copied verbatim. const REBUILT: [Tag; 8] = [ Tag::new(b"glyf"), Tag::new(b"loca"), Tag::new(b"cmap"), Tag::new(b"hmtx"), Tag::new(b"hhea"), Tag::new(b"maxp"), Tag::new(b"name"), Tag::new(b"post"), ]; /// Tables that do not survive modification. /// /// `DSIG` signs the bytes we just changed, so keeping it would ship a signature /// that fails to verify. Dropping it is what every font tool does here. const DROPPED: [Tag; 1] = [Tag::new(b"DSIG")]; pub fn build(base_bytes: &[u8], glyphs: &[&GlyphSpec], id: &Identity<'_>) -> Result { let font = FontRef::new(base_bytes).map_err(|e| Error::Font(format!("base is unreadable: {e}")))?; let variation = base::variation(base_bytes)?; if let Some(axis) = &variation { check_default_instance(id, axis)?; } // A variable base is measured at its axis default rather than out of `glyf`, // which is the same location by definition and the same numbers for a static // face. The other masters are measured the same way, one location each. let params = match &variation { Some(axis) => base::measure_at(base_bytes, axis, axis.default_master())?, None => base::measure(base_bytes)?, }; let masters: Vec<(f32, BaseParams)> = variation .iter() .flat_map(|axis| { axis.delta_masters() .into_iter() .map(|at| Ok((at.peak, base::measure_at(base_bytes, axis, at)?))) .collect::>() }) .collect::>()?; let mut mappings = base::mappings(base_bytes)?; // The reference glyph, `+`: what the recipes measure their band against and // what `quasi-type params` reads the cell off. See `extend_hvar`. let reference = *mappings .get(&0x2B) .ok_or_else(|| Error::Font("base has no `+`, so no mark can be measured".into()))?; let head = font.head().map_err(missing("head"))?; let maxp = font.maxp().map_err(missing("maxp"))?; let hhea = font.hhea().map_err(missing("hhea"))?; let base_glyph_count = maxp.num_glyphs(); // --- the new glyphs, compiled ------------------------------------------ let mut appended: Vec<(GlyphId, &GlyphSpec, Vec, Bounds)> = Vec::new(); let mut variations: Vec> = Vec::new(); let mut skipped: Vec = Vec::new(); for spec in glyphs { if let Some(existing) = mappings.get(&spec.codepoint) { // Which of the two this is decides the answer. See `Purpose`. if spec.purpose == Purpose::Coverage { skipped.push(spec.codepoint); continue; } return Err(Error::AlreadyDrawn { codepoint: spec.codepoint, base: id.base.family.clone(), gid: existing.to_u32(), }); } let simple = compile_drawing(spec, ¶ms)?; let bounds = Bounds::of(&simple); if variation.is_some() { // The same recipe at another location, which is what makes a master // a measurement rather than a second drawing. let varied = vary::Varied { default: simple.clone(), masters: masters .iter() .map(|(peak, at)| Ok((*peak, compile_drawing(spec, at)?))) .collect::>()?, }; variations.push(match varied.deltas(&spec.name)? { Some(deltas) => vary::compile(deltas)?, // A zero-length entry, which is how `gvar` says "this glyph does // not vary". Every appended glyph needs one so the offsets stay // in step with the glyph ids. None => Vec::new(), }); } let bytes = write_fonts::dump_table(&Glyph::Simple(simple)) .map_err(|e| Error::Draw(format!("{}: {e}", spec.name)))?; // Counted off what has actually been appended rather than off the // loop index, which stopped being the same number the moment a glyph // could be skipped. let gid = GlyphId::from(base_glyph_count + appended.len() as u16); mappings.insert(spec.codepoint, gid); appended.push((gid, spec, bytes, bounds)); } let glyph_count = base_glyph_count + appended.len() as u16; // --- glyf and loca ------------------------------------------------------ let base_glyf = table_bytes(&font, Tag::new(b"glyf"))?; let base_loca = read_loca(&font, base_glyph_count)?; // `loca`'s last entry is the end of the glyph data, which can sit short of // the padded table length. Appending from the table's end instead would // leave a gap the offsets do not describe. let mut glyf = base_glyf[..*base_loca.last().unwrap() as usize].to_vec(); let mut loca = base_loca; for (_, _, bytes, _) in &appended { glyf.extend_from_slice(bytes); // Long-format offsets need no alignment, but keeping glyphs on a // four-byte boundary matches what every other tool writes. while glyf.len() % 4 != 0 { glyf.push(0); } loca.push(glyf.len() as u32); } let loca_bytes: Vec = loca.iter().flat_map(|o| o.to_be_bytes()).collect(); // --- hmtx and hhea ------------------------------------------------------ let hmtx_bytes = rebuild_hmtx( &font, base_glyph_count, hhea.number_of_h_metrics(), params.advance, &appended, )?; let mut hhea_bytes = table_bytes(&font, Tag::new(b"hhea"))?.to_vec(); write_u16(&mut hhea_bytes, 34, glyph_count); // --- maxp --------------------------------------------------------------- let mut maxp_bytes = table_bytes(&font, Tag::new(b"maxp"))?.to_vec(); write_u16(&mut maxp_bytes, 4, glyph_count); if maxp_bytes.len() >= 8 { let points = appended .iter() .map(|(_, _, _, b)| b.points) .max() .unwrap_or(0); let contours = appended .iter() .map(|(_, _, _, b)| b.contours) .max() .unwrap_or(0); bump_u16(&mut maxp_bytes, 6, points); bump_u16(&mut maxp_bytes, 8, contours); } // --- head --------------------------------------------------------------- let mut head_bytes = table_bytes(&font, Tag::new(b"head"))?.to_vec(); // Long offsets unconditionally: a spliced `glyf` can cross the 128KB the // short format reaches, and converting up front means one code path. write_i16(&mut head_bytes, 50, 1); let mut bbox = (head.x_min(), head.y_min(), head.x_max(), head.y_max()); for (_, _, _, b) in &appended { bbox.0 = bbox.0.min(b.x_min); bbox.1 = bbox.1.min(b.y_min); bbox.2 = bbox.2.max(b.x_max); bbox.3 = bbox.3.max(b.y_max); } write_i16(&mut head_bytes, 36, bbox.0); write_i16(&mut head_bytes, 38, bbox.1); write_i16(&mut head_bytes, 40, bbox.2); write_i16(&mut head_bytes, 42, bbox.3); // `modified` is left exactly as the base wrote it. A build stamped with the // wall clock is a build that differs from itself, and the done condition // here is a byte-identical face from a clean checkout. // --- OS/2 --------------------------------------------------------------- let mut os2_bytes = table_bytes(&font, Tag::new(b"OS/2"))?.to_vec(); let first = mappings.keys().copied().min().unwrap_or(0); let last = mappings.keys().copied().max().unwrap_or(0); write_u16(&mut os2_bytes, 64, u16::try_from(first).unwrap_or(0xFFFF)); write_u16(&mut os2_bytes, 66, u16::try_from(last).unwrap_or(0xFFFF)); // --- cmap, name, post --------------------------------------------------- let cmap = Cmap::from_mappings( mappings .iter() .filter_map(|(cp, gid)| char::from_u32(*cp).map(|c| (c, *gid))), ) .map_err(|e| Error::Font(format!("cmap: {e}")))?; let name = name_table(id, variation.as_ref(), &font); // `post` 3.0: the base ships 2.0 with a name per glyph, and a 2.0 table has // to carry exactly `numGlyphs` entries. Extending it would mean inventing // names for the seven and rewriting the base's, and nothing on any target // reads glyph names. let post = Post { version: Version16Dot16::VERSION_3_0, italic_angle: Fixed::from_f64(0.0), underline_position: font .post() .map(|p| p.underline_position()) .unwrap_or_default(), underline_thickness: font .post() .map(|p| p.underline_thickness()) .unwrap_or_default(), is_fixed_pitch: font.post().map_or(0, |p| p.is_fixed_pitch()), ..Default::default() }; // --- assemble ----------------------------------------------------------- let mut builder = FontBuilder::new(); builder.add_raw(Tag::new(b"glyf"), glyf); builder.add_raw(Tag::new(b"loca"), loca_bytes); builder.add_raw(Tag::new(b"hmtx"), hmtx_bytes); builder.add_raw(Tag::new(b"hhea"), hhea_bytes); builder.add_raw(Tag::new(b"maxp"), maxp_bytes); builder.add_raw(Tag::new(b"head"), head_bytes); builder.add_raw(Tag::new(b"OS/2"), os2_bytes); builder .add_table(&cmap) .map_err(|e| Error::Font(format!("cmap: {e}")))?; builder .add_table(&name) .map_err(|e| Error::Font(format!("name: {e}")))?; builder .add_table(&post) .map_err(|e| Error::Font(format!("post: {e}")))?; // --- gvar --------------------------------------------------------------- let gvar_tag = Tag::new(b"gvar"); if variation.is_some() { let base_gvar = table_bytes(&font, gvar_tag).map_err(|_| { Error::Font( "the base has an `fvar` axis and no `gvar`, so its own glyphs do not \ vary. Nothing here can tell what a mark should do on an axis the base \ does not use." .into(), ) })?; let axis_count = u16::try_from(variation.iter().len()).unwrap_or(1); builder.add_raw(gvar_tag, vary::splice(base_gvar, &variations, axis_count)?); } // --- HVAR --------------------------------------------------------------- let hvar_tag = Tag::new(b"HVAR"); let has_hvar = font .table_directory() .table_records() .iter() .any(|r| r.tag() == hvar_tag); if has_hvar { builder.add_raw(hvar_tag, extend_hvar(&font, reference, appended.len())?); } // Everything the pipeline does not touch is carried across as bytes, minus // the tables that modification invalidates. for record in font.table_directory().table_records() { let tag = record.tag(); if REBUILT.contains(&tag) || DROPPED.contains(&tag) || tag == Tag::new(b"head") || tag == Tag::new(b"OS/2") || tag == hvar_tag || (variation.is_some() && tag == gvar_tag) { continue; } let data = table_bytes(&font, tag)?; builder.add_raw(tag, data.to_vec()); } Ok(Built { bytes: builder.build(), added: appended .iter() .map(|(_, spec, _, _)| (spec.codepoint, spec.name.clone())) .collect(), params, variation, kept_by_base: skipped, }) } /// One mark, drawn against one set of measurements. fn compile_drawing(spec: &GlyphSpec, params: &BaseParams) -> Result { let path = draw::draw(&spec.shape, params).to_bezpath(); SimpleGlyph::from_bezpath(&path).map_err(|e| Error::Draw(format!("{}: {e:?}", spec.name))) } /// The declared style has to be the style of the base's default instance. /// /// This is the ExtraLight trap, made mechanical. Atkinson Mono's `fvar` default /// is `wght` 200 and its own `name` table reads `Atkinson Hyperlegible Mono /// ExtraLight`, so a face cut from it and labelled `Regular` would be a file /// that says one weight and draws another — and every naive `@font-face` and /// every `fc-match` would take it at its word. fn check_default_instance(id: &Identity<'_>, axis: &Variation) -> Result<(), Error> { if id.style == axis.default_style { return Ok(()); } Err(Error::DefaultInstance { declared: id.style.to_owned(), actual: axis.default_style.clone(), tag: axis.tag.clone(), at: axis.default, }) } /// THE ADVANCE DECISION cutting the body slot from Atkinson Hyperlegible Next: /// **a mark takes the reference glyph's advance, and answers the axis by /// pointing at that glyph's own `HVAR` delta set.** /// /// The reference glyph is `+`, which is already what `quasi-type params` reads /// the cell off and what the recipes measure their band against. On a monospace /// base that is the cell and nothing changes. On a proportional one it is the /// width the base itself gives the symbols these marks are drawn to sit among, /// which is the honest answer to "how wide is a triangle in a face where glyphs /// have their own widths": as wide as the base sets its own symbols. /// /// The two rejected options are worth recording. Refitting each mark's own /// drawn width per master is the truthful-in-principle answer and needs /// variation data this pipeline would have to invent — new `HVAR` regions for /// a number that lands within a pixel of the reference glyph's anyway. Pinning /// a fixed advance that holds still across the axis is what the mono does for /// free, and on a proportional base it is wrong by a little at the heavy end, /// where Next narrows `+` from 606 units to 595. /// /// What this rules out is the accident: `HVAR`'s advance map is indexed by glyph /// id, and a glyph appended past its end picks up **whatever the last entry /// says**, which is one arbitrary glyph's width response. So the entries are /// written rather than left to fall off the end. fn extend_hvar(font: &FontRef<'_>, reference: GlyphId, added: usize) -> Result, Error> { let bytes = table_bytes(font, Tag::new(b"HVAR"))?.to_vec(); if added == 0 || bytes.len() < 20 { return Ok(bytes); } let regions = font .hvar() .ok() .and_then(|hvar| hvar.item_variation_store().ok()) .and_then(|store| store.variation_region_list().ok()) .map_or(0, |list| list.region_count()); // No regions is a table that describes no variation, which is what a // monospace face ships. Nothing to point at, and nothing to get wrong. if regions == 0 { return Ok(bytes); } let read_u32 = |at: usize| u32::from_be_bytes(bytes[at..at + 4].try_into().unwrap()) as usize; let map_offset = read_u32(8); if map_offset == 0 { return Err(Error::Font( "the base's advances vary and its `HVAR` carries no advance-width mapping, so \ delta sets are indexed by glyph id directly and an appended glyph indexes past \ the end of the data. Nothing here can say what its advance should do." .into(), )); } let map = &bytes[map_offset..]; let (entry_format, count, header) = match map[0] { 0 => ( map[1], u16::from_be_bytes([map[2], map[3]]) as usize, 4usize, ), 1 => ( map[1], u32::from_be_bytes([map[2], map[3], map[4], map[5]]) as usize, 6usize, ), other => { return Err(Error::Font(format!( "the base's `HVAR` advance mapping is format {other}, which this pipeline \ does not know how to extend" ))); } }; let entry_size = ((entry_format & 0x30) >> 4) as usize + 1; let reference = reference.to_u32() as usize; // A glyph inside the map has its own entry; one past the end already means // "the last entry", which is the rule this whole function exists to stop // relying on by accident. let at = header + reference.min(count.saturating_sub(1)) * entry_size; let entry = map .get(at..at + entry_size) .ok_or_else(|| Error::Font("the base's `HVAR` advance mapping is short".into()))? .to_vec(); // Rebuilt rather than patched in place: the four offsets in the header are // into the table, so growing one blob has to move the ones after it. Laying // them out in offset order and recomputing is shorter than deciding which // of the four happened to be last. let mut blobs: Vec<(usize, usize)> = (0..4) .map(|i| read_u32(4 + i * 4)) .filter(|&off| off != 0) .collect::>() .into_iter() .map(|start| (start, 0)) .collect(); for i in 0..blobs.len() { let end = blobs.get(i + 1).map_or(bytes.len(), |(next, _)| *next); blobs[i].1 = end; } let mut out = bytes[..20].to_vec(); let mut moved: Vec<(usize, usize)> = Vec::new(); for (start, end) in blobs { let new_start = out.len(); if start == map_offset { let mut extended = bytes[start..end].to_vec(); match extended[0] { 0 => extended[2..4].copy_from_slice(&((count + added) as u16).to_be_bytes()), _ => extended[2..6].copy_from_slice(&((count + added) as u32).to_be_bytes()), } // The map's entries run to the end of its blob, so the copies go // where the last entry already sits. let tail = header + count * entry_size; extended.truncate(tail); for _ in 0..added { extended.extend_from_slice(&entry); } out.extend_from_slice(&extended); } else { out.extend_from_slice(&bytes[start..end]); } moved.push((start, new_start)); } for i in 0..4 { let old = read_u32(4 + i * 4); let new = moved .iter() .find(|(from, _)| *from == old) .map_or(0, |(_, to)| *to); out[4 + i * 4..8 + i * 4].copy_from_slice(&(new as u32).to_be_bytes()); } Ok(out) } struct Bounds { x_min: i16, y_min: i16, x_max: i16, y_max: i16, points: u16, contours: u16, } impl Bounds { fn of(glyph: &SimpleGlyph) -> Self { let bbox = glyph.bbox; Self { x_min: bbox.x_min, y_min: bbox.y_min, x_max: bbox.x_max, y_max: bbox.y_max, points: glyph.contours.iter().map(|c| c.len() as u16).sum(), contours: glyph.contours.len() as u16, } } } fn missing(tag: &'static str) -> impl Fn(read_fonts::ReadError) -> Error { move |e| Error::Font(format!("base has no readable `{tag}`: {e}")) } fn table_bytes<'a>(font: &FontRef<'a>, tag: Tag) -> Result<&'a [u8], Error> { font.table_data(tag) .map(|d| d.as_bytes()) .ok_or_else(|| Error::Font(format!("base has no `{tag}` table"))) } fn read_loca(font: &FontRef<'_>, glyph_count: u16) -> Result, Error> { let loca = font.loca(None).map_err(missing("loca"))?; (0..=glyph_count as usize) .map(|i| { loca.get_raw(i) .ok_or_else(|| Error::Font(format!("base `loca` is short at {i}"))) }) .collect() } /// Rebuild `hmtx` with an entry per appended glyph. /// /// A base may store fewer `longHorMetrics` than glyphs, with the tail carrying /// left side bearings only. Expanding to one metric per glyph costs two bytes /// each and removes the special case; monospace faces are already full-length. fn rebuild_hmtx( font: &FontRef<'_>, base_glyph_count: u16, number_of_h_metrics: u16, advance: u16, appended: &[(GlyphId, &crate::manifest::GlyphSpec, Vec, Bounds)], ) -> Result, Error> { let hmtx = font.hmtx().map_err(missing("hmtx"))?; let raw = table_bytes(font, Tag::new(b"hmtx"))?; let mut out = Vec::with_capacity(raw.len() + appended.len() * 4); let last_advance = hmtx .advance(GlyphId::from(number_of_h_metrics.saturating_sub(1))) .ok_or_else(|| Error::Font("base `hmtx` carries no metrics".into()))?; for gid in 0..base_glyph_count { let advance = hmtx.advance(GlyphId::from(gid)).unwrap_or(last_advance); let lsb = side_bearing(raw, number_of_h_metrics, gid); out.extend_from_slice(&advance.to_be_bytes()); out.extend_from_slice(&lsb.to_be_bytes()); } for (_, _, _, bounds) in appended { // The reference glyph's advance, which on a monospace base is the cell // and on a proportional one is what the base sets its own symbols on. // See `extend_hvar` for the decision and for how it answers the axis. // The left side bearing has to match the outline's own x_min or hinting // and layout disagree. out.extend_from_slice(&advance.to_be_bytes()); out.extend_from_slice(&bounds.x_min.to_be_bytes()); } Ok(out) } fn side_bearing(raw: &[u8], number_of_h_metrics: u16, gid: u16) -> i16 { let offset = if gid < number_of_h_metrics { gid as usize * 4 + 2 } else { number_of_h_metrics as usize * 4 + (gid - number_of_h_metrics) as usize * 2 }; raw.get(offset..offset + 2) .map_or(0, |b| i16::from_be_bytes([b[0], b[1]])) } fn name_table(id: &Identity<'_>, variation: Option<&Variation>, base: &FontRef<'_>) -> Name { let full = format!("{} {}", id.family, id.style); let postscript = format!( "{}-{}", id.family.replace(' ', ""), id.style.replace(' ', "") ); // OFL 1.1 requires the base's copyright and licence to travel with a // modified build, and asks that the lineage be stated. It goes here rather // than in the family name, which a Reserved Font Name would forbid. // // The glyph set version is stated alongside the base because a cut face is // a composition of the two and the set moves on its own schedule. Naming // both is what makes "does this installed face have the octants" a question // about the file rather than about which build produced it. let description = format!( "Derived from {} {}, by drawing quasi house glyph set v{} into it. \ The letterforms are unmodified.", id.base.family, id.base.version, id.set_version ); // A variable face is named the way its own default instance forces. The // family a naive consumer reads (name 1) can only carry the four RIBBI // styles, so a default instance of ExtraLight goes into the family name and // leaves `Regular` in the subfamily, with the honest pair in the typographic // records. That is exactly what upstream does with the same face, and it is // what keeps `Quasi Mono` one family in a menu instead of seven. let ribbi = matches!(id.style, "Regular" | "Italic" | "Bold" | "Bold Italic"); let (family, subfamily) = if variation.is_some() && !ribbi { (full.clone(), "Regular".to_owned()) } else { (id.family.to_owned(), id.style.to_owned()) }; let mut records = vec![ record(NameId::COPYRIGHT_NOTICE, &id.base.copyright), record(NameId::FAMILY_NAME, &family), record(NameId::SUBFAMILY_NAME, &subfamily), // Names both halves of the composition rather than the packed version, // so the record that exists to identify one exact build identifies it // without a decoder ring. record( NameId::UNIQUE_ID, &format!( "{full}; house glyph set v{}; {} {}", id.set_version, id.base.family, id.base.version ), ), record(NameId::FULL_NAME, &full), record(NameId::VERSION_STRING, &format!("Version {}", id.version)), record(NameId::POSTSCRIPT_NAME, &postscript), record(NameId::DESCRIPTION, &description), record(NameId::DESIGNER, &id.base.designer), record( NameId::LICENSE_DESCRIPTION, "This Font Software is licensed under the SIL Open Font License, Version 1.1. \ This license is available with a FAQ at https://openfontlicense.org", ), record(NameId::LICENSE_URL, "https://openfontlicense.org"), ]; if variation.is_some() { // The typographic pair is where the face states what it really is, and // name 25 is the stem every instance's PostScript name is built from. // Without it, an application generating one for `wght` 700 would build // it out of the base's prefix and hand back the upstream's name. records.push(record(NameId::TYPOGRAPHIC_FAMILY_NAME, id.family)); records.push(record(NameId::TYPOGRAPHIC_SUBFAMILY_NAME, id.style)); records.push(record( NameId::VARIATIONS_POSTSCRIPT_NAME_PREFIX, &id.family.replace(' ', ""), )); } let mut all = Vec::with_capacity(records.len() * 2); for (name_id, value) in records { // Windows/Unicode BMP, English (US), which is the pair every consumer // reads, plus the Macintosh Roman record older tools still look for. all.push(NameRecord::new(3, 1, 0x0409, name_id, value.clone().into())); all.push(NameRecord::new(1, 0, 0, name_id, value.into())); } all.extend(inherited_names(base)); all.sort_by_key(|r| (r.platform_id, r.encoding_id, r.language_id, r.name_id)); all.dedup_by_key(|r| (r.platform_id, r.encoding_id, r.language_id, r.name_id)); Name::new(all) } /// The name records above 255, carried across from the base unchanged. /// /// These are not descriptive text: they are the strings other tables point at /// by number. `fvar` names its axis and every named instance that way, `STAT` /// names its axis values, and `GSUB` labels its stylistic sets. Rebuilding the /// table from the ten standard records alone left all of those dangling — the /// static cut has been shipping Plex Mono's stylistic sets with no names since /// the first build, and a variable cut would lose the names of its own seven /// weights. /// /// They are not rewritten, only kept. "Weight", "ExtraLight" and "alternate /// lowercase l" describe the base's design rather than our packaging of it. fn inherited_names(base: &FontRef<'_>) -> Vec { let Ok(name) = base.name() else { return Vec::new(); }; let data = name.string_data(); name.name_record() .iter() .filter(|record| record.name_id().to_u16() >= 256) .filter_map(|record| { let value: String = record.string(data).ok()?.chars().collect(); Some(NameRecord::new( record.platform_id(), record.encoding_id(), record.language_id(), record.name_id(), value.into(), )) }) .collect() } fn record(name_id: NameId, value: &str) -> (NameId, String) { (name_id, value.to_owned()) } fn write_u16(bytes: &mut [u8], offset: usize, value: u16) { if let Some(slot) = bytes.get_mut(offset..offset + 2) { slot.copy_from_slice(&value.to_be_bytes()); } } fn write_i16(bytes: &mut [u8], offset: usize, value: i16) { if let Some(slot) = bytes.get_mut(offset..offset + 2) { slot.copy_from_slice(&value.to_be_bytes()); } } fn bump_u16(bytes: &mut [u8], offset: usize, value: u16) { let current = bytes .get(offset..offset + 2) .map_or(0, |b| u16::from_be_bytes([b[0], b[1]])); write_u16(bytes, offset, current.max(value)); } /// The codepoints a built face maps, for the coverage assertion. pub fn coverage(bytes: &[u8]) -> Result, Error> { base::mappings(bytes) } /// `head`'s tag, re-exported so the CLI can name it without importing /// read-fonts. pub fn head_tag() -> Tag { read_fonts::tables::head::Head::TAG }