1from __future__ import print_function, division, absolute_import 2from fontTools.misc.py23 import * 3from fontTools.misc.fixedTools import otRound 4from fontTools import ttLib 5from fontTools.misc.textTools import safeEval 6from . import DefaultTable 7import sys 8import struct 9import array 10import logging 11 12 13log = logging.getLogger(__name__) 14 15 16class table__h_m_t_x(DefaultTable.DefaultTable): 17 18 headerTag = 'hhea' 19 advanceName = 'width' 20 sideBearingName = 'lsb' 21 numberOfMetricsName = 'numberOfHMetrics' 22 longMetricFormat = 'Hh' 23 24 def decompile(self, data, ttFont): 25 numGlyphs = ttFont['maxp'].numGlyphs 26 headerTable = ttFont.get(self.headerTag) 27 if headerTable is not None: 28 numberOfMetrics = int(getattr(headerTable, self.numberOfMetricsName)) 29 else: 30 numberOfMetrics = numGlyphs 31 if numberOfMetrics > numGlyphs: 32 log.warning("The %s.%s exceeds the maxp.numGlyphs" % ( 33 self.headerTag, self.numberOfMetricsName)) 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:] 46 47 if sys.byteorder != "big": 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?", glyphName, self.advanceName, 59 advanceWidth) 60 self.metrics[glyphName] = (advanceWidth, lsb) 61 lastAdvance = metrics[-2] 62 for i in range(numberOfSideBearings): 63 glyphName = glyphOrder[i + numberOfMetrics] 64 self.metrics[glyphName] = (lastAdvance, sideBearings[i]) 65 66 def compile(self, ttFont): 67 metrics = [] 68 hasNegativeAdvances = False 69 for glyphName in ttFont.getGlyphOrder(): 70 advanceWidth, sideBearing = self.metrics[glyphName] 71 if advanceWidth < 0: 72 log.error("Glyph %r has negative advance %s" % ( 73 glyphName, self.advanceName)) 74 hasNegativeAdvances = True 75 metrics.append([advanceWidth, sideBearing]) 76 77 headerTable = ttFont.get(self.headerTag) 78 if headerTable is not None: 79 lastAdvance = metrics[-1][0] 80 lastIndex = len(metrics) 81 while metrics[lastIndex-2][0] == lastAdvance: 82 lastIndex -= 1 83 if lastIndex <= 1: 84 # all advances are equal 85 lastIndex = 1 86 break 87 additionalMetrics = metrics[lastIndex:] 88 additionalMetrics = [otRound(sb) for _, sb in additionalMetrics] 89 metrics = metrics[:lastIndex] 90 numberOfMetrics = len(metrics) 91 setattr(headerTable, self.numberOfMetricsName, numberOfMetrics) 92 else: 93 # no hhea/vhea, can't store numberOfMetrics; assume == numGlyphs 94 numberOfMetrics = ttFont["maxp"].numGlyphs 95 additionalMetrics = [] 96 97 allMetrics = [] 98 for advance, sb in metrics: 99 allMetrics.extend([otRound(advance), otRound(sb)]) 100 metricsFmt = ">" + self.longMetricFormat * numberOfMetrics 101 try: 102 data = struct.pack(metricsFmt, *allMetrics) 103 except struct.error as e: 104 if "out of range" in str(e) and hasNegativeAdvances: 105 raise ttLib.TTLibError( 106 "'%s' table can't contain negative advance %ss" 107 % (self.tableTag, self.advanceName)) 108 else: 109 raise 110 additionalMetrics = array.array("h", additionalMetrics) 111 if sys.byteorder != "big": additionalMetrics.byteswap() 112 data = data + additionalMetrics.tostring() 113 return data 114 115 def toXML(self, writer, ttFont): 116 names = sorted(self.metrics.keys()) 117 for glyphName in names: 118 advance, sb = self.metrics[glyphName] 119 writer.simpletag("mtx", [ 120 ("name", glyphName), 121 (self.advanceName, advance), 122 (self.sideBearingName, sb), 123 ]) 124 writer.newline() 125 126 def fromXML(self, name, attrs, content, ttFont): 127 if not hasattr(self, "metrics"): 128 self.metrics = {} 129 if name == "mtx": 130 self.metrics[attrs["name"]] = (safeEval(attrs[self.advanceName]), 131 safeEval(attrs[self.sideBearingName])) 132 133 def __delitem__(self, glyphName): 134 del self.metrics[glyphName] 135 136 def __getitem__(self, glyphName): 137 return self.metrics[glyphName] 138 139 def __setitem__(self, glyphName, advance_sb_pair): 140 self.metrics[glyphName] = tuple(advance_sb_pair) 141