@@ -66,27 +66,25 @@ def _expand_with_variants(font, chars):
6666def autokern (font ):
6767 all_glyphs = [glyph .glyphname for glyph in font .glyphs ()
6868 if not glyph .glyphname .startswith (' ' )]
69- ligatures = [name for name in all_glyphs if '_' in name ]
69+ ligatures = [name for name in all_glyphs if name [ 0 ] != '_' and '_' in name ]
7070 upper_ligatures = [ligature for ligature in ligatures if ligature .upper () == ligature ]
7171 lower_ligatures = [ligature for ligature in ligatures if ligature .lower () == ligature ]
7272
73- # Expand the broad letter lists to include accented variants from the outset,
74- # so every rule that references `caps`, `lower`, or `all_chars` covers them too.
75- caps = _expand_with_variants (font , list ('ABCDEFGHIJKLMNOPQRSTUVWXYZ' ) + upper_ligatures )
76- lower = _expand_with_variants (font , list ('abcdefghijklmnopqrstuvwxyz' ) + lower_ligatures )
77- all_chars = caps + lower
73+ caps = list ('ABCDEFGHIJKLMNOPQRSTUVWXYZ' )
74+ lower = list ('abcdefghijklmnopqrstuvwxyz' )
75+ roman = caps + lower
7876
7977 font .addLookup ('kerning' , 'gpos_pair' , (), [['kern' , [['latn' , ['dflt' ]]]]])
8078 font .addLookupSubtable ('kerning' , 'kern' )
8179
82- def kern (sep , left , right , ** kwargs ):
80+ def kern (sep , left , right , damper = None , ** kwargs ):
8381 """Wraps font.autoKern: expands accented variants and leading/trailing ligatures."""
8482 def expand (chars , left_side ):
8583 expanded = _expand_with_variants (font , chars )
8684 seen = set (expanded )
8785 for glyph in font .glyphs ():
8886 name = glyph .glyphname
89- if '_' not in name :
87+ if name [ 0 ] == '_' or '_' not in name :
9088 continue
9189 parts = name .split ('_' )
9290 # Left side: ligature's right edge (last component) determines spacing.
@@ -96,26 +94,72 @@ def expand(chars, left_side):
9694 expanded .append (name )
9795 seen .add (name )
9896 return expanded
99- font .autoKern ('kern' , sep , expand (left , left_side = True ), expand (right , left_side = False ), ** kwargs )
97+ lefts = expand (left , left_side = True )
98+ rights = expand (right , left_side = False )
99+ font .autoKern ('kern' , sep , lefts , rights , ** kwargs )
100+ if damper and damper != 1.0 :
101+ for l in lefts :
102+ tuples = font [l ].getPosSub ('kern' )
103+ new_table = []
104+ for tup in tuples :
105+ if tup [1 ] == 'Pair' and tup [2 ] in rights :
106+ font [l ].addPosSub ('kern' , * (tup [2 :5 ] + (int (tup [5 ] * damper ),) + tup [6 :]))
107+
108+ def getkern (left , right ):
109+ c = font [left ]
110+ tuples = c .getPosSub ('kern' )
111+ for tup in tuples :
112+ if tup [1 ] == 'Pair' and tup [2 ] == right :
113+ return tup [5 ]
114+ return None
100115
101116 a = font ['_pad_space' ].width
102- a = max ( a - 20 , 0 )
117+ a = a - 20
103118
119+ # The same combination will be overwritten, so the one written last will take effect.
104120 # autoKern looks at the outline, so even if you change the padding, it absorbs all of it.
105121 # Use `+a` when you want to link the spacing after kerning to the padding.
106122 kern (150 , ['/' , '\\ ' ], ['/' , '\\ ' ])
107- kern (60 + a , ['s' ], set (lower ) - {'j' , 'f' }, minKern = 50 )
108- # x has diagonal strokes that leave visual space on its left side.
109- kern (90 + a , set (lower ) - {'f' }, ['x' ], minKern = 40 )
123+ # lowercase-lowercase
124+ kern (60 + a , ['s' ], set (lower ) - {'i' , 'j' , 'f' , 't' , 'x' }, onlyCloser = True , damper = 0.75 ) # loosen by damper
125+ # Overwrite sf and st. (From experience, it is often just right to adopt the larger of the two
126+ # separation required by the glyphs on the left and right.)
127+ # The horizontal bars of 'r' and 'f' get caught between the dot and the stem of 'i', causing
128+ # unexpected behavior, so 'i' and 'j' are excluded.
129+ kern (80 + a , set (lower ) - {'i' , 'j' }, ['f' , 't' ], onlyCloser = True , damper = 0.75 )
130+ kern (90 + a , set (lower ) - {'i' , 'j' }, ['x' ], onlyCloser = True , damper = 0.75 ) # kx is fine, fx is tight
131+ kern (80 + a , ['x' ], set (lower ) - {'i' , 'j' }, onlyCloser = True , damper = 0.75 )
132+ kern (100 + a , ['f' , 't' ], set (lower ) - {'i' , 'j' }, onlyCloser = True , damper = 0.75 ) # oveerwrite fx
133+ # For some reason, autoKern malfunctions on the left side of 'e', so the kerning value of 'o' is reused.
134+ kern (0 , ['r' ], ['e' , 'o' ])
135+ diff_ro_re = getkern ('r' , 'o' ) - getkern ('r' , 'e' )
136+ kern (100 + a , ['r' ], set (lower ) - {'i' , 'j' }, onlyCloser = True , damper = 0.75 )
137+ kern (100 + a + diff_ro_re , ['r' ], ['e' ], onlyCloser = True , damper = 0.75 )
138+ # including uppercase
139+ # Set *Y altogether first: CY, OY, etc. will have appropriate values set in the latter part.
140+ kern (105 + a , roman , ['Y' , 'T' ], onlyCloser = True , damper = 0.75 )
141+ kern (100 + a , caps , ['f' ], onlyCloser = True , damper = 0.75 )
110142 # F/E are separated from T/J so they can use a tighter target gap.
111- kern (130 , ['F' ], set (all_chars ) - {'f' , 'j' })
112- kern (140 , ['E' ], ['V' , 'W' , 'Y' ])
113- kern (100 , ['E' ], set (all_chars ) - {'f' , 'j' })
114- kern (120 , ['T' , 'J' ], ['R' ])
115- kern (150 , ['T' , 'J' ], set (all_chars ) - {'f' , 'j' })
116- # C: loosen from the default (was too tight for Ct/Cf/Cj).
117- kern (65 , ['C' ], set (all_chars ) - {'f' , 'j' })
118- kern (60 , ['O' ], set (all_chars ) - {'f' , 'j' })
143+ kern (110 + a , ['F' ], set (roman ) - {'j' }, onlyCloser = True , damper = 0.75 ) # keep FO≈-60
144+ # Since F and z mesh together and the kerning becomes too large,
145+ # reuse the kerning value of one of the round letterforms.
146+ diff_Fo_Fz = getkern ('F' , 'o' ) - getkern ('F' , 'z' )
147+ kern (110 + a + int (diff_Fo_Fz / 0.75 ), ['F' ], ['z' ], onlyCloser = True , damper = 0.75 )
148+ kern (90 + a , ['E' ], set (roman ) - {'j' }, onlyCloser = True , damper = 0.75 ) # keep ES≈-30
149+ kern (45 + a , ['E' ], ['V' ], onlyCloser = True , touch = True )
150+ kern (115 + a , ['T' , 'J' ], set (roman ) - {'j' }, onlyCloser = True , damper = 0.75 ) # keep Tr≈-105
151+ kern (105 + a , ['Y' ], set (roman ) - {'j' }, onlyCloser = True , damper = 0.75 )
152+ kern (85 + a , ['V' ], caps , onlyCloser = True , damper = 0.75 )
153+ # C: loosen from the default (was too tight for Cj).
154+ # Compared to E, the lower curve of C tends to come close to the next character,
155+ # but this is considered an intentional design.
156+ kern (60 + a , ['C' ], set (roman ) - {'j' }, onlyCloser = True , damper = 0.75 ) # keep CK≈-15
157+ kern (25 + a , ['C' ], ['V' ], onlyCloser = True , touch = True )
158+ kern (60 + a , ['O' ], set (roman ) - {'j' }, onlyCloser = True , damper = 0.75 ) # loosen
159+ kern (100 + a , ['P' ], set (roman ) - {'j' }, onlyCloser = True , damper = 0.75 )
160+ diff_Po_Pe = getkern ('P' , 'o' ) - getkern ('P' , 'e' )
161+ kern (100 + a + int (diff_Po_Pe / 0.75 ), ['P' ], ['e' ], onlyCloser = True , damper = 0.75 )
162+ kern (35 + a , ['L' ], set (roman ) - {'j' }, onlyCloser = True , touch = True )
119163
120164
121165autokern (font )
@@ -208,7 +252,7 @@ def expand(chars, left_side):
208252# hinting, which alters the rendered pixel positions of Latin letters. Pin
209253# all values here (derived from the Latin+diacritic glyph set) so the
210254# hinting is stable regardless of how many non-Latin glyphs are added.
211- font .private ['BlueValues' ] = (- 20 , 20 , 411 , 450 , 573 , 613 )
255+ font .private ['BlueValues' ] = (- 10 , 20 , 411 , 441 , 573 , 603 )
212256font .private ['OtherBlues' ] = (- 241 , - 190 )
213257font .private ['BlueScale' ] = 0.0208333
214258font .private ['BlueShift' ] = 16
0 commit comments