-
Notifications
You must be signed in to change notification settings - Fork 51
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
Split the UFO builder into 1 file per "feature" #235
Conversation
Glyphs can read and write UFOs. So if someone has a UFO and needs to convert it to a .glyphs file, he most likely has Glyphs around and can just open the UFO. One use/test case for the Glyphs<->UFO code could be to use it from within Glyphs to read/write UFO/designspace files. That would test the API compatibility/modularity quite well. |
If you find ways to store more smart stuff in UFOs, I might use the same structure when exporting from Glyphs. I’m interested how you implement this. I’m not familiar enough with how private data is supposed to be stored in UFO to be able to connect it back to the base object. One simple problem is extra info for components, like the alignment setting. How to decide that the data needs to be used or not when the base object has changes. |
Thanks Jany for the detailed plan you proposed.
we can get rid of that, and use Glyphs objects directly
ok
I guess it's ok to nicely fail in these cases
We do what we can do. Let's start with the easy part and see how far we get :) Thanks! |
a99c9dc
to
a33eb16
Compare
Thanks for your feedback. I pushed my latest code even though it has conflicts and probably does not run, just to show that I have further split the builder. Tomorrow I will cleanup this PR so that the only diffs are the existing code moving around and the stub functions, and I will submit the new code for UFO->Glyphs in another PR. |
Sounds good! |
9cbfb9c
to
57780e1
Compare
@anthrotype @moyogo I have moved the new code in another PR, this one is ready to be reviewed |
57780e1
to
2e51ab4
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think True and 1 are equal in 🐍.
Ugh, I need to un-learn Ruby |
I'm glad you pointed that out, because the test I just added was really moot |
d3fa265
to
ee1a017
Compare
Lib/glyphsLib/builder/__init__.py
Outdated
|
||
# Existing | ||
from .kerning import add_glyph_to_groups, add_groups_to_ufo, load_kerning | ||
from .anchors import propagate_font_anchors, propagate_glyph_anchors |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
propagate_glyph_anchors
is imported but not used here. I think it's a private function only used by propagate_font_anchros
.
Lib/glyphsLib/builder/__init__.py
Outdated
if varfont_origin: | ||
instance_data[varfont_origin_key] = varfont_origin | ||
if debug: | ||
return clear_data(font) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we need to remove this debug
argument as well as this clear_data
function.
They no longer work with the class-based interface, as clear_data expects a dict or a list from which values are popped and the ones that are left are thus "unused".
Lib/glyphsLib/builder/anchors.py
Outdated
for anchor in anchors: | ||
x, y = anchor.position | ||
anchor_dict = {'name': anchor.name, 'x': x, 'y': y} | ||
glyph.appendAnchor(glyph.anchorClass(anchorDict=anchor_dict)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm pretty sure defcon's appendAnchor also accepts a raw dict directly. It will automatically instantiate it to the respective anchor class for you.
Lib/glyphsLib/builder/blue_values.py
Outdated
unicode_literals) | ||
|
||
|
||
def to_ufo_blue_values(_context, ufo, master): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why _context
with an underscore? is it because it is not used in this function? I think it's fine to always call it context
.
for the merge conflict, I think it should be fine to |
Lib/glyphsLib/builder/context.py
Outdated
self.defcon = defcon | ||
|
||
self.ufos = OrderedDict() | ||
""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
any reasons why here you use """ multi-line string literals """
instead of simple # inline comment
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because I read in a PEP that it was a way to document class attributes: https://www.python.org/dev/peps/pep-0257/#what-is-a-docstring
String literals occurring immediately after a simple assignment at the top level of a module, class, or init method are called "attribute docstrings".
Is there another way?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting, I didn't know that...
However, if you try to do help(...)
on that class, it only shows the first multi-line string.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe auto docs generators like sphinx will be able to retrieve those. I'm fine if we leave them. We should polish the documentation at some point.
from glyphsLib.util import bin_to_int_list | ||
from .filters import parse_glyphs_filter | ||
from .constants import GLYPHS_PREFIX, PUBLIC_PREFIX, CODEPAGE_RANGES, \ | ||
UFO2FT_FILTERS_KEY |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let's stick to either parentheses or backslashes for multi-line import statements. I prefer the former
Lib/glyphsLib/builder/guidelines.py
Outdated
from __future__ import (print_function, division, absolute_import, | ||
unicode_literals) | ||
|
||
from glyphsLib.types import point |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
point
is imported but unused
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will remove it.
It's here because it was used in the WIP implementation of to_glyphs_guidelines
that I replaced with pass
. Is it still the plan to have this MR merged quickly with stub functions for everything UFO -> Glyphs?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it still the plan to have this MR merged quickly with stub functions for everything UFO -> Glyphs?
yes!
Lib/glyphsLib/builder/context.py
Outdated
"""Builder context for Glyphs to UFO + designspace.""" | ||
|
||
def __init__(self, font, defcon): | ||
"""Create a context for the Glyphs to UFO + designspace builder. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking, instead of passing around a "context" object as argument to the to_ufo_*
and to_glyphs_*
functions, we could define two classes e.g. UFOBuilder
and GlyphsBuilder
that have those functions as instance methods and use self
as the context.
We could still keep this modularized structure with the builder functions in their respective modules, and then when we define the builder classes, we would set these functions as class attributes, and they will be bound to a builder instance just like regular methods.
WDYT?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using classes makes sense.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I will do that
ee1a017
to
98fb2dc
Compare
I fixed all the comments and rebased onto master. |
Lib/glyphsLib/builder/builders.py
Outdated
class UFOBuilder(object): | ||
"""Builder for Glyphs to UFO + designspace.""" | ||
|
||
def __init__(self, font, defcon, family_name=None, propagate_anchors=True): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe ufo_module
instead of defcon
. And default to defcon when None.
Lib/glyphsLib/builder/builders.py
Outdated
|
||
|
||
@property | ||
def master_ufos(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just masters
instead of master_ufos
, then it will be the same as instances
and designspace
.
|
||
|
||
# Implementation is spit into one file per feature | ||
from .anchors import to_ufo_propagate_font_anchors, to_ufo_glyph_anchors |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was also thinking.. perhaps instead of naming the methods UFOBuilder.to_ufo_*
, and GlyphsBuilder.to_glyphs_*
, which seems a bit redundant, we could replace the to_ufo_*
and to_glyphs_*
prefixes with a generic build_*
, using the as
keyword in the import statements.
E.g. inside UFOBuilder
scope, you would do from .anchors import to_ufo_glyph_anchors as build_glyph_anchors
, whereas inside GlyphsBuilder
, from .anchors import to_glyphs_glyph_anchors as build_glyph_anchors
.
WDYT?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't mind the redundant naming because I think it makes things very clear inside the code: those names will only be used inside the implementations of the builders, with self.
in front of it, and if you're looking at a file and stumble upon self.build_anchors
, you will have to look around to find out in which direction the current function is going, whereas here it will always be very clear. Also having two names for the same function sounds confusing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok, let's keep it like that
Lib/glyphsLib/builder/__init__.py
Outdated
return result | ||
|
||
|
||
def to_glyphs(ufos, designspace=None, classes=classes): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
glyphs_module
instead of classes
would be clearer, like ufo_module
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, thanks!
This is a work-in-progress to implement the round-trip between Glyphs and UFO+designspace.
Proposed plan:
After reading the code and working on step 1, I have a few questions:
In a few places, the objects from Glyphs are "parsed"/decomposed into tuples, and then the tuples are used to fill in the UFO objects. I believe that this is a leftover from the previous builder that used raw dictionaries instead of nice objects, and that the builder should now use the objects directly, and get rid of the intermediary tuples?
It looks like the two modules "interpolation.py" and "util.py" are mostly related to the builder. Maybe they should be moved into the "builder" module? (for interpolation: I say that because the builder should be able to read/fill in a designspace document to link the produced UFOs together)
For testing and for the builder's "robustness", I see three levels of round-trip:
I saw that some Glyphs data is stored as text in the
features.fea
file. In order to import it back, I guess that I should use a feature file parser. Can I usefonttools.feaLib
for that?