use std::{
collections::{BTreeMap, HashMap},
fmt::Display,
fs::File,
io::{self, BufReader, BufWriter, Read, Write},
path::{Path, PathBuf},
sync::Arc,
};
use fea_rs::{
GlyphMap, GlyphSet, ParseTree,
compile::{Compilation, FeatureKey, PendingLookup},
};
use fontdrasil::{
coords::NormalizedLocation,
orchestration::{Access, AccessControlList, Identifier, IdentifierDiscriminant, Work},
types::GlyphName,
variations::VariationRegion,
};
use fontir::{
ir::{self, GlyphOrder, KernGroup},
orchestration::{
Context as FeContext, ContextItem, ContextMap, Flags, IdAware, Persistable,
PersistentStorage, WorkId as FeWorkIdentifier,
},
};
use ordered_float::OrderedFloat;
use serde::{Deserialize, Serialize};
use write_fonts::{
FontWrite, dump_table,
read::{FontRead, collections::IntSet, tables::gsub::Gsub as ReadGsub},
tables::{
base::Base,
cmap::Cmap,
colr::Colr,
cpal::Cpal,
fvar::Fvar,
gasp::Gasp,
gdef::{Gdef, GlyphClassDef},
glyf::Glyph as RawGlyph,
gpos::{
Gpos,
builders::{
CursivePosBuilder, MarkToBaseBuilder, MarkToLigBuilder, MarkToMarkBuilder,
PairPosBuilder, ValueRecordBuilder,
},
},
gsub::Gsub,
gvar::{GlyphDelta, GlyphDeltas},
head::Head,
hhea::Hhea,
hvar::Hvar,
layout::builders::CaretValueBuilder,
loca::LocaFormat,
maxp::Maxp,
meta::Meta,
mvar::Mvar,
name::Name,
os2::Os2,
post::Post,
stat::Stat,
vhea::Vhea,
vvar::Vvar,
},
types::{GlyphId16, Tag},
validate::Validate,
};
use crate::{avar::PossiblyEmptyAvar, error::Error, paths::Paths};
type KernBlock = usize;
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum WorkId {
Features,
FeaturesAst,
Avar,
Cmap,
Colr,
Cpal,
Font,
Fvar,
Gasp,
Glyf,
GlyfFragment(GlyphName),
Gpos,
Gsub,
Gdef,
Gvar,
GvarFragment(GlyphName),
Head,
Hhea,
Hmtx,
Hvar,
Meta,
Vhea,
Vmtx,
Vvar,
GatherIrKerning,
KernFragment(KernBlock),
GatherBeKerning,
Loca,
LocaFormat,
Marks,
Maxp,
Mvar,
Name,
Os2,
Post,
Stat,
ExtraFeaTables,
}
impl WorkId {
pub const ALL_GLYF_FRAGMENTS: WorkId = WorkId::GlyfFragment(GlyphName::NOTDEF);
pub const ALL_GVAR_FRAGMENTS: WorkId = WorkId::GvarFragment(GlyphName::NOTDEF);
}
impl Identifier for WorkId {
fn discriminant(&self) -> IdentifierDiscriminant {
match self {
WorkId::Features => "BeFeatures",
WorkId::Meta => "BeMeta",
WorkId::FeaturesAst => "BeFeaturesAst",
WorkId::Avar => "BeAvar",
WorkId::Cmap => "BeCmap",
WorkId::Colr => "BeColr",
WorkId::Cpal => "BeCpal",
WorkId::Font => "BeFont",
WorkId::Fvar => "BeFvar",
WorkId::Gasp => "BeGasp",
WorkId::Glyf => "BeGlyf",
WorkId::GlyfFragment(..) => "BeGlyfFragment",
WorkId::Gpos => "BeGpos",
WorkId::Gsub => "BeGsub",
WorkId::Gdef => "BeGdef",
WorkId::Gvar => "BeGvar",
WorkId::GvarFragment(..) => "BeGvarFragment",
WorkId::Head => "BeHead",
WorkId::Hhea => "BeHhea",
WorkId::Hmtx => "BeHmtx",
WorkId::Hvar => "BeHvar",
WorkId::GatherIrKerning => "BeGatherIr",
WorkId::KernFragment(..) => "BeKernFragment",
WorkId::GatherBeKerning => "BeGatherKernFragments",
WorkId::Loca => "BeLoca",
WorkId::LocaFormat => "BeLocaFormat",
WorkId::Marks => "BeMarks",
WorkId::Maxp => "BeMaxp",
WorkId::Mvar => "BeMvar",
WorkId::Name => "BeName",
WorkId::Os2 => "BeOs2",
WorkId::Post => "BePost",
WorkId::Stat => "BeStat",
WorkId::Vhea => "BeVhea",
WorkId::Vmtx => "BeVmtx",
WorkId::Vvar => "BeVvar",
WorkId::ExtraFeaTables => "ExtraFeaTables",
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum AnyWorkId {
Fe(FeWorkIdentifier),
Be(WorkId),
InternalTiming(&'static str),
}
impl Identifier for AnyWorkId {
fn discriminant(&self) -> IdentifierDiscriminant {
match self {
AnyWorkId::Fe(id) => id.discriminant(),
AnyWorkId::Be(id) => id.discriminant(),
AnyWorkId::InternalTiming(..) => "InternalTiming",
}
}
}
impl AnyWorkId {
pub fn unwrap_be(&self) -> &WorkId {
match self {
AnyWorkId::Be(id) => id,
_ => panic!("Not a BE identifier"),
}
}
pub fn unwrap_fe(&self) -> &FeWorkIdentifier {
match self {
AnyWorkId::Fe(id) => id,
_ => panic!("Not a FE identifier"),
}
}
}
impl From<FeWorkIdentifier> for AnyWorkId {
fn from(id: FeWorkIdentifier) -> Self {
AnyWorkId::Fe(id)
}
}
impl From<WorkId> for AnyWorkId {
fn from(id: WorkId) -> Self {
AnyWorkId::Be(id)
}
}
#[derive(Serialize, Deserialize, Debug, PartialEq)]
pub struct ExtraFeaTables {
pub name: Option<Name>,
pub head: Option<Head>,
pub hhea: Option<Hhea>,
pub vhea: Option<Vhea>,
pub os2: Option<Os2>,
pub base: Option<Base>,
pub stat: Option<Stat>,
}
impl From<Compilation> for ExtraFeaTables {
fn from(src: Compilation) -> ExtraFeaTables {
let Compilation {
head,
hhea,
vhea,
os2,
base,
name,
stat,
..
} = src;
ExtraFeaTables {
head,
hhea,
vhea,
os2,
base,
stat,
name,
}
}
}
impl ExtraFeaTables {
pub(crate) fn log_unhandled_extras(&self) {
for (name, unhandled) in [
("head", self.head.is_some()),
("hhea", self.hhea.is_some()),
("vhea", self.vhea.is_some()),
("os2", self.os2.is_some()),
("base", self.base.is_some()),
("stat", self.stat.is_some()),
] {
if unhandled {
log::warn!("FEA generated unused table '{name}'");
}
}
}
}
impl Persistable for ExtraFeaTables {
fn read(from: &mut dyn Read) -> Self {
fn read_table<'a, T: FontRead<'a>>(bytes: Option<&'a Vec<u8>>) -> Option<T> {
let bytes = bytes?.as_slice();
Some(T::read(bytes.into()).unwrap())
}
let [head, hhea, vhea, os2, base, stat, name]: [Option<Vec<u8>>; 7] =
bincode::deserialize_from(from).unwrap();
Self {
head: read_table(head.as_ref()),
hhea: read_table(hhea.as_ref()),
vhea: read_table(vhea.as_ref()),
os2: read_table(os2.as_ref()),
base: read_table(base.as_ref()),
stat: read_table(stat.as_ref()),
name: read_table(name.as_ref()),
}
}
fn write(&self, to: &mut dyn Write) {
fn dump<T: Validate + FontWrite>(table: Option<&T>) -> Option<Vec<u8>> {
table.map(write_fonts::dump_table).transpose().unwrap()
}
let ExtraFeaTables {
head,
hhea,
vhea,
os2,
base,
stat,
name,
} = self;
let out = [
dump(head.as_ref()),
dump(hhea.as_ref()),
dump(vhea.as_ref()),
dump(os2.as_ref()),
dump(base.as_ref()),
dump(stat.as_ref()),
dump(name.as_ref()),
];
bincode::serialize_into(to, &out).unwrap()
}
}
#[derive(Debug, Clone)]
pub struct Glyph {
pub name: GlyphName,
pub data: RawGlyph,
}
impl Glyph {
pub(crate) fn new(name: GlyphName, glyph: impl Into<RawGlyph>) -> Self {
Self {
name,
data: glyph.into(),
}
}
pub fn is_simple(&self) -> bool {
matches!(&self.data, RawGlyph::Simple(_))
}
pub fn is_composite(&self) -> bool {
matches!(&self.data, RawGlyph::Composite(_))
}
pub fn to_bytes(&self) -> Vec<u8> {
dump_table(&self.data).unwrap()
}
}
impl IdAware<AnyWorkId> for Glyph {
fn id(&self) -> AnyWorkId {
AnyWorkId::Be(WorkId::GlyfFragment(self.name.clone()))
}
}
impl Persistable for Glyph {
fn read(from: &mut dyn Read) -> Self {
let (name, bytes): (GlyphName, Vec<u8>) = bincode::deserialize_from(from).unwrap();
let glyph = FontRead::read(bytes.as_slice().into()).unwrap();
Glyph { name, data: glyph }
}
fn write(&self, to: &mut dyn Write) {
let glyph_bytes = dump_table(&self.data).unwrap();
let to_write = (&self.name, glyph_bytes);
bincode::serialize_into(to, &to_write).unwrap();
}
}
#[derive(Serialize, Deserialize, Debug)]
pub struct GvarFragment {
pub glyph_name: GlyphName,
pub deltas: Vec<(VariationRegion, Vec<GlyphDelta>)>,
}
impl GvarFragment {
pub fn to_deltas(&self, axis_order: &[Tag]) -> Vec<GlyphDeltas> {
self.deltas
.iter()
.filter_map(|(region, deltas)| {
if region.is_default() {
return None;
}
if deltas.is_empty() || deltas.iter().all(|d| !d.required) {
return None;
}
let coordinates = axis_order
.iter()
.map(|axis| *region.get(axis).unwrap())
.map(Into::into)
.collect();
Some(GlyphDeltas::new(coordinates, deltas.clone()))
})
.collect()
}
}
impl IdAware<AnyWorkId> for GvarFragment {
fn id(&self) -> AnyWorkId {
AnyWorkId::Be(WorkId::GvarFragment(self.glyph_name.clone()))
}
}
impl Persistable for GvarFragment {
fn read(from: &mut dyn Read) -> Self {
bincode::deserialize_from(from).unwrap()
}
fn write(&self, to: &mut dyn io::Write) {
bincode::serialize_into(to, &self).unwrap();
}
}
#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct MarkLookups {
pub(crate) mark_base: Vec<PendingLookup<MarkToBaseBuilder>>,
pub(crate) mark_mark: Vec<PendingLookup<MarkToMarkBuilder>>,
pub(crate) mark_lig: Vec<PendingLookup<MarkToLigBuilder>>,
}
#[derive(Default, Clone, Serialize, Deserialize, PartialEq)]
pub struct FeaRsMarks {
pub(crate) glyphmap: GlyphMap,
pub(crate) mark_mkmk: MarkLookups,
pub(crate) abvm: MarkLookups,
pub(crate) blwm: MarkLookups,
pub(crate) curs: Vec<PendingLookup<CursivePosBuilder>>,
pub(crate) lig_carets: BTreeMap<GlyphId16, Vec<CaretValueBuilder>>,
}
impl Persistable for FeaRsMarks {
fn read(from: &mut dyn Read) -> Self {
bincode::deserialize_from(from).unwrap()
}
fn write(&self, to: &mut dyn io::Write) {
bincode::serialize_into(to, self).unwrap()
}
}
#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)]
pub struct FeaRsKerns {
pub lookups: Vec<PendingLookup<PairPosBuilder>>,
pub features: BTreeMap<FeatureKey, Vec<usize>>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct FeaFirstPassOutput {
pub ast: ParseTree,
gsub_bytes: Option<Vec<u8>>,
pub(crate) gdef_classes: Option<HashMap<GlyphId16, GlyphClassDef>>,
}
impl FeaFirstPassOutput {
pub fn new(ast: ParseTree, compilation: Compilation) -> Result<Self, Error> {
let gsub_bytes = compilation
.gsub
.as_ref()
.map(write_fonts::dump_table)
.transpose()
.map_err(|e| Error::DumpTableError {
e,
context: "GSUB".into(),
})?;
Ok(Self {
ast,
gsub_bytes,
gdef_classes: compilation.gdef_classes,
})
}
#[cfg(test)]
pub(crate) fn for_test(ast: ParseTree, glyph_map: &GlyphMap) -> Result<Self, Error> {
use fea_rs::{
Opts,
compile::{NopFeatureProvider, NopVariationInfo},
};
match fea_rs::compile::compile::<NopVariationInfo, NopFeatureProvider>(
&ast,
glyph_map,
None,
None,
Opts::new().compile_gpos(false),
) {
Ok((compilation, _)) => Self::new(ast, compilation),
Err(err) => panic!("{}", err.display()),
}
}
pub fn gsub(&self) -> Option<ReadGsub<'_>> {
self.gsub_bytes
.as_ref()
.and_then(|bytes| ReadGsub::read(bytes.as_slice().into()).ok())
}
}
impl FeaRsKerns {
pub fn is_empty(&self) -> bool {
self.lookups.is_empty()
}
#[cfg(test)]
pub(crate) fn lookups_for_feature(
&self,
feature: &FeatureKey,
) -> Vec<&PendingLookup<PairPosBuilder>> {
self.features
.get(feature)
.into_iter()
.flatten()
.map(|id| &self.lookups[*id])
.collect()
}
}
impl Persistable for FeaRsKerns {
fn read(from: &mut dyn Read) -> Self {
bincode::deserialize_from(from).unwrap()
}
fn write(&self, to: &mut dyn io::Write) {
bincode::serialize_into(to, self).unwrap()
}
}
impl Persistable for FeaFirstPassOutput {
fn read(from: &mut dyn Read) -> Self {
bincode::deserialize_from(from).unwrap()
}
fn write(&self, to: &mut dyn Write) {
bincode::serialize_into(to, self).unwrap()
}
}
pub type KernAdjustments = BTreeMap<NormalizedLocation, OrderedFloat<f64>>;
#[derive(Default, Clone, Serialize, Deserialize, PartialEq)]
pub struct AllKerningPairs {
pub groups: BTreeMap<KernGroup, GlyphSet>,
pub adjustments: Vec<(ir::KernPair, KernAdjustments)>,
}
impl Persistable for AllKerningPairs {
fn read(from: &mut dyn Read) -> Self {
bincode::deserialize_from(from).unwrap()
}
fn write(&self, to: &mut dyn io::Write) {
bincode::serialize_into(to, self).unwrap()
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, PartialOrd, Eq, Ord)]
pub(crate) enum KernSide {
Glyph(GlyphId16),
Group(GlyphSet),
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, PartialOrd, Eq, Ord)]
pub(crate) struct KernPair {
pub(crate) side1: KernSide,
pub(crate) side2: KernSide,
pub(crate) value: ValueRecordBuilder,
}
impl KernSide {
pub(crate) const fn empty() -> Self {
Self::Group(IntSet::empty())
}
pub(crate) fn is_empty(&self) -> bool {
matches!(self, KernSide::Group(items) if items.is_empty())
}
pub(crate) fn is_group(&self) -> bool {
matches!(self, KernSide::Group(_))
}
pub(crate) fn from_ir_side(
ir: &ir::KernSide,
glyphs: &GlyphOrder,
groups: &BTreeMap<KernGroup, GlyphSet>,
) -> Option<Self> {
match ir {
ir::KernSide::Glyph(name) => glyphs.glyph_id(name).map(Self::Glyph),
ir::KernSide::Group(name) => groups.get(name).cloned().map(Self::Group),
}
}
fn contains(&self, gid: u16) -> bool {
let gid = GlyphId16::new(gid);
match self {
KernSide::Glyph(glyph_id16) => glyph_id16 == &gid,
KernSide::Group(glyph_set) => glyph_set.contains(gid),
}
}
pub(crate) fn iter(&self) -> impl Iterator<Item = GlyphId16> + '_ {
let (first, second) = match self {
Self::Glyph(gid) => (Some(*gid), None),
Self::Group(group) => (None, Some(group)),
};
first
.into_iter()
.chain(second.into_iter().flat_map(|set| set.iter()))
}
}
impl Display for KernSide {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
KernSide::Glyph(gid) => write!(f, "{gid}"),
KernSide::Group(group) => {
let mut first = true;
write!(f, "[")?;
for gid in group.iter() {
if !first {
write!(f, ", ")?;
}
write!(f, "{gid}")?;
first = false;
}
write!(f, "]")
}
}
}
}
impl KernPair {
pub(crate) fn make_rtl_compatible(&mut self) {
self.value.make_rtl_compatible()
}
#[allow(dead_code)] pub(crate) fn contains(&self, gid: u16) -> bool {
self.side1.contains(gid) || self.side2.contains(gid)
}
pub(crate) fn add_to(self, builder: &mut PairPosBuilder) {
match (self.side1, self.side2) {
(KernSide::Glyph(side1), KernSide::Glyph(side2)) => {
builder.insert_pair(side1, self.value, side2, Default::default());
}
(KernSide::Group(side1), KernSide::Group(side2)) => {
builder.insert_classes(side1, self.value, side2, Default::default());
}
(KernSide::Glyph(side1), KernSide::Group(side2)) => {
for side2 in side2.iter() {
builder.insert_pair(side1, self.value.clone(), side2, Default::default());
}
}
(KernSide::Group(side1), KernSide::Glyph(side2)) => {
for side1 in side1.iter() {
builder.insert_pair(side1, self.value.clone(), side2, Default::default());
}
}
}
}
pub(crate) fn glyphs_are_disjoint(&self, glyphs: &IntSet<GlyphId16>) -> bool {
self.first_glyphs()
.chain(self.second_glyphs())
.all(|gid| !glyphs.contains(gid))
}
pub(crate) fn with_new_glyphs(&self, side1: GlyphSet, side2: GlyphSet) -> Self {
let (side1, side2) = match (&self.side1, &self.side2) {
(KernSide::Glyph(_), KernSide::Glyph(_)) => (self.side1.clone(), self.side2.clone()),
(KernSide::Glyph(_), KernSide::Group(_)) => {
assert_eq!(side1.len(), 1);
(self.side1.clone(), KernSide::Group(side2))
}
(KernSide::Group(_), KernSide::Glyph(_)) => {
assert_eq!(side2.len(), 1);
(KernSide::Group(side1), self.side2.clone())
}
_ => {
assert!(!side1.is_empty() && !side2.is_empty());
(KernSide::Group(side1), KernSide::Group(side2))
}
};
KernPair {
side1,
side2,
value: self.value.clone(),
}
}
pub(crate) fn first_glyphs(&self) -> impl Iterator<Item = GlyphId16> + '_ {
self.side1.iter()
}
pub(crate) fn second_glyphs(&self) -> impl Iterator<Item = GlyphId16> + '_ {
self.side2.iter()
}
}
#[derive(Default, Clone, Serialize, Deserialize, PartialEq)]
pub struct KernFragment {
pub(crate) segment: usize,
pub(crate) kerns: Vec<KernPair>,
}
impl IdAware<AnyWorkId> for KernFragment {
fn id(&self) -> AnyWorkId {
AnyWorkId::Be(WorkId::KernFragment(self.segment))
}
}
impl Persistable for KernFragment {
fn read(from: &mut dyn Read) -> Self {
bincode::deserialize_from(from).unwrap()
}
fn write(&self, to: &mut dyn io::Write) {
bincode::serialize_into(to, self).unwrap()
}
}
#[derive(Clone, Copy, Serialize, Deserialize, PartialEq)]
pub struct LocaFormatWrapper(u8);
impl From<LocaFormat> for LocaFormatWrapper {
fn from(value: LocaFormat) -> Self {
LocaFormatWrapper(value as _)
}
}
impl From<LocaFormatWrapper> for LocaFormat {
fn from(value: LocaFormatWrapper) -> Self {
if value.0 == 0 {
LocaFormat::Short
} else {
LocaFormat::Long
}
}
}
impl Persistable for LocaFormatWrapper {
fn read(from: &mut dyn Read) -> Self {
bincode::deserialize_from(from).unwrap()
}
fn write(&self, to: &mut dyn io::Write) {
bincode::serialize_into(to, self).unwrap()
}
}
pub type BeWork = dyn Work<Context, AnyWorkId, Error> + Send;
pub struct BePersistentStorage {
active: bool,
pub(crate) paths: Paths,
}
impl PersistentStorage<AnyWorkId> for BePersistentStorage {
fn active(&self) -> bool {
self.active
}
fn reader(&self, id: &AnyWorkId) -> Option<Box<dyn Read>> {
let file = self.paths.target_file(id.unwrap_be());
if !file.exists() {
return None;
}
let raw_file = File::open(file.clone())
.map_err(|e| panic!("Unable to write {file:?} {e}"))
.unwrap();
Some(Box::from(BufReader::new(raw_file)))
}
fn writer(&self, id: &AnyWorkId) -> Box<dyn io::Write> {
let file = self.paths.target_file(id.unwrap_be());
let raw_file = File::create(file.clone())
.map_err(|e| panic!("Unable to write {file:?} {e}"))
.unwrap();
Box::from(BufWriter::new(raw_file))
}
}
type BeContextItem<T> = ContextItem<AnyWorkId, T, BePersistentStorage>;
type BeContextMap<T> = ContextMap<AnyWorkId, T, BePersistentStorage>;
pub struct Context {
pub flags: Flags,
pub persistent_storage: Arc<BePersistentStorage>,
pub ir: Arc<FeContext>,
pub gvar_fragments: BeContextMap<GvarFragment>,
pub glyphs: BeContextMap<Glyph>,
pub avar: BeContextItem<PossiblyEmptyAvar>,
pub cmap: BeContextItem<Cmap>,
pub colr: BeContextItem<Colr>,
pub cpal: BeContextItem<Cpal>,
pub fvar: BeContextItem<Fvar>,
pub gasp: BeContextItem<Gasp>,
pub glyf: BeContextItem<Bytes>,
pub gsub: BeContextItem<Gsub>,
pub gpos: BeContextItem<Gpos>,
pub gdef: BeContextItem<Gdef>,
pub gvar: BeContextItem<Bytes>,
pub post: BeContextItem<Post>,
pub meta: BeContextItem<Meta>,
pub loca: BeContextItem<Bytes>,
pub loca_format: BeContextItem<LocaFormatWrapper>,
pub maxp: BeContextItem<Maxp>,
pub name: BeContextItem<Name>,
pub os2: BeContextItem<Os2>,
pub head: BeContextItem<Head>,
pub hhea: BeContextItem<Hhea>,
pub hmtx: BeContextItem<Bytes>,
pub hvar: BeContextItem<Hvar>,
pub mvar: BeContextItem<Mvar>,
pub vhea: BeContextItem<Vhea>,
pub vmtx: BeContextItem<Bytes>,
pub vvar: BeContextItem<Vvar>,
pub all_kerning_pairs: BeContextItem<AllKerningPairs>,
pub kern_fragments: BeContextMap<KernFragment>,
pub fea_ast: BeContextItem<FeaFirstPassOutput>,
pub fea_rs_kerns: BeContextItem<FeaRsKerns>,
pub fea_rs_marks: BeContextItem<FeaRsMarks>,
pub extra_fea_tables: BeContextItem<ExtraFeaTables>,
pub stat: BeContextItem<Stat>,
pub font: BeContextItem<Bytes>,
}
impl Context {
fn copy(&self, acl: AccessControlList<AnyWorkId>) -> Context {
let acl = Arc::from(acl);
Context {
flags: self.flags,
persistent_storage: self.persistent_storage.clone(),
ir: self.ir.clone(),
glyphs: self.glyphs.clone_with_acl(acl.clone()),
gvar_fragments: self.gvar_fragments.clone_with_acl(acl.clone()),
avar: self.avar.clone_with_acl(acl.clone()),
cmap: self.cmap.clone_with_acl(acl.clone()),
colr: self.colr.clone_with_acl(acl.clone()),
cpal: self.cpal.clone_with_acl(acl.clone()),
fvar: self.fvar.clone_with_acl(acl.clone()),
gasp: self.gasp.clone_with_acl(acl.clone()),
glyf: self.glyf.clone_with_acl(acl.clone()),
gsub: self.gsub.clone_with_acl(acl.clone()),
gpos: self.gpos.clone_with_acl(acl.clone()),
gdef: self.gdef.clone_with_acl(acl.clone()),
gvar: self.gvar.clone_with_acl(acl.clone()),
post: self.post.clone_with_acl(acl.clone()),
loca: self.loca.clone_with_acl(acl.clone()),
loca_format: self.loca_format.clone_with_acl(acl.clone()),
maxp: self.maxp.clone_with_acl(acl.clone()),
name: self.name.clone_with_acl(acl.clone()),
os2: self.os2.clone_with_acl(acl.clone()),
head: self.head.clone_with_acl(acl.clone()),
hhea: self.hhea.clone_with_acl(acl.clone()),
hmtx: self.hmtx.clone_with_acl(acl.clone()),
hvar: self.hvar.clone_with_acl(acl.clone()),
mvar: self.mvar.clone_with_acl(acl.clone()),
meta: self.meta.clone_with_acl(acl.clone()),
vhea: self.vhea.clone_with_acl(acl.clone()),
vmtx: self.vmtx.clone_with_acl(acl.clone()),
vvar: self.vvar.clone_with_acl(acl.clone()),
all_kerning_pairs: self.all_kerning_pairs.clone_with_acl(acl.clone()),
kern_fragments: self.kern_fragments.clone_with_acl(acl.clone()),
fea_rs_kerns: self.fea_rs_kerns.clone_with_acl(acl.clone()),
fea_rs_marks: self.fea_rs_marks.clone_with_acl(acl.clone()),
stat: self.stat.clone_with_acl(acl.clone()),
fea_ast: self.fea_ast.clone_with_acl(acl.clone()),
extra_fea_tables: self.extra_fea_tables.clone_with_acl(acl.clone()),
font: self.font.clone_with_acl(acl),
}
}
pub fn new_root(flags: Flags, paths: Paths, ir: &fontir::orchestration::Context) -> Context {
let acl = Arc::from(AccessControlList::read_only());
let persistent_storage = Arc::from(BePersistentStorage {
active: flags.contains(Flags::EMIT_IR),
paths,
});
Context {
flags,
persistent_storage: persistent_storage.clone(),
ir: Arc::from(ir.read_only()),
glyphs: ContextMap::new(acl.clone(), persistent_storage.clone()),
gvar_fragments: ContextMap::new(acl.clone(), persistent_storage.clone()),
avar: ContextItem::new(WorkId::Avar.into(), acl.clone(), persistent_storage.clone()),
cmap: ContextItem::new(WorkId::Cmap.into(), acl.clone(), persistent_storage.clone()),
colr: ContextItem::new(WorkId::Colr.into(), acl.clone(), persistent_storage.clone()),
cpal: ContextItem::new(WorkId::Cpal.into(), acl.clone(), persistent_storage.clone()),
fvar: ContextItem::new(WorkId::Fvar.into(), acl.clone(), persistent_storage.clone()),
gasp: ContextItem::new(WorkId::Gasp.into(), acl.clone(), persistent_storage.clone()),
glyf: ContextItem::new(WorkId::Glyf.into(), acl.clone(), persistent_storage.clone()),
gpos: ContextItem::new(WorkId::Gpos.into(), acl.clone(), persistent_storage.clone()),
gsub: ContextItem::new(WorkId::Gsub.into(), acl.clone(), persistent_storage.clone()),
gdef: ContextItem::new(WorkId::Gdef.into(), acl.clone(), persistent_storage.clone()),
gvar: ContextItem::new(WorkId::Gvar.into(), acl.clone(), persistent_storage.clone()),
post: ContextItem::new(WorkId::Post.into(), acl.clone(), persistent_storage.clone()),
loca: ContextItem::new(WorkId::Loca.into(), acl.clone(), persistent_storage.clone()),
loca_format: ContextItem::new(
WorkId::LocaFormat.into(),
acl.clone(),
persistent_storage.clone(),
),
maxp: ContextItem::new(WorkId::Maxp.into(), acl.clone(), persistent_storage.clone()),
name: ContextItem::new(WorkId::Name.into(), acl.clone(), persistent_storage.clone()),
os2: ContextItem::new(WorkId::Os2.into(), acl.clone(), persistent_storage.clone()),
head: ContextItem::new(WorkId::Head.into(), acl.clone(), persistent_storage.clone()),
hhea: ContextItem::new(WorkId::Hhea.into(), acl.clone(), persistent_storage.clone()),
hmtx: ContextItem::new(WorkId::Hmtx.into(), acl.clone(), persistent_storage.clone()),
hvar: ContextItem::new(WorkId::Hvar.into(), acl.clone(), persistent_storage.clone()),
mvar: ContextItem::new(WorkId::Mvar.into(), acl.clone(), persistent_storage.clone()),
meta: ContextItem::new(WorkId::Meta.into(), acl.clone(), persistent_storage.clone()),
vhea: ContextItem::new(WorkId::Vhea.into(), acl.clone(), persistent_storage.clone()),
vmtx: ContextItem::new(WorkId::Vmtx.into(), acl.clone(), persistent_storage.clone()),
vvar: ContextItem::new(WorkId::Vvar.into(), acl.clone(), persistent_storage.clone()),
all_kerning_pairs: ContextItem::new(
WorkId::GatherIrKerning.into(),
acl.clone(),
persistent_storage.clone(),
),
kern_fragments: ContextMap::new(acl.clone(), persistent_storage.clone()),
fea_rs_kerns: ContextItem::new(
WorkId::GatherBeKerning.into(),
acl.clone(),
persistent_storage.clone(),
),
fea_rs_marks: ContextItem::new(
WorkId::Marks.into(),
acl.clone(),
persistent_storage.clone(),
),
fea_ast: ContextItem::new(
WorkId::FeaturesAst.into(),
acl.clone(),
persistent_storage.clone(),
),
stat: ContextItem::new(WorkId::Stat.into(), acl.clone(), persistent_storage.clone()),
extra_fea_tables: ContextItem::new(
WorkId::ExtraFeaTables.into(),
acl.clone(),
persistent_storage.clone(),
),
font: ContextItem::new(WorkId::Font.into(), acl, persistent_storage),
}
}
pub fn copy_for_work(
&self,
read_access: Access<AnyWorkId>,
write_access: Access<AnyWorkId>,
) -> Context {
self.copy(AccessControlList::read_write(read_access, write_access))
}
pub fn copy_read_only(&self) -> Context {
self.copy(AccessControlList::read_only())
}
pub fn debug_dir(&self) -> &Path {
self.persistent_storage.paths.debug_dir()
}
pub fn font_file(&self) -> PathBuf {
self.persistent_storage.paths.target_file(&WorkId::Font)
}
}
#[derive(PartialEq)]
pub struct Bytes {
buf: Vec<u8>,
}
impl Bytes {
pub fn get(&self) -> &[u8] {
&self.buf
}
}
impl From<Vec<u8>> for Bytes {
fn from(buf: Vec<u8>) -> Self {
Bytes { buf }
}
}
impl Persistable for Bytes {
fn read(from: &mut dyn Read) -> Self {
let mut buf = Vec::new();
from.read_to_end(&mut buf).unwrap();
buf.into()
}
fn write(&self, to: &mut dyn io::Write) {
to.write_all(&self.buf).unwrap();
}
}
pub(crate) fn to_bytes<T>(table: &T) -> Option<Vec<u8>>
where
T: FontWrite + Validate,
{
write_fonts::dump_table(table).ok()
}
#[cfg(test)]
mod tests {
use fontdrasil::{
coords::NormalizedCoord,
variations::{Tent, VariationRegion},
};
use super::*;
fn non_default_region() -> VariationRegion {
let mut region = VariationRegion::default();
region.insert(
Tag::new(b"wght"),
Tent::new(
NormalizedCoord::new(0.0),
NormalizedCoord::new(1.0),
NormalizedCoord::new(1.0),
),
);
region
}
#[test]
fn keeps_if_some_deltas() {
let deltas = GvarFragment {
glyph_name: "blah".into(),
deltas: vec![(
non_default_region(),
vec![
GlyphDelta::optional(0, 0),
GlyphDelta::required(1, 0),
GlyphDelta::optional(1, 0),
],
)],
}
.to_deltas(&[Tag::new(b"wght")]);
assert!(!deltas.is_empty(), "{deltas:?}");
}
#[test]
fn drops_nop_deltas() {
let can_omit = GlyphDelta::optional(0, 0);
let deltas = GvarFragment {
glyph_name: "blah".into(),
deltas: vec![(non_default_region(), vec![can_omit; 3])],
}
.to_deltas(&[Tag::new(b"wght")]);
assert!(deltas.is_empty(), "{deltas:?}");
}
#[test]
fn kern_pair_sort_order() {
let glyph = KernSide::Glyph(GlyphId16::new(5));
let class_ = KernSide::Group([1, 2, 3, 4].into_iter().map(GlyphId16::new).collect());
let value = ValueRecordBuilder::new().with_x_advance(420);
let glyph_glyph = KernPair {
side1: glyph.clone(),
side2: glyph.clone(),
value: value.clone(),
};
let glyph_class = KernPair {
side1: glyph.clone(),
side2: class_.clone(),
value: value.clone(),
};
let class_glyph = KernPair {
side1: class_.clone(),
side2: glyph.clone(),
value: value.clone(),
};
let class_class = KernPair {
side1: class_.clone(),
side2: class_.clone(),
value: value.clone(),
};
let mut unsorted = [&class_class, &glyph_class, &glyph_glyph, &class_glyph];
unsorted.sort();
assert_eq!(
unsorted,
[&glyph_glyph, &glyph_class, &class_glyph, &class_class]
);
}
}