googlefonts / cu2qu

Cubic-to-quadratic bezier curve conversion
Apache License 2.0
64 stars 21 forks source link

Possible false fail in cu2qu: "Glyphs named <x> have different number of segments" #182

Closed thundernixon closed 5 years ago

thundernixon commented 5 years ago

I'm working on a variable font that has quite a few masters. I'm drawing in RoboFont with cubic-bezier UFOs, and using FontMake to build these into a variable font.

I have been working on this for quite awhile, so I'm fairly familiar with the general usage of FontMake, and I am routinely really impressed with how good a job cu2qu does in converting my cubic drawings to quadratics – even when I'm doing some fairly experimental stuff.

However, I'm butting up against a strange blocker: a few glyphs refused to convert to quadratics, but I can't find anything incompatible about them, let alone anything that supports the error message cu2qu is giving me.

INFO:fontmake.font_project:Building variable font font-betas/work-in-progress/Recursive-mono-full--w_ital_slnt-2019_07_23.ttf
INFO:ufo2ft:Pre-processing glyphs
ERROR:cu2qu.ufo:Glyphs named 'colonmonetary' have different number of segments
ERROR:cu2qu.ufo:Glyphs named 'B' have different number of segments
ERROR:cu2qu.ufo:Glyphs named 'currency' have different number of segments
ERROR:cu2qu.ufo:Glyphs named 'n' have different number of segments
ERROR:cu2qu.ufo:Glyphs named 'a' have different number of segments
ERROR:cu2qu.ufo:Glyphs named 'bitcoin' have different number of segments
Traceback (most recent call last):
  File "/Users/stephennixon/type-repos/recursive/venv/bin/fontmake", line 10, in <module>
    sys.exit(main())
  File "/Users/stephennixon/type-repos/recursive/venv/lib/python3.7/site-packages/fontmake/__main__.py", line 407, in main
    project.run_from_designspace(designspace_path, **args)
  File "/Users/stephennixon/type-repos/recursive/venv/lib/python3.7/site-packages/fontmake/font_project.py", line 914, in run_from_designspace
    **kwargs
  File "/Users/stephennixon/type-repos/recursive/venv/lib/python3.7/site-packages/fontmake/font_project.py", line 969, in _run_from_designspace_interpolatable
    designspace, output_path=output_path, output_dir=output_dir, **kwargs
  File "/Users/stephennixon/type-repos/recursive/venv/lib/python3.7/site-packages/fontmake/font_project.py", line 411, in build_variable_font
    inplace=True,
  File "/Users/stephennixon/type-repos/recursive/venv/lib/python3.7/site-packages/ufo2ft/__init__.py", line 542, in compileVariableTTF
    inplace=inplace,
  File "/Users/stephennixon/type-repos/recursive/venv/lib/python3.7/site-packages/ufo2ft/__init__.py", line 388, in compileInterpolatableTTFsFromDS
    for source, ttf in zip(result.sources, ttfs):
  File "/Users/stephennixon/type-repos/recursive/venv/lib/python3.7/site-packages/ufo2ft/__init__.py", line 274, in compileInterpolatableTTFs
    glyphSets = preProcessor.process()
  File "/Users/stephennixon/type-repos/recursive/venv/lib/python3.7/site-packages/ufo2ft/preProcessor.py", line 220, in process
    remember_curve_type=self._rememberCurveType and self.inplace,
  File "/Users/stephennixon/type-repos/recursive/venv/lib/python3.7/site-packages/cu2qu/ufo.py", line 283, in fonts_to_quadratic
    raise IncompatibleFontsError(glyph_errors)
cu2qu.errors.IncompatibleFontsError: fonts contains incompatible glyphs: 'B', 'a', 'bitcoin', 'colonmonetary', 'currency', 'n'

I've found quite a few ways to make sure glyphs are compatible for FontMake & cu2qu:

I have also confirmed that the anchors match in the /n and the /B (the order of them is different in the /a, but based on /n failing, this probably isn't what's triggering the failure).

