• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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