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 brace and bracket layers #468

Open
madig opened this issue Nov 20, 2018 · 16 comments
Open

Support brace and bracket layers #468

madig opened this issue Nov 20, 2018 · 16 comments

Comments

@madig
Copy link
Collaborator

madig commented Nov 20, 2018

Thanks to Behdad's recent efforts on sparse masters, we could now support brace layers. This is probably an issue for multiple layers of the stack. I'll start here because glyphsLib needs to output something. I think this ties into fonttools/fonttools#1308.

designspaceLib supports substitution rules (https://github.com/fonttools/fonttools/blob/master/Lib/fontTools/designspaceLib/__init__.py#L140) to support bracket layers. How should brace layers/sparse masters be supported? For every brace layer, make a new source entry with the appropriate location and set layerName to the name of the brace layer? If e.g. there was a brace layer for e named Medium {465 100} in MyFont_Th.ufo:

    <sources>
        <source familyname="MyFont VF" filename="MyFont_Th.ufo" name="MyFont VF Thin" stylename="Thin">
            <lib copy="1" />
            <groups copy="1" />
            <features copy="1" />
            <info copy="1" />
            <location>
                <dimension name="Weight" xvalue="100" />
            </location>
        </source>
        <source familyname="MyFont VF" filename="MyFont_Th.ufo" layer="Medium {465 100}">
            <location>
                <dimension name="Weight" xvalue="465" />
            </location>
        </source>
        <source familyname="MyFont VF" filename="MyFont_Blk.ufo" name="MyFont VF Black" stylename="Black">
            <location>
                <dimension name="Weight" xvalue="1000" />
            </location>
        </source>
    </sources>

What already works is using a complete UFO (without feature file) as a sparse master, as I wrote here: googlefonts/fontmake#477 (comment).

There's an example declaration for bracket layers at https://github.com/fonttools/fonttools/blob/master/Lib/fontTools/designspaceLib/__init__.py#L141. Will have to understand those better before including them in the example here.

@madig
Copy link
Collaborator Author

madig commented Dec 3, 2018

I'm looking into bracket layers as Designspace has that covered by rules. Glyphs.app supports three cases of bracket layers as described in https://glyphsapp.com/tutorials/alternating-glyph-shapes, not counting the "Rename Glyphs" thing:

  1. Bracket layer for all layers
  2. Bracket layer for one layer
  3. Reverse bracket layer for one layer

It turns out to be a bit of an impedance mismatch, as Glyphs.app's bracket layers are, well, layers and a Designspace document works on whole glyphs. So for handling case 1 when round-tripping to UFO, glyphsLib would have to extract the brace layers into their own glyphs and turn them into layers again on round-tripping back. Another problem would be switching a single master (https://glyphsapp.com/tutorials/alternating-glyph-shapes -> "Switching Only One Master"), where for case 2 and 3 we'd need to copy the unchanged master layer to its own glyph to create an artificial case 1 again, but take care to delete it again when going to .glyphs after making sure it's the same as the actual layer (or turning it into an actual case 1).

Looking at https://glyphsapp.com/tutorials/alternating-glyph-shapes-with-multiple-axes, the "Rename Glyphs" parameter seems to be easier to translate into Designspace rules. So maybe it's possible to forego extra bracket layer logic and instead try to express it in "Rename Glyphs" terms... The problem being that this mechanism is meant to be just for one particular instance, so you'd have to insert it into all instances. And go the other way, too...

@madig
Copy link
Collaborator Author

madig commented Jan 13, 2019

Brace layers now supported in master.

@madig
Copy link
Collaborator Author

madig commented Jan 25, 2019

Bracket layers now supported in master.

@madig madig closed this as completed Jan 25, 2019
@anthrotype anthrotype reopened this Mar 5, 2019
@anthrotype
Copy link
Member

@madig are all three kinds of bracket layers that you mentioned above supported? And what about conditional substitutions along multiple axes? How do you do that in Glyphs right now, and how do we implement this in glyphsLib?

@madig
Copy link
Collaborator Author

madig commented Mar 6, 2019

Just the first kind. Bracket layers currently support only the first defined axis and you can only define cross-over points, not start--end ranges.

@anthrotype
Copy link
Member

(posting below my reply to @davelab6's asking about the status of current support for brace and bracket layers in fontmake and glyphsLib, in case anybody is interested)

TLDR: For VF, brace is done, bracket is mostly done; for static instances, neither is done, but there's a workaround.

For Variable Fonts, Glyphs' brace layers are currently supported by fontmake. FontTools varLib already supports "sparse" masters (at least for TTF; CFF2 is in the making). GlyphsLib can convert the brace layers into Designspace layer sources; the layers then get compiled into master TTFs that contain only these intermediate glyphs, and finally varLib computes deltas accordingly.

As for the the "bracket" layers: varLib and designspaceLib for some time now have supported conditional substition rules (FeatureVariations subtables in GSUB). @madig added support to glyphsLib for converting bracket layers into equivalent DS rules that gets compiled by varLib into FeatureVariations tables and "rvrn" features.

Like the original feature in Glyphs.app, the support in glyphsLib is limited to one axis only -- whereas DS rules allow all kinds of conditions along multiple axes. But unlike the original bracket feature, glyphsLib does not support yet two variants of the bracket trick, described in the "Alternating glyph shapes" blog post as "Switching Only One Master" and "Reverse Bracket Layer". These should not be difficult to implement.
As for the single-axis limitation of bracket layers in Glyphs.app, we are discussing with @schriftgestalt to see how this could be handled in Glyphs.app, and hence in glyphsLib too.

For generating static instances however, these intermediate masters (brace) and conditional rules (bracket) are not currently supported by fontmake.
The problem is fontmake is currently using the mutatorMath library to interpolate instance UFOs from designspace documents + UFO masters, and the latter does not support neither intermediate sparse layers nor conditional substition rules. MutatorMath has been deprecated, and there's a new incarnation of it called ufoProcessor. This is basically like MutatorMath (i.e. it takes a DS and UFO masters and outputs interpolated instance UFOs), but it has the option to use Behdad's VariationModel to do the interpolation math. That's good but I would rather not add it as another dependency to fontmake. My plan instead is to add UFO editing capabilities to fontTools itself (it currently only has low-level reader/writer classes, we need more high-level objects similar to what the defcon library provides, but lean and fast without all the fuss), so that the fontmake pipeline can use fontTools to do both the building of VF and the interpolation of instance UFOs from the same set of UFO masters and designspace document.

Finally there is a way to overcome the problem with static instances and sparse masters and conditional rules: one could build a VF first and then use fonttools varLib.mutator script to instantiate them. The interpolated instances will have the correct rules applied and use the intermediate masters as encoded in the VF deltas.

However I am not sure yet if that's a viable approach to recommend in the long run in all cases. Leaving aside the fact that some minor post-processing of the mutated instances is needed (e.g. the name table doesn't reflect the instance style name, but that can be fixed); building static instances usually implies removing the overlaps before converting the cubic curves to quadratic individually (i.e. not compatibly) for each UFO. The resulting fonts are usually a bit smaller (quadratic splines can use less points as they don't need to maintain compatibility across masters); and have less rendering glitches in legacy platforms. We could have mutator optionally run skia-pathops to remove the overlaps from the generated instance TTF or OTF fonts. Note that this will also render any manual TrueType hinting in the variable font useless because the point indexes change after removing the overlap.

I think interpolating font sources (in addition to font binaries) will still be a valid use case for a while, so we will have to deal with this some point. Or maybe this will fade out as variable font technology becomes the norm, and instantiating from VF binary fully covers the old use cases.

I hope I answered your question.

@davelab6
Copy link
Member

I'm curious why not add the dependency, if it already does what we want?

@madig
Copy link
Collaborator Author

madig commented Jun 6, 2019

Also todo: support bracketed glyphs as components

Imagine a glyph dollar, which contains bracket layers like on https://glyphsapp.com/tutorials/alternating-glyph-shapes. In one project we have a dollar.tf that uses dollar as a component, but the underlying rvrn OpenType feature does not switch out component glyph IDs, so glyphsLib should generate a dollar.tf.BRACKET... glyph with a reference to dollar.BRACKET... glyph.

@simoncozens
Copy link
Collaborator

I think this (alternate layers with components) is all implemented now, right?

@arrowtype
Copy link

Brace layers now supported in master.

I have become a fan of brace layers / support sources, so I wanted to verify whether this was working. At least in my test, it didn’t seem to quite work as expected.

In a Glyphs project, I added an intermediate layer for a, e, and s at my regular wght value (106.875). Then, I used glyphs2ufo, and then tried to build a VF with fontmake.

It failed with the message fontmake: Error: In 'sources/Lang_Roman.designspace': Generating fonts from Designspace failed: Locations must be unique. Sure enough, each intermediate layer is treated as a separate layer, and these aren’t combined into one layer, so it creates duplicate locations for sources in the designspace.

Once I changed those locations to be just a single weight unit apart (106.875, 107.875, and 108.875), it did compile. Of course, this isn’t a great workaround. I suppose my real workaround would be to combine those glyphs into a single layer in the UFO, and update the designspace to only point at that one layer as a source. Maybe that wouldn’t be possible to convert back into a Glyphs document, though? Or maybe it could, if the original GlyphsApp layer names were recorded in the designspace lib?

If I had to guess at what might be going wrong... maybe GlyphsApp assigns a date-based name for those layers, and then glyphsLib either doesn’t or can’t assign those to the same layer? This is just me speculating with no understanding of how glyphsLib handles this, though.

image

@anthrotype
Copy link
Member

each intermediate layer is treated as a separate layer, and these aren’t combined into one layer, so it creates duplicate locations for sources in the designspace.

this has to be a regression because it definitely used to work before.. /cc @simoncozens @schriftgestalt @madig @khaledhosny

@simoncozens
Copy link
Collaborator

I don't understand the semantics of having more than one layer at the same location. What's that supposed to do?

@anthrotype
Copy link
Member

Are you using Glyphs 2 or 3? If using Glyphs 3, have you marked those layers as "intermediate"?

I don't understand the semantics of having more than one layer at the same location. What's that supposed to do?

I think the problem is that the corresponding intermediate layers (at same location) across different glyphs are being assigned different source.layer attributes in the generated designspace so they appear to varLib as multiple sources at the same locations (invalid) whereas the expectation is that intermediate layers from different glyphs all get merged into the same UFO sparse layer associated with a single DS source.
We need to check how we generate the source layer attribute.

@anthrotype
Copy link
Member

I think this is the same issue as #851

@anthrotype
Copy link
Member

there's a pending PR from @kontur that tries to fix this #902

another hack that @arrowtype could try in the meantime is to manually rename the intermediate layers within the UI before these get marked as "intermediate" (after which their underlying name [initially just the current date/time] can't be edited any more from the UI), such that each corresponding intermediate layer in different glyphs gets assigned the same layer.name as expected. The name doesn't actually matter as long as it's the same for layers at the same location.

@arrowtype
Copy link

Ah, nice, thanks! Yes, it looks like this is the same issue, and it appears that the PR would address it (I haven't read any code or tested the PR, though).

For now, the workaround of naming layers before marking them as "intermediate" is good to know about.

Thanks again!

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

No branches or pull requests

5 participants