def __init__(self, path, font_format): self.inputPath = path self.font_format = font_format if font_format == "OTF": # It is an OTF font, we can process it directly. font = TTFont(path) if "CFF " not in font: raise FontParseError("OTF font has no CFF table <%s>." % path) else: # Else, package it in an OTF font. if font_format == "CFF": with open(path, "rb") as fp: data = fp.read() else: fd, temp_path = tempfile.mkstemp() os.close(fd) try: _run_tx(["-cff", "+b", "-std", path, temp_path]) with open(temp_path, "rb") as fp: data = fp.read() finally: os.remove(temp_path) font = TTFont() font['CFF '] = newTable('CFF ') font['CFF '].decompile(data, font) self.ttFont = font self.cffTable = font["CFF "] # for identifier in glyph-list: # Get charstring. self.topDict = self.cffTable.cff.topDictIndex[0] self.charStrings = self.topDict.CharStrings
def _compileReferencedFeatureFile(inPath, outPath, relativePath, font, verbose=False, recursionDepth=0): """ Compile the file given in inPath and write it to outPath. """ if not os.path.exists(inPath): # XXX silently fail here? return # compile and write this file with open(inPath, "r") as f: text = f.read() text, referencedFiles = _compileFeatureText(text, font, relativePath, verbose=verbose, recursionDepth=recursionDepth) with open(outPath, "w") as f: f.write(text) # recurse through the referenced files for referenceInPath, referenceOutPath in referencedFiles: _compileReferencedFeatureFile(referenceInPath, referenceOutPath, relativePath, font, verbose=verbose, recursionDepth=recursionDepth + 1)
def assert_equals_test_file(path, test_filename): with open(path) as fp: actual = fp.read() test_path = os.path.join(os.path.dirname(__file__), test_filename) with open(test_path) as fp: expected = fp.read() assert actual == expected
def test_unicodes(tmpdir): tmpdir = str(tmpdir) testDocPath = os.path.join(tmpdir, "testUnicodes.designspace") testDocPath2 = os.path.join(tmpdir, "testUnicodes_roundtrip.designspace") masterPath1 = os.path.join(tmpdir, "masters", "masterTest1.ufo") masterPath2 = os.path.join(tmpdir, "masters", "masterTest2.ufo") instancePath1 = os.path.join(tmpdir, "instances", "instanceTest1.ufo") instancePath2 = os.path.join(tmpdir, "instances", "instanceTest2.ufo") doc = DesignSpaceDocument() # add master 1 s1 = SourceDescriptor() s1.filename = os.path.relpath(masterPath1, os.path.dirname(testDocPath)) s1.name = "master.ufo1" s1.copyInfo = True s1.location = dict(weight=0) doc.addSource(s1) # add master 2 s2 = SourceDescriptor() s2.filename = os.path.relpath(masterPath2, os.path.dirname(testDocPath)) s2.name = "master.ufo2" s2.location = dict(weight=1000) doc.addSource(s2) # add instance 1 i1 = InstanceDescriptor() i1.filename = os.path.relpath(instancePath1, os.path.dirname(testDocPath)) i1.name = "instance.ufo1" i1.location = dict(weight=500) glyphData = dict(name="arrow", mute=True, unicodes=[100, 200, 300]) i1.glyphs['arrow'] = glyphData doc.addInstance(i1) # now we have sources and instances, but no axes yet. doc.axes = [] # clear the axes # write some axes a1 = AxisDescriptor() a1.minimum = 0 a1.maximum = 1000 a1.default = 0 a1.name = "weight" a1.tag = "wght" doc.addAxis(a1) # write the document doc.write(testDocPath) assert os.path.exists(testDocPath) # import it again new = DesignSpaceDocument() new.read(testDocPath) new.write(testDocPath2) # compare the file contents f1 = open(testDocPath, 'r', encoding='utf-8') t1 = f1.read() f1.close() f2 = open(testDocPath2, 'r', encoding='utf-8') t2 = f2.read() f2.close() assert t1 == t2 # check the unicode values read from the document assert new.instances[0].glyphs['arrow']['unicodes'] == [100, 200, 300]
def __removeConditionMinimumMaximumDesignSpace(path): # only for testing, so we can make an invalid designspace file # without making the designSpaceDocument also support it. f = open(path, 'r', encoding='utf-8') d = f.read() f.close() d = d.replace(' minimum="100"', '') f = open(path, 'w', encoding='utf-8') f.write(d) f.close()
def _addUnwrappedCondition(path): # only for testing, so we can make an invalid designspace file # older designspace files may have conditions that are not wrapped in a conditionset # These can be read into a new conditionset. with open(path, 'r', encoding='utf-8') as f: d = f.read() print(d) d = d.replace('<rule name="named.rule.1">', '<rule name="named.rule.1">\n\t<condition maximum="22" minimum="33" name="axisName_a" />') with open(path, 'w', encoding='utf-8') as f: f.write(d)
def test_unicodes(tmpdir): tmpdir = str(tmpdir) testDocPath = os.path.join(tmpdir, "testUnicodes.designspace") testDocPath2 = os.path.join(tmpdir, "testUnicodes_roundtrip.designspace") masterPath1 = os.path.join(tmpdir, "masters", "masterTest1.ufo") masterPath2 = os.path.join(tmpdir, "masters", "masterTest2.ufo") instancePath1 = os.path.join(tmpdir, "instances", "instanceTest1.ufo") instancePath2 = os.path.join(tmpdir, "instances", "instanceTest2.ufo") doc = DesignSpaceDocument() # add master 1 s1 = SourceDescriptor() s1.filename = os.path.relpath(masterPath1, os.path.dirname(testDocPath)) s1.name = "master.ufo1" s1.copyInfo = True s1.location = dict(weight=0) doc.addSource(s1) # add master 2 s2 = SourceDescriptor() s2.filename = os.path.relpath(masterPath2, os.path.dirname(testDocPath)) s2.name = "master.ufo2" s2.location = dict(weight=1000) doc.addSource(s2) # add instance 1 i1 = InstanceDescriptor() i1.filename = os.path.relpath(instancePath1, os.path.dirname(testDocPath)) i1.name = "instance.ufo1" i1.location = dict(weight=500) glyphData = dict(name="arrow", mute=True, unicodes=[100, 200, 300]) i1.glyphs['arrow'] = glyphData doc.addInstance(i1) # now we have sources and instances, but no axes yet. doc.axes = [] # clear the axes # write some axes a1 = AxisDescriptor() a1.minimum = 0 a1.maximum = 1000 a1.default = 0 a1.name = "weight" a1.tag = "wght" doc.addAxis(a1) # write the document doc.write(testDocPath) assert os.path.exists(testDocPath) # import it again new = DesignSpaceDocument() new.read(testDocPath) new.write(testDocPath2) # compare the file contents with open(testDocPath, 'r', encoding='utf-8') as f1: t1 = f1.read() with open(testDocPath2, 'r', encoding='utf-8') as f2: t2 = f2.read() assert t1 == t2 # check the unicode values read from the document assert new.instances[0].glyphs['arrow']['unicodes'] == [100,200,300]
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 readDesignSpaceFile(options): """ Read design space file. build a new instancesList with all the instances from the ds file - Promote all the source and instance filename attributes from relative to absolute paths - Write a temporary ds file - Return a path to the temporary ds file, and the current instances list. """ instanceEntryList = [] logger.info("Reading design space file '%s' ..." % options.dsPath) with open(options.dsPath, "r", encoding='utf-8') as f: data = f.read() ds = ET.XML(data) instances = ds.find("instances") # Remove any instances that are not in the specified list of instance # indices, from the option -i. if options.indexList: newInstanceXMLList = instances.findall("instance") numInstances = len(newInstanceXMLList) instanceIndex = numInstances while instanceIndex > 0: instanceIndex -= 1 instanceXML = newInstanceXMLList[instanceIndex] if instanceIndex not in options.indexList: instances.remove(instanceXML) # We want to build all remaining instances. for instanceXML in instances: familyName = instanceXML.attrib["familyname"] styleName = instanceXML.attrib["stylename"] curPath = instanceXML.attrib["filename"] logger.info("Adding %s %s to build list." % (familyName, styleName)) instanceEntryList.append(curPath) if os.path.exists(curPath): glyphDir = os.path.join(curPath, "glyphs") if os.path.exists(glyphDir): shutil.rmtree(glyphDir, ignore_errors=True) if not instanceEntryList: logger.error("Failed to find any instances in the ds file '%s' that " "have the postscriptfilename attribute" % options.dsPath) return None, None dsPath = "{}.temp".format(options.dsPath) data = ET.tostring(ds) with open(dsPath, "wb") as f: f.write(tobytes(data, encoding='utf-8')) return dsPath, instanceEntryList
def __removeAxesFromDesignSpace(path): # only for testing, so we can make an invalid designspace file # without making the designSpaceDocument also support it. f = open(path, 'r', encoding='utf-8') d = f.read() f.close() start = d.find("<axes>") end = d.find("</axes>") + len("</axes>") n = d[0:start] + d[end:] f = open(path, 'w', encoding='utf-8') f.write(n) f.close()
def _expect_designspace(self, doc, expected_path): actual_path = self.write_to_tmp_path(doc, "generated.designspace") with open(actual_path, mode="r", encoding="utf-8") as f: actual = f.readlines() with open(expected_path, mode="r", encoding="utf-8") as f: expected = f.readlines() if actual != expected: expected_name = os.path.basename(expected_path) for line in difflib.unified_diff( expected, actual, fromfile=expected_name, tofile="<generated>" ): sys.stderr.write(line) self.fail("*.designspace file is different from expected")
def _check_save_path(path_str): test_path = os.path.abspath(os.path.realpath(path_str)) del_test_file = True try: if os.path.exists(test_path): del_test_file = False open(test_path, 'a').close() if del_test_file: os.remove(test_path) except (IOError, OSError): raise argparse.ArgumentTypeError( "{} is not a valid path to write to.".format(test_path)) return test_path
def build(self, featureFile): path = self.temp_path(suffix=".fea") with open(path, "w", encoding="utf-8") as outfile: outfile.write(featureFile) font = makeTTFont() addOpenTypeFeatures(path, font) return font
def getOptions(): options = ACOptions() i = 1 numOptions = len(sys.argv) while i < numOptions: arg = sys.argv[i] if options.fileList and arg[0] == "-": raise ACOptionParseError("Option Error: All file names must follow all other options <%s>." % arg) if arg == "-h": logMsg(__help__) sys.exit(0) elif arg == "-u": logMsg(__usage__) sys.exit(0) elif arg == "-all": options.allStems = 1 elif arg == "-a": options.doAlign = 1 elif arg == "-d": options.debug = 1 elif arg == "-q": options.verbose = 0 elif arg == "-o": i = i +1 options.reportPath = sys.argv[i] elif arg == "-new": options.new = 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: with open(filePath, "r", encoding='utf-8') as gf: glyphString = gf.read() except (IOError,OSError): raise ACOptionParseError("Option Error: could not open glyph list file <%s>." % filePath) options.glyphList += parseGlyphListArg(glyphString) elif arg[0] == "-": raise ACOptionParseError("Option Error: Unknown option <%s>." % arg) else: arg = arg.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(). options.fileList += [arg] i += 1 if not options.fileList: raise ACOptionParseError("Option Error: You must provide at least one font file path.") return options
def convertFontToCID(inputPath, outputPath, fontinfoPath=None): """ Takes in a path to the font file to convert, a path to save the result, and an optional path to a '(cid)fontinfo' file. """ if fontinfoPath and os.path.exists(fontinfoPath): with open(fontinfoPath, "r", encoding='utf-8') as fi: fontInfoData = fi.read() else: fontInfoData = '' glyphList = getGlyphList(inputPath, False, True) fontBBox = getFontBBox(inputPath) fontName = getFontName(inputPath) blueFuzz = getBlueFuzz(inputPath) maxY = fontBBox[3] minY = fontBBox[1] # Start with an uninitialized entry for the default FDDict 0 fontDictList = [FDDict()] fdGlyphDict, fontDictList, _ = parseFontInfoFile( fontDictList, fontInfoData, glyphList, maxY, minY, fontName, blueFuzz) glyphSetList = makeSortedGlyphLists(glyphList, fdGlyphDict) fontList = makeTempFonts(fontDictList, glyphSetList, fdGlyphDict, inputPath) merge_fonts(inputPath, outputPath, fontList, glyphList, fontDictList, fdGlyphDict)
def _read_txt_file(self, path): """ Reads a text file and returns a list of its lines. """ # Hard code a first line; this way the difflib results start # from 1 instead of zero, thus matching the file's line numbers lines = [''] try: with open(path, "r", encoding=self.encoding) as f: for i, line in enumerate(f.readlines(), 1): # Skip lines that change, such as timestamps if self._line_to_skip(line): logger.debug("Matched begin of line. Skipped: {}" "".format(line.rstrip())) # Blank the line instead of actually skipping (via # 'continue'); this way the difflib results show the # correct line numbers line = '' # Skip specific lines, referenced by number elif i in self.skip_lines: logger.debug("Matched line #{}. Skipped: {}" "".format(i, line.rstrip())) line = '' # Skip lines that match regex elif self.skip_regex and self.skip_regex.match(line): logger.debug("Matched regex begin of line. Skipped: {}" "".format(line.rstrip())) line = '' # Use os-native line separator to enable running difflib lines.append(line.rstrip() + os.linesep) except UnicodeDecodeError: logger.error("Couldn't read text file using '{}' encoding.\n" " File path: {}".format(self.encoding, path)) sys.exit(1) return lines
def parseLayoutFile(filePath): layoutDict = None try: fp = open(filePath, "rt") data = fp.read() fp.close() except (IOError, OSError): raise OptionParseError( "Option Error: could not open and read the layout file <%s>." % filePath) entryList = re.findall(r"(\d+)\s+(\S+)\s+(\S+)\s+(\S+)", data) if len(entryList) < 2: print( "Error: Failed to parse layout file %s. Did not match expected entry format." % filePath) raise OptionParseError print("Found %s entries in layout file %s. Mac CID: %s" % (len(entryList), os.path.basename(filePath), entryList[-1][0])) layoutDict = {} index = 0 for entry in entryList: gname = "cid" + entry[0].zfill(5) layoutDict[gname] = [entry[1], entry[2], entry[3]] # add cide key-> family, face dir, name reversekey = repr(entry[1:]) layoutDict[reversekey] = [ gname, entry[1], entry[2], entry[3] ] # add name key -> family, face, cid as cidXXXX entryList = None if "cid00000" in layoutDict: layoutDict[".notdef"] = layoutDict["cid00000"] return layoutDict
def _read_txt_file(self, path): """ Reads a text file and returns a list of its lines. """ # Hard code a first line; this way the difflib results start # from 1 instead of zero, thus matching the file's line numbers lines = [''] try: with open(path, "r", encoding=self.encoding) as f: for i, line in enumerate(f.readlines(), 1): # Skip lines that change, such as timestamps if self._line_to_skip(line): logger.debug("Matched begin of line. Skipped: {}" "".format(line.rstrip())) # Blank the line instead of actually skipping (via # 'continue'); this way the difflib results show the # correct line numbers line = '' # Skip specific lines, referenced by number elif i in self.skip_lines: logger.debug("Matched line #{}. Skipped: {}" "".format(i, line.rstrip())) # Blank the line instead of actually skipping (via # 'continue'); this way the difflib results show the # correct line numbers line = '' # Use os-native line separator to enable running difflib lines.append(line.rstrip() + os.linesep) except UnicodeDecodeError: logger.error("Couldn't read text file using '{}' encoding.\n" " File path: {}".format(self.encoding, path)) sys.exit(1) return lines
def PrintReports(path, h_stem_list, v_stem_list, top_zone_list, bot_zone_list): items = ([h_stem_list, srtCnt], [v_stem_list, srtCnt], [top_zone_list, srtRevVal], [bot_zone_list, srtVal]) atime = time.asctime() suffixes = (".hstm.txt", ".vstm.txt", ".top.txt", ".bot.txt") titles = ( "Horizontal Stem List for %s on %s\n" % (path, atime), "Vertical Stem List for %s on %s\n" % (path, atime), "Top Zone List for %s on %s\n" % (path, atime), "Bottom Zone List for %s on %s\n" % (path, atime), ) headers = ( "Count\tWidth\tGlyph List\n", "Count\tWidth\tGlyph List\n", "Count\tTop Zone\tGlyph List\n", "Count\tBottom Zone\tGlyph List\n", ) for i, item in enumerate(items): rep_list, sortFunc = item if not rep_list: continue fName = '{}{}'.format(path, suffixes[i]) title = titles[i] header = headers[i] try: with open(fName, "w") as fp: fp.write(tounicode(title)) fp.write(tounicode(header)) for item in formatReport(rep_list, sortFunc): fp.write(tounicode(item)) print("Wrote %s" % fName) except (IOError, OSError): print("Error creating file %s!" % fName)
def parseLayoutFile(filePath): layoutDict = None try: fp = open(filePath, "rt") data = fp.read() fp.close() except (IOError,OSError): raise OptionParseError("Option Error: could not open and read the layout file <%s>." % filePath) entryList = re.findall(r"(\d+)\s+(\S+)\s+(\S+)\s+(\S+)", data) if len(entryList) < 2: print("Error: Failed to parse layout file %s. Did not match expected entry format." % filePath) raise OptionParseError print("Found %s entries in layout file %s. Mac CID: %s" % (len(entryList), os.path.basename(filePath), entryList[-1][0])) layoutDict = {} index = 0 for entry in entryList: gname = "cid" + entry[0].zfill(5) layoutDict[gname] = [entry[1], entry[2], entry[3]] # add cide key-> family, face dir, name reversekey = repr(entry[1:]) layoutDict[reversekey] = [gname, entry[1], entry[2], entry[3]] # add name key -> family, face, cid as cidXXXX entryList = None if "cid00000" in layoutDict: layoutDict[".notdef"] = layoutDict["cid00000"] return layoutDict
def main(): try: options = getOptions() except ACOptionParseError as e: logMsg(e) return 1 # verify that all files exist. haveFiles = True for path in options.fileList: if not os.path.exists(path): logMsg("File does not exist: <%s>." % path) haveFiles = False if not haveFiles: return 1 for path in options.fileList: if options.new: if options.reportPath: reportPath = options.reportPath else: reportPath = path foundOne = checkReportExists(reportPath, options.doAlign) if foundOne: logMsg("Skipping %s, as a report already exists." % (path)) continue try: collectStemsFont(path, options) except (ACFontError, ufotools.UFOParseError) as e: logMsg("\t%s" % e) return 1 if options.debug: with open("rawdata.txt", "w") as fp: fp.write(tounicode('\n'.join(rawData)))
def makeGAFile(gaPath, fontPath, glyphList, fontDictList, fdGlyphDict, removeNotdef): """ Creates a glyph alias file for each FDDict. These files will be used by 'mergefonts' tool. For documentation on the format of this file, run 'mergefonts -h'. """ glyph_list = getGlyphList(fontPath, removeNotdef) try: fdIndex = fdGlyphDict[glyph_list[0]][0] # [fdIndex value, gi] except KeyError: fdIndex = 0 fdDict = fontDictList[fdIndex] lineList = [""] lang_group = fdDict.LanguageGroup if lang_group is None: langGroup = " 0" else: langGroup = " %s" % lang_group dictName = "%s_%s" % (fdDict.FontName, fdDict.DictName) for glyph_name in glyph_list: gid = glyphList.index(glyph_name) lineList.append("%s\t%s" % (gid, glyph_name)) lineList.append("") gaText = "mergefonts %s%s%s" % (dictName, langGroup, '\n'.join(lineList)) with open(gaPath, "wb") as gf: gf.write(tobytes(gaText))
def main(): try: options = getOptions() except ACOptionParseError as e: logMsg(e) return 1 # verify that all files exist. haveFiles = True for path in options.fileList: if not os.path.exists(path): logMsg( "File does not exist: <%s>." % path) haveFiles = False if not haveFiles: return 1 for path in options.fileList: if options.new: if options.reportPath: reportPath = options.reportPath else: reportPath = path foundOne = checkReportExists(reportPath, options.doAlign) if foundOne: logMsg( "Skipping %s, as a report already exists." % (path)) continue try: collectStemsFont(path, options) except (ACFontError, ufotools.UFOParseError) as e: logMsg( "\t%s" % e) return 1 if options.debug: with open("rawdata.txt", "w") as fp: fp.write(tounicode('\n'.join(rawData)))
def getfdInfo(self, allow_no_blues, noFlex, vCounterGlyphs, hCounterGlyphs, glyphList, fdIndex=0): fdGlyphDict = None fdDict = self.getFontInfo(allow_no_blues, noFlex, vCounterGlyphs, hCounterGlyphs, fdIndex) fontDictList = [fdDict] # Check the fontinfo file, and add any other font dicts srcFontInfo = os.path.dirname(self.path) srcFontInfo = os.path.join(srcFontInfo, "fontinfo") maxX = self.getUnitsPerEm() * 2 maxY = maxX minY = -self.getUnitsPerEm() if os.path.exists(srcFontInfo): with open(srcFontInfo, "r", encoding="utf-8") as fi: fontInfoData = fi.read() fontInfoData = re.sub(r"#[^\r\n]+", "", fontInfoData) if "FDDict" in fontInfoData: fdGlyphDict, fontDictList, finalFDict = \ fdTools.parseFontInfoFile( fontDictList, fontInfoData, glyphList, maxY, minY, self.getPSName()) if finalFDict is None: # If a font dict was not explicitly specified for the # output font, use the first user-specified font dict. fdTools.mergeFDDicts(fontDictList[1:], self.fontDict) else: fdTools.mergeFDDicts([finalFDict], self.fontDict) return fdGlyphDict, fontDictList
def PrintReports(path, h_stem_list, v_stem_list, top_zone_list, bot_zone_list): items = ([h_stem_list, srtCnt], [v_stem_list, srtCnt], [top_zone_list, srtRevVal], [bot_zone_list, srtVal]) atime = time.asctime() suffixes = (".hstm.txt", ".vstm.txt", ".top.txt", ".bot.txt") titles = ("Horizontal Stem List for %s on %s\n" % (path, atime), "Vertical Stem List for %s on %s\n" % (path, atime), "Top Zone List for %s on %s\n" % (path, atime), "Bottom Zone List for %s on %s\n" % (path, atime), ) headers = ("Count\tWidth\tGlyph List\n", "Count\tWidth\tGlyph List\n", "Count\tTop Zone\tGlyph List\n", "Count\tBottom Zone\tGlyph List\n", ) for i, item in enumerate(items): rep_list, sortFunc = item if not rep_list: continue fName = '{}{}'.format(path, suffixes[i]) title = titles[i] header = headers[i] try: with open(fName, "w") as fp: fp.write(tounicode(title)) fp.write(tounicode(header)) for item in formatReport(rep_list, sortFunc): fp.write(tounicode(item)) print("Wrote %s" % fName) except (IOError, OSError): print("Error creating file %s!" % fName)
def main(args): from fontTools import configLogger # configure the library logger (for >= WARNING) configLogger() # comment this out to enable debug messages from mtiLib's logger # log.setLevel(logging.DEBUG) font = MockFont() tableTag = None if args[0].startswith('-t'): tableTag = args[0][2:] del args[0] for f in args: log.debug("Processing %s", f) table = build(open(f, 'rt', encoding="utf-8"), font, tableTag=tableTag) blob = table.compile(font) # Make sure it compiles decompiled = table.__class__() decompiled.decompile(blob, font) # Make sure it decompiles! #continue from fontTools.misc import xmlWriter tag = table.tableTag writer = xmlWriter.XMLWriter(sys.stdout) writer.begintag(tag) writer.newline() table.toXML(writer, font) #decompiled.toXML(writer, font) writer.endtag(tag) writer.newline()
def expect_designspace(self, masters, instances, expectedFile): master_dir = tempfile.mkdtemp() designspace, _ = build_designspace( masters, master_dir, os.path.join(master_dir, "out"), instances) with open(designspace, mode="r", encoding="utf-8") as f: actual = f.readlines() path, _ = os.path.split(__file__) expectedPath = os.path.join(path, "data", expectedFile) with open(expectedPath, mode="r", encoding="utf-8") as f: expected = f.readlines() if actual != expected: for line in difflib.unified_diff( expected, actual, fromfile=expectedPath, tofile=designspace): sys.stderr.write(line) self.fail("*.designspace file is different from expected") shutil.rmtree(master_dir)
def saveChanges(self): if self.font_format == "OTF": self.ttFont.save(self.outFilePath) self.ttFont.close() elif self.font_format == "CFF": data = self.ttFont["CFF "].compile(self.ttFont) with open(self.outFilePath, "wb") as tf: tf.write(data)
def build_designspace(self, masters, instances): master_dir = tempfile.mkdtemp() designspace, _ = build_designspace(masters, master_dir, os.path.join(master_dir, "out"), instances) with open(designspace, mode="r", encoding="utf-8") as f: result = f.readlines() shutil.rmtree(master_dir) return result
def _get_writer(file_or_filename, encoding): # returns text write method and release all resources after using try: write = file_or_filename.write except AttributeError: # file_or_filename is a file name f = open( file_or_filename, "w", encoding="utf-8" if encoding == "unicode" else encoding, errors="xmlcharrefreplace", ) with f: yield f.write else: # file_or_filename is a file-like object # encoding determines if it is a text or binary writer if encoding == "unicode": # use a text writer as is yield write else: # wrap a binary writer with TextIOWrapper detach_buffer = False if isinstance(file_or_filename, io.BufferedIOBase): buf = file_or_filename elif isinstance(file_or_filename, io.RawIOBase): buf = io.BufferedWriter(file_or_filename) detach_buffer = True else: # This is to handle passed objects that aren't in the # IOBase hierarchy, but just have a write method buf = io.BufferedIOBase() buf.writable = lambda: True buf.write = write try: # TextIOWrapper uses this methods to determine # if BOM (for UTF-16, etc) should be added buf.seekable = file_or_filename.seekable buf.tell = file_or_filename.tell except AttributeError: pass wrapper = io.TextIOWrapper( buf, encoding=encoding, errors="xmlcharrefreplace", newline="\n", ) try: yield wrapper.write finally: # Keep the original file open when the TextIOWrapper and # the BufferedWriter are destroyed wrapper.detach() if detach_buffer: buf.detach()
def read_ttx(self, path): lines = [] with open(path, "r", encoding="utf-8") as ttx: for line in ttx.readlines(): # Elide ttFont attributes because ttLibVersion may change, # and use os-native line separators so we can run difflib. if line.startswith("<ttFont "): lines.append("<ttFont>" + os.linesep) else: lines.append(line.rstrip() + os.linesep) return lines
def expect_designspace(self, masters, instances, expectedFile): actual = self.build_designspace(masters, instances) path, _ = os.path.split(__file__) expectedPath = os.path.join(path, "data", expectedFile) with open(expectedPath, mode="r", encoding="utf-8") as f: expected = f.readlines() if actual != expected: for line in difflib.unified_diff(expected, actual, fromfile=expectedPath, tofile="<generated>"): sys.stderr.write(line) self.fail("*.designspace file is different from expected")
def make_lexer_(file_or_path, location=None): if hasattr(file_or_path, "read"): fileobj, closing = file_or_path, False else: filename, closing = file_or_path, True try: fileobj = open(filename, "r", encoding="utf-8") except IOError as err: raise FeatureLibError(str(err), location) data = fileobj.read() filename = fileobj.name if hasattr(fileobj, "name") else "<features>" if closing: fileobj.close() return Lexer(data, filename)
def makeCIDFontInfo(fontPath, cidfontinfoPath): cfiDict = {} for key in kCIDFontInfokeyList: 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 TX_FIELDS: match = re.search(entry[0] + r"\s+(.+?)[\r\n]", report) if match: entry[2] = match.group(1) cfiDict["Registry"] = "Adobe" cfiDict["Ordering"] = "Identity" cfiDict["Supplement"] = "0" for entry in TX_FIELDS: if entry[2]: cfiDict[entry[1]] = entry[2] elif entry[1] in kRequiredCIDFontInfoFields: print("Error: did not find required info '%s' in tx dump of " "font '%s'." % (entry[1], fontPath)) try: with open(cidfontinfoPath, "w") as fp: for key in kCIDFontInfokeyList: value = cfiDict[key] if value is None: continue if value[0] == "\"": value = "(" + value[1:-1] + ")" string = tounicode("%s\t%s\n" % (key, value)) fp.write(string) except (IOError, OSError): raise FontInfoParseError( "Error. Could not open and write file '%s'" % cidfontinfoPath)
def getfdInfo(self, fontPSName, inputPath, allow_no_blues, noFlex, vCounterGlyphs, hCounterGlyphs, glyphList, fdIndex=0): topDict = self.topDict fontDictList = [] fdGlyphDict = None # Get the default fontinfo from the font's top dict. fdDict = self.getFontInfo(fontPSName, inputPath, allow_no_blues, noFlex, vCounterGlyphs, hCounterGlyphs, fdIndex) fontDictList.append(fdDict) # Check the fontinfo file, and add any other font dicts srcFontInfo = os.path.dirname(inputPath) srcFontInfo = os.path.join(srcFontInfo, "fontinfo") if os.path.exists(srcFontInfo): with open(srcFontInfo, "r", encoding="utf-8") as fi: fontInfoData = fi.read() fontInfoData = re.sub(r"#[^\r\n]+", "", fontInfoData) else: return fdGlyphDict, fontDictList if "FDDict" in fontInfoData: maxY = topDict.FontBBox[3] minY = topDict.FontBBox[1] blueFuzz = fdDict.BlueFuzz fdGlyphDict, fontDictList, finalFDict = fdTools.parseFontInfoFile( fontDictList, fontInfoData, glyphList, maxY, minY, fontPSName, blueFuzz) if finalFDict is None: # If a font dict was not explicitly specified for the # output font, use the first user-specified font dict. fdTools.mergeFDDicts(fontDictList[1:], topDict.Private) else: fdTools.mergeFDDicts([finalFDict], topDict.Private) return fdGlyphDict, fontDictList
def collect_features_content(instances, inst_idx_lst): """ Returns a dictionary whose keys are 'features.fea' file paths, and the values are the contents of the corresponding file. """ fea_dict = {} for i, inst_dscrpt in enumerate(instances): if inst_idx_lst and i not in inst_idx_lst: continue ufo_pth = inst_dscrpt.path if ufo_pth is None: continue ufo_pth = os.path.abspath(os.path.realpath(ufo_pth)) fea_pth = os.path.join(ufo_pth, FEATURES_FILENAME) if os.path.isfile(fea_pth): with open(fea_pth, 'rb') as fp: fea_cntnts = fp.read() fea_dict[fea_pth] = fea_cntnts return fea_dict
def parseCounterHintData(path): hCounterGlyphList = [] vCounterGlyphList = [] gf = open(path, "rt") data = gf.read() gf.close() lines = re.findall(r"([^\r\n]+)", data) # strip blank and comment lines lines = filter(lambda line: re.sub(r"#.+", "", line), lines) lines = filter(lambda line: line.strip(), lines) for line in lines: fields = line.split() if (len(fields) != 2) or (fields[0] not in ["V", "v", "H", "h"]) : print("\tError: could not process counter hint line '%s' in file %s. Doesn't look like V or H followed by a tab or space, and then a glyph name." % (line, path)) elif fields[0] in ["V", "v"]: vCounterGlyphList.append(fields[1]) else: hCounterGlyphList.append(fields[1]) return hCounterGlyphList, vCounterGlyphList
def makeCIDFontInfo(fontPath, cidfontinfoPath): cfiDict = {} for key in kCIDFontInfokeyList: 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 TX_FIELDS: 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 TX_FIELDS: if entry[2]: cfiDict[entry[1]] = entry[2] elif entry[1] in kRequiredCIDFontInfoFields: print("Error: did not find required info '%s' in tx dump of " "font '%s'." % (entry[1], fontPath)) try: with open(cidfontinfoPath, "w") as fp: for key in kCIDFontInfokeyList: value = cfiDict[key] if value is None: continue if value[0] == "\"": value = "(" + value[1:-1] + ")" string = tounicode("%s\t%s\n" % (key, value)) fp.write(string) except (IOError, OSError): raise FontInfoParseError( "Error. Could not open and write file '%s'" % cidfontinfoPath)
def checkFontinfoFile(options): # Check if there ia a makeotf fontinfo file in the input font directory. If so, # get any Vcounter or HCouunter glyphs from it. srcFontInfo = os.path.dirname(options.inputPath) srcFontInfo = os.path.join(srcFontInfo, "fontinfo") if os.path.exists(srcFontInfo): fi = open(srcFontInfo, "r") data = fi.read() fi.close() data = re.sub(r"#[^\r\n]+", "", data) counterGlyphLists = re.findall(r"([VH])CounterChars\s+\(\s*([^\)\r\n]+)\)", data) for entry in counterGlyphLists: glyphList = entry[1].split() if glyphList: if entry[0] == "V": options.vCounterGlyphs.extend(glyphList) else: options.hCounterGlyphs.extend(glyphList) if options.vCounterGlyphs or options.hCounterGlyphs: options.counterHintFile = srcFontInfo
def expect_designspace(self, masters, instances, expectedFile): actual = self.build_designspace(masters, instances) path, _ = os.path.split(__file__) expectedPath = os.path.join(path, "data", expectedFile) with open(expectedPath, mode="r", encoding="utf-8") as f: expected = f.readlines() if os.path.sep == '\\': # On windows, the test must not fail because of a difference between # forward and backward slashes in filname paths. # The failure happens because of line 217 of "mutatorMath\ufo\document.py" # > pathRelativeToDocument = os.path.relpath(fileName, os.path.dirname(self.path)) expected = [ line.replace('filename="out/', 'filename="out\\') for line in expected ] if actual != expected: for line in difflib.unified_diff(expected, actual, fromfile=expectedPath, tofile="<generated>"): sys.stderr.write(line) self.fail("*.designspace file is different from expected")
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 = '"%s" -v' % AUTOHINTEXE report = fdkutils.runShellCmd(command) logMsg( report) raise ACOptionParseError elif arg == "-u": print(__usage__) command = '"%s" -v' % AUTOHINTEXE 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 = open(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 in ["-decimal", "-dec"]: 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. 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 make_lexer_(filename, location): try: with open(filename, "r", encoding="utf-8") as f: return Lexer(f.read(), filename) except IOError as err: raise FeatureLibError(str(err), location)
def fixFontDict(tempPath, fdDict): txtPath = fdkutils.get_temp_file_path() command = "detype1 \"%s\" \"%s\" 2>&1" % (tempPath, txtPath) log = fdkutils.runShellCmd(command) if log: print(log) with open(txtPath, "r", encoding='utf-8') as fp: data = fp.read() # fix font name. We always search for 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 = getattr(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: if m: 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: if m: 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: target = "/LanguageGroup %s def" % fdDict.LanguageGroup data = data[:m.start()] + target + data[m.end():] 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. if fdDict.BlueValues: 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: if m: data = data[:m.start()] + data[m.end():] with open(txtPath, "w") as fp: fp.write(data) command = "type1 \"%s\" \"%s\" 2>&1" % (txtPath, tempPath) log = fdkutils.runShellCmd(command) if log: print(log)
def test_localisedNames(tmpdir): tmpdir = str(tmpdir) testDocPath = os.path.join(tmpdir, "testLocalisedNames.designspace") testDocPath2 = os.path.join(tmpdir, "testLocalisedNames_roundtrip.designspace") masterPath1 = os.path.join(tmpdir, "masters", "masterTest1.ufo") masterPath2 = os.path.join(tmpdir, "masters", "masterTest2.ufo") instancePath1 = os.path.join(tmpdir, "instances", "instanceTest1.ufo") instancePath2 = os.path.join(tmpdir, "instances", "instanceTest2.ufo") doc = DesignSpaceDocument() # add master 1 s1 = SourceDescriptor() s1.filename = os.path.relpath(masterPath1, os.path.dirname(testDocPath)) s1.name = "master.ufo1" s1.copyInfo = True s1.location = dict(weight=0) doc.addSource(s1) # add master 2 s2 = SourceDescriptor() s2.filename = os.path.relpath(masterPath2, os.path.dirname(testDocPath)) s2.name = "master.ufo2" s2.location = dict(weight=1000) doc.addSource(s2) # add instance 1 i1 = InstanceDescriptor() i1.filename = os.path.relpath(instancePath1, os.path.dirname(testDocPath)) i1.familyName = "Montserrat" i1.styleName = "SemiBold" i1.styleMapFamilyName = "Montserrat SemiBold" i1.styleMapStyleName = "Regular" i1.setFamilyName("Montserrat", "fr") i1.setFamilyName(u"モンセラート", "ja") i1.setStyleName("Demigras", "fr") i1.setStyleName(u"半ば", "ja") i1.setStyleMapStyleName(u"Standard", "de") i1.setStyleMapFamilyName("Montserrat Halbfett", "de") i1.setStyleMapFamilyName(u"モンセラート SemiBold", "ja") i1.name = "instance.ufo1" i1.location = dict(weight=500, spooky=666) # this adds a dimension that is not defined. i1.postScriptFontName = "InstancePostscriptName" glyphData = dict(name="arrow", mute=True, unicodes=[0x123]) i1.glyphs['arrow'] = glyphData doc.addInstance(i1) # now we have sources and instances, but no axes yet. doc.axes = [] # clear the axes # write some axes a1 = AxisDescriptor() a1.minimum = 0 a1.maximum = 1000 a1.default = 0 a1.name = "weight" a1.tag = "wght" # note: just to test the element language, not an actual label name recommendations. a1.labelNames[u'fa-IR'] = u"قطر" a1.labelNames[u'en'] = u"Wéíght" doc.addAxis(a1) a2 = AxisDescriptor() a2.minimum = 0 a2.maximum = 1000 a2.default = 0 a2.name = "width" a2.tag = "wdth" a2.map = [(0.0, 10.0), (401.0, 66.0), (1000.0, 990.0)] a2.labelNames[u'fr'] = u"Poids" doc.addAxis(a2) # add an axis that is not part of any location to see if that works a3 = AxisDescriptor() a3.minimum = 333 a3.maximum = 666 a3.default = 444 a3.name = "spooky" a3.tag = "spok" a3.map = [(0.0, 10.0), (401.0, 66.0), (1000.0, 990.0)] #doc.addAxis(a3) # uncomment this line to test the effects of default axes values # write some rules r1 = RuleDescriptor() r1.name = "named.rule.1" r1.conditionSets.append([ dict(name='weight', minimum=200, maximum=500), dict(name='width', minimum=0, maximum=150) ]) r1.subs.append(("a", "a.alt")) doc.addRule(r1) # write the document doc.write(testDocPath) assert os.path.exists(testDocPath) # import it again new = DesignSpaceDocument() new.read(testDocPath) new.write(testDocPath2) with open(testDocPath, 'r', encoding='utf-8') as f1: t1 = f1.read() with open(testDocPath2, 'r', encoding='utf-8') as f2: t2 = f2.read() assert t1 == t2
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_info()[0], sys.exc_info()[1])[-1]) raise ACFontError("Error opening or reading from font file <%s>." % fontFileName) except: logMsg( traceback.format_exception_only(sys.exc_info()[0], sys.exc_info()[1])[-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. tempBez = fdkutils.get_temp_file_path() tempBezNew = '{}{}'.format(tempBez, NEWBEZ_SUFFIX) tempFI = fdkutils.get_temp_file_path() 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.") fontData.close() return if fdGlyphDict == None: fdDict = fontDictList[0] with open(tempFI, "w") as fp: fp.write(tounicode(fdDict.getFontInfo())) 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, hasHints = 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) with open(tempFI, "w") as fp: fp.write(tounicode(fdDict.getFontInfo())) else: if (fdGlyphDict != None): try: fdIndex = fdGlyphDict[name][0] except KeyError: # use default dict. fdIndex = 0 if lastFDIndex != fdIndex: lastFDIndex = fdIndex fdDict = fontDictList[fdIndex] with open(tempFI, "w") as fp: fp.write(tounicode(fdDict.getFontInfo())) # 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 seen 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 or options.rehint): 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(".,") # Call auto-hint library on bez string. with open(tempBez, "wt") as bp: bp.write(tounicode(bezString)) #print "oldBezString", oldBezString #print "" #print "bezString", bezString if oldBezString != "" and oldBezString == bezString: newBezString = oldHintBezString else: if os.path.exists(tempBezNew): os.remove(tempBezNew) command = '"%s" %s%s%s%s -s %s -f "%s" "%s"' % (AUTOHINTEXE, verboseArg, suppressEditArg, supressHintSubArg, decimalArg, NEWBEZ_SUFFIX, 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 %s" % tempFI) print("Wrote AC output bez file to %s" % 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 = "%% %s%s%s" % (name, os.linesep, newBezString) 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 %s" % tempBez) 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)) if options.hintAll: logMsg("No new hints. All glyphs had hints that matched the hint history file %s, or were not in the history file and already had hints." % (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()))
] path = os.path.dirname(__file__) # ======= # = AGD = # ======= unicode2name_AGD = {} name2unicode_AGD = {} AGDPath = os.path.join(path, "AGD.txt") if os.path.exists(AGDPath): f = open(AGDPath, "r") lines = f.readlines() f.close() # format # <glyphName> # <tab> <tag>: <value> currentGlyphName = None for line in lines: if line.startswith("\t") and currentGlyphName is not None: tag, value = line.split(":") tag = tag.strip() value = value.strip() if tag == "uni": value = int(value, 16)
def get_font_format(font_file_path): with open(font_file_path, "rb") as f: head = f.read(4).decode() if head in ("\0\1\0\0", "true"): return "TTF" return None
def collectStemsFont(path, options): # 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_info()[0], sys.exc_info()[1])[-1]) raise ACFontError("Error opening or reading from font file <%s>." % fontFileName) except: logMsg( traceback.format_exception_only(sys.exc_info()[0], sys.exc_info()[1])[-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) tempBez = fdkutils.get_temp_file_path() tempReport = '{}{}'.format(tempBez, ".rpt") tempFI = fdkutils.get_temp_file_path() # 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] with open(tempFI, "w") as fp: fp.write(tounicode(fdDict.getFontInfo())) 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(".", end=' ') 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) with open(tempFI, "w") as fp: fp.write(tounicode(fdDict.getFontInfo())) else: if (fdGlyphDict != None): try: fdIndex = fdGlyphDict[name][0] except KeyError: # use default dict. fdIndex = 0 if lastFDIndex != fdIndex: lastFDIndex = fdIndex fdDict = fontDictList[fdIndex] with open(tempFI, "w") as fp: fp.write(tounicode(fdDict.getFontInfo())) glyphReports.startGlyphName(name) # Call auto-hint library on bez string. with open(tempBez, "w") as bp: bp.write(tounicode(bezString)) 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): with open(tempReport, "r", encoding='utf-8') as bp: report = bp.read() if options.debug: print("Wrote AC fontinfo data file to", tempFI) print("Wrote AC output rpt file to", tempReport) report.strip() if report: glyphReports.addGlyphReport(report) if options.debug: rawData.append(report) else: print("Error - failure in processing outline data") report = None h_stem_list, v_stem_list, top_zone_list, bot_zone_list = glyphReports.getReportLists() if options.reportPath: reportPath = options.reportPath else: reportPath = path PrintReports(reportPath, h_stem_list, v_stem_list, top_zone_list, bot_zone_list) fontData.close() logMsg( "Done with font %s. End time: %s." % (path, time.asctime()))
def SaveToFile(self, filename): with open(filename, 'wb') as fileobj: self.SaveToFileObject(fileobj)
def read_txt_file_lines(file_path): with open(file_path, "r", encoding="utf-8") as f: return f.read().splitlines()