Last active
January 17, 2023 20:56
-
-
Save frankrolf/9648335ddcf624edc3d79aebea67e3e7 to your computer and use it in GitHub Desktop.
Create contextual kerning for Catalan l·l combination
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| ''' | |
| 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