_h_e_a_d.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. from fontTools.misc import sstruct
  2. from fontTools.misc.fixedTools import floatToFixedToStr, strToFixedToFloat
  3. from fontTools.misc.textTools import safeEval, num2binary, binary2num
  4. from fontTools.misc.timeTools import (
  5. timestampFromString,
  6. timestampToString,
  7. timestampNow,
  8. )
  9. from fontTools.misc.timeTools import epoch_diff as mac_epoch_diff # For backward compat
  10. from fontTools.misc.arrayTools import intRect, unionRect
  11. from . import DefaultTable
  12. import logging
  13. log = logging.getLogger(__name__)
  14. headFormat = """
  15. > # big endian
  16. tableVersion: 16.16F
  17. fontRevision: 16.16F
  18. checkSumAdjustment: I
  19. magicNumber: I
  20. flags: H
  21. unitsPerEm: H
  22. created: Q
  23. modified: Q
  24. xMin: h
  25. yMin: h
  26. xMax: h
  27. yMax: h
  28. macStyle: H
  29. lowestRecPPEM: H
  30. fontDirectionHint: h
  31. indexToLocFormat: h
  32. glyphDataFormat: h
  33. """
  34. class table__h_e_a_d(DefaultTable.DefaultTable):
  35. dependencies = ["maxp", "loca", "CFF ", "CFF2"]
  36. def decompile(self, data, ttFont):
  37. dummy, rest = sstruct.unpack2(headFormat, data, self)
  38. if rest:
  39. # this is quite illegal, but there seem to be fonts out there that do this
  40. log.warning("extra bytes at the end of 'head' table")
  41. assert rest == b"\0\0"
  42. # For timestamp fields, ignore the top four bytes. Some fonts have
  43. # bogus values there. Since till 2038 those bytes only can be zero,
  44. # ignore them.
  45. #
  46. # https://github.com/fonttools/fonttools/issues/99#issuecomment-66776810
  47. for stamp in "created", "modified":
  48. value = getattr(self, stamp)
  49. if value > 0xFFFFFFFF:
  50. log.warning("'%s' timestamp out of range; ignoring top bytes", stamp)
  51. value &= 0xFFFFFFFF
  52. setattr(self, stamp, value)
  53. if value < 0x7C259DC0: # January 1, 1970 00:00:00
  54. log.warning(
  55. "'%s' timestamp seems very low; regarding as unix timestamp", stamp
  56. )
  57. value += 0x7C259DC0
  58. setattr(self, stamp, value)
  59. def compile(self, ttFont):
  60. if ttFont.recalcBBoxes:
  61. # For TT-flavored fonts, xMin, yMin, xMax and yMax are set in table__m_a_x_p.recalc().
  62. if "CFF " in ttFont:
  63. topDict = ttFont["CFF "].cff.topDictIndex[0]
  64. self.xMin, self.yMin, self.xMax, self.yMax = intRect(topDict.FontBBox)
  65. elif "CFF2" in ttFont:
  66. topDict = ttFont["CFF2"].cff.topDictIndex[0]
  67. charStrings = topDict.CharStrings
  68. fontBBox = None
  69. for charString in charStrings.values():
  70. bounds = charString.calcBounds(charStrings)
  71. if bounds is not None:
  72. if fontBBox is not None:
  73. fontBBox = unionRect(fontBBox, bounds)
  74. else:
  75. fontBBox = bounds
  76. if fontBBox is not None:
  77. self.xMin, self.yMin, self.xMax, self.yMax = intRect(fontBBox)
  78. if ttFont.recalcTimestamp:
  79. self.modified = timestampNow()
  80. data = sstruct.pack(headFormat, self)
  81. return data
  82. def toXML(self, writer, ttFont):
  83. writer.comment("Most of this table will be recalculated by the compiler")
  84. writer.newline()
  85. _, names, fixes = sstruct.getformat(headFormat)
  86. for name in names:
  87. value = getattr(self, name)
  88. if name in fixes:
  89. value = floatToFixedToStr(value, precisionBits=fixes[name])
  90. elif name in ("created", "modified"):
  91. value = timestampToString(value)
  92. elif name in ("magicNumber", "checkSumAdjustment"):
  93. if value < 0:
  94. value = value + 0x100000000
  95. value = hex(value)
  96. if value[-1:] == "L":
  97. value = value[:-1]
  98. elif name in ("macStyle", "flags"):
  99. value = num2binary(value, 16)
  100. writer.simpletag(name, value=value)
  101. writer.newline()
  102. def fromXML(self, name, attrs, content, ttFont):
  103. value = attrs["value"]
  104. fixes = sstruct.getformat(headFormat)[2]
  105. if name in fixes:
  106. value = strToFixedToFloat(value, precisionBits=fixes[name])
  107. elif name in ("created", "modified"):
  108. value = timestampFromString(value)
  109. elif name in ("macStyle", "flags"):
  110. value = binary2num(value)
  111. else:
  112. value = safeEval(value)
  113. setattr(self, name, value)