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 If you want to compare a glyph from a source format which supports floating point 36 coordinates and transformations against a glyph from a format which has restrictions 37 on the precision of floats, e.g. UFO vs. TTF, you must use an appropriate rounding 38 function to make the values comparable. For TTF fonts with composites, this 39 construct can be used to make the transform values conform to F2Dot14: 40 41 > ttf_hash_pen = HashPointPen(ttf_glyph_width, ttFont.getGlyphSet()) 42 > ttf_round_pen = RoundingPointPen(ttf_hash_pen, transformRoundFunc=partial(floatToFixedToFloat, precisionBits=14)) 43 > ufo_hash_pen = HashPointPen(ufo_glyph.width, ufo) 44 > ttf_glyph.drawPoints(ttf_round_pen, ttFont["glyf"]) 45 > ufo_round_pen = RoundingPointPen(ufo_hash_pen, transformRoundFunc=partial(floatToFixedToFloat, precisionBits=14)) 46 > ufo_glyph.drawPoints(ufo_round_pen) 47 > assert ttf_hash_pen.hash == ufo_hash_pen.hash 48 """ 49 50 def __init__(self, glyphWidth=0, glyphSet=None): 51 self.glyphset = glyphSet 52 self.data = ["w%s" % round(glyphWidth, 9)] 53 54 @property 55 def hash(self): 56 data = "".join(self.data) 57 if len(data) >= 128: 58 data = hashlib.sha512(data.encode("ascii")).hexdigest() 59 return data 60 61 def beginPath(self, identifier=None, **kwargs): 62 pass 63 64 def endPath(self): 65 self.data.append("|") 66 67 def addPoint( 68 self, 69 pt, 70 segmentType=None, 71 smooth=False, 72 name=None, 73 identifier=None, 74 **kwargs, 75 ): 76 if segmentType is None: 77 pt_type = "o" # offcurve 78 else: 79 pt_type = segmentType[0] 80 self.data.append(f"{pt_type}{pt[0]:g}{pt[1]:+g}") 81 82 def addComponent(self, baseGlyphName, transformation, identifier=None, **kwargs): 83 tr = "".join([f"{t:+}" for t in transformation]) 84 self.data.append("[") 85 try: 86 self.glyphset[baseGlyphName].drawPoints(self) 87 except KeyError: 88 raise MissingComponentError(baseGlyphName) 89 self.data.append(f"({tr})]") 90