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

Add support for anchors to IR and glyphs2fontir #438

Merged
merged 2 commits into from
Sep 14, 2023
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
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