diff --git a/KerningChecker.roboFontExt/info.plist b/KerningChecker.roboFontExt/info.plist
index e51493f..81f6d9a 100644
--- a/KerningChecker.roboFontExt/info.plist
+++ b/KerningChecker.roboFontExt/info.plist
@@ -21,9 +21,9 @@
requiresVersionMinor
6
timeStamp
- 1456307610.6527879
+ 1598965344.684863
version
- 1.2
+ 1.3
com.robofontmechanic.Mechanic
repository
diff --git a/KerningChecker.roboFontExt/lib/getKerningPairsFromOTF.py b/KerningChecker.roboFontExt/lib/getKerningPairsFromOTF.py
old mode 100644
new mode 100755
index 260aeee..4fd2114
--- a/KerningChecker.roboFontExt/lib/getKerningPairsFromOTF.py
+++ b/KerningChecker.roboFontExt/lib/getKerningPairsFromOTF.py
@@ -1,13 +1,16 @@
-#!/usr/bin/python
-import os, sys
+#!/usr/bin/env python3
from fontTools import ttLib
+import os
+import sys
-# taken as is from https://github.com/adobe-type-tools/kern-dump/blob/master/getKerningPairsFromOTF.py
+__doc__ = '''\
-'''
+Prints all possible kerning pairs within font.
+Supports RTL kerning.
-Gets all possible kerning pairs within font.
-Supports RTL.
+Usage:
+------
+python getKerningPairsFromOTF.py
'''
@@ -17,35 +20,33 @@
class myLeftClass:
-
def __init__(self):
self.glyphs = []
self.class1Record = 0
class myRightClass:
-
def __init__(self):
self.glyphs = []
self.class2Record = 0
-def collectUniqueKernLookupListIndexes(featureRecord):
- uniqueKernLookupIndexList = []
+def collect_unique_kern_lookup_indexes(featureRecord):
+ unique_kern_lookups = []
for featRecItem in featureRecord:
- # print featRecItem.FeatureTag
+ # print(featRecItem.FeatureTag)
# GPOS feature tags (e.g. kern, mark, mkmk, size) of each ScriptRecord
if featRecItem.FeatureTag == kKernFeatureTag:
feature = featRecItem.Feature
for featLookupItem in feature.LookupListIndex:
- if featLookupItem not in uniqueKernLookupIndexList:
- uniqueKernLookupIndexList.append(featLookupItem)
+ if featLookupItem not in unique_kern_lookups:
+ unique_kern_lookups.append(featLookupItem)
- return uniqueKernLookupIndexList
+ return unique_kern_lookups
-class ReadKerning(object):
+class OTFKernReader(object):
def __init__(self, fontPath):
self.font = ttLib.TTFont(fontPath)
@@ -57,7 +58,7 @@ def __init__(self, fontPath):
self.allRightClasses = {}
if kGPOStableName not in self.font:
- print("The font has no %s table" % kGPOStableName)
+ print("The font has no %s table" % kGPOStableName, file=sys.stderr)
self.goodbye()
else:
@@ -68,34 +69,33 @@ def __init__(self, fontPath):
self.getClassPairs()
def goodbye(self):
- print('The fun ends here.')
+ print('The fun ends here.', file=sys.stderr)
return
def analyzeFont(self):
self.gposTable = self.font[kGPOStableName].table
-
- 'ScriptList:'
self.scriptList = self.gposTable.ScriptList
- 'FeatureList:'
self.featureList = self.gposTable.FeatureList
-
self.featureCount = self.featureList.FeatureCount
self.featureRecord = self.featureList.FeatureRecord
- self.uniqueKernLookupIndexList = collectUniqueKernLookupListIndexes(self.featureRecord)
+ self.unique_kern_lookups = collect_unique_kern_lookup_indexes(
+ self.featureRecord)
def findKerningLookups(self):
- if not len(self.uniqueKernLookupIndexList):
- print("The font has no %s feature." % kKernFeatureTag)
+ if not len(self.unique_kern_lookups):
+ print(
+ "The font has no %s feature." % kKernFeatureTag,
+ file=sys.stderr)
self.goodbye()
- 'LookupList:'
- self.lookupList = self.gposTable.LookupList
+ self.lookup_list = self.gposTable.LookupList
self.lookups = []
- for kernLookupIndex in sorted(self.uniqueKernLookupIndexList):
- lookup = self.lookupList.Lookup[kernLookupIndex]
+ for kern_lookup_index in sorted(self.unique_kern_lookups):
+ lookup = self.lookup_list.Lookup[kern_lookup_index]
- # Confirm this is a GPOS LookupType 2; or using an extension table (GPOS LookupType 9):
+ # Confirm this is a GPOS LookupType 2; or
+ # using an extension table (GPOS LookupType 9):
'''
Lookup types:
@@ -108,49 +108,55 @@ def findKerningLookups(self):
7 Context positioning Position one or more glyphs in context
8 Chained Context positioning Position one or more glyphs in chained context
9 Extension positioning Extension mechanism for other positionings
- 10+ Reserved For future use
+ 10+ Reserved for future use
'''
if lookup.LookupType not in [2, 9]:
- message = '''
+ print('''
Info: GPOS LookupType %s found.
This type is neither a pair adjustment positioning lookup (GPOS LookupType 2),
- nor using an extension table (GPOS LookupType 9), which are the only supported ones.
- ''' % lookup.LookupType
- print(message)
+ nor using an extension table (GPOS LookupType 9), which are the only ones supported.
+ ''' % lookup.LookupType, file=sys.stderr)
continue
self.lookups.append(lookup)
-
def getPairPos(self):
for lookup in self.lookups:
for subtableItem in lookup.SubTable:
- if subtableItem.LookupType == 2: # normal case, not using extension table
- pairPos = subtableItem
-
- elif subtableItem.LookupType == 9: # extension table
- if subtableItem.ExtensionLookupType == 8: # contextual
- print('Contextual Kerning not (yet?) supported.')
+ if subtableItem.LookupType == 9: # extension table
+ if subtableItem.ExtensionLookupType == 8: # contextual
+ print(
+ 'Contextual Kerning not (yet?) supported.',
+ file=sys.stderr)
continue
elif subtableItem.ExtensionLookupType == 2:
- pairPos = subtableItem.ExtSubTable
+ subtableItem = subtableItem.ExtSubTable
- # if pairPos.Coverage.Format not in [1, 2]:
- if pairPos.Format not in [1, 2]:
- print("WARNING: Coverage format %d is not yet supported." % pairPos.Coverage.Format)
+ if subtableItem.Format not in [1, 2]:
+ print(
+ 'WARNING: Coverage format %d '
+ 'is not yet supported.' % subtableItem.Coverage.Format,
+ file=sys.stderr)
- if pairPos.ValueFormat1 not in [0, 4, 5]:
- print("WARNING: ValueFormat1 format %d is not yet supported." % pairPos.ValueFormat1)
+ if subtableItem.ValueFormat1 not in [0, 4, 5]:
+ print(
+ 'WARNING: ValueFormat1 format %d '
+ 'is not yet supported.' % subtableItem.ValueFormat1,
+ file=sys.stderr)
- if pairPos.ValueFormat2 not in [0]:
- print("WARNING: ValueFormat2 format %d is not yet supported." % pairPos.ValueFormat2)
+ if subtableItem.ValueFormat2 not in [0]:
+ print(
+ 'WARNING: ValueFormat2 format %d '
+ 'is not yet supported.' % subtableItem.ValueFormat2,
+ file=sys.stderr)
- self.pairPosList.append(pairPos)
+ self.pairPosList.append(subtableItem)
- # Each glyph in this list will have a corresponding PairSet which will
- # contain all the second glyphs and the kerning value in the form of PairValueRecord(s)
- # self.firstGlyphsList.extend(pairPos.Coverage.glyphs)
+ # Each glyph in this list will have a corresponding PairSet
+ # which will contain all the second glyphs and the kerning
+ # value in the form of PairValueRecord(s)
+ # self.firstGlyphsList.extend(subtableItem.Coverage.glyphs)
def getSinglePairs(self):
for pairPos in self.pairPosList:
@@ -159,24 +165,29 @@ def getSinglePairs(self):
firstGlyphsList = pairPos.Coverage.glyphs
- # This iteration is done by index so that there is a way to reference the firstGlyphsList list:
- for pairSetIndex, pairSetInstance in enumerate(pairPos.PairSet):
- for pairValueRecordItem in pairPos.PairSet[pairSetIndex].PairValueRecord:
+ # This iteration is done by index so we have a way
+ # to reference the firstGlyphsList:
+ for ps_index, _ in enumerate(pairPos.PairSet):
+ for pairValueRecordItem in pairPos.PairSet[ps_index].PairValueRecord:
secondGlyph = pairValueRecordItem.SecondGlyph
valueFormat = pairPos.ValueFormat1
if valueFormat == 5: # RTL kerning
- kernValue = "<%d 0 %d 0>" % (pairValueRecordItem.Value1.XPlacement, pairValueRecordItem.Value1.XAdvance)
+ kernValue = "<%d 0 %d 0>" % (
+ pairValueRecordItem.Value1.XPlacement,
+ pairValueRecordItem.Value1.XAdvance)
elif valueFormat == 0: # RTL pair with value <0 0 0 0>
kernValue = "<0 0 0 0>"
elif valueFormat == 4: # LTR kerning
kernValue = pairValueRecordItem.Value1.XAdvance
else:
- print("\tValueFormat1 = %d" % valueFormat)
+ print(
+ "\tValueFormat1 = %d" % valueFormat,
+ file=sys.stdout)
continue # skip the rest
- self.kerningPairs[(firstGlyphsList[pairSetIndex], secondGlyph)] = kernValue
- self.singlePairs[(firstGlyphsList[pairSetIndex], secondGlyph)] = kernValue
+ self.kerningPairs[(firstGlyphsList[ps_index], secondGlyph)] = kernValue
+ self.singlePairs[(firstGlyphsList[ps_index], secondGlyph)] = kernValue
def getClassPairs(self):
for loop, pairPos in enumerate(self.pairPosList):
@@ -185,49 +196,55 @@ def getClassPairs(self):
leftClasses = {}
rightClasses = {}
- # # Find left class with the Class1Record index="0".
- # # This first class is mixed into the "Coverage" table (e.g. all left glyphs)
- # # and has no class="X" property, that is why we have to find the glyphs in that way.
+ # Find left class with the Class1Record index="0".
+ # This first class is mixed into the "Coverage" table
+ # (e.g. all left glyphs) and has no class="X" property
+ # that is why we have to find the glyphs in that way.
lg0 = myLeftClass()
- allLeftGlyphs = pairPos.Coverage.glyphs # list of all glyphs kerned to the left of a pair
- allLeftClassGlyphs = pairPos.ClassDef1.classDefs.keys() # list of all glyphs contained within left-sided kerning classes:
+ # list of all glyphs kerned to the left of a pair:
+ allLeftGlyphs = pairPos.Coverage.glyphs
+ # list of all glyphs contained in left-sided kerning classes:
+ # allLeftClassGlyphs = pairPos.ClassDef1.classDefs.keys()
singleGlyphs = []
classGlyphs = []
- for gName, classID in pairPos.ClassDef1.classDefs.iteritems():
+ for gName, classID in pairPos.ClassDef1.classDefs.items():
if classID == 0:
singleGlyphs.append(gName)
else:
classGlyphs.append(gName)
-
- # lg0.glyphs = list(set(allLeftGlyphs) - set(allLeftClassGlyphs)) # coverage glyphs minus glyphs in a class (including class 0)
- lg0.glyphs = list(set(allLeftGlyphs) - set(classGlyphs)) # coverage glyphs minus glyphs in real class (without class 0)
+ # coverage glyphs minus glyphs in real class (without class 0)
+ lg0.glyphs = list(set(allLeftGlyphs) - set(classGlyphs))
lg0.glyphs.sort()
leftClasses[lg0.class1Record] = lg0
- self.allLeftClasses["class_%s_%s" % (loop, lg0.class1Record)] = lg0.glyphs
+ className = "class_%s_%s" % (loop, lg0.class1Record)
+ self.allLeftClasses[className] = lg0.glyphs
# Find all the remaining left classes:
for leftGlyph in pairPos.ClassDef1.classDefs:
class1Record = pairPos.ClassDef1.classDefs[leftGlyph]
- if class1Record != 0: # this was the crucial line.
+ if class1Record != 0: # this was the crucial line.
lg = myLeftClass()
lg.class1Record = class1Record
- leftClasses.setdefault(class1Record, lg).glyphs.append(leftGlyph)
- self.allLeftClasses.setdefault("class_%s_%s" % (loop, lg.class1Record), lg.glyphs)
+ leftClasses.setdefault(
+ class1Record, lg).glyphs.append(leftGlyph)
+ self.allLeftClasses.setdefault(
+ "class_%s_%s" % (loop, lg.class1Record), lg.glyphs)
# Same for the right classes:
for rightGlyph in pairPos.ClassDef2.classDefs:
class2Record = pairPos.ClassDef2.classDefs[rightGlyph]
rg = myRightClass()
rg.class2Record = class2Record
- rightClasses.setdefault(class2Record, rg).glyphs.append(rightGlyph)
- self.allRightClasses.setdefault("class_%s_%s" % (loop, rg.class2Record), rg.glyphs)
-
+ rightClasses.setdefault(
+ class2Record, rg).glyphs.append(rightGlyph)
+ self.allRightClasses.setdefault(
+ "class_%s_%s" % (loop, rg.class2Record), rg.glyphs)
for record_l in leftClasses:
for record_r in rightClasses:
@@ -236,15 +253,20 @@ def getClassPairs(self):
if valueFormat in [4, 5]:
kernValue = pairPos.Class1Record[record_l].Class2Record[record_r].Value1.XAdvance
- elif valueFormat == 0: # valueFormat zero is caused by a value of <0 0 0 0> on a class-class pair; skip these
+ elif valueFormat == 0:
+ # valueFormat zero is caused by a value of <0 0 0 0> on a class-class pair; skip these
continue
else:
- print("\tValueFormat1 = %d" % valueFormat)
- continue # skip the rest
+ print(
+ "\tValueFormat1 = %d" % valueFormat,
+ file=sys.stdout)
+ continue # skip the rest
if kernValue != 0:
- leftClassName = 'class_%s_%s' % (loop, leftClasses[record_l].class1Record)
- rightClassName = 'class_%s_%s' % (loop, rightClasses[record_r].class2Record)
+ leftClassName = 'class_%s_%s' % (
+ loop, leftClasses[record_l].class1Record)
+ rightClassName = 'class_%s_%s' % (
+ loop, rightClasses[record_r].class2Record)
self.classPairs[(leftClassName, rightClassName)] = kernValue
@@ -254,38 +276,41 @@ def getClassPairs(self):
# if the kerning pair has already been assigned in pair-to-pair kerning
continue
else:
- if valueFormat == 5: # RTL kerning
+ if valueFormat == 5: # RTL kerning
kernValue = "<%d 0 %d 0>" % (pairPos.Class1Record[record_l].Class2Record[record_r].Value1.XPlacement, pairPos.Class1Record[record_l].Class2Record[record_r].Value1.XAdvance)
self.kerningPairs[(l, r)] = kernValue
else:
- print('ERROR')
+ print('ERROR', file=sys.stderr)
if __name__ == "__main__":
if len(sys.argv) == 2:
assumedFontPath = sys.argv[1]
- if os.path.exists(assumedFontPath) and os.path.splitext(assumedFontPath)[1].lower() in ['.otf', '.ttf']:
+ if(
+ os.path.exists(assumedFontPath) and
+ os.path.splitext(assumedFontPath)[1].lower() in ['.otf', '.ttf']
+ ):
fontPath = sys.argv[1]
- f = ReadKerning(fontPath)
+ f = OTFKernReader(fontPath)
finalList = []
for pair, value in f.kerningPairs.items():
- finalList.append('/%s /%s %s' % ( pair[0], pair[1], value ))
+ finalList.append('/%s /%s %s' % (pair[0], pair[1], value))
finalList.sort()
output = '\n'.join(finalList)
- print(output)
+ print(output, file=sys.stdout)
- print('\nTotal number of kerning pairs:')
- print(len(f.kerningPairs))
+ print('\nTotal number of kerning pairs:', file=sys.stdout)
+ print(len(f.kerningPairs), file=sys.stdout)
# for i in sorted(f.allLeftClasses):
- # print i, f.allLeftClasses[i]
+ # print(i, f.allLeftClasses[i], file=sys.stdout)
else:
- print('That is not a valid font.')
+ print('That is not a valid font.', file=sys.stderr)
else:
- print('Please provide a font.')
\ No newline at end of file
+ print('Please provide a font.', file=sys.stderr)
diff --git a/KerningChecker.roboFontExt/lib/getKerningPairsFromUFO.py b/KerningChecker.roboFontExt/lib/getKerningPairsFromUFO.py
old mode 100644
new mode 100755
index ec53e18..beb7b29
--- a/KerningChecker.roboFontExt/lib/getKerningPairsFromUFO.py
+++ b/KerningChecker.roboFontExt/lib/getKerningPairsFromUFO.py
@@ -1,32 +1,41 @@
-#!/usr/bin/python
-# coding: utf-8
-
-# taken as is from https://github.com/adobe-type-tools/kern-dump/blob/master/getKerningPairsFromUFO.py
-
-import sys
-import os
+#!/usr/bin/env python3
import itertools
+import os
+import sys
class UFOkernReader(object):
- def __init__(self, font):
+ def __init__(self, font, includeZero=False):
self.f = font
+
+ try:
+ format_version = self.f.ufoFormatVersion
+ except AttributeError:
+ format_version = self.f.naked().ufoFormatVersion
+ if format_version >= 3:
+ self.group_indicator = 'public.'
+ else:
+ self.group_indicator = '@'
+
self.group_group_pairs = {}
self.group_glyph_pairs = {}
self.glyph_group_pairs = {}
self.glyph_glyph_pairs = {}
- self.allKerningPairs = self.makePairDicts()
- self.allKerningPairs_zero = self.makePairDicts(includeZero=True)
- self.output = []
-
- for (left, right), value in self.allKerningPairs.items():
- self.output.append('/%s /%s %s' % (left, right, value))
- self.output.sort()
+ self.allKerningPairs = self.makePairDicts(includeZero)
+ self.output = self.makeOutput(self.allKerningPairs)
self.totalKerning = sum(self.allKerningPairs.values())
- self.absoluteKerning = sum([abs(value) for value in self.allKerningPairs.values()])
+ self.absoluteKerning = sum(
+ [abs(value) for value in self.allKerningPairs.values()])
+
+ def makeOutput(self, kerningDict):
+ output = []
+ for (left, right), value in kerningDict.items():
+ output.append('/%s /%s %s' % (left, right, value))
+ output.sort()
+ return output
def allCombinations(self, left, right):
leftGlyphs = self.f.groups.get(left, [left])
@@ -34,22 +43,31 @@ def allCombinations(self, left, right):
combinations = list(itertools.product(leftGlyphs, rightGlyphs))
return combinations
- def makePairDicts(self, includeZero=False):
+ def makePairDicts(self, includeZero):
kerningPairs = {}
for (left, right), value in self.f.kerning.items():
- if 'public.kern1.' in left and 'public.kern2.' in right:
+ if (
+ self.group_indicator in left and
+ self.group_indicator in right
+ ):
# group-to-group-pair
for combo in self.allCombinations(left, right):
self.group_group_pairs[combo] = value
- elif 'public.kern1.' in left and 'public.kern2.' not in right:
+ elif (
+ self.group_indicator in left and
+ self.group_indicator not in right
+ ):
# group-to-glyph-pair
for combo in self.allCombinations(left, right):
self.group_glyph_pairs[combo] = value
- elif 'public.kern1.' not in left and 'public.kern2.' in right:
+ elif (
+ self.group_indicator not in left and
+ self.group_indicator in right
+ ):
# glyph-to-group-pair
for combo in self.allCombinations(left, right):
self.glyph_group_pairs[combo] = value
@@ -58,7 +76,6 @@ def makePairDicts(self, includeZero=False):
# glyph-to-glyph-pair a.k.a. single pair
self.glyph_glyph_pairs[(left, right)] = value
-
# The updates occur from the most general pairs to the most specific.
# This means that any given class kerning values are overwritten with
# the intended exceptions.
@@ -67,9 +84,10 @@ def makePairDicts(self, includeZero=False):
kerningPairs.update(self.glyph_group_pairs)
kerningPairs.update(self.glyph_glyph_pairs)
- if includeZero == False:
+ if includeZero is False:
# delete any kerning values == 0.
- # This cannot be done in the loop, since exceptions might undo a previously set kerning pair to be 0.
+ # This cannot be done in the previous loop, since exceptions
+ # might set a previously established kerning pair to be 0.
cleanKerningPairs = dict(kerningPairs)
for pair in kerningPairs:
if kerningPairs[pair] == 0:
@@ -81,7 +99,7 @@ def makePairDicts(self, includeZero=False):
def run(font):
- ukr = UFOkernReader(font)
+ ukr = UFOkernReader(font, includeZero=True)
scrap = os.popen('pbcopy', 'w')
output = '\n'.join(ukr.output)
scrap.write(output)
@@ -89,7 +107,7 @@ def run(font):
if inRF:
pass
- # print 'Total length of kerning:', ukr.totalKerning
+ # print('Total length of kerning:', ukr.totalKerning)
if inCL:
print('\n'.join(ukr.output), '\n')
@@ -109,18 +127,17 @@ def run(font):
if f:
run(f)
else:
- print('You need to open a font first. \U0001F625')
+ print(u'You need to open a font first. \U0001F625')
except ImportError:
try:
import defcon
inCL = True
- path = sys.argv[-1].rstrip(os.sep)
+ path = os.path.normpath(sys.argv[-1])
if os.path.splitext(path)[-1] in ['.ufo', '.UFO']:
f = defcon.Font(path)
run(f)
else:
print('No UFO file given.')
except ImportError:
- print('You don’t have Defcon installed. \U0001F625')
-
+ print(u'You don’t have Defcon installed. \U0001F625')
diff --git a/KerningChecker.roboFontExt/lib/kerningChecker.py b/KerningChecker.roboFontExt/lib/kerningChecker.py
index 9c19707..0b161ae 100644
--- a/KerningChecker.roboFontExt/lib/kerningChecker.py
+++ b/KerningChecker.roboFontExt/lib/kerningChecker.py
@@ -5,7 +5,7 @@
from mojo.events import addObserver
# taken from https://github.com/adobe-type-tools/kern-dump
-from getKerningPairsFromOTF import ReadKerning
+from getKerningPairsFromOTF import OTFKernReader
from getKerningPairsFromUFO import UFOkernReader
@@ -33,7 +33,7 @@ def fontDidGenerate(self, notification):
if not os.path.exists(path):
return
- binaryKerning = ReadKerning(path)
+ binaryKerning = OTFKernReader(path)
ufoKerning = UFOkernReader(font)
ufoPairs = ufoKerning.allKerningPairs