summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRasmus Andersson <rasmus@notion.se>2017-09-19 20:54:02 +0300
committerRasmus Andersson <rasmus@notion.se>2017-09-19 20:54:02 +0300
commit9209081fe9dd3fe4e4fee675758577bfb8a37950 (patch)
treee6b1f85d3a39352227fed69e4419ed26fafe807a
parente867bb7bd71f1467cf44dae4c736c704066f2490 (diff)
downloadinter-9209081fe9dd3fe4e4fee675758577bfb8a37950.tar.xz
Adds misc/kernsample.py
-rw-r--r--CONTRIBUTING.md39
-rwxr-xr-xmisc/kernsample.py141
2 files changed, 180 insertions, 0 deletions
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 91ffc7539..7877ab116 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -228,6 +228,45 @@ You can now visit `http://localhost:2015/lab/`.
After you rebuild some font files, reload the web page to refresh fonts.
+### Kerning
+
+Kerning is the concept of harmony in the pace of characters, defined as a set of distances
+between specific character pairs, like "A" & "c".
+Good kerning makes words more readable. Oftentimes this means that when adjusting kerning,
+you have to look at whole words and sentences when adjusting the kerning of a pair, since
+the spacing between two characters should work in harmony with the spacing of all other characters.
+
+All major font editors provide user interfaces for previewing and adjusting kerning.
+
+When adding or adjusting kerning:
+
+- Make sure to use kerning groups (`src/Inter-UI-*.ufo/groups.plist`)
+
+- If a glyphname is missing in kerning groups, define a new group for it.
+ Group naming scheme is: `@KERN_<DIRECTION>_<NAME>` where `<DIRECTION>`
+ is either `LEFT` or `RIGHT`, depending on if the group represents the left-hand side
+ of a pair or the right-hand side. `<NAME>` is the name of a glyph that most commonly
+ represents the group. For instance, for all glyphs that has a left-side shape similar
+ to "c", like "d", the group name is "c". This makes it easy to test kerning of groups
+ by just using the `<NAME>` part in previews.
+
+- Try to submit image samples of kerning adjustments with your pull requests whenever
+ feasible.
+
+The script `misc/kernsample.py` is helpful in generating samples for all existing
+right-hand side characters given a left-hand side glyphname.
+
+```txt
+$ misc/kernsample.py src/Inter-UI-Black.ufo P -suffix MOR
+PAMOR P/AE MOR PJMOR PXMOR PYMOR PZMOR P/ae mor P/ampersand mor P/backslash mor P/dzcaron mor P/eightsub mor P/ellipsis mor Pfmor P/four mor P/guilsinglleft mor P/idieresisacute mor P/periodcentered mor P/quotedblbase mor Psmor P/seven mor P/slash mor Ptmor P/two mor P/underscore mor Pymor
+```
+
+Type `misc/kernsample.py -h` for help on how to use the program.
+
+This only includes existing kerning and is thus only useful for adjustments.
+Additions must still be done manually.
+
+
## FAQ
> Do I need RoboFont?
diff --git a/misc/kernsample.py b/misc/kernsample.py
new file mode 100755
index 000000000..cc36fbe17
--- /dev/null
+++ b/misc/kernsample.py
@@ -0,0 +1,141 @@
+#!/usr/bin/env python
+# encoding: utf8
+from __future__ import print_function
+import os, sys, plistlib
+from collections import OrderedDict
+from argparse import ArgumentParser
+from robofab.objects.objectsRF import OpenFont
+
+
+def mapGroups(groups): # => { glyphname => set(groupname, ...), ... }
+ m = OrderedDict()
+ for groupname, glyphnames in groups.iteritems():
+ for glyphname in glyphnames:
+ m.setdefault(glyphname, set()).add(groupname)
+ return m
+
+
+def _addRightnames(groups, kerning, leftname, rightnames, includeAll=True):
+ if leftname in kerning:
+ for rightname in kerning[leftname]:
+ if rightname[0] == '@':
+ for rightname2 in groups[rightname]:
+ rightnames.add(rightname2)
+ if not includeAll:
+ # TODO: in this case, pick the one rightname that has the highest
+ # ranking in glyphorder
+ break
+ else:
+ rightnames.add(rightname)
+
+
+def fmtGlyphname(glyphname, glyph=None):
+ if glyph is not None and len(glyphname) == 1 and ord(glyphname[0]) == glyph.unicode:
+ # literal, e.g. "A"
+ return glyphname
+ else:
+ # named, e.g. "\Omega"
+ return '/' + glyphname + ' '
+
+
+def samplesForGlyphname(font, groups, groupmap, kerning, glyphname, args):
+ leftGlyph = font[glyphname] # verify it's present
+ rightnames = set()
+
+ _addRightnames(groups, kerning, glyphname, rightnames, includeAll=args.includeAllInGroup)
+
+ if glyphname in groupmap:
+ for groupname in groupmap[glyphname]:
+ _addRightnames(groups, kerning, groupname, rightnames, includeAll=args.includeAllInGroup)
+
+ rightnames = sorted(rightnames)
+
+ out = []
+ if args.formatAsUnicode:
+ left = '\\u%04X' % leftGlyph.unicode
+ for rightname in rightnames:
+ if rightname in font:
+ rightGlyph = font[rightname]
+ if rightGlyph.unicode is not None:
+ out.append('%s\\u%04X' % (left, rightGlyph.unicode))
+ else:
+ left = fmtGlyphname(glyphname, leftGlyph)
+ suffix_uc = ''
+ suffix_lc = ''
+ if len(args.suffix) > 0:
+ s = unicode(args.suffix)
+ if s[0].isupper():
+ suffix_uc = args.suffix
+ suffix_lc = args.suffix.lower()
+ else:
+ suffix_uc = args.suffix.upper()
+ suffix_lc = args.suffix
+ for rightname in rightnames:
+ if rightname in font:
+ rightGlyph = None
+ if len(rightname) == 1:
+ rightGlyph = font[rightname]
+ suffix = suffix_lc
+ if unicode(rightname[0]).isupper():
+ suffix = suffix_uc
+ out.append('%s%s%s' % (left, fmtGlyphname(rightname, rightGlyph), suffix))
+
+ print(' '.join(out))
+
+
+
+
+def main():
+ argparser = ArgumentParser(
+ description='Generate kerning samples by providing the left-hand side glyph')
+
+ argparser.add_argument(
+ '-u', dest='formatAsUnicode', action='store_const', const=True, default=False,
+ help='Format output as unicode escape sequences instead of glyphnames. ' +
+ 'E.g. "\\u2126" instead of "\\Omega"')
+
+ argparser.add_argument(
+ '-suffix', dest='suffix', metavar='<text>', type=str,
+ help='Text to append after each pair')
+
+ argparser.add_argument(
+ '-all-in-groups', dest='includeAllInGroup',
+ action='store_const', const=True, default=False,
+ help='Include all glyphs for groups rather than just the first glyph listed.')
+
+ argparser.add_argument(
+ 'fontPath', metavar='<ufofile>', type=str, help='UFO font source')
+
+ argparser.add_argument(
+ 'glyphnames', metavar='<glyphname>', type=str, nargs='+',
+ help='Name of glyphs to generate samples for. '+
+ 'You can also provide a Unicode code point using the syntax "U+XXXX"')
+
+ args = argparser.parse_args()
+ dryRun = args.dryRun
+
+ font = OpenFont(args.fontPath)
+
+ groupsFilename = os.path.join(args.fontPath, 'groups.plist')
+ kerningFilename = os.path.join(args.fontPath, 'kerning.plist')
+
+ groups = plistlib.readPlist(groupsFilename) # { groupName => [glyphName] }
+ kerning = plistlib.readPlist(kerningFilename) # { leftName => {rightName => kernVal} }
+ groupmap = mapGroups(groups)
+
+ # expand any unicode codepoints
+ glyphnames = []
+ for glyphname in args.glyphnames:
+ if len(glyphname) > 2 and glyphname[:2] == 'U+':
+ cp = int(glyphname[2:], 16)
+ ucmap = font.getCharacterMapping() # { 2126: ['Omega', ...], ...}
+ for glyphname2 in ucmap[cp]:
+ glyphnames.append(glyphname2)
+ else:
+ glyphnames.append(glyphname)
+
+ for glyphname in glyphnames:
+ samplesForGlyphname(font, groups, groupmap, kerning, glyphname, args)
+
+
+main()