Skip to content

Commit

Permalink
Merge pull request #59 from googlefonts/glyphs_glyphs
Browse files Browse the repository at this point in the history
Load Glyphs v2 axis mappings, example based on Texturina, into converter
  • Loading branch information
rsheeter authored Dec 15, 2022
2 parents 11fc77e + 6ab9425 commit 66aa397
Show file tree
Hide file tree
Showing 6 changed files with 346 additions and 23 deletions.
8 changes: 0 additions & 8 deletions fontir/src/coords.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ use std::collections::BTreeMap;

use crate::piecewise_linear_map::PiecewiseLinearMap;
use crate::serde::CoordConverterSerdeRepr;
use log::warn;
use ordered_float::OrderedFloat;
use serde::{Deserialize, Serialize};

Expand Down Expand Up @@ -103,13 +102,6 @@ impl CoordConverter {
}
}

// Will be removed once remapping is properly implemented, for now marks where we need to update
// Tentatively expected to put maps onto StaticMetadata so anyone who wants design:user gets from there
pub fn temporary_design_to_user_conversion(coord: DesignCoord) -> UserCoord {
warn!("Illegal and invalid temporary conversion");
UserCoord(coord.into_inner())
}

impl DesignCoord {
/// We do *not* provide From because we want conversion to be explicit
pub fn new(value: impl Into<OrderedFloat<f32>>) -> DesignCoord {
Expand Down
2 changes: 1 addition & 1 deletion fontir/src/stateset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,7 @@ mod tests {
let unchanged = temp_dir.path().join("fileA");
let modified = subdir.join("fileB");
let removed = subdir.join("fileC");
fs::write(&unchanged, "eh").unwrap();
fs::write(unchanged, "eh").unwrap();
fs::write(&modified, "eh").unwrap();
fs::write(&removed, "eh").unwrap();

Expand Down
63 changes: 62 additions & 1 deletion glyphs-reader/src/font.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ const V3_METRIC_NAMES: [&str; 5] = [
];
const V2_METRIC_NAMES: [&str; 5] = ["ascender", "baseline", "descender", "capHeight", "xHeight"];

type RawUserToDesignMapping = Vec<(OrderedFloat<f32>, OrderedFloat<f32>)>;

#[derive(Debug, PartialEq, Eq, Hash)]
pub struct Font {
pub family_name: String,
Expand All @@ -34,6 +36,8 @@ pub struct Font {
pub glyphs: BTreeMap<String, Glyph>,
pub glyph_order: Vec<String>,
pub codepoints: BTreeMap<Vec<u32>, String>,
// tag => (user:design) tuples
pub axis_mappings: BTreeMap<String, RawUserToDesignMapping>,
}

// The font you get directly from a plist, minimally modified
Expand Down Expand Up @@ -680,6 +684,26 @@ impl From<RawFont> for Font {
.map(|g| (g.glyphname.clone(), g))
.collect();

let mut axis_mappings: BTreeMap<String, RawUserToDesignMapping> = BTreeMap::new();
if let Some((_, axis_map)) = custom_param(&from.other_stuff, "Axis Mappings") {
let Plist::Dictionary(axis_map) = axis_map.get("value").unwrap() else {
panic!("Incomprehensible axis map");
};
for (axis_tag, mappings) in axis_map.iter() {
let Plist::Dictionary(mappings) = mappings else {
panic!("Incomprehensible mappings");
};
for (user, design) in mappings.iter() {
let user: f32 = user.parse().unwrap();
let design = design.as_f64().unwrap() as f32;
axis_mappings
.entry(axis_tag.clone())
.or_default()
.push((user.into(), design.into()));
}
}
}

Font {
family_name: from.family_name,
axes: from.axes.unwrap_or_default(),
Expand All @@ -688,6 +712,7 @@ impl From<RawFont> for Font {
glyphs,
glyph_order,
codepoints,
axis_mappings,
}
}
}
Expand Down Expand Up @@ -729,10 +754,15 @@ impl Font {

#[cfg(test)]
mod tests {
use std::path::{Path, PathBuf};
use std::{
collections::BTreeMap,
path::{Path, PathBuf},
};

use crate::{Font, FromPlist, Node, Plist};

use ordered_float::OrderedFloat;

use pretty_assertions::assert_eq;

fn testdata_dir() -> PathBuf {
Expand Down Expand Up @@ -889,4 +919,35 @@ mod tests {
let font = Font::load(&glyphs3_dir().join("WghtVar_GlyphOrder.glyphs")).unwrap();
assert_eq!(vec!["hyphen", "space", "exclam"], font.glyph_order);
}

#[test]
fn loads_global_axis_mappings_from_glyphs2() {
let font = Font::load(&glyphs2_dir().join("WghtVar_AxisMappings.glyphs")).unwrap();

// Did you load the mappings? DID YOU?!
assert_eq!(
BTreeMap::from([
(
"opsz".to_string(),
vec![
(OrderedFloat(12.0), OrderedFloat(12.0)),
(OrderedFloat(72.0), OrderedFloat(72.0))
]
),
(
"wght".to_string(),
vec![
(OrderedFloat(100.0), OrderedFloat(40.0)),
(OrderedFloat(200.0), OrderedFloat(46.0)),
(OrderedFloat(300.0), OrderedFloat(51.0)),
(OrderedFloat(400.0), OrderedFloat(57.0)),
(OrderedFloat(500.0), OrderedFloat(62.0)),
(OrderedFloat(600.0), OrderedFloat(68.0)),
(OrderedFloat(700.0), OrderedFloat(73.0)),
]
),
]),
font.axis_mappings
);
}
}
94 changes: 86 additions & 8 deletions glyphs2fontir/src/source.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use fontir::coords::{CoordConverter, UserCoord};
use fontir::coords::{CoordConverter, DesignCoord, UserCoord};
use fontir::error::{Error, WorkError};
use fontir::ir;
use fontir::ir::{Axis, StaticMetadata};
Expand Down Expand Up @@ -67,6 +67,7 @@ impl GlyphsIrSource {
glyphs: Default::default(),
glyph_order: Default::default(),
codepoints: Default::default(),
axis_mappings: font.axis_mappings.clone(),
};
state.track_memory("/font_master".to_string(), &font)?;
Ok(state)
Expand Down Expand Up @@ -196,13 +197,51 @@ impl Work for StaticMetadataWork {
axis_values[idx][font.default_master_idx].into_inner() as f32,
);

// Given in user
let min = UserCoord::new(min);
let max = UserCoord::new(max);
let default = UserCoord::new(default);

// TODO process .glpyhs coordinate conversions
let converter = CoordConverter::unmapped(min, default, max);
// Given in design coords based on a sample file
let default = DesignCoord::new(default);
let min = DesignCoord::new(min);
let max = DesignCoord::new(max);

// TODO: support Axis Location (https://glyphsapp.com/learn/creating-a-variable-font#g-axis-mappings-and-locations__option-1-axis-location-parameters)

let converter = if font.axis_mappings.contains_key(&a.tag) {
let mappings: Vec<_> = font
.axis_mappings
.get(&a.tag)
.unwrap()
.iter()
.map(|(u, d)| (UserCoord::new(*u), DesignCoord::new(*d)))
.collect();
let default_idx = mappings
.iter()
.position(|(_, dc)| *dc == default)
.unwrap_or_else(|| {
panic!("Must have a mapping for default {:?} on {}", default, a.tag)
});
mappings
.iter()
.position(|(_, dc)| *dc == min)
.unwrap_or_else(|| {
panic!("Must have a mapping for min {:?} on {}", min, a.tag)
});
mappings
.iter()
.position(|(_, dc)| *dc == max)
.unwrap_or_else(|| {
panic!("Must have a mapping for max {:?} on {}", max, a.tag)
});
CoordConverter::new(mappings, default_idx)
} else {
// There is no mapping that we can understand; let design == user
let min = UserCoord::new(min.into_inner());
let max = UserCoord::new(max.into_inner());
let default = UserCoord::new(default.into_inner());
CoordConverter::unmapped(min, default, max)
};

let default = default.to_user(&converter);
let min = min.to_user(&converter);
let max = max.to_user(&converter);

Axis {
name: a.name.clone(),
Expand Down Expand Up @@ -249,6 +288,8 @@ mod tests {
};

use fontir::{
coords::{CoordConverter, DesignCoord, UserCoord},
ir,
orchestration::{Context, WorkIdentifier},
source::{Paths, Source},
stateset::StateSet,
Expand Down Expand Up @@ -368,6 +409,43 @@ mod tests {
.unwrap();
}

#[test]
fn loads_axis_mappings_from_glyphs2() {
let (source, context) = context_for(glyphs2_dir().join("WghtVar_AxisMappings.glyphs"));
let task_context = context.copy_for_work(WorkIdentifier::StaticMetadata, None);
source
.create_static_metadata_work(&context)
.unwrap()
.exec(&task_context)
.unwrap();
let static_metadata = context.get_static_metadata();

// Did you load the mappings? DID YOU?!
assert_eq!(
vec![ir::Axis {
name: "Weight".into(),
tag: "wght".into(),
min: UserCoord::new(100.0),
default: UserCoord::new(500.0),
max: UserCoord::new(700.0),
hidden: false,
converter: CoordConverter::new(
vec![
(UserCoord::new(100.0), DesignCoord::new(40.0)),
(UserCoord::new(200.0), DesignCoord::new(46.0)),
(UserCoord::new(300.0), DesignCoord::new(51.0)),
(UserCoord::new(400.0), DesignCoord::new(57.0)),
(UserCoord::new(500.0), DesignCoord::new(62.0)), // default
(UserCoord::new(600.0), DesignCoord::new(68.0)),
(UserCoord::new(700.0), DesignCoord::new(73.0)),
],
4
),
}],
static_metadata.axes
);
}

#[test]
fn glyph_ir() {
let (source, context) = context_for(glyphs3_dir().join("WghtVar.glyphs"));
Expand Down
Loading

0 comments on commit 66aa397

Please sign in to comment.