def decompile(self, ttFont): self.glyphName = ttFont.getGlyphName(self.gid) if self.rawdata is None: from fontemon_blender_addon.fontTools import ttLib raise ttLib.TTLibError("No table data to decompile") if len(self.rawdata) > 0: if len(self.rawdata) < sbixGlyphHeaderFormatSize: from fontemon_blender_addon.fontTools import ttLib #print "Glyph %i header too short: Expected %x, got %x." % (self.gid, sbixGlyphHeaderFormatSize, len(self.rawdata)) raise ttLib.TTLibError("Glyph header too short.") sstruct.unpack(sbixGlyphHeaderFormat, self.rawdata[:sbixGlyphHeaderFormatSize], self) if self.graphicType == "dupe": # this glyph is a reference to another glyph's image data gid, = struct.unpack(">H", self.rawdata[sbixGlyphHeaderFormatSize:]) self.referenceGlyphName = ttFont.getGlyphName(gid) else: self.imageData = self.rawdata[sbixGlyphHeaderFormatSize:] self.referenceGlyphName = None # clean up del self.rawdata del self.gid
def __init__(self, glyphName=None, referenceGlyphName=None, originOffsetX=0, originOffsetY=0, graphicType=None, imageData=None, rawdata=None, gid=0): self.gid = gid self.glyphName = glyphName self.referenceGlyphName = referenceGlyphName self.originOffsetX = originOffsetX self.originOffsetY = originOffsetY self.rawdata = rawdata self.graphicType = graphicType self.imageData = imageData # fix self.graphicType if it is null terminated or too short if self.graphicType is not None: if self.graphicType[-1] == "\0": self.graphicType = self.graphicType[:-1] if len(self.graphicType) > 4: from fontemon_blender_addon.fontTools import ttLib raise ttLib.TTLibError( "Glyph.graphicType must not be longer than 4 characters.") elif len(self.graphicType) < 4: # pad with spaces self.graphicType += " "[:(4 - len(self.graphicType))]
def decompile(self, data, ttFont): # read table header sstruct.unpack(sbixHeaderFormat, data[ : sbixHeaderFormatSize], self) # collect offsets to individual strikes in self.strikeOffsets for i in range(self.numStrikes): current_offset = sbixHeaderFormatSize + i * sbixStrikeOffsetFormatSize offset_entry = sbixStrikeOffset() sstruct.unpack(sbixStrikeOffsetFormat, \ data[current_offset:current_offset+sbixStrikeOffsetFormatSize], \ offset_entry) self.strikeOffsets.append(offset_entry.strikeOffset) # decompile Strikes for i in range(self.numStrikes-1, -1, -1): current_strike = Strike(rawdata=data[self.strikeOffsets[i]:]) data = data[:self.strikeOffsets[i]] current_strike.decompile(ttFont) #print " Strike length: %xh" % len(bitmapSetData) #print "Number of Glyph entries:", len(current_strike.glyphs) if current_strike.ppem in self.strikes: from fontemon_blender_addon.fontTools import ttLib raise ttLib.TTLibError("Pixel 'ppem' must be unique for each Strike") self.strikes[current_strike.ppem] = current_strike # after the glyph data records have been extracted, we don't need the offsets anymore del self.strikeOffsets del self.numStrikes
def compile(self, ttFont): self.updateFirstAndLastCharIndex(ttFont) panose = self.panose head = ttFont["head"] if (self.fsSelection & 1) and not (head.macStyle & 1<<1): log.warning("fsSelection bit 0 (italic) and " "head table macStyle bit 1 (italic) should match") if (self.fsSelection & 1<<5) and not (head.macStyle & 1): log.warning("fsSelection bit 5 (bold) and " "head table macStyle bit 0 (bold) should match") if (self.fsSelection & 1<<6) and (self.fsSelection & 1 + (1<<5)): log.warning("fsSelection bit 6 (regular) is set, " "bits 0 (italic) and 5 (bold) must be clear") if self.version < 4 and self.fsSelection & 0b1110000000: log.warning("fsSelection bits 7, 8 and 9 are only defined in " "OS/2 table version 4 and up: version %s", self.version) self.panose = sstruct.pack(panoseFormat, self.panose) if self.version == 0: data = sstruct.pack(OS2_format_0, self) elif self.version == 1: data = sstruct.pack(OS2_format_1, self) elif self.version in (2, 3, 4): data = sstruct.pack(OS2_format_2, self) elif self.version == 5: d = self.__dict__.copy() d['usLowerOpticalPointSize'] = round(self.usLowerOpticalPointSize * 20) d['usUpperOpticalPointSize'] = round(self.usUpperOpticalPointSize * 20) data = sstruct.pack(OS2_format_5, d) else: from fontemon_blender_addon.fontTools import ttLib raise ttLib.TTLibError("unknown format for OS/2 table: version %s" % self.version) self.panose = panose return data
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 fontemon_blender_addon.fontTools import ttLib raise ttLib.TTLibError("no group defined for ratRange %d" % i)
def getGlyphOrder(self): """This function will get called by a ttLib.TTFont instance. Do not call this function yourself, use TTFont().getGlyphOrder() or its relatives instead! """ if not hasattr(self, "glyphOrder"): raise ttLib.TTLibError("illegal use of getGlyphOrder()") glyphOrder = self.glyphOrder del self.glyphOrder return glyphOrder
def compile(self, ttFont): if self.glyphName is None: from fontemon_blender_addon.fontTools import ttLib raise ttLib.TTLibError("Can't compile Glyph without glyph name") # TODO: if ttFont has no maxp, cmap etc., ignore glyph names and compile by index? # (needed if you just want to compile the sbix table on its own) self.gid = struct.pack(">H", ttFont.getGlyphID(self.glyphName)) if self.graphicType is None: self.rawdata = b"" else: self.rawdata = sstruct.pack(sbixGlyphHeaderFormat, self) + self.imageData
def fromXML(self, name, attrs, content, ttFont): if name in ["ppem", "resolution"]: setattr(self, name, safeEval(attrs["value"])) elif name == "glyph": if "graphicType" in attrs: myFormat = safeEval("'''" + attrs["graphicType"] + "'''") else: myFormat = None if "glyphname" in attrs: myGlyphName = safeEval("'''" + attrs["glyphname"] + "'''") elif "name" in attrs: myGlyphName = safeEval("'''" + attrs["name"] + "'''") else: from fontemon_blender_addon.fontTools import ttLib raise ttLib.TTLibError("Glyph must have a glyph name.") if "originOffsetX" in attrs: myOffsetX = safeEval(attrs["originOffsetX"]) else: myOffsetX = 0 if "originOffsetY" in attrs: myOffsetY = safeEval(attrs["originOffsetY"]) else: myOffsetY = 0 current_glyph = Glyph( glyphName=myGlyphName, graphicType=myFormat, originOffsetX=myOffsetX, originOffsetY=myOffsetY, ) for element in content: if isinstance(element, tuple): name, attrs, content = element current_glyph.fromXML(name, attrs, content, ttFont) current_glyph.compile(ttFont) self.glyphs[current_glyph.glyphName] = current_glyph else: from fontemon_blender_addon.fontTools import ttLib raise ttLib.TTLibError("can't handle '%s' element" % name)
def __init__(self, path, res_name_or_index): from fontemon_blender_addon.fontTools import ttLib reader = ResourceReader(path) if isinstance(res_name_or_index, basestring): rsrc = reader.getNamedResource('sfnt', res_name_or_index) else: rsrc = reader.getIndResource('sfnt', res_name_or_index) if rsrc is None: raise ttLib.TTLibError("sfnt resource not found: %s" % res_name_or_index) reader.close() self.rsrc = rsrc super(SFNTResourceReader, self).__init__(rsrc.data) self.name = path
def fromXML(self, name, attrs, content, ttFont): if name == "ref": # glyph is a "dupe", i.e. a reference to another glyph's image data. # in this case imageData contains the glyph id of the reference glyph # get glyph id from glyphname self.imageData = struct.pack( ">H", ttFont.getGlyphID(safeEval("'''" + attrs["glyphname"] + "'''"))) elif name == "hexdata": self.imageData = readHex(content) else: from fontemon_blender_addon.fontTools import ttLib raise ttLib.TTLibError("can't handle '%s' element" % name)
def decompile(self, data, ttFont): sstruct.unpack(postFormat, data[:postFormatSize], self) data = data[postFormatSize:] if self.formatType == 1.0: self.decode_format_1_0(data, ttFont) elif self.formatType == 2.0: self.decode_format_2_0(data, ttFont) elif self.formatType == 3.0: self.decode_format_3_0(data, ttFont) elif self.formatType == 4.0: self.decode_format_4_0(data, ttFont) else: # supported format raise ttLib.TTLibError("'post' table format %f not supported" % self.formatType)
def compile(self, ttFont): data = sstruct.pack(postFormat, self) if self.formatType == 1.0: pass # we're done elif self.formatType == 2.0: data = data + self.encode_format_2_0(ttFont) elif self.formatType == 3.0: pass # we're done elif self.formatType == 4.0: data = data + self.encode_format_4_0(ttFont) else: # supported format raise ttLib.TTLibError("'post' table format %f not supported" % self.formatType) return data
def fromXML(self, name, attrs, content, ttFont): if name =="version": setattr(self, name, safeEval(attrs["value"])) elif name == "flags": setattr(self, name, binary2num(attrs["value"])) elif name == "strike": current_strike = Strike() for element in content: if isinstance(element, tuple): name, attrs, content = element current_strike.fromXML(name, attrs, content, ttFont) self.strikes[current_strike.ppem] = current_strike else: from fontemon_blender_addon.fontTools import ttLib raise ttLib.TTLibError("can't handle '%s' element" % name)
def compile(self, ttFont): metrics = [] hasNegativeAdvances = False for glyphName in ttFont.getGlyphOrder(): advanceWidth, sideBearing = self.metrics[glyphName] if advanceWidth < 0: log.error("Glyph %r has negative advance %s" % (glyphName, self.advanceName)) hasNegativeAdvances = True metrics.append([advanceWidth, sideBearing]) headerTable = ttFont.get(self.headerTag) if headerTable is not None: lastAdvance = metrics[-1][0] lastIndex = len(metrics) while metrics[lastIndex - 2][0] == lastAdvance: lastIndex -= 1 if lastIndex <= 1: # all advances are equal lastIndex = 1 break additionalMetrics = metrics[lastIndex:] additionalMetrics = [otRound(sb) for _, sb in additionalMetrics] metrics = metrics[:lastIndex] numberOfMetrics = len(metrics) setattr(headerTable, self.numberOfMetricsName, numberOfMetrics) else: # no hhea/vhea, can't store numberOfMetrics; assume == numGlyphs numberOfMetrics = ttFont["maxp"].numGlyphs additionalMetrics = [] allMetrics = [] for advance, sb in metrics: allMetrics.extend([otRound(advance), otRound(sb)]) metricsFmt = ">" + self.longMetricFormat * numberOfMetrics try: data = struct.pack(metricsFmt, *allMetrics) except struct.error as e: if "out of range" in str(e) and hasNegativeAdvances: raise ttLib.TTLibError( "'%s' table can't contain negative advance %ss" % (self.tableTag, self.advanceName)) else: raise additionalMetrics = array.array("h", additionalMetrics) if sys.byteorder != "big": additionalMetrics.byteswap() data = data + additionalMetrics.tobytes() return data
def openTTFonts(path): """Given a pathname, return a list of TTFont objects. In the case of a flat TTF/OTF file, the list will contain just one font object; but in the case of a Mac font suitcase it will contain as many font objects as there are sfnt resources in the file. """ from fontemon_blender_addon.fontTools import ttLib fonts = [] sfnts = getSFNTResIndices(path) if not sfnts: fonts.append(ttLib.TTFont(path)) else: for index in sfnts: fonts.append(ttLib.TTFont(path, index)) if not fonts: raise ttLib.TTLibError("no fonts found in file '%s'" % path) return fonts
def decompile(self, data, ttFont): dummy, data = sstruct.unpack2(OS2_format_0, data, self) if self.version == 1: dummy, data = sstruct.unpack2(OS2_format_1_addition, data, self) elif self.version in (2, 3, 4): dummy, data = sstruct.unpack2(OS2_format_2_addition, data, self) elif self.version == 5: dummy, data = sstruct.unpack2(OS2_format_5_addition, data, self) self.usLowerOpticalPointSize /= 20 self.usUpperOpticalPointSize /= 20 elif self.version != 0: from fontemon_blender_addon.fontTools import ttLib raise ttLib.TTLibError("unknown format for OS/2 table: version %s" % self.version) if len(data): log.warning("too much 'OS/2' table data") self.panose = sstruct.unpack(panoseFormat, self.panose, Panose())
def decompile(self, data, ttFont): numGlyphs = ttFont['maxp'].numGlyphs headerTable = ttFont.get(self.headerTag) if headerTable is not None: numberOfMetrics = int( getattr(headerTable, self.numberOfMetricsName)) else: numberOfMetrics = numGlyphs if numberOfMetrics > numGlyphs: log.warning("The %s.%s exceeds the maxp.numGlyphs" % (self.headerTag, self.numberOfMetricsName)) numberOfMetrics = numGlyphs if len(data) < 4 * numberOfMetrics: raise ttLib.TTLibError("not enough '%s' table data" % self.tableTag) # Note: advanceWidth is unsigned, but some font editors might # read/write as signed. We can't be sure whether it was a mistake # or not, so we read as unsigned but also issue a warning... metricsFmt = ">" + self.longMetricFormat * numberOfMetrics metrics = struct.unpack(metricsFmt, data[:4 * numberOfMetrics]) data = data[4 * numberOfMetrics:] numberOfSideBearings = numGlyphs - numberOfMetrics sideBearings = array.array("h", data[:2 * numberOfSideBearings]) data = data[2 * numberOfSideBearings:] if sys.byteorder != "big": sideBearings.byteswap() if data: log.warning("too much '%s' table data" % self.tableTag) self.metrics = {} glyphOrder = ttFont.getGlyphOrder() for i in range(numberOfMetrics): glyphName = glyphOrder[i] advanceWidth, lsb = metrics[i * 2:i * 2 + 2] if advanceWidth > 32767: log.warning( "Glyph %r has a huge advance %s (%d); is it intentional or " "an (invalid) negative value?", glyphName, self.advanceName, advanceWidth) self.metrics[glyphName] = (advanceWidth, lsb) lastAdvance = metrics[-2] for i in range(numberOfSideBearings): glyphName = glyphOrder[i + numberOfMetrics] self.metrics[glyphName] = (lastAdvance, sideBearings[i])
def compile(self, ttFont): if not (self.version == 0 or self.version == 1): from fontemon_blender_addon.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 fromXML(self, name, attrs, content, ttFont): from fontemon_blender_addon.fontTools.misc.textTools import readHex from fontemon_blender_addon.fontTools import ttLib if name != "hexdata": raise ttLib.TTLibError("can't handle '%s' element" % name) self.decompile(readHex(content), ttFont)
def decompile(self, data, ttFont): totalLength = len(data) indextable = ttFont[self.indextable] for indices, isExtra in zip( (indextable.indices, indextable.extra_indices), (False, True)): programs = {} for i, (glyphID, textLength, textOffset) in enumerate(indices): if isExtra: name = self.extras[glyphID] else: name = ttFont.getGlyphName(glyphID) if textOffset > totalLength: self.log.warning("textOffset > totalLength; %r skipped" % name) continue if textLength < 0x8000: # If the length stored in the record is less than 32768, then use # that as the length of the record. pass elif textLength == 0x8000: # If the length is 32768, compute the actual length as follows: isLast = i == (len(indices) - 1) if isLast: if isExtra: # For the last "extra" record (the very last record of the # table), the length is the difference between the total # length of the TSI1 table and the textOffset of the final # record. nextTextOffset = totalLength else: # For the last "normal" record (the last record just prior # to the record containing the "magic number"), the length # is the difference between the textOffset of the record # following the "magic number" (0xFFFE) record (i.e. the # first "extra" record), and the textOffset of the last # "normal" record. nextTextOffset = indextable.extra_indices[0][2] else: # For all other records with a length of 0x8000, the length is # the difference between the textOffset of the record in # question and the textOffset of the next record. nextTextOffset = indices[i + 1][2] assert nextTextOffset >= textOffset, "entries not sorted by offset" if nextTextOffset > totalLength: self.log.warning( "nextTextOffset > totalLength; %r truncated" % name) nextTextOffset = totalLength textLength = nextTextOffset - textOffset else: from fontemon_blender_addon.fontTools import ttLib raise ttLib.TTLibError( "%r textLength (%d) must not be > 32768" % (name, textLength)) text = data[textOffset:textOffset + textLength] assert len(text) == textLength text = tounicode(text, encoding='utf-8') if text: programs[name] = text if isExtra: self.extraPrograms = programs else: self.glyphPrograms = programs