Skip to content

Commit

Permalink
Compile gasp
Browse files Browse the repository at this point in the history
  • Loading branch information
rsheeter committed Jan 9, 2025
1 parent 736be58 commit cfc3dd2
Show file tree
Hide file tree
Showing 12 changed files with 158 additions and 9 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ rayon = "1.6"
icu_properties = "1.4"

# fontations etc
write-fonts = { version = "0.33.0", features = ["serde", "read"] }
write-fonts = { version = "0.33.1", features = ["serde", "read"] }
skrifa = "0.26.1"
norad = "0.14.2"

Expand Down
10 changes: 7 additions & 3 deletions fontbe/src/font.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ use log::debug;
use write_fonts::{
read::TopLevelTable,
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,
meta::Meta, mvar::Mvar, name::Name, os2::Os2, post::Post, stat::Stat,
avar::Avar, cmap::Cmap, fvar::Fvar, gasp::Gasp, gdef::Gdef, glyf::Glyf, gpos::Gpos,
gsub::Gsub, gvar::Gvar, head::Head, hhea::Hhea, hmtx::Hmtx, hvar::Hvar, loca::Loca,
maxp::Maxp, meta::Meta, mvar::Mvar, name::Name, os2::Os2, post::Post, stat::Stat,
},
types::Tag,
FontBuilder,
Expand Down Expand Up @@ -38,6 +38,7 @@ const TABLES_TO_MERGE: &[(WorkId, Tag, TableType)] = &[
(WorkId::Head, Head::TAG, TableType::Static),
(WorkId::Hhea, Hhea::TAG, TableType::Static),
(WorkId::Hmtx, Hmtx::TAG, TableType::Static),
(WorkId::Gasp, Gasp::TAG, TableType::Static),
(WorkId::Glyf, Glyf::TAG, TableType::Static),
(WorkId::Gpos, Gpos::TAG, TableType::Static),
(WorkId::Gsub, Gsub::TAG, TableType::Static),
Expand All @@ -62,6 +63,7 @@ fn has(context: &Context, id: WorkId) -> bool {
WorkId::Head => context.head.try_get().is_some(),
WorkId::Hhea => context.hhea.try_get().is_some(),
WorkId::Hmtx => context.hmtx.try_get().is_some(),
WorkId::Gasp => context.gasp.try_get().is_some(),
WorkId::Glyf => context.glyf.try_get().is_some(),
WorkId::Gpos => context.gpos.try_get().is_some(),
WorkId::Gsub => context.gsub.try_get().is_some(),
Expand Down Expand Up @@ -89,6 +91,7 @@ fn bytes_for(context: &Context, id: WorkId) -> Result<Option<Vec<u8>>, Error> {
WorkId::Head => to_bytes(context.head.get().as_ref()),
WorkId::Hhea => to_bytes(context.hhea.get().as_ref()),
WorkId::Hmtx => Some(context.hmtx.get().as_ref().get().to_vec()),
WorkId::Gasp => to_bytes(context.gasp.get().as_ref()),
WorkId::Glyf => Some(context.glyf.get().as_ref().get().to_vec()),
WorkId::Gpos => to_bytes(context.gpos.get().as_ref()),
WorkId::Gsub => to_bytes(context.gsub.get().as_ref()),
Expand Down Expand Up @@ -121,6 +124,7 @@ impl Work<Context, AnyWorkId, Error> for FontWork {
.variant(WorkId::Head)
.variant(WorkId::Hhea)
.variant(WorkId::Hmtx)
.variant(WorkId::Gasp)
.variant(WorkId::Glyf)
.variant(WorkId::Gpos)
.variant(WorkId::Gsub)
Expand Down
56 changes: 56 additions & 0 deletions fontbe/src/gasp.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
//! Generates a [gasp](https://learn.microsoft.com/en-us/typography/opentype/spec/gasp) table.
use fontdrasil::orchestration::{Access, AccessBuilder, Work};
use fontir::orchestration::WorkId as FeWorkId;

use write_fonts::tables::gasp::Gasp;

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

#[derive(Debug)]
struct GaspWork {}

pub fn create_gasp_work() -> Box<BeWork> {
Box::new(GaspWork {})
}

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

fn read_access(&self) -> Access<AnyWorkId> {
AccessBuilder::new()
.specific_instance(FeWorkId::StaticMetadata)
.build()
}

/// Generate [gasp](https://learn.microsoft.com/en-us/typography/opentype/spec/gasp) if necessary
fn exec(&self, context: &Context) -> Result<(), Error> {
let static_metadata = context.ir.static_metadata.get();
let mut gasp_ranges = static_metadata.misc.gasp.clone();
gasp_ranges.sort_by_key(|gr| gr.range_max_ppem);

if gasp_ranges.is_empty() {
return Ok(());
}
if gasp_ranges.len() > u16::MAX as usize {
return Err(Error::OutOfBounds {
what: "gasp".to_string(),
value: format!("Too many records: {}", gasp_ranges.len()),
});
}

let gasp = Gasp {
version: 1, // "set to 1 in new fonts)" <https://learn.microsoft.com/en-us/typography/opentype/spec/gasp#gasp-table-formats>
num_ranges: gasp_ranges.len() as u16,
gasp_ranges,
};
context.gasp.set(gasp);

Ok(())
}
}
1 change: 1 addition & 0 deletions fontbe/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ pub mod error;
pub mod features;
pub mod font;
pub mod fvar;
pub mod gasp;
pub mod glyphs;
pub mod gvar;
pub mod head;
Expand Down
6 changes: 6 additions & 0 deletions fontbe/src/orchestration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ use write_fonts::{
base::Base,
cmap::Cmap,
fvar::Fvar,
gasp::Gasp,
gdef::Gdef,
glyf::Glyph as RawGlyph,
gpos::Gpos,
Expand Down Expand Up @@ -82,6 +83,7 @@ pub enum WorkId {
Cmap,
Font,
Fvar,
Gasp,
Glyf,
GlyfFragment(GlyphName),
Gpos,
Expand Down Expand Up @@ -127,6 +129,7 @@ impl Identifier for WorkId {
WorkId::Cmap => "BeCmap",
WorkId::Font => "BeFont",
WorkId::Fvar => "BeFvar",
WorkId::Gasp => "BeGasp",
WorkId::Glyf => "BeGlyf",
WorkId::GlyfFragment(..) => "BeGlyfFragment",
WorkId::Gpos => "BeGpos",
Expand Down Expand Up @@ -806,6 +809,7 @@ pub struct Context {
pub avar: BeContextItem<PossiblyEmptyAvar>,
pub cmap: BeContextItem<Cmap>,
pub fvar: BeContextItem<Fvar>,
pub gasp: BeContextItem<Gasp>,
pub glyf: BeContextItem<Bytes>,
pub gsub: BeContextItem<Gsub>,
pub gpos: BeContextItem<Gpos>,
Expand Down Expand Up @@ -845,6 +849,7 @@ impl Context {
avar: self.avar.clone_with_acl(acl.clone()),
cmap: self.cmap.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()),
Expand Down Expand Up @@ -888,6 +893,7 @@ impl Context {
avar: ContextItem::new(WorkId::Avar.into(), acl.clone(), persistent_storage.clone()),
cmap: ContextItem::new(WorkId::Cmap.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()),
Expand Down
1 change: 1 addition & 0 deletions fontbe/src/paths.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ impl Paths {
WorkId::GlyfFragment(name) => self.glyph_glyf_file(name.as_str()),
WorkId::GvarFragment(name) => self.glyph_gvar_file(name.as_str()),
WorkId::Avar => self.build_dir.join("avar.table"),
WorkId::Gasp => self.build_dir.join("gasp.table"),
WorkId::Glyf => self.build_dir.join("glyf.table"),
WorkId::Gsub => self.build_dir.join("gsub.table"),
WorkId::Gpos => self.build_dir.join("gpos.table"),
Expand Down
33 changes: 33 additions & 0 deletions fontc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ mod tests {
raw::{
tables::{
cmap::{Cmap, CmapSubtable},
gasp::GaspRangeBehavior,
glyf::{self, CompositeGlyph, CurvePoint, Glyf},
hmtx::Hmtx,
layout::FeatureParams,
Expand Down Expand Up @@ -371,6 +372,7 @@ mod tests {
BeWorkIdentifier::Cmap.into(),
BeWorkIdentifier::Font.into(),
BeWorkIdentifier::Fvar.into(),
BeWorkIdentifier::Gasp.into(),
BeWorkIdentifier::Glyf.into(),
BeWorkIdentifier::Gpos.into(),
BeWorkIdentifier::Gsub.into(),
Expand Down Expand Up @@ -634,6 +636,37 @@ mod tests {
assert_eq!(1 << 8 | 2, os2.s_family_class());
}

#[test]
fn compile_obeys_gasp_records() {
let result = TestCompile::compile_source("fontinfo.designspace");
let gasp = result.font().gasp().unwrap();
assert_eq!(
vec![
(
7,
GaspRangeBehavior::GASP_DOGRAY | GaspRangeBehavior::GASP_SYMMETRIC_SMOOTHING
),
(
65535,
GaspRangeBehavior::GASP_GRIDFIT
| GaspRangeBehavior::GASP_DOGRAY
| GaspRangeBehavior::GASP_SYMMETRIC_GRIDFIT
| GaspRangeBehavior::GASP_SYMMETRIC_SMOOTHING
),
],
gasp.gasp_ranges()
.iter()
.map(|r| (r.range_max_ppem(), r.range_gasp_behavior()))
.collect::<Vec<_>>(),
)
}

#[test]
fn compile_gasp_only_from_default() {
let result = TestCompile::compile_source("fontinfo_var.designspace");
assert!(result.font().gasp().is_err());
}

#[test]
fn compile_prefers_variable_default_to_fontinfo_value() {
let result = TestCompile::compile_source("fontinfo_var.designspace");
Expand Down
1 change: 1 addition & 0 deletions fontc/src/timing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ fn short_name(id: &AnyWorkId) -> &'static str {
AnyWorkId::Be(BeWorkIdentifier::FeaturesAst) => "fea.ast",
AnyWorkId::Be(BeWorkIdentifier::Font) => "font",
AnyWorkId::Be(BeWorkIdentifier::Fvar) => "fvar",
AnyWorkId::Be(BeWorkIdentifier::Gasp) => "gasp",
AnyWorkId::Be(BeWorkIdentifier::Gdef) => "GDEF",
AnyWorkId::Be(BeWorkIdentifier::Glyf) => "glyf",
AnyWorkId::Be(BeWorkIdentifier::GlyfFragment(..)) => "glyf-frag",
Expand Down
2 changes: 2 additions & 0 deletions fontc/src/workload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ use fontbe::{
},
font::create_font_work,
fvar::create_fvar_work,
gasp::create_gasp_work,
glyphs::{create_glyf_loca_work, create_glyf_work},
gvar::create_gvar_work,
head::create_head_work,
Expand Down Expand Up @@ -157,6 +158,7 @@ impl Workload {
// BE: f(IR, maybe other BE work) => binary
workload.add_skippable_feature_work(FeatureParsingWork::create());
workload.add_skippable_feature_work(FeatureCompilationWork::create());
workload.add(create_gasp_work());
let ir_glyphs = workload
.jobs_pending
.keys()
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::{gasp::GaspRange, gdef::GlyphClassDef, os2::SelectionFlags},
types::{GlyphId16, NameId, Tag},
OtRound,
};
Expand Down Expand Up @@ -149,6 +149,9 @@ pub struct MiscMetadata {
///
/// If empty and there is a width axis OS/2 will use the width default
pub us_width_class: Option<u16>,

// <https://learn.microsoft.com/en-us/typography/opentype/spec/gasp>
pub gasp: Vec<GaspRange>,
}

/// PANOSE bytes
Expand Down Expand Up @@ -513,6 +516,7 @@ impl StaticMetadata {
meta_table: None,
us_weight_class: None,
us_width_class: None,
gasp: Vec::new(),
},
})
}
Expand Down Expand Up @@ -1979,6 +1983,7 @@ mod tests {
meta_table: None,
us_weight_class: None,
us_width_class: None,
gasp: Vec::new(),
},
number_values: Default::default(),
}
Expand Down
23 changes: 23 additions & 0 deletions resources/testdata/FontInfo-Regular.ufo/fontinfo.plist
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,28 @@
<integer>1</integer>
<integer>2</integer>
</array>
<key>openTypeGaspRangeRecords</key>
<array>
<dict>
<key>rangeGaspBehavior</key>
<array>
<integer>1</integer>
<integer>3</integer>
</array>
<key>rangeMaxPPEM</key>
<integer>7</integer>
</dict>
<dict>
<key>rangeGaspBehavior</key>
<array>
<integer>0</integer>
<integer>1</integer>
<integer>2</integer>
<integer>3</integer>
</array>
<key>rangeMaxPPEM</key>
<integer>65535</integer>
</dict>
</array>
</dict>
</plist>
25 changes: 21 additions & 4 deletions ufo2fontir/src/source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ use fontdrasil::{
use fontir::{
error::{BadSource, BadSourceKind, Error},
ir::{
AnchorBuilder, FeaturesSource, GdefCategories, GlobalMetric, GlobalMetrics, GlyphOrder,
KernGroup, KernSide, KerningGroups, KerningInstance, MetaTableValues, NameBuilder, NameKey,
NamedInstance, Panose, PostscriptNames, StaticMetadata, DEFAULT_VENDOR_ID,
AnchorBuilder, FeaturesSource, GdefCategories, GlobalMetric, GlobalMetrics,
GlyphOrder, KernGroup, KernSide, KerningGroups, KerningInstance, MetaTableValues,
NameBuilder, NameKey, NamedInstance, Panose, PostscriptNames, StaticMetadata,
DEFAULT_VENDOR_ID,
},
orchestration::{Context, Flags, IrWork, WorkId},
source::Source,
Expand All @@ -27,7 +28,8 @@ use norad::{
fontinfo::StyleMapStyle,
};
use write_fonts::{
tables::{gdef::GlyphClassDef, os2::SelectionFlags},
read::tables::gasp::GaspRangeBehavior,
tables::{gasp::GaspRange, gdef::GlyphClassDef, os2::SelectionFlags},
types::{NameId, Tag},
OtRound,
};
Expand Down Expand Up @@ -890,6 +892,21 @@ impl Work<Context, WorkId, Error> for StaticMetadataWork {
.get("public.openTypeMeta")
.and_then(parse_meta_table_values);

if let Some(gasp_records) = font_info_at_default.open_type_gasp_range_records.as_ref() {
static_metadata.misc.gasp = gasp_records
.iter()
.map(|g| GaspRange {
range_max_ppem: (g.range_max_ppem as u16).into(),
range_gasp_behavior: GaspRangeBehavior::from_bits_truncate(
g.range_gasp_behavior
.iter()
.map(|b| 1u16 << (*b as u8))
.fold(0u16, |acc, e| acc | e),
).into(),
})
.collect();
}

context.preliminary_glyph_order.set(glyph_order);
context.static_metadata.set(static_metadata);
Ok(())
Expand Down

0 comments on commit cfc3dd2

Please sign in to comment.