googlefonts / fez

Font Engineering made eaZy
BSD 3-Clause "New" or "Revised" License
14 stars 3 forks source link

ValueError: Need to Synthesize location #19

Open clauseggers opened 1 year ago

clauseggers commented 1 year ago

Trying to run fez2fea gives me this:

fez2fea ../Playfair-2_1-Roman.glyphs nut_fractions.fez > nut_fractions.fez.fea
Traceback (most recent call last):
  File "/Users/claus/.local/bin/fez2fea", line 32, in <module>
    font = load(args.font) # XXX master
           ^^^^^^^^^^^^^^^
  File "/Users/claus/.local/virtualenv/fez/venv/lib/python3.11/site-packages/babelfont/__init__.py", line 17, in load
    return Convert(filename).load()
           ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/claus/.local/virtualenv/fez/venv/lib/python3.11/site-packages/babelfont/convertors/__init__.py", line 70, in load
    return c.load(self, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/claus/.local/virtualenv/fez/venv/lib/python3.11/site-packages/babelfont/convertors/__init__.py", line 26, in load
    return self._load()
           ^^^^^^^^^^^^
  File "/Users/claus/.local/virtualenv/fez/venv/lib/python3.11/site-packages/babelfont/convertors/glyphs.py", line 89, in _load
    self.font.instances.append(self._load_instance(ginstance))
                               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/claus/.local/virtualenv/fez/venv/lib/python3.11/site-packages/babelfont/convertors/glyphs.py", line 342, in _load_instance
    raise ValueError("Need to Synthesize location")
ValueError: Need to Synthesize location
simoncozens commented 1 year ago

OK, that looks like a problem with instance mapping in Babelfont. For now, try running it on the font binary instead of the source; I'll see what's going on with babelfont.

simoncozens commented 1 year ago

(Another data point is that Emma's fork works fine.)

simoncozens commented 1 year ago

Please try git babelfont:

pip3 install -U git+https://github.com/simoncozens/babelfont/
clauseggers commented 1 year ago

I just tried Emma’s version, and I got further, but still exception

fez2fea Playfair.glyphs nut_fractions.fez > nut_fractions.fea
<features>:42:16: Ambiguous glyph name that looks like a range: 'brevecomb-cy'
<features>:43:20: Ambiguous glyph name that looks like a range: 'brevecomb-cy.case'
<features>:46:24: Ambiguous glyph name that looks like a range: 'i-cy'
<features>:46:29: Ambiguous glyph name that looks like a range: 'je-cy'
# # Couldn't find glyph 'bar1.afrc' in font (bar1.afrc at (1, 9))
Traceback (most recent call last):
  File "/Users/claus/.local/virtualenv/fez/venv/lib/python3.11/site-packages/fez/__init__.py", line 417, in parseString
    rv = self.expand_statements(rv)
         ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/claus/.local/virtualenv/fez/venv/lib/python3.11/site-packages/fez/__init__.py", line 436, in expand_statements
    returned = callback()
               ^^^^^^^^^^
  File "/Users/claus/.local/virtualenv/fez/venv/lib/python3.11/site-packages/fez/__init__.py", line 490, in <lambda>
    verb_ret = (verb, [transformer._THUNK, lambda: transformer.action(ret)])
                                                   ^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/claus/.local/virtualenv/fez/venv/lib/python3.11/site-packages/fez/Feature.py", line 65, in action
    statements = self.parser.expand_statements(statements)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/claus/.local/virtualenv/fez/venv/lib/python3.11/site-packages/fez/__init__.py", line 436, in expand_statements
    returned = callback()
               ^^^^^^^^^^
  File "/Users/claus/.local/virtualenv/fez/venv/lib/python3.11/site-packages/fez/__init__.py", line 499, in <lambda>
    verb_ret = (verb, [transformer._THUNK, lambda : transformer.transform(tree) ])
                                                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/claus/.local/virtualenv/fez/venv/lib/python3.11/site-packages/lark/visitors.py", line 161, in transform
    return self._transform_tree(tree)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/claus/.local/virtualenv/fez/venv/lib/python3.11/site-packages/lark/visitors.py", line 157, in _transform_tree
    return self._call_userfunc(tree, children)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/claus/.local/virtualenv/fez/venv/lib/python3.11/site-packages/lark/visitors.py", line 128, in _call_userfunc
    raise VisitError(tree.data, tree, e)
lark.exceptions.VisitError: Error trying to process rule "action":

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Users/claus/.local/bin/fez2fea", line 42, in <module>
    p.parseFile(args.fee)
  File "/Users/claus/.local/virtualenv/fez/venv/lib/python3.11/site-packages/fez/__init__.py", line 406, in parseFile
    return self.parseString(data)
           ^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/claus/.local/virtualenv/fez/venv/lib/python3.11/site-packages/fez/__init__.py", line 419, in parseString
    raise e.orig_exc
  File "/Users/claus/.local/virtualenv/fez/venv/lib/python3.11/site-packages/lark/visitors.py", line 124, in _call_userfunc
    return f(children)
           ^^^^^^^^^^^
  File "/Users/claus/.local/virtualenv/fez/venv/lib/python3.11/site-packages/fez/Fractions.py", line 108, in action
    assert len(fraction) == 1
           ^^^^^^^^^^^^^^^^^^
AssertionError
simoncozens commented 1 year ago

OK, so you can only have one fraction slash glyph. What's your nut_fractions.fez?

clauseggers commented 1 year ago

So that is the next question. I want these nuts in the afrc feature and I have made special zero.dnom.afrc (&c) and zero.numr.afrc for this purpose. The horizontal solidus is called bar1.afrc.

I have yet to understand the syntax of the fez files, so I just tried running it on this:

LoadPlugin Fractions;
Feature dnom { Substitute /dnom$/~dnom -> /.dnom$/; };
Feature numr { Substitute /numr$/~numr -> /.numr$/; };
Feature afrc {
  Fractions /numr$/ bar1.afrc /dnom$/ 152;
};

PS: I can’t get the Vim syntax files to work. They are made for fee for some reason?

clauseggers commented 1 year ago

PS: I installed these things in a virtualenv. Probably didn’t alias all the binaries to the dir for this purpose that is included in my PATH. Is that the issue?

I just tried to put all the executables in my PATH but that didn’t make a difference.

simoncozens commented 1 year ago

Here's what I get with a fraction glyph which is a 400-unit long slug and a nut_fractions.fez like this:

    LoadPlugin Fractions;
    Feature frac {
      Fractions /numr$/ fraction /dnom$/ 0;
    };

shape

clauseggers commented 1 year ago

Same issue as before.

fez2fea Playfair-2_1-Roman.glyphs Settings/nut_fractions.fez > nut_fractions.fez.fea
Traceback (most recent call last):
  File "/Users/claus/.local/bin/fez2fea", line 32, in <module>
    font = load(args.font) # XXX master
           ^^^^^^^^^^^^^^^
  File "/Users/claus/.local/virtualenv/fez/venv/lib/python3.11/site-packages/babelfont/__init__.py", line 17, in load
    return Convert(filename).load()
           ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/claus/.local/virtualenv/fez/venv/lib/python3.11/site-packages/babelfont/convertors/__init__.py", line 70, in load
    return c.load(self, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/claus/.local/virtualenv/fez/venv/lib/python3.11/site-packages/babelfont/convertors/__init__.py", line 26, in load
    return self._load()
           ^^^^^^^^^^^^
  File "/Users/claus/.local/virtualenv/fez/venv/lib/python3.11/site-packages/babelfont/convertors/glyphs.py", line 89, in _load
    self.font.instances.append(self._load_instance(ginstance))
                               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/claus/.local/virtualenv/fez/venv/lib/python3.11/site-packages/babelfont/convertors/glyphs.py", line 342, in _load_instance
    raise ValueError("Need to Synthesize location")
ValueError: Need to Synthesize location
simoncozens commented 1 year ago

So that one's a babelfont issue. You'll need to install babelfont from git in your virtual env. Or just temporarily delete the "variable" instance in your glyphs file. Your fez code looks fine.

clauseggers commented 1 year ago

I did install babel from git as you linked earlier

pip3 install -U git+https://github.com/simoncozens/babelfont/

in a virtualenv, and then linked the binary to my path. This doesn‘t work giving me the last error I posted.

If I delete the VF-instance then it does work (it produces output – haven’t checked it yet)

clauseggers commented 1 year ago

Can you explain to me how this works: I generated the feature, and now have to plonk it back into Glyphs under a manual afrc feature. I then ignore the

table GDEF {
    GlyphClassDef [A Aacute … [];
} GDEF;

table, and paste the lookup routines 1 and 2 into the afrc feature, and delete the

feature afrc {
            lookup Routine_1;

} afrc;

feature afrc {
            lookup Routine_2;

} afrc;

However that gives me lookup Routine_1 containing substitutions

lookup Routine_1 {
    lookupflag 0;
    ;
    sub [zero.numr.afrc one.numr.afrc two.numr.afrc three.numr.afrc four.numr.afrc five.numr.afrc six.numr.afrc seven.numr.afrc eight.numr.afrc nine.numr.afrc] [zero.numr.afrc one.numr.afrc two.numr.afrc three.numr.afrc four.numr.afrc five.numr.afrc six.numr.afrc seven.numr.afrc eight.numr.afrc nine.numr.afrc] [zero.numr.afrc one.numr.afrc two.numr.afrc three.numr.afrc four.numr.afrc five.numr.afrc six.numr.afrc seven.numr.afrc eight.numr.afrc nine.numr.afrc] [zero.numr.afrc one.numr.afrc two.numr.afrc three.numr.afrc four.numr.afrc five.numr.afrc six.numr.afrc seven.numr.afrc eight.numr.afrc nine.numr.afrc] bar1.afrc' by bar1.afrc bar1.afrc bar1.afrc bar1.afrc;
    sub bar1.afrc' [zero.dnom.afrc one.dnom.afrc two.dnom.afrc three.dnom.afrc four.dnom.afrc five.dnom.afrc six.dnom.afrc seven.dnom.afrc eight.dnom.afrc nine.dnom.afrc] [zero.dnom.afrc one.dnom.afrc two.dnom.afrc three.dnom.afrc four.dnom.afrc five.dnom.afrc six.dnom.afrc seven.dnom.afrc eight.dnom.afrc nine.dnom.afrc] [zero.dnom.afrc one.dnom.afrc two.dnom.afrc three.dnom.afrc four.dnom.afrc five.dnom.afrc six.dnom.afrc seven.dnom.afrc eight.dnom.afrc nine.dnom.afrc] [zero.dnom.afrc one.dnom.afrc two.dnom.afrc three.dnom.afrc four.dnom.afrc five.dnom.afrc six.dnom.afrc seven.dnom.afrc eight.dnom.afrc nine.dnom.afrc] by bar1.afrc bar1.afrc bar1.afrc bar1.afrc;
    sub [zero.numr.afrc one.numr.afrc two.numr.afrc three.numr.afrc four.numr.afrc five.numr.afrc six.numr.afrc seven.numr.afrc eight.numr.afrc nine.numr.afrc] [zero.numr.afrc one.numr.afrc two.numr.afrc three.numr.afrc four.numr.afrc five.numr.afrc six.numr.afrc seven.numr.afrc eight.numr.afrc nine.numr.afrc] [zero.numr.afrc one.numr.afrc two.numr.afrc three.numr.afrc four.numr.afrc five.numr.afrc six.numr.afrc seven.numr.afrc eight.numr.afrc nine.numr.afrc] bar1.afrc' by bar1.afrc bar1.afrc bar1.afrc;
    sub bar1.afrc' [zero.dnom.afrc one.dnom.afrc two.dnom.afrc three.dnom.afrc four.dnom.afrc five.dnom.afrc six.dnom.afrc seven.dnom.afrc eight.dnom.afrc nine.dnom.afrc] [zero.dnom.afrc one.dnom.afrc two.dnom.afrc three.dnom.afrc four.dnom.afrc five.dnom.afrc six.dnom.afrc seven.dnom.afrc eight.dnom.afrc nine.dnom.afrc] [zero.dnom.afrc one.dnom.afrc two.dnom.afrc three.dnom.afrc four.dnom.afrc five.dnom.afrc six.dnom.afrc seven.dnom.afrc eight.dnom.afrc nine.dnom.afrc] by bar1.afrc bar1.afrc bar1.afrc;
    sub [zero.numr.afrc one.numr.afrc two.numr.afrc three.numr.afrc four.numr.afrc five.numr.afrc six.numr.afrc seven.numr.afrc eight.numr.afrc nine.numr.afrc] [zero.numr.afrc one.numr.afrc two.numr.afrc three.numr.afrc four.numr.afrc five.numr.afrc six.numr.afrc seven.numr.afrc eight.numr.afrc nine.numr.afrc] bar1.afrc' by bar1.afrc bar1.afrc;
    sub bar1.afrc' [zero.dnom.afrc one.dnom.afrc two.dnom.afrc three.dnom.afrc four.dnom.afrc five.dnom.afrc six.dnom.afrc seven.dnom.afrc eight.dnom.afrc nine.dnom.afrc] [zero.dnom.afrc one.dnom.afrc two.dnom.afrc three.dnom.afrc four.dnom.afrc five.dnom.afrc six.dnom.afrc seven.dnom.afrc eight.dnom.afrc nine.dnom.afrc] by bar1.afrc bar1.afrc;
} Routine_1;

and lookup Routine_2 giving me all the position adjustments.

Now where are the initial substitutions from the regular numbers? My *.(d)nom.afrc glyphs are to be used in the nut fractions, but where are the substitutions from a string like '23/45' into the `afrca glyphs?

simoncozens commented 1 year ago

However that gives me lookup Routine_1 containing substitutions and lookup Routine_2 giving me all the position adjustments.

This is all good so far.

where are the substitutions from a string like '23/45' into the `afrca glyphs?

This plugin deals with the common case where you will use the frac feature and have already defined the dnom and numr features. In such cases, a shaping engine like Harfbuzz will automatically call dnom/numr as part of fraction processing.

It sounds like you additionally want something to make the denominator/numerator substitutions for you as part of the afrc feature. This is slightly out of scope for the plugin as-written, so you'll have to implement it yourself. Here's how I would do it in FEZ. You can still copy the output into Glyphs.

DefineClass @digits = [one two three four five six seven eight nine];

Feature afrc {
  # Maybe you want to change fraction slash to bar1.afrc here. That needs a separate lookup
  Routine SlashToBar { Substitute fraction -> bar1.afrc; };

  # Denominator substitutions are easy; any digit coming after either a bar1.afrc or another denominator
  Routine Denominator {
     Substitute [bar1.afrc /dnom$/] (@digits) -> @digits.dnom.arfc;
  };
  # Numerator substitutions are harder because we don't know how many places there will be before the slash.
  Routine Numerator {
    Substitute (@digits) @digits @digits @digits bar1.afrc -> @digits.numr.arfc;
    Substitute (@digits) @digits @digits bar1.afrc -> @digits.numr.arfc;
    Substitute (@digits) @digits bar1.afrc -> @digits.numr.arfc;
    Substitute (@digits) bar1.afrc -> @digits.numr.arfc;
  };
 Routine Fraction {
     Fractions /numr$/ bar1.afrc /dnom$/ 152;
  };
};
dscorbett commented 1 year ago

Numerator substitutions are harder because we don't know how many places there will be before the slash.

Does FEZ support reverse chaining contextual single substitutions? That would make numerators as easy as denominators.

clauseggers commented 1 year ago

Thank you Simon. I want to put the afac in its own self-contained feature using its own special glyphs. So I edited your script slightly to use my naming.

LoadPlugin Fractions;
DefineClass @afrcFigures = [one two three four five six seven eight nine];
DefineClass @afrcFraction = [slash fraction];

Feature afrc {
    # Change slash to bar.afrc here. That needs a separate lookup
    Routine SlashToBar { Substitute @afrcFraction -> bar.afrc; };

    # Denominator substitutions are easy; any digit coming after either a bar.afrc or another denominator
    Routine Denominator {
        Substitute [bar.afrc /dnom$/] (@afrcFigures) -> @afrcFigures.dnom.afrc;
    };

    # Numerator substitutions are harder because we don't know how many places there will be before the slash.
    Routine Numerator {
        Substitute (@afrcFigures) @afrcFigures @afrcFigures @afrcFigures bar.afrc -> @afrcFigures.numr.afrc;
        Substitute (@afrcFigures) @afrcFigures @afrcFigures bar.afrc -> @afrcFigures.numr.afrc;
        Substitute (@afrcFigures) @afrcFigures bar.afrc -> @afrcFigures.numr.afrc;
        Substitute (@afrcFigures) bar.afrc -> @afrcFigures.numr.afrc;
    };

    Routine Fraction {
        Fractions /numr$/ bar.afrc /dnom$/ 152;
    };
};

Problem is that I can not get – what seems to be – parts of GSUB and all of GPOS to work.

Screenshot 2023-05-22 at 18 00 48
simoncozens commented 1 year ago

Does FEZ support reverse chaining contextual single substitutions?

Yes, with ReverseSubstitute, but then you're restricting yourself to actually spec compliant shaping engines, which is very few of them. :-)

simoncozens commented 1 year ago

/dnom$/ and /numr$/ should be /dnom.afrc$/ and /numr.afrc$/ respectively.

clauseggers commented 1 year ago

I see, thank you very much Simon. It now works. There are however the issue of Variable Fonts looming large. So here is the question, do you think the GPOS values could be tokenised in Glyphs?

simoncozens commented 1 year ago

Personally, that's not how I'd do it, as the values are computed based on widths of glyphs etc. FEZ has the ability to create "variable scalars", so I would need to update the plugin to be a bit more variable-aware. I haven't needed this in my projects since the fraction figures have had quite similar widths across masters, but it seems like something worth doing for the future.

However, this would not allow you to do Glyphs exports, since the "variable scalar" syntax in FEA (which FEZ generates) is only supported in fontTools and Font Creator; Glyphs handles it through tokens, as you say, because Glyphs went with its own non-standard variable FEA syntax. I suppose one could come up with something which reads a variable FEA file, turns all the variable scalars into tokens, and tells you what the values of those tokens should be in the different masters, but I'm afraid I don't really have much use for that sort of thing (I use fontmake for all my exports), so I wouldn't be very interested in writing it myself.

clauseggers commented 1 year ago

Alright, thanks for writing it out Simon. I’ll have a think about it.

clauseggers commented 1 year ago

I want to try to manually replace all the numeric GPOS values in the generated FEA with Glyphs tokens. The obstacle here is how the values are calculated, which I can’t decipher.

I can suss out some of the calculations are done in the "Center the glyphs" section but that is where I get stuck. The very first line in the generated fea gpos sections looks like this:

pos one.numr.afrc' <314 0 -243 0> one.numr.afrc' <557 0 -243 0> one.numr.afrc' <800 0 -243 0> one.numr.afrc' <1043 0 -243 0> bar.afrc bar.afrc bar.afrc bar.afrc;

The -243 I have figured out as the negative advance width of the one.numr.afrc, but the first value in the <314 0 -243 0> I can’t figure out. I have the overlap set to zero to make this easier, 314 is can’t get to looking at my glyph metrics. Can you tell me how this value is derived?