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

fea-rs: conditional compilation of GPOS & GSUB #703

Merged
merged 3 commits into from
Feb 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 16 additions & 3 deletions fea-rs/src/bin/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,10 @@ fn run() -> Result<(), Error> {
}

let var_info = args.get_var_info().transpose()?;
let opts = args.opts();

let mut compiler: Compiler<'_, NopFeatureProvider, MockVariationInfo> =
Compiler::new(fea, &glyph_names).with_opts(Opts::new().make_post_table(args.post));
Compiler::new(fea, &glyph_names).with_opts(opts);
if let Some(var_info) = var_info.as_ref() {
log::info!("compiling with {} mock variation axes", var_info.axes.len());
for axis in &var_info.axes {
Expand All @@ -53,9 +54,8 @@ fn run() -> Result<(), Error> {
let compiled = compiler.compile()?;

let path = args.out_path();
let opts = Opts::new().make_post_table(args.post);
let raw_font = compiled
.to_binary(&glyph_names, opts)
.to_binary(&glyph_names)
.expect("ttf compile failed");

log::info!("writing {} bytes to {}", raw_font.len(), path.display());
Expand Down Expand Up @@ -137,6 +137,12 @@ struct Args {
/// Optionally write a post table to the generated font
#[arg(short, long)]
post: bool,

#[arg(long)]
skip_gpos: bool,

#[arg(long)]
skip_gsub: bool,
}

impl Args {
Expand Down Expand Up @@ -184,6 +190,13 @@ impl Args {
self.glyph_order.as_deref()
}

fn opts(&self) -> Opts {
Opts::new()
.make_post_table(self.post)
.compile_gpos(!self.skip_gpos)
.compile_gsub(!self.skip_gsub)
}

fn out_path(&self) -> &Path {
self.out_path
.as_deref()
Expand Down
3 changes: 2 additions & 1 deletion fea-rs/src/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,9 @@ pub fn compile<V: VariationInfo, T: FeatureProvider>(
glyph_map: &GlyphMap,
var_info: Option<&V>,
extra_features: Option<&T>,
opts: Opts,
) -> Result<(Compilation, DiagnosticSet), DiagnosticSet> {
let mut ctx = CompilationCtx::new(glyph_map, tree.source_map(), var_info, extra_features);
let mut ctx = CompilationCtx::new(glyph_map, tree.source_map(), var_info, extra_features, opts);
ctx.compile(&tree.typed_root());
match ctx.build() {
Ok((compilation, warnings)) => {
Expand Down
16 changes: 12 additions & 4 deletions fea-rs/src/compile/compile_ctx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ use crate::{
Token,
},
typed::ContextualRuleNode,
Diagnostic, GlyphIdent, GlyphMap, Kind, NodeOrToken,
Diagnostic, GlyphIdent, GlyphMap, Kind, NodeOrToken, Opts,
};

use super::{
Expand Down Expand Up @@ -68,6 +68,7 @@ pub struct CompilationCtx<'a, F: FeatureProvider, V: VariationInfo> {
source_map: &'a SourceMap,
variation_info: Option<&'a V>,
feature_writer: Option<&'a F>,
opts: Opts,
/// Any errors or warnings generated during compilation.
pub errors: Vec<Diagnostic>,
/// Stores any [specified table values][tables] in the input FEA.
Expand Down Expand Up @@ -99,6 +100,7 @@ impl<'a, F: FeatureProvider, V: VariationInfo> CompilationCtx<'a, F, V> {
source_map: &'a SourceMap,
variation_info: Option<&'a V>,
feature_writer: Option<&'a F>,
opts: Opts,
) -> Self {
CompilationCtx {
glyph_map,
Expand All @@ -122,6 +124,7 @@ impl<'a, F: FeatureProvider, V: VariationInfo> CompilationCtx<'a, F, V> {
script: Default::default(),
mark_attach_class_id: Default::default(),
mark_filter_sets: Default::default(),
opts,
}
}

Expand Down Expand Up @@ -190,7 +193,7 @@ impl<'a, F: FeatureProvider, V: VariationInfo> CompilationCtx<'a, F, V> {
.unwrap_or_default();
let mut ivs = VariationStoreBuilder::new(axis_count);

let (mut gsub, mut gpos) = self.lookups.build(&self.features, &mut ivs);
let (mut gsub, mut gpos) = self.lookups.build(&self.features, &mut ivs, &self.opts);
if !ivs.is_empty() {
self.tables
.gdef
Expand Down Expand Up @@ -249,6 +252,7 @@ impl<'a, F: FeatureProvider, V: VariationInfo> CompilationCtx<'a, F, V> {
stat,
gsub,
gpos,
opts: self.opts.clone(),
},
self.errors.clone(),
))
Expand Down Expand Up @@ -1773,9 +1777,13 @@ impl<'a, F: FeatureProvider, V: VariationInfo> CompilationCtx<'a, F, V> {
} else if let Some(lookup) = typed::LookupBlock::cast(item) {
self.resolve_lookup_block(lookup);
} else if let Some(rule) = typed::GsubStatement::cast(item) {
self.add_gsub_statement(rule);
if self.opts.compile_gsub {
self.add_gsub_statement(rule);
}
} else if let Some(rule) = typed::GposStatement::cast(item) {
self.add_gpos_statement(rule)
if self.opts.compile_gpos {
self.add_gpos_statement(rule)
}
} else if item.kind() == Kind::Semi {
// continue
} else {
Expand Down
16 changes: 4 additions & 12 deletions fea-rs/src/compile/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ pub struct Compiler<'a, F: FeatureProvider, V: VariationInfo> {
// variable fonts only
var_info: Option<&'a V>,
feature_writer: Option<&'a F>,
// this is not in `Opts` because it is specific to the compiler struct;
// if you're compiling manually you are responsible for handling warnings.
print_warnings: bool,
max_n_errors: usize,
opts: Opts,
Expand Down Expand Up @@ -95,16 +97,6 @@ impl<'a, F: FeatureProvider, V: VariationInfo> Compiler<'a, F, V> {
self
}

/// Specify a maximum number of messages to print when errors occur.
///
/// Default is some arbitrary 'reasonable' number (currently 100.) To
/// suppress errors, pass `0`. To print all errors, pass a number as large
/// as the number of errors you intend to write.
pub fn max_error_messages(mut self, max_n_errors: usize) -> Self {
self.max_n_errors = max_n_errors;
self
}

/// Specify an explicit project root.
///
/// This is useful in cases where import resolution is based on an explicit
Expand Down Expand Up @@ -151,6 +143,7 @@ impl<'a, F: FeatureProvider, V: VariationInfo> Compiler<'a, F, V> {
tree.source_map(),
self.var_info,
self.feature_writer,
self.opts,
);
ctx.compile(&tree.typed_root());

Expand All @@ -165,9 +158,8 @@ impl<'a, F: FeatureProvider, V: VariationInfo> Compiler<'a, F, V> {

/// Compile to a binary font.
pub fn compile_binary(self) -> Result<Vec<u8>, CompilerError> {
let opts = self.opts.clone();
let glyph_map = self.glyph_map;
Ok(self.compile()?.to_binary(glyph_map, opts)?)
Ok(self.compile()?.to_binary(glyph_map)?)
}
}

Expand Down
11 changes: 6 additions & 5 deletions fea-rs/src/compile/lookups.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ use write_fonts::{
use crate::{
common::{GlyphId, GlyphOrClass, GlyphSet},
compile::{lookups::contextual::ChainOrNot, metrics::ValueRecord},
Kind,
Kind, Opts,
};

use super::{features::AllFeatures, metrics::Anchor, tables::ClassId, tags};
Expand Down Expand Up @@ -646,6 +646,7 @@ impl AllLookups {
&self,
features: &AllFeatures,
var_store: &mut VariationStoreBuilder,
opts: &Opts,
) -> (Option<write_gsub::Gsub>, Option<write_gpos::Gpos>) {
let mut gpos_builder = PosSubBuilder::new(self.gpos.clone());
let mut gsub_builder = PosSubBuilder::new(self.gsub.clone());
Expand All @@ -661,17 +662,17 @@ impl AllLookups {
let (gpos_idxes, gsub_idxes) = feature_lookups.split_base_lookups();
let mut gpos_feat_id = None;
let mut gsub_feat_id = None;
if !gpos_idxes.is_empty() {
if opts.compile_gpos && !gpos_idxes.is_empty() {
gpos_feat_id = Some(gpos_builder.add(*key, gpos_idxes.clone(), required));
}

if !gsub_idxes.is_empty() {
if opts.compile_gsub && !gsub_idxes.is_empty() {
gsub_feat_id = Some(gsub_builder.add(*key, gsub_idxes.clone(), required));
}

let variations = feature_lookups.split_variations();
for (cond, gpos_var_idxes, gsub_var_idxes) in variations {
if !gpos_var_idxes.is_empty() {
if opts.compile_gpos && !gpos_var_idxes.is_empty() {
// add the lookups for the base feature
let mut all_ids = gpos_idxes.clone();
all_ids.extend(gpos_var_idxes);
Expand All @@ -682,7 +683,7 @@ impl AllLookups {
.get_or_insert_with(|| gpos_builder.add(*key, Vec::new(), false));
gpos_builder.add_variation(*feat_id, cond, all_ids);
}
if !gsub_var_idxes.is_empty() {
if opts.compile_gsub && !gsub_var_idxes.is_empty() {
// add the lookups for the base feature
let mut all_ids = gsub_idxes.clone();
all_ids.extend(gsub_var_idxes);
Expand Down
84 changes: 83 additions & 1 deletion fea-rs/src/compile/opts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,15 @@
// a very important part of our API, and a more natural place for us to specify
// options is in the 'Compiler' struct itself.

const DEFAULT_N_MESSAGES_TO_PRINT: usize = 100;

/// Options for configuring compilation behaviour.
#[derive(Clone, Debug, Default)]
#[derive(Clone, Debug)]
pub struct Opts {
pub(crate) make_post_table: bool,
pub(crate) max_n_errors: usize,
pub(crate) compile_gsub: bool,
pub(crate) compile_gpos: bool,
}

impl Opts {
Expand All @@ -22,4 +27,81 @@ impl Opts {
self.make_post_table = flag;
self
}

/// Specify the number of errors to print when printing a [`DiagnosticSet`].
///
/// Default is some arbitrary 'reasonable' number (currently 100.) To
/// suppress errors, pass `0`. For 'all errors', pass `usize::MAX`.
///
/// [`DiagnosticSet`]: crate::DiagnosticSet
pub fn max_error_messages(mut self, max_n_errors: usize) -> Self {
Copy link
Contributor

Choose a reason for hiding this comment

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

Optional, I think I might have expected an option (-1?) to print all of them

Copy link
Member Author

Choose a reason for hiding this comment

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

this is my fault for trying to be funny in my docs. Can update to just say usize::MAX, the joke isn't good anyway 😅

self.max_n_errors = max_n_errors;
self
}

/// Specify whether or not we should compile the GPOS table. Default is `true`.
pub fn compile_gpos(mut self, flag: bool) -> Self {
self.compile_gpos = flag;
self
}

/// Specify whether or not we should compile the GSUB table. Default is `true`.
pub fn compile_gsub(mut self, flag: bool) -> Self {
self.compile_gsub = flag;
self
}
}

impl Default for Opts {
fn default() -> Self {
Self {
make_post_table: false,
max_n_errors: DEFAULT_N_MESSAGES_TO_PRINT,
compile_gsub: true,
compile_gpos: true,
}
}
}

#[cfg(test)]
mod tests {

static OSWALD_DIR: &str = "./test-data/real-files/oswald";
use std::path::Path;

use crate::{
compile::{Compilation, MockVariationInfo, NopFeatureProvider},
Compiler,
};

use super::*;

#[test]
fn skip_tables() {
fn compile_oswald(opts: Opts) -> Compilation {
let glyph_order = Path::new(OSWALD_DIR).join("glyph_order.txt");
let features = Path::new(OSWALD_DIR).join("features.fea");
let glyph_order = std::fs::read_to_string(glyph_order).unwrap();
let glyph_order = crate::compile::parse_glyph_order(&glyph_order).unwrap();
Compiler::<NopFeatureProvider, MockVariationInfo>::new(&features, &glyph_order)
.with_opts(opts)
.compile()
.unwrap()
}

// compile everything:
let compilation = compile_oswald(Opts::new());
assert!(compilation.gpos.is_some());
assert!(compilation.gsub.is_some());

// only gpos
let compilation = compile_oswald(Opts::new().compile_gsub(false));
assert!(compilation.gpos.is_some());
assert!(compilation.gsub.is_none());

// only gsub
let compilation = compile_oswald(Opts::new().compile_gpos(false));
assert!(compilation.gpos.is_none());
assert!(compilation.gsub.is_some());
}
}
6 changes: 4 additions & 2 deletions fea-rs/src/compile/output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ use crate::GlyphMap;
///
/// [`to_binary`]: Compilation::to_binary
pub struct Compilation {
/// The options passed in for this compilation.
pub(crate) opts: Opts,
/// The `head` table, if one was generated
pub head: Option<wtables::head::Head>,
/// The `hhea` table, if one was generated
Expand Down Expand Up @@ -77,13 +79,13 @@ impl Compilation {
/// This is a convenience method used for things like testing; if you are
/// building a font compiler you will probably prefer to manipulate the
/// generated tables directly.
pub fn to_binary(&self, glyph_map: &GlyphMap, opts: Opts) -> Result<Vec<u8>, BuilderError> {
pub fn to_binary(&self, glyph_map: &GlyphMap) -> Result<Vec<u8>, BuilderError> {
// because we often inspect our output with ttx, and ttx fails if maxp is
// missing, we create a maxp table.
let mut builder = self.to_font_builder()?;
let maxp = Maxp::new(glyph_map.len().try_into().unwrap());
builder.add_table(&maxp)?;
if opts.make_post_table {
if self.opts.make_post_table {
let post = glyph_map.make_post_table();
builder.add_table(&post)?;
}
Expand Down
2 changes: 1 addition & 1 deletion fea-rs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ pub mod util;
mod tests;

pub use common::{GlyphIdent, GlyphMap, GlyphName, GlyphSet};
pub use compile::Compiler;
pub use compile::{Compiler, Opts};
pub use diagnostic::{Diagnostic, DiagnosticSet, Level};
pub use parse::{ParseTree, TokenSet};
pub use token_tree::{typed, Kind, Node, NodeOrToken, Token};
1 change: 1 addition & 0 deletions fea-rs/test-data/real-files/oswald/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
This file includes GPOS features generated by fontmake feature writers.
Loading
Loading