Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add initial implementation of meta compilation #1136

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
6 changes: 5 additions & 1 deletion fontbe/src/font.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use write_fonts::{
tables::{
avar::Avar, cmap::Cmap, fvar::Fvar, gdef::Gdef, glyf::Glyf, gpos::Gpos, gsub::Gsub,
gvar::Gvar, head::Head, hhea::Hhea, hmtx::Hmtx, hvar::Hvar, loca::Loca, maxp::Maxp,
mvar::Mvar, name::Name, os2::Os2, post::Post, stat::Stat,
meta::Meta, mvar::Mvar, name::Name, os2::Os2, post::Post, stat::Stat,
},
types::Tag,
FontBuilder,
Expand Down Expand Up @@ -45,6 +45,7 @@ const TABLES_TO_MERGE: &[(WorkId, Tag, TableType)] = &[
(WorkId::Gvar, Gvar::TAG, TableType::Variable),
(WorkId::Loca, Loca::TAG, TableType::Static),
(WorkId::Maxp, Maxp::TAG, TableType::Static),
(WorkId::Meta, Meta::TAG, TableType::Static),
(WorkId::Name, Name::TAG, TableType::Static),
(WorkId::Os2, Os2::TAG, TableType::Static),
(WorkId::Post, Post::TAG, TableType::Static),
Expand All @@ -68,6 +69,7 @@ fn has(context: &Context, id: WorkId) -> bool {
WorkId::Gvar => context.gvar.try_get().is_some(),
WorkId::Loca => context.loca.try_get().is_some(),
WorkId::Maxp => context.maxp.try_get().is_some(),
WorkId::Meta => context.meta.try_get().is_some(),
WorkId::Name => context.name.try_get().is_some(),
WorkId::Os2 => context.os2.try_get().is_some(),
WorkId::Post => context.post.try_get().is_some(),
Expand All @@ -94,6 +96,7 @@ fn bytes_for(context: &Context, id: WorkId) -> Result<Option<Vec<u8>>, Error> {
WorkId::Gvar => Some(context.gvar.get().as_ref().get().to_vec()),
WorkId::Loca => Some(context.loca.get().as_ref().get().to_vec()),
WorkId::Maxp => to_bytes(context.maxp.get().as_ref()),
WorkId::Meta => to_bytes(context.meta.get().as_ref()),
WorkId::Name => to_bytes(context.name.get().as_ref()),
WorkId::Os2 => to_bytes(context.os2.get().as_ref()),
WorkId::Post => to_bytes(context.post.get().as_ref()),
Expand Down Expand Up @@ -125,6 +128,7 @@ impl Work<Context, AnyWorkId, Error> for FontWork {
.variant(WorkId::Gvar)
.variant(WorkId::Loca)
.variant(WorkId::Maxp)
.variant(WorkId::Meta)
.variant(WorkId::Name)
.variant(WorkId::Os2)
.variant(WorkId::Post)
Expand Down
1 change: 1 addition & 0 deletions fontbe/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pub mod glyphs;
pub mod gvar;
pub mod head;
pub mod hvar;
pub mod meta;
pub mod metrics_and_limits;
pub mod mvar;
pub mod name;
Expand Down
56 changes: 56 additions & 0 deletions fontbe/src/meta.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
//! Generates a [meta](https://learn.microsoft.com/en-us/typography/opentype/spec/meta) table.

use fontdrasil::orchestration::{Access, AccessBuilder, Work};
use fontir::orchestration::WorkId as FeWorkId;
use write_fonts::tables::meta::{DataMapRecord, Meta};

use crate::{
error::Error,
orchestration::{AnyWorkId, BeWork, Context, WorkId},
};

#[derive(Debug)]
struct MetaWork {}

pub fn create_meta_work() -> Box<BeWork> {
Box::new(MetaWork {})
}

impl Work<Context, AnyWorkId, Error> for MetaWork {
fn id(&self) -> AnyWorkId {
WorkId::Meta.into()
}

fn read_access(&self) -> Access<AnyWorkId> {
AccessBuilder::new()
.variant(FeWorkId::StaticMetadata) // For meta records
.build()
}

/// Generate [meta](https://learn.microsoft.com/en-us/typography/opentype/spec/meta)
fn exec(&self, context: &Context) -> Result<(), Error> {
// Build from static metadata, if at least one record is provided.
let meta = {
let static_metadata = context.ir.static_metadata.get();
let records = &static_metadata.misc.meta;

if records.is_empty() {
None
} else {
// Instantiate DataMapRecords from tuples.
Some(Meta::new(
records
.iter()
.map(|(tag, metadata)| DataMapRecord::new(*tag, metadata.clone()))
.collect(),
))
}
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like it should be possible to unindent, e.g.

let static_metadata = context.ir.static_metadata.get();
let records = &static_metadata.misc.meta;

// Guard clause: nop if there are no meta records
if records.is_empty() {
  return Ok(());
}

context.meta.set(Meta::new(...));


if let Some(meta) = meta {
context.meta.set(meta);
};

Ok(())
}
}
6 changes: 6 additions & 0 deletions fontbe/src/orchestration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ use write_fonts::{
hvar::Hvar,
loca::LocaFormat,
maxp::Maxp,
meta::Meta,
mvar::Mvar,
name::Name,
os2::Os2,
Expand Down Expand Up @@ -99,6 +100,7 @@ pub enum WorkId {
LocaFormat,
Marks,
Maxp,
Meta,
Mvar,
Name,
Os2,
Expand Down Expand Up @@ -142,6 +144,7 @@ impl Identifier for WorkId {
WorkId::LocaFormat => "BeLocaFormat",
WorkId::Marks => "BeMarks",
WorkId::Maxp => "BeMaxp",
WorkId::Meta => "BeMeta",
WorkId::Mvar => "BeMvar",
WorkId::Name => "BeName",
WorkId::Os2 => "BeOs2",
Expand Down Expand Up @@ -810,6 +813,7 @@ pub struct Context {
pub loca: BeContextItem<Bytes>,
pub loca_format: BeContextItem<LocaFormatWrapper>,
pub maxp: BeContextItem<Maxp>,
pub meta: BeContextItem<Meta>,
pub name: BeContextItem<Name>,
pub os2: BeContextItem<Os2>,
pub head: BeContextItem<Head>,
Expand Down Expand Up @@ -848,6 +852,7 @@ impl Context {
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()),
meta: self.meta.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()),
Expand Down Expand Up @@ -894,6 +899,7 @@ impl Context {
persistent_storage.clone(),
),
maxp: ContextItem::new(WorkId::Maxp.into(), acl.clone(), persistent_storage.clone()),
meta: ContextItem::new(WorkId::Meta.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()),
Expand Down
1 change: 1 addition & 0 deletions fontbe/src/paths.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ impl Paths {
WorkId::GatherBeKerning => self.build_dir.join("kern_gather.bin"),
WorkId::Marks => self.build_dir.join("marks.bin"),
WorkId::Maxp => self.build_dir.join("maxp.table"),
WorkId::Meta => self.build_dir.join("meta.table"),
WorkId::Mvar => self.build_dir.join("mvar.table"),
WorkId::Name => self.build_dir.join("name.table"),
WorkId::Os2 => self.build_dir.join("os2.table"),
Expand Down
9 changes: 9 additions & 0 deletions fontc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ use fontbe::{
gvar::create_gvar_work,
head::create_head_work,
hvar::create_hvar_work,
meta::create_meta_work,
metrics_and_limits::create_metric_and_limit_work,
mvar::create_mvar_work,
name::create_name_work,
Expand Down Expand Up @@ -455,6 +456,12 @@ fn add_hvar_be_job(workload: &mut Workload) -> Result<(), Error> {
Ok(())
}

fn add_meta_be_job(workload: &mut Workload) -> Result<(), Error> {
let work = create_meta_work().into();
workload.add(work, workload.change_detector.static_metadata_ir_change());
Ok(())
}

fn add_mvar_be_job(workload: &mut Workload) -> Result<(), Error> {
let work = create_mvar_work().into();
workload.add(
Expand Down Expand Up @@ -516,6 +523,7 @@ pub fn create_workload(
add_marks_be_job(&mut workload)?;
add_metric_and_limits_job(&mut workload)?;
add_hvar_be_job(&mut workload)?;
add_meta_be_job(&mut workload)?;
add_mvar_be_job(&mut workload)?;
add_name_be_job(&mut workload)?;
add_os2_be_job(&mut workload)?;
Expand Down Expand Up @@ -822,6 +830,7 @@ mod tests {
BeWorkIdentifier::LocaFormat.into(),
BeWorkIdentifier::Marks.into(),
BeWorkIdentifier::Maxp.into(),
BeWorkIdentifier::Meta.into(),
BeWorkIdentifier::Mvar.into(),
BeWorkIdentifier::Name.into(),
BeWorkIdentifier::Os2.into(),
Expand Down
1 change: 1 addition & 0 deletions fontc/src/timing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ fn short_name(id: &AnyWorkId) -> &'static str {
AnyWorkId::Be(BeWorkIdentifier::LocaFormat) => "loca-fmt",
AnyWorkId::Be(BeWorkIdentifier::Marks) => "Marks",
AnyWorkId::Be(BeWorkIdentifier::Maxp) => "maxp",
AnyWorkId::Be(BeWorkIdentifier::Meta) => "meta",
AnyWorkId::Be(BeWorkIdentifier::Mvar) => "MVAR",
AnyWorkId::Be(BeWorkIdentifier::Name) => "name",
AnyWorkId::Be(BeWorkIdentifier::Os2) => "OS/2",
Expand Down
7 changes: 6 additions & 1 deletion fontir/src/ir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use ordered_float::OrderedFloat;
use serde::{de::Error as _, Deserialize, Serialize};
use smol_str::SmolStr;
use write_fonts::{
tables::{gdef::GlyphClassDef, os2::SelectionFlags},
tables::{gdef::GlyphClassDef, meta::Metadata, os2::SelectionFlags},
types::{GlyphId16, NameId, Tag},
OtRound,
};
Expand Down Expand Up @@ -130,6 +130,9 @@ pub struct MiscMetadata {

// Allows source to explicitly control bits. <https://github.com/googlefonts/fontc/issues/1027>
pub codepage_range_bits: Option<HashSet<u32>>,

// See <https://learn.microsoft.com/en-gb/typography/opentype/spec/meta>
pub meta: Vec<(Tag, Metadata)>,
}

/// PANOSE bytes
Expand Down Expand Up @@ -472,6 +475,7 @@ impl StaticMetadata {
panose: None,
unicode_range_bits: None,
codepage_range_bits: None,
meta: Default::default(),
},
})
}
Expand Down Expand Up @@ -2134,6 +2138,7 @@ mod tests {
panose: None,
unicode_range_bits: None,
codepage_range_bits: None,
meta: vec![(Tag::new(b"abcd"), Metadata::Other("efghijkl".into()))],
},
number_values: Default::default(),
}
Expand Down
33 changes: 33 additions & 0 deletions glyphs-reader/src/font.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,9 @@ pub struct Font {
pub unicode_range_bits: Option<BTreeSet<u32>>,
pub codepage_range_bits: Option<BTreeSet<u32>>,
pub panose: Option<Vec<i64>>,

/// Read from "meta Table" custom parameter.
pub meta: Vec<(String, String)>,
}

/// master id => { (name or class, name or class) => adjustment }
Expand Down Expand Up @@ -415,6 +418,13 @@ impl CustomParameters {
})
}

fn meta(&self) -> Option<&Vec<MetaRecord>> {
let Some(CustomParameterValue::MetaRecords(records)) = self.get("meta Table") else {
return None;
};
Some(records)
}

fn fs_type(&self) -> Option<&Vec<i64>> {
let Some(CustomParameterValue::FsType(bits)) = self.get("fsType") else {
return None;
Expand Down Expand Up @@ -462,6 +472,7 @@ enum CustomParameterValue {
UnicodeRange(Vec<i64>),
CodepageRange(Vec<i64>),
Panose(Vec<i64>),
MetaRecords(Vec<MetaRecord>),
}

/// Hand-parse these because they take multiple shapes
Expand Down Expand Up @@ -581,6 +592,12 @@ impl FromPlist for CustomParameters {
};
value = Some(CustomParameterValue::Panose(tokenizer.parse()?));
}
_ if name == Some(String::from("meta Table")) => {
let Token::OpenParen = peek else {
return Err(Error::UnexpectedChar('('));
};
value = Some(CustomParameterValue::MetaRecords(tokenizer.parse()?));
}
_ => tokenizer.skip_rec()?,
}
// once we've seen the value we're always done
Expand Down Expand Up @@ -629,6 +646,12 @@ pub struct AxisMapping {
user_to_design: Vec<(OrderedFloat<f64>, OrderedFloat<f64>)>,
}

#[derive(Default, Debug, Clone, PartialEq, Eq, Hash, FromPlist)]
pub struct MetaRecord {
tag: String,
data: String,
}

impl FromPlist for AxisMapping {
fn parse(tokenizer: &mut Tokenizer<'_>) -> Result<Self, crate::plist::Error> {
let tag = tokenizer.parse()?;
Expand Down Expand Up @@ -2423,6 +2446,15 @@ impl TryFrom<RawFont> for Font {
})
.collect();

// If any meta records were provided, convert to tuples for IR.
let meta = from
.custom_parameters
.meta()
.into_iter()
.flatten()
.map(|record| (record.tag.clone(), record.data.clone()))
.collect();

Ok(Font {
units_per_em,
fs_type,
Expand Down Expand Up @@ -2465,6 +2497,7 @@ impl TryFrom<RawFont> for Font {
unicode_range_bits,
codepage_range_bits,
panose,
meta,
})
}
}
Expand Down
40 changes: 39 additions & 1 deletion glyphs2fontir/src/source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,11 @@ use glyphs_reader::{
use ordered_float::OrderedFloat;
use smol_str::SmolStr;
use write_fonts::{
tables::{gdef::GlyphClassDef, os2::SelectionFlags},
tables::{
gdef::GlyphClassDef,
meta::{Metadata, ScriptLangTag, DLNG, SLNG},
os2::SelectionFlags,
},
types::{NameId, Tag},
};

Expand Down Expand Up @@ -130,6 +134,8 @@ impl GlyphsIrSource {
unicode_range_bits: font.unicode_range_bits.clone(),
codepage_range_bits: font.codepage_range_bits.clone(),
panose: font.panose.clone(),
// TODO: Is this correct?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks right at a glance

meta: font.meta.clone(),
};
state.track_memory("/font_master".to_string(), &font)?;
Ok(state)
Expand Down Expand Up @@ -182,6 +188,8 @@ impl GlyphsIrSource {
unicode_range_bits: None,
codepage_range_bits: None,
panose: None,
// TODO: Is this correct?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks right, I don't think meta can change global metrics

meta: Default::default(),
};
state.track_memory("/font_master".to_string(), &font)?;
Ok(state)
Expand Down Expand Up @@ -491,6 +499,36 @@ impl Work<Context, WorkId, Error> for StaticMetadataWork {
})
.or(static_metadata.misc.created);

// TODO: Use idiomatic error handling.
// TODO: This diverges from glyphsLib, which must convert a list of
// records into UFO's intermediate dict representation; is this
// actually an improvement, or a breaking change? What does Glyphs
// do?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this seems like an @anthrotype question

// https://github.com/googlefonts/glyphsLib/blob/74c63244fdbef1da540d646b0784ae6d2c3ca834/Lib/glyphsLib/builder/custom_params.py#L568-L584
static_metadata.misc.meta = font
.meta
.iter()
.map(|(tag, data)| {
let tag = Tag::new_checked(tag.as_bytes())
.expect("Malformed tag in meta custom parameter");

// Glyphs expects ScriptLangTag formatting for DLNG and SLNG.
// https://handbook.glyphsapp.com/custom-parameter-descriptions/
let metadata = match tag {
DLNG | SLNG => Metadata::ScriptLangTags(
data.split(",")
.map(Into::into)
.map(ScriptLangTag::new)
.collect::<Result<_, _>>()
.expect("Invalid ScriptLangTag in meta custom parameter"),
),
_ => Metadata::Other(data.clone().into_bytes()),
};

(tag, metadata)
})
.collect();

context.static_metadata.set(static_metadata);

let glyph_order = font
Expand Down
Loading
Loading