File size: 4,836 Bytes
d9f69e5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
from __future__ import annotations

from typing import Optional

from fontTools.annotations import KerningPair, KerningDict, KerningGroups, IntFloat

StrDict = dict[str, str]


def lookupKerningValue(
    pair: KerningPair,
    kerning: KerningDict,
    groups: KerningGroups,
    fallback: IntFloat = 0,
    glyphToFirstGroup: Optional[StrDict] = None,
    glyphToSecondGroup: Optional[StrDict] = None,
) -> IntFloat:
    """Retrieve the kerning value (if any) between a pair of elements.

    The elments can be either individual glyphs (by name) or kerning
    groups (by name), or any combination of the two.

    Args:
      pair:
          A tuple, in logical order (first, second) with respect
          to the reading direction, to query the font for kerning
          information on. Each element in the tuple can be either
          a glyph name or a kerning group name.
      kerning:
          A dictionary of kerning pairs.
      groups:
          A set of kerning groups.
      fallback:
          The fallback value to return if no kern is found between
          the elements in ``pair``. Defaults to 0.
      glyphToFirstGroup:
          A dictionary mapping glyph names to the first-glyph kerning
          groups to which they belong. Defaults to ``None``.
      glyphToSecondGroup:
          A dictionary mapping glyph names to the second-glyph kerning
          groups to which they belong. Defaults to ``None``.

    Returns:
      The kerning value between the element pair. If no kerning for
      the pair is found, the fallback value is returned.

    Note: This function expects the ``kerning`` argument to be a flat
    dictionary of kerning pairs, not the nested structure used in a
    kerning.plist file.

    Examples::

      >>> groups = {
      ...     "public.kern1.O" : ["O", "D", "Q"],
      ...     "public.kern2.E" : ["E", "F"]
      ... }
      >>> kerning = {
      ...     ("public.kern1.O", "public.kern2.E") : -100,
      ...     ("public.kern1.O", "F") : -200,
      ...     ("D", "F") : -300
      ... }
      >>> lookupKerningValue(("D", "F"), kerning, groups)
      -300
      >>> lookupKerningValue(("O", "F"), kerning, groups)
      -200
      >>> lookupKerningValue(("O", "E"), kerning, groups)
      -100
      >>> lookupKerningValue(("O", "O"), kerning, groups)
      0
      >>> lookupKerningValue(("E", "E"), kerning, groups)
      0
      >>> lookupKerningValue(("E", "O"), kerning, groups)
      0
      >>> lookupKerningValue(("X", "X"), kerning, groups)
      0
      >>> lookupKerningValue(("public.kern1.O", "public.kern2.E"),
      ...     kerning, groups)
      -100
      >>> lookupKerningValue(("public.kern1.O", "F"), kerning, groups)
      -200
      >>> lookupKerningValue(("O", "public.kern2.E"), kerning, groups)
      -100
      >>> lookupKerningValue(("public.kern1.X", "public.kern2.X"), kerning, groups)
      0
    """
    # quickly check to see if the pair is in the kerning dictionary
    if pair in kerning:
        return kerning[pair]
    # ensure both or no glyph-to-group mappings are provided
    if (glyphToFirstGroup is None) != (glyphToSecondGroup is None):
        raise ValueError(
            "Must provide both 'glyphToFirstGroup' and 'glyphToSecondGroup', or neither."
        )
    # create glyph to group mapping
    if glyphToFirstGroup is None:
        glyphToFirstGroup = {}
        glyphToSecondGroup = {}
        for group, groupMembers in groups.items():
            if group.startswith("public.kern1."):
                for glyph in groupMembers:
                    glyphToFirstGroup[glyph] = group
            elif group.startswith("public.kern2."):
                for glyph in groupMembers:
                    glyphToSecondGroup[glyph] = group
    # ensure type safety for mappings
    assert glyphToFirstGroup is not None
    assert glyphToSecondGroup is not None
    # get group names and make sure first and second are glyph names
    first, second = pair
    firstGroup = secondGroup = None
    if first.startswith("public.kern1."):
        firstGroup = first
        firstGlyph = None
    else:
        firstGroup = glyphToFirstGroup.get(first)
        firstGlyph = first
    if second.startswith("public.kern2."):
        secondGroup = second
        secondGlyph = None
    else:
        secondGroup = glyphToSecondGroup.get(second)
        secondGlyph = second
    # make an ordered list of pairs to look up
    pairs = [
        (a, b)
        for a in (firstGlyph, firstGroup)
        for b in (secondGlyph, secondGroup)
        if a is not None and b is not None
    ]
    # look up the pairs and return any matches
    for pair in pairs:
        if pair in kerning:
            return kerning[pair]
    # use the fallback value
    return fallback


if __name__ == "__main__":
    import doctest

    doctest.testmod()