//! Fonts built in code for tests, so no font file is committed: a small variable font (#296). //! Compiled for this crate's tests and, with the `test-fonts` feature, for other crates' tests. use skrifa::instance::{LocationRef, Size}; use skrifa::raw::TableProvider; use skrifa::raw::tables::glyf::Glyph; use skrifa::{GlyphId, MetadataProvider}; use write_fonts::FontBuilder; use write_fonts::tables::fvar::{AxisInstanceArrays, Fvar, InstanceRecord, VariationAxisRecord}; use write_fonts::tables::gvar::{GlyphDelta, GlyphDeltas, GlyphVariations, Gvar, Tent}; use write_fonts::types::{F2Dot14, Fixed, NameId, Tag}; /// The family of [`variable_font`]. pub const VARIABLE_FAMILY: &str = "Varitest Sans"; /// The characters [`variable_font`] widens towards its heaviest weight (simple glyphs in Source Sans /// 3; the composite ones, like `H`, would need their components varied). pub const VARIABLE_CHARS: &str = "lIn"; /// How much wider [`VARIABLE_CHARS`] are at the heaviest weight (outlines and advances). pub const VARIABLE_WIDEN: f32 = 0.25; /// The bundled Source Sans 3 Regular as a variable font of family [`VARIABLE_FAMILY`]: one weight /// axis (400–700, default 400, the face itself being Regular) whose maximum makes the glyphs of /// [`VARIABLE_CHARS`] [`VARIABLE_WIDEN`] wider (outlines and advances, through `gvar`), with the /// named instances Normal (the default, the face Regular), SemiBold (550) and Bold (700), plus an instance without /// a name and one named like another (`BOLD`), which style lists leave out. `None` when it can't /// be built. pub fn variable_font() -> Option> { const BASE: &[u8] = include_bytes!("../../../assets/fonts/SourceSans3-Regular.ttf"); let font = skrifa::FontRef::new(BASE).ok()?; let (glyf, loca) = (font.glyf().ok()?, font.loca(None).ok()?); let metrics = font.glyph_metrics(Size::unscaled(), LocationRef::default()); let charmap = font.charmap(); let widened: Vec = VARIABLE_CHARS.chars().filter_map(|c| charmap.map(c)).collect(); let mut variations = Vec::new(); for g in 0..u32::from(font.maxp().ok()?.num_glyphs()) { let gid = write_fonts::types::GlyphId::new(g); let id = GlyphId::new(g); let deltas = if widened.contains(&id) { let Ok(Some(Glyph::Simple(s))) = loca.get_glyf(id, &glyf) else { return None }; let wider = |x: f32| (x * VARIABLE_WIDEN).round() as i16; let mut d: Vec = s.points().map(|p| GlyphDelta::required(wider(f32::from(p.x)), 0)).collect(); // The phantom points: left side bearing, advance, top, bottom. let advance = wider(metrics.advance_width(id)?); d.extend([(0, 0), (advance, 0), (0, 0), (0, 0)].map(|(x, y)| GlyphDelta::required(x, y))); vec![GlyphDeltas::new(vec![Tent::new(F2Dot14::from_f32(1.0), None)], d)] } else { vec![] }; variations.push(GlyphVariations::new(gid, deltas)); } let gvar = Gvar::new(variations, 1).ok()?; let axis = VariationAxisRecord { axis_tag: Tag::new(b"wght"), min_value: Fixed::from_i32(400), default_value: Fixed::from_i32(400), max_value: Fixed::from_i32(700), flags: 0, axis_name_id: NameId::new(256), }; let instance = |name: u16, wght: i32| InstanceRecord { subfamily_name_id: NameId::new(name), flags: 0, coordinates: vec![Fixed::from_i32(wght)], post_script_name_id: None, }; let instances = vec![instance(257, 400), instance(258, 550), instance(259, 700), instance(260, 600), instance(261, 650)]; let fvar = Fvar::new(AxisInstanceArrays::new(vec![axis], instances)); let name = name_table(&[ (1, VARIABLE_FAMILY), (2, "Regular"), (4, "Varitest Sans Regular"), (6, "VaritestSans-Regular"), (256, "Weight"), (257, "Normal"), (258, "SemiBold"), (259, "Bold"), (261, "BOLD"), ])?; let mut builder = FontBuilder::new(); builder.add_raw(Tag::new(b"name"), name); builder.add_table(&fvar).ok()?.add_table(&gvar).ok()?; // The rest of Source Sans 3's tables as they are. for r in font.table_directory.table_records() { let tag = Tag::new(&r.tag().to_be_bytes()); if !builder.contains(tag) { builder.add_raw(tag, font.table_data(r.tag())?.as_bytes()); } } Some(builder.build()) } /// A `name` table of Windows English (US) records (name id, string). pub fn name_table(records: &[(u16, &str)]) -> Option> { let mut records = records.to_vec(); records.sort_by_key(|r| r.0); let n = u16::try_from(records.len()).ok()?; let (mut head, mut strings) = (Vec::new(), Vec::new()); for v in [0, n, 6 + 12 * n] { head.extend_from_slice(&v.to_be_bytes()); } for (id, s) in records { let bytes: Vec = s.encode_utf16().flat_map(u16::to_be_bytes).collect(); for v in [3, 1, 0x0409, id, u16::try_from(bytes.len()).ok()?, u16::try_from(strings.len()).ok()?] { head.extend_from_slice(&v.to_be_bytes()); } strings.extend(bytes); } head.extend(strings); Some(head) } /// The family of [`vertical_font`]. pub const VERTICAL_FAMILY: &str = "Vertitest Sans"; /// The character whose vertical metrics [`vertical_font`] sets apart: 1.4 em down the column, its /// vertical origin 1.1 em above the baseline. pub const VERTICAL_TALL: char = '§'; /// The bundled Source Sans 3 Regular as family [`VERTICAL_FAMILY`] with vertical metrics (`vhea`, /// `vmtx`, and `VORG` when `vorg`): every glyph one em down the column with its vertical origin at /// the usual 0.88 em, except [`VERTICAL_TALL`] (1.4 em, origin 1.1 em). Without `VORG` the origin /// comes from each glyph's top and top side bearing, as in TrueType fonts. `None` when it can't be /// built. pub fn vertical_font(vorg: bool) -> Option> { const BASE: &[u8] = include_bytes!("../../../assets/fonts/SourceSans3-Regular.ttf"); let font = skrifa::FontRef::new(BASE).ok()?; let n = font.maxp().ok()?.num_glyphs(); let metrics = font.glyph_metrics(Size::unscaled(), LocationRef::default()); let tall = font.charmap().map(VERTICAL_TALL)?; let cell = |g: GlyphId| -> (u16, i16) { if g == tall { (1400, 1100) } else { (1000, 880) } }; let be16 = |v: &mut Vec, x: i16| v.extend_from_slice(&x.to_be_bytes()); // vmtx: (advance height, top side bearing = origin − the glyph's top) for every glyph. let mut vmtx = Vec::with_capacity(4 * usize::from(n)); for g in 0..u32::from(n) { let id = GlyphId::new(g); let (advance, origin) = cell(id); let top = metrics.bounds(id).map_or(0.0, |b| b.y_max).round() as i16; vmtx.extend_from_slice(&advance.to_be_bytes()); be16(&mut vmtx, origin - top); } // vhea 1.1: ascent/descent of the em box across the column, then the metrics' count. let mut vhea = Vec::with_capacity(36); vhea.extend_from_slice(&0x0001_1000_u32.to_be_bytes()); for x in [500, -500, 0] { be16(&mut vhea, x); } vhea.extend_from_slice(&1400_u16.to_be_bytes()); for x in [0, 0, 1400, 1, 0, 0, 0, 0, 0, 0, 0] { be16(&mut vhea, x); } vhea.extend_from_slice(&n.to_be_bytes()); let name = name_table(&[(1, VERTICAL_FAMILY), (2, "Regular"), (4, "Vertitest Sans Regular"), (6, "VertitestSans-Regular")])?; let mut builder = FontBuilder::new(); builder.add_raw(Tag::new(b"name"), name); builder.add_raw(Tag::new(b"vhea"), vhea); builder.add_raw(Tag::new(b"vmtx"), vmtx); if vorg { // VORG 1.0: the default origin, then the one glyph that differs. let mut t = Vec::new(); for x in [1, 0, 880, 1] { be16(&mut t, x); } t.extend_from_slice(&u16::try_from(tall.to_u32()).ok()?.to_be_bytes()); be16(&mut t, 1100); builder.add_raw(Tag::new(b"VORG"), t); } for r in font.table_directory.table_records() { let tag = Tag::new(&r.tag().to_be_bytes()); if !builder.contains(tag) { builder.add_raw(tag, font.table_data(r.tag())?.as_bytes()); } } Some(builder.build()) } /// The family of [`twin_font`]: two versions of one family and style. pub const TWIN_FAMILY: &str = "Twintest Sans"; /// The version strings of [`twin_font`]'s older and newer face. pub const TWIN_VERSIONS: [&str; 2] = ["Version 1.000", "Version 2.000"]; /// A face of family [`TWIN_FAMILY`], style Regular, in version [`TWIN_VERSIONS`]`[0]` (the bundled /// Source Sans 3 Regular) or, `newer`, `[1]` (the bundled Inter Regular): one name, two sets of /// glyphs, as a font library that keeps an old version beside a new one has. `None` when it can't /// be built. pub fn twin_font(newer: bool) -> Option> { const OLDER: &[u8] = include_bytes!("../../../assets/fonts/SourceSans3-Regular.ttf"); const NEWER: &[u8] = include_bytes!("../../../assets/fonts/Inter-Regular.ttf"); let font = skrifa::FontRef::new(if newer { NEWER } else { OLDER }).ok()?; let version = TWIN_VERSIONS[usize::from(newer)]; let name = name_table(&[(1, TWIN_FAMILY), (2, "Regular"), (4, "Twintest Sans Regular"), (5, version), (6, "TwintestSans-Regular")])?; let mut builder = FontBuilder::new(); builder.add_raw(Tag::new(b"name"), name); for r in font.table_directory.table_records() { let tag = Tag::new(&r.tag().to_be_bytes()); if !builder.contains(tag) { builder.add_raw(tag, font.table_data(r.tag())?.as_bytes()); } } Some(builder.build()) }