| from . import DefaultTable |
| from fontTools.misc import sstruct |
| from fontTools.misc.textTools import safeEval |
| import struct |
| |
| VDMX_HeaderFmt = """ |
| > # big endian |
| version: H # Version number (0 or 1) |
| numRecs: H # Number of VDMX groups present |
| numRatios: H # Number of aspect ratio groupings |
| """ |
| # the VMDX header is followed by an array of RatRange[numRatios] (i.e. aspect |
| # ratio ranges); |
| VDMX_RatRangeFmt = """ |
| > # big endian |
| bCharSet: B # Character set |
| xRatio: B # Value to use for x-Ratio |
| yStartRatio: B # Starting y-Ratio value |
| yEndRatio: B # Ending y-Ratio value |
| """ |
| # followed by an array of offset[numRatios] from start of VDMX table to the |
| # VDMX Group for this ratio range (offsets will be re-calculated on compile); |
| # followed by an array of Group[numRecs] records; |
| VDMX_GroupFmt = """ |
| > # big endian |
| recs: H # Number of height records in this group |
| startsz: B # Starting yPelHeight |
| endsz: B # Ending yPelHeight |
| """ |
| # followed by an array of vTable[recs] records. |
| VDMX_vTableFmt = """ |
| > # big endian |
| yPelHeight: H # yPelHeight to which values apply |
| yMax: h # Maximum value (in pels) for this yPelHeight |
| yMin: h # Minimum value (in pels) for this yPelHeight |
| """ |
| |
| |
| class table_V_D_M_X_(DefaultTable.DefaultTable): |
| |
| def decompile(self, data, ttFont): |
| pos = 0 # track current position from to start of VDMX table |
| dummy, data = sstruct.unpack2(VDMX_HeaderFmt, data, self) |
| pos += sstruct.calcsize(VDMX_HeaderFmt) |
| self.ratRanges = [] |
| for i in range(self.numRatios): |
| ratio, data = sstruct.unpack2(VDMX_RatRangeFmt, data) |
| pos += sstruct.calcsize(VDMX_RatRangeFmt) |
| # the mapping between a ratio and a group is defined further below |
| ratio['groupIndex'] = None |
| self.ratRanges.append(ratio) |
| lenOffset = struct.calcsize('>H') |
| _offsets = [] # temporarily store offsets to groups |
| for i in range(self.numRatios): |
| offset = struct.unpack('>H', data[0:lenOffset])[0] |
| data = data[lenOffset:] |
| pos += lenOffset |
| _offsets.append(offset) |
| self.groups = [] |
| for groupIndex in range(self.numRecs): |
| # the offset to this group from beginning of the VDMX table |
| currOffset = pos |
| group, data = sstruct.unpack2(VDMX_GroupFmt, data) |
| # the group lenght and bounding sizes are re-calculated on compile |
| recs = group.pop('recs') |
| startsz = group.pop('startsz') |
| endsz = group.pop('endsz') |
| pos += sstruct.calcsize(VDMX_GroupFmt) |
| for j in range(recs): |
| vTable, data = sstruct.unpack2(VDMX_vTableFmt, data) |
| vTableLength = sstruct.calcsize(VDMX_vTableFmt) |
| pos += vTableLength |
| # group is a dict of (yMax, yMin) tuples keyed by yPelHeight |
| group[vTable['yPelHeight']] = (vTable['yMax'], vTable['yMin']) |
| # make sure startsz and endsz match the calculated values |
| minSize = min(group.keys()) |
| maxSize = max(group.keys()) |
| assert startsz == minSize, \ |
| "startsz (%s) must equal min yPelHeight (%s): group %d" % \ |
| (group.startsz, minSize, groupIndex) |
| assert endsz == maxSize, \ |
| "endsz (%s) must equal max yPelHeight (%s): group %d" % \ |
| (group.endsz, maxSize, groupIndex) |
| self.groups.append(group) |
| # match the defined offsets with the current group's offset |
| for offsetIndex, offsetValue in enumerate(_offsets): |
| # when numRecs < numRatios there can more than one ratio range |
| # sharing the same VDMX group |
| if currOffset == offsetValue: |
| # map the group with the ratio range thas has the same |
| # index as the offset to that group (it took me a while..) |
| self.ratRanges[offsetIndex]['groupIndex'] = groupIndex |
| # check that all ratio ranges have a group |
| for i in range(self.numRatios): |
| ratio = self.ratRanges[i] |
| if ratio['groupIndex'] is None: |
| from fontTools import ttLib |
| raise ttLib.TTLibError( |
| "no group defined for ratRange %d" % i) |
| |
| def _getOffsets(self): |
| """ |
| Calculate offsets to VDMX_Group records. |
| For each ratRange return a list of offset values from the beginning of |
| the VDMX table to a VDMX_Group. |
| """ |
| lenHeader = sstruct.calcsize(VDMX_HeaderFmt) |
| lenRatRange = sstruct.calcsize(VDMX_RatRangeFmt) |
| lenOffset = struct.calcsize('>H') |
| lenGroupHeader = sstruct.calcsize(VDMX_GroupFmt) |
| lenVTable = sstruct.calcsize(VDMX_vTableFmt) |
| # offset to the first group |
| pos = lenHeader + self.numRatios*lenRatRange + self.numRatios*lenOffset |
| groupOffsets = [] |
| for group in self.groups: |
| groupOffsets.append(pos) |
| lenGroup = lenGroupHeader + len(group) * lenVTable |
| pos += lenGroup # offset to next group |
| offsets = [] |
| for ratio in self.ratRanges: |
| groupIndex = ratio['groupIndex'] |
| offsets.append(groupOffsets[groupIndex]) |
| return offsets |
| |
| def compile(self, ttFont): |
| if not(self.version == 0 or self.version == 1): |
| from fontTools import ttLib |
| raise ttLib.TTLibError( |
| "unknown format for VDMX table: version %s" % self.version) |
| data = sstruct.pack(VDMX_HeaderFmt, self) |
| for ratio in self.ratRanges: |
| data += sstruct.pack(VDMX_RatRangeFmt, ratio) |
| # recalculate offsets to VDMX groups |
| for offset in self._getOffsets(): |
| data += struct.pack('>H', offset) |
| for group in self.groups: |
| recs = len(group) |
| startsz = min(group.keys()) |
| endsz = max(group.keys()) |
| gHeader = {'recs': recs, 'startsz': startsz, 'endsz': endsz} |
| data += sstruct.pack(VDMX_GroupFmt, gHeader) |
| for yPelHeight, (yMax, yMin) in sorted(group.items()): |
| vTable = {'yPelHeight': yPelHeight, 'yMax': yMax, 'yMin': yMin} |
| data += sstruct.pack(VDMX_vTableFmt, vTable) |
| return data |
| |
| def toXML(self, writer, ttFont): |
| writer.simpletag("version", value=self.version) |
| writer.newline() |
| writer.begintag("ratRanges") |
| writer.newline() |
| for ratio in self.ratRanges: |
| groupIndex = ratio['groupIndex'] |
| writer.simpletag( |
| "ratRange", |
| bCharSet=ratio['bCharSet'], |
| xRatio=ratio['xRatio'], |
| yStartRatio=ratio['yStartRatio'], |
| yEndRatio=ratio['yEndRatio'], |
| groupIndex=groupIndex |
| ) |
| writer.newline() |
| writer.endtag("ratRanges") |
| writer.newline() |
| writer.begintag("groups") |
| writer.newline() |
| for groupIndex in range(self.numRecs): |
| group = self.groups[groupIndex] |
| recs = len(group) |
| startsz = min(group.keys()) |
| endsz = max(group.keys()) |
| writer.begintag("group", index=groupIndex) |
| writer.newline() |
| writer.comment("recs=%d, startsz=%d, endsz=%d" % |
| (recs, startsz, endsz)) |
| writer.newline() |
| for yPelHeight, (yMax, yMin) in sorted(group.items()): |
| writer.simpletag( |
| "record", |
| [('yPelHeight', yPelHeight), ('yMax', yMax), ('yMin', yMin)]) |
| writer.newline() |
| writer.endtag("group") |
| writer.newline() |
| writer.endtag("groups") |
| writer.newline() |
| |
| def fromXML(self, name, attrs, content, ttFont): |
| if name == "version": |
| self.version = safeEval(attrs["value"]) |
| elif name == "ratRanges": |
| if not hasattr(self, "ratRanges"): |
| self.ratRanges = [] |
| for element in content: |
| if not isinstance(element, tuple): |
| continue |
| name, attrs, content = element |
| if name == "ratRange": |
| if not hasattr(self, "numRatios"): |
| self.numRatios = 1 |
| else: |
| self.numRatios += 1 |
| ratio = { |
| "bCharSet": safeEval(attrs["bCharSet"]), |
| "xRatio": safeEval(attrs["xRatio"]), |
| "yStartRatio": safeEval(attrs["yStartRatio"]), |
| "yEndRatio": safeEval(attrs["yEndRatio"]), |
| "groupIndex": safeEval(attrs["groupIndex"]) |
| } |
| self.ratRanges.append(ratio) |
| elif name == "groups": |
| if not hasattr(self, "groups"): |
| self.groups = [] |
| for element in content: |
| if not isinstance(element, tuple): |
| continue |
| name, attrs, content = element |
| if name == "group": |
| if not hasattr(self, "numRecs"): |
| self.numRecs = 1 |
| else: |
| self.numRecs += 1 |
| group = {} |
| for element in content: |
| if not isinstance(element, tuple): |
| continue |
| name, attrs, content = element |
| if name == "record": |
| yPelHeight = safeEval(attrs["yPelHeight"]) |
| yMax = safeEval(attrs["yMax"]) |
| yMin = safeEval(attrs["yMin"]) |
| group[yPelHeight] = (yMax, yMin) |
| self.groups.append(group) |