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

Support loading from .glyphspackage #447

Merged
merged 17 commits into from
Sep 21, 2023
Merged

Support loading from .glyphspackage #447

merged 17 commits into from
Sep 21, 2023

Conversation

anthrotype
Copy link
Member

@anthrotype anthrotype commented Sep 19, 2023

Fixes #435

Initially this is just adding the test .glyphspackage files ("saved as" from the existing .glyphs test files using the latest Glyphs.app), so the test is going to fail. Will follow up with the implementation itself.

@anthrotype anthrotype marked this pull request as draft September 19, 2023 14:33
@anthrotype anthrotype force-pushed the glyphspackage branch 4 times, most recently from dd7c62b to ab554d2 Compare September 20, 2023 09:52
@anthrotype anthrotype changed the title WIP: Support loading from .glyphspackage Support loading from .glyphspackage Sep 20, 2023
@anthrotype anthrotype marked this pull request as ready for review September 20, 2023 16:18
@anthrotype
Copy link
Member Author

So I added a load_package method to glyphs-reader's Font, and also changed the default Font::load to call load_package when the input path is a directory instead of a regular file. In the method I glue all the glyph plists together in the right order before constructing the RawFont from the combined plist and everything seems to work.
This was the easiest way to implement this; alternatively we could have define a new GlyphsPackageIrSource alongside the current GlyphsIrSource which is backed by multiple files instead of a single in-memory plist, but I think that would touch a lot more code. Personally I'm inclined to have it like this for now and refine later if need be.

@anthrotype
Copy link
Member Author

(in case you were in the middle of a review, I just rebased on origin/main to incorporate #446 just merged, no other changes)

Copy link
Member

@cmyr cmyr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Happy to see that this patch is not actually 3k lines of code. That said I think github is starting to choke on font.rs here and we should maybe split it up a bit?

A bunch of notes inline; I spent a while playing around trying to clean this code up a bit and it's not that easy, although it did motivate me to open #450, which should let us improve the situation somewhat...

Regex::new(r"(?mi)^(?P<prefix>\s*glyphname\s*=\s*)(?P<value>(?:infinity|inf|nan));\s*$")
.unwrap();
let s = unicode_re.replace_all(&s, r#"$prefix"$value";"#);
let s = glyphname_re.replace_all(&s, r#"$prefix"$value";"#);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this based on other code, or brand new?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if a glyph is named "infinity", "inf" or "nan" and the name is un-quoted (as Glyphs.app does when a string only contains ascii), our plist parser misunderstands it as a float
https://doc.rust-lang.org/std/primitive.f64.html#grammar
so I uses regex to add the quotes and make sure they stay as strings

We had code (that I removed here) which would fix up the parsed glyph plist dict and unconditionally replace "glyphname" typed float with a hard-coded "infinity" string (ignoring "inf" or "nan").

I think the python openstep-plist parser (used by glyphsLib and thus fontmake) does not have this problem because it can detect that an unquoted string is neither an integer nor a float (https://github.com/fonttools/openstep-plist/blob/c9acd408ba3a374ba41dd62f595f43fa2e5bfa6f/src/openstep_plist/parser.pyx#L267-L309) and thus treat it as string; I think our current rust plist parser attempts to parse as number and if that succeeds (as in the case of "infinity") it treats it as such, so we need to prevent that -- or change the parser (but I haven't even looked into that.. would you like me to?)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed the plist parser itself to always treat "infinity", "inf" and "nan" as strings, so we don't need the regex hack any more. I don't think inf or NaN are useful in font source formats

glyphs-reader/src/font.rs Outdated Show resolved Hide resolved
glyphs-reader/src/font.rs Show resolved Hide resolved
glyphs-reader/src/font.rs Outdated Show resolved Hide resolved
glyphs-reader/src/font.rs Outdated Show resolved Hide resolved
glyphs-reader/src/font.rs Outdated Show resolved Hide resolved
Comment on lines 1721 to 1738
let glyph_plist = Plist::parse(&glyph_data)
.map_err(|e| Error::ParseError(entry.path(), format!("{e:#?}")))?;
let Plist::Dictionary(ref glyph_dict) = glyph_plist else {
return Err(Error::ParseError(
entry.path(),
"Glyph must be a dict".to_string(),
));
};
let glyph_name = glyph_dict
.get("glyphname")
.map(|s| s.to_string())
.ok_or_else(|| {
Error::ParseError(
entry.path(),
"Glyph dict must have a 'glyphname' key".to_string(),
)
})?;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if you rebase on #450 I hope you can now just write,

Suggested change
let glyph_plist = Plist::parse(&glyph_data)
.map_err(|e| Error::ParseError(entry.path(), format!("{e:#?}")))?;
let Plist::Dictionary(ref glyph_dict) = glyph_plist else {
return Err(Error::ParseError(
entry.path(),
"Glyph must be a dict".to_string(),
));
};
let glyph_name = glyph_dict
.get("glyphname")
.map(|s| s.to_string())
.ok_or_else(|| {
Error::ParseError(
entry.path(),
"Glyph dict must have a 'glyphname' key".to_string(),
)
})?;
let glyph_dict = Plist::parse(&glyph_data)
.and_then(Plist::expect_dict)
.map_err(|e| Error::ParseError(entry.path(), e.to_string()))?;
let glyph_name = glyph_dict
.get("glyphname")
.map(|s| s.expect_string().ok())
.ok_or_else(|| {
Error::ParseError(
entry.path(),
"Glyph dict must have a 'glyphname' key".to_string(),
)
})?;

which... is still pretty gross!

to avoid spurious diffs when comparing against .glyphspackage generated using the same Glyphs build
I could not open the infinity.glyphs test file in Glyphs, it complained about an unrecognized parenthesis, apparently the fontMaster cannot be empty
and open and save infinity.glyphs to avoid spurious diffs, sorry nice indentation
was added automatically by Glyphs when saving, but we have another test that expects it to be absent and asserts it defaults to 0.000
make load_package private
use path.extension()
use e.to_string() instead of format!({e:#?})
I'm not sure things have improved much...
remove the regex hack. We don't need to parse infinity or NaN as floats in font formats
Copy link
Member

@cmyr cmyr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's definitely a lot of room for cleanup here but I think it's worth getting this merged :)

@anthrotype anthrotype added this pull request to the merge queue Sep 21, 2023
Merged via the queue into main with commit 33fb7c0 Sep 21, 2023
@anthrotype anthrotype deleted the glyphspackage branch September 21, 2023 15:08
@rsheeter rsheeter mentioned this pull request Sep 26, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support .glyphspackage format
2 participants