Home | History | Annotate | Download | only in tables
      1 from __future__ import print_function, division, absolute_import
      2 from fontTools.misc.py23 import *
      3 from fontTools.misc.fixedTools import otRound
      4 from fontTools import ttLib
      5 from fontTools.misc.textTools import safeEval
      6 from . import DefaultTable
      7 import sys
      8 import struct
      9 import array
     10 import logging
     11 
     12 
     13 log = logging.getLogger(__name__)
     14 
     15 
     16 class 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