From 1504ce3fec6bc66052c46695bf92c22d1ebea4ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Frank=20Grie=C3=9Fhammer?= Date: Mon, 22 Jan 2024 22:21:39 +0100 Subject: [PATCH 1/4] remove codecov link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5546a40..f03f706 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![Coverage](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/adobe-bot/116f9fefb73f6fcf9ffe8ce34d72efd7/raw/covbadge.json)](https://codecov.io/gh/adobe-type-tools/kern-dump) +[![Coverage](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/adobe-bot/116f9fefb73f6fcf9ffe8ce34d72efd7/raw/covbadge.json)] # kernDump Various scripts for analyzing, reading and writing kerning information. These From db0c5633e4989a155f127b761692e2acf0a3654c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Frank=20Grie=C3=9Fhammer?= Date: Mon, 22 Jan 2024 22:37:58 +0100 Subject: [PATCH 2/4] remove outdated UFOkerning scripts --- UFOkerning/README.md | 40 ------- UFOkerning/convertFEAtoMMG.py | 139 ---------------------- UFOkerning/exportFLCfromUFO.py | 148 ------------------------ UFOkerning/mmg2flc.py | 139 ---------------------- UFOkerning/subsetUFOKerningAndGroups.py | 74 ------------ 5 files changed, 540 deletions(-) delete mode 100644 UFOkerning/README.md delete mode 100644 UFOkerning/convertFEAtoMMG.py delete mode 100644 UFOkerning/exportFLCfromUFO.py delete mode 100755 UFOkerning/mmg2flc.py delete mode 100644 UFOkerning/subsetUFOKerningAndGroups.py diff --git a/UFOkerning/README.md b/UFOkerning/README.md deleted file mode 100644 index 186248c..0000000 --- a/UFOkerning/README.md +++ /dev/null @@ -1,40 +0,0 @@ -# UFOkerning -More specific scripts for UFO kerning-related operations. - ---- - -### `convertFEAtoMMG.py` -Create a MetricsMachine Groups (MMG) file from a kern feature file. - -__Dependencies:__ None -__Environment:__ command line - -``` -python convertFEAtoMMG.py kern.fea -``` - ---- - -### `exportFLCfromUFO.py` -Dumps a FontLab Classes file (FLC) from a UFO. -Assumes MetricsMachine-built class names. - -__Dependencies:__ [defcon](https://github.com/typesupply/defcon) -__Environment:__ command line - -``` -python exportFLCfromUFO.py font.ufo -``` - ---- - -### `subsetUFOKerningAndGroups.py` -Subset kerning and groups in a UFO given a list of glyphs provided. -Will export new plist files that can be swapped into the UFO. - -__Dependencies:__ [defcon](https://github.com/typesupply/defcon) -__Environment:__ command line - -``` -python subsetUFOKerningAndGroups.py glyphList font.ufo -``` diff --git a/UFOkerning/convertFEAtoMMG.py b/UFOkerning/convertFEAtoMMG.py deleted file mode 100644 index 407592b..0000000 --- a/UFOkerning/convertFEAtoMMG.py +++ /dev/null @@ -1,139 +0,0 @@ -import re -import os, sys -from random import randint - -if len(sys.argv) > 1: - input = sys.argv[1] -else: - print 'No feature file provided.' - sys.exit() - -if os.path.isfile(input): - f = open(input, 'r') - output = open(os.path.splitext(f.name)[0]+'.mmg', 'w') -else: - print 'No proper UFO file provided.' - sys.exit() - - -leftglyphs = [] -rightglyphs = [] -#start writing the mmg file -output.write('\n\n') - -#finding out which glyphs are present in a class -# def glyphlist(line): -# result = '' -# result += ' \n' -# -# glyphs = line.split('[')[1] -# glyphs = glyphs.strip('];\n').split() -# for x in glyphs: -# result += ' '+x+'\n' -# result += ' \n \n' -# return result - - -#writing groups, and finally putting the glyphs inside -def kernclass(): - glyphXMLstring = '' - for line in f: - left = False - right = False - red = str(0.1*randint(0,10))+' ' - green = str(0.1*randint(0,10))+' ' - blue = str(0.1*randint(0,10))+' ' - start = (' \n') - end_r = ('" side="right" type="kerning">\n') - - - # @ALPHA_UC_LEFT_GRK - if line.startswith('@'): -# glyphXMLstring = glyphlist(line) - classname = line.split('=')[0].strip('@ ') - glyphs = line.split('[')[1] - glyphs = glyphs.strip('];\n').split() - - glyphXMLstring = ' \n %s\n \n ' % '\n '.join(glyphs) - - - if 'LEFT' in classname.split('_'): - left = True - elif 'RIGHT' in classname.split('_'): - right = True - else: - (left, right) = (True, True) - - - - if (left, right) == (True, True): - ok = False - for g in glyphs: - if not g in leftglyphs: - leftglyphs.append(g) - ok = True - else: - print 'Glyph %s has already been used. Check class %s' % (g, classname) - ok = False - break - if ok: - print classname, 'fine' - output.write(start+classname+end_l) - output.write(glyphXMLstring) - - ok = False - for g in glyphs: - if not g in rightglyphs: - rightglyphs.append(g) - ok = True - else: - print 'Glyph %s has already been used. Check class %s' % (g, classname) - ok = False - break - - if ok: - output.write(start+classname+end_r) - output.write(glyphXMLstring) - - if (left, right) == (True, False): - ok = False - for g in glyphs: - if not g in leftglyphs: - leftglyphs.append(g) - ok = True - else: - print 'Glyph %s has already been used. Check class %s' % (g, classname) - ok = False - break - if ok: - output.write(start+classname+end_l) - output.write(glyphXMLstring) - - if (left, right) == (False, True): - ok = False - for g in glyphs: - if not g in rightglyphs: - rightglyphs.append(g) - ok = True - else: - print 'Glyph %s has already been used. Check class %s' % (g, classname) - ok = False - break - - if ok: - output.write(start+classname+end_r) - output.write(glyphXMLstring) - - -#run -kernclass() - -output.write('\n') - -#cleanup -f.close() -output.close() - - - \ No newline at end of file diff --git a/UFOkerning/exportFLCfromUFO.py b/UFOkerning/exportFLCfromUFO.py deleted file mode 100644 index 34e03e6..0000000 --- a/UFOkerning/exportFLCfromUFO.py +++ /dev/null @@ -1,148 +0,0 @@ -# writes FLC-style classes from UFO -# Don't forget to delete Metrics Machine reference groups! -from defcon import Font as defconFont -import re, os, sys - - -if len(sys.argv) > 1: - input = sys.argv[1] -else: - print 'No UFO file provided.' - sys.exit() - -if os.path.exists(input): - font = defconFont(input) -else: - print 'No UFO file provided.' - sys.exit() - -# filePath = GetFile("Pick UFO...") -kerningGroups = [groupName for groupName in font.groups.keys() if '@MMK_' in groupName] -otherGroups = [groupName for groupName in font.groups.keys() if not groupName in kerningGroups] - -def getKeyGlyph(groupName): - if groupName in kerningGroups: - if alt_mode: - # this is for groups named 'LAT_LC_a', 'CYR_LC_a.cyr' etc. - - if groupName.split('_')[-1] in font.groups[groupName]: - return '%s' % groupName.split('_')[-1] - elif '%s_%s' % (groupName.split('_')[-2], groupName.split('_')[-1]) in font.groups[groupName]: # ligatures - return '%s_%s' % (groupName.split('_')[-2], groupName.split('_')[-1]) - elif '%s_%s_%s' % (groupName.split('_')[-3], groupName.split('_')[-2], groupName.split('_')[-1]) in font.groups[groupName]: # ligatures - return '%s_%s_%s' % (groupName.split('_')[-3], groupName.split('_')[-2], groupName.split('_')[-1]) - else: - return font.groups[groupName][0] - - else: - # this is for groups named 'I', 'I_LC', 'S_LC_LEFT_LAT' etc. - # assuming Metrics Machine-built group names - glyph = groupName.split('_')[2] - - if glyph in font.groups[groupName]: - return '%s' % glyph - elif glyph.lower() in font.groups[groupName]: - return '%s' % glyph.lower() - -# elif '%s_%s' % (groupName.split('_')[-2], groupName.split('_')[-1]) in font.groups[groupName]: # ligatures -# return '%s_%s' % (groupName.split('_')[-2], groupName.split('_')[-1]) -# elif '%s_%s_%s' % (groupName.split('_')[-3], groupName.split('_')[-2], groupName.split('_')[-1]) in font.groups[groupName]: # ligatures -# return '%s_%s_%s' % (groupName.split('_')[-3], groupName.split('_')[-2], groupName.split('_')[-1]) - - else: - return font.groups[groupName][0] - - -def getOtherGlyphs(groupName): - if groupName in kerningGroups: - glyphlist = font.groups[groupName] - glyphlist.remove(getKeyGlyph(groupName)) - return sorted(glyphlist) - - -def getFlag(groupName): - if groupName in kerningGroups: - return groupName.split('_')[1] - - -def getWSystem(groupName): - wSystem = None - for i in groupName.split('_'): - if re.match(r'(LAT|GRK|CYR)', i): - wSystem = i - break - return wSystem - - -def getCase(groupName): - case = None - for i in groupName.split('_'): - if len(i) > 1: - if re.match(r'(UC|LC)', i): - case = i - return case - -def adobeName(groupName): - # change class name to match adobe class naming. - if getFlag(groupName) == 'L': - flag = 'LEFT' - else: flag = 'RIGHT' - - name = '_%s' % getKeyGlyph(groupName) - - if getCase(groupName): - name += '_%s' % getCase(groupName) - - name += '_%s' % flag - - if getWSystem(groupName): - name += '_%s' % getWSystem(groupName) - - return name - - -def writeFLclass(groupName): - return '''\ -%%%%CLASS %s -%%%%GLYPHS %s' %s -%%%%KERNING %s 0 -%%%%END -''' % ('%s' % adobeName(groupName), getKeyGlyph(groupName), ' '.join(getOtherGlyphs(groupName)), getFlag(groupName)) - - -## figure which way the classes are named: -alt_mode = False # this is the alternate mode, for groups named 'LAT_LC_a', 'CYR_LC_a.cyr' etc. - -rex = r'(LAT|CYR|GRK)_(LC|UC)' -counter = 0 -for g in kerningGroups: - if re.search(rex, g): counter += 1 -if counter > len(kerningGroups)/2: - alt_mode = False - - -# mode1 = False # this is for groups named 'LAT_LC_a', 'CYR_LC_a.cyr' etc. -# mode2 = False # this is for groups named 'I_LC', 'S_LC_LEFT_LAT' etc. -# -# rex1 = r'(LAT|CYR|GRK)_(LC|UC)' -# rex2 = r'(.+?)_(LC|UC)' -# counter1 = 0 -# counter2 = 0 -# for g in font.groups.keys(): -# if re.search(rex1, g): counter1 += 1 -# elif re.search(rex2, g): counter2 += 1 -# if counter1 > len(font.groups.keys())/2: -# mode1 = True -# if counter2 > len(font.groups.keys())/2: -# mode2 = True - - -print '%%FONTLAB CLASSES\n' - -for groupName in sorted(kerningGroups): -# groupName = '_%s' % groupName[1:] - print writeFLclass(groupName) - -if otherGroups: - print '#' * 50 - print 'The follwing groups are not kerning groups,\nand were therefore not exported:\n\n%s' % '\n'.join(otherGroups) diff --git a/UFOkerning/mmg2flc.py b/UFOkerning/mmg2flc.py deleted file mode 100755 index c33585c..0000000 --- a/UFOkerning/mmg2flc.py +++ /dev/null @@ -1,139 +0,0 @@ -#!/bin/env python -import os -import sys -import xml.etree.ElementTree as ET - -__doc__ = '''\ -mmg to flc: -Converts Metrics Machine group files (.mmg) to -FontLab-compatible class files (.flc). - -usage: -python mmg2flc.py file.mmg - -''' - - -class Group(object): - def __init__(self, name, keyglyph, side, glyphs): - - self.name = name - self.side = side - self.keyglyph = keyglyph - self.glyphs = glyphs - - -class Parser(object): - def __init__(self): - self.glyphdict = {} - self.grouplist = [] - self.output = [] - - def parse(self, tree): - for parent in tree.getiterator(): - if parent.tag == 'group': - for child in parent: - self.glyphdict[parent.get('name')] = child.text.split() - return self.glyphdict - - def makeClasses(self, group): - self.group = group - if len(group.glyphs) > 0: - if group.keyglyph in group.glyphs: - group.glyphs.remove(group.keyglyph) - kg = group.keyglyph - else: - kg = group.glyphs[0] - group.glyphs.remove(kg) - - # syntax example: - '''\ - %%CLASS _PARENLEFT_SC_RIGHT - %%GLYPHS parenleft.sc' braceleft.sc bracketleft.sc - %%KERNING R 0 - %%END - ''' - - self.output.append('%%%%CLASS _%s\r%%%%GLYPHS %s\' %s\r%%%%KERNING %s 0\r%%%%END\r' % (group.name, kg, ' '.join(group.glyphs), group.side)) - return self.output - - def convert(self): - li = [] - for i in self.glyphdict: - li.append(i) - - for i in li: - spli = [i.split('_') for i in li] - - for i in spli: - while len(i) != 5: - i.append('') - - deco = [((a[3], a[1]), a) for a in spli] - deco.sort() - sorted = [v for k, v in deco] - - for i in sorted: - i = filter(None, i) - i = '_'.join(i) - - # finding side - if 'LEFT' in i.split('_'): - side = 'L' - elif 'RIGHT' in i.split('_'): - side = 'R' - else: - side = 'LR' - - # finding keyglyph - if len(i.split('_')) > 3: - if i.split('_')[1] == 'UC': - kg = i.split('_')[0].title() - elif i.split('_')[1] == 'LC': - kg = i.split('_')[0].lower() - elif i.split('_')[1] == 'SC': - kg = '%s.sc' % i.split('_')[0].lower() - else: - kg = i.split('_')[0].lower() - else: - kg = i.split('_')[0].lower() - - # the glyphs` - glyphs = self.glyphdict[i] - - myGroup = Group(i, kg, side, glyphs) - self.makeClasses(myGroup) - - -def readFile(path): - # Reading content of a file, closing the file. - - file = open(path, 'r') - data = file.read() - file.close() - return data - - -def run(): - - inputfile = sys.argv[1] - path = inputfile.split(os.sep)[:-1] - outputfilename = '%s.flc' % inputfile.split(os.sep)[-1].split('.')[0] - input = readFile(inputfile) - - mmgconverter = Parser() - tree = ET.XML(input) - mmgconverter.parse(tree) - mmgconverter.convert() - - path.append(outputfilename) - outputfile = open(os.sep.join((path)), 'w') - outputfile.write('%%FONTLAB CLASSES\r\r') - for i in mmgconverter.output: - outputfile.write(i) - outputfile.write('\r') - outputfile.close() - - -if __name__ == "__main__": - run() diff --git a/UFOkerning/subsetUFOKerningAndGroups.py b/UFOkerning/subsetUFOKerningAndGroups.py deleted file mode 100644 index ac15a4b..0000000 --- a/UFOkerning/subsetUFOKerningAndGroups.py +++ /dev/null @@ -1,74 +0,0 @@ -#!/usr/bin/python -import sys -from plistlib import writePlist -from defcon import Font - - -__doc__ = ''' -Subset kerning and groups in a UFO given a list of glyphs provided. -Will export new plist files that can be swapped into the UFO. - -Usage: -python subsetUFOKerningAndGroups.py subsetList font.ufo -''' - - -class SubsetKerning(object): - def __init__(self, font, subsetFile): - self.font = Font(font) - self.subsetFile = subsetFile - - with open(self.subsetFile, 'r') as ssfile: - rawData = ssfile.read() - self.subsetGlyphList = [line.split()[0] for line in rawData.splitlines()] - - - def subsetGroups(self): - - newGroups = {} - for groupName, glyphList in self.font.groups.items(): - combinedGlyphs = set(self.subsetGlyphList) & set(glyphList) - newGlyphList = sorted(list(combinedGlyphs)) - - if len(newGlyphList): - newGroups[groupName] = newGlyphList - return newGroups - - - - def subsetKerning(self): - newGroups = self.subsetGroups() - newKerning = {} - plistStyleKerning = {} - - # All allowed items for kerning, which are our subset glyphs, - # plus the groups filtered earlier: - allowedItems = set(newGroups) | set(self.subsetGlyphList) - - for [left, right], value in self.font.kerning.items(): - if set([left, right]) <= allowedItems: - newKerning[left, right] = value - - # Since the kerning paradigm stored in the plist differs from the - # in the kerning object, the data structure needs some modification: - - for [left, right], value in newKerning.items(): - partnerDict = plistStyleKerning.setdefault(left, {}) - partnerDict[right] = value - - return plistStyleKerning - - -def run(): - sk = SubsetKerning(sys.argv[-1], sys.argv[-2]) - - writePlist(sk.subsetGroups(), 'subset_groups.plist') - writePlist(sk.subsetKerning(), 'subset_kerning.plist') - print 'done' - - -if len(sys.argv) == 3: - run() -else: - print __doc__ - From 26c611d0c3718f54338d75a5e3f89616359cca83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Frank=20Grie=C3=9Fhammer?= Date: Mon, 22 Jan 2024 22:38:53 +0100 Subject: [PATCH 3/4] remove transferKerningStructure.py messed-up script. Python2! --- transferKerningStructure.py | 478 ------------------------------------ 1 file changed, 478 deletions(-) delete mode 100755 transferKerningStructure.py diff --git a/transferKerningStructure.py b/transferKerningStructure.py deleted file mode 100755 index 9a6157b..0000000 --- a/transferKerningStructure.py +++ /dev/null @@ -1,478 +0,0 @@ -#!/usr/bin/python -# coding: utf-8 - -from __future__ import division -import os -import sys -import itertools -import getKerningPairsFromUFO -from collections import defaultdict -from robofab.world import RFont as Font -from robofab import ufoLib -import time - -# from defcon import Font -# ufo 3 defcon would write proper glif names, -# but creates a huge kerning plist. (why?) - -reload(getKerningPairsFromUFO) - -startTime = time.time() - -''' -2016/03/11 12:47:07 -# XXX This script works, but is not beautiful. -# XXX I consider it to be a work in progress. - -2016/09/01 -# XXX Big problem: This script changes start points in the target UFO. -# XXX At this point, it seems easier to just revert the change in the -# XXX .glif files via git than re-writing the script. - - -Transfer group structure from source UFO to (kerned) target UFO. ----------------------------------------------------------------- -This script is necessary if the groups change in one of several interpolating -masters -- they need to be re-unified. - -If `verbatim` mode is on, the kerning data in the target UFO is absolutely the -same before & after; but there might be a number of new exceptions. - -If `verbatim` mode is off, the best available group kerning value is chosen, -which might overwrite some zero-value exceptions. I found that to be the better -option in some cases. - - -required steps: -1. explode target kerning (handled outside) -2. match pairs to source groups -3. consolidate exploded kerning -4. delete target kerning & groups, replace data -''' - - -def glyphNameToFileName(glyphName, glyphSet): - - parts = glyphName.split(".") - - for partIndex, part in enumerate(parts): - part = part.replace('_', '-') - # substitute ligature markers with hyphens - - if part == '': - part = '_' - # rename .notdef to _.notdef - - letterList = list(part) - for i, letter in enumerate(letterList): - if letter != letter.lower(): - letter = '%s_' % letter - letterList[i] = letter - # marker following every capital letter - - part = '%s' % (''.join(letterList)) - parts[partIndex] = part - - return ".".join(parts) + ".glif" - - -class CustomWriter(ufoLib.UFOWriter): - - def getGlyphSet(self, glyphNameToFileNameFunc=None): - glyphNameToFileNameFunc = glyphNameToFileName - # print "Custom GN2FN active." - return super(CustomWriter, self).getGlyphSet(glyphNameToFileNameFunc) - - -class Group(object): - "Container for storing groups and related information." - - def __init__(self): - self.glyphs = [] - self.name = '' - self.side = '' - self.used = False - - -class KerningPair(object): - "Container for storing a kerning pair with attributes." - - def __init__(self): - self.kerningPair = 0 - self.variations = 0 - self.recordedVariations = 0 - self.flatPairValueDict = {} - - -loggedProgress = [] - - -def progressBar(totalCount, progressCount, increment=10, previousProgress=0): - 'simple progress bar' - progress = '{:.0f}'.format(progressCount / totalCount * 100) - if int(progress) % increment == 0 and not progress in loggedProgress: - if int(progress) > 0: - print '{}% done'.format(progress) - loggedProgress.append(progress) - - -def organizeGroups(f): - 'function to give groups more attributes than they already have.' - groupList = [] - usedGroups = set( - [item for item in itertools.chain(*f.kerning.keys()) if item.startswith('@')]) - # for groupName, glyphList in f.groups.items(): - for groupName in usedGroups: - group = Group() - group.glyphs = f.groups[groupName] - group.name = groupName - - if 'MMK_L' in groupName: - group.side = 'L' - elif 'MMK_R' in groupName: - group.side = 'R' - else: - group.side = None - groupList.append(group) - - return groupList - - -def askGlyphForGroupName(gName, side, groupList): - for group in groupList: - if gName in group.glyphs and group.side == side: - return group.name - break - - -def consolidateSingleException(sourceGlyphName, groupName, groupList, side, exceptionsDict): - '''Merges single exception pairs back into group exceptions, as far as possible.''' - # kernValueToPairList = {} - kernValueToPairList = defaultdict(list) - - for glyphName in groupList: - if side == 'L': - pair = glyphName, sourceGlyphName - elif side == 'R': - pair = sourceGlyphName, glyphName - - # kernValueToPairList.setdefault(kernValue, []).append(pair) - # kernValueToPairList[kernValue] = [] - kernValue = exceptionsDict[pair] - kernValueToPairList[kernValue].append(pair) - - if len(kernValueToPairList) > 1: - bestKernValue = max( - kernValueToPairList, key=lambda x: len(kernValueToPairList[x])) - pairs_to_consolidate = kernValueToPairList[bestKernValue] - else: - bestKernValue = kernValueToPairList.keys()[0] - pairs_to_consolidate = kernValueToPairList.values()[0] - - for pair in pairs_to_consolidate: - del exceptionsDict[pair] - - if side == 'L': - newPair = groupName, sourceGlyphName - elif side == 'R': - newPair = sourceGlyphName, groupName - - exceptionsDict[newPair] = bestKernValue - - -def make_glyph_to_group_dict(f, side, groupList): - sideDict = {} - for glyphName in f.keys(): - groupName = askGlyphForGroupName(glyphName, side, groupList) - if groupName != None: - sideDict.setdefault( - glyphName, askGlyphForGroupName(glyphName, side, groupList)) - return sideDict - - -def make_glyphList_to_group_dict(side, groupList): - hashGroupDict = {} - sideGroups = [group for group in groupList if group.side == side] - for group in sideGroups: - # hashGroupDict[hash(frozenset(group.glyphs))] = group.name - hashGroupDict[frozenset(group.glyphs)] = group.name - return hashGroupDict - - -def consolidateExceptions(exceptionDict, organizedGroups): - - leftGlyphListToGroupName = make_glyphList_to_group_dict( - 'L', organizedGroups) - rightGlyphListToGroupName = make_glyphList_to_group_dict( - 'R', organizedGroups) - - print 'consolidating exceptions ...' - leftExceptionGlyphs = set([left for left, right in exceptionDict.keys()]) - for leftGlyphName in leftExceptionGlyphs: - kernedAgainst = [ - right for left, right in exceptionDict.keys() if left == leftGlyphName] - for glyphList, groupName in rightGlyphListToGroupName.items(): - if glyphList <= set(kernedAgainst): - consolidateSingleException( - leftGlyphName, groupName, glyphList, 'R', exceptionDict) - - rightExceptionGlyphs = set([right for left, right in exceptionDict.keys()]) - for rightGlyphName in rightExceptionGlyphs: - kernedAgainst = [ - left for left, right in exceptionDict.keys() if right == rightGlyphName] - for glyphList, groupName in leftGlyphListToGroupName.items(): - if glyphList <= set(kernedAgainst): - consolidateSingleException( - rightGlyphName, groupName, glyphList, 'L', exceptionDict) - - return exceptionDict - - -def recordKerningPairData(flatPair, kerningPair, kernValue, allVariations, kerningPairData): - - kp = kerningPairData.setdefault(kerningPair, KerningPair()) - kp.kerningPair = kerningPair - kp.flatPairValueDict.setdefault(kernValue, []).append(flatPair) - kp.variations = allVariations - kp.recordedVariations += 1 - - -def makeValueDistribution(kerningPairData, verbatim=True): - best_value_pairs = {} - exceptions = {} - - if verbatim == True: - print 'verbatim mode' - else: - print 'non-verbatim mode' - - print 'analyzing value distribution and finding best standard kerning value ...' - for pair, pairData in kerningPairData.items(): - - if verbatim == True: - if pairData.variations == pairData.recordedVariations: - # all kerning variations have been found. No unmentioned zero - # kerns. - if pairData.variations > 1: - # If a pair of groups is found kerned with different - # values, the value with maximum occurrence is being - # picked. - maxOccurring = max( - pairData.flatPairValueDict, key=lambda x: len(pairData.flatPairValueDict[x])) - bestValue = maxOccurring - else: - bestValue = pairData.flatPairValueDict.keys()[0] - - best_value_pairs[pair] = bestValue - - else: - # exceptions to non-kerned pairs. - for kernValue, pairList in pairData.flatPairValueDict.items(): - for pair in pairList: - exceptions[pair] = kernValue - print 'exception to unkerned class found: %s %s' % (' '.join(pair), kernValue) - else: - # this mode adds kerning pairs, removes exceptions. - if pairData.variations > 1: - # If a pair of groups is found kerned with different values, - # the value with maximum occurrence is being picked. - maxOccurring = max( - pairData.flatPairValueDict, key=lambda x: len(pairData.flatPairValueDict[x])) - bestValue = maxOccurring - else: - bestValue = pairData.flatPairValueDict.keys()[0] - - best_value_pairs[pair] = bestValue - - return best_value_pairs, exceptions - - -def transferGroups(f_source, f_target): - f_target.groups.clear() - newGroups = {} - targetGlyphs = set(f_target.keys()) - - for group, glyphList in f_source.groups.items(): - # newGlyphList = list(set(glyphList) & targetGlyphs) - # if len(newGlyphList) != len(glyphList): - # print group - # print set(newGlyphList) - set(glyphList) - newGroups[group] = sorted(list(set(glyphList) & targetGlyphs)) - - f_target.groups.update(newGroups) - - -def analyzeKerning(f_source, f_target, flattenedPairDict, organizedSourceGroups): - # this works great if input- and output font is the same. But not for - # transfer (yet) - - singleGlyphsL = set([left for (left, right) in itertools.chain( - f_source.kerning.keys()) if not left.startswith('@')]) - singleGlyphsR = set([right for (left, right) in itertools.chain( - f_source.kerning.keys()) if not right.startswith('@')]) - # list of glyphs kerned on left and right side with additional info - - leftGroupsGlyphLists = [ - group.glyphs for group in organizedSourceGroups if group.side == 'L'] - rightGroupsGlyphLists = [ - group.glyphs for group in organizedSourceGroups if group.side == 'R'] - # nested list of glyph names - - exploded_group_pairs = [] - left_vs_exploded_pairs = [] - exploded_vs_right_pairs = [] - - print 'exploding all groups ...' - for leftGroupList, rightGroupList in itertools.product(leftGroupsGlyphLists, rightGroupsGlyphLists): - exploded_group_pairs.extend( - itertools.product(leftGroupList, rightGroupList)) - - print 'exploding single glyphs vs right groups ...' - for rightGroupList in rightGroupsGlyphLists: - left_vs_exploded_pairs.extend( - itertools.product(singleGlyphsL, rightGroupList)) - - print 'exploding left groups vs single glyphs ...' - for leftGroupList in leftGroupsGlyphLists: - exploded_vs_right_pairs.extend( - itertools.product(leftGroupList, singleGlyphsR)) - - print 'analyzing all single pairs ...' - all_single_pairs = [ - pair for pair in itertools.product(singleGlyphsL, singleGlyphsR)] - - print 'creating reverse group lookup dictionaries ...' - leftG2GDict = make_glyph_to_group_dict( - f_source, 'L', organizedSourceGroups) - rightG2GDict = make_glyph_to_group_dict( - f_source, 'R', organizedSourceGroups) - - flattenedPairDictSet = set(flattenedPairDict.keys()) - exploded_group_pairs_set = set(exploded_group_pairs) - left_vs_exploded_pairs_set = set(left_vs_exploded_pairs) - exploded_vs_right_pairs_set = set(exploded_vs_right_pairs) - - kerningPairData = {} - # dictionary of kerning pairs and kerningPair objects. - - gr_gr_set = set.intersection( - exploded_group_pairs_set, flattenedPairDictSet) - gl_gr_set = set.intersection( - left_vs_exploded_pairs_set, flattenedPairDictSet) - gr_gl_set = set.intersection( - exploded_vs_right_pairs_set, flattenedPairDictSet) - gl_gl_set = flattenedPairDictSet - exploded_group_pairs_set - \ - left_vs_exploded_pairs_set - exploded_vs_right_pairs_set - gr_gl_set - - progressCount = 0 - totalCount = sum(map(len, [gr_gr_set, gl_gr_set, gr_gl_set, gl_gl_set])) - - print 'iterating through all possible kerning combinations (%s) ...' % len(flattenedPairDict) - - for (left, right) in gr_gr_set: - progressCount += 1 - progress = progressBar(totalCount, progressCount) - leftGroupName = leftG2GDict[left] - rightGroupName = rightG2GDict[right] - kerningPair = leftGroupName, rightGroupName - kernValue = flattenedPairDict[(left, right)] - allVariations = len(list(itertools.product( - f_source.groups[leftGroupName], f_source.groups[rightGroupName]))) - recordKerningPairData( - (left, right), kerningPair, kernValue, allVariations, kerningPairData) - - for (left, right) in gl_gr_set: - progressCount += 1 - progress = progressBar(totalCount, progressCount) - rightGroupName = rightG2GDict[right] - kerningPair = left, rightGroupName - kernValue = flattenedPairDict[(left, right)] - allVariations = len( - list(itertools.product([left], f_source.groups[rightGroupName]))) - recordKerningPairData( - (left, right), kerningPair, kernValue, allVariations, kerningPairData) - - for (left, right) in gr_gl_set: - progressCount += 1 - progress = progressBar(totalCount, progressCount) - leftGroupName = leftG2GDict[left] - kerningPair = leftGroupName, right - kernValue = flattenedPairDict[(left, right)] - allVariations = len( - list(itertools.product(f_source.groups[leftGroupName], [right]))) - recordKerningPairData( - (left, right), kerningPair, kernValue, allVariations, kerningPairData) - - for (left, right) in gl_gl_set: - progressCount += 1 - progress = progressBar(totalCount, progressCount) - kerningPair = left, right - kernValue = flattenedPairDict[(left, right)] - allVariations = 1 - recordKerningPairData( - (left, right), kerningPair, kernValue, allVariations, kerningPairData) - - # best_value_pairs, flatExceptions = makeValueDistribution(kerningPairData, verbatim=False) - best_value_pairs, flatExceptions = makeValueDistribution( - kerningPairData, verbatim=True) - - for (left, right), value in flattenedPairDict.items(): - leftItem = leftG2GDict.get(left, left) - rightItem = rightG2GDict.get(right, right) - - pair = leftItem, rightItem - # pair_with_value = leftItem, rightItem, value - - if best_value_pairs.get(pair) == value: - del flattenedPairDict[(left, right)] - - else: - if not (left, right) in flatExceptions: - flatExceptions[(left, right)] = value - else: - if flatExceptions.get((left, right)) == value: - continue - else: - print 'conflicting Exceptions:', left, right, value - del flattenedPairDict[(left, right)] - - exceptions = consolidateExceptions(flatExceptions, organizedSourceGroups) - - print 'building new kerning object ...' - newKerning = {} - newKerning.update(best_value_pairs) - # print len(best_value_pairs) - newKerning.update(exceptions) - - return newKerning - - -ufoLib.UFOWriter = CustomWriter -# subclassing UFO writer to use the writer contained in this script. - -f_source = Font(sys.argv[-2]) -f_target = Font(sys.argv[-1]) -# if f_target.lib['org.unifiedfontobject.normalizer.modTimes']: -# del(f_target.lib['org.unifiedfontobject.normalizer.modTimes']) - -inputDirName, inputFileName = os.path.split(f_target.path.rstrip(os.sep)) -outputFileName = inputFileName.replace('.ufo', '_mod.ufo') -outputPath = os.path.join(inputDirName, outputFileName) - -flatTargetPairsDict = getKerningPairsFromUFO.UFOkernReader( - f_target, includeZero=True).allKerningPairs -organizedSourceGroups = organizeGroups(f_source) -newKerning = analyzeKerning( - f_source, f_target, flatTargetPairsDict, organizedSourceGroups) - -transferGroups(f_source, f_target) -f_target.kerning.clear() -f_target.kerning.update(newKerning) - -f_target.lib['com.typesupply.MetricsMachine4.groupColors'] = f_source.lib[ - 'com.typesupply.MetricsMachine4.groupColors'] -f_target.save(outputPath) - -print 'done.' -elapsedTime = time.time() - startTime -print '{:.2f} seconds'.format(elapsedTime) From a83c109db502645b43d18396b11faed0f250c33f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Frank=20Grie=C3=9Fhammer?= Date: Mon, 22 Jan 2024 22:59:57 +0100 Subject: [PATCH 4/4] minor documentation updates --- README.md | 2 +- convertKernedOTFtoKernedUFO.py | 6 ------ dumpKernFeatureFromOTF.py | 32 +++++++++++++++----------------- dumpkerning.py | 4 ++++ getKerningPairsFromFEA.py | 3 +-- getKerningPairsFromOTF.py | 16 +++++++++------- getKerningPairsFromUFO.py | 6 ++++++ getKerningPairsFromVFB.py | 6 ++++++ 8 files changed, 42 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index f03f706..7000b2d 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,7 @@ Extract a list of all (flat) kerning pairs from a VFB’s kern object, and report the absolute number of pairs. Run as a FontLab script. __Dependencies:__ [FontLab 5](http://old.fontlab.com/font-editor/fontlab-studio/) -__Environment:__ FontLab 5 +__Environment:__ FontLab Studio 5 --- diff --git a/convertKernedOTFtoKernedUFO.py b/convertKernedOTFtoKernedUFO.py index 89a2e95..989d8f5 100755 --- a/convertKernedOTFtoKernedUFO.py +++ b/convertKernedOTFtoKernedUFO.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 - ''' This script extracts kerning and groups from a compiled OTF and injects them into a new UFO file (which is created via `tx`). @@ -23,11 +22,6 @@ import getKerningPairsFromOTF -kKernFeatureTag = 'kern' -compressSinglePairs = True -# Switch to control if single pairs shall be written plainly, -# or in a more space-saving notation (using enum). - def sortGlyphs(glyphlist): ''' diff --git a/dumpKernFeatureFromOTF.py b/dumpKernFeatureFromOTF.py index a772b25..def83ac 100755 --- a/dumpKernFeatureFromOTF.py +++ b/dumpKernFeatureFromOTF.py @@ -1,4 +1,14 @@ #!/usr/bin/env python3 +''' +This script extracts a viable kern feature file from a compiled OTF. +It requires the script 'getKerningPairsFromOTF.py'; which is distributed +in the same folder. + +usage: +python dumpKernFeatureFromOTF.py font.otf > outputfile + +''' + import argparse import os import string @@ -8,20 +18,8 @@ import getKerningPairsFromOTF reload(getKerningPairsFromOTF) -__doc__ = ''' - - This script extracts a viable kern feature file from a compiled OTF. - It requires the script 'getKerningPairsFromOTF.py'; which is distributed - in the same folder. - - usage: - python dumpKernFeatureFromOTF.py font.otf > outputfile - - ''' - +# compress related single pairs into one line (using enum pos), or no? compressSinglePairs = True -# Switch to control if single pairs shall be written plainly, or in a more -# space-saving notation (using enum pos). def sortGlyphs(glyphlist): @@ -124,9 +122,9 @@ def makeKernFeature(fontPath): exploding_class_class = [] # Compress the single pairs to a more space-saving notation. - # First, dictionaries for each left glyph are created. + # First, create dictionaries for each left glyph. # If the kerning value to any right glyph happens to be equal, - # those right glyphs are merged into a 'class'. + # merge those right glyphs into a 'class'. for (left, right), value in singlePairsList: leftGlyph = left @@ -155,7 +153,7 @@ def makeKernFeature(fontPath): left = sortGlyphs(left) compressedBoth.append((left, right.split(), value)) - # Splitting the compressed single-pair kerning into four different + # Split the compressed single-pair kerning into four different # lists; organized by type: for left, right, value in compressedBoth: @@ -176,7 +174,7 @@ def makeKernFeature(fontPath): else: print(f'ERROR with ({" ".join(left, right, value)})') - # Making sure all the pairs made it through the process: + # Make sure all the pairs made it through the process: if len(compressedBoth) != ( len(class_glyph) + len(glyph_class) + len(glyph_glyph) + len(exploding_class_class) diff --git a/dumpkerning.py b/dumpkerning.py index 1a17683..366e60f 100755 --- a/dumpkerning.py +++ b/dumpkerning.py @@ -1,4 +1,8 @@ #!/usr/bin/env python3 +''' +Wrapper script for all the getKerningPairsFromXXX scripts. + +''' from getKerningPairsFromFEA import FEAKernReader from getKerningPairsFromOTF import OTFKernReader diff --git a/getKerningPairsFromFEA.py b/getKerningPairsFromFEA.py index f86466c..7a63a0d 100755 --- a/getKerningPairsFromFEA.py +++ b/getKerningPairsFromFEA.py @@ -1,7 +1,6 @@ #!/usr/bin/env python3 ''' -Print a list of all kerning pairs to be expected from a kern feature file. -The feature file has to be passed to the script as an argument. +Print all kerning pairs to be expected from a kern feature file. This script has the ability to use a GlyphOrderAndAliasDB file for translating "friendly" glyph names to final glyph names (for comparison with OTF). ''' diff --git a/getKerningPairsFromOTF.py b/getKerningPairsFromOTF.py index 6e11800..d4fe3c7 100755 --- a/getKerningPairsFromOTF.py +++ b/getKerningPairsFromOTF.py @@ -1,13 +1,10 @@ #!/usr/bin/env python3 -from fontTools import ttLib -from pathlib import Path -import argparse -import sys - -__doc__ = '''\ +''' +Extract a list of all (flat) GPOS kerning pairs in a font, and report the +absolute number of pairs. -Print all possible kerning pairs within a font. Supports RTL kerning. +Only GPOS kerning is considered. Usage: ------ @@ -15,6 +12,11 @@ ''' +from fontTools import ttLib +from pathlib import Path +import argparse +import sys + class LeftClass: def __init__(self): diff --git a/getKerningPairsFromUFO.py b/getKerningPairsFromUFO.py index 189923b..303742e 100755 --- a/getKerningPairsFromUFO.py +++ b/getKerningPairsFromUFO.py @@ -1,4 +1,10 @@ #!/usr/bin/env python3 +''' +Extract a list of all (flat) kerning pairs in a UFO file’s kern object, and +report the absolute number of pairs. + +''' + import argparse import itertools from pathlib import Path diff --git a/getKerningPairsFromVFB.py b/getKerningPairsFromVFB.py index f234774..3d21a7c 100644 --- a/getKerningPairsFromVFB.py +++ b/getKerningPairsFromVFB.py @@ -1,3 +1,8 @@ +''' +Extract a list of all (flat) kerning pairs from a VFB’s kern object, and +report the absolute number of pairs. Run as a FontLab script. + +''' import itertools from FL import fl f = fl.font @@ -197,5 +202,6 @@ def run(): print '\nList of kerning pairs written to\n{}'.format(dumpFileName) + if __name__ == '__main__': run()