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