From 6bd56235f842ec45d5ac33e1bbff53a7352aece0 Mon Sep 17 00:00:00 2001 From: Rod S Date: Fri, 26 May 2023 11:50:07 -0700 Subject: [PATCH] Capture feature tables individually and include in final font --- fontbe/src/features.rs | 62 ++++++++----- fontbe/src/font.rs | 35 ++++++- fontbe/src/orchestration.rs | 91 +++++++++---------- fontbe/src/paths.rs | 4 +- fontc/src/args.rs | 22 +++-- fontc/src/lib.rs | 33 ++++--- fontc/src/workload.rs | 17 +++- fontdrasil/src/orchestration.rs | 2 +- fontir/src/orchestration.rs | 22 ++++- .../testdata/Static-Regular.ufo/features.fea | 6 +- 10 files changed, 192 insertions(+), 102 deletions(-) diff --git a/fontbe/src/features.rs b/fontbe/src/features.rs index 00819c939..9d3667fd2 100644 --- a/fontbe/src/features.rs +++ b/fontbe/src/features.rs @@ -12,14 +12,15 @@ use fea_rs::{ Compiler, GlyphMap, GlyphName as FeaRsGlyphName, }; use fontir::{ir::Features, orchestration::Flags}; -use log::{debug, error, trace, warn}; -use write_fonts::FontBuilder; +use log::{debug, error, warn}; +use read_fonts::{FontRef, TableProvider}; +use write_fonts::{from_obj::ToOwnedTable, FontBuilder}; use fontdrasil::orchestration::Work; use crate::{ error::Error, - orchestration::{BeWork, Context}, + orchestration::{BeWork, Context, WorkId}, }; pub struct FeatureWork {} @@ -111,29 +112,42 @@ fn write_debug_fea(context: &Context, is_error: bool, why: &str, fea_content: &s impl Work for FeatureWork { fn exec(&self, context: &Context) -> Result<(), Error> { let features = context.ir.get_features(); - if let Features::Empty = *features { - // set a default in place so subsequent compiles skip this step - trace!("No fea file, dull compile"); - context.set_features(FontBuilder::default()); - return Ok(()); - } - let glyph_order = &context.ir.get_final_static_metadata().glyph_order; - if glyph_order.is_empty() { - warn!("Glyph order is empty; feature compile improbable"); - } - let glyph_map = glyph_order - .iter() - .map(|n| Into::::into(n.as_str())) - .collect(); - - let result = self.compile(&features, glyph_map); - if result.is_err() || context.flags.contains(Flags::EMIT_DEBUG) { - if let Features::Memory(fea_content) = &*features { - write_debug_fea(context, result.is_err(), "compile failed", fea_content); + if !matches!(*features, Features::Empty) { + let glyph_order = &context.ir.get_final_static_metadata().glyph_order; + if glyph_order.is_empty() { + warn!("Glyph order is empty; feature compile improbable"); + } + let glyph_map = glyph_order + .iter() + .map(|n| Into::::into(n.as_str())) + .collect(); + + let result = self.compile(&features, glyph_map); + if result.is_err() || context.flags.contains(Flags::EMIT_DEBUG) { + if let Features::Memory(fea_content) = &*features { + write_debug_fea(context, result.is_err(), "compile failed", fea_content); + } + } + let buf = result?.build(); + let font = FontRef::new(&buf).unwrap(); + debug!( + "Built features, gpos? {} gsub? {}", + font.gpos().is_ok(), + font.gsub().is_ok() + ); + if let Ok(gpos) = font.gpos() { + context.set_gpos(gpos.to_owned_table()); } + if let Ok(gsub) = font.gsub() { + context.set_gsub(gsub.to_owned_table()); + } + } else { + debug!("No fea file, dull compile"); + } + // Enables the assumption that if the file exists features were compiled + if context.flags.contains(Flags::EMIT_IR) { + fs::write(context.paths.target_file(&WorkId::Features), "1").map_err(Error::IoError)?; } - let font = result?; - context.set_features(font); Ok(()) } } diff --git a/fontbe/src/font.rs b/fontbe/src/font.rs index ad1ea23c0..9a31d5157 100644 --- a/fontbe/src/font.rs +++ b/fontbe/src/font.rs @@ -4,8 +4,9 @@ use fontdrasil::orchestration::Work; use log::debug; use read_fonts::{ tables::{ - avar::Avar, cmap::Cmap, fvar::Fvar, glyf::Glyf, gvar::Gvar, head::Head, hhea::Hhea, - hmtx::Hmtx, loca::Loca, maxp::Maxp, name::Name, os2::Os2, post::Post, + avar::Avar, cmap::Cmap, fvar::Fvar, glyf::Glyf, gpos::Gpos, gsub::Gsub, gvar::Gvar, + head::Head, hhea::Hhea, hmtx::Hmtx, loca::Loca, maxp::Maxp, name::Name, os2::Os2, + post::Post, }, types::Tag, TopLevelTable, @@ -36,6 +37,8 @@ const TABLES_TO_MERGE: &[(WorkId, Tag, TableType)] = &[ (WorkId::Hhea, Hhea::TAG, TableType::Static), (WorkId::Hmtx, Hmtx::TAG, TableType::Static), (WorkId::Glyf, Glyf::TAG, TableType::Static), + (WorkId::Gpos, Gpos::TAG, TableType::Static), + (WorkId::Gsub, Gsub::TAG, TableType::Static), (WorkId::Gvar, Gvar::TAG, TableType::Variable), (WorkId::Loca, Loca::TAG, TableType::Static), (WorkId::Maxp, Maxp::TAG, TableType::Static), @@ -44,6 +47,27 @@ const TABLES_TO_MERGE: &[(WorkId, Tag, TableType)] = &[ (WorkId::Post, Post::TAG, TableType::Static), ]; +fn has(context: &Context, id: WorkId) -> bool { + match id { + WorkId::Avar => context.has_avar(), + WorkId::Cmap => context.has_cmap(), + WorkId::Fvar => context.has_fvar(), + WorkId::Head => context.has_head(), + WorkId::Hhea => context.has_hhea(), + WorkId::Hmtx => context.has_hmtx(), + WorkId::Glyf => context.has_glyf_loca(), + WorkId::Gpos => context.has_gpos(), + WorkId::Gsub => context.has_gsub(), + WorkId::Gvar => context.has_gvar(), + WorkId::Loca => context.has_glyf_loca(), + WorkId::Maxp => context.has_maxp(), + WorkId::Name => context.has_name(), + WorkId::Os2 => context.has_os2(), + WorkId::Post => context.has_post(), + _ => false, + } +} + fn bytes_for(context: &Context, id: WorkId) -> Result, Error> { let bytes = match id { WorkId::Avar => to_bytes(context.get_avar().as_ref()), @@ -53,6 +77,8 @@ fn bytes_for(context: &Context, id: WorkId) -> Result, Error> { WorkId::Hhea => to_bytes(&*context.get_hhea()), WorkId::Hmtx => context.get_hmtx().get().to_vec(), WorkId::Glyf => context.get_glyf_loca().glyf.clone(), + WorkId::Gpos => to_bytes(&*context.get_gpos()), + WorkId::Gsub => to_bytes(&*context.get_gsub()), WorkId::Gvar => context.get_gvar().get().to_vec(), WorkId::Loca => context.get_glyf_loca().raw_loca.clone(), WorkId::Maxp => to_bytes(&*context.get_maxp()), @@ -81,11 +107,16 @@ impl Work for FontWork { debug!("Skip {tag} because this is a static font"); continue; } + if !has(context, work_id.clone()) { + debug!("Skip {tag} because we don't have it"); + continue; + } debug!("Grabbing {tag} for final font"); let bytes = bytes_for(context, work_id.clone())?; builder.add_table(*tag, bytes); } + debug!("Building font"); let font = builder.build(); debug!("Assembled {} byte font", font.len()); context.set_font(Bytes::new(font)); diff --git a/fontbe/src/orchestration.rs b/fontbe/src/orchestration.rs index 803b59081..a0280a48e 100644 --- a/fontbe/src/orchestration.rs +++ b/fontbe/src/orchestration.rs @@ -25,6 +25,8 @@ use write_fonts::{ cmap::Cmap, fvar::Fvar, glyf::{Bbox, SimpleGlyph}, + gpos::Gpos, + gsub::Gsub, gvar::GlyphDeltas, head::Head, hhea::Hhea, @@ -35,7 +37,7 @@ use write_fonts::{ variations::Tuple, }, validate::Validate, - FontBuilder, FontWrite, OtRound, + FontWrite, OtRound, }; use write_fonts::{from_obj::FromTableRef, tables::glyf::CompositeGlyph}; @@ -61,6 +63,8 @@ pub enum WorkId { Fvar, Glyf, GlyfFragment(GlyphName), + Gpos, + Gsub, Gvar, GvarFragment(GlyphName), Head, @@ -346,8 +350,6 @@ pub struct Context { // work results we've completed or restored from disk // We create individual caches so we can return typed results from get fns - features: Arc>>>>, - glyphs: Arc>>>, gvar_fragments: Arc>>>, @@ -355,6 +357,8 @@ pub struct Context { avar: ContextItem, cmap: ContextItem, fvar: ContextItem, + gsub: ContextItem, + gpos: ContextItem, gvar: ContextItem, post: ContextItem, loca_format: ContextItem, @@ -374,13 +378,14 @@ impl Context { paths: self.paths.clone(), ir: self.ir.clone(), acl, - features: self.features.clone(), glyphs: self.glyphs.clone(), gvar_fragments: self.gvar_fragments.clone(), glyf_loca: self.glyf_loca.clone(), avar: self.avar.clone(), cmap: self.cmap.clone(), fvar: self.fvar.clone(), + gsub: self.gsub.clone(), + gpos: self.gpos.clone(), gvar: self.gvar.clone(), post: self.post.clone(), loca_format: self.loca_format.clone(), @@ -400,13 +405,14 @@ impl Context { paths: Arc::from(paths), ir: Arc::from(ir.read_only()), acl: AccessControlList::read_only(), - features: Arc::from(RwLock::new(None)), glyphs: Arc::from(RwLock::new(HashMap::new())), gvar_fragments: Arc::from(RwLock::new(HashMap::new())), glyf_loca: Arc::from(RwLock::new(None)), avar: Arc::from(RwLock::new(None)), cmap: Arc::from(RwLock::new(None)), fvar: Arc::from(RwLock::new(None)), + gpos: Arc::from(RwLock::new(None)), + gsub: Arc::from(RwLock::new(None)), gvar: Arc::from(RwLock::new(None)), post: Arc::from(RwLock::new(None)), loca_format: Arc::from(RwLock::new(None)), @@ -437,13 +443,6 @@ impl Context { self.paths.debug_dir() } - fn maybe_persist(&self, file: &Path, content: &[u8]) { - if !self.flags.contains(Flags::EMIT_IR) { - return; - } - self.persist(file, content); - } - // we need a self.persist for macros fn persist(&self, file: &Path, content: &[u8]) { persist(file, content); @@ -454,29 +453,6 @@ impl Context { fs::read(self.paths.target_file(&id)) } - pub fn get_features(&self) -> Arc> { - let id = WorkId::Features; - self.acl.assert_read_access(&id.clone().into()); - { - let rl = self.features.read(); - if rl.is_some() { - return rl.as_ref().unwrap().clone(); - } - } - let font = read_entire_file(&self.paths.target_file(&id)); - set_cached(&self.features, font); - let rl = self.features.read(); - rl.as_ref().expect(MISSING_DATA).clone() - } - - pub fn set_features(&self, mut font: FontBuilder) { - let id = WorkId::Features; - self.acl.assert_write_access(&id.clone().into()); - let font = font.build(); - self.maybe_persist(&self.paths.target_file(&id), &font); - set_cached(&self.features, font); - } - fn set_cached_glyph(&self, glyph_name: GlyphName, glyph: Glyph) { let mut wl = self.glyphs.write(); wl.insert(glyph_name, Arc::from(glyph)); @@ -568,6 +544,23 @@ impl Context { rl.as_ref().expect(MISSING_DATA).clone() } + pub fn has_glyf_loca(&self) -> bool { + let ids = [ + WorkId::Glyf.into(), + WorkId::Loca.into(), + WorkId::LocaFormat.into(), + ]; + self.acl.assert_read_access_to_all(&ids); + { + let rl = self.glyf_loca.read(); + if rl.is_some() { + return true; + } + } + ids.iter() + .all(|id| self.paths.target_file(id.unwrap_be()).is_file()) + } + pub fn set_glyf_loca(&self, glyf_loca: GlyfLoca) { let ids = [ WorkId::Glyf.into(), @@ -586,21 +579,23 @@ impl Context { } // Lovely little typed accessors - context_accessors! { get_avar, set_avar, avar, Avar, WorkId::Avar, from_file, to_bytes } - context_accessors! { get_cmap, set_cmap, cmap, Cmap, WorkId::Cmap, from_file, to_bytes } - context_accessors! { get_fvar, set_fvar, fvar, Fvar, WorkId::Fvar, from_file, to_bytes } - context_accessors! { get_loca_format, set_loca_format, loca_format, LocaFormat, WorkId::LocaFormat, loca_format_from_file, loca_format_to_bytes } - context_accessors! { get_maxp, set_maxp, maxp, Maxp, WorkId::Maxp, from_file, to_bytes } - context_accessors! { get_name, set_name, name, Name, WorkId::Name, from_file, to_bytes } - context_accessors! { get_os2, set_os2, os2, Os2, WorkId::Os2, from_file, to_bytes } - context_accessors! { get_post, set_post, post, Post, WorkId::Post, from_file, to_bytes } - context_accessors! { get_head, set_head, head, Head, WorkId::Head, from_file, to_bytes } - context_accessors! { get_hhea, set_hhea, hhea, Hhea, WorkId::Hhea, from_file, to_bytes } + context_accessors! { get_avar, set_avar, has_avar, avar, Avar, WorkId::Avar, from_file, to_bytes } + context_accessors! { get_cmap, set_cmap, has_cmap, cmap, Cmap, WorkId::Cmap, from_file, to_bytes } + context_accessors! { get_fvar, set_fvar, has_fvar, fvar, Fvar, WorkId::Fvar, from_file, to_bytes } + context_accessors! { get_loca_format, set_loca_format, has_loca_format, loca_format, LocaFormat, WorkId::LocaFormat, loca_format_from_file, loca_format_to_bytes } + context_accessors! { get_maxp, set_maxp, has_maxp, maxp, Maxp, WorkId::Maxp, from_file, to_bytes } + context_accessors! { get_name, set_name, has_name, name, Name, WorkId::Name, from_file, to_bytes } + context_accessors! { get_os2, set_os2, has_os2, os2, Os2, WorkId::Os2, from_file, to_bytes } + context_accessors! { get_post, set_post, has_post, post, Post, WorkId::Post, from_file, to_bytes } + context_accessors! { get_head, set_head, has_head, head, Head, WorkId::Head, from_file, to_bytes } + context_accessors! { get_hhea, set_hhea, has_hhea, hhea, Hhea, WorkId::Hhea, from_file, to_bytes } + context_accessors! { get_gpos, set_gpos, has_gpos, gpos, Gpos, WorkId::Gpos, from_file, to_bytes } + context_accessors! { get_gsub, set_gsub, has_gsub, gsub, Gsub, WorkId::Gsub, from_file, to_bytes } // Accessors where value is raw bytes - context_accessors! { get_gvar, set_gvar, gvar, Bytes, WorkId::Gvar, raw_from_file, raw_to_bytes } - context_accessors! { get_hmtx, set_hmtx, hmtx, Bytes, WorkId::Hmtx, raw_from_file, raw_to_bytes } - context_accessors! { get_font, set_font, font, Bytes, WorkId::Font, raw_from_file, raw_to_bytes } + context_accessors! { get_gvar, set_gvar, has_gvar, gvar, Bytes, WorkId::Gvar, raw_from_file, raw_to_bytes } + context_accessors! { get_hmtx, set_hmtx, has_hmtx, hmtx, Bytes, WorkId::Hmtx, raw_from_file, raw_to_bytes } + context_accessors! { get_font, set_font, has_font, font, Bytes, WorkId::Font, raw_from_file, raw_to_bytes } } fn set_cached(lock: &Arc>>>, value: T) { diff --git a/fontbe/src/paths.rs b/fontbe/src/paths.rs index 03dc686c0..1238d48f3 100644 --- a/fontbe/src/paths.rs +++ b/fontbe/src/paths.rs @@ -47,11 +47,13 @@ impl Paths { pub fn target_file(&self, id: &WorkId) -> PathBuf { match id { - WorkId::Features => self.build_dir.join("features.ttf"), + WorkId::Features => self.build_dir.join("features.marker"), 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::Glyf => self.build_dir.join("glyf.table"), + WorkId::Gsub => self.build_dir.join("gsub.table"), + WorkId::Gpos => self.build_dir.join("gpos.table"), WorkId::Gvar => self.build_dir.join("gvar.table"), WorkId::Loca => self.build_dir.join("loca.table"), WorkId::LocaFormat => self.build_dir.join("loca.format"), diff --git a/fontc/src/args.rs b/fontc/src/args.rs index c40265c72..62b18508e 100644 --- a/fontc/src/args.rs +++ b/fontc/src/args.rs @@ -56,16 +56,22 @@ impl Args { /// Manually create args for testing #[cfg(test)] pub fn for_test(build_dir: &std::path::Path, source: &str) -> Args { - fn testdata_dir() -> PathBuf { - let path = PathBuf::from("../resources/testdata") - .canonicalize() - .unwrap(); - assert!(path.is_dir(), "{path:#?} isn't a dir"); - path - } + // cargo test seems to run in the project directory + // VSCode test seems to run in the workspace directory + // probe for the file we want in hopes of finding it regardless + let potential_paths = vec!["./resources/testdata", "../resources/testdata"]; + let source = potential_paths + .iter() + .map(PathBuf::from) + .find(|pb| pb.exists()) + .unwrap() + .join(source) + .canonicalize() + .unwrap(); + Args { glyph_name_filter: None, - source: testdata_dir().join(source), + source, emit_ir: true, emit_debug: false, build_dir: build_dir.to_path_buf(), diff --git a/fontc/src/lib.rs b/fontc/src/lib.rs index 98f173de3..14f4aa705 100644 --- a/fontc/src/lib.rs +++ b/fontc/src/lib.rs @@ -225,7 +225,13 @@ fn add_feature_be_job( ) -> Result<(), Error> { if change_detector.feature_be_change() && change_detector.glyph_name_filter().is_none() { let id: AnyWorkId = BeWorkIdentifier::Features.into(); - let write_access = Access::one(id.clone()); + + // fea-rs compiles, or will compile, several tables + // we want to capture them individually + let write_access = Access::Set(HashSet::from([ + BeWorkIdentifier::Gsub.into(), + BeWorkIdentifier::Gpos.into(), + ])); workload.insert( id, Job { @@ -244,6 +250,8 @@ fn add_feature_be_job( warn!("Not processing BE Features because a glyph name filter is active"); } workload.mark_success(BeWorkIdentifier::Features); + workload.mark_success(BeWorkIdentifier::Gpos); + workload.mark_success(BeWorkIdentifier::Gsub); } Ok(()) } @@ -562,6 +570,7 @@ fn add_os2_be_job( ) -> Result<(), Error> { if change_detector.init_static_metadata_ir_change() { let mut dependencies = HashSet::new(); + dependencies.insert(BeWorkIdentifier::Features.into()); dependencies.insert(FeWorkIdentifier::FinalizeStaticMetadata.into()); dependencies.insert(FeWorkIdentifier::GlobalMetrics.into()); dependencies.insert(BeWorkIdentifier::Hhea.into()); @@ -634,6 +643,7 @@ fn add_metric_and_limits_job( }, ); } else { + workload.mark_success(BeWorkIdentifier::Hhea); workload.mark_success(BeWorkIdentifier::Hmtx); } Ok(()) @@ -654,6 +664,8 @@ fn add_font_be_job( dependencies.insert(BeWorkIdentifier::Cmap.into()); dependencies.insert(BeWorkIdentifier::Fvar.into()); dependencies.insert(BeWorkIdentifier::Glyf.into()); + dependencies.insert(BeWorkIdentifier::Gpos.into()); + dependencies.insert(BeWorkIdentifier::Gsub.into()); dependencies.insert(BeWorkIdentifier::Gvar.into()); dependencies.insert(BeWorkIdentifier::Head.into()); dependencies.insert(BeWorkIdentifier::Hhea.into()); @@ -947,6 +959,8 @@ mod tests { BeWorkIdentifier::Glyf.into(), BeWorkIdentifier::GlyfFragment("bar".into()).into(), BeWorkIdentifier::GlyfFragment("plus".into()).into(), + BeWorkIdentifier::Gpos.into(), + BeWorkIdentifier::Gsub.into(), BeWorkIdentifier::Gvar.into(), BeWorkIdentifier::GvarFragment("bar".into()).into(), BeWorkIdentifier::GvarFragment("plus".into()).into(), @@ -1041,18 +1055,15 @@ mod tests { fn compile_fea() { let temp_dir = tempdir().unwrap(); let build_dir = temp_dir.path(); + compile(Args::for_test(build_dir, "static.designspace")); - let result = compile(Args::for_test(build_dir, "static.designspace")); - assert!( - result - .work_completed - .contains(&BeWorkIdentifier::Features.into()), - "Missing BE feature work in {:?}", - result.work_completed - ); + let font_file = build_dir.join("font.ttf"); + assert!(font_file.exists()); + let buf = fs::read(font_file).unwrap(); + let font = FontRef::new(&buf).unwrap(); - let feature_ttf = build_dir.join("features.ttf"); - assert!(feature_ttf.is_file(), "Should have written {feature_ttf:?}"); + assert!(font.gpos().is_ok()); + assert!(font.gsub().is_ok()); } fn build_contour_and_composite_glyph( diff --git a/fontc/src/workload.rs b/fontc/src/workload.rs index d4e332e4e..640e833bb 100644 --- a/fontc/src/workload.rs +++ b/fontc/src/workload.rs @@ -102,6 +102,12 @@ impl Workload { } } + // Features handles GSUB/POS + AnyWorkId::Be(BeWorkIdentifier::Features) => { + self.mark_success(AnyWorkId::Be(BeWorkIdentifier::Gpos)); + self.mark_success(AnyWorkId::Be(BeWorkIdentifier::Gsub)); + } + // GlyfFragment carries GvarFragment along for the ride AnyWorkId::Be(BeWorkIdentifier::GlyfFragment(glyph_name)) => { self.mark_success(AnyWorkId::Be(BeWorkIdentifier::GvarFragment(glyph_name))); @@ -261,7 +267,16 @@ impl Workload { } log::error!("Unable to proceed with:"); for (id, job) in self.jobs_pending.iter() { - log::error!(" {:?}, happens-after {:?}", id, job.dependencies); + let unfulfilled = job + .dependencies + .iter() + .filter(|id| !self.success.contains(id)) + .collect::>(); + log::error!( + " {:?}, happens-after {:?}, unfulfilled {unfulfilled:?}", + id, + job.dependencies + ); } assert!( !launchable.is_empty(), diff --git a/fontdrasil/src/orchestration.rs b/fontdrasil/src/orchestration.rs index 476baa0fc..16016caa3 100644 --- a/fontdrasil/src/orchestration.rs +++ b/fontdrasil/src/orchestration.rs @@ -18,7 +18,7 @@ pub enum Access { All, /// Access to one specific resource is permitted One(I), - /// Access to two specific resource is permitted + /// Access to multiple resources is permitted Set(HashSet), /// A closure is used to determine access Custom(Arc bool + Send + Sync>), diff --git a/fontir/src/orchestration.rs b/fontir/src/orchestration.rs index a1942d206..c93d219d1 100644 --- a/fontir/src/orchestration.rs +++ b/fontir/src/orchestration.rs @@ -44,7 +44,7 @@ pub type ContextItem = Arc>>>; /// #[macro_export] macro_rules! context_accessors { - ($getter_name:ident, $setter_name:ident, $lock_name:ident, $value_type:ty, $id:expr, $restore_fn:ident, $prepersist_fn:ident) => { + ($getter_name:ident, $setter_name:ident, $has_name:ident, $lock_name:ident, $value_type:ty, $id:expr, $restore_fn:ident, $prepersist_fn:ident) => { pub fn $getter_name(&self) -> Arc<$value_type> { let id = $id; self.acl.assert_read_access(&id.clone().into()); @@ -59,6 +59,18 @@ macro_rules! context_accessors { rl.as_ref().expect(MISSING_DATA).clone() } + pub fn $has_name(&self) -> bool { + let id = $id; + self.acl.assert_read_access(&id.clone().into()); + { + let rl = self.$lock_name.read(); + if rl.is_some() { + return true; + } + } + self.paths.target_file(&id).is_file() + } + pub fn $setter_name(&self, value: $value_type) { let id = $id; self.acl.assert_write_access(&id.clone().into()); @@ -218,10 +230,10 @@ impl Context { self.set_cached_glyph(ir); } - context_accessors! { get_init_static_metadata, set_init_static_metadata, init_static_metadata, ir::StaticMetadata, WorkId::InitStaticMetadata, restore, nop } - context_accessors! { get_final_static_metadata, set_final_static_metadata, final_static_metadata, ir::StaticMetadata, WorkId::FinalizeStaticMetadata, restore, nop } - context_accessors! { get_global_metrics, set_global_metrics, global_metrics, ir::GlobalMetrics, WorkId::GlobalMetrics, restore, nop } - context_accessors! { get_features, set_features, feature_ir, ir::Features, WorkId::Features, restore, nop } + context_accessors! { get_init_static_metadata, set_init_static_metadata, has_init_static_metadata, init_static_metadata, ir::StaticMetadata, WorkId::InitStaticMetadata, restore, nop } + context_accessors! { get_final_static_metadata, set_final_static_metadata, has_final_static_metadata, final_static_metadata, ir::StaticMetadata, WorkId::FinalizeStaticMetadata, restore, nop } + context_accessors! { get_global_metrics, set_global_metrics, has_global_metrics, global_metrics, ir::GlobalMetrics, WorkId::GlobalMetrics, restore, nop } + context_accessors! { get_features, set_features, has_feature_ir, feature_ir, ir::Features, WorkId::Features, restore, nop } } fn nop(v: &T) -> &T { diff --git a/resources/testdata/Static-Regular.ufo/features.fea b/resources/testdata/Static-Regular.ufo/features.fea index 8afa30ac5..cf2e0eab5 100644 --- a/resources/testdata/Static-Regular.ufo/features.fea +++ b/resources/testdata/Static-Regular.ufo/features.fea @@ -3,4 +3,8 @@ languagesystem latn dflt; feature kern { position bar plus -100; -} kern; \ No newline at end of file +} kern; + +feature liga { + substitute bar bar by plus; +} liga; \ No newline at end of file