• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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