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