def makeFont(self, variations): glyphs = [".notdef", "space", "I"] Axis = getTableModule("fvar").Axis Glyph = getTableModule("glyf").Glyph glyf, fvar, gvar = newTable("glyf"), newTable("fvar"), newTable("gvar") font = FakeFont(glyphs) font.tables = {"glyf": glyf, "gvar": gvar, "fvar": fvar} glyf.glyphs = {glyph: Glyph() for glyph in glyphs} glyf.glyphs["I"].coordinates = [(10, 10), (10, 20), (20, 20), (20, 10)] fvar.axes = [Axis(), Axis()] fvar.axes[0].axisTag, fvar.axes[1].axisTag = "wght", "wdth" gvar.variations = variations return font, gvar
def makeFont(self, variations): glyphs=[".notdef", "space", "I"] Axis = getTableModule("fvar").Axis Glyph = getTableModule("glyf").Glyph glyf, fvar, gvar = newTable("glyf"), newTable("fvar"), newTable("gvar") font = FakeFont(glyphs) font.tables = {"glyf": glyf, "gvar": gvar, "fvar": fvar} glyf.glyphs = {glyph: Glyph() for glyph in glyphs} glyf.glyphs["I"].coordinates = [(10, 10), (10, 20), (20, 20), (20, 10)] fvar.axes = [Axis(), Axis()] fvar.axes[0].axisTag, fvar.axes[1].axisTag = "wght", "wdth" gvar.variations = variations return font, gvar
def merge(self, m, tables): # TODO Handle format=14. # Only merges 4/3/1 and 12/3/10 subtables, ignores all other subtables # If there is a format 12 table for the same font, ignore the format 4 table cmapTables = [] for fontIdx,table in enumerate(tables): format4 = None format12 = None for subtable in table.tables: properties = (subtable.format, subtable.platformID, subtable.platEncID) if properties == (4,3,1): format4 = subtable elif properties == (12,3,10): format12 = subtable if format12 is not None: cmapTables.append((format12, fontIdx)) elif format4 is not None: cmapTables.append((format4, fontIdx)) # Build a unicode mapping, then decide which format is needed to store it. cmap = {} for table,fontIdx in cmapTables: # handle duplicates for uni,gid in table.cmap.items(): oldgid = cmap.get(uni, None) if oldgid is None: cmap[uni] = gid elif oldgid != gid: # Char previously mapped to oldgid, now to gid. # Record, to fix up in GSUB 'locl' later. if m.duplicateGlyphsPerFont[fontIdx].get(oldgid, gid) == gid: m.duplicateGlyphsPerFont[fontIdx][oldgid] = gid else: # char previously mapped to oldgid but already remapped to a different gid, # save new gid as an alternate # TODO: try harder to save these log.warn("Dropped mapping from codepoint %#06X to glyphId '%s'", uni, gid) cmapBmpOnly = {uni: gid for uni,gid in cmap.items() if uni <= 0xFFFF} self.tables = [] module = ttLib.getTableModule('cmap') if len(cmapBmpOnly) != len(cmap): # format-12 required. cmapTable = module.cmap_classes[12](12) cmapTable.platformID = 3 cmapTable.platEncID = 10 cmapTable.language = 0 cmapTable.cmap = cmap self.tables.append(cmapTable) # always create format-4 cmapTable = module.cmap_classes[4](4) cmapTable.platformID = 3 cmapTable.platEncID = 1 cmapTable.language = 0 cmapTable.cmap = cmapBmpOnly # ordered by platform then encoding self.tables.insert(0, cmapTable) self.tableVersion = 0 self.numSubTables = len(self.tables) return self
def _add_object(self, key, value): # Make sure item is decompiled try: value["asdf"] except (AttributeError, KeyError, TypeError, ttLib.TTLibError): pass if isinstance(value, ttLib.getTableModule('glyf').Glyph): # Glyph type needs explicit expanding to be useful value.expand(self._font['glyf']) if isinstance(value, misc.psCharStrings.T2CharString): try: value.decompile() except TypeError: # Subroutines can't be decompiled pass if isinstance(value, cffLib.BaseDict): for k in value.rawDict.keys(): getattr(value, k) if isinstance(value, cffLib.Index): # Load all items for i in range(len(value)): value[i] # Discard offsets as should not be needed anymore if hasattr(value, 'offsets'): del value.offsets self._value_str = value.__class__.__name__ if isinstance(value, ttLib.tables.DefaultTable.DefaultTable): self._value_str += ' (%d Bytes)' % self._font.reader.tables[key].length self._items = sorted(value.__dict__.items()) self._filter_items()
def fix(self): unencoded_glyphs = get_unencoded_glyphs(self.font) if not unencoded_glyphs: return ucs2cmap = None cmap = self.font["cmap"] # Check if an UCS-2 cmap exists for ucs2cmapid in ((3, 1), (0, 3), (3, 0)): ucs2cmap = cmap.getcmap(ucs2cmapid[0], ucs2cmapid[1]) if ucs2cmap: break # Create UCS-4 cmap and copy the contents of UCS-2 cmap # unless UCS 4 cmap already exists ucs4cmap = cmap.getcmap(3, 10) if not ucs4cmap: cmapModule = ttLib.getTableModule('cmap') ucs4cmap = cmapModule.cmap_format_12(12) ucs4cmap.platformID = 3 ucs4cmap.platEncID = 10 ucs4cmap.language = 0 if ucs2cmap: ucs4cmap.cmap = copy.deepcopy(ucs2cmap.cmap) cmap.tables.append(ucs4cmap) # Map all glyphs to UCS-4 cmap Supplementary PUA-A codepoints # by 0xF0000 + glyphID ucs4cmap = cmap.getcmap(3, 10) for glyphID, glyph in enumerate(self.font.getGlyphOrder()): if glyph in unencoded_glyphs: ucs4cmap.cmap[0xF0000 + glyphID] = glyph self.font['cmap'] = cmap return True
def addGlyph(self, uchar, glyph): # Add to glyph list glyphOrder = self.font.getGlyphOrder() # assert glyph not in glyphOrder glyphOrder.append(glyph) self.font.setGlyphOrder(glyphOrder) # Add horizontal metrics (to zero) self.font['hmtx'][glyph] = [0, 0] # Add to cmap for table in self.font['cmap'].tables: if not (table.platformID == 3 and table.platEncID in [1, 10]): continue if not table.cmap: # Skip UVS cmaps continue assert uchar not in table.cmap table.cmap[uchar] = glyph # Add empty glyph outline if 'glyf' in self.font: self.font['glyf'].glyphs[glyph] = ttLib.getTableModule('glyf').Glyph() else: cff = self.font['CFF '].cff addCFFGlyph( glyphName=glyph, private=cff.topDictIndex[0].topDict.Private, globalSubrs=cff.GlobalSubrs, charStringsIndex=cff.topDictIndex[0].topDict.CharStrings.charStrings.charStringsIndex, topDict=cff.topDictIndex[0], charStrings=cff.topDictIndex[0].CharStrings ) return glyph
def merge(self, m, tables): # TODO Handle format=14. if not hasattr(m, 'cmap'): computeMegaCmap(m, tables) cmap = m.cmap cmapBmpOnly = {uni: gid for uni, gid in cmap.items() if uni <= 0xFFFF} self.tables = [] module = ttLib.getTableModule('cmap') if len(cmapBmpOnly) != len(cmap): # format-12 required. cmapTable = module.cmap_classes[12](12) cmapTable.platformID = 3 cmapTable.platEncID = 10 cmapTable.language = 0 cmapTable.cmap = cmap self.tables.append(cmapTable) # always create format-4 cmapTable = module.cmap_classes[4](4) cmapTable.platformID = 3 cmapTable.platEncID = 1 cmapTable.language = 0 cmapTable.cmap = cmapBmpOnly # ordered by platform then encoding self.tables.insert(0, cmapTable) self.tableVersion = 0 self.numSubTables = len(self.tables) return self
def merge(self, m, tables): # TODO Handle format=14. cmapTables = [(t,fontIdx) for fontIdx,table in enumerate(tables) for t in table.tables if t.isUnicode()] # TODO Better handle format-4 and format-12 coexisting in same font. # TODO Insert both a format-4 and format-12 if needed. module = ttLib.getTableModule('cmap') assert all(t.format in [4, 12] for t,_ in cmapTables) format = max(t.format for t,_ in cmapTables) cmapTable = module.cmap_classes[format](format) cmapTable.cmap = {} cmapTable.platformID = 3 cmapTable.platEncID = max(t.platEncID for t,_ in cmapTables) cmapTable.language = 0 cmap = cmapTable.cmap for table,fontIdx in cmapTables: # TODO handle duplicates. for uni,gid in table.cmap.items(): oldgid = cmap.get(uni, None) if oldgid is None: cmap[uni] = gid elif oldgid != gid: # Char previously mapped to oldgid, now to gid. # Record, to fix up in GSUB 'locl' later. assert m.duplicateGlyphsPerFont[fontIdx].get(oldgid, gid) == gid m.duplicateGlyphsPerFont[fontIdx][oldgid] = gid self.tableVersion = 0 self.tables = [cmapTable] self.numSubTables = len(self.tables) return self
def makeFont(self): cvt, cvar, fvar = newTable("cvt "), newTable("cvar"), newTable("fvar") font = {"cvt ": cvt, "cvar": cvar, "fvar": fvar} cvt.values = [0, 0, 0, 1000, -2000] Axis = getTableModule("fvar").Axis fvar.axes = [Axis(), Axis()] fvar.axes[0].axisTag, fvar.axes[1].axisTag = "wght", "wdth" return font, cvar
def makeSubtable(self, format, platformID, platEncID, cmap): module = ttLib.getTableModule('cmap') subtable = module.cmap_classes[format](format) (subtable.platformID, subtable.platEncID, subtable.language, subtable.cmap) = (platformID, platEncID, 0, cmap) return subtable
def makeFont(self): cvt, cvar, fvar = newTable("cvt "), newTable("cvar"), newTable("fvar") font = {"cvt ": cvt, "cvar": cvar, "fvar": fvar} cvt.values = [0, 1000, -2000] Axis = getTableModule("fvar").Axis fvar.axes = [Axis(), Axis()] fvar.axes[0].axisTag, fvar.axes[1].axisTag = "wght", "wdth" return font, cvar
def openOpenTypeFile(path, outFilePath, options): from psautohint.BezTools import CFFFontData from fontTools.ttLib import TTFont, getTableModule # If input font is CFF, build a dummy ttFont in memory. # Return ttFont, and flag if is a real OTF font. # Return flag is 0 if OTF, and 1 if CFF. fontType = 0 # OTF try: ff = open(path, "rb") data = ff.read(10) ff.close() except (IOError, OSError): logMsg("Failed to open and read font file %s." % path) if data[:4] == b"OTTO": # it is an OTF font, can process file directly try: ttFont = TTFont(path) except (IOError, OSError): raise ACFontError("Error opening or reading from font file <%s>." % path) except TTLibError: raise ACFontError("Error parsing font file <%s>." % path) try: cffTable = ttFont["CFF "] except KeyError: raise ACFontError("Error: font is not a CFF font <%s>." % fontFileName) else: # It is not an OTF file. if (data[0] == b'\1') and (data[1] == b'\0'): # CFF file fontType = 1 else: logMsg("Font file must be a CFF or OTF fontfile: %s." % path) raise ACFontError("Font file must be CFF or OTF file: %s." % path) # now package the CFF font as an OTF font. ff = open(path, "rb") data = ff.read() ff.close() try: ttFont = TTFont() cffModule = getTableModule('CFF ') cffTable = cffModule.table_C_F_F_('CFF ') ttFont['CFF '] = cffTable cffTable.decompile(data, ttFont) except: logMsg("\t%s" % (traceback.format_exception_only(sys.exc_info()[0], sys.exc_info()[1])[-1])) logMsg("Attempted to read font %s as CFF." % path) raise ACFontError("Error parsing font file <%s>." % path) fontData = CFFFontData(ttFont, path, outFilePath, fontType, logMsg) return fontData
def colorize(font): COLR = newTable("COLR") CPAL = newTable("CPAL") glyf = font["glyf"] hmtx = font["hmtx"] CPAL.version = 0 COLR.version = 0 palette = list(GROUPS.values()) CPAL.palettes = [palette] CPAL.numPaletteEntries = len(palette) COLR.ColorLayers = {} glyphOrder = list(font.getGlyphOrder()) for name in glyphOrder: glyph = glyf[name] layers = [] if glyph.isComposite() and len(glyph.components) > 1: componentColors = [getGlyphColor(c.getComponentInfo()[0]) for c in glyph.components] if any(componentColors): for component in glyph.components: componentName, trans = component.getComponentInfo() componentColor = getGlyphColor(componentName) if componentColor is None: componentColor = 0xFFFF else: componentColor = palette.index(componentColor) if trans == (1, 0, 0, 1, 0, 0): layers.append(newLayer(componentName, componentColor)) else: newName = "%s.%s" % (componentName, hash(trans)) if newName not in font.glyphOrder: font.glyphOrder.append(newName) newGlyph = getTableModule("glyf").Glyph() newGlyph.numberOfContours = -1 newGlyph.components = [component] glyf.glyphs[newName] = newGlyph assert len(glyf.glyphs) == len(font.glyphOrder), (name, newName) width = hmtx[name][0] lsb = hmtx[componentName][1] + trans[4] hmtx.metrics[newName] = [width, lsb] layers.append(newLayer(newName, componentColor)) if not layers: color = getGlyphColor(name) if color is not None: layers = [newLayer(name, palette.index(color))] if layers: COLR[name] = layers font["COLR"] = COLR font["CPAL"] = CPAL
def openOpenTypeFile(path, outFilePath): # If input font is CFF or PS, build a dummy ttFont in memory.. # return ttFont, and flag if is a real OTF font Return flag is 0 if OTF, 1 if CFF, and 2 if PS/ fontType = 0 # OTF tempPathCFF = fdkutils.get_temp_file_path() try: with open(path, "rb") as ff: head = ff.read(4) except (IOError, OSError): logMsg("Failed to open and read font file %s." % path) if head == b"OTTO": # it is an OTF font, can process file directly try: ttFont = TTFont(path) except (IOError, OSError): raise ACFontError("Error opening or reading from font file <%s>." % path) except TTLibError: raise ACFontError("Error parsing font file <%s>." % path) try: cffTable = ttFont["CFF "] except KeyError: raise ACFontError("Error: font is not a CFF font <%s>." % fontFileName) else: # It is not an OTF file. if head[0:2] == b'\x01\x00': # CFF file fontType = 1 tempPathCFF = path else: # It is a PS file. Convert to CFF. fontType = 2 print("Converting Type1 font to temp CFF font file...") command="tx -cff +b -std \"%s\" \"%s\" 2>&1" % (path, tempPathCFF) report = fdkutils.runShellCmd(command) if "fatal" in report: logMsg("Attempted to convert font %s from PS to a temporary CFF data file." % path) logMsg(report) raise ACFontError("Failed to convert PS font %s to a temp CFF font." % path) # now package the CFF font as an OTF font. with open(tempPathCFF, "rb") as ff: data = ff.read() try: ttFont = TTFont() cffModule = getTableModule('CFF ') cffTable = cffModule.table_C_F_F_('CFF ') ttFont['CFF '] = cffTable cffTable.decompile(data, ttFont) except: logMsg( "\t%s" %(traceback.format_exception_only(sys.exc_info()[0], sys.exc_info()[1])[-1])) logMsg("Attempted to read font %s as CFF." % path) raise ACFontError("Error parsing font file <%s>." % path) fontData = CFFFontData(ttFont, path, outFilePath, fontType, logMsg) return fontData
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 makeGlyfBBox2(bbox): font = getTTFont(sfntTTFSourcePath, recalcBBoxes=False) glyf = font["glyf"] hmtx = font["hmtx"] name = "bbox1" glyph = getTableModule('glyf').Glyph() glyph.numberOfContours = 0 glyph.xMin = glyph.xMax = glyph.yMin = glyph.yMax = bbox glyph.data = sstruct.pack(getTableModule('glyf').glyphHeaderFormat, glyph) glyf.glyphs[name] = glyph hmtx.metrics[name] = (0, 0) glyf.glyphOrder.append(name) tableData = getSFNTData(font)[0] font.close() del font header, directory, tableData = defaultSFNTTestData(tableData=tableData, flavor="TTF") data = packSFNT(header, directory, tableData, flavor="TTF") return data
def assemble_components(comps_data): """ Assemble and return a list of GlyphComponent objects. """ components = [] for i, cname in enumerate(comps_data.names): component = getTableModule('glyf').GlyphComponent() component.glyphName = cname component.x, component.y = comps_data.positions[i] component.flags = 0x204 if i == 0 else 0x4 components.append(component) return components
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 assemble_components(comps_data): """ Assemble and return a list of GlyphComponent objects. """ components = [] for i, cname in enumerate(comps_data.names): component = getTableModule('glyf').GlyphComponent() component.glyphName = cname component.x, component.y = comps_data.positions[i] component.flags = 0x4 if i == 0 and comps_data.same_advwidth: component.flags = 0x204 components.append(component) return components
def test_compile_v0_sharingColors(self): cpal = newTable('CPAL') cpal.version = 0 Color = getTableModule('CPAL').Color palette1 = [Color(red=0x22, green=0x33, blue=0x44, alpha=0xff), Color(red=0x99, green=0x88, blue=0x77, alpha=0x11), Color(red=0x55, green=0x55, blue=0x55, alpha=0x55)] palette2 = [Color(red=0x22, green=0x33, blue=0x44, alpha=0xff), Color(red=0x99, green=0x88, blue=0x77, alpha=0x11), Color(red=0xFF, green=0xFF, blue=0xFF, alpha=0xFF)] cpal.numPaletteEntries = len(palette1) cpal.palettes = [palette1, palette1, palette2, palette1] self.assertEqual(cpal.compile(ttFont=None), CPAL_DATA_V0_SHARING_COLORS)
def makeTempOTF(srcPath): ff = file(srcPath, "rb") data = ff.read() ff.close() try: ttFont = TTFont() cffModule = getTableModule('CFF ') cffTable = cffModule.table_C_F_F_('CFF ') ttFont['CFF '] = cffTable cffTable.decompile(data, ttFont) except: print "\t%s" %(traceback.format_exception_only(sys.exc_type, sys.exc_value)[-1]) print "Attempted to read font %s as CFF." % filePath raise LocalError("Error parsing font file <%s>." % filePath) return ttFont
def makeTempOTF(srcPath): ff = file(srcPath, "rb") data = ff.read() ff.close() try: ttFont = TTFont() cffModule = getTableModule('CFF ') cffTable = cffModule.table_C_F_F_('CFF ') ttFont['CFF '] = cffTable cffTable.decompile(data, ttFont) except: print "\t%s" % (traceback.format_exception_only( sys.exc_type, sys.exc_value)[-1]) print "Attempted to read font %s as CFF." % filePath raise LocalError("Error parsing font file <%s>." % filePath) return ttFont
def test_recalcUnicodeRanges(self): font = TTFont() font['OS/2'] = os2 = newTable('OS/2') font['cmap'] = cmap = newTable('cmap') st = getTableModule('cmap').CmapSubtable.newSubtable(4) st.platformID, st.platEncID, st.language = 3, 1, 0 st.cmap = {0x0041:'A', 0x03B1: 'alpha', 0x0410: 'Acyr'} cmap.tables = [] cmap.tables.append(st) os2.setUnicodeRanges({0, 1, 9}) # 'pruneOnly' will clear any bits for which there's no intersection: # bit 1 ('Latin 1 Supplement'), in this case. However, it won't set # bit 7 ('Greek and Coptic') despite the "alpha" character is present. self.assertEqual(os2.recalcUnicodeRanges(font, pruneOnly=True), {0, 9}) # try again with pruneOnly=False: bit 7 is now set. self.assertEqual(os2.recalcUnicodeRanges(font), {0, 7, 9}) # add a non-BMP char from 'Mahjong Tiles' block (bit 122) st.cmap[0x1F000] = 'eastwindtile' # the bit 122 and the special bit 57 ('Non Plane 0') are also enabled self.assertEqual(os2.recalcUnicodeRanges(font), {0, 7, 9, 57, 122})
def test_recalcUnicodeRanges(self): font = TTFont() font['OS/2'] = os2 = newTable('OS/2') font['cmap'] = cmap = newTable('cmap') st = getTableModule('cmap').CmapSubtable.newSubtable(4) st.platformID, st.platEncID, st.language = 3, 1, 0 st.cmap = {0x0041: 'A', 0x03B1: 'alpha', 0x0410: 'Acyr'} cmap.tables = [] cmap.tables.append(st) os2.setUnicodeRanges({0, 1, 9}) # 'pruneOnly' will clear any bits for which there's no intersection: # bit 1 ('Latin 1 Supplement'), in this case. However, it won't set # bit 7 ('Greek and Coptic') despite the "alpha" character is present. self.assertEqual(os2.recalcUnicodeRanges(font, pruneOnly=True), {0, 9}) # try again with pruneOnly=False: bit 7 is now set. self.assertEqual(os2.recalcUnicodeRanges(font), {0, 7, 9}) # add a non-BMP char from 'Mahjong Tiles' block (bit 122) st.cmap[0x1F000] = 'eastwindtile' # the bit 122 and the special bit 57 ('Non Plane 0') are also enabled self.assertEqual(os2.recalcUnicodeRanges(font), {0, 7, 9, 57, 122})
def merge(self, m, tables): # TODO Handle format=14. cmapTables = [t for table in tables for t in table.tables if t.isUnicode()] # TODO Better handle format-4 and format-12 coexisting in same font. # TODO Insert both a format-4 and format-12 if needed. module = ttLib.getTableModule('cmap') assert all(t.format in [4, 12] for t in cmapTables) format = max(t.format for t in cmapTables) cmapTable = module.cmap_classes[format](format) cmapTable.cmap = {} cmapTable.platformID = 3 cmapTable.platEncID = max(t.platEncID for t in cmapTables) cmapTable.language = 0 for table in cmapTables: # TODO handle duplicates. cmapTable.cmap.update(table.cmap) self.tableVersion = 0 self.tables = [cmapTable] self.numSubTables = len(self.tables) return self
def addGlyph(font, uchar, glyphName): # Add to glyph list glyphOrder = font.getGlyphOrder() assert glyphName not in glyphOrder glyphOrder.append(glyphName) font.setGlyphOrder(glyphOrder) # Add horizontal metrics (to zero) font['hmtx'][glyphName] = [0, 0] # Add to cmap for table in font['cmap'].tables: if not (table.platformID == 3 and table.platEncID in [1, 10]): continue if not table.cmap: # Skip UVS cmaps continue assert uchar not in table.cmap table.cmap[uchar] = glyphName # Add empty glyph outline font['glyf'].glyphs[glyphName] = ttLib.getTableModule('glyf').Glyph() return glyphName
def addGlyph(self, uchar, glyph): # Add to glyph list glyphOrder = self.font.getGlyphOrder() # assert glyph not in glyphOrder glyphOrder.append(glyph) self.font.setGlyphOrder(glyphOrder) # Add horizontal metrics (to zero) self.font['hmtx'][glyph] = [0, 0] # Add to cmap for table in self.font['cmap'].tables: if not (table.platformID == 3 and table.platEncID in [1, 10]): continue if not table.cmap: # Skip UVS cmaps continue assert uchar not in table.cmap table.cmap[uchar] = glyph # Add empty glyph outline if 'glyf' in self.font: self.font['glyf'].glyphs[glyph] = ttLib.getTableModule( 'glyf').Glyph() else: cff = self.font['CFF '].cff self.addCFFGlyph( glyphName=glyph, private=cff.topDictIndex[0].Private, globalSubrs=cff.GlobalSubrs, charStringsIndex=cff.topDictIndex[0].CharStrings. charStringsIndex, # charStringsIndex=cff.topDictIndex[0].CharStrings.charStrings.charStringsIndex, topDict=cff.topDictIndex[0], charStrings=cff.topDictIndex[0].CharStrings) import ipdb ipdb.set_trace() return glyph
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 newLayer(name, colorID): return getTableModule("COLR").LayerRecord(name=name, colorID=colorID)
def merge(self, m, tables): # TODO Handle format=14. # Only merges 4/3/1 and 12/3/10 subtables, ignores all other subtables # If there is a format 12 table for the same font, ignore the format 4 table cmapTables = [] for fontIdx,table in enumerate(tables): format4 = None format12 = None for subtable in table.tables: properties = (subtable.format, subtable.platformID, subtable.platEncID) if properties == (4,3,1): format4 = subtable elif properties == (12,3,10): format12 = subtable if format12 is not None: cmapTables.append((format12, fontIdx)) elif format4 is not None: cmapTables.append((format4, fontIdx)) # Build a unicode mapping, then decide which format is needed to store it. cmap = {} fontIndexForGlyph = {} glyphSets = [None for f in m.fonts] if hasattr(m, 'fonts') else None for table,fontIdx in cmapTables: # handle duplicates for uni,gid in table.cmap.items(): oldgid = cmap.get(uni, None) if oldgid is None: cmap[uni] = gid fontIndexForGlyph[gid] = fontIdx elif oldgid != gid: # Char previously mapped to oldgid, now to gid. # Record, to fix up in GSUB 'locl' later. if m.duplicateGlyphsPerFont[fontIdx].get(oldgid) is None: if glyphSets is not None: oldFontIdx = fontIndexForGlyph[oldgid] for idx in (fontIdx, oldFontIdx): if glyphSets[idx] is None: glyphSets[idx] = m.fonts[idx].getGlyphSet() if _glyphsAreSame(glyphSets[oldFontIdx], glyphSets[fontIdx], oldgid, gid): continue m.duplicateGlyphsPerFont[fontIdx][oldgid] = gid elif m.duplicateGlyphsPerFont[fontIdx][oldgid] != gid: # Char previously mapped to oldgid but oldgid is already remapped to a different # gid, because of another Unicode character. # TODO: Try harder to do something about these. log.warning("Dropped mapping from codepoint %#06X to glyphId '%s'", uni, gid) cmapBmpOnly = {uni: gid for uni,gid in cmap.items() if uni <= 0xFFFF} self.tables = [] module = ttLib.getTableModule('cmap') if len(cmapBmpOnly) != len(cmap): # format-12 required. cmapTable = module.cmap_classes[12](12) cmapTable.platformID = 3 cmapTable.platEncID = 10 cmapTable.language = 0 cmapTable.cmap = cmap self.tables.append(cmapTable) # always create format-4 cmapTable = module.cmap_classes[4](4) cmapTable.platformID = 3 cmapTable.platEncID = 1 cmapTable.language = 0 cmapTable.cmap = cmapBmpOnly # ordered by platform then encoding self.tables.insert(0, cmapTable) self.tableVersion = 0 self.numSubTables = len(self.tables) return self
def colorize(font): COLR = newTable("COLR") CPAL = newTable("CPAL") hmtx = font["hmtx"] glyf = font.get("glyf", None) CFF = font.get("CFF ", None) if glyf is None: assert False, "CFF is not supported" CPAL.version = 0 COLR.version = 0 palette = list(GROUPS.values()) CPAL.palettes = [palette] CPAL.numPaletteEntries = len(palette) COLR.ColorLayers = {} glyphOrder = list(font.getGlyphOrder()) for name in glyphOrder: glyph = glyf[name] layers = [] if glyph.isComposite() and len(glyph.components) > 1: componentColors = [ getGlyphColor(c.getComponentInfo()[0]) for c in glyph.components ] if any(componentColors): for component in glyph.components: componentName, trans = component.getComponentInfo() componentColor = getGlyphColor(componentName) if componentColor is None: # Special palette index that means the glyph has no # specified color and takes the text color. componentColor = 0xFFFF else: componentColor = palette.index(componentColor) # Unique identifier for each layer, so we can reuse # identical layers and avoid needless duplication. width = hmtx[name][0] lsb = hmtx[componentName][1] + trans[4] componentHash = hash((trans, width, lsb)) if componentName not in HASHES: HASHES[componentName] = [] if componentHash not in HASHES[componentName]: HASHES[componentName].append(componentHash) index = HASHES[componentName].index(componentHash) newName = "%s.l%s" % (componentName, index) if newName not in font.glyphOrder: font.glyphOrder.append(newName) newGlyph = getTableModule("glyf").Glyph() newGlyph.numberOfContours = -1 newGlyph.components = [component] glyf.glyphs[newName] = newGlyph assert (len(glyf.glyphs) == len( font.glyphOrder)), (name, newName) hmtx.metrics[newName] = [width, lsb] layers.append(newLayer(newName, componentColor)) if not layers: color = getGlyphColor(name) if color is not None: layers = [newLayer(name, palette.index(color))] if layers: COLR[name] = layers font["COLR"] = COLR font["CPAL"] = CPAL
import argparse from fontTools.ttLib import TTFont, getTableModule, newTable Color = getTableModule("CPAL").Color RED = Color(red=0xcc, green=0x33, blue=0x33, alpha=0xff) YELLOW = Color(red=0xee, green=0x99, blue=0x33, alpha=0xff) GREEN = Color(red=0x00, green=0xa5, blue=0x50, alpha=0xff) BLUE = Color(red=0x33, green=0x66, blue=0x99, alpha=0xff) BLACK = Color(red=0x00, green=0x00, blue=0x00, alpha=0xff) HAMAZAT_GLYPHS = ( "uni0621", "uni0654", "uni0655", ) MARKS_GLYPHS = ( "uni0618", "uni0619", "uni061A", "uni064B", "uni064C", "uni064D", "uni064E", "uni064F", "uni0650", "uni0651", "uni0652", "uni0657",
def openFile(path, txPath): # If input font is CFF or PS, build a dummy ttFont in memory for use by AC. # return ttFont, and flag if is a real OTF font Return flag is 0 if OTF, 1 if CFF, and 2 if PS/ fontType = 0 # OTF tempPath = os.path.dirname(path) tempPathBase = os.path.join(tempPath, "temp.ac") tempPathCFF = tempPathBase + ".cff" try: ff = file(path, "rb") data = ff.read(10) ff.close() except (IOError, OSError): logMsg("Failed to open and read font file %s." % path) if data[:4] == "OTTO": # it is an OTF font, can process file directly try: ttFont = TTFont(path) except (IOError, OSError): raise ACFontError("Error opening or reading from font file <%s>." % path) except TTLibError: raise ACFontError("Error parsing font file <%s>." % path) try: cffTable = ttFont["CFF "] except KeyError: raise ACFontError("Error: font is not a CFF font <%s>." % fontFileName) return ttFont, fontType # It is not an OTF file. if (data[0] == '\1') and (data[1] == '\0'): # CFF file fontType = 1 tempPathCFF = path elif not "%" in data: #not a PS file either logMsg("Font file must be a PS, CFF or OTF fontfile: %s." % path) raise ACFontError("Font file must be PS, CFF or OTF file: %s." % path) else: # It is a PS file. Convert to CFF. fontType = 2 command = "%s -cff +b \"%s\" \"%s\" 2>&1" % (txPath, path, tempPathCFF) report = FDKUtils.runShellCmd(command) if "fatal" in report: logMsg( "Attempted to convert font %s from PS to a temporary CFF data file." % path) logMsg(report) raise ACFontError( "Failed to convert PS font %s to a temp CFF font." % path) # now package the CFF font as an OTF font for use by AC. ff = file(tempPathCFF, "rb") data = ff.read() ff.close() try: ttFont = TTFont() cffModule = getTableModule('CFF ') cffTable = cffModule.table_C_F_F_('CFF ') ttFont['CFF '] = cffTable cffTable.decompile(data, ttFont) except: import traceback traceback.print_exc() logMsg("Attempted to read font %s as CFF." % path) raise ACFontError("Error parsing font file <%s>." % fontFileName) # Delete the temporary temp.ac.cff file if os.path.exists(tempPathCFF): os.remove(tempPathCFF) return ttFont, fontType
def openFile(path, txPath): # If input font is CFF or PS, build a dummy ttFont. tempPathCFF = None cffPath = None # If it is CID-keyed font, we need to convert it to a name-keyed font. This is a hack, but I really don't want to add CID support to # the very simple-minded PDF library. command = "%s -dump -0 \"%s\" 2>&1" % (txPath, path) report = fdkutils.runShellCmd(command) if "CIDFontName" in report: tfd, tempPath1 = tempfile.mkstemp() os.close(tfd) command = "%s -t1 -decid -usefd 0 \"%s\" \"%s\" 2>&1" % ( txPath, path, tempPath1) report = fdkutils.runShellCmd(command) if "fatal" in report: logMsg(report) logMsg( "Failed to convert CID-keyed font %s to a temporary Typ1 data file." % path) tfd, tempPathCFF = tempfile.mkstemp() os.close(tfd) command = "%s -cff +b \"%s\" \"%s\" 2>&1" % (txPath, tempPath1, tempPathCFF) report = fdkutils.runShellCmd(command) if "fatal" in report: logMsg(report) logMsg( "Failed to convert CID-keyed font %s to a temporary CFF data file." % path) cffPath = tempPathCFF os.remove(tempPath1) elif os.path.isdir(path): # See if it is a UFO font by truing to dump it. command = "%s -dump -0 \"%s\" 2>&1" % (txPath, path) report = fdkutils.runShellCmd(command) if not "sup.srcFontType" in report: logMsg(report) logMsg("Failed to open directory %s as a UFO font." % path) tfd, tempPathCFF = tempfile.mkstemp() os.close(tfd) command = "%s -cff +b \"%s\" \"%s\" 2>&1" % (txPath, path, tempPathCFF) report = fdkutils.runShellCmd(command) if "fatal" in report: logMsg(report) logMsg( "Failed to convert ufo font %s to a temporary CFF data file." % path) cffPath = tempPathCFF else: try: with open(path, "rb") as ff: head = ff.read(10) except (IOError, OSError): traceback.print_exc() raise FontError( "Failed to open and read font file %s. Check file/directory permissions." % path) if len(head) < 10: raise FontError( "Error: font file was zero size: may be a resource fork font, which this program does not process. <%s>." % path) # it is an OTF/TTF font, can process file directly elif head[:4] in (b"OTTO", b"true", b"\0\1\0\0"): try: ttFont = TTFont(path) except (IOError, OSError): raise FontError( "Error opening or reading from font file <%s>." % path) except TTLibError: raise if 'CFF ' not in ttFont and 'glyf' not in ttFont: raise FontError( "Error: font is not a CFF or TrueType font <%s>." % path) return ttFont, tempPathCFF # It is not an SFNT file. elif head[0:2] == b'\x01\x00': # CFF file cffPath = path elif b"%" not in head: # not a PS file either logMsg("Font file must be a PS, CFF or OTF fontfile: %s." % path) raise FontError("Font file must be PS, CFF or OTF file: %s." % path) else: # It is a PS file. Convert to CFF. tfd, tempPathCFF = tempfile.mkstemp() os.close(tfd) cffPath = tempPathCFF command = "%s -cff +b \"%s\" \"%s\" 2>&1" % (txPath, path, tempPathCFF) report = fdkutils.runShellCmd(command) if "fatal" in report: logMsg( "Attempted to convert font %s from PS to a temporary CFF data file." % path) logMsg(report) raise FontError( "Failed to convert PS font %s to a temp CFF font." % path) # now package the CFF font as an OTF font with open(cffPath, "rb") as ff: data = ff.read() try: ttFont = TTFont() cffModule = getTableModule('CFF ') cffTable = cffModule.table_C_F_F_('CFF ') ttFont['CFF '] = cffTable cffTable.decompile(data, ttFont) except: traceback.print_exc() logMsg("Attempted to read font %s as CFF." % path) raise FontError("Error parsing font file <%s>." % path) return ttFont, tempPathCFF
def openOpenTypeFile(path, outFilePath): # If input font is CFF or PS, build a dummy ttFont in memory.. # return ttFont, and flag if is a real OTF font Return flag is 0 if OTF, 1 if CFF, and 2 if PS/ fontType = 0 # OTF tempPathCFF = path + kTempCFFSuffix try: ff = file(path, "rb") data = ff.read(10) ff.close() except (IOError, OSError): logMsg("Failed to open and read font file %s." % path) if data[:4] == "OTTO": # it is an OTF font, can process file directly try: ttFont = TTFont(path) except (IOError, OSError): raise focusFontError("Error opening or reading from font file <%s>." % path) except TTLibError: raise focusFontError("Error parsing font file <%s>." % path) try: cffTable = ttFont["CFF "] except KeyError: raise focusFontError("Error: font is not a CFF font <%s>." % fontFileName) else: # It is not an OTF file. if (data[0] == '\1') and (data[1] == '\0'): # CFF file fontType = 1 tempPathCFF = path elif not "%" in data: #not a PS file either logMsg("Font file must be a PS, CFF or OTF fontfile: %s." % path) raise focusFontError("Font file must be PS, CFF or OTF file: %s." % path) else: # It is a PS file. Convert to CFF. fontType = 2 print "Converting Type1 font to temp CFF font file..." command="tx -cff +b -std \"%s\" \"%s\" 2>&1" % (path, tempPathCFF) report = FDKUtils.runShellCmd(command) if "fatal" in report: logMsg("Attempted to convert font %s from PS to a temporary CFF data file." % path) logMsg(report) raise focusFontError("Failed to convert PS font %s to a temp CFF font." % path) # now package the CFF font as an OTF font. ff = file(tempPathCFF, "rb") data = ff.read() ff.close() try: ttFont = TTFont() cffModule = getTableModule('CFF ') cffTable = cffModule.table_C_F_F_('CFF ') ttFont['CFF '] = cffTable cffTable.decompile(data, ttFont) except: logMsg( "\t%s" %(traceback.format_exception_only(sys.exc_type, sys.exc_value)[-1])) logMsg("Attempted to read font %s as CFF." % path) raise focusFontError("Error parsing font file <%s>." % path) fontData = CFFFontData(ttFont, path, outFilePath, fontType, logMsg) return fontData
def openFile(path, txPath): # If input font is CFF or PS, build a dummy ttFont. tempPathCFF = None try: ff = file(path, "rb") data = ff.read(10) ff.close() except (IOError, OSError): import traceback traceback.print_exc() raise FontError("Failed to open and read font file %s. Check file/directory permissions." % path) if len(data) < 10: raise FontError("Error: font file was zero size: may be a resource fork font, which this program does not process. <%s>." % path) if (data[:4] == "OTTO") or (data[:4] == "true") or (data[:4] == "\0\1\0\0"): # it is an OTF/TTF font, can process file directly try: ttFont = ttLib.TTFont(path) except (IOError, OSError): raise FontError("Error opening or reading from font file <%s>." % path) except TTLibError: raise FontError("Error parsing font file 333 <%s>." % path) if not (ttFont.has_key('CFF ') or ttFont.has_key('glyf')): raise FontError("Error: font is not a CFF or TrueType font <%s>." % path) return ttFont, tempPathCFF # It is not an OTF file. if (data[0] == '\1') and (data[1] == '\0'): # CFF file cffPath = path elif not "%" in data: #not a PS file either logMsg("Font file must be a PS, CFF or OTF fontfile: %s." % path) raise FontError("Font file must be PS, CFF or OTF file: %s." % path) else: # It is a PS file. Convert to CFF. tfd,tempPathCFF = tempfile.mkstemp() os.close(tfd) cffPath = tempPathCFF command="%s -cff +b \"%s\" \"%s\" 2>&1" % (txPath, path, tempPathCFF) report = FDKUtils.runShellCmd(command) if "fatal" in report: logMsg("Attempted to convert font %s from PS to a temporary CFF data file." % path) logMsg(report) raise FontError("Failed to convert PS font %s to a temp CFF font." % path) # now package the CFF font as an OTF font for use by autohint. ff = file(cffPath, "rb") data = ff.read() ff.close() try: ttFont = ttLib.TTFont() cffModule = ttLib.getTableModule('CFF ') cffTable = cffModule.table_C_F_F_('CFF ') ttFont['CFF '] = cffTable cffTable.decompile(data, ttFont) except: import traceback traceback.print_exc() logMsg("Attempted to read font %s as CFF." % path) raise FontError("Error parsing font file <%s>." % path) return ttFont, tempPathCFF
def openOpenTypeFile(path, outFilePath): # If input font is CFF or PS, build a dummy ttFont in memory.. # return ttFont, and flag if is a real OTF font Return flag is 0 if OTF, 1 if CFF, and 2 if PS/ fontType = 0 # OTF tempPathCFF = path + kTempCFFSuffix try: ff = file(path, "rb") data = ff.read(10) ff.close() except (IOError, OSError): logMsg("Failed to open and read font file %s." % path) if data[:4] == "OTTO": # it is an OTF font, can process file directly try: ttFont = TTFont(path) except (IOError, OSError): raise ACFontError("Error opening or reading from font file <%s>." % path) except TTLibError: raise ACFontError("Error parsing font file <%s>." % path) try: cffTable = ttFont["CFF "] except KeyError: raise ACFontError("Error: font is not a CFF font <%s>." % fontFileName) else: # It is not an OTF file. if (data[0] == '\1') and (data[1] == '\0'): # CFF file fontType = 1 tempPathCFF = path elif not "%" in data: #not a PS file either logMsg("Font file must be a PS, CFF or OTF fontfile: %s." % path) raise ACFontError("Font file must be PS, CFF or OTF file: %s." % path) else: # It is a PS file. Convert to CFF. fontType = 2 print "Converting Type1 font to temp CFF font file..." command = "tx -cff +b -std \"%s\" \"%s\" 2>&1" % (path, tempPathCFF) report = FDKUtils.runShellCmd(command) if "fatal" in report: logMsg( "Attempted to convert font %s from PS to a temporary CFF data file." % path) logMsg(report) raise ACFontError( "Failed to convert PS font %s to a temp CFF font." % path) # now package the CFF font as an OTF font. ff = file(tempPathCFF, "rb") data = ff.read() ff.close() try: ttFont = TTFont() cffModule = getTableModule('CFF ') cffTable = cffModule.table_C_F_F_('CFF ') ttFont['CFF '] = cffTable cffTable.decompile(data, ttFont) except: logMsg("\t%s" % (traceback.format_exception_only( sys.exc_type, sys.exc_value)[-1])) logMsg("Attempted to read font %s as CFF." % path) raise ACFontError("Error parsing font file <%s>." % path) fontData = CFFFontData(ttFont, path, outFilePath, fontType, logMsg) return fontData
import argparse from fontTools.ttLib import TTFont, getTableModule, newTable Color = getTableModule("CPAL").Color RED = Color(red=0xCC, green=0x33, blue=0x33, alpha=0xFF) YELLOW = Color(red=0xEE, green=0x99, blue=0x33, alpha=0xFF) GREEN = Color(red=0x00, green=0xA5, blue=0x50, alpha=0xFF) BLUE = Color(red=0x33, green=0x66, blue=0x99, alpha=0xFF) HAMAZAT_GLYPHS = ("uni0621", "uni0654", "uni0655") MARKS_GLYPHS = ( "uni0618", "uni0619", "uni061A", "uni064B", "uni064C", "uni064D", "uni064E", "uni064F", "uni0650", "uni0651", "uni0652", "uni0657", "uni0658", "uni065C", "uni0670", "uni06DC", # XXX: can be both a mark and a pause "uni06DF",
def openFile(path, txPath): # If input font is CFF or PS, build a dummy ttFont. tempPathCFF = None cffPath = None # If it is CID-keyed font, we need to convert it to a name-keyed font. This is a hack, but I really don't want to add CID support to # the very simple-minded PDF library. command="%s -dump -0 \"%s\" 2>&1" % (txPath, path) report = FDKUtils.runShellCmd(command) if "CIDFontName" in report: tfd,tempPath1 = tempfile.mkstemp() os.close(tfd) command="%s -t1 -decid -usefd 0 \"%s\" \"%s\" 2>&1" % (txPath, path, tempPath1) report = FDKUtils.runShellCmd(command) if "fatal" in report: logMsg(report) logMsg("Failed to convert CID-keyed font %s to a temporary Typ1 data file." % path) tfd,tempPathCFF = tempfile.mkstemp() os.close(tfd) command="%s -cff +b \"%s\" \"%s\" 2>&1" % (txPath, tempPath1, tempPathCFF) report = FDKUtils.runShellCmd(command) if "fatal" in report: logMsg(report) logMsg("Failed to convert CID-keyed font %s to a temporary CFF data file." % path) cffPath = tempPathCFF os.remove(tempPath1) elif os.path.isdir(path): # See if it is a UFO font by truing to dump it. command="%s -dump -0 \"%s\" 2>&1" % (txPath, path) report = FDKUtils.runShellCmd(command) if not "sup.srcFontType" in report: logMsg(report) logMsg("Failed to open directory %s as a UFO font." % path) tfd,tempPathCFF = tempfile.mkstemp() os.close(tfd) command="%s -cff +b \"%s\" \"%s\" 2>&1" % (txPath, path, tempPathCFF) report = FDKUtils.runShellCmd(command) if "fatal" in report: logMsg(report) logMsg("Failed to convert ufo font %s to a temporary CFF data file." % path) cffPath = tempPathCFF else: try: ff = file(path, "rb") data = ff.read(10) ff.close() except (IOError, OSError): import traceback traceback.print_exc() raise FontError("Failed to open and read font file %s. Check file/directory permissions." % path) if len(data) < 10: raise FontError("Error: font file was zero size: may be a resource fork font, which this program does not process. <%s>." % path) if (data[:4] == "OTTO") or (data[:4] == "true") or (data[:4] == "\0\1\0\0"): # it is an OTF/TTF font, can process file directly try: ttFont = ttLib.TTFont(path) except (IOError, OSError): raise FontError("Error opening or reading from font file <%s>." % path) except TTLibError: raise FontError("Error parsing font file 333 <%s>." % path) if not (ttFont.has_key('CFF ') or ttFont.has_key('glyf')): raise FontError("Error: font is not a CFF or TrueType font <%s>." % path) return ttFont, tempPathCFF # It is not an OTF file. if (data[0] == '\1') and (data[1] == '\0'): # CFF file cffPath = path elif not "%" in data: #not a PS file either logMsg("Font file must be a PS, CFF or OTF fontfile: %s." % path) raise FontError("Font file must be PS, CFF or OTF file: %s." % path) else: # It is a PS file. Convert to CFF. tfd,tempPathCFF = tempfile.mkstemp() os.close(tfd) cffPath = tempPathCFF command="%s -cff +b \"%s\" \"%s\" 2>&1" % (txPath, path, tempPathCFF) report = FDKUtils.runShellCmd(command) if "fatal" in report: logMsg("Attempted to convert font %s from PS to a temporary CFF data file." % path) logMsg(report) raise FontError("Failed to convert PS font %s to a temp CFF font." % path) # now package the CFF font as an OTF font ff = file(cffPath, "rb") data = ff.read() ff.close() try: ttFont = ttLib.TTFont() cffModule = ttLib.getTableModule('CFF ') cffTable = cffModule.table_C_F_F_('CFF ') ttFont['CFF '] = cffTable cffTable.decompile(data, ttFont) except: import traceback traceback.print_exc() logMsg("Attempted to read font %s as CFF." % path) raise FontError("Error parsing font file <%s>." % path) return ttFont, tempPathCFF
def fix(self, check=False): retval = False fontfile = os.path.basename(self.fontpath) space = self.getGlyph(0x0020) nbsp = self.getGlyph(0x00A0) if space not in ["space", "uni0020"]: logger.error( 'ER: {}: Glyph 0x0020 is called "{}": Change to "space" or "uni0020"' .format(fontfile, space)) if nbsp not in ["nbsp", "uni00A0", "nonbreakingspace", "nbspace"]: logger.error( 'ER: {}: Glyph 0x00A0 is called "{}": Change to "nbsp" or "uni00A0"' .format(fontfile, nbsp)) isNbspAdded = isSpaceAdded = False if not nbsp: isNbspAdded = True try: nbsp = self.addGlyph(0x00A0, 'nbsp') except Exception as ex: logger.error('ER: {}'.format(ex)) return False if not space: isSpaceAdded = True try: space = self.addGlyph(0x0020, 'space') except Exception as ex: logger.error('ER: {}'.format(ex)) return False for g in [space, nbsp]: if self.glyphHasInk(g): if check: logger.error( 'ER: {}: Glyph "{}" has ink. Delete any contours or components' .format(fontfile, g)) else: logger.error( 'ER: {}: Glyph "{}" has ink. Fixed: Overwritten by an empty glyph' .format(fontfile, g)) #overwrite existing glyph with an empty one self.font['glyf'].glyphs[g] = ttLib.getTableModule( 'glyf').Glyph() retval = True spaceWidth = self.getWidth(space) nbspWidth = self.getWidth(nbsp) if spaceWidth != nbspWidth or nbspWidth < 0: self.setWidth(nbsp, min(nbspWidth, spaceWidth)) self.setWidth(space, min(nbspWidth, spaceWidth)) if isNbspAdded: if check: msg = 'ER: {} space {} nbsp None: Add nbsp with advanceWidth {}' else: msg = 'ER: {} space {} nbsp None: Added nbsp with advanceWidth {}' logger.error(msg.format(fontfile, spaceWidth, spaceWidth)) if isSpaceAdded: if check: msg = 'ER: {} space None nbsp {}: Add space with advanceWidth {}' else: msg = 'ER: {} space None nbsp {}: Added space with advanceWidth {}' logger.error(msg.format(fontfile, nbspWidth, nbspWidth)) if nbspWidth > spaceWidth and spaceWidth >= 0: if check: msg = 'ER: {} space {} nbsp {}: Change space advanceWidth to {}' else: msg = 'ER: {} space {} nbsp {}: Fixed space advanceWidth to {}' logger.error( msg.format(fontfile, spaceWidth, nbspWidth, nbspWidth)) else: if check: msg = 'ER: {} space {} nbsp {}: Change nbsp advanceWidth to {}' else: msg = 'ER: {} space {} nbsp {}: Fixed nbsp advanceWidth to {}' logger.error( msg.format(fontfile, spaceWidth, nbspWidth, spaceWidth)) return True logger.info('OK: {} space {} nbsp {}'.format(fontfile, spaceWidth, nbspWidth)) return retval
def merge(self, m, tables): # TODO Handle format=14. # Only merge format 4 and 12 Unicode subtables, ignores all other subtables # If there is a format 12 table for the same font, ignore the format 4 table cmapTables = [] for fontIdx,table in enumerate(tables): format4 = None format12 = None for subtable in table.tables: properties = (subtable.format, subtable.platformID, subtable.platEncID) if properties in CmapUnicodePlatEncodings.BMP: format4 = subtable elif properties in CmapUnicodePlatEncodings.FullRepertoire: format12 = subtable else: log.warning( "Dropped cmap subtable from font [%s]:\t" "format %2s, platformID %2s, platEncID %2s", fontIdx, subtable.format, subtable.platformID, subtable.platEncID ) if format12 is not None: cmapTables.append((format12, fontIdx)) elif format4 is not None: cmapTables.append((format4, fontIdx)) # Build a unicode mapping, then decide which format is needed to store it. cmap = {} fontIndexForGlyph = {} glyphSets = [None for f in m.fonts] if hasattr(m, 'fonts') else None for table,fontIdx in cmapTables: # handle duplicates for uni,gid in table.cmap.items(): oldgid = cmap.get(uni, None) if oldgid is None: cmap[uni] = gid fontIndexForGlyph[gid] = fontIdx elif oldgid != gid: # Char previously mapped to oldgid, now to gid. # Record, to fix up in GSUB 'locl' later. if m.duplicateGlyphsPerFont[fontIdx].get(oldgid) is None: if glyphSets is not None: oldFontIdx = fontIndexForGlyph[oldgid] for idx in (fontIdx, oldFontIdx): if glyphSets[idx] is None: glyphSets[idx] = m.fonts[idx].getGlyphSet() if _glyphsAreSame(glyphSets[oldFontIdx], glyphSets[fontIdx], oldgid, gid): continue m.duplicateGlyphsPerFont[fontIdx][oldgid] = gid elif m.duplicateGlyphsPerFont[fontIdx][oldgid] != gid: # Char previously mapped to oldgid but oldgid is already remapped to a different # gid, because of another Unicode character. # TODO: Try harder to do something about these. log.warning("Dropped mapping from codepoint %#06X to glyphId '%s'", uni, gid) cmapBmpOnly = {uni: gid for uni,gid in cmap.items() if uni <= 0xFFFF} self.tables = [] module = ttLib.getTableModule('cmap') if len(cmapBmpOnly) != len(cmap): # format-12 required. cmapTable = module.cmap_classes[12](12) cmapTable.platformID = 3 cmapTable.platEncID = 10 cmapTable.language = 0 cmapTable.cmap = cmap self.tables.append(cmapTable) # always create format-4 cmapTable = module.cmap_classes[4](4) cmapTable.platformID = 3 cmapTable.platEncID = 1 cmapTable.language = 0 cmapTable.cmap = cmapBmpOnly # ordered by platform then encoding self.tables.insert(0, cmapTable) self.tableVersion = 0 self.numSubTables = len(self.tables) return 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 openFile(path, txPath): # If input font is CFF or PS, build a dummy ttFont. tempPathCFF = None try: ff = file(path, "rb") data = ff.read(10) ff.close() except (IOError, OSError): import traceback traceback.print_exc() raise FontError( "Failed to open and read font file %s. Check file/directory permissions." % path) if len(data) < 10: raise FontError( "Error: font file was zero size: may be a resource fork font, which this program does not process. <%s>." % path) if (data[:4] == "OTTO") or (data[:4] == "true") or ( data[:4] == "\0\1\0\0"): # it is an OTF/TTF font, can process file directly try: ttFont = ttLib.TTFont(path) except (IOError, OSError): raise FontError("Error opening or reading from font file <%s>." % path) except TTLibError: raise FontError("Error parsing font file 333 <%s>." % path) if not (ttFont.has_key('CFF ') or ttFont.has_key('glyf')): raise FontError("Error: font is not a CFF or TrueType font <%s>." % path) return ttFont, tempPathCFF # It is not an OTF file. if (data[0] == '\1') and (data[1] == '\0'): # CFF file cffPath = path elif not "%" in data: #not a PS file either logMsg("Font file must be a PS, CFF or OTF fontfile: %s." % path) raise FontError("Font file must be PS, CFF or OTF file: %s." % path) else: # It is a PS file. Convert to CFF. tfd, tempPathCFF = tempfile.mkstemp() os.close(tfd) cffPath = tempPathCFF command = "%s -cff +b \"%s\" \"%s\" 2>&1" % (txPath, path, tempPathCFF) report = FDKUtils.runShellCmd(command) if "fatal" in report: logMsg( "Attempted to convert font %s from PS to a temporary CFF data file." % path) logMsg(report) raise FontError( "Failed to convert PS font %s to a temp CFF font." % path) # now package the CFF font as an OTF font for use by autohint. ff = file(cffPath, "rb") data = ff.read() ff.close() try: ttFont = ttLib.TTFont() cffModule = ttLib.getTableModule('CFF ') cffTable = cffModule.table_C_F_F_('CFF ') ttFont['CFF '] = cffTable cffTable.decompile(data, ttFont) except: import traceback traceback.print_exc() logMsg("Attempted to read font %s as CFF." % path) raise FontError("Error parsing font file <%s>." % path) return ttFont, tempPathCFF
def fix(self, check=False): retval = False fontfile = os.path.basename(self.fontpath) space = self.getGlyph(0x0020) nbsp = self.getGlyph(0x00A0) if space not in ["space", "uni0020"]: logger.error( 'ER: {}: Glyph 0x0020 is called "{}": Change to "space" or "uni0020"' .format(fontfile, space)) if nbsp not in ["nbsp", "uni00A0"]: logger.error( 'ER: {}: Glyph 0x00A0 is called "{}": Change to "nbsp" or "uni00A0"' .format(fontfile, nbsp)) isNbspAdded = isSpaceAdded = False if not nbsp: isNbspAdded = True try: nbsp = self.addGlyph(0x00A0, 'nbsp') except Exception as ex: logger.error('ER: {}'.format(ex)) return False if not space: isSpaceAdded = True try: space = self.addGlyph(0x0020, 'space') except Exception as ex: logger.error('ER: {}'.format(ex)) return False for g in [space, nbsp]: if self.glyphHasInk(g): if check: logger.error( 'ER: {}: Glyph "{}" has ink. Delete any contours or components' .format(fontfile, g)) else: logger.error( 'ER: {}: Glyph "{}" has ink. Fixed: Overwritten by an empty glyph' .format(fontfile, g)) #overwrite existing glyph with an empty one self.font['glyf'].glyphs[g] = ttLib.getTableModule( 'glyf').Glyph() retval = True spaceWidth = self.getWidth(space) nbspWidth = self.getWidth(nbsp) if spaceWidth != nbspWidth or nbspWidth < 0: self.setWidth(nbsp, min(nbspWidth, spaceWidth)) self.setWidth(space, min(nbspWidth, spaceWidth)) if isNbspAdded: if check: msg = 'ER: {} space {} nbsp None: Add nbsp with advanceWidth {}' else: msg = 'ER: {} space {} nbsp None: Added nbsp with advanceWidth {}' logger.error(msg.format(fontfile, spaceWidth, spaceWidth)) if isSpaceAdded: if check: msg = 'ER: {} space None nbsp {}: Add space with advanceWidth {}' else: msg = 'ER: {} space None nbsp {}: Added space with advanceWidth {}' logger.error(msg.format(fontfile, nbspWidth, nbspWidth)) if nbspWidth > spaceWidth and spaceWidth >= 0: if check: msg = 'ER: {} space {} nbsp {}: Change space advanceWidth to {}' else: msg = 'ER: {} space {} nbsp {}: Fixed space advanceWidth to {}' logger.error( msg.format(fontfile, spaceWidth, nbspWidth, nbspWidth)) else: if check: msg = 'ER: {} space {} nbsp {}: Change nbsp advanceWidth to {}' else: msg = 'ER: {} space {} nbsp {}: Fixed nbsp advanceWidth to {}' logger.error( msg.format(fontfile, spaceWidth, nbspWidth, spaceWidth)) return True logger.info('OK: {} space {} nbsp {}'.format(fontfile, spaceWidth, nbspWidth)) return retval
def colorize(font): COLR = newTable("COLR") CPAL = newTable("CPAL") glyf = font["glyf"] hmtx = font["hmtx"] CPAL.version = 0 COLR.version = 0 palette = list(GROUPS.values()) palette.append(BLACK) CPAL.palettes = [palette] CPAL.numPaletteEntries = len(palette) COLR.ColorLayers = {} glyphOrder = list(font.getGlyphOrder()) for name in glyphOrder: glyph = glyf[name] layers = [] if glyph.isComposite() and len(glyph.components) > 1: componentColors = [ getGlyphColor(c.getComponentInfo()[0]) for c in glyph.components ] if any(componentColors): for component in glyph.components: componentName, trans = component.getComponentInfo() componentColor = getGlyphColor(componentName) if trans == (1, 0, 0, 1, 0, 0): if componentColor is None: # broken in current versions of Firefox, # see https://bugzilla.mozilla.org/show_bug.cgi?id=1283932 #layers.append(newLayer(componentName, 0xFFFF)) # broken if FF47 layers.append( newLayer(componentName, palette.index(BLACK))) else: layers.append( newLayer(componentName, palette.index(componentColor))) else: newName = "%s.%s" % (componentName, hash(trans)) if newName not in font.glyphOrder: font.glyphOrder.append(newName) newGlyph = getTableModule("glyf").Glyph() newGlyph.numberOfContours = -1 newGlyph.components = [component] glyf.glyphs[newName] = newGlyph assert (len(glyf.glyphs) == len( font.glyphOrder)), (name, newName) width = hmtx[name][0] lsb = hmtx[componentName][1] + trans[4] hmtx.metrics[newName] = [width, lsb] if componentColor is None: # broken in current versions of Firefox, # see https://bugzilla.mozilla.org/show_bug.cgi?id=1283932 #layers.append(newLayer(componentName, 0xFFFF)) # broken if FF47 layers.append( newLayer(newName, palette.index(BLACK))) else: layers.append( newLayer(newName, palette.index(componentColor))) if not layers: color = getGlyphColor(name) if color is not None: layers = [newLayer(name, palette.index(color))] if layers: COLR[name] = layers font["COLR"] = COLR font["CPAL"] = CPAL