hashPointPen.py 3.5 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889
  1. # Modified from https://github.com/adobe-type-tools/psautohint/blob/08b346865710ed3c172f1eb581d6ef243b203f99/python/psautohint/ufoFont.py#L800-L838
  2. import hashlib
  3. from fontTools.pens.basePen import MissingComponentError
  4. from fontTools.pens.pointPen import AbstractPointPen
  5. class HashPointPen(AbstractPointPen):
  6. """
  7. This pen can be used to check if a glyph's contents (outlines plus
  8. components) have changed.
  9. Components are added as the original outline plus each composite's
  10. transformation.
  11. Example: You have some TrueType hinting code for a glyph which you want to
  12. compile. The hinting code specifies a hash value computed with HashPointPen
  13. that was valid for the glyph's outlines at the time the hinting code was
  14. written. Now you can calculate the hash for the glyph's current outlines to
  15. check if the outlines have changed, which would probably make the hinting
  16. code invalid.
  17. > glyph = ufo[name]
  18. > hash_pen = HashPointPen(glyph.width, ufo)
  19. > glyph.drawPoints(hash_pen)
  20. > ttdata = glyph.lib.get("public.truetype.instructions", None)
  21. > stored_hash = ttdata.get("id", None) # The hash is stored in the "id" key
  22. > if stored_hash is None or stored_hash != hash_pen.hash:
  23. > logger.error(f"Glyph hash mismatch, glyph '{name}' will have no instructions in font.")
  24. > else:
  25. > # The hash values are identical, the outline has not changed.
  26. > # Compile the hinting code ...
  27. > pass
  28. If you want to compare a glyph from a source format which supports floating point
  29. coordinates and transformations against a glyph from a format which has restrictions
  30. on the precision of floats, e.g. UFO vs. TTF, you must use an appropriate rounding
  31. function to make the values comparable. For TTF fonts with composites, this
  32. construct can be used to make the transform values conform to F2Dot14:
  33. > ttf_hash_pen = HashPointPen(ttf_glyph_width, ttFont.getGlyphSet())
  34. > ttf_round_pen = RoundingPointPen(ttf_hash_pen, transformRoundFunc=partial(floatToFixedToFloat, precisionBits=14))
  35. > ufo_hash_pen = HashPointPen(ufo_glyph.width, ufo)
  36. > ttf_glyph.drawPoints(ttf_round_pen, ttFont["glyf"])
  37. > ufo_round_pen = RoundingPointPen(ufo_hash_pen, transformRoundFunc=partial(floatToFixedToFloat, precisionBits=14))
  38. > ufo_glyph.drawPoints(ufo_round_pen)
  39. > assert ttf_hash_pen.hash == ufo_hash_pen.hash
  40. """
  41. def __init__(self, glyphWidth=0, glyphSet=None):
  42. self.glyphset = glyphSet
  43. self.data = ["w%s" % round(glyphWidth, 9)]
  44. @property
  45. def hash(self):
  46. data = "".join(self.data)
  47. if len(data) >= 128:
  48. data = hashlib.sha512(data.encode("ascii")).hexdigest()
  49. return data
  50. def beginPath(self, identifier=None, **kwargs):
  51. pass
  52. def endPath(self):
  53. self.data.append("|")
  54. def addPoint(
  55. self,
  56. pt,
  57. segmentType=None,
  58. smooth=False,
  59. name=None,
  60. identifier=None,
  61. **kwargs,
  62. ):
  63. if segmentType is None:
  64. pt_type = "o" # offcurve
  65. else:
  66. pt_type = segmentType[0]
  67. self.data.append(f"{pt_type}{pt[0]:g}{pt[1]:+g}")
  68. def addComponent(self, baseGlyphName, transformation, identifier=None, **kwargs):
  69. tr = "".join([f"{t:+}" for t in transformation])
  70. self.data.append("[")
  71. try:
  72. self.glyphset[baseGlyphName].drawPoints(self)
  73. except KeyError:
  74. raise MissingComponentError(baseGlyphName)
  75. self.data.append(f"({tr})]")