Skip to content

Commit

Permalink
Merge pull request #438 from googlefonts/glyph_anchors
Browse files Browse the repository at this point in the history
Add support for anchors to IR and glyphs2fontir
  • Loading branch information
rsheeter authored Sep 14, 2023
2 parents d12a5df + 69e21ff commit b4f744d
Show file tree
Hide file tree
Showing 7 changed files with 507 additions and 13 deletions.
1 change: 1 addition & 0 deletions fontdrasil/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,4 @@ impl Display for GlyphName {
}

pub type GroupName = GlyphName;
pub type AnchorName = GlyphName;
27 changes: 26 additions & 1 deletion fontir/src/ir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use crate::{
use chrono::{DateTime, Utc};
use font_types::NameId;
use font_types::Tag;
use fontdrasil::types::{GlyphName, GroupName};
use fontdrasil::types::{AnchorName, GlyphName, GroupName};
use indexmap::IndexSet;
use kurbo::{Affine, BezPath, PathEl, Point};
use log::warn;
Expand Down Expand Up @@ -821,6 +821,21 @@ impl Features {
}
}

/// The complete set of anchor data
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct Anchors {
pub anchors: HashMap<GlyphName, Vec<Anchor>>,
}

/// A variable definition of an anchor.
///
/// Must have at least one definition, at the default location.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct Anchor {
pub name: AnchorName,
pub positions: HashMap<NormalizedLocation, Point>,
}

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

impl Persistable for Anchors {
fn read(from: &mut dyn Read) -> Self {
serde_yaml::from_reader(from).unwrap()
}

fn write(&self, to: &mut dyn std::io::Write) {
serde_yaml::to_writer(to, self).unwrap();
}
}

/// A variable definition of a single glyph.
///
/// If defined in many locations, presumed to vary continuously
Expand Down
8 changes: 6 additions & 2 deletions fontir/src/orchestration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,7 @@ pub enum WorkId {
GlyphOrder,
Features,
Kerning,
Anchors,
}

impl Identifier for WorkId {}
Expand Down Expand Up @@ -370,6 +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 fn set_cached<T>(lock: &Arc<RwLock<Option<Arc<T>>>>, value: T) {
Expand All @@ -390,7 +392,8 @@ impl Context {
global_metrics: self.global_metrics.clone_with_acl(acl.clone()),
glyphs: self.glyphs.clone_with_acl(acl.clone()),
features: self.features.clone_with_acl(acl.clone()),
kerning: self.kerning.clone_with_acl(acl),
kerning: self.kerning.clone_with_acl(acl.clone()),
anchors: self.anchors.clone_with_acl(acl),
}
}

Expand Down Expand Up @@ -426,7 +429,8 @@ 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, persistent_storage),
kerning: ContextItem::new(WorkId::Kerning, acl.clone(), persistent_storage.clone()),
anchors: ContextItem::new(WorkId::Anchors, acl, persistent_storage),
}
}