However, I'm unable to find anything incompatible or unique about the glyphs that are failing this test. I suspect that there must be something I've missed because most glyphs I have checked are building just fine.

Still, because I have confirmed that the number of segments in these glyphs is exactly the same, I think it's likely that the wrong error message is getting triggered by something in my drawings, and this is probably a bug that could be fixed in cu2qu.

Reproducing the issue

Here's a permalink to a set of UFOs and a designspace that gives me this issue:

https://github.com/thundernixon/recursive/tree/a149f28374805800c24168621ce5f52e8283e58c/src/masters/mono

UPDATE: here is the correct permalink: https://github.com/thundernixon/recursive/tree/a149f28374805800c24168621ce5f52e8283e58c/src/masters/mono/recursive_mono-varfontprep-2019_07_23-19_14_09

If you clone the repo and checkout commit (a149f283), the designspace giving this issue can be built with the following command:

fontmake -m src/masters/mono/recursive_mono-varfontprep-2019_07_23-19_14_09/Recursive-mono-full--w_ital_slnt.designspace -o variable

(You may need to install FontMake if you don't already have it, but I'm guessing if you're checking this issue, you do).

Thanks for any pointers, questions, and suggestions!

thundernixon commented 5 years ago

Just to add a couple of possibly useful visuals here, I wanted to show how I tested the number of segments. I am using a script to check segments in all open fonts, for the current glyph.

For checking the count of points in general, I am using Prepolator for basic matching, plus something I can't publicly share right now that counts all points, including off-curve points.

/n

Check for number & type of segments in /n, across masters:

image

/B

Check for number & type of segments in /n, across masters:

image

miguelsousa commented 5 years ago

I've checked out https://github.com/thundernixon/recursive/commit/a149f28374805800c24168621ce5f52e8283e58c and ran an internal script we have (which leverages cu2qu v1.6.5) on all the 12 UFOs at https://github.com/thundernixon/recursive/tree/master/src/masters/mono

The output of that script is below. Everything before the line INCOMPATIBILITY REPORT: are messages that come straight from cu2qu, and the lines after are basically just a summary of the errors that were found.

It doesn't seem like my report has any of the glyphs you're getting errors on, so perhaps the UFOs you're using are different from those that have been committed.

Glyphs named 'hungarumlautcomb' have different number of segments
Glyphs named 'M.topserif' have different number of segments
Glyphs named 'arrowup' have different number of segments
Glyphs named '.arrowhead' have incompatible segment types:
   4: ('curve', 'curve', 'curve', 'curve', 'line', 'line', 'curve', 'curve')
  10: ('curve', 'curve', 'curve', 'curve', 'line', 'line', 'curve', 'curve')
Glyphs named 'macron' have different number of segments
Glyphs named 'S.morph' have incompatible segment types:
   9: ('line', 'line', 'line', 'curve')
  10: ('line', 'line', 'line', 'curve')
  21: ('curve', 'curve', 'curve', 'line')
Glyphs named 'a.curve_tail' have different number of segments
Glyphs named 'two.replaced_with_rounder' have different number of segments
Glyphs named 'a.italic_curl' have incompatible segment types:
  21: ('curve', 'curve', 'curve', 'curve', 'curve', 'curve', 'line', 'curve', 'curve', 'curve')
  25: ('line', 'line', 'line', 'line', 'curve', 'line', 'curve', 'line', 'line', 'curve')
Glyphs named 'ohm.sketch' have incompatible segment types:
  13: ('curve', 'line')
  16: ('curve', 'line')
Glyphs named 'n.replaced_with_med_curvy' have different number of segments
Glyphs named 'one.flatflag' have different number of segments
Glyphs named 'o.curvy' have different number of segments
Glyphs named 'l.curvy' have different number of segments
Glyphs named 'dotbelowcomb' have different number of segments
Glyphs named 'f.replaced_with_reverse' have incompatible segment types:
  10: ('curve', 'curve', 'curve', 'line')
Glyphs named 'multiply' have different number of segments
Glyphs named 'arrowleft' have different number of segments
Glyphs named 'ampersand.code_experimental_2' have incompatible segment types:
   7: ('curve', 'line')
  38: ('curve', 'line')
  62: ('line', 'curve')
  72: ('curve', 'line')
Glyphs named 'r.simple_italic' have incompatible segment types:
   9: ('curve', 'line', 'curve', 'curve')
  12: ('curve', 'line', 'curve', 'curve')
Glyphs named 'r.sharp' have incompatible segment types:
   9: ('curve', 'line', 'curve', 'curve')
  12: ('curve', 'line', 'curve', 'curve')
Glyphs named 'Germandbls' have different number of segments
Glyphs named 'c.sharp' have different number of segments
Glyphs named 'i.curvy' have different number of segments
Glyphs named 'Thorn' have different number of segments
Glyphs named 'ampersand' have different number of segments
Glyphs named 'bar' have different number of segments
Glyphs named 'IJ' have different number of segments
Glyphs named 'h.curvy' have different number of segments
Glyphs named 'a.ss01' have incompatible segment types:
  11: ('line', 'curve')
  14: ('curve', 'line')
  15: ('line', 'curve')
  18: ('curve', 'close')
  19: ('curve', 'move')
  20: ('line', 'curve')
  22: ('close', 'line')
  23: ('move', 'curve')
  25: ('curve', 'line')
  27: ('curve', 'line')
  30: ('close', 'curve')
  31: ('move', 'line')
  33: ('curve', 'close')
  34: ('curve', 'move')
Glyphs named 'AE' have different number of segments
Glyphs named 'commaturnedabovecomb' have different number of segments
Glyphs named 'at.short' have incompatible segment types:
   1: ('line', 'curve')
   5: ('line', 'curve')
   7: ('curve', 'close')
   8: ('curve', 'move')
   9: ('curve', 'line')
  10: ('line', 'curve')
  12: ('close', 'curve')
  13: ('move', 'line')
  18: ('curve', 'line')
  29: ('curve', 'close')
  30: ('curve', 'move')
  32: ('close', 'line')
  33: ('move', 'curve')
  37: ('line', 'curve')
  41: ('curve', 'close')
  42: ('close', 'move')
  43: ('move', 'curve')
  46: ('curve', 'line')
  49: ('line', 'curve')
Glyphs named 'ampersand.code_experimental' have incompatible segment types:
   7: ('curve', 'line')
  44: ('curve', 'line')
  62: ('curve', 'line')
Glyphs named 'guarani' have different number of segments
Glyphs named 'ohm' have different number of segments
Glyphs named 'ampersand.code_experimental_3' have incompatible segment types:
   7: ('curve', 'line')
  38: ('curve', 'line')
  72: ('curve', 'line')
Glyphs named 'S.simpleserif' have incompatible segment types:
  24: ('curve', 'curve', 'curve', 'line')
Glyphs named 'OE' have different number of segments
Glyphs named 'Lslash' have different number of segments
Glyphs named 'Z.topserif' have different number of segments
Glyphs named 'w.italic_diagonal' have different number of segments
Glyphs named 'germandbls' have different number of segments
Glyphs named 'Schwa' have different number of segments

INCOMPATIBILITY REPORT:
Glyphs: 44
Contours: 31
Segments: 58

Incompatible contours in:
    AE
    Germandbls
    IJ
    Lslash
    M.topserif
    OE
    Schwa
    Thorn
    Z.topserif
    a.curve_tail
    ampersand
    arrowleft
    arrowup
    bar
    c.sharp
    commaturnedabovecomb
    dotbelowcomb
    germandbls
    guarani
    h.curvy
    hungarumlautcomb
    i.curvy
    l.curvy
    macron
    multiply
    n.replaced_with_med_curvy
    o.curvy
    ohm
    one.flatflag
    two.replaced_with_rounder
    w.italic_diagonal

Incompatible segments in:
    (2) .arrowhead
    (3) S.morph
    (1) S.simpleserif
    (2) a.italic_curl
    (14) a.ss01
    (3) ampersand.code_experimental
    (4) ampersand.code_experimental_2
    (3) ampersand.code_experimental_3
    (19) at.short
    (1) f.replaced_with_reverse
    (2) ohm.sketch
    (2) r.sharp
    (2) r.simple_italic
thundernixon commented 5 years ago

Hey @miguelsousa, thanks so much for taking a look!

I'm really sorry, but I posted a link that was one directory too high (I had posted the correct one, but then updated things, and posted the incorrect one 😑).

The masters that have been pre-processed for build are here:

https://github.com/thundernixon/recursive/tree/a149f28374805800c24168621ce5f52e8283e58c/src/masters/mono/recursive_mono-varfontprep-2019_07_23-19_14_09

And FontMake can be pointed at this designspace:

https://github.com/thundernixon/recursive/blob/a149f28374805800c24168621ce5f52e8283e58c/src/masters/mono/recursive_mono-varfontprep-2019_07_23-19_14_09/Recursive-mono-full--w_ital_slnt.designspace

If you're willing to take another look, that would be amazing!


Notes:

I should have checked & reported the cu2qu version in my project. It is 1.6.5, as well.

On your example, I tried to use just cu2qu in this python script to convert pre-processed fonts to quadratics. Strangely, the fonts_to_quadratic function returns True, but fontscom.github.googlei18n.cu2qu.curve_type is not added to fonts and they don't seem to be quadratic when I open them in font editors. I also don't get any output from cu2qu on the command line, despite asking for stats. Maybe I'm missing something important?

miguelsousa commented 5 years ago

Confirmed, I get no cu2qu errors with the UFOs inside the recursive_mono-varfontprep-2019_07_23-19_14_09 folder.

And the variable font builds fine if in the designspace file you disable the two sources that reference layer="support.w.middle". So my guess is that the outline incompatibilities are due to the glyphs in that layer.

miguelsousa commented 5 years ago

I also don't get any output from cu2qu on the command line

I ran your script on the UFOs in the mono folder,

masters$ python3 ../build-scripts/convert-fonts-cu2qu-CLI.py mono

and got all the error messages.

arrowtype commented 5 years ago

the variable font builds fine if in the designspace file you disable the two sources that reference layer="support.w.middle". So my guess is that the outline incompatibilities are due to the glyphs in that layer.

Ohhh wow, I should have tried this but didn't think to. I'll need to confirm later, but I bet some drawings snuck their way into that support layer for the glyphs that were failing. Thanks so much for taking the time to help me find the problem!

anthrotype commented 5 years ago

if that's the case, then maybe cu2qu should also print the name of the layer where the incompatible glyphs are.

thundernixon commented 5 years ago

If it did print out the designspace source causing the problem, that would be really helpful!

thundernixon commented 5 years ago

Okay, I checked for glyphs in the support layer, and sure enough, the failing glyphs had glif files in that layer, even though these glyph files had no contours. (This seems to be the result of an unrelated script being over-eager to save settings into glif.lib).

If anyone else faces this issue in the future, I was able to search through a bunch of fonts to pinpoint glyphs in the support layer:

glyphsWithSupportLayer = "w"

fontsToCheck = []

for f in AllFonts():
    for layer in f.layers:
        if layer.name == "support.w.middle":
            fontsToCheck.append(f)

print("Glyphs that shouldn't be in layer `support.w.middle`:")

for f in fontsToCheck:
    print("\n",f.info.styleName)
    for layer in f.layers:
        if layer.name == "support.w.middle":
            for glyphName in layer.keys():
                if glyphName not in glyphsWithSupportLayer.split(" "):
                    print("  •", glyphName)

Then, I just deleted these glif files and updated the contents.plist in each layer. It was very few, so it ended up being simpler and faster than figuring out how to remove those glyphs in script.

I'll close this issue. I do think it would be great for cu2qu failure messages to point to problem sources/layers if possible, but that might be better as a separate issue. Thanks for all the help, here!