Example #1
0
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()
Example #3
0
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)]
Example #5
0
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')

Example #7
0

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)
Example #8
0
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()
Example #11
0
    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:
                            
                    
Example #12
0
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)
Example #13
0
    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():
Example #14
0
        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)
Example #16
0
        # 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 😅
Example #17
0
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()
Example #19
0
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()
Example #20
0
        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'],