googlefonts / fontmake

Compile fonts from sources (UFO, Glyphs) to binary (OpenType, TrueType).

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

should decompose components with different 2x2 transforms when building VF

cjdunn opened this issue · comments

Example: the /Otildeacute glyph in the Black named instance doesn't match the same glyph in the Black UFO master when it uses a nested component. But when the nested component (tildecomb_acutecomb.case) is decomposed, the glyph in the var font matches what's in the master. This seems like a bug. (screenshots below for reference)

As a designer it's better to keep the sources with high level information (ie /tildecomb_acutecomb.case is made up of /tildecomb and /acutecomb.case). And I would expect that a Black named instance in the var font should match the static font generated from that Black UFO master, especially when there is only one axis and only two masters.

I understand that components can't be nested in the final font. But it seems that fontmake could do a better job of decomposing the nested component on generate so the named instance matches the master and we can leave the sources as is for future editing. Thanks! (the example here is from the Alegreya project for @davelab6 and @m4rc1e )

UFO_master
fontmake_compare

I have experienced similar issues with Josefin Sans, where components of components were getting offset, and simply decomposing solved the issue.

davelab6/josefinsans#4
davelab6/josefinsans#14

I’d love to retain components of components in the sources if possible. Many thanks!

I cloned the fonts from https://github.com/TypeNetwork/Alegreya-Sans and built with current fontmake v2.0.4 but I cannot reproduce this issue. Did you maybe commit a workaround already?

The glyph "Otildeacute" is composed of component glyphs "O" and "tildecomb_acutecomb.case", which in turn is composed from "tildcomb_acutecomb" (the latter is a simple outline glyph).
When I render the Otildecomb glyph in the VF and static instance fonts, it looks correct in both.

A simple reproducer is worth more than a thousand words.

I see what's going on. The issue is not to do with nested components.
Basically, you can't have glyphs containing components that have different scale/slant/rotation between different masters of a variable font; only the placement of a component can vary across masters: i.e. the component's X and Y offset. Here's the relevant bit of the OT spec:

Adjustment deltas do not have any effect on scaling or 2 × 2 transformations applied to a component. The deltas only affect the positioning of the component.

https://docs.microsoft.com/en-us/typography/opentype/spec/gvar#point-numbers-and-processing-for-composite-glyphs

In your font you have the tildecomb_acutecomb.case glyph that looks like this in the Alegreya-Regular.ufo:

<glyph name="tildecomb_acutecomb.case" format="2">
  <outline>
    <component base="tildecomb" yOffset="140"/>
    <component base="acutecomb.case" xScale="0.9986" xyScale="-0.0523" yxScale="0.0523" yScale="0.9986" xOffset="-75" yOffset="139"/>
  </outline>
</glyph>

and the same glyph in the bolder master is:

<glyph name="tildecomb_acutecomb.case" format="2">
  <outline>
    <component base="tildecomb" xScale="0.9259" yScale="0.9259" xOffset="15" yOffset="189"/>
    <component base="acutecomb.case" xOffset="-21" yOffset="180"/>
  </outline>
</glyph>

So it's a tecnical limitation of the font format that requires such composite glyphs with complex transformations beyond simple XY offsets to be decomposed.

I suppose fontmake could be smarter and decompose these components on-the-fly before they reach varLib (in a ufo2ft filter).

The reason this works when generating static instances is because when we interpolate UFOs with fontMath, the latter also interpolates the 2x2 transform matrix, not just the component's offsets.

Thanks Cosimo! that makes sense.

@anthrotype thank you for taking a look at this. The fact that they get decomposed in the end isn't the problem for me, and I understand that's a limitation of the format.

fontmake could be smarter and decompose these components on-the-fly before they reach varLib (in a ufo2ft filter).

Yes, this would be amazing, thank you!

btw, it's not the fact that there is any transformation, but that the transformations differ across the masters. If they are the same, i think it should be ok, as the transform will be written in the default glyf table, and the gvar will take care of shifting components around. Although that should probably be tested.

i'm curious, do you know if Glyphs.app decomposes them when generating a VF?

I haven't been generating any VFs with Glyphs.app so I'm not sure.

commented

You can add a custom parameter in Glyphs' instance panel that goes when exporting VFs (Decompose Components). It requests you add a list of which glyphs to decompose, which is helpful IMO.

Thanks Micah! That reminded me that fontmake also supports specifying a ufo2ft filter to decompose specific composite glyphs at build time.
ufo2ft filters are still not well documented (though @madig had been working on a PR googlefonts/ufo2ft#286).

But basically, in your specific case, you can have fontmake decompose "tildecomb_acutecomb.case" (and similar glyphs with complex transforms) in Alegreya-Regular.ufo and Alegreya-Bold.ufo by adding a "com.github.googlei18n.ufo2ft.filters" key in the UFOs lib.plist, containing a "decomposeComponents" filter like this one:

diff --git a/sources/Alegreya-Bold.ufo/lib.plist b/sources/Alegreya-Bold.ufo/lib.plist
index 76f20563..6716983b 100644
--- a/sources/Alegreya-Bold.ufo/lib.plist
+++ b/sources/Alegreya-Bold.ufo/lib.plist
@@ -5963,5 +5963,18 @@
     </dict>
     <key>width</key>
     <integer>30</integer>
+    <key>com.github.googlei18n.ufo2ft.filters</key>
+    <array>
+      <dict>
+        <key>name</key>
+        <string>decomposeComponents</string>
+        <key>include</key>
+        <array>
+          <string>tildecomb_acutecomb.case</string>
+        </array>
+      </dict>
+    </array>
   </dict>
 </plist>
diff --git a/sources/Alegreya-Regular.ufo/lib.plist b/sources/Alegreya-Regular.ufo/lib.plist
index 4d25f045..48a6e0dd 100644
--- a/sources/Alegreya-Regular.ufo/lib.plist
+++ b/sources/Alegreya-Regular.ufo/lib.plist
@@ -5967,5 +5967,18 @@
     </dict>
     <key>width</key>
     <integer>84</integer>
+    <key>com.github.googlei18n.ufo2ft.filters</key>
+    <array>
+      <dict>
+        <key>name</key>
+        <string>decomposeComponents</string>
+        <key>include</key>
+        <array>
+          <string>tildecomb_acutecomb.case</string>
+        </array>
+      </dict>
+    </array>
   </dict>
 </plist>

Normally the decomposeComponents filter runs by default on all fonts (we need to decompose mixed contour+component glyphs). Here we make a second pass but only on a particular selection of glyphs, those specified in the include array.

Of couse, with this manual approach the font developer will have to get/know the list of glyphs with transforms that require decomposition. Ideally fontmake should do that automatically when building a VF.

For a Glyphs-based build workflow, the same thing can be achieved by adding a "Filter" or ("PreFilter") custom parameter named "Decompose Components" to both the Regular and Bold masters (not to the instances, otherwise the VF will not see them, since it is built from the masters), with an include: tildecomb_acutecomb.case, ... list of glyphs to decompose.
Fontmake (via glyphsLib) knows how to convert this into the equivalent ufo2ft filter.

I ran into this while doing release work on Inconsolata, and ended up writing a quick script to detect mismatched 2x2 matrices and decompose just those components. I hope others find it useful: