def merge_fonts(inputFontPath, outputPath, fontList, glyphList, fontDictList, fdGlyphDict): cidfontinfoPath = fdkutils.get_temp_file_path() makeCIDFontInfo(inputFontPath, cidfontinfoPath) lastFont = "" dstPath = '' for i, fontPath in enumerate(fontList): gaPath = fdkutils.get_temp_file_path() dstPath = fdkutils.get_temp_file_path() removeNotdef = i != 0 makeGAFile(gaPath, fontPath, glyphList, fontDictList, fdGlyphDict, removeNotdef) if lastFont: command = 'mergefonts -std -cid "%s" "%s" "%s" "%s" "%s" 2>&1' % ( cidfontinfoPath, dstPath, lastFont, gaPath, fontPath) else: command = 'mergefonts -std -cid "%s" "%s" "%s" "%s" 2>&1' % ( cidfontinfoPath, dstPath, gaPath, fontPath) log = fdkutils.runShellCmd(command) if "rror" in log: raise FontInfoParseError( "Error running command '%s'\nLog: %s" % (command, log)) lastFont = dstPath if os.path.exists(outputPath): os.remove(outputPath) os.rename(dstPath, outputPath)
def test_remove_overlap_otf(): actual_path = get_temp_file_path() copy2(get_input_path('font.otf'), actual_path) runner(CMD + ['-f', actual_path, '-o', 'e', 'q']) actual_ttx = generate_ttx_dump(actual_path, ['CFF ']) expected_ttx = get_expected_path('font.ttx') assert differ([expected_ttx, actual_ttx, '-s', '<ttFont sfntVersion'])
def test_cef_cefsvg(): font_path = get_input_path('cff2_vf.otf') output_path = get_temp_file_path() runner(CMD + ['-a', '-o', 'cef', 'cefsvg', 'cr', 'gn1', 'abs', 'sa', '-f', font_path, output_path]) expected_path = get_expected_path('cef_cefsvg_cr.svg') assert differ([expected_path, output_path])
def filterDesignspaceInstances(dsDoc, options): """ - Filter unwanted instances out of dsDoc as specified by -i option (options.indexList), which has already been validated. - Promote dsDoc.instance.paths to absolute, referring to original dsDoc's location. - Remove any existing instance - Write the modified doc to a proper temp file - Return the path to the temp DS file. """ origDSPath = options.dsPath filteredInstances = [] for idx in sorted(options.indexList): instance = dsDoc.instances[idx] instance.path = os.path.abspath( os.path.realpath(os.path.join( origDSPath, instance.path, ))) if os.path.exists(instance.path): glyphDir = os.path.join(instance.path, "glyphs") if os.path.exists(glyphDir): shutil.rmtree(glyphDir, ignore_errors=True) filteredInstances.append(instance) dsDoc.instances = filteredInstances tmpPath = get_temp_file_path() dsDoc.write(tmpPath) return tmpPath
def generate_ps_dump(font_path): with open(font_path, 'rb') as ps_file: data = ps_file.read() line = '' i = 0 in_binary_section = False bytes_read = 0 byte_count = 0 temp_path = get_temp_file_path() with open(temp_path, 'w') as temp_file: while i < len(data): x = data[i] if not in_binary_section: if x != 0x0A: line += '%c' % x else: if line.startswith('%%BeginData'): in_binary_section = True byte_count = int(line.split()[1]) line += '\n' temp_file.write(line) line = '' else: bytes_read += 1 if bytes_read == byte_count: in_binary_section = False if bytes_read % 32 != 1: temp_file.write('\n') else: temp_file.write('%02X' % x) if bytes_read % 32 == 0: temp_file.write('\n') i += 1 return temp_path
def test_date_and_time_pdf(): """ test the use of date and time functions in pdfwrite.c """ input_path = get_input_path('font.otf') output_path = get_temp_file_path() runner(CMD + ['-a', '-o', 'pdf', '-f', input_path, output_path]) now = time.time() tz = time.timezone tz_hr = abs(int(tz / 3600)) # ignore sign since we're splitting on +/- tz_min = (tz % 3600) // 60 with open(output_path) as output_file: lines = output_file.readlines() creation_date_str = re.split(r'[()]', lines[13])[1] mod_date_str = re.split(r'[()]', lines[14])[1] assert (creation_date_str == mod_date_str) (date_time_str, tz_hr_str, tz_min_str) = \ re.split(r"[:+\-Z']", creation_date_str)[1:4] creation_time = time.mktime( time.strptime(date_time_str, '%Y%m%d%H%M%S')) hours_diff = abs(now - creation_time) / 3600 assert (hours_diff < 1) creation_tz_hr = int(tz_hr_str) assert (creation_tz_hr == tz_hr) creation_tz_min = int(tz_min_str) assert (creation_tz_min == tz_min) file_date_str = re.split(r"[():]", lines[36])[2].strip() file_time_str = re.split(r"[() ]", lines[38])[3] file_date_time_str = file_date_str + ' ' + file_time_str file_time = time.mktime( time.strptime(file_date_time_str, "%d %b %y %H:%M")) hours_diff = abs(now - file_time) / 3600 assert (hours_diff < 1)
def test_skip_ufo3_global_guides_bug700(): input_filename = "bug700.ufo" actual_path = get_temp_file_path() runner(CMD + [ '-o', 'f', f'_{get_input_path(input_filename)}', 'o', f'_{actual_path}' ]) assert font_has_table(actual_path, 'CFF ')
def test_write_fdselect_format_4(): font_name = 'FDArrayTest257FontDicts.otf' input_path = get_input_path(font_name) output_path = get_temp_file_path() runner(CMD + ['-a', '-o', 'cff2', '-f', input_path, output_path]) expected_path = get_expected_path('FDArrayTest257FontDicts.cff2') assert differ([expected_path, output_path, '-m', 'bin'])
def test_recalculate_font_bbox_bug618(to_format, args, exp_filename): font_path = get_input_path('bug618.pfa') save_path = get_temp_file_path() runner(CMD + ['-f', font_path, save_path, '-o', to_format] + args) file_ext = to_format if to_format == 't1': file_ext = 'pfa' elif to_format == 'afm': file_ext = 'txt' expected_path = get_expected_path(f'bug618/{exp_filename}.{file_ext}') diff_mode = [] if to_format == 'cff': diff_mode = ['-m', 'bin'] skip = [] if to_format == 'afm': skip = [ '-s', 'Comment Creation Date:' + SPLIT_MARKER + 'Comment Copyright' ] elif to_format == 't1': skip = ['-s'] + PFA_SKIP[:] assert differ([expected_path, save_path] + diff_mode + skip)
def test_read_fdselect_format_4(option): font_name = 'fdselect4.otf' input_path = get_input_path(font_name) output_path = get_temp_file_path() runner(CMD + ['-a', '-o', option, '-f', input_path, output_path]) expected_path = get_expected_path(font_name + '.' + option) assert differ([expected_path, output_path, '-s', '## Filename'])
def test_overlap_removal(): input_path = get_input_path('overlaps.ufo') expected_path = get_expected_path('overlaps.pfa') output_path = get_temp_file_path() args = [TOOL, '-t1', '+V', '-o', output_path, input_path] subprocess.call(args) assert differ([expected_path, output_path, '-s', PFA_SKIP[0]])
def test_cff2_extract(args, exp_filename): # read CFF2 VF, write CFF2 table font_path = get_input_path('SourceCodeVariable-Roman.otf') cff2_path = get_temp_file_path() runner(CMD + ['-a', '-f', font_path, cff2_path, '-o', 'cff2'] + args) expected_path = get_expected_path(exp_filename) assert differ([expected_path, cff2_path, '-m', 'bin'])
def test_ttread_varinst(): font_path = get_input_path('AdobeVFPrototype.ttf') save_path = get_temp_file_path() runner(CMD + ['-a', '-o', '3', 'g', '_A', 'U', '_500,800', '-f', font_path, save_path]) expected_path = get_expected_path('vfproto_tt_inst500_800.txt') assert differ([expected_path, save_path, '-s', '## Filename'])
def makeTempFonts(fontDictList, glyphSetList, fdGlyphDict, inputPath): fontList = [] for glyphList in glyphSetList: arg = ",".join(glyphList) tempPath = fdkutils.get_temp_file_path() 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) try: fdIndex = fdGlyphDict[glyphList[0]][0] except KeyError: # 'fontinfo' file was not provided; # assign list of glyphs to default FDDict fdIndex = 0 fdDict = fontDictList[fdIndex] fixFontDict(tempPath, fdDict) fontList.append(tempPath) return fontList
def test_multiple_inputs_fail(): path1 = get_input_path('latincid.otf') path2 = get_input_path('sans.otf') out_path = get_temp_file_path() with pytest.raises(SystemExit) as exc_info: otf2ttf(['-o', out_path, path1, path2]) assert exc_info.value.code == 2
def makeTempFonts(fontDictList, glyphSetList, fdGlyphDict, inputPath): fontList = [] for glyphList in glyphSetList: arg = ",".join(glyphList) tempPath = fdkutils.get_temp_file_path() 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) try: fdIndex = fdGlyphDict[glyphList[0]][0] except KeyError: # 'fontinfo' file was not provided; # assign list of glyphs to default FDDict fdIndex = 0 fdDict = fontDictList[fdIndex] fixFontDict(tempPath, fdDict) fontList.append(tempPath) return fontList
def test_componentize(): ttf_path = _get_test_ttf_path() save_path = get_temp_file_path() opts = Object() setattr(opts, 'font_path', ttf_path) setattr(opts, 'output_path', save_path) ufo, ps_names = ttfcomp.get_glyph_names_mapping(_get_test_ufo_path()) ttcomp_obj = ttfcomp.TTComponentizer(ufo, ps_names, opts) ttcomp_obj.componentize() # 'get_composites_data' method comps_data = ttcomp_obj.composites_data comps_name_list = sorted(comps_data.keys()) comps_comp_list = [comps_data[gname] for gname in comps_name_list] assert comps_name_list == [ 'aa', 'aacute', 'adieresis', 'atilde', 'uni01CE' ] assert comps_comp_list[1].names == ('a', 'uni0301') assert comps_comp_list[4].names == ('a', 'uni030C') assert comps_comp_list[1].positions == ((0, 0), (263.35, 0)) assert comps_comp_list[4].positions == ((0, 0), (263, 0)) # 'assemble_components' method comps_data = ttfcomp.ComponentsData() comps_data.names = ('a', 'uni01CE') comps_data.positions = ((0, 0), (263, 0)) comps_data.same_advwidth = True comp_one, comp_two = ttcomp_obj.assemble_components(comps_data) assert comp_one.glyphName == 'a' assert comp_two.glyphName == 'uni01CE' assert (comp_one.x, comp_one.y) == (0, 0) assert (comp_two.x, comp_two.y) == (263, 0) assert comp_one.flags == 0x204 assert comp_two.flags == 0x4
def test_lib_removes_outlines_bug1366(): input_path = get_input_path("bug1366.ufo") expected_path = get_expected_path("bug1366.pfa") output_path = get_temp_file_path() subprocess.call([TOOL, '-t1', '-o', output_path, input_path]) expected_path = generate_ps_dump(expected_path) output_path = generate_ps_dump(output_path) assert differ([expected_path, output_path, '-s', PFA_SKIP[0]])
def test_build_options_cs_cl_bug459(args, input_filename, ttx_filename): actual_path = get_temp_file_path() runner(CMD + [ '-o', 'f', f'_{get_input_path(input_filename)}', 'o', f'_{actual_path}' ] + args) actual_ttx = generate_ttx_dump(actual_path, ['cmap']) expected_ttx = get_expected_path(ttx_filename) assert differ([expected_ttx, actual_ttx, '-s', '<ttFont sfntVersion'])
def test_run_invalid_ufo(): ttf_path = _get_test_ttf_path() temp_dir = get_temp_dir_path() save_path = get_temp_file_path(directory=temp_dir) ufo_path = save_path + '.ufo' copy2(ttf_path, save_path) copy2(ttf_path, ufo_path) assert ttfcomp.main([save_path]) == 1
def test_run_cli_with_output_path(): actual_path = get_temp_file_path() runner(CMD + [ '-o', 'o', f'_{actual_path}', f'_{get_input_path(TEST_TTF_FILENAME)}' ]) actual_ttx = generate_ttx_dump(actual_path, ['maxp', 'glyf']) expected_ttx = get_expected_path('ttfcomponentizer.ttx') assert differ([expected_ttx, actual_ttx, '-s', '<ttFont sfntVersion'])
def generate_spot_dumptables(font_path, tables): tmp_txt_path = get_temp_file_path() myargs = ['spot', "-t" + ",".join(tables), font_path] spot_txt = subprocess.check_output(myargs, timeout=TIMEOUT) tf = open(tmp_txt_path, "wb") tf.write(spot_txt) tf.close() return tmp_txt_path
def test_run_with_output_path(): ttf_path = _get_test_ttf_path() save_path = get_temp_file_path() ttfdecomp.main(['-o', save_path, ttf_path]) font = TTFont(save_path) gtable = font['glyf'] composites = [gtable[gl].isComposite() for gl in font.glyphOrder] assert not any(composites)
def test_run_with_output_path(): ttf_path = _get_test_ttf_path() save_path = get_temp_file_path() ttfcomp.main(['-o', save_path, ttf_path]) gtable = TTFont(save_path)['glyf'] composites = [ gname for gname in gtable.glyphs if (gtable[gname].isComposite()) ] assert sorted(composites) == ['aa', 'aacute', 'uni01CE']
def test_feature_includes_ufo_bug164(): input_filename = "bug164/d1/d2/font.ufo" otf_path = get_temp_file_path() runner( CMD + ['-o', 'f', f'_{get_input_path(input_filename)}', 'o', f'_{otf_path}']) assert font_has_table(otf_path, 'head')
def test_remove_hints_bug180(): font_path = get_input_path('cid.otf') cid_path = get_temp_file_path() runner(CMD + ['-a', '-o', 't1', 'n', '-f', font_path, cid_path]) expected_path = get_expected_path('cid_nohints.ps') expected_path = generate_ps_dump(expected_path) actual_path = generate_ps_dump(cid_path) skip = ['-s'] + PS_SKIP2 assert differ([expected_path, actual_path] + skip)
def test_dcf_with_infinite_recursion_bug775(): font_path = get_bad_input_path('subr_test_font_infinite_recursion.otf') dcf_path = get_temp_file_path() with pytest.raises(subprocess.CalledProcessError) as err: runner(CMD + ['-a', '-o', 'dcf', '-f', font_path, dcf_path]) assert(err.value.returncode == 1) # exit code of 1, not segfault of -11 expected_path = get_expected_path( 'subr_test_font_infinite_recursion.dcf.txt') assert differ([expected_path, dcf_path])
def test_V_option(arg, exp_filename): input_path = get_input_path('overlap.pfa') expected_path = get_expected_path(f'overlap_{exp_filename}.pfa') output_path = get_temp_file_path() args = [TOOL, '-t1', '-o', output_path, input_path] if arg: args.insert(2, arg) subprocess.call(args) assert differ([expected_path, output_path, '-s', PFA_SKIP[0]])
def test_cff2_no_vf_bug353(): # read CFF2 WITHOUT VF info, write a CFF2 out. 'regular_CFF2.otf' # is derived by taking the regular.otf file from the sfntdiff # 'input_data' directory, and converting the CFF table to CFF2. font_path = get_input_path('regular_CFF2.otf') cff2_path = get_temp_file_path() runner(CMD + ['-a', '-o', 'cff2', '-f', font_path, cff2_path]) expected_path = get_expected_path('regular_CFF2.cff2') assert differ([expected_path, cff2_path, '-m', 'bin'])
def test_script_file(dcf_dump_level): font_path = get_input_path('cid.otf') opts_path = get_temp_file_path() opts_file_content = f'\n# foo\n # bar\r -{dcf_dump_level}\t"{font_path}"' with open(opts_path, 'a') as fp: fp.write(opts_file_content) actual_path = runner(CMD + ['-s', '-a', '-o', 'dcf', 's', '-f', opts_path]) expected_path = get_expected_path(f'cid_dcf_{dcf_dump_level}.txt') assert differ([expected_path, actual_path])
def test_stdin(): input_path = get_input_path('type1.pfa') expected_path = get_expected_path('stdin.txt') output_path = get_temp_file_path() with open(input_path) as fp: output = subprocess.check_output([TOOL], stdin=fp) with open(output_path, 'wb') as fp: fp.write(output) assert differ([expected_path, output_path])
def test_beztools_hhint_over_limit_bug629(): test_filename = 'bug629.pfa' actual_path = get_temp_file_path() expected_path = get_expected_path(test_filename) runner(CMD + ['-o', 'nb', 'o', f'_{actual_path}', '-f', test_filename]) assert differ([ expected_path, actual_path, '-s', r'%ADOt1write' + SPLIT_MARKER + r'%%Copyright: Copyright' ])
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 main(): args = sys.argv if len(args) >= 2: inputPath = args[1] outputPath = fdkutils.get_temp_file_path() fontinfoPath = None if len(args) == 3: fontinfoPath = args[2] convertFontToCID(inputPath, outputPath, fontinfoPath) save_path = '{}{}'.format(os.path.splitext(inputPath)[0], '-CID.ps') if os.path.isfile(outputPath): os.rename(outputPath, save_path) else: print('ERROR: Missing path to font to convert.')
def mergeFontToCFF(srcPath, outputPath, doSubr): """ Used by makeotf. Assumes 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 = fdkutils.get_temp_file_path() 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 run 'tx -cff +b' on file %s" % srcPath) # Now merge it into the output file. command = "sfntedit -a CFF=\"%s\" \"%s\" 2>&1" % (tempPath, outputPath) report = fdkutils.runShellCmd(command) if ("fatal" in report) or ("error" in report): print(report) raise FontInfoParseError( "Failed to run 'sfntedit -a CFF=' on file %s" % srcPath)
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()))
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 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)