• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1from fontTools.misc.roundTools import otRound
2from fontTools import ttLib
3from fontTools.misc.textTools import safeEval
4from . import DefaultTable
5import sys
6import struct
7import array
8import logging
9
10
11log = logging.getLogger(__name__)
12
13
14class table__h_m_t_x(DefaultTable.DefaultTable):
15    headerTag = "hhea"
16    advanceName = "width"
17    sideBearingName = "lsb"
18    numberOfMetricsName = "numberOfHMetrics"
19    longMetricFormat = "Hh"
20
21    def decompile(self, data, ttFont):
22        numGlyphs = ttFont["maxp"].numGlyphs
23        headerTable = ttFont.get(self.headerTag)
24        if headerTable is not None:
25            numberOfMetrics = int(getattr(headerTable, self.numberOfMetricsName))
26        else:
27            numberOfMetrics = numGlyphs
28        if numberOfMetrics > numGlyphs:
29            log.warning(
30                "The %s.%s exceeds the maxp.numGlyphs"
31                % (self.headerTag, self.numberOfMetricsName)
32            )
33            numberOfMetrics = numGlyphs
34        if len(data) < 4 * numberOfMetrics:
35            raise ttLib.TTLibError("not enough '%s' table data" % self.tableTag)
36        # Note: advanceWidth is unsigned, but some font editors might
37        # read/write as signed. We can't be sure whether it was a mistake
38        # or not, so we read as unsigned but also issue a warning...
39        metricsFmt = ">" + self.longMetricFormat * numberOfMetrics
40        metrics = struct.unpack(metricsFmt, data[: 4 * numberOfMetrics])
41        data = data[4 * numberOfMetrics :]
42        numberOfSideBearings = numGlyphs - numberOfMetrics
43        sideBearings = array.array("h", data[: 2 * numberOfSideBearings])
44        data = data[2 * numberOfSideBearings :]
45
46        if sys.byteorder != "big":
47            sideBearings.byteswap()
48        if data:
49            log.warning("too much '%s' table data" % self.tableTag)
50        self.metrics = {}
51        glyphOrder = ttFont.getGlyphOrder()
52        for i in range(numberOfMetrics):
53            glyphName = glyphOrder[i]
54            advanceWidth, lsb = metrics[i * 2 : i * 2 + 2]
55            if advanceWidth > 32767:
56                log.warning(
57                    "Glyph %r has a huge advance %s (%d); is it intentional or "
58                    "an (invalid) negative value?",
59                    glyphName,
60                    self.advanceName,
61                    advanceWidth,
62                )
63            self.metrics[glyphName] = (advanceWidth, lsb)
64        lastAdvance = metrics[-2]
65        for i in range(numberOfSideBearings):
66            glyphName = glyphOrder[i + numberOfMetrics]
67            self.metrics[glyphName] = (lastAdvance, sideBearings[i])
68
69    def compile(self, ttFont):
70        metrics = []
71        hasNegativeAdvances = False
72        for glyphName in ttFont.getGlyphOrder():
73            advanceWidth, sideBearing = self.metrics[glyphName]
74            if advanceWidth < 0:
75                log.error(
76                    "Glyph %r has negative advance %s" % (glyphName, self.advanceName)
77                )
78                hasNegativeAdvances = True
79            metrics.append([advanceWidth, sideBearing])
80
81        headerTable = ttFont.get(self.headerTag)
82        if headerTable is not None:
83            lastAdvance = metrics[-1][0]
84            lastIndex = len(metrics)
85            while metrics[lastIndex - 2][0] == lastAdvance:
86                lastIndex -= 1
87                if lastIndex <= 1:
88                    # all advances are equal
89                    lastIndex = 1
90                    break
91            additionalMetrics = metrics[lastIndex:]
92            additionalMetrics = [otRound(sb) for _, sb in additionalMetrics]
93            metrics = metrics[:lastIndex]
94            numberOfMetrics = len(metrics)
95            setattr(headerTable, self.numberOfMetricsName, numberOfMetrics)
96        else:
97            # no hhea/vhea, can't store numberOfMetrics; assume == numGlyphs
98            numberOfMetrics = ttFont["maxp"].numGlyphs
99            additionalMetrics = []
100
101        allMetrics = []
102        for advance, sb in metrics:
103            allMetrics.extend([otRound(advance), otRound(sb)])
104        metricsFmt = ">" + self.longMetricFormat * numberOfMetrics
105        try:
106            data = struct.pack(metricsFmt, *allMetrics)
107        except struct.error as e:
108            if "out of range" in str(e) and hasNegativeAdvances:
109                raise ttLib.TTLibError(
110                    "'%s' table can't contain negative advance %ss"
111                    % (self.tableTag, self.advanceName)
112                )
113            else:
114                raise
115        additionalMetrics = array.array("h", additionalMetrics)
116        if sys.byteorder != "big":
117            additionalMetrics.byteswap()
118        data = data + additionalMetrics.tobytes()
119        return data
120
121    def toXML(self, writer, ttFont):
122        names = sorted(self.metrics.keys())
123        for glyphName in names:
124            advance, sb = self.metrics[glyphName]
125            writer.simpletag(
126                "mtx",
127                [
128                    ("name", glyphName),
129                    (self.advanceName, advance),
130                    (self.sideBearingName, sb),
131                ],
132            )
133            writer.newline()
134
135    def fromXML(self, name, attrs, content, ttFont):
136        if not hasattr(self, "metrics"):
137            self.metrics = {}
138        if name == "mtx":
139            self.metrics[attrs["name"]] = (
140                safeEval(attrs[self.advanceName]),
141                safeEval(attrs[self.sideBearingName]),
142            )
143
144    def __delitem__(self, glyphName):
145        del self.metrics[glyphName]
146
147    def __getitem__(self, glyphName):
148        return self.metrics[glyphName]
149
150    def __setitem__(self, glyphName, advance_sb_pair):
151        self.metrics[glyphName] = tuple(advance_sb_pair)
152