1from __future__ import print_function, division, absolute_import 2from fontTools.misc.py23 import * 3from . import DefaultTable 4from fontTools.misc import sstruct 5from fontTools.misc.textTools import safeEval 6import struct 7 8VDMX_HeaderFmt = """ 9 > # big endian 10 version: H # Version number (0 or 1) 11 numRecs: H # Number of VDMX groups present 12 numRatios: H # Number of aspect ratio groupings 13""" 14# the VMDX header is followed by an array of RatRange[numRatios] (i.e. aspect 15# ratio ranges); 16VDMX_RatRangeFmt = """ 17 > # big endian 18 bCharSet: B # Character set 19 xRatio: B # Value to use for x-Ratio 20 yStartRatio: B # Starting y-Ratio value 21 yEndRatio: B # Ending y-Ratio value 22""" 23# followed by an array of offset[numRatios] from start of VDMX table to the 24# VDMX Group for this ratio range (offsets will be re-calculated on compile); 25# followed by an array of Group[numRecs] records; 26VDMX_GroupFmt = """ 27 > # big endian 28 recs: H # Number of height records in this group 29 startsz: B # Starting yPelHeight 30 endsz: B # Ending yPelHeight 31""" 32# followed by an array of vTable[recs] records. 33VDMX_vTableFmt = """ 34 > # big endian 35 yPelHeight: H # yPelHeight to which values apply 36 yMax: h # Maximum value (in pels) for this yPelHeight 37 yMin: h # Minimum value (in pels) for this yPelHeight 38""" 39 40 41class table_V_D_M_X_(DefaultTable.DefaultTable): 42 43 def decompile(self, data, ttFont): 44 pos = 0 # track current position from to start of VDMX table 45 dummy, data = sstruct.unpack2(VDMX_HeaderFmt, data, self) 46 pos += sstruct.calcsize(VDMX_HeaderFmt) 47 self.ratRanges = [] 48 for i in range(self.numRatios): 49 ratio, data = sstruct.unpack2(VDMX_RatRangeFmt, data) 50 pos += sstruct.calcsize(VDMX_RatRangeFmt) 51 # the mapping between a ratio and a group is defined further below 52 ratio['groupIndex'] = None 53 self.ratRanges.append(ratio) 54 lenOffset = struct.calcsize('>H') 55 _offsets = [] # temporarily store offsets to groups 56 for i in range(self.numRatios): 57 offset = struct.unpack('>H', data[0:lenOffset])[0] 58 data = data[lenOffset:] 59 pos += lenOffset 60 _offsets.append(offset) 61 self.groups = [] 62 for groupIndex in range(self.numRecs): 63 # the offset to this group from beginning of the VDMX table 64 currOffset = pos 65 group, data = sstruct.unpack2(VDMX_GroupFmt, data) 66 # the group lenght and bounding sizes are re-calculated on compile 67 recs = group.pop('recs') 68 startsz = group.pop('startsz') 69 endsz = group.pop('endsz') 70 pos += sstruct.calcsize(VDMX_GroupFmt) 71 for j in range(recs): 72 vTable, data = sstruct.unpack2(VDMX_vTableFmt, data) 73 vTableLength = sstruct.calcsize(VDMX_vTableFmt) 74 pos += vTableLength 75 # group is a dict of (yMax, yMin) tuples keyed by yPelHeight 76 group[vTable['yPelHeight']] = (vTable['yMax'], vTable['yMin']) 77 # make sure startsz and endsz match the calculated values 78 minSize = min(group.keys()) 79 maxSize = max(group.keys()) 80 assert startsz == minSize, \ 81 "startsz (%s) must equal min yPelHeight (%s): group %d" % \ 82 (group.startsz, minSize, groupIndex) 83 assert endsz == maxSize, \ 84 "endsz (%s) must equal max yPelHeight (%s): group %d" % \ 85 (group.endsz, maxSize, groupIndex) 86 self.groups.append(group) 87 # match the defined offsets with the current group's offset 88 for offsetIndex, offsetValue in enumerate(_offsets): 89 # when numRecs < numRatios there can more than one ratio range 90 # sharing the same VDMX group 91 if currOffset == offsetValue: 92 # map the group with the ratio range thas has the same 93 # index as the offset to that group (it took me a while..) 94 self.ratRanges[offsetIndex]['groupIndex'] = groupIndex 95 # check that all ratio ranges have a group 96 for i in range(self.numRatios): 97 ratio = self.ratRanges[i] 98 if ratio['groupIndex'] is None: 99 from fontTools import ttLib 100 raise ttLib.TTLibError( 101 "no group defined for ratRange %d" % i) 102 103 def _getOffsets(self): 104 """ 105 Calculate offsets to VDMX_Group records. 106 For each ratRange return a list of offset values from the beginning of 107 the VDMX table to a VDMX_Group. 108 """ 109 lenHeader = sstruct.calcsize(VDMX_HeaderFmt) 110 lenRatRange = sstruct.calcsize(VDMX_RatRangeFmt) 111 lenOffset = struct.calcsize('>H') 112 lenGroupHeader = sstruct.calcsize(VDMX_GroupFmt) 113 lenVTable = sstruct.calcsize(VDMX_vTableFmt) 114 # offset to the first group 115 pos = lenHeader + self.numRatios*lenRatRange + self.numRatios*lenOffset 116 groupOffsets = [] 117 for group in self.groups: 118 groupOffsets.append(pos) 119 lenGroup = lenGroupHeader + len(group) * lenVTable 120 pos += lenGroup # offset to next group 121 offsets = [] 122 for ratio in self.ratRanges: 123 groupIndex = ratio['groupIndex'] 124 offsets.append(groupOffsets[groupIndex]) 125 return offsets 126 127 def compile(self, ttFont): 128 if not(self.version == 0 or self.version == 1): 129 from fontTools import ttLib 130 raise ttLib.TTLibError( 131 "unknown format for VDMX table: version %s" % self.version) 132 data = sstruct.pack(VDMX_HeaderFmt, self) 133 for ratio in self.ratRanges: 134 data += sstruct.pack(VDMX_RatRangeFmt, ratio) 135 # recalculate offsets to VDMX groups 136 for offset in self._getOffsets(): 137 data += struct.pack('>H', offset) 138 for group in self.groups: 139 recs = len(group) 140 startsz = min(group.keys()) 141 endsz = max(group.keys()) 142 gHeader = {'recs': recs, 'startsz': startsz, 'endsz': endsz} 143 data += sstruct.pack(VDMX_GroupFmt, gHeader) 144 for yPelHeight, (yMax, yMin) in sorted(group.items()): 145 vTable = {'yPelHeight': yPelHeight, 'yMax': yMax, 'yMin': yMin} 146 data += sstruct.pack(VDMX_vTableFmt, vTable) 147 return data 148 149 def toXML(self, writer, ttFont): 150 writer.simpletag("version", value=self.version) 151 writer.newline() 152 writer.begintag("ratRanges") 153 writer.newline() 154 for ratio in self.ratRanges: 155 groupIndex = ratio['groupIndex'] 156 writer.simpletag( 157 "ratRange", 158 bCharSet=ratio['bCharSet'], 159 xRatio=ratio['xRatio'], 160 yStartRatio=ratio['yStartRatio'], 161 yEndRatio=ratio['yEndRatio'], 162 groupIndex=groupIndex 163 ) 164 writer.newline() 165 writer.endtag("ratRanges") 166 writer.newline() 167 writer.begintag("groups") 168 writer.newline() 169 for groupIndex in range(self.numRecs): 170 group = self.groups[groupIndex] 171 recs = len(group) 172 startsz = min(group.keys()) 173 endsz = max(group.keys()) 174 writer.begintag("group", index=groupIndex) 175 writer.newline() 176 writer.comment("recs=%d, startsz=%d, endsz=%d" % 177 (recs, startsz, endsz)) 178 writer.newline() 179 for yPelHeight, (yMax, yMin) in sorted(group.items()): 180 writer.simpletag( 181 "record", 182 [('yPelHeight', yPelHeight), ('yMax', yMax), ('yMin', yMin)]) 183 writer.newline() 184 writer.endtag("group") 185 writer.newline() 186 writer.endtag("groups") 187 writer.newline() 188 189 def fromXML(self, name, attrs, content, ttFont): 190 if name == "version": 191 self.version = safeEval(attrs["value"]) 192 elif name == "ratRanges": 193 if not hasattr(self, "ratRanges"): 194 self.ratRanges = [] 195 for element in content: 196 if not isinstance(element, tuple): 197 continue 198 name, attrs, content = element 199 if name == "ratRange": 200 if not hasattr(self, "numRatios"): 201 self.numRatios = 1 202 else: 203 self.numRatios += 1 204 ratio = { 205 "bCharSet": safeEval(attrs["bCharSet"]), 206 "xRatio": safeEval(attrs["xRatio"]), 207 "yStartRatio": safeEval(attrs["yStartRatio"]), 208 "yEndRatio": safeEval(attrs["yEndRatio"]), 209 "groupIndex": safeEval(attrs["groupIndex"]) 210 } 211 self.ratRanges.append(ratio) 212 elif name == "groups": 213 if not hasattr(self, "groups"): 214 self.groups = [] 215 for element in content: 216 if not isinstance(element, tuple): 217 continue 218 name, attrs, content = element 219 if name == "group": 220 if not hasattr(self, "numRecs"): 221 self.numRecs = 1 222 else: 223 self.numRecs += 1 224 group = {} 225 for element in content: 226 if not isinstance(element, tuple): 227 continue 228 name, attrs, content = element 229 if name == "record": 230 yPelHeight = safeEval(attrs["yPelHeight"]) 231 yMax = safeEval(attrs["yMax"]) 232 yMin = safeEval(attrs["yMin"]) 233 group[yPelHeight] = (yMax, yMin) 234 self.groups.append(group) 235