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

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( 

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 :] 

46 

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]) 

69 

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]) 

81 

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 = [] 

101 

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 

121 

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() 

135 

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 ) 

144 

145 def __delitem__(self, glyphName): 

146 del self.metrics[glyphName] 

147 

148 def __getitem__(self, glyphName): 

149 return self.metrics[glyphName] 

150 

151 def __setitem__(self, glyphName, advance_sb_pair): 

152 self.metrics[glyphName] = tuple(advance_sb_pair)