1from fontTools.misc import sstruct 2from fontTools.misc.textTools import bytechr, byteord, strjoin 3from . import DefaultTable 4import array 5from collections.abc import Mapping 6 7hdmxHeaderFormat = """ 8 > # big endian! 9 version: H 10 numRecords: H 11 recordSize: l 12""" 13 14 15class _GlyphnamedList(Mapping): 16 def __init__(self, reverseGlyphOrder, data): 17 self._array = data 18 self._map = dict(reverseGlyphOrder) 19 20 def __getitem__(self, k): 21 return self._array[self._map[k]] 22 23 def __len__(self): 24 return len(self._map) 25 26 def __iter__(self): 27 return iter(self._map) 28 29 def keys(self): 30 return self._map.keys() 31 32 33class table__h_d_m_x(DefaultTable.DefaultTable): 34 def decompile(self, data, ttFont): 35 numGlyphs = ttFont["maxp"].numGlyphs 36 glyphOrder = ttFont.getGlyphOrder() 37 dummy, data = sstruct.unpack2(hdmxHeaderFormat, data, self) 38 self.hdmx = {} 39 for i in range(self.numRecords): 40 ppem = byteord(data[0]) 41 maxSize = byteord(data[1]) 42 widths = _GlyphnamedList( 43 ttFont.getReverseGlyphMap(), array.array("B", data[2 : 2 + numGlyphs]) 44 ) 45 self.hdmx[ppem] = widths 46 data = data[self.recordSize :] 47 assert len(data) == 0, "too much hdmx data" 48 49 def compile(self, ttFont): 50 self.version = 0 51 numGlyphs = ttFont["maxp"].numGlyphs 52 glyphOrder = ttFont.getGlyphOrder() 53 self.recordSize = 4 * ((2 + numGlyphs + 3) // 4) 54 pad = (self.recordSize - 2 - numGlyphs) * b"\0" 55 self.numRecords = len(self.hdmx) 56 data = sstruct.pack(hdmxHeaderFormat, self) 57 items = sorted(self.hdmx.items()) 58 for ppem, widths in items: 59 data = data + bytechr(ppem) + bytechr(max(widths.values())) 60 for glyphID in range(len(glyphOrder)): 61 width = widths[glyphOrder[glyphID]] 62 data = data + bytechr(width) 63 data = data + pad 64 return data 65 66 def toXML(self, writer, ttFont): 67 writer.begintag("hdmxData") 68 writer.newline() 69 ppems = sorted(self.hdmx.keys()) 70 records = [] 71 format = "" 72 for ppem in ppems: 73 widths = self.hdmx[ppem] 74 records.append(widths) 75 format = format + "%4d" 76 glyphNames = ttFont.getGlyphOrder()[:] 77 glyphNames.sort() 78 maxNameLen = max(map(len, glyphNames)) 79 format = "%" + repr(maxNameLen) + "s:" + format + " ;" 80 writer.write(format % (("ppem",) + tuple(ppems))) 81 writer.newline() 82 writer.newline() 83 for glyphName in glyphNames: 84 row = [] 85 for ppem in ppems: 86 widths = self.hdmx[ppem] 87 row.append(widths[glyphName]) 88 if ";" in glyphName: 89 glyphName = "\\x3b".join(glyphName.split(";")) 90 writer.write(format % ((glyphName,) + tuple(row))) 91 writer.newline() 92 writer.endtag("hdmxData") 93 writer.newline() 94 95 def fromXML(self, name, attrs, content, ttFont): 96 if name != "hdmxData": 97 return 98 content = strjoin(content) 99 lines = content.split(";") 100 topRow = lines[0].split() 101 assert topRow[0] == "ppem:", "illegal hdmx format" 102 ppems = list(map(int, topRow[1:])) 103 self.hdmx = hdmx = {} 104 for ppem in ppems: 105 hdmx[ppem] = {} 106 lines = (line.split() for line in lines[1:]) 107 for line in lines: 108 if not line: 109 continue 110 assert line[0][-1] == ":", "illegal hdmx format" 111 glyphName = line[0][:-1] 112 if "\\" in glyphName: 113 from fontTools.misc.textTools import safeEval 114 115 glyphName = safeEval('"""' + glyphName + '"""') 116 line = list(map(int, line[1:])) 117 assert len(line) == len(ppems), "illegal hdmx format" 118 for i in range(len(ppems)): 119 hdmx[ppems[i]][glyphName] = line[i] 120