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 '--expand-features-to-instances' arg to fontmake UFO generation #985

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

arrowtype
Copy link
Contributor

UFOs can use relative include statements to import feature files, which is very helpful in the case of a family that has many UFO sources, but just one feature file required. Or, it’s helpful if there are many features, and several complex ones, so they are split into multiple files (example: a Recursive UFO source and its features directory).

Currently, the Builder just brings the include statement directly into the instance UFOs, but then the relative paths are broken, so the fonts don’t build.

fontmake: Error: Compiling UFO failed: <features>:1:8: The following feature file should be included but cannot be found: ./features/features.fea

However, it is very easy to support by adding the arg --expand-features-to-instances to the UFO instantiation step, as I have done in this PR.

I’ve tested this on a UFO-based family (with includes) and a Glyphs-based family (with normal Glyphs-based features), and it seems to work well in both cases. The features are built into the fonts, as expected.

I’m not aware of any downsides or hidden issues with adding this, but they might exist. If any are known, I would be very interested in learning about such cases.

Please let me know if you have any questions or suggestions here! Thanks so much.

@arrowtype
Copy link
Contributor Author

arrowtype commented Oct 3, 2024

Oh, I’ve also just realized that the current config option for this seems to activately prevent a build from working.

After I tested the above solution on a config that included expandFeaturesToInstances: true, the build failed with this:

[229/1410] buildTTF
FAILED: /var/folders/nb/mc9zt2n930vd_jkbg0kn2d680000gn/T/tmp8ertvdij 
/Users/stephennixon/venv/bin/python -m gftools.builder.jobrunner fontmake --output-path /var/folders/nb/mc9zt2n930vd_jkbg0kn2d680000gn/T/tmp8ertvdij -o ttf -u sources/ufo/instance_ufos/FamilynameExtraCompressed-Hairline.ufo.json --filter ...  --filter FlattenComponentsFilter --filter DecomposeTransformedComponentsFilter --expand-features-to-instances 

Command failed:
fontmake --output-path /var/folders/nb/mc9zt2n930vd_jkbg0kn2d680000gn/T/tmp8ertvdij -o ttf -u sources/ufo/instance_ufos/FamilynameExtraCompressed-Hairline.ufo.json --filter ... --filter FlattenComponentsFilter --filter DecomposeTransformedComponentsFilter --expand-features-to-instances

usage: fontmake [-h] [--version] [-g GLYPHS | -u UFO [UFO ...] | -m
                DESIGNSPACE] [--glyph-data GLYPHDATA] [-o FORMAT [FORMAT ...]]
                # etc etc etc

fontmake: error: "--expand-features-to-instances" option invalid for UFO source

With that arg removed from the static font step, the build does complete.

However, the next challenge is adding the config option to the instantiateUfo process. I need to figure out how to access the config at that stage (assuming I can, somehow... I may not yet properly understand the logic here).

Okay! I’ve figured out how to access and pass that config option to the instantiateUfo step, and I’ve made it default to true, as is written in the builder docs. I’ve tested the config without the option, and with the option set to true and then set to false. It acted as expected, in each case. It inserts that arg into the UFO instantiation, making those features work in the final build:

▶ gftools builder 'Familyname/config.yaml'
[1/1410] instantiateUfo
fontmake -i Familyname Extra Compressed Extra Bold -o ufo -m sources/ufo/FamilynameRoman.designspace --ufo-structure=json --expand-features-to-instances --output-dir sources/ufo/instance_ufos
[2/1410] instantiateUfo
fontmake -i Familyname Extra Compressed Thin -o ufo -m sources/ufo/FamilynameRoman.designspace --ufo-structure=json --expand-features-to-instances --output-dir sources/ufo/instance_ufos

Copy link
Collaborator

@m4rc1e m4rc1e left a comment

Choose a reason for hiding this comment

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

@arrowtype Please fix the lint issues and I'll take another look.

@arrowtype arrowtype force-pushed the fontmake-expand-features-to-instances branch from a429d71 to cc8617e Compare October 4, 2024 19:01
@arrowtype
Copy link
Contributor Author

Thanks, @m4rc1e! I believe it’s now formatted as desired. It seems to need approval to run the workflow to verify that, though.

Copy link
Collaborator

@m4rc1e m4rc1e 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 no way of enabling and disabling this flag inside a config file, You need to add expandFeaturesToInstances to the builder's argument schema, https://github.com/googlefonts/gftools/blob/main/Lib/gftools/builder/schema.py#L66.

It is enabled by default in this pr. I'd prefer it if it was set to false and then the user has to enable it in their config files.

@arrowtype
Copy link
Contributor Author

Okay, thanks for taking a look and for suggesting that update! Fair enough. I’ll try to make those adjustments soon.

@arrowtype
Copy link
Contributor Author

@m4rc1e Oh wait, isn’t it already there in the schema? It’s also described in the docs. This PR merely makes it work as already described.

Optional("removeOutlineOverlaps"): Bool(),
Optional("expandFeaturesToInstances"): Bool(),
Optional("version"): Str(),

- `expandFeaturesToInstances`: Resolve all includes in the sources\'
features, so that generated instances can be compiled without
errors. Defaults to `true`.

@arrowtype
Copy link
Contributor Author

In the past, @anthrotype has suggested that this option should be the default:

We should make that the default...

The builder seems like a place it might make sense to do that, and that’s why I figured it was documented as such. Or, do you think it’s important to keep it non-default? I don’t think it would be a breaking chance to make it default, as I don’t think it blocks features that are already flattened into each source.

@@ -59,6 +59,8 @@ def targets(self):
def variables(self):
vars = super().variables
vars["args"] += " --ufo-structure=json "
if self.original["expandFeaturesToInstances"]:
Copy link
Collaborator

@m4rc1e m4rc1e Nov 7, 2024

Choose a reason for hiding this comment

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

Any reason why you're using self.original here? It is breaking the tests since expandFeaturesToInstances isn't in self.original. If I change it to vars it seems to be working but I could be wrong.

Personally, I would use a dict's get method if I cannot guarantee that a key exists. It also allows you to set a value incase it doesn't exist so in your case self.original.get("expandFeaturesToInstances", False) will return False if the key doesn't exist, or it will simply return the value if it does.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

First, thank you for that tip on .get() – that is news to me, but really helpful to learn!

Any reason why you're using self.original here?

As far as I can remember, I think I just tried to use a similar format as what is a few lines below:

if self.original.get("glyphData") is not None:

...and when it seemed to work, I thought I had solved it.

My impression was that self.original is the GOOGLEFONTS_SCHEMA, but I now realize that, even if that is true, there are other possible schemas.

Comment on lines -196 to -197
if self.config.get("expandFeaturesToInstances"):
args += " --expand-features-to-instances"
Copy link
Collaborator

Choose a reason for hiding this comment

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

Are we 100% sure that this doesn't work? If I was implementing such a feature then I'd simply chuck it here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is the approach in the release as of when I made the PR, right? If so, then yes, I’m 100% sure it wasn’t working. I can test it again, though, if it seems likely that it may have changed since then.

Copy link
Contributor

Choose a reason for hiding this comment

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

That line feels right - I think I'd like to understand why it's not working before trying something else, because understanding why not might lead us to the right answer.

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.

3 participants