summaryrefslogtreecommitdiff
path: root/scripts/lib/fontbuild/kerning.py
blob: c93e3036d47118f443460d5749a854b725366f03 (plain)
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
# Copyright 2015 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


from feaTools import parser
from feaTools.writers.baseWriter import AbstractFeatureWriter


class KernFeatureWriter(AbstractFeatureWriter):
    """Generates a kerning feature based on glyph class definitions.

    Uses the kerning rules contained in an RFont's kerning attribute, as well as
    glyph classes from parsed OTF text. Class-based rules are set based on the
    existing rules for their key glyphs.
    """

    def __init__(self, font):
        self.kerning = font.kerning
        self.leftClasses = []
        self.rightClasses = []
        self.classSizes = {}

    def write(self, linesep="\n"):
        """Write kern feature."""

        # maintain collections of different rule types
        leftClassKerning, rightClassKerning, classPairKerning = {}, {}, {}
        for leftName, leftContents in self.leftClasses:
            leftKey = leftContents[0]

            # collect rules with two classes
            for rightName, rightContents in self.rightClasses:
                rightKey = rightContents[0]
                pair = leftKey, rightKey
                kerningVal = self.kerning[pair]
                if kerningVal is None:
                    continue
                classPairKerning[leftName, rightName] = kerningVal
                self.kerning.remove(pair)

            # collect rules with left class and right glyph
            for pair, kerningVal in self.kerning.getLeft(leftKey):
                leftClassKerning[leftName, pair[1]] = kerningVal
                self.kerning.remove(pair)

        # collect rules with left glyph and right class
        for rightName, rightContents in self.rightClasses:
            rightKey = rightContents[0]
            for pair, kerningVal in self.kerning.getRight(rightKey):
                rightClassKerning[pair[0], rightName] = kerningVal
                self.kerning.remove(pair)

        # write the feature
        self.ruleCount = 0
        lines = ["feature kern {"]
        lines.append(self._writeKerning(self.kerning, linesep))
        lines.append(self._writeKerning(leftClassKerning, linesep, True))
        lines.append(self._writeKerning(rightClassKerning, linesep, True))
        lines.append(self._writeKerning(classPairKerning, linesep))
        lines.append("} kern;")
        return linesep.join(lines)

    def _writeKerning(self, kerning, linesep, enum=False):
        """Write kerning rules for a mapping of pairs to values."""

        lines = []
        enum = "enum " if enum else ""
        pairs = kerning.items()
        pairs.sort()
        for (left, right), val in pairs:
            if enum:
                rulesAdded = (self.classSizes.get(left, 1) *
                              self.classSizes.get(right, 1))
            else:
                rulesAdded = 1
            self.ruleCount += rulesAdded
            if self.ruleCount > 1024:
                lines.append("    subtable;")
                self.ruleCount = rulesAdded
            lines.append("    %spos %s %s %d;" % (enum, left, right, val))
        return linesep.join(lines)

    def classDefinition(self, name, contents):
        """Store a class definition as either a left- or right-hand class."""

        if not name.startswith("@_"):
            return
        info = (name, contents)
        if name.endswith("_L"):
            self.leftClasses.append(info)
        elif name.endswith("_R"):
            self.rightClasses.append(info)
        self.classSizes[name] = len(contents)


def makeKernFeature(font, text):
    """Add a kern feature to the font, using a KernFeatureWriter."""

    writer = KernFeatureWriter(font)
    parser.parseFeatures(writer, text)
    font.features.text += writer.write()