Skip to content

Commit

Permalink
Capture anchors
Browse files Browse the repository at this point in the history
  • Loading branch information
rsheeter committed Sep 15, 2023
1 parent 20675e2 commit 4fb8c80
Show file tree
Hide file tree
Showing 9 changed files with 320 additions and 60 deletions.
119 changes: 79 additions & 40 deletions fontc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -496,53 +496,91 @@ mod tests {
result
}

#[test]
fn compile_work() {
fn assert_compile_work(source: &str, glyphs: Vec<&str>) {
let temp_dir = tempdir().unwrap();
let build_dir = temp_dir.path();

let result = compile(Args::for_test(build_dir, "wght_var.designspace"));
let result = compile(Args::for_test(build_dir, source));
let mut completed = result.work_executed.iter().cloned().collect::<Vec<_>>();

let mut expected = vec![
AnyWorkId::Fe(FeWorkIdentifier::StaticMetadata),
FeWorkIdentifier::GlobalMetrics.into(),
FeWorkIdentifier::PreliminaryGlyphOrder.into(),
FeWorkIdentifier::GlyphOrder.into(),
FeWorkIdentifier::Features.into(),
FeWorkIdentifier::Kerning.into(),
BeWorkIdentifier::Features.into(),
BeWorkIdentifier::Avar.into(),
BeWorkIdentifier::Cmap.into(),
BeWorkIdentifier::Font.into(),
BeWorkIdentifier::Fvar.into(),
BeWorkIdentifier::Glyf.into(),
BeWorkIdentifier::Gpos.into(),
BeWorkIdentifier::Gsub.into(),
BeWorkIdentifier::Gdef.into(),
BeWorkIdentifier::Gvar.into(),
BeWorkIdentifier::Head.into(),
BeWorkIdentifier::Hhea.into(),
BeWorkIdentifier::Hmtx.into(),
BeWorkIdentifier::Loca.into(),
BeWorkIdentifier::LocaFormat.into(),
BeWorkIdentifier::Maxp.into(),
BeWorkIdentifier::Name.into(),
BeWorkIdentifier::Os2.into(),
BeWorkIdentifier::Post.into(),
BeWorkIdentifier::Stat.into(),
];

expected.extend(
glyphs
.iter()
.map(|n| FeWorkIdentifier::Glyph((*n).into()).into()),
);
expected.extend(
glyphs
.iter()
.map(|n| FeWorkIdentifier::Anchor((*n).into()).into()),
);
expected.extend(
glyphs
.iter()
.map(|n| BeWorkIdentifier::GlyfFragment((*n).into()).into()),
);
expected.extend(
glyphs
.iter()
.map(|n| BeWorkIdentifier::GvarFragment((*n).into()).into()),
);

expected.extend(vec![
BeWorkIdentifier::GlyfFragment((".notdef").into()).into(),
BeWorkIdentifier::GvarFragment((".notdef").into()).into(),
]);

completed.sort();
assert_eq!(
expected.sort();
assert_eq!(expected, completed);
}

#[test]
fn compile_work_for_designspace() {
assert_compile_work("wght_var.designspace", vec!["bar", "plus"])
}

#[test]
fn compile_work_for_glyphs() {
assert_compile_work(
"glyphs3/WghtVar.glyphs",
vec![
AnyWorkId::Fe(FeWorkIdentifier::StaticMetadata),
FeWorkIdentifier::GlobalMetrics.into(),
FeWorkIdentifier::Glyph("bar".into()).into(),
FeWorkIdentifier::Glyph("plus".into()).into(),
FeWorkIdentifier::PreliminaryGlyphOrder.into(),
FeWorkIdentifier::GlyphOrder.into(),
FeWorkIdentifier::Features.into(),
FeWorkIdentifier::Kerning.into(),
BeWorkIdentifier::Features.into(),
BeWorkIdentifier::Avar.into(),
BeWorkIdentifier::Cmap.into(),
BeWorkIdentifier::Font.into(),
BeWorkIdentifier::Fvar.into(),
BeWorkIdentifier::Glyf.into(),
BeWorkIdentifier::GlyfFragment(".notdef".into()).into(),
BeWorkIdentifier::GlyfFragment("bar".into()).into(),
BeWorkIdentifier::GlyfFragment("plus".into()).into(),
BeWorkIdentifier::Gpos.into(),
BeWorkIdentifier::Gsub.into(),
BeWorkIdentifier::Gdef.into(),
BeWorkIdentifier::Gvar.into(),
BeWorkIdentifier::GvarFragment(".notdef".into()).into(),
BeWorkIdentifier::GvarFragment("bar".into()).into(),
BeWorkIdentifier::GvarFragment("plus".into()).into(),
BeWorkIdentifier::Head.into(),
BeWorkIdentifier::Hhea.into(),
BeWorkIdentifier::Hmtx.into(),
BeWorkIdentifier::Loca.into(),
BeWorkIdentifier::LocaFormat.into(),
BeWorkIdentifier::Maxp.into(),
BeWorkIdentifier::Name.into(),
BeWorkIdentifier::Os2.into(),
BeWorkIdentifier::Post.into(),
BeWorkIdentifier::Stat.into(),
"bracketleft",
"bracketright",
"exclam",
"hyphen",
"manual-component",
"space",
],
completed
);
)
}

#[test]
Expand Down Expand Up @@ -582,6 +620,7 @@ mod tests {
assert_eq!(
vec![
AnyWorkId::Fe(FeWorkIdentifier::Glyph("bar".into())),
FeWorkIdentifier::Anchor("bar".into()).into(),
BeWorkIdentifier::Cmap.into(),
BeWorkIdentifier::Font.into(),
BeWorkIdentifier::Glyf.into(),
Expand Down
13 changes: 12 additions & 1 deletion fontir/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::{error, io, path::PathBuf};

use font_types::{InvalidTag, Tag};
use fontdrasil::types::GlyphName;
use fontdrasil::types::{AnchorName, GlyphName};
use kurbo::Point;
use thiserror::Error;

Expand Down Expand Up @@ -134,6 +134,17 @@ pub enum WorkError {
AxisMustMapMax(Tag),
#[error("No kerning group or glyph for name {0:?}")]
InvalidKernSide(String),
#[error("Multiple definitions for glyph {glyph} anchor {anchor} at {loc:?}")]
AmbiguousAnchor {
glyph: GlyphName,
anchor: AnchorName,
loc: NormalizedLocation,
},
#[error("No value at default for glyph {glyph} anchor {anchor}")]
NoDefaultForAnchor {
glyph: GlyphName,
anchor: AnchorName,
},
}

/// An async work error, hence one that must be Send
Expand Down
86 changes: 82 additions & 4 deletions fontir/src/ir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -821,10 +821,22 @@ impl Features {
}
}

/// The complete set of anchor data
/// The anchors for a [Glyph]
///
/// Not having any is fine.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct Anchors {
pub anchors: HashMap<GlyphName, Vec<Anchor>>,
pub struct GlyphAnchors {
pub glyph_name: GlyphName,
pub anchors: Vec<Anchor>,
}

impl GlyphAnchors {
pub fn new(glyph_name: GlyphName, anchors: Vec<Anchor>) -> Result<Self, WorkError> {
Ok(GlyphAnchors {
glyph_name,
anchors,
})
}
}

/// A variable definition of an anchor.
Expand All @@ -836,6 +848,66 @@ pub struct Anchor {
pub positions: HashMap<NormalizedLocation, Point>,
}

#[derive(Debug, Clone)]
pub struct AnchorBuilder {
glyph_name: GlyphName,
anchors: HashMap<AnchorName, HashMap<NormalizedLocation, Point>>,
}

impl AnchorBuilder {
pub fn new(glyph_name: GlyphName) -> Self {
Self {
glyph_name,
anchors: Default::default(),
}
}

pub fn add(
&mut self,
name: AnchorName,
loc: NormalizedLocation,
pos: Point,
) -> Result<(), WorkError> {
if self
.anchors
.entry(name.clone())
.or_default()
.insert(loc.clone(), pos)
.is_some()
{
return Err(WorkError::AmbiguousAnchor {
glyph: self.glyph_name.clone(),
anchor: name,
loc,
});
}
Ok(())
}
}

impl TryInto<GlyphAnchors> for AnchorBuilder {
type Error = WorkError;

fn try_into(self) -> Result<GlyphAnchors, Self::Error> {
// It would be nice if everyone was defined at default
for (anchor, positions) in self.anchors.iter() {
if !positions.keys().any(|loc| !loc.has_any_non_zero()) {
return Err(WorkError::NoDefaultForAnchor {
glyph: self.glyph_name.clone(),
anchor: anchor.clone(),
});
}
}
GlyphAnchors::new(
self.glyph_name,
self.anchors
.into_iter()
.map(|(name, positions)| Anchor { name, positions })
.collect(),
)
}
}

/// A variable definition of a single glyph.
///
/// Guarrantees at least one definition. Currently that must be at
Expand Down Expand Up @@ -969,7 +1041,13 @@ impl Persistable for Kerning {
}
}

impl Persistable for Anchors {
impl IdAware<WorkId> for GlyphAnchors {
fn id(&self) -> WorkId {
WorkId::Anchor(self.glyph_name.clone())
}
}

impl Persistable for GlyphAnchors {
fn read(from: &mut dyn Read) -> Self {
serde_yaml::from_reader(from).unwrap()
}
Expand Down
6 changes: 3 additions & 3 deletions fontir/src/orchestration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ pub enum WorkId {
GlyphOrder,
Features,
Kerning,
Anchors,
Anchor(GlyphName),
}

impl Identifier for WorkId {}
Expand Down Expand Up @@ -371,7 +371,7 @@ pub struct Context {
pub glyphs: FeContextMap<ir::Glyph>,
pub features: FeContextItem<ir::Features>,
pub kerning: FeContextItem<ir::Kerning>,
pub anchors: FeContextItem<ir::Anchors>,
pub anchors: FeContextMap<ir::GlyphAnchors>,
}

pub fn set_cached<T>(lock: &Arc<RwLock<Option<Arc<T>>>>, value: T) {
Expand Down Expand Up @@ -430,7 +430,7 @@ impl Context {
glyphs: ContextMap::new(acl.clone(), persistent_storage.clone()),
features: ContextItem::new(WorkId::Features, acl.clone(), persistent_storage.clone()),
kerning: ContextItem::new(WorkId::Kerning, acl.clone(), persistent_storage.clone()),
anchors: ContextItem::new(WorkId::Anchors, acl, persistent_storage),
anchors: ContextMap::new(acl, persistent_storage),
}
}

Expand Down
9 changes: 8 additions & 1 deletion fontir/src/paths.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,20 @@ use crate::orchestration::WorkId;
#[derive(Debug, Clone)]
pub struct Paths {
build_dir: PathBuf,
anchor_ir_dir: PathBuf,
glyph_ir_dir: PathBuf,
ir_input_file: PathBuf,
}

impl Paths {
pub fn new(build_dir: &Path) -> Paths {
let build_dir = build_dir.to_path_buf();
let anchor_ir_dir = build_dir.join("glyph_ir");
let glyph_ir_dir = build_dir.join("glyph_ir");
let ir_input_file = build_dir.join("irinput.yml");
Paths {
build_dir,
anchor_ir_dir,
glyph_ir_dir,
ir_input_file,
}
Expand All @@ -37,13 +40,17 @@ impl Paths {
&self.ir_input_file
}

fn anchor_ir_file(&self, name: &str) -> PathBuf {
self.anchor_ir_dir.join(glyph_file(name, ".yml"))
}

fn glyph_ir_file(&self, name: &str) -> PathBuf {
self.glyph_ir_dir.join(glyph_file(name, ".yml"))
}

pub fn target_file(&self, id: &WorkId) -> PathBuf {
match id {
WorkId::Anchors => self.build_dir.join("anchors.yml"),
WorkId::Anchor(name) => self.anchor_ir_file(name.as_str()),
WorkId::StaticMetadata => self.build_dir.join("static_metadata.yml"),
WorkId::PreliminaryGlyphOrder => self.build_dir.join("glyph_order.preliminary.yml"),
WorkId::GlyphOrder => self.build_dir.join("glyph_order.yml"),
Expand Down
3 changes: 2 additions & 1 deletion fontir/src/source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ pub trait Source {
/// Expected to return a Vec aligned with the glyph_names input. That is,
/// result vec nth entry is the work for the nth glyph name.
///
/// When run work should update [Context] with [crate::ir::Glyph] for the glyph name.
/// When run work should update [Context] with [crate::ir::Glyph] and [crate::ir::Anchor]
/// for the glyph name.
fn create_glyph_ir_work(
&self,
glyph_names: &IndexSet<GlyphName>,
Expand Down
Loading

0 comments on commit 4fb8c80

Please sign in to comment.