Created
February 15, 2022 17:47
-
-
Save simoncozens/e71469df81be1f70f7f09694be8f97e6 to your computer and use it in GitHub Desktop.
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
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