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