tooling
This commit is contained in:
parent
6688a81405
commit
ca1cb8c942
5 changed files with 636 additions and 0 deletions
45
misc/glyphcheck.py
Executable file
45
misc/glyphcheck.py
Executable file
|
|
@ -0,0 +1,45 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# encoding: utf8
|
||||||
|
import sys, argparse
|
||||||
|
from fontTools import ttLib
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
argparser = argparse.ArgumentParser(description='Check glyph names')
|
||||||
|
|
||||||
|
argparser.add_argument('fontfiles', metavar='<path>', type=str, nargs='+',
|
||||||
|
help='TrueType or OpenType font files')
|
||||||
|
|
||||||
|
args = argparser.parse_args()
|
||||||
|
|
||||||
|
nmissing = 0
|
||||||
|
|
||||||
|
matchnames = set()
|
||||||
|
for line in sys.stdin:
|
||||||
|
line = line.strip()
|
||||||
|
if len(line) > 0 and line[0] != '#':
|
||||||
|
for line2 in line.split():
|
||||||
|
line2 = line2.strip()
|
||||||
|
if len(line2) > 0:
|
||||||
|
matchnames.add(line2)
|
||||||
|
|
||||||
|
for fontfile in args.fontfiles:
|
||||||
|
font = ttLib.TTFont(fontfile)
|
||||||
|
glyphnames = set(font.getGlyphOrder())
|
||||||
|
|
||||||
|
# for name in glyphnames:
|
||||||
|
# if not name in matchnames:
|
||||||
|
# print('%s missing in input' % name)
|
||||||
|
|
||||||
|
for name in matchnames:
|
||||||
|
if not name in glyphnames:
|
||||||
|
print('%s missing in font' % name)
|
||||||
|
nmissing = nmissing + 1
|
||||||
|
|
||||||
|
|
||||||
|
if nmissing == 0:
|
||||||
|
print('all glyphs found')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
28
misc/kerndiff/README.md
Normal file
28
misc/kerndiff/README.md
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
# kerndiff
|
||||||
|
|
||||||
|
Shows unified diff for kerning pairs in two font files.
|
||||||
|
|
||||||
|
Accepts OTF, TTF and UFO fonts.
|
||||||
|
|
||||||
|
Synopsis:
|
||||||
|
|
||||||
|
```
|
||||||
|
kerndiff.sh <font1> <font2>
|
||||||
|
```
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
kerndiff.sh Inter-UI-Regular-v2.4.otf src/Inter-UI-Regular.ufo
|
||||||
|
--- Inter-UI-Regular-v2.4.otf 2018-08-30 19:16:47.000000000 -0700
|
||||||
|
+++ Inter-UI-Regular.ufo 2018-08-30 19:16:47.000000000 -0700
|
||||||
|
@@ -35126,7 +35081,6 @@
|
||||||
|
/s /Ydieresis -149
|
||||||
|
/s /Ygrave -149
|
||||||
|
/s /Yhook -149
|
||||||
|
-/s /a 0
|
||||||
|
+/s /a 8
|
||||||
|
/s /afii10026 -47
|
||||||
|
/s /b -47
|
||||||
|
/s /dagger -29
|
||||||
|
```
|
||||||
323
misc/kerndiff/getKerningPairsFromOTF.py
Normal file
323
misc/kerndiff/getKerningPairsFromOTF.py
Normal file
|
|
@ -0,0 +1,323 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
#
|
||||||
|
# Copyright (c) 2013-2016 Adobe Systems Incorporated. All rights reserved.
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
|
# in the Software without restriction, including without limitation the rights
|
||||||
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in all
|
||||||
|
# copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
# SOFTWARE. import sys import os import itertools
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from fontTools import ttLib
|
||||||
|
|
||||||
|
__doc__ = '''\
|
||||||
|
|
||||||
|
Prints all possible kerning pairs within font.
|
||||||
|
Supports RTL kerning.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
------
|
||||||
|
python getKerningPairsFromOTF.py <path to font file>
|
||||||
|
|
||||||
|
'''
|
||||||
|
|
||||||
|
kKernFeatureTag = 'kern'
|
||||||
|
kGPOStableName = 'GPOS'
|
||||||
|
finalList = []
|
||||||
|
|
||||||
|
|
||||||
|
class myLeftClass:
|
||||||
|
def __init__(self):
|
||||||
|
self.glyphs = []
|
||||||
|
self.class1Record = 0
|
||||||
|
|
||||||
|
|
||||||
|
class myRightClass:
|
||||||
|
def __init__(self):
|
||||||
|
self.glyphs = []
|
||||||
|
self.class2Record = 0
|
||||||
|
|
||||||
|
|
||||||
|
def collectUniqueKernLookupListIndexes(featureRecord):
|
||||||
|
uniqueKernLookupIndexList = []
|
||||||
|
for featRecItem in featureRecord:
|
||||||
|
# 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)
|
||||||
|
|
||||||
|
return uniqueKernLookupIndexList
|
||||||
|
|
||||||
|
|
||||||
|
class OTFKernReader(object):
|
||||||
|
|
||||||
|
def __init__(self, fontPath):
|
||||||
|
self.font = ttLib.TTFont(fontPath)
|
||||||
|
self.kerningPairs = {}
|
||||||
|
self.singlePairs = {}
|
||||||
|
self.classPairs = {}
|
||||||
|
self.pairPosList = []
|
||||||
|
self.allLeftClasses = {}
|
||||||
|
self.allRightClasses = {}
|
||||||
|
|
||||||
|
if kGPOStableName not in self.font:
|
||||||
|
print("The font has no %s table" % kGPOStableName, file=sys.stderr)
|
||||||
|
self.goodbye()
|
||||||
|
|
||||||
|
else:
|
||||||
|
self.analyzeFont()
|
||||||
|
self.findKerningLookups()
|
||||||
|
self.getPairPos()
|
||||||
|
self.getSinglePairs()
|
||||||
|
self.getClassPairs()
|
||||||
|
|
||||||
|
def goodbye(self):
|
||||||
|
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)
|
||||||
|
|
||||||
|
def findKerningLookups(self):
|
||||||
|
if not len(self.uniqueKernLookupIndexList):
|
||||||
|
print("The font has no %s feature." % kKernFeatureTag, file=sys.stderr)
|
||||||
|
self.goodbye()
|
||||||
|
|
||||||
|
'LookupList:'
|
||||||
|
self.lookupList = self.gposTable.LookupList
|
||||||
|
self.lookups = []
|
||||||
|
for kernLookupIndex in sorted(self.uniqueKernLookupIndexList):
|
||||||
|
lookup = self.lookupList.Lookup[kernLookupIndex]
|
||||||
|
|
||||||
|
# Confirm this is a GPOS LookupType 2; or
|
||||||
|
# using an extension table (GPOS LookupType 9):
|
||||||
|
|
||||||
|
'''
|
||||||
|
Lookup types:
|
||||||
|
1 Single adjustment Adjust position of a single glyph
|
||||||
|
2 Pair adjustment Adjust position of a pair of glyphs
|
||||||
|
3 Cursive attachment Attach cursive glyphs
|
||||||
|
4 MarkToBase attachment Attach a combining mark to a base glyph
|
||||||
|
5 MarkToLigature attachment Attach a combining mark to a ligature
|
||||||
|
6 MarkToMark attachment Attach a combining mark to another mark
|
||||||
|
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
|
||||||
|
'''
|
||||||
|
|
||||||
|
if lookup.LookupType not in [2, 9]:
|
||||||
|
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 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 == 9: # extension table
|
||||||
|
if subtableItem.ExtensionLookupType == 8: # contextual
|
||||||
|
print('Contextual Kerning not (yet?) supported.', file=sys.stderr)
|
||||||
|
continue
|
||||||
|
elif subtableItem.ExtensionLookupType == 2:
|
||||||
|
subtableItem = subtableItem.ExtSubTable
|
||||||
|
|
||||||
|
|
||||||
|
# if subtableItem.Coverage.Format not in [1, 2]: # previous fontTools
|
||||||
|
if subtableItem.Format not in [1, 2]:
|
||||||
|
print("WARNING: Coverage format %d is not yet supported." % subtableItem.Coverage.Format, file=sys.stderr)
|
||||||
|
|
||||||
|
if subtableItem.ValueFormat1 not in [0, 4, 5]:
|
||||||
|
print("WARNING: ValueFormat1 format %d is not yet supported." % subtableItem.ValueFormat1, file=sys.stderr)
|
||||||
|
|
||||||
|
if subtableItem.ValueFormat2 not in [0]:
|
||||||
|
print("WARNING: ValueFormat2 format %d is not yet supported." % subtableItem.ValueFormat2, file=sys.stderr)
|
||||||
|
|
||||||
|
|
||||||
|
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(subtableItem.Coverage.glyphs)
|
||||||
|
|
||||||
|
|
||||||
|
def getSinglePairs(self):
|
||||||
|
for pairPos in self.pairPosList:
|
||||||
|
if pairPos.Format == 1:
|
||||||
|
# single pair adjustment
|
||||||
|
|
||||||
|
firstGlyphsList = pairPos.Coverage.glyphs
|
||||||
|
|
||||||
|
# This iteration is done by index so we have a way
|
||||||
|
# to reference the firstGlyphsList:
|
||||||
|
for pairSetIndex, pairSetInstance in enumerate(pairPos.PairSet):
|
||||||
|
for pairValueRecordItem in pairPos.PairSet[pairSetIndex].PairValueRecord:
|
||||||
|
secondGlyph = pairValueRecordItem.SecondGlyph
|
||||||
|
valueFormat = pairPos.ValueFormat1
|
||||||
|
|
||||||
|
if valueFormat == 5: # RTL kerning
|
||||||
|
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, file=sys.stdout)
|
||||||
|
continue # skip the rest
|
||||||
|
|
||||||
|
self.kerningPairs[(firstGlyphsList[pairSetIndex], secondGlyph)] = kernValue
|
||||||
|
self.singlePairs[(firstGlyphsList[pairSetIndex], secondGlyph)] = kernValue
|
||||||
|
|
||||||
|
def getClassPairs(self):
|
||||||
|
for loop, pairPos in enumerate(self.pairPosList):
|
||||||
|
if pairPos.Format == 2:
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
lg0 = myLeftClass()
|
||||||
|
|
||||||
|
# list of all glyphs kerned to the left of a pair:
|
||||||
|
allLeftGlyphs = pairPos.Coverage.glyphs
|
||||||
|
# list of all glyphs contained within left-sided kerning classes:
|
||||||
|
# allLeftClassGlyphs = pairPos.ClassDef1.classDefs.keys()
|
||||||
|
|
||||||
|
singleGlyphs = []
|
||||||
|
classGlyphs = []
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
lg0.glyphs.sort()
|
||||||
|
leftClasses[lg0.class1Record] = lg0
|
||||||
|
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.
|
||||||
|
lg = myLeftClass()
|
||||||
|
lg.class1Record = class1Record
|
||||||
|
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)
|
||||||
|
|
||||||
|
for record_l in leftClasses:
|
||||||
|
for record_r in rightClasses:
|
||||||
|
if pairPos.Class1Record[record_l].Class2Record[record_r]:
|
||||||
|
valueFormat = pairPos.ValueFormat1
|
||||||
|
|
||||||
|
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
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
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)
|
||||||
|
|
||||||
|
self.classPairs[(leftClassName, rightClassName)] = kernValue
|
||||||
|
|
||||||
|
for l in leftClasses[record_l].glyphs:
|
||||||
|
for r in rightClasses[record_r].glyphs:
|
||||||
|
if (l, r) in self.kerningPairs:
|
||||||
|
# if the kerning pair has already been assigned in pair-to-pair kerning
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
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', 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']:
|
||||||
|
fontPath = sys.argv[1]
|
||||||
|
f = OTFKernReader(fontPath)
|
||||||
|
|
||||||
|
finalList = []
|
||||||
|
for pair, value in f.kerningPairs.items():
|
||||||
|
finalList.append('/%s /%s %s' % ( pair[0], pair[1], value ))
|
||||||
|
|
||||||
|
finalList.sort()
|
||||||
|
|
||||||
|
output = '\n'.join(sorted(finalList))
|
||||||
|
print(output, file=sys.stdout)
|
||||||
|
|
||||||
|
# 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], file=sys.stdout)
|
||||||
|
|
||||||
|
else:
|
||||||
|
print('That is not a valid font.', file=sys.stderr)
|
||||||
|
else:
|
||||||
|
print('Please provide a font.', file=sys.stderr)
|
||||||
144
misc/kerndiff/getKerningPairsFromUFO.py
Normal file
144
misc/kerndiff/getKerningPairsFromUFO.py
Normal file
|
|
@ -0,0 +1,144 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
# coding: utf-8
|
||||||
|
#
|
||||||
|
# Copyright (c) 2013-2016 Adobe Systems Incorporated. All rights reserved.
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
|
# in the Software without restriction, including without limitation the rights
|
||||||
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in all
|
||||||
|
# copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
# SOFTWARE.
|
||||||
|
|
||||||
|
import sys, os, itertools
|
||||||
|
|
||||||
|
|
||||||
|
class UFOkernReader(object):
|
||||||
|
|
||||||
|
def __init__(self, font, includeZero=False):
|
||||||
|
self.f = font
|
||||||
|
self.group_group_pairs = {}
|
||||||
|
self.group_glyph_pairs = {}
|
||||||
|
self.glyph_group_pairs = {}
|
||||||
|
self.glyph_glyph_pairs = {}
|
||||||
|
|
||||||
|
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()])
|
||||||
|
|
||||||
|
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])
|
||||||
|
rightGlyphs = self.f.groups.get(right, [right])
|
||||||
|
combinations = list(itertools.product(leftGlyphs, rightGlyphs))
|
||||||
|
return combinations
|
||||||
|
|
||||||
|
def makePairDicts(self, includeZero):
|
||||||
|
kerningPairs = {}
|
||||||
|
|
||||||
|
for (left, right), value in self.f.kerning.items():
|
||||||
|
|
||||||
|
if '@' in left and '@' in right:
|
||||||
|
# group-to-group-pair
|
||||||
|
for combo in self.allCombinations(left, right):
|
||||||
|
self.group_group_pairs[combo] = value
|
||||||
|
|
||||||
|
elif '@' in left and '@' not in right:
|
||||||
|
# group-to-glyph-pair
|
||||||
|
for combo in self.allCombinations(left, right):
|
||||||
|
self.group_glyph_pairs[combo] = value
|
||||||
|
|
||||||
|
elif '@' not in left and '@' in right:
|
||||||
|
# glyph-to-group-pair
|
||||||
|
for combo in self.allCombinations(left, right):
|
||||||
|
self.glyph_group_pairs[combo] = value
|
||||||
|
|
||||||
|
else:
|
||||||
|
# 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.
|
||||||
|
kerningPairs.update(self.group_group_pairs)
|
||||||
|
kerningPairs.update(self.group_glyph_pairs)
|
||||||
|
kerningPairs.update(self.glyph_group_pairs)
|
||||||
|
kerningPairs.update(self.glyph_glyph_pairs)
|
||||||
|
|
||||||
|
if includeZero is False:
|
||||||
|
# delete any kerning values == 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:
|
||||||
|
del cleanKerningPairs[pair]
|
||||||
|
return cleanKerningPairs
|
||||||
|
|
||||||
|
else:
|
||||||
|
return kerningPairs
|
||||||
|
|
||||||
|
|
||||||
|
def run(font):
|
||||||
|
ukr = UFOkernReader(font, includeZero=True)
|
||||||
|
print('\n'.join(sorted(ukr.output)))
|
||||||
|
# scrap = os.popen('pbcopy', 'w')
|
||||||
|
# output = '\n'.join(ukr.output)
|
||||||
|
# scrap.write(output)
|
||||||
|
# scrap.close()
|
||||||
|
|
||||||
|
if inRF:
|
||||||
|
pass
|
||||||
|
# print('Total length of kerning:', ukr.totalKerning)
|
||||||
|
|
||||||
|
# if inCL:
|
||||||
|
# print('\n'.join(ukr.output), '\n')
|
||||||
|
|
||||||
|
# print('Total amount of kerning pairs:', len(ukr.output))
|
||||||
|
# print('List of kerning pairs copied to clipboard.')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
inRF = False
|
||||||
|
inCL = False
|
||||||
|
|
||||||
|
try:
|
||||||
|
import mojo
|
||||||
|
inRF = True
|
||||||
|
f = CurrentFont()
|
||||||
|
if f:
|
||||||
|
run(f)
|
||||||
|
else:
|
||||||
|
print(u'You need to open a font first. \U0001F625')
|
||||||
|
|
||||||
|
except ImportError:
|
||||||
|
try:
|
||||||
|
import defcon
|
||||||
|
inCL = True
|
||||||
|
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(u'You don’t have Defcon installed. \U0001F625')
|
||||||
96
misc/kerndiff/kerndiff.sh
Executable file
96
misc/kerndiff/kerndiff.sh
Executable file
|
|
@ -0,0 +1,96 @@
|
||||||
|
#!/bin/bash -e
|
||||||
|
|
||||||
|
function usage() {
|
||||||
|
cat 1>&2 <<__END
|
||||||
|
usage: $0 [options] <font1> <font2>
|
||||||
|
options:
|
||||||
|
-h, --help Show usage and exit
|
||||||
|
Rest of options are forwarded to the "diff" program
|
||||||
|
__END
|
||||||
|
}
|
||||||
|
|
||||||
|
diffargs=()
|
||||||
|
file1=
|
||||||
|
file2=
|
||||||
|
|
||||||
|
while [ "$1" != "" ]; do
|
||||||
|
PARAM=`echo $1 | awk -F= '{print $1}'`
|
||||||
|
VALUE=`echo $1 | awk -F= '{print $2}'`
|
||||||
|
case $PARAM in
|
||||||
|
-h | -help | --help)
|
||||||
|
usage
|
||||||
|
exit
|
||||||
|
;;
|
||||||
|
-*)
|
||||||
|
diffargs[${#diffargs[*]}]=$1
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
if [[ "$file1" == "" ]]; then
|
||||||
|
file1=$PARAM
|
||||||
|
elif [[ "$file2" == "" ]]; then
|
||||||
|
file2=$PARAM
|
||||||
|
else
|
||||||
|
echo "Too many files" 1>&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ "$file1" == "" ]] && [[ "$file2" == "" ]]; then
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
elif [[ "$file1" == "" ]] || [[ "$file2" == "" ]]; then
|
||||||
|
echo "Not enough files" 1>&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
tmpdir=$TMPDIR
|
||||||
|
if [[ "$tmpdir" == "" ]]; then
|
||||||
|
tmpdir=/tmp
|
||||||
|
fi
|
||||||
|
tmpdir=$tmpdir/kerndiff-tmp
|
||||||
|
mkdir -p "$tmpdir"
|
||||||
|
|
||||||
|
file1x="$(basename "$file1")"
|
||||||
|
file2x="$(basename "$file2")"
|
||||||
|
|
||||||
|
pushd "$(dirname "$0")" >/dev/null
|
||||||
|
KERNDIFF_DIR=$PWD
|
||||||
|
popd >/dev/null
|
||||||
|
|
||||||
|
case $file1 in
|
||||||
|
*.otf)
|
||||||
|
python "$KERNDIFF_DIR/getKerningPairsFromOTF.py" "$file1" \
|
||||||
|
> "$tmpdir/$file1x"
|
||||||
|
;;
|
||||||
|
*.ufo)
|
||||||
|
python "$KERNDIFF_DIR/getKerningPairsFromUFO.py" "$file1" \
|
||||||
|
> "$tmpdir/$file1x"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "unsupported file format: $file1"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
case $file2 in
|
||||||
|
*.otf)
|
||||||
|
python "$KERNDIFF_DIR/getKerningPairsFromOTF.py" "$file2" \
|
||||||
|
> "$tmpdir/$file2x"
|
||||||
|
;;
|
||||||
|
*.ufo)
|
||||||
|
python "$KERNDIFF_DIR/getKerningPairsFromUFO.py" "$file2" \
|
||||||
|
> "$tmpdir/$file2x"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "unsupported file format: $file2"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
pushd $tmpdir >/dev/null
|
||||||
|
diff -u "${diffargs[@]}" "$file1x" "$file2x"
|
||||||
|
popd >/dev/null
|
||||||
|
rm -rf "$tmpdir"
|
||||||
Reference in a new issue