[buildmasterotfs] incompatible sources produced
This scenario has been coming up in FDK workshops periodically. I explain that VFs by themselves do not support extrapolation, but it is possible to “fake” an extrapolating VF, by extrapolating static UFOs, and assigning those as new sources.
The problem is that sometimes those new sources are no longer compatible – in this specific scenario, an implied closepath (contour 0 point 0) is extrapolated into non-existence:
Since the buildmasterotfs tool is basically just running makeotf, there is no reason for the tool to insert a closepath when start- and endpoint of the contour are equal (as in this example). The result is a set of incompatible OTFs.
When the buildcff2vf step is taken, this is the output:
Reading source fonts...
Building variable OTF (CFF2) font...
The input set requires compatibilization. Please try again with the -c (--check-compat) option.
using the -c option:
Reading source fonts...
Checking outline compatibility in source fonts...
Traceback (most recent call last):
File "/bin/buildcff2vf", line 8, in <module>
sys.exit(main())
File "/lib/python3.10/site-packages/afdko/buildcff2vf.py", line 564, in main
do_compatibility(vf, font_list, ds_data.base_idx)
File "/lib/python3.10/site-packages/afdko/buildcff2vf.py", line 332, in do_compatibility
region_charstring.draw(compat_pen)
File "/lib/python3.10/site-packages/fontTools/misc/psCharStrings.py", line 1148, in draw
extractor.execute(self)
File "/lib/python3.10/site-packages/fontTools/misc/psCharStrings.py", line 616, in execute
super().execute(charString)
File "/lib/python3.10/site-packages/fontTools/misc/psCharStrings.py", line 340, in execute
rv = handler(index)
File "/lib/python3.10/site-packages/fontTools/misc/psCharStrings.py", line 676, in op_rmoveto
self.rMoveTo(self.popallWidth())
File "/lib/python3.10/site-packages/fontTools/misc/psCharStrings.py", line 628, in rMoveTo
self.pen.moveTo(self._nextPoint(point))
File "/lib/python3.10/site-packages/fontTools/pens/basePen.py", line 324, in moveTo
self._moveTo(pt)
File "/lib/python3.10/site-packages/fontTools/varLib/cff.py", line 612, in _moveTo
self.add_point("rmoveto", pt_coords)
File "/lib/python3.10/site-packages/afdko/buildcff2vf.py", line 134, in add_point
raise VarLibCFFPointTypeMergeError(
fontTools.varLib.errors.VarLibCFFPointTypeMergeError: Glyph 'sterling': 'rmoveto' at point index 14 in master index 1 differs from the default font point type 'rlineto'
While this particular scenario may not be all too common, the fact that buildmasterotfs is unaware of point compatibility concerns is a problem.
To reproduce the problem, you may use the attached source files and run the following commands:
makeinstancesufo -ac -d 1_extrapolation.designspace
buildmasterotfs -d 2_Exercise2.designspace
buildcff2vf -d 2_Exercise2.designspace
This happens because of generalizeCFF in buildmasterotfs. I'm not sure what the reason was for adding that, but maybe we should have a flag to skip it for situations like this. For CJK production I don't use buildmasterotfs because it's not needed at all. I just run makeotf.
This should be addressed by https://github.com/adobe-type-tools/afdko/pull/1754 (I think?) @punchcutter ?
I don't think so. That's only updating makeinstancesufo. The problem in this one is happening inside buildmasterotfs.
🤦 My bad - should have read better, I guess
Skef will look at generalizeCFF
This actually happens before generalizeCFF when we produce the initial CFF using tx. I believe the problem traces to a coincidence: The instance has a zero length line at the end of a path. The ABF closepath function has code to remove path-closing lines that are too short, where a length of 0 counts as too short.
I think the solution will be to add a new flag at the same level as ABF_PATH_REMOVE_OVERLAP (the only existing flag for abfEndFont) to suppress this removal and then hook it up to some option to tx , which we can then pass from buildmasterotfs.
I lost my nerve when discussing this with @punchcutter earlier today but I went back and I still think the issue does actually occur prior to generalizeCFF, even though removing the call to generalizeCFF allows the paths to interpolate.
Before the call to generlizeCFF each version of the glyph looks pretty much like this in ttx:
<CharString name="sterling">
581 39 hmoveto
533 214 -220 4 hlineto
16 26 -1 12 10 36 -15 46 34 vvcurveto
55 25 1 22 8 13 -2 -21 19 vhcurveto
134 135 rlineto
68 -60 -63 24 -81 hhcurveto
-144 -116 -92 -150 hvcurveto
-33 36 -60 -38 vvcurveto
-65 -62 -33 -44 -12 vhcurveto
12 89 rmoveto
420 150 -351 hlineto
-69 -9 rlineto
endchar
</CharString>
Note that there's a vhcurveto right before the second (r)moveto, even though
those paths end with a line. So what's happening here is that at this point the last line was already optimized away as unnecessary because CFF will close the path.
Then what happens is we call generalizeCFF, which among other things ensures that all paths are closed. It therefore adds the line back in in three out of four cases. In the fourth case the end of the path was already coincident with the start, so it does nothing. Now the paths don't interpolate.
Eliminating the first optimization should fix this.
Ok, the problem was here. The fix is trivial -- just don't suppress the closing lineTo. The question is whether to add a flag to tx to turn this on or just do it by default.
Adding a flag isn't great because our flags are organized by output format rather than input format, so people might not notice its there. And it was a questionable thing to do in the first place.
On the other hand, changing it generally makes 75 tests "break" (not because anything is actually broken, but because the generated output doesn't match what we're checking against) ...