def setUp(self): self.font = font = ttLib.TTFont(recalcBBoxes=False, recalcTimestamp=False) font['head'] = ttLib.getTableClass('head')() font['maxp'] = ttLib.getTableClass('maxp')() font['loca'] = WOFF2LocaTable() font['glyf'] = WOFF2GlyfTable() for tag in self.transformedTags: font[tag].decompile(self.tables[tag], font)
class WOFF2LocaTable(getTableClass('loca')): """Same as parent class. The only difference is that it attempts to preserve the 'indexFormat' as encoded in the WOFF2 glyf table. """ def __init__(self, tag=None): self.tableTag = Tag(tag or 'loca') def compile(self, ttFont): try: max_location = max(self.locations) except AttributeError: self.set([]) max_location = 0 if 'glyf' in ttFont and hasattr(ttFont['glyf'], 'indexFormat'): # copile loca using the indexFormat specified in the WOFF2 glyf table indexFormat = ttFont['glyf'].indexFormat if indexFormat == 0: if max_location >= 0x20000: raise TTLibError("indexFormat is 0 but local offsets > 0x20000") if not all(l % 2 == 0 for l in self.locations): raise TTLibError("indexFormat is 0 but local offsets not multiples of 2") locations = array.array("H") for i in range(len(self.locations)): locations.append(self.locations[i] // 2) else: locations = array.array("I", self.locations) if sys.byteorder != "big": locations.byteswap() data = locations.tobytes() else: # use the most compact indexFormat given the current glyph offsets data = super(WOFF2LocaTable, self).compile(ttFont) return data
def normalise_table(font, tag, padding=4): """ Return normalised table data. Keep 'font' instance unmodified. """ assert tag in ('glyf', 'loca', 'head') assert tag in font if tag == 'head': origHeadFlags = font['head'].flags font['head'].flags |= (1 << 11) tableData = font['head'].compile(font) if font.sfntVersion in ("\x00\x01\x00\x00", "true"): assert {'glyf', 'loca', 'head'}.issubset(font.keys()) origIndexFormat = font['head'].indexToLocFormat if hasattr(font['loca'], 'locations'): origLocations = font['loca'].locations[:] else: origLocations = [] glyfTable = ttLib.getTableClass('glyf')() glyfTable.decompile(font.getTableData('glyf'), font) glyfTable.padding = padding if tag == 'glyf': tableData = glyfTable.compile(font) elif tag == 'loca': glyfTable.compile(font) tableData = font['loca'].compile(font) if tag == 'head': glyfTable.compile(font) font['loca'].compile(font) tableData = font['head'].compile(font) font['head'].indexToLocFormat = origIndexFormat font['loca'].set(origLocations) if tag == 'head': font['head'].flags = origHeadFlags return tableData
def doit(args): font = args.ifont args.type = args.type.upper() for tag in ('GSUB', 'GPOS'): if tag == args.type or args.type == 'BOTH': table = ttLib.getTableClass(tag)() t = getattr(otTables, tag, None)() t.Version = 1.0 t.ScriptList = otTables.ScriptList() t.ScriptList.ScriptRecord = [] t.FeatureList = otTables.FeatureList() t.FeatureList.FeatureRecord = [] t.LookupList = otTables.LookupList() t.LookupList.Lookup = [] srec = otTables.ScriptRecord() srec.ScriptTag = args.script srec.Script = otTables.Script() srec.Script.DefaultLangSys = None srec.Script.LangSysRecord = [] t.ScriptList.ScriptRecord.append(srec) t.ScriptList.ScriptCount = 1 t.FeatureList.FeatureCount = 0 t.LookupList.LookupCount = 0 table.table = t font[tag] = table return font
def _startElementHandler(self, name, attrs): stackSize = self.stackSize self.stackSize = stackSize + 1 if not stackSize: if name != "ttFont": raise TTXParseError("illegal root tag: %s" % name) sfntVersion = attrs.get("sfntVersion") if sfntVersion is not None: if len(sfntVersion) != 4: sfntVersion = safeEval('"' + sfntVersion + '"') self.ttFont.sfntVersion = sfntVersion self.contentStack.append([]) elif stackSize == 1: subFile = attrs.get("src") if subFile is not None: if hasattr(self.file, 'name'): # if file has a name, get its parent directory dirname = os.path.dirname(self.file.name) else: # else fall back to using the current working directory dirname = os.getcwd() subFile = os.path.join(dirname, subFile) subReader = XMLReader(subFile, self.ttFont, self.progress, self.quiet) subReader.read() self.contentStack.append([]) return tag = ttLib.xmlToTag(name) msg = "Parsing '%s' table..." % tag if self.progress: self.progress.setLabel(msg) elif self.ttFont.verbose: ttLib.debugmsg(msg) else: if not self.quiet: print(msg) if tag == "GlyphOrder": tableClass = ttLib.GlyphOrder elif "ERROR" in attrs or ('raw' in attrs and safeEval(attrs['raw'])): tableClass = DefaultTable else: tableClass = ttLib.getTableClass(tag) if tableClass is None: tableClass = DefaultTable if tag == 'loca' and tag in self.ttFont: # Special-case the 'loca' table as we need the # original if the 'glyf' table isn't recompiled. self.currentTable = self.ttFont[tag] else: self.currentTable = tableClass(tag) self.ttFont[tag] = self.currentTable self.contentStack.append([]) elif stackSize == 2: self.contentStack.append([]) self.root = (name, attrs, self.contentStack[-1]) else: l = [] self.contentStack[-1].append((name, attrs, l)) self.contentStack.append(l)
def add_gsub_to_font(fontfile): """Adds an empty GSUB table to a font.""" font = ttLib.TTFont(fontfile) gsub_table = ttLib.getTableClass('GSUB')('GSUB') gsub_table.table = otTables.GSUB() gsub_table.table.Version = 1.0 gsub_table.table.ScriptList = otTables.ScriptList() gsub_table.table.ScriptCount = 1 gsub_table.table.LookupList = otTables.LookupList() gsub_table.table.LookupList.LookupCount = 0 gsub_table.table.LookupList.Lookup = [] gsub_table.table.FeatureList = otTables.FeatureList() gsub_table.table.FeatureList.FeatureCount = 0 gsub_table.table.LookupList.FeatureRecord = [] script_record = otTables.ScriptRecord() script_record.ScriptTag = get_opentype_script_tag(fontfile) script_record.Script = otTables.Script() script_record.Script.LangSysCount = 0 script_record.Script.LangSysRecord = [] default_lang_sys = otTables.DefaultLangSys() default_lang_sys.FeatureIndex = [] default_lang_sys.FeatureCount = 0 default_lang_sys.LookupOrder = None default_lang_sys.ReqFeatureIndex = 65535 script_record.Script.DefaultLangSys = default_lang_sys gsub_table.table.ScriptList.ScriptRecord = [script_record] font['GSUB'] = gsub_table target_file = tempfile.gettempdir() + '/' + os.path.basename(fontfile) font.save(target_file) return target_file
def makeGDEF(self): gdef = otTables.GDEF() gdef.Version = 1.0 gdef.GlyphClassDef = otTables.GlyphClassDef() gdef.GlyphClassDef.classDefs = {} glyphMarkClass = {} # glyph --> markClass for markClass in self.parseTree.markClasses.values(): for glyph in markClass.anchors.keys(): if glyph in glyphMarkClass: other = glyphMarkClass[glyph] name1, name2 = sorted([markClass.name, other.name]) raise FeatureLibError( 'glyph %s cannot be both in markClass @%s and @%s' % (glyph, name1, name2), markClass.location) glyphMarkClass[glyph] = markClass gdef.GlyphClassDef.classDefs[glyph] = 3 gdef.AttachList = None gdef.LigCaretList = None gdef.MarkAttachClassDef = None if len(gdef.GlyphClassDef.classDefs) == 0: return None result = getTableClass("GDEF")() result.table = gdef return result
def parseGDEF(lines, font): container = ttLib.getTableClass('GDEF')() log.debug("Parsing GDEF") self = ot.GDEF() fields = { 'class definition begin': ('GlyphClassDef', lambda lines, font: parseClassDef(lines, font, klass=ot.GlyphClassDef)), 'attachment list begin': ('AttachList', parseAttachList), 'carets begin': ('LigCaretList', parseCaretList), 'mark attachment class definition begin': ('MarkAttachClassDef', lambda lines, font: parseClassDef(lines, font, klass=ot.MarkAttachClassDef)), 'markfilter set definition begin': ('MarkGlyphSetsDef', parseMarkFilteringSets), } for attr,parser in fields.values(): setattr(self, attr, None) while lines.peek() is not None: typ = lines.peek()[0].lower() if typ not in fields: log.debug('Skipping %s', typ) next(lines) continue attr,parser = fields[typ] assert getattr(self, attr) is None, attr setattr(self, attr, parser(lines, font)) self.Version = 0x00010000 if self.MarkGlyphSetsDef is None else 0x00010002 container.table = self return container
def build_name(self): if not self.names_: return table = self.font.get("name") if not table: # this only happens for unit tests table = self.font["name"] = getTableClass("name")() for name in self.names_: nameID, platformID, platEncID, langID, string = name table.setName(string, nameID, platformID, platEncID, langID)
def merge(self, fontfiles): mega = ttLib.TTFont() # # Settle on a mega glyph order. # fonts = [ttLib.TTFont(fontfile) for fontfile in fontfiles] glyphOrders = [font.getGlyphOrder() for font in fonts] megaGlyphOrder = self._mergeGlyphOrders(glyphOrders) # Reload fonts and set new glyph names on them. # TODO Is it necessary to reload font? I think it is. At least # it's safer, in case tables were loaded to provide glyph names. fonts = [ttLib.TTFont(fontfile) for fontfile in fontfiles] for font, glyphOrder in zip(fonts, glyphOrders): font.setGlyphOrder(glyphOrder) mega.setGlyphOrder(megaGlyphOrder) for font in fonts: self._preMerge(font) self.fonts = fonts self.duplicateGlyphsPerFont = [{} for f in fonts] allTags = reduce(set.union, (list(font.keys()) for font in fonts), set()) allTags.remove('GlyphOrder') # Make sure we process cmap before GSUB as we have a dependency there. if 'GSUB' in allTags: allTags.remove('GSUB') allTags = ['GSUB'] + list(allTags) if 'cmap' in allTags: allTags.remove('cmap') allTags = ['cmap'] + list(allTags) for tag in allTags: with timer("merge '%s'" % tag): tables = [font.get(tag, NotImplemented) for font in fonts] log.info("Merging '%s'.", tag) clazz = ttLib.getTableClass(tag) table = clazz(tag).merge(self, tables) # XXX Clean this up and use: table = mergeObjects(tables) if table is not NotImplemented and table is not False: mega[tag] = table log.info("Merged '%s'.", tag) else: log.info("Dropped '%s'.", tag) del self.duplicateGlyphsPerFont del self.fonts self._postMerge(mega) return mega
def _decompileTable(self, tag): """Decompile table data and store it inside self.ttFont.""" data = self[tag] if self.ttFont.isLoaded(tag): return self.ttFont[tag] tableClass = getTableClass(tag) table = tableClass(tag) self.ttFont.tables[tag] = table table.decompile(data, self.ttFont)
def merge(self, fontfiles): mega = ttLib.TTFont() # # Settle on a mega glyph order. # fonts = [ttLib.TTFont(fontfile) for fontfile in fontfiles] glyphOrders = [font.getGlyphOrder() for font in fonts] megaGlyphOrder = self._mergeGlyphOrders(glyphOrders) # Reload fonts and set new glyph names on them. # TODO Is it necessary to reload font? I think it is. At least # it's safer, in case tables were loaded to provide glyph names. fonts = [ttLib.TTFont(fontfile) for fontfile in fontfiles] for font,glyphOrder in zip(fonts, glyphOrders): font.setGlyphOrder(glyphOrder) mega.setGlyphOrder(megaGlyphOrder) for font in fonts: self._preMerge(font) self.fonts = fonts self.duplicateGlyphsPerFont = [{} for f in fonts] allTags = reduce(set.union, (list(font.keys()) for font in fonts), set()) allTags.remove('GlyphOrder') # Make sure we process cmap before GSUB as we have a dependency there. if 'GSUB' in allTags: allTags.remove('GSUB') allTags = ['GSUB'] + list(allTags) if 'cmap' in allTags: allTags.remove('cmap') allTags = ['cmap'] + list(allTags) for tag in allTags: with timer("merge '%s'" % tag): tables = [font.get(tag, NotImplemented) for font in fonts] log.info("Merging '%s'.", tag) clazz = ttLib.getTableClass(tag) table = clazz(tag).merge(self, tables) # XXX Clean this up and use: table = mergeObjects(tables) if table is not NotImplemented and table is not False: mega[tag] = table log.info("Merged '%s'.", tag) else: log.info("Dropped '%s'.", tag) del self.duplicateGlyphsPerFont del self.fonts self._postMerge(mega) return mega
def build_head(self): if not self.fontRevision_: return table = self.font.get("head") if not table: # this only happens for unit tests table = self.font["head"] = getTableClass("head")() table.decompile(b"\0" * 54, self.font) table.tableVersion = 1.0 table.created = table.modified = 3406620153 # 2011-12-13 11:22:33 table.fontRevision = self.fontRevision_
def inject_meta_into_font(ttf, flatbuffer_bin_filename): """inject metadata binary into font""" if not 'meta' in ttf: ttf['meta'] = ttLib.getTableClass('meta')() meta = ttf['meta'] with open(flatbuffer_bin_filename) as flatbuffer_bin_file: meta.data[EMOJI_META_TAG_NAME] = flatbuffer_bin_file.read() # sort meta tables for faster access update_ttlib_orig_sort()
def create_simple_gsub(lookups, script='DFLT', feature='ccmp'): """Create a simple GSUB table.""" gsub_class = ttLib.getTableClass('GSUB') gsub = gsub_class('GSUB') gsub.table = otTables.GSUB() gsub.table.Version = 1.0 gsub.table.ScriptList = create_script_list(script) gsub.table.FeatureList = create_feature_list(feature, len(lookups)) gsub.table.LookupList = create_lookup_list(lookups) return gsub
def makeGDEF(self): gdef = otTables.GDEF() gdef.Version = 1.0 gdef.GlyphClassDef = otTables.GlyphClassDef() inferredGlyphClass = {} for lookup in self.lookups_: inferredGlyphClass.update(lookup.inferGlyphClasses()) marks = {} # glyph --> markClass for markClass in self.parseTree.markClasses.values(): for markClassDef in markClass.definitions: for glyph in markClassDef.glyphSet(): other = marks.get(glyph) if other not in (None, markClass): name1, name2 = sorted([markClass.name, other.name]) raise FeatureLibError( 'Glyph %s cannot be both in ' 'markClass @%s and @%s' % (glyph, name1, name2), markClassDef.location) marks[glyph] = markClass inferredGlyphClass[glyph] = 3 gdef.GlyphClassDef.classDefs = inferredGlyphClass gdef.AttachList = None gdef.LigCaretList = None markAttachClass = {g: c for g, (c, _) in self.markAttach_.items()} if markAttachClass: gdef.MarkAttachClassDef = otTables.MarkAttachClassDef() gdef.MarkAttachClassDef.classDefs = markAttachClass else: gdef.MarkAttachClassDef = None if self.markFilterSets_: gdef.Version = 0x00010002 m = gdef.MarkGlyphSetsDef = otTables.MarkGlyphSetsDef() m.MarkSetTableFormat = 1 m.MarkSetCount = len(self.markFilterSets_) m.Coverage = [] filterSets = [(id, glyphs) for (glyphs, id) in self.markFilterSets_.items()] for i, glyphs in sorted(filterSets): coverage = otTables.Coverage() coverage.glyphs = sorted(glyphs, key=self.font.getGlyphID) m.Coverage.append(coverage) if (len(gdef.GlyphClassDef.classDefs) == 0 and gdef.MarkAttachClassDef is None): return None result = getTableClass("GDEF")() result.table = gdef return result
def test_getVersion(self): # no version self.assertEqual((0, 0), self.writer._getVersion()) # version from head.fontRevision fontRevision = self.font['head'].fontRevision versionTuple = tuple(int(i) for i in str(fontRevision).split(".")) entry = self.writer.tables['head'] = ttLib.getTableClass('head')() entry.data = self.font.getTableData('head') self.assertEqual(versionTuple, self.writer._getVersion()) # version from writer.flavorData flavorData = self.writer.flavorData = WOFF2FlavorData() flavorData.majorVersion, flavorData.minorVersion = (10, 11) self.assertEqual((10, 11), self.writer._getVersion())
def startElementHandler(self, name, attrs): stackSize = self.stackSize self.stackSize = stackSize + 1 if not stackSize: if name <> "ttFont": raise TTXParseError, "illegal root tag: %s" % name sfntVersion = attrs.get("sfntVersion") if sfntVersion is not None: if len(sfntVersion) <> 4: sfntVersion = safeEval('"' + sfntVersion + '"') self.ttFont.sfntVersion = sfntVersion self.contentStack.append([]) elif stackSize == 1: subFile = attrs.get("src") if subFile is not None: subFile = os.path.join(os.path.dirname(self.fileName), subFile) importXML(self.ttFont, subFile, self.progress) self.contentStack.append([]) return tag = ttLib.xmlToTag(name) msg = "Parsing '%s' table..." % tag if self.progress: self.progress.setlabel(msg) elif self.ttFont.verbose: ttLib.debugmsg(msg) else: if not self.quiet: print msg if tag == "GlyphOrder": tableClass = ttLib.GlyphOrder elif attrs.has_key("ERROR"): tableClass = DefaultTable else: tableClass = ttLib.getTableClass(tag) if tableClass is None: tableClass = DefaultTable if tag == 'loca' and self.ttFont.has_key(tag): # Special-case the 'loca' table as we need the # original if the 'glyf' table isn't recompiled. self.currentTable = self.ttFont[tag] else: self.currentTable = tableClass(tag) self.ttFont[tag] = self.currentTable self.contentStack.append([]) elif stackSize == 2: self.contentStack.append([]) self.root = (name, attrs, self.contentStack[-1]) else: list = [] self.contentStack[-1].append((name, attrs, list)) self.contentStack.append(list)
def parseGSUBGPOS(lines, font, tableTag): container = ttLib.getTableClass(tableTag)() lookupMap = DeferredMapping() featureMap = DeferredMapping() assert tableTag in ('GSUB', 'GPOS') log.debug("Parsing %s", tableTag) self = getattr(ot, tableTag)() self.Version = 0x00010000 fields = { 'script table begin': ('ScriptList', lambda lines: parseScriptList (lines, featureMap)), 'feature table begin': ('FeatureList', lambda lines: parseFeatureList (lines, lookupMap, featureMap)), 'lookup': ('LookupList', None), } for attr,parser in fields.values(): setattr(self, attr, None) while lines.peek() is not None: typ = lines.peek()[0].lower() if typ not in fields: log.debug('Skipping %s', lines.peek()) next(lines) continue attr,parser = fields[typ] if typ == 'lookup': if self.LookupList is None: self.LookupList = ot.LookupList() self.LookupList.Lookup = [] _, name, _ = lines.peek() lookup = parseLookup(lines, tableTag, font, lookupMap) if lookupMap is not None: assert name not in lookupMap, "Duplicate lookup name: %s" % name lookupMap[name] = len(self.LookupList.Lookup) else: assert int(name) == len(self.LookupList.Lookup), "%d %d" % (name, len(self.Lookup)) self.LookupList.Lookup.append(lookup) else: assert getattr(self, attr) is None, attr setattr(self, attr, parser(lines)) if self.LookupList: self.LookupList.LookupCount = len(self.LookupList.Lookup) if lookupMap is not None: lookupMap.applyDeferredMappings() if featureMap is not None: featureMap.applyDeferredMappings() container.table = self return container
def startElementHandler(self, name, attrs): stackSize = self.stackSize self.stackSize = stackSize + 1 if not stackSize: if name <> "ttFont": raise TTXParseError, "illegal root tag: %s" % name sfntVersion = attrs.get("sfntVersion") if sfntVersion is not None: if len(sfntVersion) <> 4: sfntVersion = safeEval('"' + sfntVersion + '"') self.ttFont.sfntVersion = sfntVersion self.contentStack.append([]) elif stackSize == 1: subFile = attrs.get("src") if subFile is not None: subFile = os.path.join(os.path.dirname(self.fileName), subFile) importXML(self.ttFont, subFile, self.progress) self.contentStack.append([]) return tag = ttLib.xmlToTag(name) msg = "Parsing '%s' table..." % tag if self.progress: self.progress.setlabel(msg) elif self.ttFont.verbose: ttLib.debugmsg(msg) else: print msg if tag == "GlyphOrder": tableClass = ttLib.GlyphOrder elif attrs.has_key("ERROR"): tableClass = DefaultTable else: tableClass = ttLib.getTableClass(tag) if tableClass is None: tableClass = DefaultTable if tag == 'loca' and self.ttFont.has_key(tag): # Special-case the 'loca' table as we need the # original if the 'glyf' table isn't recompiled. self.currentTable = self.ttFont[tag] else: self.currentTable = tableClass(tag) self.ttFont[tag] = self.currentTable self.contentStack.append([]) elif stackSize == 2: self.contentStack.append([]) self.root = (name, attrs, self.contentStack[-1]) else: list = [] self.contentStack[-1].append((name, attrs, list)) self.contentStack.append(list)
def buildGDEF(self): gdef = otTables.GDEF() gdef.GlyphClassDef = self.buildGDEFGlyphClassDef_() gdef.AttachList = otl.buildAttachList(self.attachPoints_, self.glyphMap) gdef.LigCaretList = otl.buildLigCaretList(self.ligCaretCoords_, self.ligCaretPoints_, self.glyphMap) gdef.MarkAttachClassDef = self.buildGDEFMarkAttachClassDef_() gdef.MarkGlyphSetsDef = self.buildGDEFMarkGlyphSetsDef_() gdef.Version = 0x00010002 if gdef.MarkGlyphSetsDef else 1.0 if any( (gdef.GlyphClassDef, gdef.AttachList, gdef.LigCaretList, gdef.MarkAttachClassDef, gdef.MarkGlyphSetsDef) ): result = getTableClass("GDEF")() result.table = gdef return result else: return None
def build(self): self.parseTree = Parser(self.featurefile_path).parse() self.parseTree.build(self) for tag in ('GPOS', 'GSUB'): table = self.makeTable(tag) if (table.ScriptList.ScriptCount > 0 or table.FeatureList.FeatureCount > 0 or table.LookupList.LookupCount > 0): fontTable = self.font[tag] = getTableClass(tag)() fontTable.table = table elif tag in self.font: del self.font[tag] gdef = self.makeGDEF() if gdef: self.font["GDEF"] = gdef elif "GDEF" in self.font: del self.font["GDEF"]
def _decompileTable(self, tag): """ Fetch table data, decompile it, and store it inside self.ttFont. """ tag = Tag(tag) if tag not in self.tables: raise TTLibError("missing required table: %s" % tag) if self.ttFont.isLoaded(tag): return data = self.tables[tag].data if tag == 'loca': tableClass = WOFF2LocaTable elif tag == 'glyf': tableClass = WOFF2GlyfTable else: tableClass = getTableClass(tag) table = tableClass(tag) self.ttFont.tables[tag] = table table.decompile(data, self.ttFont)
def parseTable(lines, font, tableTag=None): debug("Parsing table") line = lines.peek() if line[0].split()[0] == "FontDame": next(lines) tag = line[0].split()[1].ljust(4) if tableTag is None: tableTag = tag else: assert tableTag == tag, (tableTag, tag) assert tableTag is not None, "Don't know what table to parse and data doesn't specify" container = ttLib.getTableClass(tableTag)() table = {"GSUB": parseGSUB, "GPOS": parseGPOS, "GDEF": parseGDEF}[tableTag](lines, font) container.table = table return container
def buildGDEF(self): gdef = otTables.GDEF() gdef.GlyphClassDef = self.buildGDEFGlyphClassDef_() gdef.AttachList = \ otl.buildAttachList(self.attachPoints_, self.glyphMap) gdef.LigCaretList = \ otl.buildLigCaretList(self.ligCaretCoords_, self.ligCaretPoints_, self.glyphMap) gdef.MarkAttachClassDef = self.buildGDEFMarkAttachClassDef_() gdef.MarkGlyphSetsDef = self.buildGDEFMarkGlyphSetsDef_() gdef.Version = 0x00010002 if gdef.MarkGlyphSetsDef else 1.0 if any((gdef.GlyphClassDef, gdef.AttachList, gdef.LigCaretList, gdef.MarkAttachClassDef, gdef.MarkGlyphSetsDef)): result = getTableClass("GDEF")() result.table = gdef return result else: return None
def parseTable(lines, font, tableTag=None): log.debug("Parsing table") line = lines.peek() if line[0].split()[0] == 'FontDame': next(lines) tag = line[0].split()[1].ljust(4) if tableTag is None: tableTag = tag else: assert tableTag == tag, (tableTag, tag) assert tableTag is not None, "Don't know what table to parse and data doesn't specify" container = ttLib.getTableClass(tableTag)() table = {'GSUB': parseGSUB, 'GPOS': parseGPOS, 'GDEF': parseGDEF, }[tableTag](lines, font) container.table = table return container
def openOpenTypeFile(path, outFilePath, font_format, options): # If input font is CFF, build a dummy ttFont in memory. if font_format == "OTF": # it is an OTF font, can process file directly ttFont = TTFont(path) if "CFF " not in ttFont: raise ACFontError("Font is not a CFF font <%s>." % path) elif font_format == "CFF": # now package the CFF font as an OTF font. with open(path, "rb") as ff: data = ff.read() ttFont = TTFont() cffClass = getTableClass('CFF ') ttFont['CFF '] = cffClass('CFF ') ttFont['CFF '].decompile(data, ttFont) else: raise ACFontError("Font file must be CFF or OTF file: %s." % path) fontData = CFFFontData(ttFont, path, outFilePath, options.allowDecimalCoords, font_format) return fontData
def merge(self, fontfiles): mega = ttLib.TTFont() # # Settle on a mega glyph order. # fonts = [ttLib.TTFont(fontfile) for fontfile in fontfiles] glyphOrders = [font.getGlyphOrder() for font in fonts] megaGlyphOrder = self._mergeGlyphOrders(glyphOrders) # Reload fonts and set new glyph names on them. # TODO Is it necessary to reload font? I think it is. At least # it's safer, in case tables were loaded to provide glyph names. fonts = [ttLib.TTFont(fontfile) for fontfile in fontfiles] for font,glyphOrder in zip(fonts, glyphOrders): font.setGlyphOrder(glyphOrder) mega.setGlyphOrder(megaGlyphOrder) for font in fonts: self._preMerge(font) allTags = reduce(set.union, (list(font.keys()) for font in fonts), set()) allTags.remove('GlyphOrder') for tag in allTags: clazz = ttLib.getTableClass(tag) tables = [font.get(tag, NotImplemented) for font in fonts] table = clazz(tag).merge(self, tables) if table is not NotImplemented and table is not False: mega[tag] = table self.log("Merged '%s'." % tag) else: self.log("Dropped '%s'." % tag) self.log.lapse("merge '%s'" % tag) self._postMerge(mega) return mega
def parseCmap(lines, font): container = ttLib.getTableClass('cmap')() log.debug("Parsing cmap") tables = [] while lines.peek() is not None: lines.expect('cmap subtable %d' % len(tables)) platId, encId, fmt, lang = [ parseCmapId(lines, field) for field in ('platformID', 'encodingID', 'format', 'language')] table = cmap_classes[fmt](fmt) table.platformID = platId table.platEncID = encId table.language = lang table.cmap = {} line = next(lines) while line[0] != 'end subtable': table.cmap[int(line[0], 16)] = line[1] line = next(lines) tables.append(table) container.tableVersion = 0 container.tables = tables return container
@_add_method(DefaultTable, allowDefaultTable=True) def merge(self, m, tables): if not hasattr(self, 'mergeMap'): log.info("Don't know how to merge '%s'.", self.tableTag) return NotImplemented logic = self.mergeMap if isinstance(logic, dict): return m.mergeObjects(self, self.mergeMap, tables) else: return logic(tables) ttLib.getTableClass('maxp').mergeMap = { '*': max, 'tableTag': equal, 'tableVersion': equal, 'numGlyphs': sum, 'maxStorage': first, 'maxFunctionDefs': first, 'maxInstructionDefs': first, # TODO When we correctly merge hinting data, update these values: # maxFunctionDefs, maxInstructionDefs, maxSizeOfInstructions } headFlagsMergeBitMap = { 'size': 16, '*': bitwise_or, 1: bitwise_and, # Baseline at y = 0
def build(self): parsetree = Parser(self.featurefile_path).parse() parsetree.build(self) for tag in ('GPOS', 'GSUB'): fontTable = self.font[tag] = getTableClass(tag)() fontTable.table = self.makeTable(tag)
from fontTools import ttLib superclass = ttLib.getTableClass("hmtx") class table__v_m_t_x(superclass): headerTag = 'vhea' advanceName = 'height' sideBearingName = 'tsb' numberOfMetricsName = 'numberOfVMetrics'
# Yield sorted, non-overlapping (min, max) ranges of consecutive integers sorted_ints = iter(sorted(set(ints))) try: start = end = next(sorted_ints) except StopIteration: return for v in sorted_ints: if v - 1 == end: end = v else: yield (start, end) start = end = v yield (start, end) @_add_method(ttLib.getTableClass("SVG ")) def subset_glyphs(self, s) -> bool: if etree is None: raise ModuleNotFoundError( "No module named 'lxml', required to subset SVG") # glyph names (before subsetting) glyph_order: List[str] = s.orig_glyph_order # map from glyph names to original glyph indices rev_orig_glyph_map: Dict[str, int] = s.reverseOrigGlyphMap # map from original to new glyph indices (after subsetting) glyph_index_map: Dict[int, int] = s.glyph_index_map new_docs: List[Tuple[bytes, int, int]] = [] for doc, start, end in self.docList:
from fontTools.misc.py23 import * from fontTools.misc.testTools import FakeFont, getXML, parseXML from fontTools.misc.textTools import deHexStr, hexStr from fontTools.ttLib import TTLibError, getTableClass, getTableModule, newTable import unittest from fontTools.ttLib.tables.TupleVariation import TupleVariation gvarClass = getTableClass("gvar") GVAR_DATA = deHexStr( "0001 0000 " # 0: majorVersion=1 minorVersion=0 "0002 0000 " # 4: axisCount=2 sharedTupleCount=0 "0000001C " # 8: offsetToSharedTuples=28 "0003 0000 " # 12: glyphCount=3 flags=0 "0000001C " # 16: offsetToGlyphVariationData=28 "0000 0000 000C 002F " # 20: offsets=[0,0,12,47], times 2: [0,0,24,94], # # +offsetToGlyphVariationData: [28,28,52,122] # # 28: Glyph variation data for glyph #0, ".notdef" # ------------------------------------------------ # (no variation data for this glyph) # # 28: Glyph variation data for glyph #1, "space" # ---------------------------------------------- "8001 000C " # 28: tupleVariationCount=1|TUPLES_SHARE_POINT_NUMBERS, offsetToData=12(+28=40) "000A " # 32: tvHeader[0].variationDataSize=10 "8000 " # 34: tvHeader[0].tupleIndex=EMBEDDED_PEAK "0000 2CCD " # 36: tvHeader[0].peakTuple={wght:0.0, wdth:0.7} "00 " # 40: all points
from fontTools import ttLib superclass = ttLib.getTableClass("fpgm") class table__p_r_e_p(superclass): pass
from __future__ import print_function, division, absolute_import, unicode_literals from fontTools.misc.py23 import * from fontTools.misc.testTools import FakeFont, getXML, parseXML from fontTools.misc.textTools import deHexStr, hexStr from fontTools.ttLib import TTLibError, getTableClass, getTableModule, newTable import unittest from fontTools.ttLib.tables.TupleVariation import TupleVariation gvarClass = getTableClass("gvar") GVAR_DATA = deHexStr( "0001 0000 " # 0: majorVersion=1 minorVersion=0 "0002 0000 " # 4: axisCount=2 sharedTupleCount=0 "0000001C " # 8: offsetToSharedTuples=28 "0003 0000 " # 12: glyphCount=3 flags=0 "0000001C " # 16: offsetToGlyphVariationData=28 "0000 0000 000C 002F " # 20: offsets=[0,0,12,47], times 2: [0,0,24,94], # # +offsetToGlyphVariationData: [28,28,52,122] # # 28: Glyph variation data for glyph #0, ".notdef" # ------------------------------------------------ # (no variation data for this glyph) # # 28: Glyph variation data for glyph #1, "space" # ---------------------------------------------- "0001 000C " # 28: tupleVariationCount=1, offsetToData=12(+28=40) "000B " # 32: tvHeader[0].variationDataSize=11 "A000 " # 34: tvHeader[0].tupleIndex=EMBEDDED_PEAK|PRIVATE_POINTS "0000 2CCD " # 36: tvHeader[0].peakTuple={wght:0.0, wdth:0.7}
@_add_method(DefaultTable, allowDefaultTable=True) def merge(self, m, tables): if not hasattr(self, 'mergeMap'): m.log("Don't know how to merge '%s'." % self.tableTag) return NotImplemented logic = self.mergeMap if isinstance(logic, dict): return m.mergeObjects(self, self.mergeMap, tables) else: return logic(tables) ttLib.getTableClass('maxp').mergeMap = { '*': max, 'tableTag': equal, 'tableVersion': equal, 'numGlyphs': sum, 'maxStorage': first, 'maxFunctionDefs': first, 'maxInstructionDefs': first, # TODO When we correctly merge hinting data, update these values: # maxFunctionDefs, maxInstructionDefs, maxSizeOfInstructions } headFlagsMergeBitMap = { 'size': 16, '*': bitwise_or, 1: bitwise_and, # Baseline at y = 0
from __future__ import print_function, division, absolute_import from fontTools.misc.py23 import * from fontTools import ttLib superclass = ttLib.getTableClass("fpgm") class table__p_r_e_p(superclass): pass
globalSubrs) self.components = components def op_endchar(self, index): args = self.popall() if len(args) >= 4: from fontTools.encodings.StandardEncoding import StandardEncoding # endchar can do seac accent bulding; The T2 spec says it's deprecated, # but recent software that shall remain nameless does output it. adx, ady, bchar, achar = args[-4:] baseGlyph = StandardEncoding[bchar] accentGlyph = StandardEncoding[achar] self.components.add(baseGlyph) self.components.add(accentGlyph) @_add_method(ttLib.getTableClass('CFF ')) def closure_glyphs(self, s): cff = self.cff assert len(cff) == 1 font = cff[cff.keys()[0]] glyphSet = font.CharStrings decompose = s.glyphs while decompose: components = set() for g in decompose: if g not in glyphSet: continue gl = glyphSet[g] subrs = getattr(gl.private, "Subrs", [])
class WOFF2HmtxTable(getTableClass("hmtx")): def __init__(self, tag=None): self.tableTag = Tag(tag or 'hmtx') def reconstruct(self, data, ttFont): flags, = struct.unpack(">B", data[:1]) data = data[1:] if flags & 0b11111100 != 0: raise TTLibError("Bits 2-7 of '%s' flags are reserved" % self.tableTag) # When bit 0 is _not_ set, the lsb[] array is present hasLsbArray = flags & 1 == 0 # When bit 1 is _not_ set, the leftSideBearing[] array is present hasLeftSideBearingArray = flags & 2 == 0 if hasLsbArray and hasLeftSideBearingArray: raise TTLibError( "either bits 0 or 1 (or both) must set in transformed '%s' flags" % self.tableTag ) glyfTable = ttFont["glyf"] headerTable = ttFont["hhea"] glyphOrder = glyfTable.glyphOrder numGlyphs = len(glyphOrder) numberOfHMetrics = min(int(headerTable.numberOfHMetrics), numGlyphs) assert len(data) >= 2 * numberOfHMetrics advanceWidthArray = array.array("H", data[:2 * numberOfHMetrics]) if sys.byteorder != "big": advanceWidthArray.byteswap() data = data[2 * numberOfHMetrics:] if hasLsbArray: assert len(data) >= 2 * numberOfHMetrics lsbArray = array.array("h", data[:2 * numberOfHMetrics]) if sys.byteorder != "big": lsbArray.byteswap() data = data[2 * numberOfHMetrics:] else: # compute (proportional) glyphs' lsb from their xMin lsbArray = array.array("h") for i, glyphName in enumerate(glyphOrder): if i >= numberOfHMetrics: break glyph = glyfTable[glyphName] xMin = getattr(glyph, "xMin", 0) lsbArray.append(xMin) numberOfSideBearings = numGlyphs - numberOfHMetrics if hasLeftSideBearingArray: assert len(data) >= 2 * numberOfSideBearings leftSideBearingArray = array.array("h", data[:2 * numberOfSideBearings]) if sys.byteorder != "big": leftSideBearingArray.byteswap() data = data[2 * numberOfSideBearings:] else: # compute (monospaced) glyphs' leftSideBearing from their xMin leftSideBearingArray = array.array("h") for i, glyphName in enumerate(glyphOrder): if i < numberOfHMetrics: continue glyph = glyfTable[glyphName] xMin = getattr(glyph, "xMin", 0) leftSideBearingArray.append(xMin) if data: raise TTLibError("too much '%s' table data" % self.tableTag) self.metrics = {} for i in range(numberOfHMetrics): glyphName = glyphOrder[i] advanceWidth, lsb = advanceWidthArray[i], lsbArray[i] self.metrics[glyphName] = (advanceWidth, lsb) lastAdvance = advanceWidthArray[-1] for i in range(numberOfSideBearings): glyphName = glyphOrder[i + numberOfHMetrics] self.metrics[glyphName] = (lastAdvance, leftSideBearingArray[i]) def transform(self, ttFont): glyphOrder = ttFont.getGlyphOrder() glyf = ttFont["glyf"] hhea = ttFont["hhea"] numberOfHMetrics = hhea.numberOfHMetrics # check if any of the proportional glyphs has left sidebearings that # differ from their xMin bounding box values. hasLsbArray = False for i in range(numberOfHMetrics): glyphName = glyphOrder[i] lsb = self.metrics[glyphName][1] if lsb != getattr(glyf[glyphName], "xMin", 0): hasLsbArray = True break # do the same for the monospaced glyphs (if any) at the end of hmtx table hasLeftSideBearingArray = False for i in range(numberOfHMetrics, len(glyphOrder)): glyphName = glyphOrder[i] lsb = self.metrics[glyphName][1] if lsb != getattr(glyf[glyphName], "xMin", 0): hasLeftSideBearingArray = True break # if we need to encode both sidebearings arrays, then no transformation is # applicable, and we must use the untransformed hmtx data if hasLsbArray and hasLeftSideBearingArray: return # set bit 0 and 1 when the respective arrays are _not_ present flags = 0 if not hasLsbArray: flags |= 1 << 0 if not hasLeftSideBearingArray: flags |= 1 << 1 data = struct.pack(">B", flags) advanceWidthArray = array.array( "H", [ self.metrics[glyphName][0] for i, glyphName in enumerate(glyphOrder) if i < numberOfHMetrics ] ) if sys.byteorder != "big": advanceWidthArray.byteswap() data += advanceWidthArray.tobytes() if hasLsbArray: lsbArray = array.array( "h", [ self.metrics[glyphName][1] for i, glyphName in enumerate(glyphOrder) if i < numberOfHMetrics ] ) if sys.byteorder != "big": lsbArray.byteswap() data += lsbArray.tobytes() if hasLeftSideBearingArray: leftSideBearingArray = array.array( "h", [ self.metrics[glyphOrder[i]][1] for i in range(numberOfHMetrics, len(glyphOrder)) ] ) if sys.byteorder != "big": leftSideBearingArray.byteswap() data += leftSideBearingArray.tobytes() return data
def setUp(self): self.font = font = ttLib.TTFont(recalcBBoxes=False, recalcTimestamp=False) font['head'] = ttLib.getTableClass('head') font['loca'] = WOFF2LocaTable() font['glyf'] = WOFF2GlyfTable()
@_add_method(DefaultTable, allowDefaultTable=True) def merge(self, m, tables): if not hasattr(self, 'mergeMap'): m.log("Don't know how to merge '%s'." % self.tableTag) return NotImplemented logic = self.mergeMap if isinstance(logic, dict): return m.mergeObjects(self, self.mergeMap, tables) else: return logic(tables) ttLib.getTableClass('maxp').mergeMap = { '*': max, 'tableTag': equal, 'tableVersion': equal, 'numGlyphs': sum, 'maxStorage': first, 'maxFunctionDefs': first, 'maxInstructionDefs': first, # TODO When we correctly merge hinting data, update these values: # maxFunctionDefs, maxInstructionDefs, maxSizeOfInstructions } headFlagsMergeMap = { 'size': 16, '*': bitwise_or, 1: bitwise_and, # Baseline at y = 0
from __future__ import print_function, division, absolute_import from fontTools.misc.py23 import * from fontTools import ttLib superclass = ttLib.getTableClass("TSI0") class table_T_S_I__2(superclass): dependencies = ["TSI3"]
from fontTools import ttLib superclass = ttLib.getTableClass(b"TSI1") class table_T_S_I__3(superclass): extras = {0xfffa: "reserved0", 0xfffb: "reserved1", 0xfffc: "reserved2", 0xfffd: "reserved3"} indextable = b"TSI2"
class WOFF2GlyfTable(getTableClass('glyf')): """Decoder/Encoder for WOFF2 'glyf' table transform.""" subStreams = ('nContourStream', 'nPointsStream', 'flagStream', 'glyphStream', 'compositeStream', 'bboxStream', 'instructionStream') def __init__(self, tag=None): self.tableTag = Tag(tag or 'glyf') def reconstruct(self, data, ttFont): """ Decompile transformed 'glyf' data. """ inputDataSize = len(data) if inputDataSize < woff2GlyfTableFormatSize: raise TTLibError("not enough 'glyf' data") dummy, data = sstruct.unpack2(woff2GlyfTableFormat, data, self) offset = woff2GlyfTableFormatSize for stream in self.subStreams: size = getattr(self, stream + 'Size') setattr(self, stream, data[:size]) data = data[size:] offset += size if offset != inputDataSize: raise TTLibError( "incorrect size of transformed 'glyf' table: expected %d, received %d bytes" % (offset, inputDataSize)) bboxBitmapSize = ((self.numGlyphs + 31) >> 5) << 2 bboxBitmap = self.bboxStream[:bboxBitmapSize] self.bboxBitmap = array.array('B', bboxBitmap) self.bboxStream = self.bboxStream[bboxBitmapSize:] self.nContourStream = array.array("h", self.nContourStream) if sys.byteorder != "big": self.nContourStream.byteswap() assert len(self.nContourStream) == self.numGlyphs if 'head' in ttFont: ttFont['head'].indexToLocFormat = self.indexFormat try: self.glyphOrder = ttFont.getGlyphOrder() except: self.glyphOrder = None if self.glyphOrder is None: self.glyphOrder = [".notdef"] self.glyphOrder.extend( ["glyph%.5d" % i for i in range(1, self.numGlyphs)]) else: if len(self.glyphOrder) != self.numGlyphs: raise TTLibError( "incorrect glyphOrder: expected %d glyphs, found %d" % (len(self.glyphOrder), self.numGlyphs)) glyphs = self.glyphs = {} for glyphID, glyphName in enumerate(self.glyphOrder): glyph = self._decodeGlyph(glyphID) glyphs[glyphName] = glyph def transform(self, ttFont): """ Return transformed 'glyf' data """ self.numGlyphs = len(self.glyphs) if not hasattr(self, "glyphOrder"): try: self.glyphOrder = ttFont.getGlyphOrder() except: self.glyphOrder = None if self.glyphOrder is None: self.glyphOrder = [".notdef"] self.glyphOrder.extend( ["glyph%.5d" % i for i in range(1, self.numGlyphs)]) if len(self.glyphOrder) != self.numGlyphs: raise TTLibError( "incorrect glyphOrder: expected %d glyphs, found %d" % (len(self.glyphOrder), self.numGlyphs)) if 'maxp' in ttFont: ttFont['maxp'].numGlyphs = self.numGlyphs self.indexFormat = ttFont['head'].indexToLocFormat for stream in self.subStreams: setattr(self, stream, b"") bboxBitmapSize = ((self.numGlyphs + 31) >> 5) << 2 self.bboxBitmap = array.array('B', [0] * bboxBitmapSize) for glyphID in range(self.numGlyphs): self._encodeGlyph(glyphID) self.bboxStream = self.bboxBitmap.tostring() + self.bboxStream for stream in self.subStreams: setattr(self, stream + 'Size', len(getattr(self, stream))) self.version = 0 data = sstruct.pack(woff2GlyfTableFormat, self) data += bytesjoin([getattr(self, s) for s in self.subStreams]) return data def _decodeGlyph(self, glyphID): glyph = getTableModule('glyf').Glyph() glyph.numberOfContours = self.nContourStream[glyphID] if glyph.numberOfContours == 0: return glyph elif glyph.isComposite(): self._decodeComponents(glyph) else: self._decodeCoordinates(glyph) self._decodeBBox(glyphID, glyph) return glyph def _decodeComponents(self, glyph): data = self.compositeStream glyph.components = [] more = 1 haveInstructions = 0 while more: component = getTableModule('glyf').GlyphComponent() more, haveInstr, data = component.decompile(data, self) haveInstructions = haveInstructions | haveInstr glyph.components.append(component) self.compositeStream = data if haveInstructions: self._decodeInstructions(glyph) def _decodeCoordinates(self, glyph): data = self.nPointsStream endPtsOfContours = [] endPoint = -1 for i in range(glyph.numberOfContours): ptsOfContour, data = unpack255UShort(data) endPoint += ptsOfContour endPtsOfContours.append(endPoint) glyph.endPtsOfContours = endPtsOfContours self.nPointsStream = data self._decodeTriplets(glyph) self._decodeInstructions(glyph) def _decodeInstructions(self, glyph): glyphStream = self.glyphStream instructionStream = self.instructionStream instructionLength, glyphStream = unpack255UShort(glyphStream) glyph.program = ttProgram.Program() glyph.program.fromBytecode(instructionStream[:instructionLength]) self.glyphStream = glyphStream self.instructionStream = instructionStream[instructionLength:] def _decodeBBox(self, glyphID, glyph): haveBBox = bool(self.bboxBitmap[glyphID >> 3] & (0x80 >> (glyphID & 7))) if glyph.isComposite() and not haveBBox: raise TTLibError('no bbox values for composite glyph %d' % glyphID) if haveBBox: dummy, self.bboxStream = sstruct.unpack2(bboxFormat, self.bboxStream, glyph) else: glyph.recalcBounds(self) def _decodeTriplets(self, glyph): def withSign(flag, baseval): assert 0 <= baseval and baseval < 65536, 'integer overflow' return baseval if flag & 1 else -baseval nPoints = glyph.endPtsOfContours[-1] + 1 flagSize = nPoints if flagSize > len(self.flagStream): raise TTLibError("not enough 'flagStream' data") flagsData = self.flagStream[:flagSize] self.flagStream = self.flagStream[flagSize:] flags = array.array('B', flagsData) triplets = array.array('B', self.glyphStream) nTriplets = len(triplets) assert nPoints <= nTriplets x = 0 y = 0 glyph.coordinates = getTableModule('glyf').GlyphCoordinates.zeros( nPoints) glyph.flags = array.array("B") tripletIndex = 0 for i in range(nPoints): flag = flags[i] onCurve = not bool(flag >> 7) flag &= 0x7f if flag < 84: nBytes = 1 elif flag < 120: nBytes = 2 elif flag < 124: nBytes = 3 else: nBytes = 4 assert ((tripletIndex + nBytes) <= nTriplets) if flag < 10: dx = 0 dy = withSign(flag, ((flag & 14) << 7) + triplets[tripletIndex]) elif flag < 20: dx = withSign(flag, (((flag - 10) & 14) << 7) + triplets[tripletIndex]) dy = 0 elif flag < 84: b0 = flag - 20 b1 = triplets[tripletIndex] dx = withSign(flag, 1 + (b0 & 0x30) + (b1 >> 4)) dy = withSign(flag >> 1, 1 + ((b0 & 0x0c) << 2) + (b1 & 0x0f)) elif flag < 120: b0 = flag - 84 dx = withSign(flag, 1 + ((b0 // 12) << 8) + triplets[tripletIndex]) dy = withSign( flag >> 1, 1 + (((b0 % 12) >> 2) << 8) + triplets[tripletIndex + 1]) elif flag < 124: b2 = triplets[tripletIndex + 1] dx = withSign(flag, (triplets[tripletIndex] << 4) + (b2 >> 4)) dy = withSign(flag >> 1, ((b2 & 0x0f) << 8) + triplets[tripletIndex + 2]) else: dx = withSign(flag, (triplets[tripletIndex] << 8) + triplets[tripletIndex + 1]) dy = withSign(flag >> 1, (triplets[tripletIndex + 2] << 8) + triplets[tripletIndex + 3]) tripletIndex += nBytes x += dx y += dy glyph.coordinates[i] = (x, y) glyph.flags.append(int(onCurve)) bytesConsumed = tripletIndex self.glyphStream = self.glyphStream[bytesConsumed:] def _encodeGlyph(self, glyphID): glyphName = self.getGlyphName(glyphID) glyph = self[glyphName] self.nContourStream += struct.pack(">h", glyph.numberOfContours) if glyph.numberOfContours == 0: return elif glyph.isComposite(): self._encodeComponents(glyph) else: self._encodeCoordinates(glyph) self._encodeBBox(glyphID, glyph) def _encodeComponents(self, glyph): lastcomponent = len(glyph.components) - 1 more = 1 haveInstructions = 0 for i in range(len(glyph.components)): if i == lastcomponent: haveInstructions = hasattr(glyph, "program") more = 0 component = glyph.components[i] self.compositeStream += component.compile(more, haveInstructions, self) if haveInstructions: self._encodeInstructions(glyph) def _encodeCoordinates(self, glyph): lastEndPoint = -1 for endPoint in glyph.endPtsOfContours: ptsOfContour = endPoint - lastEndPoint self.nPointsStream += pack255UShort(ptsOfContour) lastEndPoint = endPoint self._encodeTriplets(glyph) self._encodeInstructions(glyph) def _encodeInstructions(self, glyph): instructions = glyph.program.getBytecode() self.glyphStream += pack255UShort(len(instructions)) self.instructionStream += instructions def _encodeBBox(self, glyphID, glyph): assert glyph.numberOfContours != 0, "empty glyph has no bbox" if not glyph.isComposite(): # for simple glyphs, compare the encoded bounding box info with the calculated # values, and if they match omit the bounding box info currentBBox = glyph.xMin, glyph.yMin, glyph.xMax, glyph.yMax calculatedBBox = calcIntBounds(glyph.coordinates) if currentBBox == calculatedBBox: return self.bboxBitmap[glyphID >> 3] |= 0x80 >> (glyphID & 7) self.bboxStream += sstruct.pack(bboxFormat, glyph) def _encodeTriplets(self, glyph): assert len(glyph.coordinates) == len(glyph.flags) coordinates = glyph.coordinates.copy() coordinates.absoluteToRelative() flags = array.array('B') triplets = array.array('B') for i in range(len(coordinates)): onCurve = glyph.flags[i] x, y = coordinates[i] absX = abs(x) absY = abs(y) onCurveBit = 0 if onCurve else 128 xSignBit = 0 if (x < 0) else 1 ySignBit = 0 if (y < 0) else 1 xySignBits = xSignBit + 2 * ySignBit if x == 0 and absY < 1280: flags.append(onCurveBit + ((absY & 0xf00) >> 7) + ySignBit) triplets.append(absY & 0xff) elif y == 0 and absX < 1280: flags.append(onCurveBit + 10 + ((absX & 0xf00) >> 7) + xSignBit) triplets.append(absX & 0xff) elif absX < 65 and absY < 65: flags.append(onCurveBit + 20 + ((absX - 1) & 0x30) + (((absY - 1) & 0x30) >> 2) + xySignBits) triplets.append((((absX - 1) & 0xf) << 4) | ((absY - 1) & 0xf)) elif absX < 769 and absY < 769: flags.append(onCurveBit + 84 + 12 * (((absX - 1) & 0x300) >> 8) + (((absY - 1) & 0x300) >> 6) + xySignBits) triplets.append((absX - 1) & 0xff) triplets.append((absY - 1) & 0xff) elif absX < 4096 and absY < 4096: flags.append(onCurveBit + 120 + xySignBits) triplets.append(absX >> 4) triplets.append(((absX & 0xf) << 4) | (absY >> 8)) triplets.append(absY & 0xff) else: flags.append(onCurveBit + 124 + xySignBits) triplets.append(absX >> 8) triplets.append(absX & 0xff) triplets.append(absY >> 8) triplets.append(absY & 0xff) self.flagStream += flags.tostring() self.glyphStream += triplets.tostring()