Expand Down
1 change: 1 addition & 0 deletions fontir/src/paths.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ impl Paths {

pub fn target_file(&self, id: &WorkId) -> PathBuf {
match id {
WorkId::Anchors => self.build_dir.join("anchors.yml"),
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
86 changes: 76 additions & 10 deletions glyphs-reader/src/font.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ pub struct Layer {
pub layer_id: String,
pub width: OrderedFloat<f64>,
pub shapes: Vec<Shape>,
pub anchors: Vec<Anchor>,
}

#[derive(Debug, PartialEq, Hash)]
Expand All @@ -101,7 +102,7 @@ pub enum Shape {

// The font you get directly from a plist, minimally modified
// Types chosen specifically to accomodate plist translation.
#[derive(Debug, FromPlist, PartialEq, Eq)]
#[derive(Debug, FromPlist, PartialEq)]
#[allow(non_snake_case)]
struct RawFont {
pub units_per_em: Option<i64>,
Expand Down Expand Up @@ -166,7 +167,7 @@ pub struct Axis {
pub hidden: Option<bool>,
}

#[derive(Clone, Debug, FromPlist, PartialEq, Eq)]
#[derive(Clone, Debug, FromPlist, PartialEq)]
pub struct RawGlyph {
pub layers: Vec<RawLayer>,
pub glyphname: String,
Expand All @@ -176,15 +177,15 @@ pub struct RawGlyph {
pub other_stuff: BTreeMap<String, Plist>,
}

#[derive(Clone, Debug, FromPlist, PartialEq, Eq)]
#[derive(Clone, Debug, FromPlist, PartialEq)]
pub struct RawLayer {
pub layer_id: String,
pub associated_master_id: Option<String>,
pub width: OrderedFloat<f64>,
shapes: Option<Vec<RawShape>>,
paths: Option<Vec<Path>>,
components: Option<Vec<Component>>,
//pub anchors: Option<Vec<Anchor>>,
pub anchors: Option<Vec<RawAnchor>>,
#[fromplist(rest)]
pub other_stuff: BTreeMap<String, Plist>,
}
Expand Down Expand Up @@ -273,10 +274,24 @@ pub enum NodeType {
QCurveSmooth,
}

#[derive(Clone, Debug, FromPlist, PartialEq)]
pub struct RawAnchor {
pub name: String,
pub pos: Option<Point>, // v3
pub position: Option<String>, // v2
}

#[derive(Clone, Debug, FromPlist, PartialEq)]
pub struct Anchor {
pub name: String,
pub position: Point,
pub pos: Point,
}

impl Hash for Anchor {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.name.hash(state);
PointForEqAndHash::new(self.pos).hash(state);
}
}

#[derive(Clone, Debug, PartialEq, Hash)]
Expand Down Expand Up @@ -541,10 +556,17 @@ impl FromPlist for Affine {
impl FromPlist for Point {
fn from_plist(plist: Plist) -> Self {
let raw = plist.as_str().unwrap();
let raw = &raw[1..raw.len() - 1];
let coords: Vec<f64> = raw.split(", ").map(|c| c.parse().unwrap()).collect();
Point::new(coords[0], coords[1])
match plist {
Plist::Array(values) if values.len() == 2 => {
Point::new(values[0].as_f64().unwrap(), values[1].as_f64().unwrap())
}
Plist::String(value) => {
let raw = &value[1..value.len() - 1];
let coords: Vec<f64> = raw.split(", ").map(|c| c.parse().unwrap()).collect();
Point::new(coords[0], coords[1])
}
_ => panic!("Cannot parse point from {plist:?}"),
}
}
}

Expand Down Expand Up @@ -1346,10 +1368,27 @@ impl TryFrom<RawLayer> for Layer {
shapes.push(raw_shape.try_into()?);
}

let anchors = from
.anchors
.unwrap_or_default()
.into_iter()
.map(|ra| {
let pos = if let Some(pos) = ra.pos {
pos
} else if let Some(pos) = ra.position {
Point::from_plist(Plist::String(pos))
} else {
panic!("No position for anchor {ra:?}");
};
Anchor { name: ra.name, pos }
})
.collect();

Ok(Layer {
layer_id: from.layer_id,
width: from.width,
shapes,
anchors,
})
}
}
Expand Down Expand Up @@ -1747,7 +1786,7 @@ mod tests {

use pretty_assertions::assert_eq;

use kurbo::Affine;
use kurbo::{Affine, Point};

fn testdata_dir() -> PathBuf {
// working dir varies CLI vs VSCode
Expand Down Expand Up @@ -1863,6 +1902,11 @@ mod tests {
assert_load_v2_matches_load_v3("WghtVar_OS2.glyphs");
}

#[test]
fn read_wght_var_anchors_2_and_3() {
assert_load_v2_matches_load_v3("WghtVar_Anchors.glyphs");
}

fn only_shape_in_only_layer<'a>(font: &'a Font, glyph_name: &str) -> &'a Shape {
let glyph = font.glyphs.get(glyph_name).unwrap();
assert_eq!(1, glyph.layers.len());
Expand Down Expand Up @@ -2285,4 +2329,26 @@ mod tests {
(actual_groups, actual_kerning),
);
}

#[test]
fn read_simple_anchor() {
let font = Font::load(&glyphs3_dir().join("WghtVar_Anchors.glyphs")).unwrap();
assert_eq!(
vec![
("m01", "top", Point::new(300.0, 700.0)),
("l2", "top", Point::new(325.0, 725.0))
],
font.glyphs
.get("A")
.unwrap()
.layers
.iter()
.flat_map(|l| l.anchors.iter().map(|a| (
l.layer_id.as_str(),
a.name.as_str(),
a.pos
)))
.collect::<Vec<_>>()
);
}
}
Loading

0 comments on commit b4f744d

Please sign in to comment.