Skip to content

Instantly share code, notes, and snippets.

@simoncozens
Created February 15, 2022 17:47
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save simoncozens/e71469df81be1f70f7f09694be8f97e6 to your computer and use it in GitHub Desktop.
Save simoncozens/e71469df81be1f70f7f09694be8f97e6 to your computer and use it in GitHub Desktop.
from fontTools.ttLib import TTFont
import fontTools.ttLib.tables.otTables as ot
def coverage_for(lookup):
coverage = set()
for st in lookup.SubTable:
coverage |= coverage_for_subtable(st)
return coverage
def coverage_for_subtable(st):
if isinstance(st, ot.SingleSubst):
return set(st.mapping.keys())
if isinstance(st, ot.ExtensionSubst):
return coverage_for_subtable(st.ExtSubTable)
raise NotImplementedError("Can't check coverage of %s" % st.__class__.__name__)
def remap_lookups_in_substlookuprecord(sr, mapping):
for csr in sr.SubstLookupRecord:
csr.LookupListIndex = mapping.get(csr.LookupListIndex, csr.LookupListIndex)
def remap_lookups_in_subtable(subtables, mapping):
for st in subtables:
if hasattr(st, "SubstLookupRecord"):
remap_lookups_in_substlookuprecord(st, mapping)
elif hasattr(st, "ChainSubClassSet"):
for sr in st.ChainSubClassSet:
for csr in sr.ChainSubClassRule:
remap_lookups_in_substlookuprecord(csr, mapping)
elif hasattr(st, "ChainSubRuleSet"):
for sr in st.ChainSubRuleSet:
for csr in sr.ChainSubRule:
remap_lookups_in_substlookuprecord(csr, mapping)
elif hasattr(st, "SubRuleSet"):
for sr in st.SubRuleSet:
for csr in sr.SubRule:
remap_lookups_in_substlookuprecord(csr, mapping)
else:
import IPython;IPython.embed()
def remap_lookups_in_ext_lookuplist(lookup, mapping):
remap_lookups_in_subtable([lookup.SubTable[0].ExtSubTable], mapping)
def remap_lookups_in_feature_table(table, mapping):
for fr in table.FeatureList.FeatureRecord:
fr.Feature.LookupListIndex = [
mapping.get(i, i) for i in fr.Feature.LookupListIndex
]
def remap_contextuals(table, mapping, contextuals, extension):
for ix, lookup in enumerate(table.LookupList.Lookup):
if lookup.LookupType in contextuals:
remap_lookups_in_subtable(lookup.SubTable, mapping)
if (
lookup.LookupType == extension
and lookup.SubTable[0].ExtensionLookupType in contextuals
):
remap_lookups_in_ext_lookuplist(lookup, mapping)
def merge_lookup(a_ix, b_ix, table, table_name):
print("Merging lookup %i and %i" % (a_ix, b_ix))
contextuals = [5, 6] if table_name == "GSUB" else [7, 8]
extension = 7 if table_name == "GSUB" else 9
a = table.LookupList.Lookup[a_ix]
b = table.LookupList.Lookup[b_ix]
if isinstance(a.SubTable[0], ot.SingleSubst):
a.SubTable[0].mapping.update(b.SubTable[0].mapping)
else:
a.SubTable.extend(b.SubTable)
remap_contextuals(table, {b_ix: a_ix}, contextuals, extension)
# Delete b
# mapping = {}
# for ix in range(b_ix + 1, len(table.LookupList.Lookup)):
# mapping[ix] = ix - 1
# print("Remapping: ", mapping)
# remap_lookups_in_feature_table(table, mapping)
# del table.LookupList.Lookup[b_ix]
font = TTFont("fonts/ttf/Gulzar-Regular.ttf")
def share_contextual_lookups(font, table, table_name):
contextuals = [5, 6] if table_name == "GSUB" else [7, 8]
extension = 7 if table_name == "GSUB" else 9
# Share contextual lookups
# If two lookups
lookups = table.LookupList.Lookup
# which do not appear in the feature table
toplevel_lookup_indices = set()
for fr in table.FeatureList.FeatureRecord:
toplevel_lookup_indices |= set(fr.Feature.LookupListIndex)
for a_ix, a in enumerate(lookups):
if a_ix in toplevel_lookup_indices:
continue
for b_ix, b in enumerate(lookups):
if b_ix <= a_ix:
continue
if b_ix in toplevel_lookup_indices:
continue
# share the same flags etc
if a.LookupType != b.LookupType:
continue
if a.LookupFlag != b.LookupFlag:
continue
lookup_type = a.LookupType
if a.LookupType == extension:
if (
a.SubTable[0].ExtensionLookupType
!= b.SubTable[0].ExtensionLookupType
):
continue
lookup_type = a.SubTable[0].ExtensionLookupType
if lookup_type in contextuals:
continue
if a.LookupFlag & 0x10:
if a.MarkFilteringSet != b.MarkFilteringSet:
continue
# ...and do not overlap in coverage...
a_coverage = coverage_for(a)
b_coverage = coverage_for(b)
if a_coverage and b_coverage and a_coverage.isdisjoint(b_coverage):
print("Merging %i %i" % (a_ix, b_ix), a_coverage, b_coverage)
# ...they can be combined
merge_lookup(a_ix, b_ix, table, table_name)
return True
return False
table = font["GSUB"].table
print(len(table.LookupList.Lookup))
while share_contextual_lookups(font, table, "GSUB"):
print(len(table.LookupList.Lookup))
pass
font.save("Merged.ttf")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment