bBoxType / Kernkraft

GNU General Public License v3.0
50 stars 5 forks source link

Kerning group exceptions bug in Kernschmelze #10

Open arialcrime opened 7 years ago

arialcrime commented 7 years ago

Hello,

I have been using Kernschmelze (0.7) recently and came across the following problem: In a 2 master setup I had the same kerning groups in both. One of the masters also included an exception for one glyph that was missing in the other. After “schmelzing” the two, I ended up with an exception in the second master too, but instead of having the standard value of the class, it has the value 0.

Is this on purpose? How would I go about to end up with the correct value in the second master?

Thanks in advance!

Mark2Mark commented 7 years ago

Hi,

Thanks for reporting.

Putting the (empty) value 0 in there is on purpose in order to make the masters compatible for interpolation. Both masters need to have any values, but part of the plugin’s job is to fill these up for you. At least that was the idea.

What would you exactly expect to happen on that particular example? What do you mean with »standard value of the class«? Do you mean it shouldn’t come up with 0 but a certain value that the other master has set for that exception?

Did I address all your questions?

arialcrime commented 7 years ago

Hi Mark,

Thanks for the quick reply. The basic idea of the plugin makes sense, and adding zero pairs which do not exist in a master is also very helpful to ensure master compatibility.

In the particular case, /T is kerned to a class of g-like shapes (value = -90) with the exception of /gcircumflex (value = -20) in one master. The other master also included kerning between /T and the group (-50), but does not have the exception.

The problem now is that instead of using the value from “/T to group” (-50) for the exception in the second master, it got zero as the value.

But maybe this goes beyond the concept of Kernschmelze being a helper to Kernkraft.

Mark2Mark commented 7 years ago

This has actually nothing to do with being a helper to Kernkraft. They can be used independently from each other.

Anyway, for your example it cannot use the value from “/T to group” (-50), because the source master doesn’t have a “/T to group” stored for /T/gcircumflex. Hence in the target master, it doesn’t find that combination in the kerning entires and puts the zero value there.

Kerning is stored in Glyphs in four different ways: Group—Group NonGroup—NonGroup NonGroup—Group Group—NonGroup While NonGroups are basically the exceptions. The script can only read and apply these.

Does this make sense? Or did I not understand your example properly?

axani commented 7 years ago

Hi Mark,

we dealt with this at our end and are currently trying an alogrithm, that may be interesting for this issue:

for master_index, (master_id, kerning_data) in enumerate(this_font.kerning.iteritems()):
    for left_side_raw, kerning_values in kerning_data.iteritems():
        left_side = makeNiceName(this_font, left_side_raw) # Returns glyphname or classname
        left_glyph = this_font.glyphs[left_side] if not left_side.startswith('@') else this_font.glyphs[left_side[7:]]
        if left_side not in kerning_test_dict.keys():
            # Rebuilding the kerning dict with readable names (no glyph ids)
            kerning_test_dict[left_side] = {}

        for right_side_raw, val in kerning_values.iteritems():
            right_side = makeNiceName(this_font, right_side_raw)
            # right_glyph is None when it is not a glyph, but a group
            right_glyph = this_font.glyphs[right_side] if not right_side.startswith('@') else this_font.glyphs[right_side[7:]]

            if right_side not in kerning_test_dict[left_side].keys():
                # Rebuild the right side with own data structure
                # Basically all masters will have their data at one place. Unlike the native glyphs app kerning dict 
                # At the beginning I set everything to None or 0.0. This may be overwritten later when there are actual values.
                kerning_test_dict[left_side][right_side] = {
                    'current': [None for m in this_font.masters], 
                    'recommended': [0.0 for m in this_font.masters],
                    'base_glyph': None,
                    'left_glyph': left_glyph.name,
                }
                # Use group kerning value as recommended value when it is present
                if right_glyph and right_glyph.leftKerningKey:
                    group_val = this_font.kerningForPair(master_id, left_side, right_glyph.leftKerningKey)
                    kerning_test_dict[left_side][right_side]['recommended'] = [group_val for m in this_font.masters]

            # Overwrite the None value with the actual value 
            kerning_test_dict[left_side][right_side]['current'][master_index] = val

            # If a value is 0, it is interpreted as suspicious and I look into it
            if val == 0:
                # So check if the glyph has a left kerning group that is another glyph from the font
                # Example: Ä has a 0 value but is part of the A group that has a value
                if right_glyph.leftKerningGroup and this_font.glyphs[right_glyph.leftKerningGroup]:
                    # Get Kerningvalue the baseglyph of the group the (right) glyph was found
                    recommended_value = this_font.kerningForPair(master_id, left_side, this_font.glyphs[right_glyph.leftKerningGroup].leftKerningKey)
                    kerning_test_dict[left_side][right_side]['base_glyph'] = right_glyph.leftKerningGroup
                    if recommended_value < 10000: # it is 9.22337203685e+18 if there is no kerning for kerninggroup ...
                        # ... so only add a recommended value if there is a (realistic) value
                        kerning_test_dict[left_side][right_side]['recommended'][master_index] = recommended_value
                elif right_glyph.name == right_glyph.leftKerningKey:
                    # Kerning should actually be 0 because glyph is in no group and has itself as kerning key
                    kerning_test_dict[left_side][right_side]['recommended'][master_index] = val

            else:
                # Overwrite recommended value with actual value
                kerning_test_dict[left_side][right_side]['recommended'][master_index] = val

After this I go on with this kerning_test_dict. But I guess you get the picture.

We are currently testing if this catches all problems. So let me know, if you see some issues with this snippet (or if you want to take a look at the whole script).

carrois commented 7 years ago

Hi.

Mark is on holiday until 4. March. Please be patient :-)

Thanks a lot!

on the way please excuse typos and briefness ralph

On 22 Feb 2017, at 16:37, Filip Axani notifications@github.com wrote:

Hi Mark,

we dealt with this at our end and are currently trying an alogrithm, that may be interesting for this issue:

for master_index, (master_id, kerning_data) in enumerate(this_font.kerning.iteritems()): for left_side_raw, kerning_values in kerning_data.iteritems(): left_side = makeNiceName(this_font, left_side_raw) # Returns glyphname or classname left_glyph = this_font.glyphs[left_side] if not left_side.startswith('@') else this_font.glyphs[left_side[7:]] if left_side not in kerning_test_dict.keys():

Rebuilding the kerning dict with readable names (no glyph ids)

        kerning_test_dict[left_side] = {}

    for right_side_raw, val in kerning_values.iteritems():
        right_side = makeNiceName(this_font, right_side_raw)
        # right_glyph is None when it is not a glyph, but a group
        right_glyph = this_font.glyphs[right_side] if not right_side.startswith('@') else this_font.glyphs[right_side[7:]]

        if right_side not in kerning_test_dict[left_side].keys():
            # Rebuild the right side with own data structure
            # Basically all masters will have their data at one place. Unlike the native glyphs app kerning dict 
            # At the beginning I set everything to None or 0.0. This may be overwritten later when there are actual values.
            kerning_test_dict[left_side][right_side] = {
                'current': [None for m in this_font.masters], 
                'recommended': [0.0 for m in this_font.masters],
                'base_glyph': None,
                'left_glyph': left_glyph.name,
            }
            # Use group kerning value as recommended value when it is present
            if right_glyph and right_glyph.leftKerningKey:
                group_val = this_font.kerningForPair(master_id, left_side, right_glyph.leftKerningKey)
                kerning_test_dict[left_side][right_side]['recommended'] = [group_val for m in this_font.masters]

        # Overwrite the None value with the actual value 
        kerning_test_dict[left_side][right_side]['current'][master_index] = val

        # If a value is 0, it is interpreted as suspicious and I look into it
        if val == 0:
            # So check if the glyph has a left kerning group that is another glyph from the font
            # Example: Ä has a 0 value but is part of the A group that has a value
            if right_glyph.leftKerningGroup and this_font.glyphs[right_glyph.leftKerningGroup]:
                # Get Kerningvalue the baseglyph of the group the (right) glyph was found
                recommended_value = this_font.kerningForPair(master_id, left_side, this_font.glyphs[right_glyph.leftKerningGroup].leftKerningKey)
                kerning_test_dict[left_side][right_side]['base_glyph'] = right_glyph.leftKerningGroup
                if recommended_value < 10000: # it is 9.22337203685e+18 if there is no kerning for kerninggroup ...
                    # ... so only add a recommended value if there is a (realistic) value
                    kerning_test_dict[left_side][right_side]['recommended'][master_index] = recommended_value
            elif right_glyph.name == right_glyph.leftKerningKey:
                # Kerning should actually be 0 because glyph is in no group and has itself as kerning key
                kerning_test_dict[left_side][right_side]['recommended'][master_index] = val

        else:
            # Overwrite recommended value with actual value
            kerning_test_dict[left_side][right_side]['recommended'][master_index] = val

After this I go on with this kerning_test_dict. But I guess you get the picture.

We are currently testing if this catches all problems. So let me know, if you see some issues with this snippet (or if you want to take a look at the whole script).

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub, or mute the thread.

Mark2Mark commented 7 years ago

Hi @axani Filip,

Thanks for the proposal. I cannot wrap my head around the issue, since I still don’t really get the problem. If I don’t know the problem, I have difficulties to seek for / accept a solution. Can you please clarify what your attempt is? If I understand the mechanic, I am happy to merge your code.