_v_h_e_a.py 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. from fontTools.misc import sstruct
  2. from fontTools.misc.textTools import safeEval
  3. from fontTools.misc.fixedTools import (
  4. ensureVersionIsLong as fi2ve,
  5. versionToFixed as ve2fi,
  6. )
  7. from . import DefaultTable
  8. import math
  9. vheaFormat = """
  10. > # big endian
  11. tableVersion: L
  12. ascent: h
  13. descent: h
  14. lineGap: h
  15. advanceHeightMax: H
  16. minTopSideBearing: h
  17. minBottomSideBearing: h
  18. yMaxExtent: h
  19. caretSlopeRise: h
  20. caretSlopeRun: h
  21. caretOffset: h
  22. reserved1: h
  23. reserved2: h
  24. reserved3: h
  25. reserved4: h
  26. metricDataFormat: h
  27. numberOfVMetrics: H
  28. """
  29. class table__v_h_e_a(DefaultTable.DefaultTable):
  30. # Note: Keep in sync with table__h_h_e_a
  31. dependencies = ["vmtx", "glyf", "CFF ", "CFF2"]
  32. def decompile(self, data, ttFont):
  33. sstruct.unpack(vheaFormat, data, self)
  34. def compile(self, ttFont):
  35. if ttFont.recalcBBoxes and (
  36. ttFont.isLoaded("glyf")
  37. or ttFont.isLoaded("CFF ")
  38. or ttFont.isLoaded("CFF2")
  39. ):
  40. self.recalc(ttFont)
  41. self.tableVersion = fi2ve(self.tableVersion)
  42. return sstruct.pack(vheaFormat, self)
  43. def recalc(self, ttFont):
  44. if "vmtx" not in ttFont:
  45. return
  46. vmtxTable = ttFont["vmtx"]
  47. self.advanceHeightMax = max(adv for adv, _ in vmtxTable.metrics.values())
  48. boundsHeightDict = {}
  49. if "glyf" in ttFont:
  50. glyfTable = ttFont["glyf"]
  51. for name in ttFont.getGlyphOrder():
  52. g = glyfTable[name]
  53. if g.numberOfContours == 0:
  54. continue
  55. if g.numberOfContours < 0 and not hasattr(g, "yMax"):
  56. # Composite glyph without extents set.
  57. # Calculate those.
  58. g.recalcBounds(glyfTable)
  59. boundsHeightDict[name] = g.yMax - g.yMin
  60. elif "CFF " in ttFont or "CFF2" in ttFont:
  61. if "CFF " in ttFont:
  62. topDict = ttFont["CFF "].cff.topDictIndex[0]
  63. else:
  64. topDict = ttFont["CFF2"].cff.topDictIndex[0]
  65. charStrings = topDict.CharStrings
  66. for name in ttFont.getGlyphOrder():
  67. cs = charStrings[name]
  68. bounds = cs.calcBounds(charStrings)
  69. if bounds is not None:
  70. boundsHeightDict[name] = int(
  71. math.ceil(bounds[3]) - math.floor(bounds[1])
  72. )
  73. if boundsHeightDict:
  74. minTopSideBearing = float("inf")
  75. minBottomSideBearing = float("inf")
  76. yMaxExtent = -float("inf")
  77. for name, boundsHeight in boundsHeightDict.items():
  78. advanceHeight, tsb = vmtxTable[name]
  79. bsb = advanceHeight - tsb - boundsHeight
  80. extent = tsb + boundsHeight
  81. minTopSideBearing = min(minTopSideBearing, tsb)
  82. minBottomSideBearing = min(minBottomSideBearing, bsb)
  83. yMaxExtent = max(yMaxExtent, extent)
  84. self.minTopSideBearing = minTopSideBearing
  85. self.minBottomSideBearing = minBottomSideBearing
  86. self.yMaxExtent = yMaxExtent
  87. else: # No glyph has outlines.
  88. self.minTopSideBearing = 0
  89. self.minBottomSideBearing = 0
  90. self.yMaxExtent = 0
  91. def toXML(self, writer, ttFont):
  92. formatstring, names, fixes = sstruct.getformat(vheaFormat)
  93. for name in names:
  94. value = getattr(self, name)
  95. if name == "tableVersion":
  96. value = fi2ve(value)
  97. value = "0x%08x" % value
  98. writer.simpletag(name, value=value)
  99. writer.newline()
  100. def fromXML(self, name, attrs, content, ttFont):
  101. if name == "tableVersion":
  102. setattr(self, name, ve2fi(attrs["value"]))
  103. return
  104. setattr(self, name, safeEval(attrs["value"]))
  105. # reserved0 is caretOffset for legacy reasons
  106. @property
  107. def reserved0(self):
  108. return self.caretOffset
  109. @reserved0.setter
  110. def reserved0(self, value):
  111. self.caretOffset = value