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