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