123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151 |
- from fontTools.misc.roundTools import otRound
- from fontTools import ttLib
- from fontTools.misc.textTools import safeEval
- from . import DefaultTable
- import sys
- import struct
- import array
- import logging
- log = logging.getLogger(__name__)
- class table__h_m_t_x(DefaultTable.DefaultTable):
- headerTag = "hhea"
- advanceName = "width"
- sideBearingName = "lsb"
- numberOfMetricsName = "numberOfHMetrics"
- longMetricFormat = "Hh"
- def decompile(self, data, ttFont):
- numGlyphs = ttFont["maxp"].numGlyphs
- headerTable = ttFont.get(self.headerTag)
- if headerTable is not None:
- numberOfMetrics = int(getattr(headerTable, self.numberOfMetricsName))
- else:
- numberOfMetrics = numGlyphs
- if numberOfMetrics > numGlyphs:
- log.warning(
- "The %s.%s exceeds the maxp.numGlyphs"
- % (self.headerTag, self.numberOfMetricsName)
- )
- numberOfMetrics = numGlyphs
- if len(data) < 4 * numberOfMetrics:
- raise ttLib.TTLibError("not enough '%s' table data" % self.tableTag)
- # Note: advanceWidth is unsigned, but some font editors might
- # read/write as signed. We can't be sure whether it was a mistake
- # or not, so we read as unsigned but also issue a warning...
- metricsFmt = ">" + self.longMetricFormat * numberOfMetrics
- metrics = struct.unpack(metricsFmt, data[: 4 * numberOfMetrics])
- data = data[4 * numberOfMetrics :]
- numberOfSideBearings = numGlyphs - numberOfMetrics
- sideBearings = array.array("h", data[: 2 * numberOfSideBearings])
- data = data[2 * numberOfSideBearings :]
- if sys.byteorder != "big":
- sideBearings.byteswap()
- if data:
- log.warning("too much '%s' table data" % self.tableTag)
- self.metrics = {}
- glyphOrder = ttFont.getGlyphOrder()
- for i in range(numberOfMetrics):
- glyphName = glyphOrder[i]
- advanceWidth, lsb = metrics[i * 2 : i * 2 + 2]
- if advanceWidth > 32767:
- log.warning(
- "Glyph %r has a huge advance %s (%d); is it intentional or "
- "an (invalid) negative value?",
- glyphName,
- self.advanceName,
- advanceWidth,
- )
- self.metrics[glyphName] = (advanceWidth, lsb)
- lastAdvance = metrics[-2]
- for i in range(numberOfSideBearings):
- glyphName = glyphOrder[i + numberOfMetrics]
- self.metrics[glyphName] = (lastAdvance, sideBearings[i])
- def compile(self, ttFont):
- metrics = []
- hasNegativeAdvances = False
- for glyphName in ttFont.getGlyphOrder():
- advanceWidth, sideBearing = self.metrics[glyphName]
- if advanceWidth < 0:
- log.error(
- "Glyph %r has negative advance %s" % (glyphName, self.advanceName)
- )
- hasNegativeAdvances = True
- metrics.append([advanceWidth, sideBearing])
- headerTable = ttFont.get(self.headerTag)
- if headerTable is not None:
- lastAdvance = metrics[-1][0]
- lastIndex = len(metrics)
- while metrics[lastIndex - 2][0] == lastAdvance:
- lastIndex -= 1
- if lastIndex <= 1:
- # all advances are equal
- lastIndex = 1
- break
- additionalMetrics = metrics[lastIndex:]
- additionalMetrics = [otRound(sb) for _, sb in additionalMetrics]
- metrics = metrics[:lastIndex]
- numberOfMetrics = len(metrics)
- setattr(headerTable, self.numberOfMetricsName, numberOfMetrics)
- else:
- # no hhea/vhea, can't store numberOfMetrics; assume == numGlyphs
- numberOfMetrics = ttFont["maxp"].numGlyphs
- additionalMetrics = []
- allMetrics = []
- for advance, sb in metrics:
- allMetrics.extend([otRound(advance), otRound(sb)])
- metricsFmt = ">" + self.longMetricFormat * numberOfMetrics
- try:
- data = struct.pack(metricsFmt, *allMetrics)
- except struct.error as e:
- if "out of range" in str(e) and hasNegativeAdvances:
- raise ttLib.TTLibError(
- "'%s' table can't contain negative advance %ss"
- % (self.tableTag, self.advanceName)
- )
- else:
- raise
- additionalMetrics = array.array("h", additionalMetrics)
- if sys.byteorder != "big":
- additionalMetrics.byteswap()
- data = data + additionalMetrics.tobytes()
- return data
- def toXML(self, writer, ttFont):
- names = sorted(self.metrics.keys())
- for glyphName in names:
- advance, sb = self.metrics[glyphName]
- writer.simpletag(
- "mtx",
- [
- ("name", glyphName),
- (self.advanceName, advance),
- (self.sideBearingName, sb),
- ],
- )
- writer.newline()
- def fromXML(self, name, attrs, content, ttFont):
- if not hasattr(self, "metrics"):
- self.metrics = {}
- if name == "mtx":
- self.metrics[attrs["name"]] = (
- safeEval(attrs[self.advanceName]),
- safeEval(attrs[self.sideBearingName]),
- )
- def __delitem__(self, glyphName):
- del self.metrics[glyphName]
- def __getitem__(self, glyphName):
- return self.metrics[glyphName]
- def __setitem__(self, glyphName, advance_sb_pair):
- self.metrics[glyphName] = tuple(advance_sb_pair)
|