mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-28 11:47:21 -06:00
A variable font's COLR layers follow its instance, a colour glyph turns with type on a path, and the canvas draws an SVG glyph's art in its own colours and the type's fill.
751 lines
37 KiB
Rust
751 lines
37 KiB
Rust
//! 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<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"),
|
||
(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 [`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 [`pair_kerning_font`].
|
||
pub const PAIR_KERNING_FAMILY: &str = "Pairtest Sans";
|
||
|
||
/// 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 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())
|
||
}
|