Files
vectorcraft/crates/text/src/test_fonts.rs
T
ruchirguitarandClaude Opus 5.5 163ce1731b G9: .ai font set keys on version too; test /5 (#6388)
Two installed versions of one font are two fonts of the set, so each
keeps its own /5. The generated variable test font now has a version
(name ID 5), and the test checks each variable record writes it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 01:30:11 +05:30

1685 lines
87 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! 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 std::sync::atomic::{AtomicBool, Ordering};
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};
/// Whether faces use and keep shaping plans ([`keep_shape_plans`]).
static KEEP_SHAPE_PLANS: AtomicBool = AtomicBool::new(true);
/// Sets whether font faces use and keep shaping plans, for the whole process. When `keep` is false,
/// every shaped segment makes a plan of its own, so a test can compare what kept plans produce with
/// what its own plan produces. Plans a face already holds stay and are used again when it is true.
pub fn keep_shape_plans(keep: bool) {
KEEP_SHAPE_PLANS.store(keep, Ordering::SeqCst);
}
pub(crate) fn keeps_shape_plans() -> bool {
KEEP_SHAPE_PLANS.load(Ordering::SeqCst)
}
std::thread_local! {
/// How many layouts this thread has started ([`layouts_on_this_thread`]).
static LAYOUTS: std::cell::Cell<u64> = const { std::cell::Cell::new(0) };
}
pub(crate) fn count_layout() {
LAYOUTS.with(|n| n.set(n.get() + 1));
}
/// How many layouts this thread has started.
pub fn layouts_on_this_thread() -> u64 {
LAYOUTS.with(std::cell::Cell::get)
}
/// Drop every layout kept for a first frame, so that a test can draw what laying out again draws.
pub fn drop_kept_layouts() {
crate::kept::drop_kept_layouts();
}
/// 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. Its version (name ID 5) is
/// `Version 1.000`. `None` when it can't
/// be built.
pub fn variable_font() -> Option<Vec<u8>> {
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<GlyphId> = 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<GlyphDelta> = 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"),
(5, "Version 1.000"),
(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())
}
/// The family of [`two_axis_font`].
pub const TWO_AXIS_FAMILY: &str = "Biaxtest Sans";
/// How much narrower [`two_axis_font`] makes [`VARIABLE_CHARS`] at its narrowest width.
pub const TWO_AXIS_NARROW: f32 = 0.2;
/// The bundled Source Sans 3 Regular as a variable font of family [`TWO_AXIS_FAMILY`] with two axes:
/// weight (`wght` 400–700, default 400), whose maximum makes the glyphs of [`VARIABLE_CHARS`]
/// [`VARIABLE_WIDEN`] wider, and width (`wdth` 75–100, default 100), whose minimum makes them
/// [`TWO_AXIS_NARROW`] narrower (outlines and advances, through `gvar`). Its named instances are
/// Regular (the default), Bold (`wght` 700) and Condensed (`wdth` 75). `None` when it can't be
/// built.
pub fn two_axis_font() -> Option<Vec<u8>> {
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 varied: Vec<GlyphId> = 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 id = GlyphId::new(g);
let mut deltas = vec![];
if varied.contains(&id) {
let Ok(Some(Glyph::Simple(s))) = loca.get_glyf(id, &glyf) else { return None };
let advance = metrics.advance_width(id)?;
// At each axis's far end: every point and the advance phantom point moved by `k` of their x.
let moved = |k: f32| {
let by = |x: f32| (x * k).round() as i16;
let mut d: Vec<GlyphDelta> = s.points().map(|p| GlyphDelta::required(by(f32::from(p.x)), 0)).collect();
d.extend([(0, 0), (by(advance), 0), (0, 0), (0, 0)].map(|(x, y)| GlyphDelta::required(x, y)));
d
};
let peak = |w: f32, d: f32| vec![Tent::new(F2Dot14::from_f32(w), None), Tent::new(F2Dot14::from_f32(d), None)];
deltas.push(GlyphDeltas::new(peak(1.0, 0.0), moved(VARIABLE_WIDEN)));
deltas.push(GlyphDeltas::new(peak(0.0, -1.0), moved(-TWO_AXIS_NARROW)));
}
variations.push(GlyphVariations::new(write_fonts::types::GlyphId::new(g), deltas));
}
let gvar = Gvar::new(variations, 2).ok()?;
let axis = |tag: &[u8; 4], min: i32, default: i32, max: i32, name: u16| VariationAxisRecord {
axis_tag: Tag::new(tag),
min_value: Fixed::from_i32(min),
default_value: Fixed::from_i32(default),
max_value: Fixed::from_i32(max),
flags: 0,
axis_name_id: NameId::new(name),
};
let instance = |name: u16, wght: i32, wdth: i32| InstanceRecord {
subfamily_name_id: NameId::new(name),
flags: 0,
coordinates: vec![Fixed::from_i32(wght), Fixed::from_i32(wdth)],
post_script_name_id: None,
};
let axes = vec![axis(b"wght", 400, 400, 700, 256), axis(b"wdth", 75, 100, 100, 257)];
let fvar = Fvar::new(AxisInstanceArrays::new(axes, vec![instance(258, 400, 100), instance(259, 700, 100), instance(260, 400, 75)]));
let name = name_table(&[
(1, TWO_AXIS_FAMILY),
(2, "Regular"),
(4, "Biaxtest Sans Regular"),
(6, "BiaxtestSans-Regular"),
(256, "Weight"),
(257, "Width"),
(258, "Regular"),
(259, "Bold"),
(260, "Condensed"),
])?;
let mut builder = FontBuilder::new();
builder.add_raw(Tag::new(b"name"), name);
builder.add_table(&fvar).ok()?.add_table(&gvar).ok()?;
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<Vec<u8>> {
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<u8> = 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<Vec<u8>> {
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<u8>, 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 [`height_font`] with its heights.
pub const HEIGHT_FAMILY: &str = "Heighttest Sans";
/// The family of [`height_font`] without them.
pub const HEIGHTLESS_FAMILY: &str = "Heightlesstest Sans";
/// The cap height and x height [`height_font`] gives, in font units (1,000 to the em).
pub const HEIGHT_CAP: i16 = 700;
pub const HEIGHT_X: i16 = 500;
/// The height (and the width across a vertical line) of [`height_font`]'s ideographic character
/// face, in ems.
pub const HEIGHT_ICF: f64 = 0.9;
/// The family of [`height_outside_font`].
pub const HEIGHT_OUTSIDE_FAMILY: &str = "Heightoutsidetest Sans";
/// The cap height and x height [`height_outside_font`] gives, in font units (1,000 to the em):
/// 2.5 em and 0.05 em, outside the 0.1 to 2 ems a font size measures.
pub const HEIGHT_OUTSIDE_CAP: i16 = 2500;
pub const HEIGHT_OUTSIDE_X: i16 = 50;
/// The bundled Source Sans 3 Regular as family [`HEIGHT_FAMILY`], with the cap height
/// [`HEIGHT_CAP`] and x height [`HEIGHT_X`] in its OS/2 table and a `BASE` table whose Han
/// baselines put the ideographic character face [`HEIGHT_ICF`] em high inside the em box (`ideo`
/// -120, `idtp` 880, `icfb` -70, `icft` 830). Without `heights`: family [`HEIGHTLESS_FAMILY`],
/// with a version 1 OS/2 table, which holds neither height, and no `BASE` table. `None` when it
/// can't be built.
pub fn height_font(heights: bool) -> Option<Vec<u8>> {
if heights { heights_font(HEIGHT_FAMILY, Some((HEIGHT_X, HEIGHT_CAP)), true) } else { heights_font(HEIGHTLESS_FAMILY, None, false) }
}
/// The bundled Source Sans 3 Regular as family [`HEIGHT_OUTSIDE_FAMILY`], with the cap height
/// [`HEIGHT_OUTSIDE_CAP`] and x height [`HEIGHT_OUTSIDE_X`] in its OS/2 table and no `BASE`
/// table. `None` when it can't be built.
pub fn height_outside_font() -> Option<Vec<u8>> {
heights_font(HEIGHT_OUTSIDE_FAMILY, Some((HEIGHT_OUTSIDE_X, HEIGHT_OUTSIDE_CAP)), false)
}
/// The bundled Source Sans 3 Regular as `family`: with the x height and cap height `heights` in
/// its OS/2 table, else a version 1 OS/2 table, which holds neither; with `base`, the `BASE`
/// table of [`height_font`].
fn heights_font(family: &str, heights: Option<(i16, i16)>, base: bool) -> Option<Vec<u8>> {
use write_fonts::tables::base::{Axis, Base, BaseCoord, BaseScript, BaseScriptList, BaseScriptRecord, BaseTagList, BaseValues};
const BASE: &[u8] = include_bytes!("../../../assets/fonts/SourceSans3-Regular.ttf");
let font = skrifa::FontRef::new(BASE).ok()?;
let full = format!("{family} Regular");
let postscript = format!("{}-Regular", family.replace(' ', ""));
let name = name_table(&[(1, family), (2, "Regular"), (4, &full), (6, &postscript)])?;
let mut builder = FontBuilder::new();
builder.add_raw(Tag::new(b"name"), name);
if base {
let tags = BaseTagList::new([b"icfb", b"icft", b"ideo", b"idtp"].map(Tag::new).to_vec());
let values = BaseValues::new(2, [-70, 830, -120, 880].map(BaseCoord::format_1).to_vec());
let hani = BaseScriptRecord::new(Tag::new(b"hani"), BaseScript::new(Some(values), None, vec![]));
let axis = Axis::new(Some(tags), BaseScriptList::new(vec![hani]));
builder.add_table(&Base::new(Some(axis), None)).ok()?;
}
for r in font.table_directory.table_records() {
let tag = Tag::new(&r.tag().to_be_bytes());
if builder.contains(tag) {
continue;
}
let mut data = font.table_data(r.tag())?.as_bytes().to_vec();
if tag == Tag::new(b"OS/2") {
if let Some((x, cap)) = heights {
// sxHeight and sCapHeight, at bytes 86 and 88 of a version 2 or later table.
data.get_mut(86..90)?.copy_from_slice(&[x.to_be_bytes(), cap.to_be_bytes()].concat());
} else {
// Version 1 ends before sxHeight.
data.truncate(86);
data.get_mut(0..2)?.copy_from_slice(&1_u16.to_be_bytes());
}
}
builder.add_raw(tag, data);
}
Some(builder.build())
}
/// The family of [`vertical_punctuation_font`].
pub const VERTICAL_PUNCTUATION_FAMILY: &str = "Punctuationtest Sans";
/// A test-only cmap over bundled Source Sans 3: fullwidth !/? use the ASCII glyphs,
/// 「 uses L and ー uses a hyphen. Optionally `vert` substitutes I/J for !/?, so tests
/// exercise both nominal fallbacks and actual vertical substitutions without OS fonts.
pub fn vertical_punctuation_font(alternates: bool) -> Option<Vec<u8>> {
use write_fonts::tables::cmap::Cmap;
use write_fonts::tables::gsub::{Gsub, SingleSubstFormat2, SubstitutionLookup, SubstitutionLookupList};
use write_fonts::tables::layout::{CoverageFormat1, Feature, FeatureList, FeatureRecord, LangSys, Lookup, Script, ScriptList, ScriptRecord};
use write_fonts::types::GlyphId16;
const BASE: &[u8] = include_bytes!("../../../assets/fonts/SourceSans3-Regular.ttf");
let font = skrifa::FontRef::new(BASE).ok()?;
let charmap = font.charmap();
let mapping = [('!', '!'), ('?', '?'), ('「', 'L'), ('ー', '-'), ('!', '!'), ('?', '?'), ('I', 'I'), ('J', 'J'), ('L', 'L'), ('-', '-')];
let mappings = mapping.into_iter().map(|(c, source)| Some((c, write_fonts::types::GlyphId::new(charmap.map(source)?.to_u32()))));
let cmap = Cmap::from_mappings(mappings.collect::<Option<Vec<_>>>()?).ok()?;
let family = VERTICAL_PUNCTUATION_FAMILY;
let name = name_table(&[(1, family), (2, "Regular"), (4, "Punctuationtest Sans Regular"), (6, "PunctuationtestSans-Regular")])?;
let mut builder = FontBuilder::new();
builder.add_table(&cmap).ok()?;
builder.add_raw(Tag::new(b"name"), name);
if alternates {
let gid = |c| Some(GlyphId16::new(u16::try_from(charmap.map(c)?.to_u32()).ok()?));
let mut pairs = [(gid('!')?, gid('I')?), (gid('?')?, gid('J')?)];
pairs.sort_by_key(|p| p.0);
let coverage = CoverageFormat1::new(pairs.iter().map(|p| p.0).collect());
let subst = SingleSubstFormat2::new(coverage.into(), pairs.iter().map(|p| p.1).collect());
let scripts = ScriptList::new(vec![ScriptRecord::new(Tag::new(b"DFLT"), Script::new(Some(LangSys::new(vec![0])), vec![]))]);
let features = FeatureList::new(vec![FeatureRecord::new(Tag::new(b"vert"), Feature::new(None, vec![0]))]);
let lookups = SubstitutionLookupList::new(vec![SubstitutionLookup::Single(Lookup::new(Default::default(), vec![subst.into()]))]);
builder.add_table(&Gsub::new(scripts, features, lookups)).ok()?;
}
for r in font.table_directory.table_records() {
let tag = Tag::new(&r.tag().to_be_bytes());
if tag != Tag::new(b"GSUB") && !builder.contains(tag) {
builder.add_raw(tag, font.table_data(r.tag())?.as_bytes());
}
}
Some(builder.build())
}
/// The family of [`half_form_font`].
pub const HALF_FORM_FAMILY: &str = "Halftest Sans";
/// A test-only cmap over bundled Source Sans 3: Devanagari Ka uses K, the halant uses a period, Ssa
/// uses S and the zero width joiner uses the space. The `GSUB` table lists the `dev2` script either
/// way. With `half`, its `half` feature substitutes k for K followed by the period (Ka's half form);
/// without it, the script has no features and Ka has no half form. `None` when it can't be built.
pub fn half_form_font(half: bool) -> Option<Vec<u8>> {
use write_fonts::tables::cmap::Cmap;
use write_fonts::tables::gsub::{Gsub, Ligature, LigatureSet, LigatureSubstFormat1, SubstitutionLookup, SubstitutionLookupList};
use write_fonts::tables::layout::{CoverageFormat1, Feature, FeatureList, FeatureRecord, LangSys, Lookup, Script, ScriptList, ScriptRecord};
use write_fonts::types::GlyphId16;
const BASE: &[u8] = include_bytes!("../../../assets/fonts/SourceSans3-Regular.ttf");
let font = skrifa::FontRef::new(BASE).ok()?;
let charmap = font.charmap();
let mapping = [('क', 'K'), ('\u{094D}', '.'), ('ष', 'S'), ('\u{200D}', ' ')];
let mappings = mapping.into_iter().map(|(c, source)| Some((c, write_fonts::types::GlyphId::new(charmap.map(source)?.to_u32()))));
let cmap = Cmap::from_mappings(mappings.collect::<Option<Vec<_>>>()?).ok()?;
let family = HALF_FORM_FAMILY;
let name = name_table(&[(1, family), (2, "Regular"), (4, "Halftest Sans Regular"), (6, "HalftestSans-Regular")])?;
let mut builder = FontBuilder::new();
builder.add_table(&cmap).ok()?;
builder.add_raw(Tag::new(b"name"), name);
let (features, lookups) = if half {
let gid = |c| Some(GlyphId16::new(u16::try_from(charmap.map(c)?.to_u32()).ok()?));
let coverage = CoverageFormat1::new(vec![gid('K')?]);
let subst = LigatureSubstFormat1::new(coverage.into(), vec![LigatureSet::new(vec![Ligature::new(gid('k')?, vec![gid('.')?])])]);
let features = FeatureList::new(vec![FeatureRecord::new(Tag::new(b"half"), Feature::new(None, vec![0]))]);
let lookups = SubstitutionLookupList::new(vec![SubstitutionLookup::Ligature(Lookup::new(Default::default(), vec![subst]))]);
(features, lookups)
} else {
(FeatureList::new(vec![]), SubstitutionLookupList::new(vec![]))
};
let langsys = LangSys::new(if half { vec![0] } else { vec![] });
let scripts = ScriptList::new(vec![ScriptRecord::new(Tag::new(b"dev2"), Script::new(Some(langsys), vec![]))]);
builder.add_table(&Gsub::new(scripts, features, lookups)).ok()?;
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 [`conjunct_font`].
pub const CONJUNCT_FAMILY: &str = "Conjuncttest Sans";
/// A test-only cmap over bundled Source Sans 3: Devanagari Ka uses K, the halant uses a period and
/// the vowel sign I (drawn before its syllable) uses i. The `GSUB` table's `dev2` script has an
/// `aalt` single substitution of K by k and a `salt` one of K by i, which list k and the vowel
/// sign's glyph as alternates of Ka, and a `half` ligature of K and the period into k (Ka's half
/// form). Family [`CONJUNCT_FAMILY`]. `None` when it can't be built.
pub fn conjunct_font() -> Option<Vec<u8>> {
use write_fonts::tables::cmap::Cmap;
use write_fonts::tables::gsub::{
Gsub, Ligature, LigatureSet, LigatureSubstFormat1, SingleSubstFormat2, SubstitutionLookup, SubstitutionLookupList,
};
use write_fonts::tables::layout::{CoverageFormat1, Feature, FeatureList, FeatureRecord, LangSys, Lookup, Script, ScriptList, ScriptRecord};
use write_fonts::types::GlyphId16;
const BASE: &[u8] = include_bytes!("../../../assets/fonts/SourceSans3-Regular.ttf");
let font = skrifa::FontRef::new(BASE).ok()?;
let charmap = font.charmap();
let mapping = [('क', 'K'), ('\u{094D}', '.'), ('\u{093F}', 'i')];
let mappings = mapping.into_iter().map(|(c, source)| Some((c, write_fonts::types::GlyphId::new(charmap.map(source)?.to_u32()))));
let cmap = Cmap::from_mappings(mappings.collect::<Option<Vec<_>>>()?).ok()?;
let family = CONJUNCT_FAMILY;
let name = name_table(&[(1, family), (2, "Regular"), (4, "Conjuncttest Sans Regular"), (6, "ConjuncttestSans-Regular")])?;
let mut builder = FontBuilder::new();
builder.add_table(&cmap).ok()?;
builder.add_raw(Tag::new(b"name"), name);
let gid = |c| Some(GlyphId16::new(u16::try_from(charmap.map(c)?.to_u32()).ok()?));
let ka = gid('K')?;
let aalt = SingleSubstFormat2::new(CoverageFormat1::new(vec![ka]).into(), vec![gid('k')?]);
let half =
LigatureSubstFormat1::new(CoverageFormat1::new(vec![ka]).into(), vec![LigatureSet::new(vec![Ligature::new(gid('k')?, vec![gid('.')?])])]);
let salt = SingleSubstFormat2::new(CoverageFormat1::new(vec![ka]).into(), vec![gid('i')?]);
// Features sorted by tag, as OpenType requires: `aalt`, `half`, then `salt`.
let features = FeatureList::new(vec![
FeatureRecord::new(Tag::new(b"aalt"), Feature::new(None, vec![0])),
FeatureRecord::new(Tag::new(b"half"), Feature::new(None, vec![1])),
FeatureRecord::new(Tag::new(b"salt"), Feature::new(None, vec![2])),
]);
let lookups = SubstitutionLookupList::new(vec![
SubstitutionLookup::Single(Lookup::new(Default::default(), vec![aalt.into()])),
SubstitutionLookup::Ligature(Lookup::new(Default::default(), vec![half])),
SubstitutionLookup::Single(Lookup::new(Default::default(), vec![salt.into()])),
]);
let scripts = ScriptList::new(vec![ScriptRecord::new(Tag::new(b"dev2"), Script::new(Some(LangSys::new(vec![0, 1, 2])), vec![]))]);
builder.add_table(&Gsub::new(scripts, features, lookups)).ok()?;
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 [`matra_font`].
pub const MATRA_FAMILY: &str = "Matratest Sans";
/// A test-only cmap over bundled Source Sans 3: Devanagari Ka uses a, the vowel sign I (drawn
/// before its consonant) uses i and the nukta uses the period. The `GSUB` table's `dev2` script has
/// a `nukt` ligature of Ka and the nukta into N, and a `salt` single substitution of Ka by b.
/// Family [`MATRA_FAMILY`]. `None` when it can't be built.
pub fn matra_font() -> Option<Vec<u8>> {
use write_fonts::tables::cmap::Cmap;
use write_fonts::tables::gsub::{
Gsub, Ligature, LigatureSet, LigatureSubstFormat1, SingleSubstFormat2, SubstitutionLookup, SubstitutionLookupList,
};
use write_fonts::tables::layout::{CoverageFormat1, Feature, FeatureList, FeatureRecord, LangSys, Lookup, Script, ScriptList, ScriptRecord};
use write_fonts::types::GlyphId16;
const BASE: &[u8] = include_bytes!("../../../assets/fonts/SourceSans3-Regular.ttf");
let font = skrifa::FontRef::new(BASE).ok()?;
let charmap = font.charmap();
let mapping = [('क', 'a'), ('\u{093F}', 'i'), ('\u{093C}', '.')];
let mappings = mapping.into_iter().map(|(c, source)| Some((c, write_fonts::types::GlyphId::new(charmap.map(source)?.to_u32()))));
let cmap = Cmap::from_mappings(mappings.collect::<Option<Vec<_>>>()?).ok()?;
let family = MATRA_FAMILY;
let name = name_table(&[(1, family), (2, "Regular"), (4, "Matratest Sans Regular"), (5, "Version 1.000"), (6, "MatratestSans-Regular")])?;
let mut builder = FontBuilder::new();
builder.add_table(&cmap).ok()?;
builder.add_raw(Tag::new(b"name"), name);
let gid = |c| Some(GlyphId16::new(u16::try_from(charmap.map(c)?.to_u32()).ok()?));
let ka = gid('a')?;
let ligature = LigatureSet::new(vec![Ligature::new(gid('N')?, vec![gid('.')?])]);
let nukt = LigatureSubstFormat1::new(CoverageFormat1::new(vec![ka]).into(), vec![ligature]);
let salt = SingleSubstFormat2::new(CoverageFormat1::new(vec![ka]).into(), vec![gid('b')?]);
// Features sorted by tag, as OpenType requires: `nukt`, then `salt`.
let features = FeatureList::new(vec![
FeatureRecord::new(Tag::new(b"nukt"), Feature::new(None, vec![0])),
FeatureRecord::new(Tag::new(b"salt"), Feature::new(None, vec![1])),
]);
let lookups = SubstitutionLookupList::new(vec![
SubstitutionLookup::Ligature(Lookup::new(Default::default(), vec![nukt])),
SubstitutionLookup::Single(Lookup::new(Default::default(), vec![salt.into()])),
]);
let scripts = ScriptList::new(vec![ScriptRecord::new(Tag::new(b"dev2"), Script::new(Some(LangSys::new(vec![0, 1])), vec![]))]);
builder.add_table(&Gsub::new(scripts, features, lookups)).ok()?;
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 [`lam_alef_font`] whose lam-alef ligature is in `feature`.
pub fn lam_alef_family(feature: &[u8; 4]) -> &'static str {
match feature {
b"liga" => "Lamtest Liga",
b"rlig" => "Lamtest Rlig",
_ => "Lamtest Other",
}
}
/// The glyph [`lam_alef_font`]'s `salt` gives its lam + alef ligature (H): M.
pub const LAM_ALEF_SALT: char = 'M';
/// A test-only Arabic cmap over bundled Source Sans 3: lam uses l, alef I, alef with hamza above
/// J, beh b and fatha the combining acute (a mark in the font's `GDEF`). The `GSUB` table's `arab`
/// script has one ligature lookup, under `feature`, that skips marks and joins lam + alef into H
/// and lam + alef with hamza above into K, and a `salt` lookup that turns H into
/// [`LAM_ALEF_SALT`]. Older fonts put the mandatory lam-alef ligature in `liga` rather than
/// `rlig`; this builds either, without an OS font. `None` when it can't be built.
pub fn lam_alef_font(feature: &[u8; 4]) -> Option<Vec<u8>> {
use write_fonts::tables::cmap::Cmap;
use write_fonts::tables::gsub::{
Gsub, Ligature, LigatureSet, LigatureSubstFormat1, SingleSubstFormat2, SubstitutionLookup, SubstitutionLookupList,
};
use write_fonts::tables::layout::{
CoverageFormat1, Feature, FeatureList, FeatureRecord, LangSys, Lookup, LookupFlag, Script, ScriptList, ScriptRecord,
};
use write_fonts::types::GlyphId16;
const BASE: &[u8] = include_bytes!("../../../assets/fonts/SourceSans3-Regular.ttf");
let font = skrifa::FontRef::new(BASE).ok()?;
let charmap = font.charmap();
let mapping = [('\u{0644}', 'l'), ('\u{0627}', 'I'), ('\u{0623}', 'J'), ('\u{0628}', 'b'), ('\u{064E}', '\u{0301}')];
let mappings = mapping.into_iter().map(|(c, source)| Some((c, write_fonts::types::GlyphId::new(charmap.map(source)?.to_u32()))));
let cmap = Cmap::from_mappings(mappings.collect::<Option<Vec<_>>>()?).ok()?;
let family = lam_alef_family(feature);
let full = format!("{family} Regular");
let postscript = format!("{}-Regular", family.replace(' ', ""));
let name = name_table(&[(1, family), (2, "Regular"), (4, &full), (6, &postscript)])?;
let mut builder = FontBuilder::new();
builder.add_table(&cmap).ok()?;
builder.add_raw(Tag::new(b"name"), name);
let gid = |c| Some(GlyphId16::new(u16::try_from(charmap.map(c)?.to_u32()).ok()?));
let coverage = CoverageFormat1::new(vec![gid('l')?]);
let ligatures = vec![Ligature::new(gid('H')?, vec![gid('I')?]), Ligature::new(gid('K')?, vec![gid('J')?])];
let subst = LigatureSubstFormat1::new(coverage.into(), vec![LigatureSet::new(ligatures)]);
let salt = SingleSubstFormat2::new(CoverageFormat1::new(vec![gid('H')?]).into(), vec![gid(LAM_ALEF_SALT)?]);
// Features sorted by tag, as OpenType requires: `liga` / `rlig`, then `salt`.
let features = FeatureList::new(vec![
FeatureRecord::new(Tag::new(feature), Feature::new(None, vec![0])),
FeatureRecord::new(Tag::new(b"salt"), Feature::new(None, vec![1])),
]);
let lookups = SubstitutionLookupList::new(vec![
SubstitutionLookup::Ligature(Lookup::new(LookupFlag::IGNORE_MARKS, vec![subst])),
SubstitutionLookup::Single(Lookup::new(Default::default(), vec![salt.into()])),
]);
let scripts = ScriptList::new(vec![ScriptRecord::new(Tag::new(b"arab"), Script::new(Some(LangSys::new(vec![0, 1])), vec![]))]);
builder.add_table(&Gsub::new(scripts, features, lookups)).ok()?;
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 [`alternates_font`] with an `aalt` feature, and without one.
pub fn alternates_family(aalt: bool) -> &'static str {
if aalt { "Alttest Sans" } else { "Alttest Sans Noaalt" }
}
/// Four glyphs of the bundled Source Sans 3 Regular that its `cmap` maps no character to, the
/// lowest ids first: the unencoded alternates of [`alternates_font`]. `None` when there are not
/// four.
pub fn unencoded_glyphs() -> Option<[u32; 4]> {
const BASE: &[u8] = include_bytes!("../../../assets/fonts/SourceSans3-Regular.ttf");
let font = skrifa::FontRef::new(BASE).ok()?;
let mapped: std::collections::HashSet<u32> = font.charmap().mappings().map(|(_, g)| g.to_u32()).collect();
let glyphs = u32::from(font.maxp().ok()?.num_glyphs());
let found: Vec<u32> = (1..glyphs).filter(|g| !mapped.contains(g)).take(4).collect();
found.try_into().ok()
}
/// The family of [`crowded_alternates_font`].
pub const CROWDED_ALTERNATES_FAMILY: &str = "Crowdtest Sans";
/// The bundled Source Sans 3 Regular, its `cmap` kept, with a `GSUB` table whose 17 `cv01` feature
/// records each list a single substitution of `a` by `c` 256 times, followed by a `salt` record
/// whose single substitution gives `b`. Family [`CROWDED_ALTERNATES_FAMILY`]. `None` when it can't
/// be built.
pub fn crowded_alternates_font() -> Option<Vec<u8>> {
use write_fonts::tables::gsub::{Gsub, SingleSubst, SingleSubstFormat2, SubstitutionLookup, SubstitutionLookupList};
use write_fonts::tables::layout::{CoverageFormat1, Feature, FeatureList, FeatureRecord, LangSys, Lookup, Script, ScriptList, ScriptRecord};
use write_fonts::types::GlyphId16;
const BASE: &[u8] = include_bytes!("../../../assets/fonts/SourceSans3-Regular.ttf");
let font = skrifa::FontRef::new(BASE).ok()?;
let charmap = font.charmap();
let family = CROWDED_ALTERNATES_FAMILY;
let name = name_table(&[(1, family), (2, "Regular"), (4, "Crowdtest Sans Regular"), (5, "Version 1.000"), (6, "CrowdtestSans-Regular")])?;
let mut builder = FontBuilder::new();
builder.add_raw(Tag::new(b"name"), name);
let gid = |c| Some(GlyphId16::new(u16::try_from(charmap.map(c)?.to_u32()).ok()?));
let a = gid('a')?;
let single = |to: GlyphId16| {
SubstitutionLookup::Single(Lookup::new(
Default::default(),
vec![SingleSubst::from(SingleSubstFormat2::new(CoverageFormat1::new(vec![a]).into(), vec![to]))],
))
};
let lookups = SubstitutionLookupList::new(vec![single(gid('c')?), single(gid('b')?)]);
let mut records: Vec<FeatureRecord> = (0..17).map(|_| FeatureRecord::new(Tag::new(b"cv01"), Feature::new(None, vec![0; 256]))).collect();
records.push(FeatureRecord::new(Tag::new(b"salt"), Feature::new(None, vec![1])));
let indices = (0..records.len() as u16).collect();
let scripts = ScriptList::new(vec![ScriptRecord::new(Tag::new(b"DFLT"), Script::new(Some(LangSys::new(indices)), vec![]))]);
builder.add_table(&Gsub::new(scripts, FeatureList::new(records), lookups)).ok()?;
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 bundled Source Sans 3 Regular, its `cmap` kept, with a `GSUB` table of alternates for `a`
/// and nothing else. With [`unencoded_glyphs`] `[u1, u2, u3, u4]`: `aalt` (alternate
/// substitution) lists a, u1 and b; `cv01` (single substitution) gives c; `salt` (single) gives b;
/// `smcp` (single) gives d; `ss01` (single, a glyph-id delta) gives u2; `ss02` (single, in an
/// extension lookup) gives u4; and `swsh` (alternate substitution) lists u3 and u1. Without `aalt`
/// the font has the others only. Family [`alternates_family`]. `None` when it can't be built.
pub fn alternates_font(aalt: bool) -> Option<Vec<u8>> {
use write_fonts::tables::gsub::{
AlternateSet, AlternateSubstFormat1, ExtensionSubstFormat1, ExtensionSubtable, Gsub, SingleSubst, SingleSubstFormat1, SingleSubstFormat2,
SubstitutionLookup, SubstitutionLookupList,
};
use write_fonts::tables::layout::{CoverageFormat1, Feature, FeatureList, FeatureRecord, LangSys, Lookup, Script, ScriptList, ScriptRecord};
use write_fonts::types::GlyphId16;
const BASE: &[u8] = include_bytes!("../../../assets/fonts/SourceSans3-Regular.ttf");
let font = skrifa::FontRef::new(BASE).ok()?;
let charmap = font.charmap();
let family = alternates_family(aalt);
let full = format!("{family} Regular");
let postscript = format!("{}-Regular", family.replace(' ', ""));
let name = name_table(&[(1, family), (2, "Regular"), (4, &full), (5, "Version 1.000"), (6, &postscript)])?;
let mut builder = FontBuilder::new();
builder.add_raw(Tag::new(b"name"), name);
let id = |g: u32| Some(GlyphId16::new(u16::try_from(g).ok()?));
let gid = |c| id(charmap.map(c)?.to_u32());
let [u1, u2, u3, u4] = unencoded_glyphs()?.map(id);
let (u1, u2, u3, u4, a) = (u1?, u2?, u3?, u4?, gid('a')?);
let cover = || CoverageFormat1::new(vec![a]);
let single = |to: GlyphId16| SingleSubst::from(SingleSubstFormat2::new(cover().into(), vec![to]));
let lookup = |subtable: SingleSubst| SubstitutionLookup::Single(Lookup::new(Default::default(), vec![subtable]));
let alternates = |set: Vec<GlyphId16>| {
SubstitutionLookup::Alternate(Lookup::new(Default::default(), vec![AlternateSubstFormat1::new(cover().into(), vec![AlternateSet::new(set)])]))
};
let delta = i16::try_from(i32::from(u2.to_u16()) - i32::from(a.to_u16())).ok()?;
let extension = ExtensionSubtable::Single(ExtensionSubstFormat1::new(1, single(u4)));
// Lookups: 0 aalt, 1 cv01, 2 salt, 3 smcp, 4 ss01, 5 ss02, 6 swsh.
let lookups = SubstitutionLookupList::new(vec![
alternates(vec![a, u1, gid('b')?]),
lookup(single(gid('c')?)),
lookup(single(gid('b')?)),
lookup(single(gid('d')?)),
lookup(SingleSubst::from(SingleSubstFormat1::new(cover().into(), delta))),
SubstitutionLookup::Extension(Lookup::new(Default::default(), vec![extension])),
alternates(vec![u3, u1]),
]);
// Features sorted by tag, as OpenType requires.
let tags: [&[u8; 4]; 7] = [b"aalt", b"cv01", b"salt", b"smcp", b"ss01", b"ss02", b"swsh"];
let records: Vec<FeatureRecord> = tags
.iter()
.zip(0u16..)
.filter(|(t, _)| aalt || **t != b"aalt")
.map(|(t, i)| FeatureRecord::new(Tag::new(t), Feature::new(None, vec![i])))
.collect();
let indices = (0..records.len() as u16).collect();
let scripts = ScriptList::new(vec![ScriptRecord::new(Tag::new(b"DFLT"), Script::new(Some(LangSys::new(indices)), vec![]))]);
builder.add_table(&Gsub::new(scripts, FeatureList::new(records), lookups)).ok()?;
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 [`ligature_caret_font`].
pub const LIGATURE_CARET_FAMILY: &str = "Carettest Sans";
/// The glyph [`ligature_caret_font`]'s `liga` gives for f + i: W, wider than either letter.
pub const LIGATURE_CARET_GLYPH: char = 'W';
/// A test-only cmap over bundled Source Sans 3 for f, i and the space, whose `GSUB` `liga` joins f +
/// i into [`LIGATURE_CARET_GLYPH`]. With `caret`, a `GDEF` ligature caret list puts that glyph's one
/// caret at x = `caret` font units; without it the font has no `GDEF` table. `None` when it can't be
/// built.
pub fn ligature_caret_font(caret: Option<i16>) -> Option<Vec<u8>> {
use write_fonts::tables::cmap::Cmap;
use write_fonts::tables::gdef::{CaretValue, Gdef, LigCaretList, LigGlyph};
use write_fonts::tables::gsub::{Gsub, Ligature, LigatureSet, LigatureSubstFormat1, SubstitutionLookup, SubstitutionLookupList};
use write_fonts::tables::layout::{CoverageFormat1, Feature, FeatureList, FeatureRecord, LangSys, Lookup, Script, ScriptList, ScriptRecord};
use write_fonts::types::GlyphId16;
const BASE: &[u8] = include_bytes!("../../../assets/fonts/SourceSans3-Regular.ttf");
let font = skrifa::FontRef::new(BASE).ok()?;
let charmap = font.charmap();
let mapping = [('f', 'f'), ('i', 'i'), (' ', ' ')];
let mappings = mapping.into_iter().map(|(c, source)| Some((c, write_fonts::types::GlyphId::new(charmap.map(source)?.to_u32()))));
let cmap = Cmap::from_mappings(mappings.collect::<Option<Vec<_>>>()?).ok()?;
let family = LIGATURE_CARET_FAMILY;
let name = name_table(&[(1, family), (2, "Regular"), (4, "Carettest Sans Regular"), (6, "CarettestSans-Regular")])?;
let mut builder = FontBuilder::new();
builder.add_table(&cmap).ok()?;
builder.add_raw(Tag::new(b"name"), name);
let gid = |c| Some(GlyphId16::new(u16::try_from(charmap.map(c)?.to_u32()).ok()?));
let ligature = gid(LIGATURE_CARET_GLYPH)?;
let subst = LigatureSubstFormat1::new(
CoverageFormat1::new(vec![gid('f')?]).into(),
vec![LigatureSet::new(vec![Ligature::new(ligature, vec![gid('i')?])])],
);
let features = FeatureList::new(vec![FeatureRecord::new(Tag::new(b"liga"), Feature::new(None, vec![0]))]);
let lookups = SubstitutionLookupList::new(vec![SubstitutionLookup::Ligature(Lookup::new(Default::default(), vec![subst]))]);
let langsys = || Script::new(Some(LangSys::new(vec![0])), vec![]);
let scripts = ScriptList::new(vec![ScriptRecord::new(Tag::new(b"DFLT"), langsys()), ScriptRecord::new(Tag::new(b"latn"), langsys())]);
builder.add_table(&Gsub::new(scripts, features, lookups)).ok()?;
if let Some(x) = caret {
let carets = LigCaretList::new(CoverageFormat1::new(vec![ligature]).into(), vec![LigGlyph::new(vec![CaretValue::format_1(x)])]);
builder.add_table(&Gdef::new(None, None, Some(carets), None)).ok()?;
}
for r in font.table_directory.table_records() {
let tag = Tag::new(&r.tag().to_be_bytes());
if tag != Tag::new(b"GDEF") && !builder.contains(tag) {
builder.add_raw(tag, font.table_data(r.tag())?.as_bytes());
}
}
Some(builder.build())
}
/// The family of [`vertical_ligature_caret_font`].
pub const VERTICAL_LIGATURE_CARET_FAMILY: &str = "Vertical Carettest Sans";
/// [`ligature_caret_font`] with its one caret at y = `caret` font units, as family
/// [`VERTICAL_LIGATURE_CARET_FAMILY`], with vertical metrics (`vhea`, `vmtx` and `VORG`): every glyph
/// one em down the column with its vertical origin at 0.88 em. `None` when it can't be built.
pub fn vertical_ligature_caret_font(caret: i16) -> Option<Vec<u8>> {
let base = ligature_caret_font(Some(caret))?;
let font = skrifa::FontRef::new(&base).ok()?;
let n = font.maxp().ok()?.num_glyphs();
let be16 = |v: &mut Vec<u8>, x: i16| v.extend_from_slice(&x.to_be_bytes());
// vmtx: (advance height, top side bearing) for every glyph; VORG gives the origin.
let mut vmtx = Vec::with_capacity(4 * usize::from(n));
for _ in 0..n {
vmtx.extend_from_slice(&1000_u16.to_be_bytes());
be16(&mut vmtx, 0);
}
// 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(&1000_u16.to_be_bytes());
for x in [0, 0, 1000, 1, 0, 0, 0, 0, 0, 0, 0] {
be16(&mut vhea, x);
}
vhea.extend_from_slice(&n.to_be_bytes());
// VORG 1.0: the default origin and no glyph that differs.
let mut vorg = Vec::new();
for x in [1, 0, 880, 0] {
be16(&mut vorg, x);
}
let family = VERTICAL_LIGATURE_CARET_FAMILY;
let name = name_table(&[(1, family), (2, "Regular"), (4, "Vertical Carettest Sans Regular"), (6, "VerticalCarettestSans-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);
builder.add_raw(Tag::new(b"VORG"), vorg);
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())
}
/// [`vertical_ligature_caret_font`] with its one caret at y = `caret` font units, f + i joined by
/// `ccmp` (which shaping applies top to bottom too, unlike `liga`), and its `GPOS` replaced by a
/// `vpal` single adjustment that changes the ligature glyph's advance down the column by
/// `y_advance` font units. `None` when it can't be built.
pub fn proportional_vertical_ligature_caret_font(caret: i16, y_advance: i16) -> Option<Vec<u8>> {
use write_fonts::tables::gpos::{Gpos, PositionLookup, PositionLookupList, SinglePos, ValueRecord};
use write_fonts::tables::gsub::{Gsub, Ligature, LigatureSet, LigatureSubstFormat1, SubstitutionLookup, SubstitutionLookupList};
use write_fonts::tables::layout::{CoverageFormat1, Feature, FeatureList, FeatureRecord, LangSys, Lookup, Script, ScriptList, ScriptRecord};
use write_fonts::types::GlyphId16;
let base = vertical_ligature_caret_font(caret)?;
let font = skrifa::FontRef::new(&base).ok()?;
let gid = |c| Some(GlyphId16::new(source_sans_gid(c)?));
let ligature = gid(LIGATURE_CARET_GLYPH)?;
let langsys = || Script::new(Some(LangSys::new(vec![0])), vec![]);
let scripts = || ScriptList::new(vec![ScriptRecord::new(Tag::new(b"DFLT"), langsys()), ScriptRecord::new(Tag::new(b"latn"), langsys())]);
let subst = LigatureSubstFormat1::new(
CoverageFormat1::new(vec![gid('f')?]).into(),
vec![LigatureSet::new(vec![Ligature::new(ligature, vec![gid('i')?])])],
);
let features = FeatureList::new(vec![FeatureRecord::new(Tag::new(b"ccmp"), Feature::new(None, vec![0]))]);
let lookups = SubstitutionLookupList::new(vec![SubstitutionLookup::Ligature(Lookup::new(Default::default(), vec![subst]))]);
let gsub = Gsub::new(scripts(), features, lookups);
let pos = SinglePos::format_1(CoverageFormat1::new(vec![ligature]).into(), ValueRecord::new().with_y_advance(y_advance));
let features = FeatureList::new(vec![FeatureRecord::new(Tag::new(b"vpal"), Feature::new(None, vec![0]))]);
let lookups = PositionLookupList::new(vec![PositionLookup::Single(Lookup::new(Default::default(), vec![pos]))]);
let mut builder = FontBuilder::new();
builder.add_table(&gsub).ok()?;
builder.add_table(&Gpos::new(scripts(), features, lookups)).ok()?;
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 [`variable_ligature_caret_font`].
pub const VARIABLE_LIGATURE_CARET_FAMILY: &str = "Varicaret Sans";
/// [`ligature_caret_font`] as a variable font of family [`VARIABLE_LIGATURE_CARET_FAMILY`]: one
/// weight axis (400–700, default 400) with the named instances Regular (400) and Bold (700), and a
/// `GDEF` ligature caret list whose one caret is a format 3 value at x = `caret` font units with a
/// variation index into the `GDEF` item variation store that moves it by `delta` at 700. `None`
/// when it can't be built.
pub fn variable_ligature_caret_font(caret: i16, delta: i16) -> Option<Vec<u8>> {
use write_fonts::tables::gdef::{CaretValue, Gdef, LigCaretList, LigGlyph};
use write_fonts::tables::layout::{CoverageFormat1, DeviceOrVariationIndex};
use write_fonts::tables::variations::{ItemVariationData, ItemVariationStore, RegionAxisCoordinates, VariationRegion, VariationRegionList};
use write_fonts::types::GlyphId16;
let base = ligature_caret_font(None)?;
let font = skrifa::FontRef::new(&base).ok()?;
let ligature = GlyphId16::new(source_sans_gid(LIGATURE_CARET_GLYPH)?);
let value = CaretValue::format_3(caret, DeviceOrVariationIndex::variation_index(0, 0));
let carets = LigCaretList::new(CoverageFormat1::new(vec![ligature]).into(), vec![LigGlyph::new(vec![value])]);
let mut gdef = Gdef::new(None, None, Some(carets), None);
let region = VariationRegion::new(vec![RegionAxisCoordinates::new(F2Dot14::from_f32(0.0), F2Dot14::from_f32(1.0), F2Dot14::from_f32(1.0))]);
// One delta set of one 16-bit delta for the one region.
let data = ItemVariationData::new(1, 1, vec![0], delta.to_be_bytes().to_vec());
gdef.item_var_store = Some(ItemVariationStore::new(VariationRegionList::new(1, vec![region]), vec![Some(data)])).into();
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 fvar = Fvar::new(AxisInstanceArrays::new(vec![axis], vec![instance(257, 400), instance(258, 700)]));
let family = VARIABLE_LIGATURE_CARET_FAMILY;
let name = name_table(&[
(1, family),
(2, "Regular"),
(4, "Varicaret Sans Regular"),
(6, "VaricaretSans-Regular"),
(256, "Weight"),
(257, "Regular"),
(258, "Bold"),
])?;
let mut builder = FontBuilder::new();
builder.add_raw(Tag::new(b"name"), name);
builder.add_table(&gdef).ok()?.add_table(&fvar).ok()?;
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())
}
/// [`ligature_caret_font`] with its one caret at x = `caret` font units and a `GPOS` `kern` single
/// adjustment that places the ligature glyph `x` font units right of its pen position. `None` when
/// it can't be built.
pub fn placed_ligature_caret_font(caret: i16, x: i16) -> Option<Vec<u8>> {
use write_fonts::tables::gpos::{Gpos, PositionLookup, PositionLookupList, SinglePos, ValueRecord};
use write_fonts::tables::layout::{CoverageFormat1, Feature, FeatureList, FeatureRecord, LangSys, Lookup, Script, ScriptList, ScriptRecord};
use write_fonts::types::GlyphId16;
let base = ligature_caret_font(Some(caret))?;
let font = skrifa::FontRef::new(&base).ok()?;
let ligature = GlyphId16::new(source_sans_gid(LIGATURE_CARET_GLYPH)?);
let pos = SinglePos::format_1(CoverageFormat1::new(vec![ligature]).into(), ValueRecord::new().with_x_placement(x));
let features = FeatureList::new(vec![FeatureRecord::new(Tag::new(b"kern"), Feature::new(None, vec![0]))]);
let lookups = PositionLookupList::new(vec![PositionLookup::Single(Lookup::new(Default::default(), vec![pos]))]);
let langsys = || Script::new(Some(LangSys::new(vec![0])), vec![]);
let scripts = ScriptList::new(vec![ScriptRecord::new(Tag::new(b"DFLT"), langsys()), ScriptRecord::new(Tag::new(b"latn"), langsys())]);
let mut builder = FontBuilder::new();
builder.add_table(&Gpos::new(scripts, features, lookups)).ok()?;
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 [`pair_kerning_font`].
pub const PAIR_KERNING_FAMILY: &str = "Pairtest Sans";
/// The family of [`no_pair_kerning_font`].
pub const NO_PAIR_KERNING_FAMILY: &str = "No Pairtest Sans";
/// A font with the bundled Source Sans 3 outlines and no `GPOS` or `kern` table.
pub fn no_pair_kerning_font() -> Option<Vec<u8>> {
const BASE: &[u8] = include_bytes!("../../../assets/fonts/SourceSans3-Regular.ttf");
let font = skrifa::FontRef::new(BASE).ok()?;
let family = NO_PAIR_KERNING_FAMILY;
let name = name_table(&[(1, family), (2, "Regular"), (4, "No Pairtest Sans Regular"), (6, "NoPairtestSans-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 !matches!(tag, t if t == Tag::new(b"name") || t == Tag::new(b"GPOS") || t == Tag::new(b"kern")) {
builder.add_raw(tag, font.table_data(r.tag())?.as_bytes());
}
}
Some(builder.build())
}
/// The family of [`right_to_left_optical_font`].
pub const RIGHT_TO_LEFT_OPTICAL_FAMILY: &str = "Right-to-left Profile Sans";
/// Two Hebrew characters mapped to the bundled Source Sans 3 A and V outlines.
pub fn right_to_left_optical_font() -> Option<Vec<u8>> {
remapped_source_sans(RIGHT_TO_LEFT_OPTICAL_FAMILY, "RtlProfileSans-Regular", &[('א', 'A'), ('ב', 'V')])
}
/// The family of [`ideograph_optical_font`].
pub const IDEOGRAPH_OPTICAL_FAMILY: &str = "Ideograph Profile Sans";
/// Two ideographs, two kana and a fullwidth letter mapped to the bundled Source Sans 3 A, V, T, o
/// and W outlines: shapes Optical would space, on characters that keep the font's spacing.
pub fn ideograph_optical_font() -> Option<Vec<u8>> {
remapped_source_sans(IDEOGRAPH_OPTICAL_FAMILY, "IdeographProfileSans-Regular", &[('日', 'A'), ('本', 'V'), ('カ', 'T'), ('な', 'o'), ('W', 'W')])
}
/// A face named `family` that maps only the characters of `mapping` to the bundled Source Sans 3
/// outlines of their letters, with no `GPOS` or `kern` table.
fn remapped_source_sans(family: &str, postscript: &str, mapping: &[(char, char)]) -> Option<Vec<u8>> {
use write_fonts::tables::cmap::Cmap;
const BASE: &[u8] = include_bytes!("../../../assets/fonts/SourceSans3-Regular.ttf");
let font = skrifa::FontRef::new(BASE).ok()?;
let charmap = font.charmap();
let mappings = mapping.iter().map(|&(c, source)| Some((c, write_fonts::types::GlyphId::new(charmap.map(source)?.to_u32()))));
let cmap = Cmap::from_mappings(mappings.collect::<Option<Vec<_>>>()?).ok()?;
let full = format!("{family} Regular");
let name = name_table(&[(1, family), (2, "Regular"), (4, full.as_str()), (6, postscript)])?;
let mut builder = FontBuilder::new();
builder.add_table(&cmap).ok()?;
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 tag != Tag::new(b"GPOS") && tag != Tag::new(b"kern") && !builder.contains(tag) {
builder.add_raw(tag, font.table_data(r.tag())?.as_bytes());
}
}
Some(builder.build())
}
/// The family of [`tall_ideograph_font`].
pub const TALL_IDEOGRAPH_FAMILY: &str = "Tall Ideograph Sans";
/// A face that maps the ideograph 编 to the bundled Source Sans 3 `H` outline, which stands on the
/// baseline, with a taller ascent (1,058 units per 1,000) and descent (262) than Source Sans 3 in
/// its `hhea` and `OS/2` tables and no line gap: the vertical proportions of a CJK system face.
pub fn tall_ideograph_font() -> Option<Vec<u8>> {
const ASCENT: i16 = 1058;
const DESCENT: i16 = 262;
let mut data = remapped_source_sans(TALL_IDEOGRAPH_FAMILY, "TallIdeographSans-Regular", &[('编', 'H')])?;
let tables: Vec<(Tag, usize)> = {
let font = skrifa::FontRef::new(&data).ok()?;
font.table_directory.table_records().iter().map(|r| (Tag::new(&r.tag().to_be_bytes()), r.offset() as usize)).collect()
};
let (ascent, descent, gap) = (ASCENT.to_be_bytes(), (-DESCENT).to_be_bytes(), [0, 0]);
for (tag, at) in tables {
if tag == Tag::new(b"hhea") {
// ascender, descender and lineGap at bytes 4 to 10.
data.get_mut(at + 4..at + 10)?.copy_from_slice(&[ascent, descent, gap].concat());
} else if tag == Tag::new(b"OS/2") {
// sTypoAscender, sTypoDescender, sTypoLineGap, usWinAscent and usWinDescent at bytes 68 to 78.
let win = [ASCENT.unsigned_abs().to_be_bytes(), DESCENT.unsigned_abs().to_be_bytes()];
data.get_mut(at + 68..at + 78)?.copy_from_slice(&[ascent, descent, gap, win[0], win[1]].concat());
}
}
Some(data)
}
/// A family of [`path_align_font`]: OS/2 cap height 660, H and O.
pub const PATH_ALIGN_ROMAN_FAMILY: &str = "Pathalign Roman Sans";
/// A family of [`path_align_font`]: no cap height, no H or O, and a `head` box above the baseline.
pub const PATH_ALIGN_BARE_FAMILY: &str = "Pathalign Bare Sans";
/// A family of [`path_align_font`]: a `BASE` table with `ideo` at -150 and no `idtp`.
pub const PATH_ALIGN_BASE_FAMILY: &str = "Pathalign Base Sans";
/// A family of [`path_align_font`]: Japanese code page, 永 on the ⌠ outline (ink -51 to 949).
pub const PATH_ALIGN_IDEOGRAPH_FAMILY: &str = "Pathalign Ideograph Sans";
/// A family of [`path_align_font`]: as [`PATH_ALIGN_IDEOGRAPH_FAMILY`], with ┼ on the | outline
/// (ink -250 to 750, one em).
pub const PATH_ALIGN_CROSS_FAMILY: &str = "Pathalign Cross Sans";
/// A family of [`path_align_font`]: Japanese code page, 永 on the H outline (ink 0 to 656), and a
/// typographic box from -100 to 900.
pub const PATH_ALIGN_TYPO_FAMILY: &str = "Pathalign Typo Sans";
/// A family of [`path_align_font`]: as [`PATH_ALIGN_TYPO_FAMILY`], with a typographic box of 0
/// to 0 and an `hhea` box from -100 to 900.
pub const PATH_ALIGN_HHEA_FAMILY: &str = "Pathalign Hhea Sans";
/// A family of [`path_align_font`]: as [`PATH_ALIGN_TYPO_FAMILY`], as a variable font with one
/// weight axis (400–700, named instances Regular and Bold) whose `MVAR` table raises the
/// typographic ascender and descender by 100 at 700: a box from 0 to 1,000 in Bold.
pub const PATH_ALIGN_VARIABLE_FAMILY: &str = "Pathalign Variable Sans";
/// A family of [`path_align_font`]: Japanese code page, with no reference ideograph and no ┼.
pub const PATH_ALIGN_NONE_FAMILY: &str = "Pathalign None Sans";
/// A family of [`path_align_font`]: no cap height, and H and O swapped (H top 668, O top 656).
pub const PATH_ALIGN_LOWER_FAMILY: &str = "Pathalign Lower Sans";
/// A family of [`path_align_font`]: OS/2 cap height 660, O and no H, and a `BASE` table as
/// [`PATH_ALIGN_BASE_FAMILY`]'s.
pub const PATH_ALIGN_CAPITAL_FAMILY: &str = "Pathalign Capital Sans";
/// A family of [`path_align_font`]: as [`PATH_ALIGN_IDEOGRAPH_FAMILY`], with a typographic box
/// from -101 to 1,002: margins of 53 and 50 round 永's ink.
pub const PATH_ALIGN_MARGINS_FAMILY: &str = "Pathalign Margins Sans";
/// A family of [`path_align_font`]: as [`PATH_ALIGN_IDEOGRAPH_FAMILY`], with a typographic box
/// from -101 to 1,003: margins of 54 and 50 round 永's ink.
pub const PATH_ALIGN_UNEVEN_FAMILY: &str = "Pathalign Uneven Sans";
/// A family of [`path_align_font`]: a `BASE` table with `ideo` at -150 and `romn` below it at
/// -200.
pub const PATH_ALIGN_DAMAGED_FAMILY: &str = "Pathalign Damaged Sans";
/// The bundled Source Sans 3 Regular (1,000 units to the em, typographic box -326 to 1,000, `head`
/// box bottom -295, H top 656, O top 668) without its `BASE` table, as one of the
/// `PATH_ALIGN_*_FAMILY` faces, which map x, and H and O except where the family says otherwise.
/// `None` when it can't be built.
pub fn path_align_font(family: &str) -> Option<Vec<u8>> {
use write_fonts::tables::base::{Axis, Base, BaseCoord, BaseScript, BaseScriptList, BaseScriptRecord, BaseTagList, BaseValues};
use write_fonts::tables::mvar::{Mvar, ValueRecord};
use write_fonts::tables::variations::{ItemVariationData, ItemVariationStore, RegionAxisCoordinates, VariationRegion, VariationRegionList};
use write_fonts::types::MajorMinor;
let typo = family == PATH_ALIGN_TYPO_FAMILY || family == PATH_ALIGN_VARIABLE_FAMILY;
let ideograph = [PATH_ALIGN_IDEOGRAPH_FAMILY, PATH_ALIGN_CROSS_FAMILY, PATH_ALIGN_MARGINS_FAMILY, PATH_ALIGN_UNEVEN_FAMILY].contains(&family);
let japanese = typo || ideograph || [PATH_ALIGN_HHEA_FAMILY, PATH_ALIGN_NONE_FAMILY].contains(&family);
let mut mapping = vec![('x', 'x')];
match family {
PATH_ALIGN_BARE_FAMILY => {}
PATH_ALIGN_LOWER_FAMILY => mapping.extend([('H', 'O'), ('O', 'H')]),
PATH_ALIGN_CAPITAL_FAMILY => mapping.push(('O', 'O')),
_ => mapping.extend([('H', 'H'), ('O', 'O')]),
}
if ideograph {
mapping.push(('永', '⌠'));
}
if family == PATH_ALIGN_CROSS_FAMILY {
mapping.push(('┼', '|'));
}
if typo || family == PATH_ALIGN_HHEA_FAMILY {
mapping.push(('永', 'H'));
}
let mut data = remapped_source_sans(family, &format!("{}-Regular", family.replace(' ', "")), &mapping)?;
let tables: Vec<(Tag, usize)> = {
let font = skrifa::FontRef::new(&data).ok()?;
font.table_directory.table_records().iter().map(|r| (Tag::new(&r.tag().to_be_bytes()), r.offset() as usize)).collect()
};
for (tag, at) in tables {
if tag == Tag::new(b"OS/2") {
if japanese {
// ulCodePageRange1 at bytes 78 to 82: bit 17, JIS/Japan.
data.get_mut(at + 78..at + 82)?.copy_from_slice(&(1_u32 << 17).to_be_bytes());
}
if typo {
// sTypoAscender and sTypoDescender at bytes 68 to 72.
data.get_mut(at + 68..at + 72)?.copy_from_slice(&[900_i16.to_be_bytes(), (-100_i16).to_be_bytes()].concat());
}
if family == PATH_ALIGN_HHEA_FAMILY {
data.get_mut(at + 68..at + 72)?.copy_from_slice(&[0, 0, 0, 0]);
}
if family == PATH_ALIGN_MARGINS_FAMILY || family == PATH_ALIGN_UNEVEN_FAMILY {
let top: i16 = if family == PATH_ALIGN_MARGINS_FAMILY { 1002 } else { 1003 };
data.get_mut(at + 68..at + 72)?.copy_from_slice(&[top.to_be_bytes(), (-101_i16).to_be_bytes()].concat());
}
if family == PATH_ALIGN_BARE_FAMILY || family == PATH_ALIGN_LOWER_FAMILY {
// sCapHeight at bytes 88 to 90.
data.get_mut(at + 88..at + 90)?.copy_from_slice(&0_i16.to_be_bytes());
}
} else if tag == Tag::new(b"hhea") && family == PATH_ALIGN_HHEA_FAMILY {
// ascender and descender at bytes 4 to 8.
data.get_mut(at + 4..at + 8)?.copy_from_slice(&[900_i16.to_be_bytes(), (-100_i16).to_be_bytes()].concat());
} else if tag == Tag::new(b"head") && family == PATH_ALIGN_BARE_FAMILY {
// yMin at bytes 38 to 40.
data.get_mut(at + 38..at + 40)?.copy_from_slice(&10_i16.to_be_bytes());
}
}
// Source Sans 3's own `BASE` table (`ideo` -170) is left out.
let font = skrifa::FontRef::new(&data).ok()?;
let mut builder = FontBuilder::new();
if [PATH_ALIGN_BASE_FAMILY, PATH_ALIGN_CAPITAL_FAMILY, PATH_ALIGN_DAMAGED_FAMILY].contains(&family) {
let romn = if family == PATH_ALIGN_DAMAGED_FAMILY { -200 } else { 0 };
let tags = BaseTagList::new([b"ideo", b"romn"].map(Tag::new).to_vec());
let values = BaseValues::new(1, [-150, romn].map(BaseCoord::format_1).to_vec());
let latn = BaseScriptRecord::new(Tag::new(b"latn"), BaseScript::new(Some(values), None, vec![]));
let axis = Axis::new(Some(tags), BaseScriptList::new(vec![latn]));
builder.add_table(&Base::new(Some(axis), None)).ok()?;
}
if family == PATH_ALIGN_VARIABLE_FAMILY {
let region = VariationRegion::new(vec![RegionAxisCoordinates::new(F2Dot14::from_f32(0.0), F2Dot14::from_f32(1.0), F2Dot14::from_f32(1.0))]);
// Two delta sets (hasc, hdsc) of one 16-bit delta each for the one region.
let data = ItemVariationData::new(2, 1, vec![0], [100_i16.to_be_bytes(), 100_i16.to_be_bytes()].concat());
let store = ItemVariationStore::new(VariationRegionList::new(1, vec![region]), vec![Some(data)]);
let records = vec![ValueRecord::new(Tag::new(b"hasc"), 0, 0), ValueRecord::new(Tag::new(b"hdsc"), 0, 1)];
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 fvar = Fvar::new(AxisInstanceArrays::new(vec![axis], vec![instance(257, 400), instance(258, 700)]));
let name = name_table(&[
(1, family),
(2, "Regular"),
(4, "Pathalign Variable Sans Regular"),
(6, "PathalignVariableSans-Regular"),
(256, "Weight"),
(257, "Regular"),
(258, "Bold"),
])?;
builder.add_raw(Tag::new(b"name"), name);
builder.add_table(&fvar).ok()?.add_table(&Mvar::new(MajorMinor::VERSION_1_0, Some(store), records)).ok()?;
}
for r in font.table_directory.table_records() {
let tag = Tag::new(&r.tag().to_be_bytes());
if !builder.contains(tag) && tag != Tag::new(b"BASE") {
builder.add_raw(tag, font.table_data(r.tag())?.as_bytes());
}
}
Some(builder.build())
}
/// The family of [`soft_hyphen_outline_font`].
pub const SOFT_HYPHEN_OUTLINE_FAMILY: &str = "Soft Hyphen Profile Sans";
/// A face that maps the invisible soft hyphen to the bundled hyphen outline.
pub fn soft_hyphen_outline_font() -> Option<Vec<u8>> {
use write_fonts::tables::cmap::Cmap;
const BASE: &[u8] = include_bytes!("../../../assets/fonts/SourceSans3-Regular.ttf");
let font = skrifa::FontRef::new(BASE).ok()?;
let charmap = font.charmap();
let mappings = [('A', 'A'), ('V', 'V'), ('-', '-'), ('\u{00AD}', '-')]
.into_iter()
.map(|(c, source)| Some((c, write_fonts::types::GlyphId::new(charmap.map(source)?.to_u32()))));
let cmap = Cmap::from_mappings(mappings.collect::<Option<Vec<_>>>()?).ok()?;
let family = SOFT_HYPHEN_OUTLINE_FAMILY;
let name = name_table(&[(1, family), (2, "Regular"), (4, "Soft Hyphen Profile Sans Regular"), (6, "SoftHyphenProfileSans-Regular")])?;
let mut builder = FontBuilder::new();
builder.add_table(&cmap).ok()?;
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 tag != Tag::new(b"GPOS") && tag != Tag::new(b"kern") && !builder.contains(tag) {
builder.add_raw(tag, font.table_data(r.tag())?.as_bytes());
}
}
Some(builder.build())
}
/// The bundled Source Sans 3 Regular as family [`PAIR_KERNING_FAMILY`], its `GPOS` replaced by two
/// `kern` pairs, A followed by V and S followed by S: each takes 60 units off the first glyph's
/// advance (the pair's first value record) and moves the second 100 units left (its second).
/// `None` when it can't be built.
pub fn pair_kerning_font() -> Option<Vec<u8>> {
use write_fonts::tables::gpos::{Gpos, PairPos, PairSet, PairValueRecord, PositionLookup, PositionLookupList, ValueRecord};
use write_fonts::tables::layout::{CoverageFormat1, Feature, FeatureList, FeatureRecord, LangSys, Lookup, Script, ScriptList, ScriptRecord};
use write_fonts::types::GlyphId16;
const BASE: &[u8] = include_bytes!("../../../assets/fonts/SourceSans3-Regular.ttf");
let font = skrifa::FontRef::new(BASE).ok()?;
let charmap = font.charmap();
let gid = |c| Some(GlyphId16::new(u16::try_from(charmap.map(c)?.to_u32()).ok()?));
let pair = |second| PairValueRecord::new(second, ValueRecord::new().with_x_advance(-60), ValueRecord::new().with_x_placement(-100));
let mut pairs = [(gid('A')?, gid('V')?), (gid('S')?, gid('S')?)];
pairs.sort_by_key(|p| p.0);
let coverage = CoverageFormat1::new(pairs.iter().map(|p| p.0).collect());
let pos = PairPos::format_1(coverage.into(), pairs.iter().map(|p| PairSet::new(vec![pair(p.1)])).collect());
let scripts = ScriptList::new(vec![ScriptRecord::new(Tag::new(b"DFLT"), Script::new(Some(LangSys::new(vec![0])), vec![]))]);
let features = FeatureList::new(vec![FeatureRecord::new(Tag::new(b"kern"), Feature::new(None, vec![0]))]);
let lookups = PositionLookupList::new(vec![PositionLookup::Pair(Lookup::new(Default::default(), vec![pos]))]);
let family = PAIR_KERNING_FAMILY;
let name = name_table(&[(1, family), (2, "Regular"), (4, "Pairtest Sans Regular"), (6, "PairtestSans-Regular")])?;
let mut builder = FontBuilder::new();
builder.add_table(&Gpos::new(scripts, features, lookups)).ok()?;
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 tag != Tag::new(b"kern") && !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<Vec<u8>> {
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())
}
/// The family of [`numbered_font`]: styles numbered W3 and W6, and no Regular.
pub const NUMBERED_FAMILY: &str = "Numtest Mincho";
/// A face of family [`NUMBERED_FAMILY`] in `style` (`W3` or `W6`; the bundled Source Sans 3
/// Regular renamed), as families whose styles are numbered have, with no Regular among them.
/// `None` when it can't be built.
pub fn numbered_font(style: &str) -> Option<Vec<u8>> {
const FACE: &[u8] = include_bytes!("../../../assets/fonts/SourceSans3-Regular.ttf");
let font = skrifa::FontRef::new(FACE).ok()?;
let full = format!("{NUMBERED_FAMILY} {style}");
let postscript = format!("NumtestMincho-{style}");
let name = name_table(&[(1, NUMBERED_FAMILY), (2, style), (4, &full), (5, "Version 1.000"), (6, &postscript)])?;
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())
}
/// The character whose glyph [`colour_bitmap_font`] draws as a colour bitmap.
pub const COLOUR_CHAR: char = 'E';
/// The colour of [`colour_bitmap_font`]'s bitmap in its largest strike (RGBA); the `sbix` font's
/// smaller strike is [`COLOUR_SMALL`].
pub const COLOUR_LARGE: [u8; 4] = [255, 0, 0, 255];
/// The colour of the `sbix` font's smaller strike.
pub const COLOUR_SMALL: [u8; 4] = [0, 0, 255, 255];
/// The pixels per em of [`colour_bitmap_font`]'s largest strike: its square bitmap is one em wide.
pub const COLOUR_PPEM: u8 = 16;
/// Where [`colour_bitmap_font`]`(false)`'s (`CBDT`) bitmap sits, in pixels: its left edge right of
/// the glyph's origin, its top edge above the baseline.
pub const COLOUR_CBDT_BEARINGS: (i8, i8) = (2, 12);
/// The family of [`colour_bitmap_font`] (`sbix`, or `CBDT`).
pub fn colour_bitmap_family(sbix: bool) -> &'static str {
if sbix { "Sbixtest Emoji" } else { "Cbdttest Emoji" }
}
/// The bundled Source Sans 3 Regular as family [`colour_bitmap_family`], [`COLOUR_CHAR`] a solid
/// square colour bitmap one em wide: in `sbix` strikes of [`COLOUR_PPEM`] ([`COLOUR_LARGE`]) and
/// half that ([`COLOUR_SMALL`]), each with no origin offset, or (`sbix` false) in one `CBDT`
/// strike of [`COLOUR_PPEM`] ([`COLOUR_LARGE`], bearings [`COLOUR_CBDT_BEARINGS`]). Its outline
/// stays too. Tables written from the OpenType specification. `None` when it can't be built.
pub fn colour_bitmap_font(sbix: bool) -> Option<Vec<u8>> {
colour_bitmap_font_with(sbix, &solid_png(COLOUR_PPEM.into(), COLOUR_LARGE))
}
/// [`colour_bitmap_font`] with `png` as the bitmap of its largest strike (a malformed one, say).
pub fn colour_bitmap_font_with(sbix: bool, png: &[u8]) -> Option<Vec<u8>> {
const BASE: &[u8] = include_bytes!("../../../assets/fonts/SourceSans3-Regular.ttf");
let font = skrifa::FontRef::new(BASE).ok()?;
let glyphs = font.maxp().ok()?.num_glyphs();
let gid = u16::try_from(font.charmap().map(COLOUR_CHAR)?.to_u32()).ok()?;
let family = colour_bitmap_family(sbix);
let ps = family.replace(' ', "");
let name = name_table(&[(1, family), (2, "Regular"), (4, &format!("{family} Regular")), (6, &format!("{ps}-Regular"))])?;
let mut builder = FontBuilder::new();
builder.add_raw(Tag::new(b"name"), name);
if sbix {
let small = solid_png((COLOUR_PPEM / 2).into(), COLOUR_SMALL);
builder.add_raw(Tag::new(b"sbix"), sbix_table(glyphs, gid, &[(COLOUR_PPEM / 2, &small), (COLOUR_PPEM, png)]));
} else {
let (cblc, cbdt) = cbdt_tables(gid, png);
builder.add_raw(Tag::new(b"CBLC"), cblc);
builder.add_raw(Tag::new(b"CBDT"), cbdt);
}
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())
}
/// An `sbix` table of `strikes` (ppem, PNG), each holding a PNG for glyph `gid` alone.
fn sbix_table(glyphs: u16, gid: u16, strikes: &[(u8, &[u8])]) -> Vec<u8> {
let mut out = Vec::new();
out.extend(1u16.to_be_bytes()); // version
out.extend(1u16.to_be_bytes()); // flags
out.extend((strikes.len() as u32).to_be_bytes());
let header = 8 + 4 * strikes.len();
let mut data = Vec::new();
for (ppem, png) in strikes {
out.extend(((header + data.len()) as u32).to_be_bytes());
let offsets = 4 + 4 * (usize::from(glyphs) + 1);
let mut strike = Vec::new();
strike.extend(u16::from(*ppem).to_be_bytes());
strike.extend(72u16.to_be_bytes()); // ppi
// Glyph data offsets: empty but for `gid`'s (origin offsets 0, `png `, the PNG).
let record = 8 + png.len();
for g in 0..=glyphs {
let at = offsets + if g > gid { record } else { 0 };
strike.extend((at as u32).to_be_bytes());
}
strike.extend([0, 0, 0, 0]);
strike.extend(b"png ");
strike.extend(*png);
data.extend(strike);
}
out.extend(data);
out
}
/// `CBLC` and `CBDT` tables of one strike of [`COLOUR_PPEM`] holding `png` for glyph `gid`
/// (index subtable format 1, glyph data format 17).
fn cbdt_tables(gid: u16, png: &[u8]) -> (Vec<u8>, Vec<u8>) {
let ppem = COLOUR_PPEM;
let mut cbdt = Vec::new();
cbdt.extend(3u16.to_be_bytes());
cbdt.extend(0u16.to_be_bytes());
// Small glyph metrics: height, width, bearing x, bearing y, advance.
let (bx, by) = COLOUR_CBDT_BEARINGS;
cbdt.extend([ppem, ppem, bx as u8, by as u8, ppem]);
cbdt.extend((png.len() as u32).to_be_bytes());
cbdt.extend(png);
let mut cblc = Vec::new();
cblc.extend(3u16.to_be_bytes());
cblc.extend(0u16.to_be_bytes());
cblc.extend(1u32.to_be_bytes()); // numSizes
let array = 8 + 48;
let subtable = 8;
let index_size = subtable + 8 + 2 * 4;
cblc.extend((array as u32).to_be_bytes());
cblc.extend((index_size as u32).to_be_bytes());
cblc.extend(1u32.to_be_bytes()); // numberOfIndexSubTables
cblc.extend(0u32.to_be_bytes()); // colorRef
// Horizontal and vertical line metrics.
let line = [ppem as i8, 0, ppem as i8, 1, 0, 0, 0, 0, 0, 0, 0, 0].map(|v| v as u8);
cblc.extend(line);
cblc.extend(line);
cblc.extend(gid.to_be_bytes());
cblc.extend(gid.to_be_bytes());
cblc.extend([ppem, ppem, 32, 1]); // ppem x, ppem y, bit depth, flags (horizontal)
// The index subtable array, then the subtable: format 1, image format 17, its data at 4.
cblc.extend(gid.to_be_bytes());
cblc.extend(gid.to_be_bytes());
cblc.extend((subtable as u32).to_be_bytes());
cblc.extend(1u16.to_be_bytes());
cblc.extend(17u16.to_be_bytes());
cblc.extend(4u32.to_be_bytes());
cblc.extend(0u32.to_be_bytes());
cblc.extend(((cbdt.len() - 4) as u32).to_be_bytes());
(cblc, cbdt)
}
/// A `side`×`side` PNG of one colour (RGBA), written uncompressed (stored deflate blocks).
pub fn solid_png(side: u32, rgba: [u8; 4]) -> Vec<u8> {
fn crc32(bytes: &[u8]) -> u32 {
let mut c = !0u32;
for b in bytes {
c ^= u32::from(*b);
for _ in 0..8 {
c = if c & 1 != 0 { 0xedb8_8320 ^ (c >> 1) } else { c >> 1 };
}
}
!c
}
fn chunk(out: &mut Vec<u8>, kind: &[u8; 4], data: &[u8]) {
out.extend((data.len() as u32).to_be_bytes());
let mut body = kind.to_vec();
body.extend(data);
out.extend(&body);
out.extend(crc32(&body).to_be_bytes());
}
// Each row: filter type 0 (none), then its pixels.
let row: Vec<u8> = std::iter::once(0).chain(rgba.repeat(side as usize)).collect();
let raw = row.repeat(side as usize);
let (mut a, mut b) = (1u32, 0u32);
for v in &raw {
a = (a + u32::from(*v)) % 65521;
b = (b + a) % 65521;
}
let mut zlib = vec![0x78, 0x01];
let blocks: Vec<&[u8]> = raw.chunks(0xffff).collect();
for (i, block) in blocks.iter().enumerate() {
zlib.push(u8::from(i + 1 == blocks.len()));
let n = block.len() as u16;
zlib.extend(n.to_le_bytes());
zlib.extend((!n).to_le_bytes());
zlib.extend(*block);
}
zlib.extend(((b << 16) | a).to_be_bytes());
let mut out = b"\x89PNG\r\n\x1a\n".to_vec();
let mut ihdr = Vec::new();
ihdr.extend(side.to_be_bytes());
ihdr.extend(side.to_be_bytes());
ihdr.extend([8, 6, 0, 0, 0]); // 8 bits, RGBA
chunk(&mut out, b"IHDR", &ihdr);
chunk(&mut out, b"IDAT", &zlib);
chunk(&mut out, b"IEND", &[]);
out
}
/// The palette of [`colr_font`] (palette 0, RGBA): its indices 0, 1 and 2.
pub const COLR_RED: [u8; 4] = [255, 0, 0, 255];
pub const COLR_BLUE: [u8; 4] = [0, 0, 255, 255];
pub const COLR_GREEN: [u8; 4] = [0, 255, 0, 255];
/// The character whose glyph [`colr_font`]`(true)` paints with a sweep gradient.
pub const COLR_SWEEP_CHAR: char = 'S';
/// The family of [`colr_font`] (COLRv1, or v0).
pub fn colr_family(v1: bool) -> &'static str {
if v1 { "Colrtest Paints" } else { "Colrtest Layers" }
}
/// The glyph ids of `chars` in the bundled Source Sans 3.
fn source_sans_gids<const N: usize>(chars: [char; N]) -> Option<[u16; N]> {
let font = skrifa::FontRef::new(SOURCE_SANS).ok()?;
let mut gids = [0; N];
for (gid, ch) in gids.iter_mut().zip(chars) {
*gid = u16::try_from(font.charmap().map(ch)?.to_u32()).ok()?;
}
Some(gids)
}
const SOURCE_SANS: &[u8] = include_bytes!("../../../assets/fonts/SourceSans3-Regular.ttf");
/// The bundled Source Sans 3 Regular as family [`colr_family`], with `COLR` and `CPAL` tables
/// written from the OpenType specification, palette 0 [`COLR_RED`], [`COLR_BLUE`], [`COLR_GREEN`].
/// v0: [`COLOUR_CHAR`] is the outline of `O` in red, then that of `I` in the foreground colour
/// (palette index 0xFFFF). v1: [`COLOUR_CHAR`] is, in a clip box, `O` filled with a linear
/// gradient red → blue, `I` scaled by a half about the origin with a radial gradient blue → green,
/// and `-` in green composited with Multiply over `-` in red; [`COLR_SWEEP_CHAR`] is `O` with a
/// sweep gradient red → blue. Their outlines stay. `None` when it can't be built.
pub fn colr_font(v1: bool) -> Option<Vec<u8>> {
use write_fonts::tables::colr::{
BaseGlyph, BaseGlyphList, BaseGlyphPaint, Clip, ClipBox, ClipList, ColorLine, ColorStop, Colr, CompositeMode, Extend, Layer, LayerList, Paint,
};
use write_fonts::types::{FWord, GlyphId16, UfWord};
let [base, sweep, o, i, dash] = source_sans_gids([COLOUR_CHAR, COLR_SWEEP_CHAR, 'O', 'I', '-'])?;
let (base, sweep, o, i, dash) = (GlyphId16::new(base), GlyphId16::new(sweep), GlyphId16::new(o), GlyphId16::new(i), GlyphId16::new(dash));
let colr = if v1 {
let one = F2Dot14::from_f32(1.0);
let line = |from: u16, to: u16| {
ColorLine::new(Extend::Pad, 2, vec![ColorStop::new(F2Dot14::from_f32(0.0), from, one), ColorStop::new(one, to, one)])
};
let w = FWord::new;
let layers = vec![
Paint::glyph(Paint::linear_gradient(line(0, 1), w(0), w(0), w(500), w(0), w(0), w(500)), o),
Paint::scale_uniform(
Paint::glyph(Paint::radial_gradient(line(1, 2), w(250), w(300), UfWord::new(0), w(250), w(300), UfWord::new(300)), i),
F2Dot14::from_f32(0.5),
),
Paint::composite(Paint::glyph(Paint::solid(2, one), dash), CompositeMode::Multiply, Paint::glyph(Paint::solid(0, one), dash)),
];
let mut colr = Colr::new(0, None, None, 0);
colr.layer_list = Some(LayerList::new(layers.len() as u32, layers)).into();
let mut glyphs = vec![
BaseGlyphPaint::new(base, Paint::colr_layers(3, 0)),
BaseGlyphPaint::new(sweep, Paint::glyph(Paint::sweep_gradient(line(0, 1), w(250), w(300), F2Dot14::from_f32(0.0), one), o)),
];
glyphs.sort_by_key(|g| g.glyph_id);
colr.base_glyph_list = Some(BaseGlyphList::new(glyphs.len() as u32, glyphs)).into();
let clip = ClipBox::format_1(w(-50), w(-250), w(750), w(800));
colr.clip_list = Some(ClipList::new(1, 1, vec![Clip::new(base, base, clip)])).into();
colr
} else {
Colr::new(1, Some(vec![BaseGlyph::new(base, 0, 2)]), Some(vec![Layer::new(o, 0), Layer::new(i, 0xFFFF)]), 2)
};
colr_font_with(colr_family(v1), &colr)
}
/// The bundled Source Sans 3 Regular as `family` with `colr` and [`colr_font`]'s palette.
pub fn colr_font_with(family: &str, colr: &write_fonts::tables::colr::Colr) -> Option<Vec<u8>> {
with_colr(SOURCE_SANS, family_names(family, &[])?, colr)
}
/// The family of [`colr_variable_font`].
pub const COLR_VARIABLE_FAMILY: &str = "Colrtest Variable";
/// [`variable_font`] as family [`COLR_VARIABLE_FAMILY`], with a `COLR` (v0) table in which
/// [`COLOUR_CHAR`] is the outline of `l` (one of [`VARIABLE_CHARS`]) in red, so its layer widens
/// with the weight. `None` when it can't be built.
pub fn colr_variable_font() -> Option<Vec<u8>> {
use write_fonts::tables::colr::{BaseGlyph, Colr, Layer};
use write_fonts::types::GlyphId16;
let [base, l] = source_sans_gids([COLOUR_CHAR, 'l'])?;
let colr = Colr::new(1, Some(vec![BaseGlyph::new(GlyphId16::new(base), 0, 1)]), Some(vec![Layer::new(GlyphId16::new(l), 0)]), 1);
let axis_names = [(256, "Weight"), (257, "Normal"), (258, "SemiBold"), (259, "Bold")];
with_colr(&variable_font()?, family_names(COLR_VARIABLE_FAMILY, &axis_names)?, &colr)
}
/// A `name` table for Regular of `family`, with `more` records.
fn family_names(family: &str, more: &[(u16, &str)]) -> Option<Vec<u8>> {
let ps = family.replace(' ', "");
let (full, postscript) = (format!("{family} Regular"), format!("{ps}-Regular"));
let mut records = vec![(1, family), (2, "Regular"), (4, full.as_str()), (6, postscript.as_str())];
records.extend_from_slice(more);
name_table(&records)
}
/// Font `base` with the `name` table `name`, `colr` and [`colr_font`]'s palette.
fn with_colr(base: &[u8], name: Vec<u8>, colr: &write_fonts::tables::colr::Colr) -> Option<Vec<u8>> {
use write_fonts::tables::cpal::{ColorRecord, Cpal};
let font = skrifa::FontRef::new(base).ok()?;
let records = [COLR_RED, COLR_BLUE, COLR_GREEN].map(|[r, g, b, a]| ColorRecord::new(b, g, r, a)).to_vec();
let cpal = Cpal::new(3, 1, 3, Some(records), vec![0]);
let mut builder = FontBuilder::new();
builder.add_raw(Tag::new(b"name"), name);
builder.add_table(colr).ok()?;
builder.add_table(&cpal).ok()?;
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 character whose glyph [`svg_font`]'s first document draws.
pub const SVG_CHAR: char = 'E';
/// The glyph id of `ch` in the bundled Source Sans 3 (and the fonts made from it).
pub fn source_sans_gid(ch: char) -> Option<u16> {
source_sans_gids([ch]).map(|[g]| g)
}
/// The bundled Source Sans 3 Regular as `family` with an `SVG ` table written from the OpenType
/// specification: `documents` are (first glyph, last glyph, SVG document), in glyph order. Its
/// outlines stay. `None` when it can't be built.
pub fn svg_font(family: &str, documents: &[(u16, u16, &str)]) -> Option<Vec<u8>> {
let font = skrifa::FontRef::new(SOURCE_SANS).ok()?;
let ps = family.replace(' ', "");
let name = name_table(&[(1, family), (2, "Regular"), (4, &format!("{family} Regular")), (6, &format!("{ps}-Regular"))])?;
// The header (version, the document list's offset, reserved), then the document list: its
// records (first and last glyph, the document's offset from the list and length), then the
// documents.
let records = documents.len();
let mut list = (u16::try_from(records).ok()?).to_be_bytes().to_vec();
let mut offset = 2 + 12 * records;
for (first, last, doc) in documents {
list.extend(first.to_be_bytes());
list.extend(last.to_be_bytes());
list.extend((u32::try_from(offset).ok()?).to_be_bytes());
list.extend((u32::try_from(doc.len()).ok()?).to_be_bytes());
offset += doc.len();
}
for (_, _, doc) in documents {
list.extend(doc.as_bytes());
}
let mut svg = vec![0, 0, 0, 0, 0, 10, 0, 0, 0, 0];
svg.extend(list);
let mut builder = FontBuilder::new();
builder.add_raw(Tag::new(b"name"), name);
builder.add_raw(Tag::new(b"SVG "), svg);
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())
}