def scaleFont(source, destination, factor): font = OpenFont(source) factor = float(factor) # Transform metadata font.info.descender = font.info.descender * factor font.info.xHeight = font.info.xHeight * factor font.info.capHeight = font.info.capHeight * factor font.info.ascender = font.info.ascender * factor font.info.postscriptUnderlineThickness = font.info.postscriptUnderlineThickness * factor font.info.postscriptUnderlinePosition = font.info.postscriptUnderlinePosition * factor font.info.postscriptBlueValues = list( map(lambda x: x * factor, font.info.postscriptBlueValues)) # Transform glyphs for glyph in font: glyph.scaleBy(factor, None, True, True) for component in glyph.components: component.scaleBy(1 / factor) # Transform kerning font.kerning.scaleBy(factor) # Round everything and save font.info.round() font.save(destination)
def sourceFontNamePopUpButtonCallback(self, sender): index = sender.get() name = sender.getItems()[index] if name == openFontItemName: font = OpenFont(path=None, showInterface=False) if font is None: name = self.getSourceFontNames()[0] else: name = self.addSourceFont(font) names = self.getSourceFontNames() index = names.index(name) sender.setItems(names) sender.set(index) self.sourceFont = self.sourceFonts[name] self.populateSourceGlyphs() self.populateSourceLayers()
def ufo2mf(dest_path, ufo_path=None, logs=True): """ Converts UFO format to METAFONT format. Args: dest_path:: str ufo_path:: str (default is None) logs:: bool """ if ufo_path is None: font = CurrentFont() else: font = OpenFont(os.path.abspath(ufo_path), showInterface=False) dest_path = os.path.abspath(dest_path) with open(os.path.join(dest_path, mfc.RADICAL)) as radical_mf, \ open(os.path.join(dest_path, mfc.COMBINATION)) as combination_mf: if logs: print( "====================== Start ufo2mf() ======================") # print("Date:") print("Target:", font.path) print("Destination Path:", dest_path) print("Radical Path:", radical_mf) print("Combination Path:", combination_mf) # Remove exist Metafont file for renew try: os.remove(radical_mf) os.remove(combination_mf) except: pass for key in font.keys: glyph = font.getGlyph(key) glyph2mf(glyph, radical_mf, combination_mf, logs) if logs: print("======================= End ufo2mf() =======================")
for i, colName in enumerate(measurementsCols): mDict[colName] = row[i] # only analyze rows with a direction if mDict['Direction']: measurementNames.append(row[0]) measurements[row[0]] = mDict # loop through designspace doc = DesignSpaceDocument() doc.read(designspacePath) designspaceFileName = os.path.split(designspacePath)[1] # gather glyph index for width counting for source in doc.sources: if source.copyInfo: f = OpenFont(source.path, showInterface=False) for gname in f.glyphOrder: if gname in f: widthsCols.append(gname) f.close() widthsRows.append(widthsCols) # process each source for source in doc.sources: if not os.path.exists(source.path): print('missing source', source.path) continue f = OpenFont(source.path, showInterface=False) charMap = f.getCharacterMapping() row = [os.path.split(source.path)[1], int(f.info.unitsPerEm)]
def buildDesignSpace(masterFont=None, destPath=None, glyphNames=[], compositionType="rotate", outlineAmount=None, zOffset=None, shadowLengthFactor=1, doForceSmooth=False, doMakeSubSources=False, familyName=None, alwaysConnect=False, cap="RoundSimple", connection="Round", layerName=None, styleName=None): # Open the master UFO if type(masterFont) == str: masterFont = OpenFont(masterFont, showInterface=False) # Get the master file name, if it's saved basePath = None masterFileName = "Font" if masterFont.path: basePath, masterFileName = os.path.split(masterFont.path) # Try to make a dest path, if there isn't one if destPath == None: if basePath: destPath = os.path.join(basePath, "Rotated") # Make new folders for the destPath if not os.path.exists(destPath): os.makedirs(destPath) # Use all glyphs, if no names are called for if glyphNames == []: glyphNames = list(masterFont.keys()) glyphNames.sort() # Default names if not familyName: familyName = masterFont.info.familyName if not styleName: styleName = "Regular" """ Collect glyph data """ # Organize the point data out of the glyph lib # and check to see which glyphs need to be present in a SubSource glyphPointData = {} needSubHROT = [ ] # Glyphs that need to be included in a SubSource when HROT is default needSubVROT = [] for gName in glyphNames: if gName in masterFont: g = masterFont[gName] if layerName: # Copy point data to the layer if ZPOSITIONLIBKEY in g.lib.keys(): libdata = copy.deepcopy(g.lib[ZPOSITIONLIBKEY]) else: libdata = {} g = g.getLayer(layerName) g.lib[ZPOSITIONLIBKEY] = libdata pointData = readGlyphPointData(g) glyphPointData[gName] = pointData # Test for self-overlapping contours if doMakeSubSources == True: overlapResult = checkCurveOverlap(g) if "x" in overlapResult: needSubHROT.append(gName) elif "y" in overlapResult: needSubVROT.append(gName) """ Organize Source combinations """ # Collect source info, based on the layout type # Organize file names, designspace locations, glyph lists, etc. sourceCombinations = [] for locHROT, tagHROT in [(-45, "HROTn"), (0, "HROTd"), (45, "HROTx")]: for locVROT, tagVROT in [(-45, "VROTn"), (0, "VROTd"), (45, "VROTx")]: if "shadow" in compositionType: for locSLEN, tagSLEN in [(0, "SLENn"), (100, "SLENx")]: for locSANG, tagSANG in [(-45, "SANGn"), (45, "SANGx")]: # Rotate and Shadow fileName = "Source-%s_%s_%s_%s.ufo" % ( tagHROT, tagVROT, tagSLEN, tagSANG) loc = dict(HROT=locHROT, VROT=locVROT, SLEN=locSLEN, SANG=locSANG) sourceInfo = dict(glyphNames=glyphNames, loc=loc, fileName=fileName, nudgeLoc=[0, 0]) sourceCombinations.append(sourceInfo) elif "depth" in compositionType: for locDPTH, tagDPTH in [(0, "DPTHn"), (100, "DPTHx")]: # Rotate and depth fileName = "Source-%s_%s_%s.ufo" % (tagHROT, tagVROT, tagDPTH) loc = dict(HROT=locHROT, VROT=locVROT, DPTH=locDPTH) sourceInfo = dict(glyphNames=glyphNames, loc=loc, fileName=fileName, nudgeLoc=[0, 0]) sourceCombinations.append(sourceInfo) else: # Rotate only fileName = "Source-%s_%s.ufo" % (tagHROT, tagVROT) loc = dict(HROT=locHROT, VROT=locVROT) sourceInfo = dict(glyphNames=glyphNames, loc=loc, fileName=fileName, nudgeLoc=[0, 0]) sourceCombinations.append(sourceInfo) # Process the sourceCombinations and make SubSources if necessary #print("needSubHROT", needSubHROT) #print("needSubVROT", needSubVROT) # @@@ Temporarily force all glyphs to be in all submasters needSubHROT = glyphNames needSubVROT = glyphNames if doMakeSubSources: doSubHROT = len(needSubHROT) doSubVROT = len(needSubVROT) else: doSubHROT = False doSubVROT = False # Loop through once to add new HROT SubSources newSourceCombos = [] for sourceInfo in sourceCombinations: if sourceInfo["loc"]["HROT"] == 0: if doSubHROT: subSourceInfo = copy.deepcopy(sourceInfo) subSourceInfo["nudgeLoc"][ 0] = 0 # Don't nudge, move the location instead subSourceInfo["loc"]["HROT"] += SLIGHTLYOFFAXIS subSourceInfo["glyphNames"] = needSubHROT subSourceInfo[ "fileName"] = "Sub" + subSourceInfo["fileName"].replace( "HROTd", "HROTdd") newSourceCombos.append(subSourceInfo) # Nudge the default source sourceInfo["nudgeLoc"][0] -= SLIGHTLYOFFAXIS sourceCombinations += newSourceCombos # Looping back through to add VROT SubSources and to catch all of the new HROT SubSources newSourceCombos = [] for sourceInfo in sourceCombinations: if sourceInfo["loc"]["VROT"] == 0: if doSubVROT: subSourceInfo = copy.deepcopy(sourceInfo) subSourceInfo["nudgeLoc"][ 1] = 0 # Don't nudge, move the location instead subSourceInfo["loc"]["VROT"] -= SLIGHTLYOFFAXIS # Append the glyph list if this was the HROT=SLIGHTLYOFFAXIS if not subSourceInfo["loc"]["HROT"] == SLIGHTLYOFFAXIS: subSourceInfo["glyphNames"] = [] subSourceInfo["glyphNames"] += needSubVROT subSourceInfo["fileName"] = subSourceInfo["fileName"].replace( "VROTd", "VROTdd") if not "Sub" in subSourceInfo["fileName"]: subSourceInfo[ "fileName"] = "Sub" + subSourceInfo["fileName"] newSourceCombos.append(subSourceInfo) # Nudge the default source sourceInfo["nudgeLoc"][1] += SLIGHTLYOFFAXIS sourceCombinations += newSourceCombos """ Make the source UFOs """ for sourceInfo in sourceCombinations: sourceUfoPath = os.path.join(destPath, sourceInfo["fileName"]) if not os.path.exists(sourceUfoPath): sourceFont = NewFont(showInterface=False) sourceFont.save(sourceUfoPath) sourceFont.info.familyName = familyName sourceFont.info.styleName = styleName sourceFont.save() sourceFont.close() """ Process Glyphs into Source UFOs """ # Process each UFO source, one at a time for sourceInfo in sourceCombinations: # Combine the nudgeLoc and loc, use this value when rotating rotateLoc = copy.deepcopy(sourceInfo["loc"]) rotateLoc["HROT"] += sourceInfo["nudgeLoc"][0] rotateLoc["VROT"] += sourceInfo["nudgeLoc"][1] sourceUfoPath = os.path.join(destPath, sourceInfo["fileName"]) sourceFont = OpenFont(sourceUfoPath, showInterface=False) for gName in sourceInfo["glyphNames"]: if gName in glyphPointData: pointData = copy.deepcopy(glyphPointData[gName]) else: pointData = {} # Get the glyph started g = masterFont[gName] if layerName: g = g.getLayer(layerName) # Remove the glyph if it already existed and make a new one if gName in sourceFont: for layer in sourceFont.layers: if gName in layer: layer.removeGlyph(gName) sourceFont.newGlyph(gName) gDest = sourceFont[gName] gDest.appendGlyph(g) gDest.width = g.width gDest.unicode = g.unicode # Add anchors to the pointData so that they shift correctly # Use anchor + str(idx) as the ident for aIdx, anc in enumerate(gDest.anchors): ident = "anchor%s" % aIdx pos = list(anc.position) pointData[ident] = dict(x=pos[0], y=pos[1], z=0) # Decompose components in glyphs that were not bulit with Glyph Builder # If the glyph has components, and if it's not marked with the "Glyph Builder Gray", # it will need to be decomposed at this stage (but leave overlaps of course) # Otherwise, glyphs that are gray will have their components reapplied after rotating with Glyph Builder. isComponent = False if not g.markColor == COMPOSITEGRAY: if len(gDest.components): for c in gDest.components: baseName = c.baseGlyph masterBaseGlyph = masterFont[baseName] # Decomposing from the master font because the base glyph might not be in the source font yet for mc in masterBaseGlyph.contours: gDest.appendContour(mc, offset=c.offset) gDest.removeComponent(c) # ...and copy over point data, taking into account the component offset if baseName in glyphPointData: basePointData = copy.deepcopy( glyphPointData[baseName]) for ident in basePointData: pointData[ident] = dict( x=basePointData[ident]["x"] + c.offset[0], y=basePointData[ident]["y"] + c.offset[1], z=basePointData[ident]["z"]) isComponent = True # Flatten the depth if "DPTH" in sourceInfo["loc"].keys(): for ident in pointData: pointData[ident]["z"] *= (sourceInfo["loc"]["DPTH"] * 0.01 ) # Scale by the depth value # Shift the "z" value by an offset if not zOffset == None: for ident in pointData: if not "anchor" in ident: # ...but don't shift the anchors, the components are already shifted pointData[ident]["z"] += zOffset # Extend the shadow if "SANG" in sourceInfo["loc"].keys(): if sourceInfo["loc"]["SANG"] == -45: shadowDirection = "left" else: shadowDirection = "right" finalShadowLengthFactor = (sourceInfo["loc"]["SLEN"] * 0.01) * shadowLengthFactor pointData = flattenShadow(gDest, pointData, shadowDirection, finalShadowLengthFactor) # Rotate the glyph # Merge the location and the nudgeLoc, if there is one marginChange, pointData = rotateGlyphPointData( gDest, rotateLoc, pointData) # Move the contour points into the correct position for c in gDest.contours: for pt in c.points: ident = getIdent(pt) if ident in pointData: pt.x = pointData[ident]["x"] pt.y = pointData[ident]["y"] # Move the anchors into the correct position for aIdx, anc in enumerate(gDest.anchors): ident = "anchor%s" % aIdx if ident in pointData: anc.position = (int(round(pointData[ident]["x"])), int(round(pointData[ident]["y"]))) # Shift the glyph to take in the sidebearings gDest.moveBy((-marginChange[0], 0)) gDest.width -= marginChange[0] * 2 if doForceSmooth and not isComponent: # If a bPoint was a smooth curve point in the original glyph, # force the related bPoint in the rotated glyph to be smooth for cIdx, c in enumerate(gDest.contours): for bptIdx, thisBPt in enumerate(c.bPoints): sourceBPt = g.contours[cIdx].bPoints[bptIdx] if sourceBPt.type == "curve": forceSmooth(thisBPt) # Round the point coordinates before outlining gDest.round() gDest.changed() # Outline the glyph if outlineAmount: outlineGlyph(sourceFont, gDest, outlineAmount, alwaysConnect=alwaysConnect, cap=cap, connection=connection) # Round the point coordinates again, now that it's outlined gDest.round() gDest.changed() # Update #gDest.changed() # Resort the font sourceFont.glyphOrder = masterFont.glyphOrder # Copy the kerning sourceFont.groups.update(copy.deepcopy(masterFont.groups)) sourceFont.kerning.update(copy.deepcopy(masterFont.kerning)) # Copy the features sourceFont.features.text = masterFont.features.text # Done, save sourceFont.changed() sourceFont.save() """ New DesignSpaceDocument """ designSpace = DesignSpaceDocument() designSpaceDocFilename = os.path.splitext( masterFileName)[0] + ".designspace" designSpaceDocPath = os.path.join(destPath, designSpaceDocFilename) """ Axis Descriptors """ for tag in sourceCombinations[0]["loc"].keys(): a = AxisDescriptor() a.minimum = AXISINFO[tag]["minimum"] a.maximum = AXISINFO[tag]["maximum"] a.default = AXISINFO[tag]["default"] a.name = AXISINFO[tag]["name"] a.tag = tag a.labelNames[u'en'] = AXISINFO[tag]["name"] designSpace.addAxis(a) """ Source Descriptors """ for sourceInfo in sourceCombinations: sourceUfoPath = os.path.join(destPath, sourceInfo["fileName"]) # Make a source description s = SourceDescriptor() s.path = sourceUfoPath s.name = os.path.splitext(sourceInfo["fileName"])[0] #s.font = defcon.Font(s.name) s.copyLib = True s.copyInfo = True s.copyInfoures = True s.familyName = masterFont.info.familyName s.styleName = s.name # Convert the loc from tags to names loc = {} for tag, value in sourceInfo["loc"].items(): axisName = AXISINFO[tag]["name"] loc[axisName] = value s.location = loc designSpace.addSource(s) designSpace.write(designSpaceDocPath)
} } head, tail = os.path.split(fontPath) newPath = head + '/gsub-rules.txt' GSUBrules = open(newPath, "w+") def printWrite(line): print(line) GSUBrules.write(line) font = OpenFont(fontPath, showInterface=False) def makeRules(): for suffix in suffixes: printWrite(f'\n'.ljust(80, '-') + '\n') printWrite(f'{suffix} '.ljust(80, '-') + '\n') printWrite('\n') for g in font: if '.' in g.name and g.name.split('.')[1] == suffix: # print(g) baseName = g.name.split('.')[0] rule = f'<sub name="{baseName}" with="{g.name}" />' printWrite(rule + '\n')
def makeGrades(maxMaster, minMaster): for k in maxMaster.keys(): if maxMaster[k]: maxMaster[k].leftMargin = int(maxMaster[k].leftMargin) maxMaster[k].rightMargin = int(maxMaster[k].rightMargin) widthDiff = maxMaster[k].width - minMaster[k].width if widthDiff % 2 == 0: minMaster[k].leftMargin += int(widthDiff / 2) minMaster[k].rightMargin += int(widthDiff / 2) else: widthDiff += 1 minMaster[k].leftMargin += int(widthDiff / 2) - 1 minMaster[k].rightMargin += int(widthDiff / 2) minMaster.save() maxMaster.save() #verifications here print(minMaster.info.styleName + " " + minMaster[k].name + " = " + str(minMaster[k].width)) print(maxMaster.info.styleName + " " + maxMaster[k].name + " = " + str(maxMaster[k].width) + "\n") for masterMax in maxGradeMasters: for masterMin in minGradeMasters: if masterMax.replace(maxGradeFlag, "") == masterMin.replace(minGradeFlag, ""): maxMaster = OpenFont(masterMax) minMaster = OpenFont(masterMin) makeGrades(maxMaster, minMaster)
from fontParts.world import OpenFont myFont = OpenFont('Source Serif Pro Regular.ufo') glyphSet = myFont.groups['public.kern1.zero.lf'] print(glyphSet) # >>> ('zero.lf', 'zero.lfslash', 'zero.cap')
# since we are not propagating to all sources, this is currently hard-coded defaultPath = 'Amstelvar-Roman.ufo' paths = [ 'Amstelvar-Roman-opsz-36.ufo', 'Amstelvar-Roman-opsz-84-wghtmin.ufo', 'Amstelvar-Roman-opsz-84.ufo', 'Amstelvar-Roman-opsz-max-wdthmin-wghtmax.ufo', 'Amstelvar-Roman-opsz-max-wghtmin.ufo', 'Amstelvar-Roman-opsz-max.ufo', 'Amstelvar-Roman-opsz-min.ufo', 'Amstelvar-Roman-wdthmax.ufo', 'Amstelvar-Roman-wdthmin.ufo', 'Amstelvar-Roman-wghtmax.ufo', 'Amstelvar-Roman-wghtmin.ufo' ] default = OpenFont(os.path.join(os.getcwd(), defaultPath), showInterface=False) defaultxtraValue = getValueFromGlyphIndex( default['H'], 22)[0] - getValueFromGlyphIndex(default['H'], 11)[0] print("| Source | Multiplier | XTRA value |") print('| default', '|', 1, '|', defaultxtraValue, '|') for path in paths: f = OpenFont(os.path.join(os.getcwd(), path), showInterface=False) xtraValue = getValueFromGlyphIndex(f['H'], 22)[0] - getValueFromGlyphIndex( f['H'], 11)[0] m = xtraValue / defaultxtraValue """ Input: opsz 008 wdth 100 wght 400 lxtra 0.98 + from dxtra... Input: opsz 014 wdth 050 wght 400 lxtra 1.10 Input: opsz 014 wdth 150 wght 400 lxtra 1.50
import rbWindow.editWindow as ew from rbWindow.ExtensionSetting.extensionValue import * from jsonConverter.makeJsonFile import * from testCode.initialization import * from parseSyllable.configVersionFinal import * from fontParts.world import CurrentFont, OpenFont from mojo.UI import * testPath, testFile = launchFontTool() if testPath is None: quit() print("testPath(launchFontTool) = ", testPath) configPreset = ConfigExtensionSetting(DefaultKey) configPreset.checkLangauge() configPreset.registerSettings() groupDict = None # launchFontTool() 리턴 값과 같다면 지워도 무방할듯...? 일단 안지움 testFile = OpenFont(testPath, showInterface=False) FileNameList = StartProgram(testPath, testFile, CurrentFont()) setExtensionDefault(DefaultKey + ".font", CurrentFont()) setExtensionDefault(DefaultKey + ".jsonFileName1", FileNameList[0]) setExtensionDefault(DefaultKey + ".jsonFileName2", FileNameList[1]) setExtensionDefault(DefaultKey + ".testPath", testPath) KoreanCheck = getExtensionDefault(DefaultKey + ".korean") menuWindow = ew.EditGroupMenu(groupDict, FileNameList[0], FileNameList[1]) fontWindowObserver()
test = OpenFont except: from fontParts.world import OpenFont # if we can get vanilla, select the designspace # if not, use the commandline argument try: from vanilla.dialogs import getFolder, getFile designspacePath = getFile('Get designspace file')[0] except: designspacePath = sys.argv[1] doc = DesignSpaceDocument() doc.read(designspacePath) designspaceFileName = os.path.split(designspacePath)[1] for source in doc.sources: if source.copyInfo: f = OpenFont(source.path) measurements = {} for gname in f.glyphOrder: g = f[gname] getIndices: pointLabels = g.lib.get(com.typemytype.robofont.pointLabels) for pointLabel in pointLabels:
def execute(tool, fn, scriptargspec, chain=None): # Function to handle parameter parsing, font and file opening etc in command-line scripts # Supports opening (and saving) fonts using PysilFont UFO (UFO), fontParts (FP) or fontTools (FT) # Special handling for: # -d variation on -h to print extra info about defaults # -q quiet mode - only output a single line with count of errors (if there are any) # -l opens log file and also creates a logger function to write to the log file # -p other parameters. Includes backup settings and loglevel/scrlevel settings for logger # for UFOlib scripts, also includes all outparams keys and ufometadata settings argspec = list(scriptargspec) chainfirst = False if chain == "first": # If first call to execute has this set, only do the final return part of chaining chainfirst = True chain = None params = chain["params"] if chain else parameters() logger = chain[ "logger"] if chain else params.logger # paramset has already created a basic logger argv = chain["argv"] if chain else sys.argv if tool == "UFO": from silfont.ufo import Ufont elif tool == "FT": from fontTools import ttLib elif tool == "FP": from fontParts.world import OpenFont elif tool == "" or tool is None: tool = None else: logger.log("Invalid tool in call to execute()", "X") return basemodule = sys.modules[fn.__module__] poptions = {} poptions['prog'] = splitfn(argv[0])[1] poptions['description'] = basemodule.__doc__ poptions['formatter_class'] = argparse.RawDescriptionHelpFormatter epilog = "For more help options use -h ?. For more documentation see https://github.com/silnrsi/pysilfont/blob/master/docs/scripts.md#" + poptions[ 'prog'] + "\n\n" poptions['epilog'] = epilog + "Version: " + params.sets['default'][ 'version'] + "\n" + params.sets['default']['copyright'] parser = argparse.ArgumentParser(**poptions) parser._optionals.title = "other arguments" # Add standard arguments standardargs = { 'quiet': ('-q', '--quiet', { 'help': 'Quiet mode - only display severe errors', 'action': 'store_true' }, {}), 'log': ('-l', '--log', { 'help': 'Log file' }, { 'type': 'outfile' }), 'params': ('-p', '--params', { 'help': 'Other parameters - see parameters.md for details', 'action': 'append' }, { 'type': 'optiondict' }), 'nq': ('--nq', { 'help': argparse.SUPPRESS, 'action': 'store_true' }, {}) } suppliedargs = [] for a in argspec: argn = a[:-2][ -1] # [:-2] will give either 1 or 2, the last of which is the full argument name if argn[0:2] == "--": argn = argn[2:] # Will start with -- for options suppliedargs.append(argn) for arg in sorted(standardargs): if arg not in suppliedargs: argspec.append(standardargs[arg]) defhelp = False if "-h" in argv: # Look for help option supplied pos = argv.index("-h") if pos < len(argv) - 1: # There is something following -h! opt = argv[pos + 1] if opt in ("d", "defaults"): defhelp = True # Normal help will be displayed with default info displayed by the epilog deffiles = [] defother = [] elif opt in ("p", "params"): params.printhelp() sys.exit(0) else: if opt != "?": print("Invalid -h value") print("-h ? displays help options") print( "-h d (or -h defaults) lists details of default values for arguments and parameters" ) print( "-h p (or -h params) gives help on parameters that can be set with -p or --params" ) sys.exit(0) quiet = True if "-q" in argv and '--nq' not in argv else False if quiet: logger.scrlevel = "S" # Process the supplied argument specs, add args to parser, store other info in arginfo arginfo = [] logdef = None for a in argspec: # Process all but last tuple entry as argparse arguments nonkwds = a[:-2] kwds = a[-2] parser.add_argument(*nonkwds, **kwds) # Create ainfo, a dict of framework keywords using argument name argn = nonkwds[ -1] # Find the argument name from first 1 or 2 tuple entries if argn[0:2] == "--": argn = argn[2:] # Will start with -- for options ainfo = dict(a[-1]) #Make a copy so original argspec is not changed for key in ainfo: # Check all keys are valid if key not in ("def", "type", "optlog"): logger.log("Invalid argspec framework key: " + key, "X") ainfo['name'] = argn if argn == 'log': logdef = ainfo['def'] if 'def' in ainfo else None optlog = ainfo['optlog'] if 'optlog' in ainfo else False arginfo.append(ainfo) if defhelp: arg = nonkwds[0] if 'def' in ainfo: defval = ainfo['def'] if argn == 'log' and logdef: defval += " in logs subdirectory" deffiles.append([arg, defval]) elif 'default' in kwds: defother.append([arg, kwds['default']]) # if -h d specified, change the help epilog to info about argument defaults if defhelp: if not (deffiles or defother): deftext = "No defaults for parameters/options" else: deftext = "Defaults for parameters/options - see user docs for details\n" if deffiles: deftext = deftext + "\n Font/file names\n" for (param, defv) in deffiles: deftext = deftext + ' {:<20}{}\n'.format(param, defv) if defother: deftext = deftext + "\n Other parameters\n" for (param, defv) in defother: deftext = deftext + ' {:<20}{}\n'.format(param, defv) parser.epilog = deftext + "\n\n" + parser.epilog # Parse the command-line arguments. If errors or -h used, procedure will exit here args = parser.parse_args(argv[1:]) # Process the first positional parameter to get defaults for file names fppval = getattr(args, arginfo[0]['name']) if isinstance( fppval, list): # When nargs="+" or nargs="*" is used a list is returned (fppath, fpbase, fpext) = splitfn(fppval[0]) if len(fppval) > 1: fpbase = "wildcard" else: if fppval is None: fppval = "" # For scripts that can be run with no positional parameters (fppath, fpbase, fpext) = splitfn(fppval) # First pos param use for defaulting # Process parameters if chain: execparams = params.sets["main"] args.params = {} # clparams not used when chaining else: # Read config file from disk if it exists configname = os.path.join(fppath, "pysilfont.cfg") if os.path.exists(configname): params.addset("config file", configname, configfile=configname) else: params.addset("config file") # Create empty set if not quiet and "scrlevel" in params.sets["config file"]: logger.scrlevel = params.sets["config file"]["scrlevel"] # Process command-line parameters clparams = {} if 'params' in args.__dict__: if args.params is not None: for param in args.params: x = param.split("=", 1) if len(x) != 2: logger.log("params must be of the form 'param=value'", "S") if x[1] == "\\t": x[1] = "\t" # Special handling for tab characters clparams[x[0]] = x[1] args.params = clparams params.addset("command line", "command line", inputdict=clparams) if not quiet and "scrlevel" in params.sets["command line"]: logger.scrlevel = params.sets["command line"]["scrlevel"] # Create main set of parameters based on defaults then update with config file values and command line values params.addset("main", copyset="default") params.sets["main"].updatewith("config file") params.sets["main"].updatewith("command line") execparams = params.sets["main"] # Set up logging if chain: setattr(args, 'logger', logger) args.logfile = logger.logfile else: logfile = None logname = args.log if 'log' in args.__dict__ and args.log is not None else "" if 'log' in args.__dict__: if logdef is not None and (logname is not "" or optlog == False): (path, base, ext) = splitfn(logname) (dpath, dbase, dext) = splitfn(logdef) if not path: if base and ext: # If both specified then use cwd, ie no path path = "" else: path = (fppath if dpath is "" else os.path.join( fppath, dpath)) path = os.path.join(path, "logs") if not base: if dbase == "": base = fpbase elif dbase[ 0] == "_": # Append to font name if starts with _ base = fpbase + dbase else: base = dbase if not ext and dext: ext = dext logname = os.path.join(path, base + ext) if logname == "": logfile = None else: (logname, logpath, exists) = fullpath(logname) if not exists: (parent, subd) = os.path.split(logpath) if subd == "logs" and os.path.isdir( parent ): # Create directory if just logs subdir missing logger.log("Creating logs subdirectory in " + parent, "P") os.mkdir(logpath) else: # Fails, since missing dir is probably a typo! logger.log("Directory " + parent + " does not exist", "S") logger.log('Opening log file for output: ' + logname, "P") try: logfile = io.open(logname, "w", encoding="utf-8") except Exception as e: print(e) sys.exit(1) args.log = logfile # Set up logger details logger.loglevel = execparams['loglevel'].upper() if not quiet: logger.scrlevel = execparams['scrlevel'].upper() logger.logfile = logfile setattr(args, 'logger', logger) # Process the argument values returned from argparse outfont = None infontlist = [] for c, ainfo in enumerate(arginfo): aval = getattr(args, ainfo['name']) if ainfo['name'] in ('params', 'log'): continue # params and log already processed atype = None adef = None if 'type' in ainfo: atype = ainfo['type'] if atype not in ('infont', 'outfont', 'infile', 'outfile', 'incsv', 'filename', 'optiondict'): logger.log("Invalid type of " + atype + " supplied in argspec", "X") if atype != 'optiondict': # All other types are file types, so adef must be set, even if just to "" adef = ainfo['def'] if 'def' in ainfo else "" if adef is None and aval is None: # If def explicitly set to None then this is optional setattr(args, ainfo['name'], None) continue if c == 0: if aval is None: logger.log("Invalid first positional parameter spec", "X") if aval[-1] in ("\\", "/"): aval = aval[0:-1] # Remove trailing slashes else: #Handle defaults for all but first positional parameter if adef is not None: if not aval: aval = "" if aval == "" and adef == "": # Only valid for output font parameter if atype != "outfont": logger.log("No value suppiled for " + ainfo['name'], "S") ## Not sure why this needs to fail - we need to cope with other optional file or filename parameters (apath, abase, aext) = splitfn(aval) (dpath, dbase, dext) = splitfn(adef) # dpath should be None if not apath: if abase and aext: # If both specified then use cwd, ie no path apath = "" else: apath = fppath if not abase: if dbase == "": abase = fpbase elif dbase[ 0] == "_": # Append to font name if starts with _ abase = fpbase + dbase else: abase = dbase if not aext: if dext: aext = dext elif (atype == 'outfont' or atype == 'infont'): aext = fpext aval = os.path.join(apath, abase + aext) # Open files/fonts if atype == 'infont': if tool is None: logger.log("Can't specify a font without a font tool", "X") infontlist.append(( ainfo['name'], aval)) # Build list of fonts to open when other args processed elif atype == 'infile': logger.log('Opening file for input: ' + aval, "P") try: aval = io.open(aval, "r", encoding="utf-8") except Exception as e: print(e) sys.exit(1) elif atype == 'incsv': logger.log('Opening file for input: ' + aval, "P") aval = csvreader(aval, logger=logger) elif atype == 'outfile': (aval, path, exists) = fullpath(aval) if not exists: logger.log("Output file directory " + path + " does not exist", "S") logger.log('Opening file for output: ' + aval, "P") try: aval = io.open(aval, 'w', encoding="utf-8") except Exception as e: print(e) sys.exit(1) elif atype == 'outfont': if tool is None: logger.log("Can't specify a font without a font tool", "X") outfont = aval outfontpath = apath outfontbase = abase outfontext = aext elif atype == 'optiondict': # Turn multiple options in the form ['opt1=a', 'opt2=b'] into a dictionary avaldict = {} if aval is not None: for option in aval: x = option.split("=", 1) if len(x) != 2: logger.log("options must be of the form 'param=value'", "S") if x[1] == "\\t": x[1] = "\t" # Special handling for tab characters avaldict[x[0]] = x[1] aval = avaldict setattr(args, ainfo['name'], aval) # Open fonts - needs to be done after processing other arguments so logger and params are defined for name, aval in infontlist: if chain and name == 'ifont': aval = chain["font"] else: if tool == "UFO": aval = Ufont(aval, params=params) if tool == "FT": aval = ttLib.TTFont(aval) if tool == "FP": aval = OpenFont(aval) setattr(args, name, aval) # Assign the font object to args attribute # All arguments processed, now call the main function setattr(args, "paramsobj", params) setattr(args, "cmdlineargs", argv) newfont = fn(args) # If an output font is expected and one is returned, output the font if chainfirst: chain = True # Special handling for first call of chaining if newfont: if chain: # return font to be handled by chain() return (args, newfont) else: if outfont: # Backup the font if output is overwriting original input font if outfont == infontlist[0][1]: backupdir = os.path.join(outfontpath, execparams['backupdir']) backupmax = int(execparams['backupkeep']) backup = str2bool(execparams['backup']) if backup: if not os.path.isdir( backupdir ): # Create backup directory if not present try: os.mkdir(backupdir) except Exception as e: print(e) sys.exit(1) backupbase = os.path.join(backupdir, outfontbase + outfontext) # Work out backup name based on existing backups nums = sorted( [ int(i[len(backupbase) + 1 - len(i):-1]) for i in glob(backupbase + ".*~") ] ) # Extract list of backup numbers from existing backups newnum = max(nums) + 1 if nums else 1 backupname = backupbase + "." + str(newnum) + "~" # Backup the font logger.log("Backing up input font to " + backupname, "P") shutil.copytree(outfont, backupname) # Purge old backups for i in range(0, len(nums) - backupmax + 1): backupname = backupbase + "." + str(nums[i]) + "~" logger.log("Purging old backup " + backupname, "I") shutil.rmtree(backupname) else: logger.log( "No font backup done due to backup parameter setting", "W") # Output the font if tool in ("FT", "FP"): logger.log("Saving font to " + outfont, "P") newfont.save(outfont) else: # Must be Pyslifont Ufont newfont.write(outfont) else: logger.log( "Font returned to execute() but no output font is specified in arg spec", "X") elif chain: # ) When chaining return just args - the font can be accessed by args.ifont return (args, None ) # ) assuming that the script has not changed the input font if logger.errorcount or logger.warningcount: message = "Command completed with " + str( logger.errorcount) + " errors and " + str( logger.warningcount) + " warnings" if logger.scrlevel in ("S", "E") and logname is not "": if logger.scrlevel == "S" or logger.warningcount: message = message + " - see " + logname if logger.errorcount: if quiet: logger.raisescrlevel("E") logger.log(message, "E") logger.resetscrlevel() else: logger.log(message, "P") if logger.scrlevel == "P" and logger.warningcount: logger.log( "See log file for warning messages or rerun with '-p scrlevel=w'", "P") else: logger.log("Command completed with no warnings", "P") return (args, newfont)
if addMissingGlyphsFromDefault or buildSlugs: masters2 = [] base = os.path.split(__file__)[0] temp = os.path.join(base, 'temp') if os.path.exists(temp): shutil.rmtree(temp, ignore_errors=True) if not os.path.exists(temp): os.mkdir(temp) os.mkdir(os.path.join(temp, pathReplace)) for i, master in enumerate(masters): srcPath = os.path.join(base, master) destPath = os.path.join(temp, master) shutil.copytree(srcPath, destPath) if i == 0: f = OpenFont(destPath) src = OpenFont(destPath) else: f = OpenFont(destPath) if buildSlugs: print "Building slugs..." for slug in slugs.items(): slugName, slugInfo = slug slugComponents, slugUnicode = slugInfo setSlug(f, slugName, slugComponents) f[slugName].unicodes = [slugUnicode] if i != 0 and addMissingGlyphsFromDefault: print "Adding missing glyphs in sources from default..." for gname in src.keys():
if self.layers['segment']: self.drawSegment() drawBot.restore() #--------- # testing #--------- if __name__ == '__main__': folder = os.getcwd() ufoPath = os.path.join(folder, 'FontParts.ufo') size('A4Landscape') f = OpenFont(ufoPath) L = FontPartsLogoType(f) L.interpolationFactor = 0.5 L.draw((60, 100)) L.scale = 0.07 L.layers['font'] = False L.layers['anchor'] = False L.layers['bPoint'] = False L.layers['point'] = False L.infoStrokeWidth = 10 L.infoValuesDraw = False # L.guidelineValuesDraw = False L.infoLineDash = None # 90, 30 L.contourStrokeWidth = 20 L.glyphWidthStrokeWidth = 15
def test_font_open(self): OpenFont(self.font_path)
# first group, second glyph elif first.startswith(FIRST_PREFIX): for firstGlyph in aFont.groups[first]: flatK[(firstGlyph, second)] = correction # first glyph, second group elif second.startswith(SECOND_PREFIX): for secondGlyph in aFont.groups[second]: flatK[(first, secondGlyph)] = correction # first glyph, second glyph else: flatK[(first, second)] = correction return flatK ### Instructions if __name__ == '__main__': from fontParts.world import OpenFont fontName = 'Source Serif Pro Regular.ufo' thisFont = OpenFont(fontName) print(len(thisFont.kerning)) # 7970 flatK = flatKerning(thisFont) print(len(flatK)) # 237806 😅
import sys, os from fontParts.world import OpenFont path = os.path.abspath(__file__) dir_path = os.path.dirname(path) numbersource = os.path.join(dir_path, "numbersfont.ufo") numbers = [ "one", "two", "three", "four", "five", "six", "seven", "eight", "nine" ] numbersfont = OpenFont(numbersource)
def clean(folder): if exists(folder): rmtree(folder) mkdir(folder) ### Instructions if __name__ == '__main__': outputFolder = 'output' clean(outputFolder) fontPaths = catchFiles('fonts', '.ufo') glyphSets = {'uppercase': ascii_uppercase, 'lowercase': ascii_lowercase} for eachPath in fontPaths: thisFont = OpenFont(eachPath) for setName, eachSet in glyphSets.items(): flat = flatKerning(thisFont, eachSet) canvasSize = CELL_SIZE * (len(eachSet) + 4) newDrawing() newPage(canvasSize, canvasSize) translate(CELL_SIZE * 2, CELL_SIZE * 2) kerningHeatMap(flat, eachSet, isFirstVertical=True) fontName = f'{thisFont.info.familyName} {thisFont.info.styleName}' saveImage(join(outputFolder, f'{fontName} - {setName}.pdf')) endDrawing()
from fontTools.pens.cocoaPen import CocoaPen from AppKit import NSBezierPath from fontParts.world import OpenFont fontA = OpenFont("Roboto-Regular.ufo", showInterface=False) fontB = OpenFont("../src/RobotoDelta-Regular.ufo", showInterface=False) def drawGlyph(pen, glyph): save() stroke(None) fill(0) pen.path = NSBezierPath.bezierPath() glyph.draw(pen) drawPath(pen.path) restore() def drawOutline(pen, glyph, color, s): save() strokeWidth(1 / s) stroke(*color) fill(None) pen.path = NSBezierPath.bezierPath() glyph.draw(pen) drawPath(pen.path) restore() def drawMetrics(glyph, color, s=1): save()
comparedDictionary = dict( d=-250, D=-270, B=-20, x=500, X=520, c=700, C=720, a=750, A=770, ) self.assertEqual(dictionary, comparedDictionary) if __name__ == '__main__': curFont = CurrentFont() path = Path('test_font.ufo') if curFont.path and Path(curFont.path).absolute() != path.absolute(): OpenFont(path) loader = unittest.TestLoader() tests = [] for _, obj in list(locals().items()): try: if issubclass(obj, unittest.TestCase): tests.append(loader.loadTestsFromTestCase(obj)) except: pass suite = unittest.TestSuite(tests) runner = unittest.TextTestRunner() result = runner.run(suite) print(result)
axis.minimum = opsz['min'] print() print("Processing OpszMin") for ufo in set([m.path for m in doc.sources]): newUfo = ufo.replace('.ufo', '-OpszMin.ufo') shutil.copytree(ufo, newUfo) source = SourceDescriptor() source.path = newUfo source.familyName = familyName if "Light" in newUfo or "Thin" in newUfo: lightUfo = newUfo font = OpenFont(newUfo) tweakSpacing(font, adjustments['min']['offset'], adjustments['min']['percentage']) font.save() if adjustments['min']['scaleFactor'] != 1: factor = adjustments['min']['scaleFactor'] print('Scaling %s by %s' % (font.path, factor)) scaleFont(font.path, font.path, factor) source.location = {'Weight': wght['min'], 'Optical size': opsz['min']} source.styleName = "ThinMin" else: blackUfo = newUfo font = OpenFont(newUfo) tweakSpacing(font, adjustments['max']['offset'],