_h_m_t_x.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. from fontTools.misc.roundTools import otRound
  2. from fontTools import ttLib
  3. from fontTools.misc.textTools import safeEval
  4. from . import DefaultTable
  5. import sys
  6. import struct
  7. import array
  8. import logging
  9. log = logging.getLogger(__name__)
  10. class table__h_m_t_x(DefaultTable.DefaultTable):
  11. headerTag = "hhea"
  12. advanceName = "width"
  13. sideBearingName = "lsb"
  14. numberOfMetricsName = "numberOfHMetrics"
  15. longMetricFormat = "Hh"
  16. def decompile(self, data, ttFont):
  17. numGlyphs = ttFont["maxp"].numGlyphs
  18. headerTable = ttFont.get(self.headerTag)
  19. if headerTable is not None:
  20. numberOfMetrics = int(getattr(headerTable, self.numberOfMetricsName))
  21. else:
  22. numberOfMetrics = numGlyphs
  23. if numberOfMetrics > numGlyphs:
  24. log.warning(
  25. "The %s.%s exceeds the maxp.numGlyphs"
  26. % (self.headerTag, self.numberOfMetricsName)
  27. )
  28. numberOfMetrics = numGlyphs
  29. if len(data) < 4 * numberOfMetrics:
  30. raise ttLib.TTLibError("not enough '%s' table data" % self.tableTag)
  31. # Note: advanceWidth is unsigned, but some font editors might
  32. # read/write as signed. We can't be sure whether it was a mistake
  33. # or not, so we read as unsigned but also issue a warning...
  34. metricsFmt = ">" + self.longMetricFormat * numberOfMetrics
  35. metrics = struct.unpack(metricsFmt, data[: 4 * numberOfMetrics])
  36. data = data[4 * numberOfMetrics :]
  37. numberOfSideBearings = numGlyphs - numberOfMetrics
  38. sideBearings = array.array("h", data[: 2 * numberOfSideBearings])
  39. data = data[2 * numberOfSideBearings :]
  40. if sys.byteorder != "big":
  41. sideBearings.byteswap()
  42. if data:
  43. log.warning("too much '%s' table data" % self.tableTag)
  44. self.metrics = {}
  45. glyphOrder = ttFont.getGlyphOrder()
  46. for i in range(numberOfMetrics):
  47. glyphName = glyphOrder[i]
  48. advanceWidth, lsb = metrics[i * 2 : i * 2 + 2]
  49. if advanceWidth > 32767:
  50. log.warning(
  51. "Glyph %r has a huge advance %s (%d); is it intentional or "
  52. "an (invalid) negative value?",
  53. glyphName,
  54. self.advanceName,
  55. advanceWidth,
  56. )
  57. self.metrics[glyphName] = (advanceWidth, lsb)
  58. lastAdvance = metrics[-2]
  59. for i in range(numberOfSideBearings):
  60. glyphName = glyphOrder[i + numberOfMetrics]
  61. self.metrics[glyphName] = (lastAdvance, sideBearings[i])
  62. def compile(self, ttFont):
  63. metrics = []
  64. hasNegativeAdvances = False
  65. for glyphName in ttFont.getGlyphOrder():
  66. advanceWidth, sideBearing = self.metrics[glyphName]
  67. if advanceWidth < 0:
  68. log.error(
  69. "Glyph %r has negative advance %s" % (glyphName, self.advanceName)
  70. )
  71. hasNegativeAdvances = True
  72. metrics.append([advanceWidth, sideBearing])
  73. headerTable = ttFont.get(self.headerTag)
  74. if headerTable is not None:
  75. lastAdvance = metrics[-1][0]
  76. lastIndex = len(metrics)
  77. while metrics[lastIndex - 2][0] == lastAdvance:
  78. lastIndex -= 1
  79. if lastIndex <= 1:
  80. # all advances are equal
  81. lastIndex = 1
  82. break
  83. additionalMetrics = metrics[lastIndex:]
  84. additionalMetrics = [otRound(sb) for _, sb in additionalMetrics]
  85. metrics = metrics[:lastIndex]
  86. numberOfMetrics = len(metrics)
  87. setattr(headerTable, self.numberOfMetricsName, numberOfMetrics)
  88. else:
  89. # no hhea/vhea, can't store numberOfMetrics; assume == numGlyphs
  90. numberOfMetrics = ttFont["maxp"].numGlyphs
  91. additionalMetrics = []
  92. allMetrics = []
  93. for advance, sb in metrics:
  94. allMetrics.extend([otRound(advance), otRound(sb)])
  95. metricsFmt = ">" + self.longMetricFormat * numberOfMetrics
  96. try:
  97. data = struct.pack(metricsFmt, *allMetrics)
  98. except struct.error as e:
  99. if "out of range" in str(e) and hasNegativeAdvances:
  100. raise ttLib.TTLibError(
  101. "'%s' table can't contain negative advance %ss"
  102. % (self.tableTag, self.advanceName)
  103. )
  104. else:
  105. raise
  106. additionalMetrics = array.array("h", additionalMetrics)
  107. if sys.byteorder != "big":
  108. additionalMetrics.byteswap()
  109. data = data + additionalMetrics.tobytes()
  110. return data
  111. def toXML(self, writer, ttFont):
  112. names = sorted(self.metrics.keys())
  113. for glyphName in names:
  114. advance, sb = self.metrics[glyphName]
  115. writer.simpletag(
  116. "mtx",
  117. [
  118. ("name", glyphName),
  119. (self.advanceName, advance),
  120. (self.sideBearingName, sb),
  121. ],
  122. )
  123. writer.newline()
  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"]] = (
  129. safeEval(attrs[self.advanceName]),
  130. safeEval(attrs[self.sideBearingName]),
  131. )
  132. def __delitem__(self, glyphName):
  133. del self.metrics[glyphName]
  134. def __getitem__(self, glyphName):
  135. return self.metrics[glyphName]
  136. def __setitem__(self, glyphName, advance_sb_pair):
  137. self.metrics[glyphName] = tuple(advance_sb_pair)