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