Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/fontTools/ttLib/tables/_h_m_t_x.py: 21%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

105 statements  

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)