def CheckEnvironment(): txPath = 'tx' txError = 0 try: exe_dir, fdkSharedDataDir = FDKUtils.findFDKDirs() except FDKUtils.FDKEnvError: logMsg( "Please re-install the FDK. Cannot find the FDK/Tools/SharedData directory." ) raise FDKEnvironmentError command = "%s -u 2>&1" % (txPath) report = FDKUtils.runShellCmd(command) if "options" not in report: txError = 1 if txError: logMsg( "Please re-install the FDK. The executable directory \"%s\" is missing the tool: < %s >." % (exe_dir, txPath)) logMsg("or the files referenced by the shell script is missing.") raise FDKEnvironmentError return txPath, fdkSharedDataDir
def mergeFontToCFF(srcPath, outputPath, doSubr): # We assume that srcPath is a type 1 font,and outputPath is an OTF font. # First, convert src font to cff, and subroutinize it if so requested. tempPath = srcPath + ".temp.cff" subrArg = "" if doSubr: subrArg = " +S" command = "tx -cff +b%s \"%s\" \"%s\" 2>&1" % (subrArg, srcPath, tempPath) report = FDKUtils.runShellCmd(command) if ("fatal" in report) or ("error" in report): print report raise FontInfoParseError("Failed to convert font '%s' to CFF." % (fontPath)) # Now merge it into the output file. command = "sfntedit -a CFF=\"%s\" \"%s\" 2>&1" % (tempPath, outputPath) report = FDKUtils.runShellCmd(command) if not debug: if os.path.exists(tempPath): os.remove(tempPath) if ("fatal" in report) or ("error" in report): print report raise FontInfoParseError("Failed to convert font '%s' to CFF." % (fontPath))
def CheckEnvironment(): if curSystem == "Windows": tx_path = subprocess.check_output(["where", "tx.exe"]).strip() else: tx_path = subprocess.check_output(["which", "tx"]).strip() txError = 0 try: exe_dir, _ = FDKUtils.findFDKDirs() except FDKUtils.FDKEnvError: logMsg("Please re-install the afdko. Cannot find the " "afdko/Tools/SharedData directory.") raise FDKEnvironmentError command = "%s -u 2>&1" % tx_path report = FDKUtils.runShellCmd(command) if "options" not in report: txError = 1 if txError: logMsg("Please re-install the afdko. The executable directory \"%s\" " "is missing the tool: < %s >." % (exe_dir, tx_path)) logMsg("or the files referenced by the shell script is missing.") raise FDKEnvironmentError return tx_path
def mergeFonts(inputFontPath, outputPath, fontList, glyphList, fontDictList, fdGlyphDict): cidfontinfoPath = "%s.temp.cidfontinfo" % (inputFontPath) makeCIDFontInfo(inputFontPath, cidfontinfoPath) lastFont ="" tempfileList = [] i = 0 print "Merging temp fonts:", for fontPath in fontList: print ".", sys.stdout.flush() gaPath = fontPath + ".ga" dstPath = "%s.temp.merge.%s" % (inputFontPath, i) removeNotdef = i != 0 makeGAFile(gaPath, fontPath, glyphList, fontDictList, fdGlyphDict, removeNotdef) command = "mergeFonts -std -cid \"%s\" \"%s\" %s %s %s 2>&1" % (cidfontinfoPath, dstPath, lastFont, gaPath, fontPath) log = FDKUtils.runShellCmd(command) if debug: print command print log if "rror" in log: msg = "Error merging font %s. Log: %s." % (fontPath, log) raise FontInfoParseError(msg) i += 1 lastFont = dstPath tempfileList.append(gaPath) tempfileList.append(dstPath) print "" os.rename(dstPath, outputPath) if not debug: for path in tempfileList: if os.path.exists(path): os.remove(path) os.remove(cidfontinfoPath) return
def makeTempFonts(fontDictList, glyphSetList, fdGlyphDict, inputPath): fontList = [] setIndex = 0 for glyphList in glyphSetList: if not glyphList: continue setIndex += 1 arg = ",".join(glyphList) tempPath = "%s.temp.%s.pfa" % (inputPath, setIndex) command = "tx -t1 -g \"%s\" \"%s\" \"%s\" 2>&1" % (arg, inputPath, tempPath) log = FDKUtils.runShellCmd(command) if log: print "Have log output in subsetting command for %s to %s with %s glyphs." % ( inputPath, tempPath, len(glyphList)) print log fdIndex = fdGlyphDict[glyphList[0]][0] fdDict = fontDictList[fdIndex] fixFontDict(tempPath, fdDict) fontList.append(tempPath) if debug: print glyphList[0], len(glyphList), fdGlyphDict[ glyphList[0]], tempPath return fontList
def makeTempFonts(fontDictList, glyphSetList, fdGlyphDict, inputPath): fontList = [] setIndex = 0 for glyphList in glyphSetList: if not glyphList: continue setIndex += 1 arg = ",".join(glyphList) tempPath = "%s.temp.%s.pfa" % (inputPath, setIndex) command = 'tx -t1 -g "%s" "%s" "%s" 2>&1' % (arg, inputPath, tempPath) log = FDKUtils.runShellCmd(command) if log: print "Have log output in subsetting command for %s to %s with %s glyphs." % ( inputPath, tempPath, len(glyphList), ) print log fdIndex = fdGlyphDict[glyphList[0]][0] fdDict = fontDictList[fdIndex] fixFontDict(tempPath, fdDict) fontList.append(tempPath) if debug: print glyphList[0], len(glyphList), fdGlyphDict[glyphList[0]], tempPath return fontList
def CheckEnvironment(): txPath = 'tx' txError = 0 command = "%s -u 2>&1" % (txPath) report = FDKUtils.runShellCmd(command) if "options" not in report: txError = 1 if txError: logMsg("Please re-install the FDK. The path to the program 'tx' is not in the environment variable PATH.") raise FDKEnvironmentError command = "autohintexe -u 2>&1" report = FDKUtils.runShellCmd(command) if "version" not in report: logMsg("Please re-install the FDK. The path to the program 'autohintexe' is not in the environment variable PATH.") raise FDKEnvironmentError return
def CheckEnvironment(): txPath = 'tx' txError = 0 command = "%s -u 2>&1" % (txPath) report = FDKUtils.runShellCmd(command) if "options" not in report: txError = 1 if txError: logMsg("Please re-install the FDK. The path to the program 'tx' is not in the environment variable PATH.") raise FDKEnvironmentError command = "checkoutlinesexe -u 2>&1" report = FDKUtils.runShellCmd(command) if "version" not in report: logMsg("Please re-install the FDK. The path to the program 'checkoutlinesexe' is not in the environment variable PATH.") raise FDKEnvironmentError return
def getFontName(fPath): command = "tx -dump -0 %s 2>&1" % (fPath) data = FDKUtils.runShellCmd(command) if not data: raise FontInfoParseError("Error: Failed getting log from tx from %, when tryin to get FontName." % (fPath)) m = re.search(r"FontName\s+\"([^\"]+)", data) if not m: print data raise FontInfoParseError("Error: Failed finding FontName in tx log from %s." % (fPath)) return m.group(1)
def getBlueFuzz(fPath): blueFuzz = 1.0 command = "tx -dump -0 %s 2>&1" % (fPath) data = FDKUtils.runShellCmd(command) if not data: raise FontInfoParseError("Error: Failed getting log from tx from %, when trying to get FontName." % (fPath)) m = re.search(r"BlueFuzz\s+(\d+(?:\.\d+)*)", data) if m: blueFuzz = int(m.group(1)) return blueFuzz
def CheckEnvironment(): txPath = 'tx' txError = 0 try: exe_dir, fdkSharedDataDir = FDKUtils.findFDKDirs() except FDKUtils.FDKEnvError: logMsg("Please re-install the FDK. Cannot find the FDK/Tools/SharedData directory.") raise FDKEnvironmentError command = "%s -u 2>&1" % (txPath) report = FDKUtils.runShellCmd(command) if "options" not in report: txError = 1 if txError: logMsg("Please re-install the FDK. The executable directory \"%s\" is missing the tool: < %s >." % (exe_dir, txPath )) logMsg("or the files referenced by the shell script is missing.") raise FDKEnvironmentError return txPath, fdkSharedDataDir
def saveFontFile(ttFont, inputPath, outFilePath, fontType, txPath): overwriteOriginal = 0 if inputPath == outFilePath: overwriteOriginal = 1 tempPath = inputPath + ".temp.ac" if fontType == 0: # OTF if overwriteOriginal: ttFont.save(tempPath) ttFont.close() if os.path.exists(inputPath): try: os.remove(inputPath) os.rename(tempPath, inputPath) except (OSError, IOError): logMsg("\t%s" % (traceback.format_exception_only( sys.exc_type, sys.exc_value)[-1])) logMsg( "Error: could not overwrite original font file path '%s'. Hinted font file path is '%s'." % (inputPath, tempPath)) else: ttFont.save(outFilePath) ttFont.close() else: data = ttFont["CFF "].compile(ttFont) if fontType == 1: # CFF if overwriteOriginal: tf = file(tempPath, "wb") tf.write(data) tf.close() os.rename(tempPath, inputPath) else: tf = file(outFilePath, "wb") tf.write(data) tf.close() elif fontType == 2: # PS. tf = file(tempPath, "wb") tf.write(data) tf.close() finalPath = outFilePath command = "%s -t1 -std \"%s\" \"%s\" 2>&1" % (txPath, tempPath, outFilePath) report = FDKUtils.runShellCmd(command) logMsg(report) if "fatal" in report: raise IOError( "Failed to convert hinted font temp file with tx %s. Maybe target font font file '%s' is set to read-only." % (tempPath, outFilePath)) if os.path.exists(tempPath): os.remove(tempPath)
def saveChanges(self): ttFont = self.ttFont fontType = self.fontType inputPath = self.inputPath outFilePath = self.outFilePath overwriteOriginal = 0 if inputPath == outFilePath: overwriteOriginal = 1 tempPath = inputPath + ".temp.ac" if fontType == 0: # OTF if overwriteOriginal: ttFont.save(tempPath) ttFont.close() if os.path.exists(inputPath): try: os.remove(inputPath) os.rename(tempPath, inputPath) except (OSError, IOError): self.logMsg( "\t%s" %(traceback.format_exception_only(sys.exc_type, sys.exc_value)[-1])) self.logMsg("Error: could not overwrite original font file path '%s'. Hinted font file path is '%s'." % (inputPath, tempPath)) else: ttFont.save(outFilePath) ttFont.close() else: data = ttFont["CFF "].compile(ttFont) if fontType == 1: # CFF if overwriteOriginal: tf = file(tempPath, "wb") tf.write(data) tf.close() os.rename(tempPath, inputPath) else: tf = file(outFilePath, "wb") tf.write(data) tf.close() elif fontType == 2: # PS. tf = file(tempPath, "wb") tf.write(data) tf.close() finalPath = outFilePath command="tx -t1 -std \"%s\" \"%s\" 2>&1" % (tempPath, outFilePath) report = FDKUtils.runShellCmd(command) self.logMsg(report) if "fatal" in report: raise IOError("Failed to convert hinted font temp file with tx %s. Maybe target font font file '%s' is set to read-only." % (tempPath, outFilePath)) if os.path.exists(tempPath): os.remove(tempPath)
def CheckEnvironment(): txPath = 'tx' txError = 0 command = "%s -u 2>&1" % (txPath) report = FDKUtils.runShellCmd(command) if "Copyright" not in report: txError = 1 if txError: logMsg( "Please re-install the FDK. The path to the program 'tx' is not in the environment variable PATH." ) raise FDKEnvironmentError command = "outlineCheck -u 2>&1" report = FDKUtils.runShellCmd(command) if "version" not in report: logMsg( "Please re-install the FDK. The path to the program o'utlineCheck' is not in the environment variable PATH." ) raise FDKEnvironmentError return txPath
def CheckEnvironment(): txPath = 'tx' txError = 0 command = "%s -u 2>&1" % (txPath) report = FDKUtils.runShellCmd(command) if "options" not in report: txError = 1 if txError: logMsg("Please re-install the FDK. The executable directory \"%s\" is missing the tool: < %s >." % (exe_dir, txPath )) logMsg("or the files referenced by the shell script is missing.") raise FDKEnvironmentError return txPath
def mergeFontToCFF(srcPath, outputPath, doSubr): # We assume that srcPath is a type 1 font,and outputPath is an OTF font. # First, convert src font to cff, and subroutinize it if so requested. tempPath = srcPath + ".temp.cff" subrArg = "" if doSubr: subrArg=" +S" command="tx -cff +b%s \"%s\" \"%s\" 2>&1" % (subrArg, srcPath, tempPath) report = FDKUtils.runShellCmd(command) if ("fatal" in report) or ("error" in report): print report raise FontInfoParseError("Failed to convert font '%s' to CFF." % (fontPath)) # Now merge it into the output file. command="sfntedit -a CFF=\"%s\" \"%s\" 2>&1" % (tempPath, outputPath) report = FDKUtils.runShellCmd(command) if not debug: if os.path.exists(tempPath): os.remove(tempPath) if ("fatal" in report) or ("error" in report): print report raise FontInfoParseError("Failed to convert font '%s' to CFF." % (fontPath))
def getFontBBox(fPath): fontBBox = [-200, -200,1000,100] command = "tx -dump -0 %s 2>&1" % (fPath) data = FDKUtils.runShellCmd(command) if not data: raise FontInfoParseError("Error: Failed getting log from tx from %, when tryingg to get FontBBox." % (fPath)) m = re.search(r"FontBBox[^{]+{([^}]+)}", data) if not m: print data raise FontInfoParseError("Error: Failed finding FontBBox in tx log from %s." % (fPath)) fontBBox = m.group(1).split(",") fontBBox = map(int, fontBBox) return fontBBox
def getGlyphList(fPath, removeNotdef = 0): command = "tx -dump -4 %s 2>&1" % (fPath) data = FDKUtils.runShellCmd(command) if not data: print "Error: Failed getting glyph names from %s with tx." % (fPath) return [] nameList = re.findall(r"[\r\n]glyph.+?{([^,]+),", data) # initial newline keeps us from picking up the first column header line if not nameList: print "Error: Failed getting glyph names from %s with tx." % (fPath) print data return [] if removeNotdef: nameList.remove(".notdef") return nameList
def makeCIDFontInfo(fontPath, cidfontinfoPath): cfiDict = {} for key in kRequiredCIDFontInfoFields + kOptionalFields: cfiDict[key] = None cfiDict["Weight"] = "(Regular)" cfiDict["AdobeCopyright"] = "0" # get regular FontDict. command = "tx -0 \"%s\" 2>&1" % (fontPath) report = FDKUtils.runShellCmd(command) if ("fatal" in report) or ("error" in report): print report raise FontInfoParseError( "Failed to dump font dict using tx from font '%s'" % (fontPath)) for entry in txFields: match = re.search(entry[0] + "\s+(.+?)[\r\n]", report) if match: entry[2] = match.group(1) cfiDict["Registry"] = "Adobe" cfiDict["Ordering"] = "Identity" cfiDict["Supplement"] = "0" for entry in txFields: if entry[2]: cfiDict[entry[1]] = entry[2] elif entry[1] in kRequiredCIDFontInfoFields: haveError = 1 print "Error: did not find required info '%s' in tx dump of font '%s'." % ( entry[1], fontPath) try: fp = open(cidfontinfoPath, "wt") for key in kCIDFontInfokeyList: value = cfiDict[key] if value == None: continue if value[0] == "\"": value = "(" + value[1:-1] + ")" string = "%s\t%s" % (key, value) fp.write(string + os.linesep) fp.write(os.linesep) fp.close() except (IOError, OSError): msg = "Error. Could not open and write file '%s'" % (cidfontinfoPath) raise FontInfoParseError(msg) return
def CheckEnvironment(): txPath = 'tx' txError = 0 command = "%s -u 2>&1" % (txPath) report = FDKUtils.runShellCmd(command) if "Copyright" not in report: txError = 1 if txError: logMsg( "Please re-install the FDK. The executable directory \"%s\" is missing the tool: < %s >." % (exe_dir, txPath)) logMsg("or the files referenced by the shell script is missing.") raise FDKEnvironmentError return txPath
def makeCIDFontInfo(fontPath, cidfontinfoPath): cfiDict = {} for key in kRequiredCIDFontInfoFields + kOptionalFields: cfiDict[key] = None cfiDict["Weight"] = "(Regular)" cfiDict["AdobeCopyright"] = "0" # get regular FontDict. command="tx -0 \"%s\" 2>&1" % (fontPath) report = FDKUtils.runShellCmd(command) if ("fatal" in report) or ("error" in report): print report raise FontInfoParseError("Failed to dump font dict using tx from font '%s'" % (fontPath)) for entry in txFields: match = re.search(entry[0]+ "\s+(.+?)[\r\n]", report) if match: entry[2] = match.group(1) cfiDict["Registry"] = "Adobe" cfiDict["Ordering"] = "Identity" cfiDict["Supplement"] = "0" for entry in txFields: if entry[2]: cfiDict[entry[1]] = entry[2] elif entry[1] in kRequiredCIDFontInfoFields: haveError = 1 print "Error: did not find required info '%s' in tx dump of font '%s'." % (entry[1], fontPath) try: fp = open(cidfontinfoPath, "wt") for key in kCIDFontInfokeyList: value = cfiDict[key] if value == None: continue if value[0] == "\"": value = "(" + value[1:-1] + ")" string = "%s\t%s" % (key, value) fp.write(string + os.linesep) fp.write(os.linesep) fp.close() except (IOError, OSError): msg = "Error. Could not open and write file '%s'" % (cidfontinfoPath) raise FontInfoParseError(msg) return
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 checkFile(path, options): # use fontTools library to open font and extract CFF table. # If error, skip font and report error. seenChangedGlyph = 0 fontFileName = os.path.basename(path) logMsg("Checking font %s. Start time: %s." % (path, time.asctime())) try: fontData = openFile(path, options.outFilePath, options.allowChanges) fontData.allowDecimalCoords = options.allowDecimalCoords except (IOError, OSError): logMsg( traceback.format_exception_only(sys.exc_type, sys.exc_value)[-1]) raise focusFontError("Error opening or reading from font file <%s>." % fontFileName) except: logMsg( traceback.format_exception_only(sys.exc_type, sys.exc_value)[-1]) raise focusFontError("Error parsing font file <%s>." % fontFileName) # filter specified list, if any, with font list. fontGlyphList = fontData.getGlyphList() glyphList = filterGlyphList(options, fontGlyphList, fontFileName) if not glyphList: raise focusFontError( "Error: selected glyph list is empty for font <%s>." % fontFileName) removeHints = 1 reportCB = logMsg reportText = "" # If the user has not specified an em-square, and the font has am em-square other than 1000, supply it. if not options.emSquare: emSquare = fontData.getUnitsPerEm() if emSquare != "1000": options.emSquare = emSquare arg_string = buildArgString(options) dotCount = 0 seenGlyphCount = 0 processedGlyphCount = 0 for name in glyphList: seenGlyphCount += 1 if options.beVerbose: logMsg("Checking %s -- ," % (aliasName(name))) # output message when -v option is used else: logMsg(".,") dotCount += 1 if dotCount > 40: dotCount = 0 logMsg( "") # I do this to never have more than 40 dots on a line. # This in turn give reasonable performance when calling checkOutlines in a subprocess # and getting output with std.readline() if os.path.exists(kTempFilepathOut): os.remove(kTempFilepathOut) # Convert to bez format bezString, width, hasHints = fontData.convertToBez( name, removeHints, options.beVerbose, options.checkAll) if bezString == None: continue processedGlyphCount += 1 if "mt" not in bezString: # skip empty glyphs. continue # write bez file and run checkoutlinesexe fp = open(kTempFilepath, "wt") fp.write(bezString) fp.close() command = "checkoutlinesexe -o %s %s" % (arg_string, kTempFilepath) if debug: print "calling command", command log = FDKUtils.runShellCmd(command) # The suffix saying what bez file was written isn't useful here. log = re.sub(r"Wrote fixed file.+\s*", "", log) if log: if not options.beVerbose: dotCount = 0 logMsg("") logMsg("Checking %s -- ," % (aliasName(name)) ) # output message when -v option is NOT used logMsg(log) else: logMsg(log) if options.allowChanges and os.path.exists(kTempFilepathOut): fp = open(kTempFilepathOut, "rt") bezData = fp.read() fp.close() if bezData != bezString: fontData.updateFromBez(bezData, name, width, options.beVerbose) seenChangedGlyph = 1 if not options.beVerbose: logMsg("") if seenChangedGlyph: # save fontFile. fontData.saveChanges() else: fontData.close() if processedGlyphCount != seenGlyphCount: logMsg("Skipped %s of %s glyphs." % (seenGlyphCount - processedGlyphCount, seenGlyphCount)) logMsg("Done with font %s. End time: %s." % (path, time.asctime())) if debug: print "Temp bez file in", kTempFilepath print "Temp bez file out", kTempFilepathOut else: if os.path.exists(kTempFilepathOut): os.remove(kTempFilepathOut) if os.path.exists(kTempFilepath): os.remove(kTempFilepath) # remove temp file left over from openFile. tempPathCFF = path + kTempCFFSuffix if os.path.exists(tempPathCFF): os.remove(tempPathCFF)
def collectStemsFont(path, options, txPath): # use fontTools library to open font and extract CFF table. # If error, skip font and report error. fontFileName = os.path.basename(path) logMsg("") if options.doAlign: logMsg("Collecting alignment zones for font %s. Start time: %s." % (path, time.asctime())) else: logMsg("Collecting stems for font %s. Start time: %s." % (path, time.asctime())) try: fontData = openFile(path) except (IOError, OSError): logMsg( traceback.format_exception_only(sys.exc_type, sys.exc_value)[-1]) raise ACFontError("Error opening or reading from font file <%s>." % fontFileName) except: logMsg( traceback.format_exception_only(sys.exc_type, sys.exc_value)[-1]) raise ACFontError("Error parsing font file <%s>." % fontFileName) # filter specified list, if any, with font list. fontGlyphList = fontData.getGlyphList() glyphList = filterGlyphList(options, fontGlyphList, fontFileName) if not glyphList: raise ACFontError( "Error: selected glyph list is empty for font <%s>." % fontFileName) tempBaseName = os.tempnam() tempBez = tempBaseName + ".bez" tempReport = tempBez + ".rpt" tempFI = tempBaseName + ".fi" # open font plist file, if any. If not, create empty font plist. psName = fontData.getPSName() # build alignment zone string allow_no_blues = 1 noFlex = 0 vCounterGlyphs = hCounterGlyphs = [] fdGlyphDict, fontDictList = fontData.getfdInfo(psName, path, allow_no_blues, noFlex, vCounterGlyphs, hCounterGlyphs, glyphList) if fdGlyphDict == None: fdDict = fontDictList[0] fp = open(tempFI, "wt") fp.write(fdDict.getFontInfo()) fp.close() else: if not options.verbose: logMsg( "Note: Using alternate FDDict global values from fontinfo file for some glyphs. Remove option '-q' to see which dict is used for which glyphs." ) removeHints = 1 isCID = fontData.isCID() lastFDIndex = None glyphReports = GlyphReports() if not options.verbose: dotCount = 0 curTime = time.time() for name in glyphList: if name == ".notdef": continue if options.verbose: logMsg("Checking %s." % name) else: newTime = time.time() if (newTime - curTime) > 1: print ".", sys.stdout.flush() curTime = newTime dotCount += 1 if dotCount > 40: dotCount = 0 print "" # Convert to bez format bezString, width, hasHints = fontData.convertToBez( name, removeHints, options.verbose) if bezString == None: continue if "mt" not in bezString: # skip empty glyphs. continue # get new fontinfo string if FD array index has changed, as # as each FontDict has different alignment zones. gid = fontData.getGlyphID(name) if isCID: # fdIndex = fontData.getfdIndex(gid) if not fdIndex == lastFDIndex: lastFDIndex = fdIndex fdDict = fontData.getFontInfo(psName, path, options.allow_no_blues, options.noFlex, options.vCounterGlyphs, options.hCounterGlyphs, fdIndex) fp = open(tempFI, "wt") fp.write(fdDict.getFontInfo()) fp.close() else: if (fdGlyphDict != None): try: fdIndex = fdGlyphDict[name][0] except KeyError: # use default dict. fdIndex = 0 if lastFDIndex != fdIndex: lastFDIndex = fdIndex fdDict = fontDictList[fdIndex] fp = open(tempFI, "wt") fp.write(fdDict.getFontInfo()) fp.close() glyphReports.startGlyphName(name) # Call auto-hint library on bez string. bp = open(tempBez, "wt") bp.write(bezString) bp.close() if os.path.exists(tempReport): os.remove(tempReport) if options.doAlign: doAlign = "-ra" else: doAlign = "-rs" if options.allStems: allStems = "-a" else: allStems = "" command = "autohintexe -q %s %s -f \"%s\" \"%s\" 2>&1" % ( doAlign, allStems, tempFI, tempBez) if options.debug: print command log = FDKUtils.runShellCmd(command) if log: print log if "number terminator while" in log: print tempBez sys.exit() if os.path.exists(tempReport): bp = open(tempReport, "rt") report = bp.read() bp.close() if options.debug: print "Wrote AC fontinfo data file to", tempFI print "Wrote AC output rpt file to", tempReport else: os.remove(tempReport) report.strip() if report: glyphReports.addGlyphReport(report) if options.debug: rawData.append(report) else: print "Error - failure in processing outline data" report = None hStemDict, vStemDict, topZoneDict, bottomZoneDict = glyphReports.getReportDicts( ) if options.reportPath: reportPath = options.reportPath else: reportPath = path PrintReports(reportPath, hStemDict, vStemDict, topZoneDict, bottomZoneDict) fontData.close() logMsg("Done with font %s. End time: %s." % (path, time.asctime()))
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
def getOptions(): global gLogFile options = ACOptions() i = 1 numOptions = len(sys.argv) while i < numOptions: arg = sys.argv[i] if options.inputPath: raise ACOptionParseError("Option Error: All options must preceed the input font path <%s>." % arg) if arg == "-h": print __help__ command = "autohintexe -v" report = FDKUtils.runShellCmd(command) logMsg( report) raise ACOptionParseError elif arg == "-u": print __usage__ command = "autohintexe -v" report = FDKUtils.runShellCmd(command) logMsg( report) raise ACOptionParseError elif arg == "-hfd": print __FDDoc__ raise ACOptionParseError elif arg == "-pfd": options.printDefaultFDDict = 1 elif arg == "-pfdl": options.printFDDictList = 1 elif arg == "-hf": options.usePlistFile = 1 elif arg == "-a": options.hintAll = 1 elif arg == "-all": options.hintAll = 1 elif arg == "-r": options.rehint = 1 elif arg == "-q": options.verbose = 0 elif arg == "-c": options.allowChanges = 1 elif arg == "-nf": options.noFlex = 1 elif arg == "-ns": options.noHintSub = 1 elif arg == "-nb": options.allow_no_blues = 1 elif arg in ["-xg", "-g"]: if arg == "-xg": options.excludeGlyphList = 1 i = i +1 glyphString = sys.argv[i] if glyphString[0] == "-": raise ACOptionParseError("Option Error: it looks like the first item in the glyph list following '-g' is another option.") options.glyphList += parseGlyphListArg(glyphString) elif arg in ["-xgf", "-gf"]: if arg == "-xgf": options.excludeGlyphList = 1 i = i +1 filePath = sys.argv[i] if filePath[0] == "-": raise ACOptionParseError("Option Error: it looks like the the glyph list file following '-gf' is another option.") try: gf = file(filePath, "rt") glyphString = gf.read() gf.close() except (IOError,OSError): raise ACOptionParseError("Option Error: could not open glyph list file <%s>." % filePath) options.glyphList += parseGlyphListArg(glyphString) elif arg == "-cf": i = i +1 filePath = sys.argv[i] if filePath[0] == "-": raise ACOptionParseError("Option Error: it looks like the the counter hint glyph list file following '-cf' is another option.") try: options.counterHintFile = filePath options.hCounterGlyphs, options.vCounterGlyphs = parseCounterHintData(filePath) except (IOError,OSError): raise ACOptionParseError("Option Error: could not open counter hint glyph list file <%s>." % filePath) elif arg == "-logOnly": options.logOnly = 1 elif arg == "-log": i = i +1 options.logFilePath = sys.argv[i] gLogFile = open(options.logFilePath, "wt") elif arg == "-o": i = i +1 options.outputPath = sys.argv[i] elif arg == "-d": options.debug = 1 elif arg == "-decimal": options.allowDecimalCoords = True elif arg[0] == "-": raise ACOptionParseError("Option Error: Unknown option <%s>." % arg) else: options.inputPath = arg i += 1 if not options.inputPath: raise ACOptionParseError("Option Error: You must provide a font file path.") if not os.path.exists(options.inputPath): raise ACOptionParseError("Option Error: The input font file path %s' does not exist." % (options.inputPath)) else: options.inputPath = options.inputPath.rstrip(os.sep) # might be a UFO font. auto completion in some shells adds a dir separator, which then causes problems with os.path.dirname(). checkFontinfoFile(options) if options.logOnly: options.verbose = 1 options.hintAll = 1 return options
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 fixFontDict(tempPath, fdDict): txtPath = tempPath + ".txt" command = "detype1 %s %s 2>&1" % (tempPath, txtPath) log = FDKUtils.runShellCmd(command) if log: print log fp = open(txtPath, "rt") data = fp.read() fp.close() # fix font name. # We always search of it, as it is always present, and we can use the following # white space to get the file new line. m = re.search(r"(/FontName\s+/\S+\s+def)(\s+)", data) newLine = m.group(2) if not m: raise FontParseError("Failed to find FontName in input font!. %s" % (tempPath)) if fdDict.FontName: target = "/FontName /%s def" % (fdDict.FontName) data = data[:m.start(1)] + target + data[m.end(1):] # fix em square if fdDict.OrigEmSqUnits: m = re.search(r"/FontMatrix\s+\[.+?\]\s+def", data) if not m: raise FontParseError("Failed to find FontMatrix in input font! %s" % (tempPath)) emUnits = eval(fdDict.OrigEmSqUnits) a = 1.0/emUnits target = "/FontMatrix [%s 0 0 %s 0 0] def" % ( a, a) data = data[:m.start()] + target + data[m.end():] # fix StemSnapH. Remove StemSnapH if fdDict.StemSnapH is not defined, else set it. m = re.search(r"/StemSnapH\s+\[.+?\]\s+def", data) if fdDict.DominantH: target = "/StemSnapH %s def" % ( fdDict.DominantH) data = data[:m.start()] + target + data[m.end():] insertIndex = m.start() + len(target) else: data = data[:m.start()] + data[m.end():] # fix StemSnapV. Remove StemSnapV entry if fdDict.StemSnapV is not defined, else set it. m = re.search(r"/StemSnapV\s+\[.+?\]\s+def", data) if fdDict.DominantV: target = "/StemSnapV %s def" % ( fdDict.DominantV) data = data[:m.start()] + target + data[m.end():] insertIndex = m.start() + len(target) else: data = data[:m.start()] + data[m.end():] # LanguageGroup. Remove LanguageGroup entry if fdDict.LanguageGroup is not defined, else set it. if fdDict.LanguageGroup: m = re.search(r"/LanguageGroup\s+\d+\s+def", data) if not m: target = "%s/LanguageGroup %s def" % (newLine, fdDict.LanguageGroup) data = data[:insertIndex] + data[insertIndex] + target + data[insertIndex:] else: data = data[:m.start()] + target + data[m.end():] target = "/LanguageGroup %s def" % (fdDict.LanguageGroup) else: m = re.search(r"/LanguageGroup\s+\d+\s+def", data) if m: data = data[:m.start()] + data[m.end():] # Fix BlueValues. Must be present. m = re.search(r"/BlueValues\s+\[.+?\]\s+def", data) if not m: raise FontParseError("Failed to find BlueValues in input font! %s" % (tempPath)) target = "/BlueValues %s def" % (fdDict.BlueValues) data = data[:m.start()] + target + data[m.end():] insertIndex = m.start() + len(target) # Fix OtherBlues, if present. Remove if there are no OtherBlues entry. m = re.search(r"/OtherBlues\s+\[.+?\]\s+def", data) if fdDict.OtherBlues: if not m: target = "%s/OtherBlues %s def" % (newLine, fdDict.OtherBlues) data = data[:insertIndex] + target + data[insertIndex:] else: target = "/OtherBlues %s def" % (fdDict.OtherBlues) data = data[:m.start()] + target + data[m.end():] else: data = data[:m.start()] + data[m.end():] fp = open(txtPath, "wt") fp.write(data) fp.close() command = "type1 %s %s 2>&1" % (txtPath, tempPath) log = FDKUtils.runShellCmd(command) if log: print log if not debug: os.remove(txtPath) return
def hintFile(options): path = options.inputPath fontFileName = os.path.basename(path) logMsg("Hinting font %s. Start time: %s." % (path, time.asctime())) try: useHashMap = not options.logOnly # for UFO fonts only. We always use the hash map, unless the user has said to only report issues. fontData = openFile(path, options.outputPath, useHashMap) fontData.allowDecimalCoords = options.allowDecimalCoords if options.writeToDefaultLayer and hasattr(fontData, "setWriteToDefault"): # UFO fonts only fontData.setWriteToDefault() except (IOError, OSError): logMsg( traceback.format_exception_only(sys.exc_type, sys.exc_value)[-1]) raise ACFontError("Error opening or reading from font file <%s>." % fontFileName) except: logMsg( traceback.format_exception_only(sys.exc_type, sys.exc_value)[-1]) raise ACFontError("Error parsing font file <%s>." % fontFileName) # filter specified list, if any, with font list. fontGlyphList = fontData.getGlyphList() glyphList = filterGlyphList(options, fontGlyphList, fontFileName) if not glyphList: raise ACFontError("Error: selected glyph list is empty for font <%s>." % fontFileName) # temp file names for input and output bez files, and for the fontinfo file. tempBaseName = os.tempnam() tempBez = tempBaseName + ".bez" tempBezNew = tempBez + ".new" tempFI = tempBaseName + ".fi" #print "tempBaseName", tempBaseName psName = fontData.getPSName() if (not options.logOnly) and options.usePlistFile: fontPlist, fontPlistFilePath, isNewPlistFile = openFontPlistFile(psName, os.path.dirname(path)) if isNewPlistFile and not (options.hintAll or options.rehint): logMsg("No hint info plist file was found, so all glyphs are unknown to autohint. To hint all glyphs, run autohint again with option -a to hint all glyphs unconditionally.") logMsg("Done with font %s. End time: %s." % (path, time.asctime())) fontData.close() return # Check counter glyphs, if any. if options.hCounterGlyphs or options.vCounterGlyphs: missingList = filter(lambda name: name not in fontGlyphList, options.hCounterGlyphs + options.vCounterGlyphs) if missingList: logMsg( "\tError: glyph named in counter hint list file '%s' are not in font: %s" % (options.counterHintFile, missingList) ) # build alignment zone string if (options.printDefaultFDDict): logMsg("Showing default FDDict Values:") fdDict = fontData.getFontInfo(psName, path, options.allow_no_blues, options.noFlex, options.vCounterGlyphs, options.hCounterGlyphs) parseFontInfoString(str(fdDict)) fontData.close() return fdGlyphDict, fontDictList = fontData.getfdInfo(psName, path, options.allow_no_blues, options.noFlex, options.vCounterGlyphs, options.hCounterGlyphs, glyphList) if options.printFDDictList: # Print the user defined FontDicts, and exit. if fdGlyphDict: logMsg("Showing user-defined FontDict Values:") for fi in range(len(fontDictList)): fontDict = fontDictList[fi] logMsg("") logMsg(fontDict.DictName) parseFontInfoString(str(fontDict)) gnameList = [] itemList = fdGlyphDict.items() itemList.sort(cmpFDDictEntries) for gName, entry in itemList: if entry[0] == fi: gnameList.append(gName) logMsg("%d glyphs:" % len(gnameList)) if len(gnameList) > 0: gTxt = " ".join(gnameList) else: gTxt = "None" logMsg(gTxt) else: logMsg("There are no user-defined FontDict Values.") tempPathCFF = options.inputPath + kTempCFFSuffix removeTempFiles( [tempPathCFF] ) fontData.close() return if fdGlyphDict == None: fdDict = fontDictList[0] fp = open(tempFI, "wt") fp.write(fdDict.getFontInfo()) fp.close() else: if not options.verbose: logMsg("Note: Using alternate FDDict global values from fontinfo file for some glyphs. Remove option '-q' to see which dict is used for which glyphs.") # for identifier in glyph-list: # Get charstring. removeHints = 1 isCID = fontData.isCID() lastFDIndex = None reportCB = ACreport anyGlyphChanged = 0 pListChanged = 0 if isCID: options.noFlex = 1 if options.verbose: verboseArg = "" else: verboseArg = " -q" dotCount = 0 curTime = time.time() if options.allowChanges: suppressEditArg = "" else: suppressEditArg = " -e" if options.noHintSub: supressHintSubArg = " -n" else: supressHintSubArg = "" if options.allowDecimalCoords: decimalArg = " -d" else: decimalArg = "" dotCount = 0 seenGlyphCount = 0 processedGlyphCount = 0 for name in glyphList: prevACIdentifier = None seenGlyphCount +=1 # Convert to bez format bezString, width = fontData.convertToBez(name, removeHints, options.verbose, options.hintAll) processedGlyphCount += 1 if bezString == None: continue if "mt" not in bezString: # skip empty glyphs. continue # get new fontinfo string if FD array index has changed, as # as each FontDict has different alignment zones. gid = fontData.getGlyphID(name) if isCID: # fdIndex = fontData.getfdIndex(gid) if not fdIndex == lastFDIndex: lastFDIndex = fdIndex fdDict = fontData.getFontInfo(psName, path, options.allow_no_blues, options.noFlex, options.vCounterGlyphs, options.hCounterGlyphs, fdIndex) fp = open(tempFI, "wt") fp.write(fdDict.getFontInfo()) fp.close() else: if (fdGlyphDict != None): try: fdIndex = fdGlyphDict[name][0] except KeyError: # use default dict. fdIndex = 0 if lastFDIndex != fdIndex: lastFDIndex = fdIndex fdDict = fontDictList[fdIndex] fp = open(tempFI, "wt") fp.write(fdDict.getFontInfo()) fp.close() # Build autohint point list identifier oldBezString = "" oldHintBezString = "" if (not options.logOnly) and options.usePlistFile: # If the glyph is not in the plist file, then we skip it unless kReHintUnknown is set. # If the glyph is in the plist file and the outline has changed, we hint it. ACidentifier = makeACIdentifier(bezString) try: (prevACIdentifier, ACtime, oldBezString, oldHintBezString) = fontPlist[kACIDKey][name] except ValueError: (prevACIdentifier, ACtime) = fontPlist[kACIDKey][name] oldBezString = oldHintBezString = "" except KeyError: pListChanged = 1 # Didn't have an entry in tempList file, so we will add one. if hasHints and not (options.rehint): # Glyphs is hinted, but not referenced in the plist file. Skip it unless options.rehint is se if not isNewPlistFile: # Comment only if there is a plist file; otherwise, we'd be complaining for almost every glyph. logMsg("%s Skipping glyph - it has hints, but it is not in the hint info plist file." % aliasName(name)) dotCount = 0 continue if prevACIdentifier and (prevACIdentifier == ACidentifier): # there is an entry in the plist file and it matches what's in the font. if hasHints and not options.hintAll: continue else: pListChanged = 1 if options.verbose: if fdGlyphDict: logMsg("Hinting %s with fdDict %s." % (aliasName(name), fdDict.DictName) ) else: logMsg("Hinting %s." % aliasName(name)) else: logMsg(".,") dotCount += 1 if dotCount > 40: dotCount = 0 logMsg("") # I do this to never have more than 40 dots on a line. # This in turn give reasonable performance when calling autohint in a subprocess # and getting output with std.readline() # Call auto-hint library on bez string. bp = open(tempBez, "wt") bp.write(bezString) bp.close() #print "oldBezString", oldBezString #print "" #print "bezString", bezString if oldBezString != "" and oldBezString == bezString: newBezString = oldHintBezString else: if os.path.exists(tempBezNew): os.remove(tempBezNew) command = "autohintexe %s%s%s%s -s .new -f \"%s\" \"%s\"" % (verboseArg, suppressEditArg, supressHintSubArg, decimalArg, tempFI, tempBez) if options.debug: print command report = FDKUtils.runShellCmd(command) if report: if not options.verbose: logMsg("") # end series of "." logMsg(report) if os.path.exists(tempBezNew): bp = open(tempBezNew, "rt") newBezString = bp.read() bp.close() if options.debug: print "Wrote AC fontinfo data file to", tempFI print "Wrote AC output bez file to", tempBezNew else: os.remove(tempBezNew) else: newBezString = None if not newBezString: if not options.verbose: logMsg("") logMsg("%s Error - failure in processing outline data." % aliasName(name)) continue if not (("ry" in newBezString[:200]) or ("rb" in newBezString[:200]) or ("rm" in newBezString[:200]) or ("rv" in newBezString[:200])): print "No hints added!" if options.logOnly: continue # Convert bez to charstring, and update CFF. anyGlyphChanged = 1 fontData.updateFromBez(newBezString, name, width, options.verbose) if options.usePlistFile: bezString, hasHints, t2Wdth = convertT2GlyphToBez(t2CharString, 1) bezString = "%% %s%s%s" % (name, os.linesep, bezString) ACidentifier = makeACIdentifier(bezString) # add glyph hint entry to plist file if options.allowChanges: if prevACIdentifier and (prevACIdentifier != ACidentifier): logMsg("\t%s Glyph outline changed" % aliasName(name)) dotCount = 0 fontPlist[kACIDKey][name] = (ACidentifier, time.asctime(), bezString, newBezString ) if not options.verbose: print "" # print final new line after progress dots. if options.debug: print "Wrote input AC bez file to", tempBez else: tempPathCFF = options.inputPath + kTempCFFSuffix # created when a PS file is opened. removeTempFiles( [tempBez, tempBezNew, tempFI, tempPathCFF] ) if not options.logOnly: if anyGlyphChanged: logMsg("Saving font file with new hints..." + time.asctime()) fontData.saveChanges() else: fontData.close() if options.usePlistFile: if options.rehint: logMsg("No new hints. All glyphs had hints that matched the hint record file %s." % (fontPlistFilePath)) else: logMsg("No new hints. All glyphs were already hinted.") else: logMsg("No glyphs were hinted.") if options.usePlistFile and (anyGlyphChanged or pListChanged): # save font plist file. fontPlist.write(fontPlistFilePath) if processedGlyphCount != seenGlyphCount: logMsg("Skipped %s of %s glyphs." % (seenGlyphCount - processedGlyphCount, seenGlyphCount)) logMsg("Done with font %s. End time: %s." % (path, time.asctime()))
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 collectStemsFont(path, options, txPath): # use fontTools library to open font and extract CFF table. # If error, skip font and report error. fontFileName = os.path.basename(path) logMsg("") if options.doAlign: logMsg("Collecting alignment zones for font %s. Start time: %s." % (path, time.asctime())) else: logMsg("Collecting stems for font %s. Start time: %s." % (path, time.asctime())) ttFont, fontType = openFile(path, txPath) fontGlyphList = ttFont.getGlyphOrder() try: cffTable = ttFont["CFF "] except KeyError: raise ACFontError("Error: font is not a CFF font <%s>." % fontFileName) # filter specified list, if any, with font list. glyphList = filterGlyphList(options, fontGlyphList, fontFileName) # Never include the notdef in the glyph list. if not glyphList: raise ACFontError( "Error: selected glyph llist is empty for font <%s>." % fontFileName) tempBaseName = os.tempnam() tempBez = tempBaseName + ".bez" tempReport = tempBez + ".rpt" tempFI = tempBaseName + ".fi" # open font plist file, if any. If not, create empty font plist. psName = cffTable.cff.fontNames[0] # build alignment zone string topDict = cffTable.cff.topDictIndex[0] fontinfo = getFontInfo(topDict, psName, options) fp = open(tempFI, "wt") fp.write(fontinfo) fp.close() # for identifier in glyph-list: # Get charstring. charStrings = topDict.CharStrings charStringIndex = charStrings.charStringsIndex removeHints = 1 isCID = hasattr(topDict, "FDSelect") lastFDIndex = 0 glyphReports = GlyphReports() if not options.verbose: dotCount = 0 curTime = time.time() for name in glyphList: if name == ".notdef": continue if options.verbose: logMsg("Checking %s." % name) else: newTime = time.time() if (newTime - curTime) > 1: print ".", sys.stdout.flush() curTime = newTime dotCount += 1 if dotCount > 40: dotCount = 0 print "" # get new fontinfo string if FD array index has changed, as # as each FontDict has different alignment zones. if isCID: gid = ttFont.getGlyphID(name) fdIndex = topDict.FDSelect[gid] if not fdIndex == lastFDIndex: lastFDIndex = fdIndex fontinfo = getFontInfo(topDict, psName, options, fdIndex) fp = open(tempFI, "wt") fp.write(fontinfo) fp.close() else: gid = charStrings.charStrings[name] glyphReports.startGlyphName(name) # Convert to bez format t2CharString = charStringIndex[gid] try: bezString, hasHints, t2Wdth = convertT2GlyphToBez( t2CharString, removeHints) bezString = "%% %s%s" % (name, os.linesep) + bezString except SEACError: print "Skipping %s; can't process 'seac' composite glyphs." % ( name) continue # Call auto-hint library on bez string. # Call auto-hint library on bez string. bp = open(tempBez, "wt") bp.write(bezString) bp.close() if os.path.exists(tempReport): os.remove(tempReport) if options.doAlign: doAlign = "-ra" else: doAlign = "-rs" if options.allStems: allStems = "-a" else: allStems = "" command = "autohintexe -q %s %s -f \"%s\" \"%s\"" % ( doAlign, allStems, tempFI, tempBez) if options.debug: print command log = FDKUtils.runShellCmd(command) if log: print log if os.path.exists(tempReport): bp = open(tempReport, "rt") report = bp.read() bp.close() if options.debug: print "Wrote AC fontinfo data file to", tempFI print "Wrote AC output rpt file to", tempReport else: os.remove(tempReport) report.strip() if report: glyphReports.addGlyphReport(report) if options.debug: rawData.append(report) else: print "Error - failure in processing outline data" report = None hStemDict, vStemDict, topZoneDict, bottomZoneDict = glyphReports.getReportDicts( ) if options.reportPath: reportPath = options.reportPath else: reportPath = path PrintReports(reportPath, hStemDict, vStemDict, topZoneDict, bottomZoneDict) ttFont.close() logMsg("Done with font %s. End time: %s." % (path, time.asctime()))
def proofMakePDF(pathList, params, txPath): # use fontTools library to open font and extract CFF table. # If error, skip font and report error. if params.rt_doFontSet: pdfFontList = [] logMsg("") logMsg( "Collecting font data from:") fontCount = 0 for path in pathList: fontFileName = os.path.basename(path) params.rt_filePath = os.path.abspath(path) logMsg( "\t%s." % (path)) try: ttFont, tempPathCFF = openFile(path, txPath) except FontError: print traceback.format_exception_only(sys.exc_type, sys.exc_value)[-1] return try: fontGlyphList = ttFont.getGlyphOrder() except FontError: print traceback.format_exception_only(sys.exc_type, sys.exc_value)[-1] return # filter specified list, if any, with font list. glyphList = filterGlyphList(params, fontGlyphList, fontFileName) if not glyphList: raise FontError("Error: selected glyph list is empty for font <%s>." % fontFileName) params.rt_reporter = logMsg if ttFont.has_key("CFF "): pdfFont = otfPDF.txPDFFont(ttFont, params) elif ttFont.has_key("glyf"): pdfFont = ttfPDF.txPDFFont(ttFont, params) else: logMsg( "Quitting. Font type is not recognized. %s.." % (path)) break if tempPathCFF: pdfFont.path = tempPathCFF pdfFontList.append([glyphList, pdfFont, tempPathCFF]) pdfFilePath = makeFontSetPDF(pdfFontList, params) for entry in pdfFontList: tempPathCFF = entry[2] pdfFont = entry[1] pdfFont.clientFont.close() if tempPathCFF: os.remove(tempPathCFF) logMsg( "Wrote proof file %s. End time: %s." % (pdfFilePath, time.asctime())) if pdfFilePath and params.openPDFWhenDone: if curSystem == "Windows": curdir = os.getcwdu() basedir, pdfName = os.path.split(pdfFilePath) os.chdir(basedir) command = "start %s" % (pdfName) print command FDKUtils.runShellCmdLogging(command) os.chdir(curdir) elif os.name == "Linux": command = "xdg-open \"" + pdfFilePath + "\"" + " &" FDKUtils.runShellCmdLogging(command) else: command = "open \"" + pdfFilePath + "\"" + " &" FDKUtils.runShellCmdLogging(command) else: tmpList = [] for path in pathList: fontFileName = os.path.basename(path) params.rt_filePath = os.path.abspath(path) logMsg("") logMsg( "Proofing font %s. Start time: %s." % (path, time.asctime())) try: ttFont, tempPathCFF = openFile(path, txPath) fontGlyphList = ttFont.getGlyphOrder() except FontError: print traceback.format_exception_only(sys.exc_type, sys.exc_value)[-1] return # filter specified list, if any, with font list. params.rt_glyphList = filterGlyphList(params, fontGlyphList, fontFileName) if not params.rt_glyphList: raise FontError("Error: selected glyph list is empty for font <%s>." % fontFileName) params.rt_reporter = logMsg if ttFont.has_key("CFF "): pdfFont = otfPDF.txPDFFont(ttFont, params) elif ttFont.has_key("glyf"): pdfFont = ttfPDF.txPDFFont(ttFont, params) else: logMsg( "Quitting. Font type is not recognized. %s.." % (path)) return if tempPathCFF: pdfFont.path = tempPathCFF pdfFilePath = makePDF(pdfFont, params) ttFont.close() if tempPathCFF: os.remove(tempPathCFF) logMsg( "Wrote proof file %s. End time: %s." % (pdfFilePath, time.asctime())) if pdfFilePath and params.openPDFWhenDone: if curSystem == "Windows": curdir = os.getcwdu() basedir, pdfName = os.path.split(pdfFilePath) os.chdir(basedir) command = "start %s" % (pdfName) print command FDKUtils.runShellCmdLogging(command) os.chdir(curdir) elif curSystem == "Linux": command = "xdg-open \"" + pdfFilePath + "\"" + " &" print command FDKUtils.runShellCmdLogging(command) else: command = "open \"" + pdfFilePath + "\"" + " &" FDKUtils.runShellCmdLogging(command)
def checkFile(path, options): # use fontTools library to open font and extract CFF table. # If error, skip font and report error. seenChangedGlyph = 0 fontFileName = os.path.basename(path) logMsg("Checking font %s. Start time: %s." % (path, time.asctime())) try: fontData = openFile(path, options.outFilePath, options.allowChanges) fontData.allowDecimalCoords = options.allowDecimalCoords except (IOError, OSError): logMsg( traceback.format_exception_only(sys.exc_type, sys.exc_value)[-1]) raise focusFontError("Error opening or reading from font file <%s>." % fontFileName) except: logMsg( traceback.format_exception_only(sys.exc_type, sys.exc_value)[-1]) raise focusFontError("Error parsing font file <%s>." % fontFileName) # filter specified list, if any, with font list. fontGlyphList = fontData.getGlyphList() glyphList = filterGlyphList(options, fontGlyphList, fontFileName) if not glyphList: raise focusFontError("Error: selected glyph list is empty for font <%s>." % fontFileName) removeHints = 1 reportCB = logMsg reportText = "" # If the user has not specified an em-square, and the font has am em-square other than 1000, supply it. if not options.emSquare: emSquare = fontData.getUnitsPerEm() if emSquare != "1000": options.emSquare = emSquare arg_string = buildArgString(options) dotCount = 0 seenGlyphCount = 0 processedGlyphCount = 0 for name in glyphList: seenGlyphCount +=1 if options.beVerbose: logMsg("Checking %s -- ," % ( aliasName(name) )) # output message when -v option is used else: logMsg(".,") dotCount += 1 if dotCount > 40: dotCount = 0 logMsg("") # I do this to never have more than 40 dots on a line. # This in turn give reasonable performance when calling checkOutlines in a subprocess # and getting output with std.readline() if os.path.exists(kTempFilepathOut): os.remove(kTempFilepathOut) # Convert to bez format bezString, width, hasHints = fontData.convertToBez(name, removeHints, options.beVerbose, options.checkAll) if bezString == None: continue processedGlyphCount += 1 if "mt" not in bezString: # skip empty glyphs. continue # write bez file and run checkoutlinesexe fp = open(kTempFilepath, "wt") fp.write(bezString) fp.close() command = "checkoutlinesexe -o %s %s" % (arg_string, kTempFilepath) if debug: print "calling command", command log = FDKUtils.runShellCmd(command) # The suffix saying what bez file was written isn't useful here. log = re.sub(r"Wrote fixed file.+\s*", "", log) if log: if not options.beVerbose: dotCount = 0 logMsg("") logMsg("Checking %s -- ," % (aliasName(name))) # output message when -v option is NOT used logMsg(log) else: logMsg(log) if options.allowChanges and os.path.exists(kTempFilepathOut): fp = open(kTempFilepathOut, "rt") bezData = fp.read() fp.close() if bezData != bezString: fontData.updateFromBez(bezData, name, width, options.beVerbose) seenChangedGlyph = 1 if not options.beVerbose: logMsg("") if seenChangedGlyph: # save fontFile. fontData.saveChanges() else: fontData.close() if processedGlyphCount != seenGlyphCount: logMsg("Skipped %s of %s glyphs." % (seenGlyphCount - processedGlyphCount, seenGlyphCount)) logMsg("Done with font %s. End time: %s." % (path, time.asctime())) if debug: print "Temp bez file in", kTempFilepath print "Temp bez file out", kTempFilepathOut else: if os.path.exists(kTempFilepathOut): os.remove(kTempFilepathOut) if os.path.exists(kTempFilepath): os.remove(kTempFilepath) # remove temp file left over from openFile. tempPathCFF = path + kTempCFFSuffix if os.path.exists(tempPathCFF): os.remove(tempPathCFF)
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 getOptions(): global gLogFile options = ACOptions() i = 1 numOptions = len(sys.argv) while i < numOptions: arg = sys.argv[i] if options.inputPath: raise ACOptionParseError("Option Error: All options must preceed the input font path <%s>." % arg) if arg == "-h": print __help__ command = "autohintexe -v" report = FDKUtils.runShellCmd(command) logMsg( report) raise ACOptionParseError elif arg == "-u": print __usage__ command = "autohintexe -v" report = FDKUtils.runShellCmd(command) logMsg( report) raise ACOptionParseError elif arg == "-hfd": print __FDDoc__ raise ACOptionParseError elif arg == "-pfd": options.printDefaultFDDict = 1 elif arg == "-pfdl": options.printFDDictList = 1 elif arg == "-hf": options.usePlistFile = 1 elif arg == "-a": options.hintAll = 1 elif arg == "-all": options.hintAll = 1 elif arg == "-r": options.rehint = 1 elif arg == "-q": options.verbose = 0 elif arg == "-c": options.allowChanges = 1 elif arg == "-nf": options.noFlex = 1 elif arg == "-ns": options.noHintSub = 1 elif arg == "-nb": options.allow_no_blues = 1 elif arg in ["-xg", "-g"]: if arg == "-xg": options.excludeGlyphList = 1 i = i +1 glyphString = sys.argv[i] if glyphString[0] == "-": raise ACOptionParseError("Option Error: it looks like the first item in the glyph list following '-g' is another option.") options.glyphList += parseGlyphListArg(glyphString) elif arg in ["-xgf", "-gf"]: if arg == "-xgf": options.excludeGlyphList = 1 i = i +1 filePath = sys.argv[i] if filePath[0] == "-": raise ACOptionParseError("Option Error: it looks like the the glyph list file following '-gf' is another option.") try: gf = file(filePath, "rt") glyphString = gf.read() gf.close() except (IOError,OSError): raise ACOptionParseError("Option Error: could not open glyph list file <%s>." % filePath) options.glyphList += parseGlyphListArg(glyphString) elif arg == "-cf": i = i +1 filePath = sys.argv[i] if filePath[0] == "-": raise ACOptionParseError("Option Error: it looks like the the counter hint glyph list file following '-cf' is another option.") try: options.counterHintFile = filePath options.hCounterGlyphs, options.vCounterGlyphs = parseCounterHintData(filePath) except (IOError,OSError): raise ACOptionParseError("Option Error: could not open counter hint glyph list file <%s>." % filePath) elif arg == "-logOnly": options.logOnly = 1 elif arg == "-log": i = i +1 options.logFilePath = sys.argv[i] gLogFile = open(options.logFilePath, "wt") elif arg == "-o": i = i +1 options.outputPath = sys.argv[i] elif arg == "-d": options.debug = 1 elif arg == "-decimal": options.allowDecimalCoords = True elif arg =="-wd": options.writeToDefaultLayer = 1 elif arg[0] == "-": raise ACOptionParseError("Option Error: Unknown option <%s>." % arg) else: options.inputPath = arg i += 1 if not options.inputPath: raise ACOptionParseError("Option Error: You must provide a font file path.") if not os.path.exists(options.inputPath): raise ACOptionParseError("Option Error: The input font file path %s' does not exist." % (options.inputPath)) else: options.inputPath = options.inputPath.rstrip(os.sep) # might be a UFO font. auto completion in some shells adds a dir separator, which then causes problems with os.path.dirname(). checkFontinfoFile(options) if options.logOnly: options.verbose = 1 options.hintAll = 1 return options
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 hintFile(options): path = options.inputPath fontFileName = os.path.basename(path) logMsg("Hinting font %s. Start time: %s." % (path, time.asctime())) try: useHashMap = not options.logOnly # for UFO fonts only. We always use the hash map, unless the user has said to only report issues. fontData = openFile(path, options.outputPath, useHashMap) fontData.allowDecimalCoords = options.allowDecimalCoords except (IOError, OSError): logMsg( traceback.format_exception_only(sys.exc_type, sys.exc_value)[-1]) raise ACFontError("Error opening or reading from font file <%s>." % fontFileName) except: logMsg( traceback.format_exception_only(sys.exc_type, sys.exc_value)[-1]) raise ACFontError("Error parsing font file <%s>." % fontFileName) # filter specified list, if any, with font list. fontGlyphList = fontData.getGlyphList() glyphList = filterGlyphList(options, fontGlyphList, fontFileName) if not glyphList: raise ACFontError("Error: selected glyph list is empty for font <%s>." % fontFileName) # temp file names for input and output bez files, and for the fontinfo file. tempBaseName = os.tempnam() tempBez = tempBaseName + ".bez" tempBezNew = tempBez + ".new" tempFI = tempBaseName + ".fi" #print "tempBaseName", tempBaseName psName = fontData.getPSName() if (not options.logOnly) and options.usePlistFile: fontPlist, fontPlistFilePath, isNewPlistFile = openFontPlistFile(psName, os.path.dirname(path)) if isNewPlistFile and not (options.hintAll or options.rehint): logMsg("No hint info plist file was found, so all glyphs are unknown to autohint. To hint all glyphs, run autohint again with option -a to hint all glyphs unconditionally.") logMsg("Done with font %s. End time: %s." % (path, time.asctime())) fontData.close() return # Check counter glyphs, if any. if options.hCounterGlyphs or options.vCounterGlyphs: missingList = filter(lambda name: name not in fontGlyphList, options.hCounterGlyphs + options.vCounterGlyphs) if missingList: logMsg( "\tError: glyph named in counter hint list file '%s' are not in font: %s" % (options.counterHintFile, missingList) ) # build alignment zone string if (options.printDefaultFDDict): logMsg("Showing default FDDict Values:") fdDict = fontData.getFontInfo(psName, path, options.allow_no_blues, options.noFlex, options.vCounterGlyphs, options.hCounterGlyphs) parseFontInfoString(str(fdDict)) fontData.close() return fdGlyphDict, fontDictList = fontData.getfdInfo(psName, path, options.allow_no_blues, options.noFlex, options.vCounterGlyphs, options.hCounterGlyphs, glyphList) if options.printFDDictList: # Print the user defined FontDicts, and exit. if fdGlyphDict: logMsg("Showing user-defined FontDict Values:") for fi in range(len(fontDictList)): fontDict = fontDictList[fi] logMsg("") logMsg(fontDict.DictName) parseFontInfoString(str(fontDict)) gnameList = [] itemList = fdGlyphDict.items() itemList.sort(cmpFDDictEntries) for gName, entry in itemList: if entry[0] == fi: gnameList.append(gName) logMsg("%d glyphs:" % len(gnameList)) if len(gnameList) > 0: gTxt = " ".join(gnameList) else: gTxt = "None" logMsg(gTxt) else: logMsg("There are no user-defined FontDict Values.") tempPathCFF = options.inputPath + kTempCFFSuffix removeTempFiles( [tempPathCFF] ) fontData.close() return if fdGlyphDict == None: fdDict = fontDictList[0] fp = open(tempFI, "wt") fp.write(fdDict.getFontInfo()) fp.close() else: if not options.verbose: logMsg("Note: Using alternate FDDict global values from fontinfo file for some glyphs. Remove option '-q' to see which dict is used for which glyphs.") # for identifier in glyph-list: # Get charstring. removeHints = 1 isCID = fontData.isCID() lastFDIndex = None reportCB = ACreport anyGlyphChanged = 0 pListChanged = 0 if isCID: options.noFlex = 1 if options.verbose: verboseArg = "" else: verboseArg = " -q" dotCount = 0 curTime = time.time() if options.allowChanges: suppressEditArg = "" else: suppressEditArg = " -e" if options.noHintSub: supressHintSubArg = " -n" else: supressHintSubArg = "" if options.allowDecimalCoords: decimalArg = " -d" else: decimalArg = "" dotCount = 0 seenGlyphCount = 0 processedGlyphCount = 0 for name in glyphList: prevACIdentifier = None seenGlyphCount +=1 # Convert to bez format bezString, width = fontData.convertToBez(name, removeHints, options.verbose, options.hintAll) processedGlyphCount += 1 if bezString == None: continue if "mt" not in bezString: # skip empty glyphs. continue # get new fontinfo string if FD array index has changed, as # as each FontDict has different alignment zones. gid = fontData.getGlyphID(name) if isCID: # fdIndex = fontData.getfdIndex(gid) if not fdIndex == lastFDIndex: lastFDIndex = fdIndex fdDict = fontData.getFontInfo(psName, path, options.allow_no_blues, options.noFlex, options.vCounterGlyphs, options.hCounterGlyphs, fdIndex) fp = open(tempFI, "wt") fp.write(fdDict.getFontInfo()) fp.close() else: if (fdGlyphDict != None): try: fdIndex = fdGlyphDict[name][0] except KeyError: # use default dict. fdIndex = 0 if lastFDIndex != fdIndex: lastFDIndex = fdIndex fdDict = fontDictList[fdIndex] fp = open(tempFI, "wt") fp.write(fdDict.getFontInfo()) fp.close() # Build autohint point list identifier oldBezString = "" oldHintBezString = "" if (not options.logOnly) and options.usePlistFile: # If the glyph is not in the plist file, then we skip it unless kReHintUnknown is set. # If the glyph is in the plist file and the outline has changed, we hint it. ACidentifier = makeACIdentifier(bezString) try: (prevACIdentifier, ACtime, oldBezString, oldHintBezString) = fontPlist[kACIDKey][name] except ValueError: (prevACIdentifier, ACtime) = fontPlist[kACIDKey][name] oldBezString = oldHintBezString = "" except KeyError: pListChanged = 1 # Didn't have an entry in tempList file, so we will add one. if hasHints and not (options.rehint): # Glyphs is hinted, but not referenced in the plist file. Skip it unless options.rehint is se if not isNewPlistFile: # Comment only if there is a plist file; otherwise, we'd be complaining for almost every glyph. logMsg("%s Skipping glyph - it has hints, but it is not in the hint info plist file." % aliasName(name)) dotCount = 0 continue if prevACIdentifier and (prevACIdentifier == ACidentifier): # there is an entry in the plist file and it matches what's in the font. if hasHints and not options.hintAll: continue else: pListChanged = 1 if options.verbose: if fdGlyphDict: logMsg("Hinting %s with fdDict %s." % (aliasName(name), fdDict.DictName) ) else: logMsg("Hinting %s." % aliasName(name)) else: logMsg(".,") dotCount += 1 if dotCount > 40: dotCount = 0 logMsg("") # I do this to never have more than 40 dots on a line. # This in turn give reasonable performance when calling autohint in a subprocess # and getting output with std.readline() # Call auto-hint library on bez string. bp = open(tempBez, "wt") bp.write(bezString) bp.close() #print "oldBezString", oldBezString #print "" #print "bezString", bezString if oldBezString != "" and oldBezString == bezString: newBezString = oldHintBezString else: if os.path.exists(tempBezNew): os.remove(tempBezNew) command = "autohintexe %s%s%s%s -s .new -f \"%s\" \"%s\"" % (verboseArg, suppressEditArg, supressHintSubArg, decimalArg, tempFI, tempBez) if options.debug: print command report = FDKUtils.runShellCmd(command) if report: if not options.verbose: logMsg("") # end series of "." logMsg(report) if os.path.exists(tempBezNew): bp = open(tempBezNew, "rt") newBezString = bp.read() bp.close() if options.debug: print "Wrote AC fontinfo data file to", tempFI print "Wrote AC output bez file to", tempBezNew else: os.remove(tempBezNew) else: newBezString = None if not newBezString: if not options.verbose: logMsg("") logMsg("%s Error - failure in processing outline data." % aliasName(name)) continue if not (("ry" in newBezString[:200]) or ("rb" in newBezString[:200]) or ("rm" in newBezString[:200]) or ("rv" in newBezString[:200])): print "No hints added!" if options.logOnly: continue # Convert bez to charstring, and update CFF. anyGlyphChanged = 1 fontData.updateFromBez(newBezString, name, width, options.verbose) if options.usePlistFile: bezString, hasHints, t2Wdth = convertT2GlyphToBez(t2CharString, 1) bezString = "%% %s%s%s" % (name, os.linesep, bezString) ACidentifier = makeACIdentifier(bezString) # add glyph hint entry to plist file if options.allowChanges: if prevACIdentifier and (prevACIdentifier != ACidentifier): logMsg("\t%s Glyph outline changed" % aliasName(name)) dotCount = 0 fontPlist[kACIDKey][name] = (ACidentifier, time.asctime(), bezString, newBezString ) if not options.verbose: print "" # print final new line after progress dots. if options.debug: print "Wrote input AC bez file to", tempBez else: tempPathCFF = options.inputPath + kTempCFFSuffix # created when a PS file is opened. removeTempFiles( [tempBez, tempBezNew, tempFI, tempPathCFF] ) if not options.logOnly: if anyGlyphChanged: logMsg("Saving font file with new hints..." + time.asctime()) fontData.saveChanges() else: fontData.close() if options.usePlistFile: if options.rehint: logMsg("No new hints. All glyphs had hints that matched the hint record file %s." % (fontPlistFilePath)) else: logMsg("No new hints. All glyphs were already hinted.") else: logMsg("No glyphs were hinted.") if options.usePlistFile and (anyGlyphChanged or pListChanged): # save font plist file. fontPlist.write(fontPlistFilePath) if processedGlyphCount != seenGlyphCount: logMsg("Skipped %s of %s glyphs." % (seenGlyphCount - processedGlyphCount, seenGlyphCount)) logMsg("Done with font %s. End time: %s." % (path, time.asctime()))
def collectStemsFont(path, options, txPath): # use fontTools library to open font and extract CFF table. # If error, skip font and report error. fontFileName = os.path.basename(path) logMsg("") if options.doAlign: logMsg( "Collecting alignment zones for font %s. Start time: %s." % (path, time.asctime())) else: logMsg( "Collecting stems for font %s. Start time: %s." % (path, time.asctime())) try: fontData = openFile(path) except (IOError, OSError): logMsg( traceback.format_exception_only(sys.exc_type, sys.exc_value)[-1]) raise ACFontError("Error opening or reading from font file <%s>." % fontFileName) except: logMsg( traceback.format_exception_only(sys.exc_type, sys.exc_value)[-1]) raise ACFontError("Error parsing font file <%s>." % fontFileName) # filter specified list, if any, with font list. fontGlyphList = fontData.getGlyphList() glyphList = filterGlyphList(options, fontGlyphList, fontFileName) if not glyphList: raise ACFontError("Error: selected glyph list is empty for font <%s>." % fontFileName) tempBaseName = os.tempnam() tempBez = tempBaseName + ".bez" tempReport = tempBez + ".rpt" tempFI = tempBaseName + ".fi" # open font plist file, if any. If not, create empty font plist. psName = fontData.getPSName() # build alignment zone string allow_no_blues = 1 noFlex = 0 vCounterGlyphs = hCounterGlyphs = [] fdGlyphDict, fontDictList = fontData.getfdInfo(psName, path, allow_no_blues, noFlex, vCounterGlyphs, hCounterGlyphs, glyphList) if fdGlyphDict == None: fdDict = fontDictList[0] fp = open(tempFI, "wt") fp.write(fdDict.getFontInfo()) fp.close() else: if not options.verbose: logMsg("Note: Using alternate FDDict global values from fontinfo file for some glyphs. Remove option '-q' to see which dict is used for which glyphs.") removeHints = 1 isCID = fontData.isCID() lastFDIndex = None glyphReports = GlyphReports() if not options.verbose: dotCount = 0 curTime = time.time() for name in glyphList: if name == ".notdef": continue if options.verbose: logMsg("Checking %s." %name) else: newTime = time.time() if (newTime - curTime) > 1: print ".", sys.stdout.flush() curTime = newTime dotCount +=1 if dotCount > 40: dotCount = 0 print "" # Convert to bez format bezString, width, hasHints = fontData.convertToBez(name, removeHints, options.verbose) if bezString == None: continue if "mt" not in bezString: # skip empty glyphs. continue # get new fontinfo string if FD array index has changed, as # as each FontDict has different alignment zones. gid = fontData.getGlyphID(name) if isCID: # fdIndex = fontData.getfdIndex(gid) if not fdIndex == lastFDIndex: lastFDIndex = fdIndex fdDict = fontData.getFontInfo(psName, path, options.allow_no_blues, options.noFlex, options.vCounterGlyphs, options.hCounterGlyphs, fdIndex) fp = open(tempFI, "wt") fp.write(fdDict.getFontInfo()) fp.close() else: if (fdGlyphDict != None): try: fdIndex = fdGlyphDict[name][0] except KeyError: # use default dict. fdIndex = 0 if lastFDIndex != fdIndex: lastFDIndex = fdIndex fdDict = fontDictList[fdIndex] fp = open(tempFI, "wt") fp.write(fdDict.getFontInfo()) fp.close() glyphReports.startGlyphName(name) # Call auto-hint library on bez string. bp = open(tempBez, "wt") bp.write(bezString) bp.close() if os.path.exists(tempReport): os.remove(tempReport) if options.doAlign: doAlign = "-ra" else: doAlign = "-rs" if options.allStems: allStems = "-a" else: allStems = "" command = "autohintexe -q %s %s -f \"%s\" \"%s\" 2>&1" % (doAlign, allStems, tempFI, tempBez) if options.debug: print command log = FDKUtils.runShellCmd(command) if log: print log if "number terminator while" in log: print tempBez sys.exit() if os.path.exists(tempReport): bp = open(tempReport, "rt") report = bp.read() bp.close() print "report <%s>" % (report) if options.debug: print "Wrote AC fontinfo data file to", tempFI print "Wrote AC output rpt file to", tempReport else: os.remove(tempReport) report.strip() if report: glyphReports.addGlyphReport(report) if options.debug: rawData.append(report) else: print "Error - failure in processing outline data" report = None hStemDict, vStemDict,topZoneDict, bottomZoneDict = glyphReports.getReportDicts() if options.reportPath: reportPath = options.reportPath else: reportPath = path PrintReports(reportPath, hStemDict, vStemDict,topZoneDict, bottomZoneDict) fontData.close() logMsg( "Done with font %s. End time: %s." % (path, time.asctime()))
def checkFile(path, options, txPath): # use fontTools library to open font and extract CFF table. # If error, skip font and report error. seenChangedGlyph = 0 fontFileName = os.path.basename(path) logMsg("Checking font %s. Start time: %s." % (path, time.asctime())) try: ttFont, fontType = openFile(path, txPath) fontGlyphList = ttFont.getGlyphOrder() except (IOError, OSError): logMsg( traceback.format_exception_only(sys.exc_type, sys.exc_value)[-1]) raise focusFontError("Error opening or reading from font file <%s>." % fontFileName) except: logMsg( traceback.format_exception_only(sys.exc_type, sys.exc_value)[-1]) raise focusFontError("Error parsing font file <%s>." % fontFileName) try: cffTable = ttFont["CFF "] topDict = cffTable.cff.topDictIndex[0] except KeyError: raise focusFontError("Error: font is not a CFF font <%s>." % fontFileName) # filter specified list, if any, with font list. glyphList = filterGlyphList(options, fontGlyphList, fontFileName) if not glyphList: raise focusFontError( "Error: selected glyph list is empty for font <%s>." % fontFileName) # for identifier in glyph-list: # Get charstring. charStrings = topDict.CharStrings charStringIndex = charStrings.charStringsIndex removeHints = 1 isCID = hasattr(topDict, "FDSelect") lastFDIndex = 0 reportCB = logMsg reportText = "" # If the user has not specified an em-square, and the font has am em-square other than 1000, supply it. if not options.emSquare: if hasattr(topDict, "FontMatrix"): matrix = topDict.FontMatrix emSquare = "%s" % (int(round(1.0 / matrix[0]))) if emSquare != "1000": options.emSquare = emSquare arg_string = buildArgString(options) dotCount = 0 for name in glyphList: if options.beVerbose: logMsg("Checking %s -- ," % (aliasName(name))) # output message when -v option is used else: logMsg(".,") dotCount += 1 if dotCount > 40: dotCount = 0 logMsg( "") # I do this to never have more than 40 dots on a line. # This in turn give reasonable performance when calling checkOutlines in a subprocess # and getting output with std.readline() if os.path.exists(kTempFilepathOut): os.remove(kTempFilepathOut) # Convert to bez format gid = charStrings.charStrings[name] t2CharString = charStringIndex[gid] try: bezString, hasHints, t2Wdth = convertT2GlyphToBez( t2CharString, removeHints) except SEACError: if not options.beVerbose: dotCount = 0 logMsg("") # end series of "." logMsg("Checking %s -- ," % (aliasName(name)) ) # output message when SEAC glyph is found logMsg("Skipping %s: can't process SEAC composite glyphs." % (aliasName(name))) continue # skip SEAC composite glyphs. fp = open(kTempFilepath, "wt") fp.write(bezString) fp.close() command = "outlineCheck -o %s %s" % (arg_string, kTempFilepath) if debug: print "calling command", command log = FDKUtils.runShellCmd(command) # The suffix saying what bez file was written isn't useful here. log = re.sub(r"Wrote fixed file.+\s*", "", log) if log: if not options.beVerbose: dotCount = 0 logMsg("") logMsg("Checking %s -- ," % (aliasName(name)) ) # output message when -v option is NOT used logMsg(log) else: logMsg(log) if options.allowChanges and os.path.exists(kTempFilepathOut): fp = open(kTempFilepathOut, "rt") bezData = fp.read() fp.close() if bezData != bezString: t2Program = [t2Wdth] + convertBezToT2(bezData) if t2Program: t2CharString.program = t2Program else: if not options.beVerbose: dotCount = 0 logMsg("") # end series of "." logMsg("Checking %s -- ," % (aliasName(name))) logMsg("Skipping %s: error in processing fixed outline." % (aliasName(name))) seenChangedGlyph = 1 if not options.beVerbose: logMsg("") if seenChangedGlyph: # save fontFile. logMsg("Writing changed file %s" % (options.outFilePath)) saveFontFile(ttFont, path, options.outFilePath, fontType, txPath) logMsg("Done with font %s. End time: %s." % (path, time.asctime())) if debug: print "Temp bez file in", kTempFilepath print "Temp bez file out", kTempFilepathOut else: if os.path.exists(kTempFilepathOut): os.remove(kTempFilepathOut) if os.path.exists(kTempFilepath): os.remove(kTempFilepath) # remove temp file left over from openFile. tempPathCFF = path + kTempCFFSuffix if os.path.exists(tempPathCFF): os.remove(tempPathCFF)
def fixFontDict(tempPath, fdDict): txtPath = tempPath + ".txt" command = "detype1 %s %s 2>&1" % (tempPath, txtPath) log = FDKUtils.runShellCmd(command) if log: print log fp = open(txtPath, "rt") data = fp.read() fp.close() # fix font name. # We always search of it, as it is always present, and we can use the following # white space to get the file new line. m = re.search(r"(/FontName\s+/\S+\s+def)(\s+)", data) newLine = m.group(2) if not m: raise FontParseError("Failed to find FontName in input font!. %s" % (tempPath)) if fdDict.FontName: target = "/FontName /%s def" % (fdDict.FontName) data = data[:m.start(1)] + target + data[m.end(1):] # fix em square if fdDict.OrigEmSqUnits: m = re.search(r"/FontMatrix\s+\[.+?\]\s+def", data) if not m: raise FontParseError( "Failed to find FontMatrix in input font! %s" % (tempPath)) emUnits = eval(fdDict.OrigEmSqUnits) a = 1.0 / emUnits target = "/FontMatrix [%s 0 0 %s 0 0] def" % (a, a) data = data[:m.start()] + target + data[m.end():] # fix StemSnapH. Remove StemSnapH if fdDict.StemSnapH is not defined, else set it. m = re.search(r"/StemSnapH\s+\[.+?\]\s+def", data) if fdDict.DominantH: target = "/StemSnapH %s def" % (fdDict.DominantH) data = data[:m.start()] + target + data[m.end():] insertIndex = m.start() + len(target) else: data = data[:m.start()] + data[m.end():] # fix StemSnapV. Remove StemSnapV entry if fdDict.StemSnapV is not defined, else set it. m = re.search(r"/StemSnapV\s+\[.+?\]\s+def", data) if fdDict.DominantV: target = "/StemSnapV %s def" % (fdDict.DominantV) data = data[:m.start()] + target + data[m.end():] insertIndex = m.start() + len(target) else: data = data[:m.start()] + data[m.end():] # LanguageGroup. Remove LanguageGroup entry if fdDict.LanguageGroup is not defined, else set it. if fdDict.LanguageGroup: m = re.search(r"/LanguageGroup\s+\d+\s+def", data) if not m: target = "%s/LanguageGroup %s def" % (newLine, fdDict.LanguageGroup) data = data[:insertIndex] + data[insertIndex] + target + data[ insertIndex:] else: data = data[:m.start()] + target + data[m.end():] target = "/LanguageGroup %s def" % (fdDict.LanguageGroup) else: m = re.search(r"/LanguageGroup\s+\d+\s+def", data) if m: data = data[:m.start()] + data[m.end():] # Fix BlueValues. Must be present. m = re.search(r"/BlueValues\s+\[.+?\]\s+def", data) if not m: raise FontParseError("Failed to find BlueValues in input font! %s" % (tempPath)) target = "/BlueValues %s def" % (fdDict.BlueValues) data = data[:m.start()] + target + data[m.end():] insertIndex = m.start() + len(target) # Fix OtherBlues, if present. Remove if there are no OtherBlues entry. m = re.search(r"/OtherBlues\s+\[.+?\]\s+def", data) if fdDict.OtherBlues: if not m: target = "%s/OtherBlues %s def" % (newLine, fdDict.OtherBlues) data = data[:insertIndex] + target + data[insertIndex:] else: target = "/OtherBlues %s def" % (fdDict.OtherBlues) data = data[:m.start()] + target + data[m.end():] else: data = data[:m.start()] + data[m.end():] fp = open(txtPath, "wt") fp.write(data) fp.close() command = "type1 %s %s 2>&1" % (txtPath, tempPath) log = FDKUtils.runShellCmd(command) if log: print log if not debug: os.remove(txtPath) return