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

Upconvert UFO v1 and v2 kerning data #32

Merged
merged 3 commits into from
Apr 5, 2020
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 Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,4 @@ optional = true
failure = "0.1.6"
serde_test = "1.0.102"
tempdir = "0.3.7"
maplit = "1.0.2"
3 changes: 3 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@ pub enum Error {
PlistError(PlistError),
FontInfoError,
GroupsError(GroupsValidationError),
GroupsUpconversionError(GroupsValidationError),
ExpectedPlistDictionaryError,
}

/// An error representing a failure to validate UFO groups.
#[derive(Debug)]
pub enum GroupsValidationError {
InvalidName,
Expand Down Expand Up @@ -93,6 +95,7 @@ impl std::fmt::Display for Error {
Error::PlistError(e) => e.fmt(f),
Error::FontInfoError => write!(f, "FontInfo contains invalid data"),
Error::GroupsError(ge) => ge.fmt(f),
Error::GroupsUpconversionError(ge) => write!(f, "Upconverting UFO v1 or v2 kerning data to v3 failed: {}", ge),
Error::ExpectedPlistDictionaryError => write!(f, "The files groups.plist, kerning.plist and lib.plist must contain plist dictionaries."),
}
}
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ pub mod glyph;
mod layer;
mod shared_types;
mod ufo;
mod upconversion;

pub use error::Error;
pub use fontinfo::FontInfo;
Expand Down
51 changes: 31 additions & 20 deletions src/ufo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use crate::error::GroupsValidationError;
use crate::fontinfo::FontInfo;
use crate::glyph::{Glyph, GlyphName};
use crate::layer::Layer;
use crate::upconversion::upconvert_kerning;
use crate::Error;
use plist;

Expand All @@ -28,16 +29,23 @@ static DEFAULT_LAYER_NAME: &str = "public.default";
static DEFAULT_GLYPHS_DIRNAME: &str = "glyphs";
static DEFAULT_METAINFO_CREATOR: &str = "org.linebender.norad";

/// Groups is a map of group name to a list of glyph names. It's a BTreeMap because we need sorting
/// for serialization.
pub type Groups = BTreeMap<String, Vec<GlyphName>>;
/// Kerning is a map of first half of a kerning pair (glyph name or group name) to the second half
/// of a pair (glyph name or group name), which maps to the kerning value (high-level view:
/// (first, second) => value). It's a BTreeMap because we need sorting for serialization.
pub type Kerning = BTreeMap<String, BTreeMap<String, f32>>;

/// A Unified Font Object.
#[derive(Clone)]
pub struct Ufo {
pub meta: MetaInfo,
pub font_info: Option<FontInfo>,
pub layers: Vec<LayerInfo>,
pub lib: Option<plist::Dictionary>,
// groups and kerning: BTreeMap because we need sorting for deserialization.
pub groups: Option<BTreeMap<String, Vec<String>>>,
pub kerning: Option<BTreeMap<String, BTreeMap<String, f32>>>,
pub groups: Option<Groups>,
pub kerning: Option<Kerning>,
pub features: Option<String>,
__non_exhaustive: (),
}
Expand Down Expand Up @@ -123,7 +131,7 @@ impl Ufo {
// minimize monomorphization
fn load_impl(path: &Path) -> Result<Ufo, Error> {
let meta_path = path.join(METAINFO_FILE);
let meta: MetaInfo = plist::from_file(meta_path)?;
let mut meta: MetaInfo = plist::from_file(meta_path)?;
let fontinfo_path = path.join(FONTINFO_FILE);
let font_info = if fontinfo_path.exists() {
let font_info: FontInfo = plist::from_file(fontinfo_path)?;
Expand All @@ -147,19 +155,16 @@ impl Ufo {

let groups_path = path.join(GROUPS_FILE);
let groups = if groups_path.exists() {
let groups: BTreeMap<String, Vec<String>> = plist::from_file(groups_path)?;
let groups: Groups = plist::from_file(groups_path)?;
validate_groups(&groups).map_err(Error::GroupsError)?;

Some(groups)
} else {
None
};

let kerning_path = path.join(KERNING_FILE);
let kerning = if kerning_path.exists() {
let kerning: BTreeMap<String, BTreeMap<String, f32>> =
plist::from_file(kerning_path)?;

let kerning: Kerning = plist::from_file(kerning_path)?;
Some(kerning)
} else {
None
Expand All @@ -168,7 +173,6 @@ impl Ufo {
let features_path = path.join(FEATURES_FILE);
let features = if features_path.exists() {
let features = fs::read_to_string(features_path)?;

Some(features)
} else {
None
Expand All @@ -194,6 +198,21 @@ impl Ufo {
.collect();
let layers = layers?;

// Upconvert UFO v1 or v2 kerning data if necessary. To upconvert, we need at least
// a groups.plist file, while a kerning.plist is optional.
let (groups, kerning) = match (meta.format_version, groups, kerning) {
(FormatVersion::V3, g, k) => (g, k), // For v3, we do nothing.
(_, None, k) => (None, k), // Without a groups.plist, there's nothing to upgrade.
(_, Some(g), k) => {
let (groups, kerning) =
upconvert_kerning(&g, &k.unwrap_or_default(), &glyph_names);
validate_groups(&groups).map_err(Error::GroupsUpconversionError)?;
(Some(groups), Some(kerning))
}
};

meta.format_version = FormatVersion::V3;

Ok(Ufo {
layers,
meta,
Expand Down Expand Up @@ -341,9 +360,7 @@ impl Ufo {

/// Validate the contents of the groups.plist file according to the rules in the
/// [Unified Font Object v3 specification for groups.plist](http://unifiedfontobject.org/versions/ufo3/groups.plist/#specification).
fn validate_groups(
groups_map: &BTreeMap<String, Vec<String>>,
) -> Result<(), GroupsValidationError> {
fn validate_groups(groups_map: &Groups) -> Result<(), GroupsValidationError> {
let mut kern1_set = HashSet::new();
let mut kern2_set = HashSet::new();
for (group_name, group_glyph_names) in groups_map {
Expand Down Expand Up @@ -406,14 +423,8 @@ mod tests {
font_obj.lib.unwrap().get("com.typemytype.robofont.compileSettings.autohint"),
Some(&plist::Value::Boolean(true))
);

assert_eq!(
font_obj.groups.unwrap().get("public.kern1.@MMK_L_A"),
Some(&vec!["A".to_string()])
);

assert_eq!(font_obj.groups.unwrap().get("public.kern1.@MMK_L_A"), Some(&vec!["A".into()]));
assert_eq!(font_obj.kerning.unwrap().get("B").unwrap().get("H").unwrap(), &-40.0);

assert_eq!(font_obj.features.unwrap(), "# this is the feature from lightWide\n");
}

Expand Down
Loading