diff options
-rw-r--r-- | CONTRIBUTING.md | 39 | ||||
-rwxr-xr-x | misc/kernsample.py | 141 |
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() |