• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1from fontTools.misc import sstruct
2from fontTools.misc.fixedTools import floatToFixedToStr
3from fontTools.misc.textTools import safeEval
4
5# from itertools import *
6from functools import partial
7from . import DefaultTable
8from . import grUtils
9import struct
10
11
12Glat_format_0 = """
13    >        # big endian
14    version: 16.16F
15"""
16
17Glat_format_3 = """
18    >
19    version: 16.16F
20    compression:L    # compression scheme or reserved
21"""
22
23Glat_format_1_entry = """
24    >
25    attNum:     B    # Attribute number of first attribute
26    num:        B    # Number of attributes in this run
27"""
28Glat_format_23_entry = """
29    >
30    attNum:     H    # Attribute number of first attribute
31    num:        H    # Number of attributes in this run
32"""
33
34Glat_format_3_octabox_metrics = """
35    >
36    subboxBitmap:   H    # Which subboxes exist on 4x4 grid
37    diagNegMin:     B    # Defines minimum negatively-sloped diagonal (si)
38    diagNegMax:     B    # Defines maximum negatively-sloped diagonal (sa)
39    diagPosMin:     B    # Defines minimum positively-sloped diagonal (di)
40    diagPosMax:     B    # Defines maximum positively-sloped diagonal (da)
41"""
42
43Glat_format_3_subbox_entry = """
44    >
45    left:           B    # xi
46    right:          B    # xa
47    bottom:         B    # yi
48    top:            B    # ya
49    diagNegMin:     B    # Defines minimum negatively-sloped diagonal (si)
50    diagNegMax:     B    # Defines maximum negatively-sloped diagonal (sa)
51    diagPosMin:     B    # Defines minimum positively-sloped diagonal (di)
52    diagPosMax:     B    # Defines maximum positively-sloped diagonal (da)
53"""
54
55
56class _Object:
57    pass
58
59
60class _Dict(dict):
61    pass
62
63
64class table_G__l_a_t(DefaultTable.DefaultTable):
65    """
66    Support Graphite Glat tables
67    """
68
69    def __init__(self, tag=None):
70        DefaultTable.DefaultTable.__init__(self, tag)
71        self.scheme = 0
72
73    def decompile(self, data, ttFont):
74        sstruct.unpack2(Glat_format_0, data, self)
75        self.version = float(floatToFixedToStr(self.version, precisionBits=16))
76        if self.version <= 1.9:
77            decoder = partial(self.decompileAttributes12, fmt=Glat_format_1_entry)
78        elif self.version <= 2.9:
79            decoder = partial(self.decompileAttributes12, fmt=Glat_format_23_entry)
80        elif self.version >= 3.0:
81            (data, self.scheme) = grUtils.decompress(data)
82            sstruct.unpack2(Glat_format_3, data, self)
83            self.hasOctaboxes = (self.compression & 1) == 1
84            decoder = self.decompileAttributes3
85
86        gloc = ttFont["Gloc"]
87        self.attributes = {}
88        count = 0
89        for s, e in zip(gloc, gloc[1:]):
90            self.attributes[ttFont.getGlyphName(count)] = decoder(data[s:e])
91            count += 1
92
93    def decompileAttributes12(self, data, fmt):
94        attributes = _Dict()
95        while len(data) > 3:
96            e, data = sstruct.unpack2(fmt, data, _Object())
97            keys = range(e.attNum, e.attNum + e.num)
98            if len(data) >= 2 * e.num:
99                vals = struct.unpack_from((">%dh" % e.num), data)
100                attributes.update(zip(keys, vals))
101                data = data[2 * e.num :]
102        return attributes
103
104    def decompileAttributes3(self, data):
105        if self.hasOctaboxes:
106            o, data = sstruct.unpack2(Glat_format_3_octabox_metrics, data, _Object())
107            numsub = bin(o.subboxBitmap).count("1")
108            o.subboxes = []
109            for b in range(numsub):
110                if len(data) >= 8:
111                    subbox, data = sstruct.unpack2(
112                        Glat_format_3_subbox_entry, data, _Object()
113                    )
114                    o.subboxes.append(subbox)
115        attrs = self.decompileAttributes12(data, Glat_format_23_entry)
116        if self.hasOctaboxes:
117            attrs.octabox = o
118        return attrs
119
120    def compile(self, ttFont):
121        data = sstruct.pack(Glat_format_0, self)
122        if self.version <= 1.9:
123            encoder = partial(self.compileAttributes12, fmt=Glat_format_1_entry)
124        elif self.version <= 2.9:
125            encoder = partial(self.compileAttributes12, fmt=Glat_format_1_entry)
126        elif self.version >= 3.0:
127            self.compression = (self.scheme << 27) + (1 if self.hasOctaboxes else 0)
128            data = sstruct.pack(Glat_format_3, self)
129            encoder = self.compileAttributes3
130
131        glocs = []
132        for n in range(len(self.attributes)):
133            glocs.append(len(data))
134            data += encoder(self.attributes[ttFont.getGlyphName(n)])
135        glocs.append(len(data))
136        ttFont["Gloc"].set(glocs)
137
138        if self.version >= 3.0:
139            data = grUtils.compress(self.scheme, data)
140        return data
141
142    def compileAttributes12(self, attrs, fmt):
143        data = b""
144        for e in grUtils.entries(attrs):
145            data += sstruct.pack(fmt, {"attNum": e[0], "num": e[1]}) + struct.pack(
146                (">%dh" % len(e[2])), *e[2]
147            )
148        return data
149
150    def compileAttributes3(self, attrs):
151        if self.hasOctaboxes:
152            o = attrs.octabox
153            data = sstruct.pack(Glat_format_3_octabox_metrics, o)
154            numsub = bin(o.subboxBitmap).count("1")
155            for b in range(numsub):
156                data += sstruct.pack(Glat_format_3_subbox_entry, o.subboxes[b])
157        else:
158            data = ""
159        return data + self.compileAttributes12(attrs, Glat_format_23_entry)
160
161    def toXML(self, writer, ttFont):
162        writer.simpletag("version", version=self.version, compressionScheme=self.scheme)
163        writer.newline()
164        for n, a in sorted(
165            self.attributes.items(), key=lambda x: ttFont.getGlyphID(x[0])
166        ):
167            writer.begintag("glyph", name=n)
168            writer.newline()
169            if hasattr(a, "octabox"):
170                o = a.octabox
171                formatstring, names, fixes = sstruct.getformat(
172                    Glat_format_3_octabox_metrics
173                )
174                vals = {}
175                for k in names:
176                    if k == "subboxBitmap":
177                        continue
178                    vals[k] = "{:.3f}%".format(getattr(o, k) * 100.0 / 255)
179                vals["bitmap"] = "{:0X}".format(o.subboxBitmap)
180                writer.begintag("octaboxes", **vals)
181                writer.newline()
182                formatstring, names, fixes = sstruct.getformat(
183                    Glat_format_3_subbox_entry
184                )
185                for s in o.subboxes:
186                    vals = {}
187                    for k in names:
188                        vals[k] = "{:.3f}%".format(getattr(s, k) * 100.0 / 255)
189                    writer.simpletag("octabox", **vals)
190                    writer.newline()
191                writer.endtag("octaboxes")
192                writer.newline()
193            for k, v in sorted(a.items()):
194                writer.simpletag("attribute", index=k, value=v)
195                writer.newline()
196            writer.endtag("glyph")
197            writer.newline()
198
199    def fromXML(self, name, attrs, content, ttFont):
200        if name == "version":
201            self.version = float(safeEval(attrs["version"]))
202            self.scheme = int(safeEval(attrs["compressionScheme"]))
203        if name != "glyph":
204            return
205        if not hasattr(self, "attributes"):
206            self.attributes = {}
207        gname = attrs["name"]
208        attributes = _Dict()
209        for element in content:
210            if not isinstance(element, tuple):
211                continue
212            tag, attrs, subcontent = element
213            if tag == "attribute":
214                k = int(safeEval(attrs["index"]))
215                v = int(safeEval(attrs["value"]))
216                attributes[k] = v
217            elif tag == "octaboxes":
218                self.hasOctaboxes = True
219                o = _Object()
220                o.subboxBitmap = int(attrs["bitmap"], 16)
221                o.subboxes = []
222                del attrs["bitmap"]
223                for k, v in attrs.items():
224                    setattr(o, k, int(float(v[:-1]) * 255.0 / 100.0 + 0.5))
225                for element in subcontent:
226                    if not isinstance(element, tuple):
227                        continue
228                    (tag, attrs, subcontent) = element
229                    so = _Object()
230                    for k, v in attrs.items():
231                        setattr(so, k, int(float(v[:-1]) * 255.0 / 100.0 + 0.5))
232                    o.subboxes.append(so)
233                attributes.octabox = o
234        self.attributes[gname] = attributes
235