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