Skip to content

Instantly share code, notes, and snippets.

@frankrolf
Last active January 17, 2023 20:56
Show Gist options
  • Select an option

  • Save frankrolf/9648335ddcf624edc3d79aebea67e3e7 to your computer and use it in GitHub Desktop.

Select an option

Save frankrolf/9648335ddcf624edc3d79aebea67e3e7 to your computer and use it in GitHub Desktop.
Create contextual kerning for Catalan l·l combination
'''
Create contextual kerning for the /l /periodcenterd /l pair to reproduce
the positioning found in the built-in (but obsolete) /ldot glyph.
This script expects the /ldot composite glyph to be built from
/l and /periodcentered components.
'''
from defcon import Font
import sys
import os
IN_RF = 'mojo' in sys.modules
base_composite = [('l', 'ldot'), ('L', 'Ldot'), ('L.sc', 'Ldot.sc')]
ctxt_kerning_filename = 'kern_ctxt.fea'
def make_grouped_dict(f):
'''
Create a dictionary of glyph names, which can be used to indicate
on which side they are grouped into kerning classes
'''
grouped_dict = {}
for group_name, glyph_list in f.groups.items():
if group_name.startswith('public.kern1'):
for gName in glyph_list:
grouped_dict.setdefault(gName, [None, None])
grouped_dict[gName][0] = group_name
if group_name.startswith('public.kern2'):
for gName in glyph_list:
grouped_dict.setdefault(gName, [None, None])
grouped_dict[gName][1] = group_name
return grouped_dict
def get_kerning_for_pair(font, l_glyph, r_glyph):
'''
Return kerning value for a pair of glyph names, no matter if they are
grouped or not.
'''
# easiest scenario: glyph pair exists directly in kerning:
flat_pair = font.kerning.get((l_glyph, r_glyph))
if flat_pair:
return flat_pair
# if the pair does not exist, we need to look into the groups:
grouped_dict = make_grouped_dict(font)
l_group = r_group = None
l_grouped = grouped_dict.get(l_glyph)
if l_grouped:
l_group = l_grouped[0]
r_grouped = grouped_dict.get(r_glyph)
if r_grouped:
r_group = r_grouped[1]
# does the pair exist as a glyph-to-group-pair?
gl_gr_pair = font.kerning.get((l_glyph, r_group))
if gl_gr_pair:
return gl_gr_pair
# does the pair exist as a group-to-glyph-pair?
gr_gl_pair = font.kerning.get((l_group, r_glyph))
if gr_gl_pair:
return gr_gl_pair
# does the pair exist as a group-to-group-pair?
gr_gr_pair = font.kerning.get((l_group, r_group))
if gr_gr_pair:
return gr_gr_pair
# return zero if the pair is not kerned at all
return 0
def make_ctxt_kerning(font):
f_path = font.path
font_dir = os.path.dirname(os.path.normpath(f_path))
output = []
output_file = os.path.join(font_dir, ctxt_kerning_filename)
for base_glyph_name, composite_glyph_name in base_composite:
if base_glyph_name in f.keys():
# calculate the value a periodcentered following an l needs to be
# shifted so it lands at the position of the component in the
# l/Ldot composite glyph:
ldot_glyph = f[composite_glyph_name]
base_glyph = f[base_glyph_name]
pc_glyph = f['periodcentered']
dot_component = [
c for c in ldot_glyph.components if
c.baseGlyph != base_glyph_name][0]
dot_origin_x, dot_origin_y = dot_component.bounds[:2]
# right margin of the dot within composed ldot glyph
dot_margin = ldot_glyph.width - dot_component.bounds[2]
pc_x, pc_y = pc_glyph.bounds[:2]
# find kerning which may previously exist (so it can be un-done):
existing_kerning_l_pc = get_kerning_for_pair(
f, base_glyph_name, 'periodcentered')
pc_shift_x = int(
dot_origin_x - (base_glyph.width + pc_x) - existing_kerning_l_pc)
period_shift_y = int(
dot_origin_y - pc_y)
# calculate the value the now-moved periodcentered needs to be
# kerned to the following l in a chain of 3 by comparing the
# right sidebearing:
pc_margin = pc_glyph.rightMargin
current_margin = pc_margin - pc_shift_x
kern_value = dot_margin - current_margin
# undo any previously-existing kerning to the right edge glyph
existing_kerning_pc_l = get_kerning_for_pair(
f, 'periodcentered', base_glyph_name)
kern_value -= existing_kerning_pc_l
# if the base L is the same width as the Ldot, check if L and L
# are kerned to each other
if base_glyph.width == ldot_glyph.width:
existing_kerning_l_l = get_kerning_for_pair(
f, base_glyph_name, base_glyph_name)
# adjust the final kerning value accordingly
kern_value += existing_kerning_l_l
# write out the kerning value record
v_record = (
f"pos {base_glyph_name} periodcentered' "
f"<{pc_shift_x} {period_shift_y} {kern_value} 0> "
f"{base_glyph_name};")
output.append(v_record)
with open(output_file, 'w') as kf:
kf.write('\n'.join(output))
kf.write('\n')
print(output_file, 'written ✔')
if IN_RF:
f = CurrentFont()
make_ctxt_kerning(f)
else:
for f_path in sys.argv[1:]:
f = Font(f_path)
make_ctxt_kerning(f)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment