Skip to content

Instantly share code, notes, and snippets.

@simoncozens
Created January 10, 2020 16:42
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save simoncozens/cad7640a37265b087b35c2c59a510f9f to your computer and use it in GitHub Desktop.
Save simoncozens/cad7640a37265b087b35c2c59a510f9f to your computer and use it in GitHub Desktop.
JSTF Table builder
#!/usr/bin/env python
# Builds a JSTF table from specially named features
# To use:
# First, set up the features in your font.
# You may add as many or as few features as you like, but they must conform
# to the following naming convention:
# exs1: First priority extension substitution.
# i.e. the first rules we try to substitute glyphs to make the line longer
# exp1: First priority extension positioning.
# i.e. the first rules we try to reposition glyphs to make the line longer
# shs1: First priority shrinkage substitution.
# i.e. the first rules we try to substitute glyphs to make the line shorter
# exp1: First priority shrinkage positioning.
# i.e. the first rules we try to reposition glyphs to make the line shorter
# If you want to specify a second level of suggestions to the layout engine,
# add one or more features called exs2, exp2, shs2, or shp2.
# For example: to tell the layout engine that, if it wants a longer line, it can
# try replacing H by H.wide and/or O by O.wide, add a feature:
# feature exs1 {
# sub H by H.wide;
# sub O by O.wide
# } exs1;
# To tell the layout engine that, if it *still* wants a longer line, it should next
# try replacing G by G.wide, add a feature:
# feature exs2 {
# sub G by G.wide;
# } exs2;
# Next run this script on your font. Expected output:
# % python3 jstf-builder.py JSTFSans-Regular.otf
# Looking for suggestion 1...
# Found extension substitution feature exs1 (lookups=0)
# Found shrinkage substitution feature shs1 (lookups=2)
# Looking for suggestion 2...
# Found extension substitution feature exs2 (lookups=1)
# Found shrinkage substitution feature shs2 (lookups=3)
# Looking for suggestion 3...
# Not found, ending here
# Saving JSTF-JSTFSans-Regular.otf
import argparse
from fontTools.ttLib import TTFont
from fontTools.misc.py23 import *
from fontTools import ttLib
import sys
from fontTools.ttLib.tables import otTables as ot
from collections import OrderedDict
from argparse import ArgumentParser
parser = ArgumentParser()
parser.add_argument("input",
help="font file to process", metavar="FILE")
parser.add_argument("--extender", dest="extenders", action='append',
help="Glyph names which act as extenders (tatweels, etc.)")
parser.add_argument("-o", dest="output",
help="path to output font", metavar="FILE")
args = parser.parse_args()
extenderGlyphs = args.extenders
fontPath = args.input
output = args.output
if output is None:
output = "JSTF-"+args.input
font = TTFont(fontPath)
tag = Tag("JSTF")
jstfTable = ttLib.getTableClass(tag)(tag)
tableClass = getattr(ot, tag)
jstfTable.table = tableClass()
jstfTable.table.populateDefaults()
setattr(jstfTable.table, "Version", 0x00010000)
# For now, everything lives in DFLT/dflt
rec = ot.JstfScriptRecord()
setattr(rec, "JstfScriptTag", "DFLT")
script = ot.JstfScript()
extender = ot.ExtenderGlyph()
setattr(extender, "ExtenderGlyph", extenderGlyphs)
setattr(script, "ExtenderGlyph", extender)
deflang = ot.DefJstfLangSys()
setattr(script, "DefJstfLangSys", deflang)
# Gather GSUB stuff
gsubfeatures = OrderedDict()
gposfeatures = OrderedDict()
def _prepareFeatureLangSys(langTag, langSys, table, features, scriptTag):
# This is a part of prepareFeatures
for featureIdx in langSys.FeatureIndex:
featureRecord = table.FeatureList.FeatureRecord[featureIdx]
featureTag = featureRecord.FeatureTag
scripts = features.get(featureTag, None)
if scripts is None:
scripts = OrderedDict()
features[featureTag] = scripts
languages = scripts.get(scriptTag, None)
if languages is None:
languages = OrderedDict()
scripts[scriptTag] = languages
lookups = languages.get(langTag, None)
if lookups is None:
lookups = []
languages[langTag] = lookups
for lookupIdx in featureRecord.Feature.LookupListIndex:
lookups.append(lookupIdx)
for scriptRecord in font["GSUB"].table.ScriptList.ScriptRecord:
scriptTag = scriptRecord.ScriptTag
if scriptRecord.Script.DefaultLangSys is not None:
_prepareFeatureLangSys('dflt', scriptRecord.Script.DefaultLangSys, font["GSUB"].table, gsubfeatures, scriptTag)
for langSysRecord in scriptRecord.Script.LangSysRecord:
_prepareFeatureLangSys(langSysRecord.LangSysTag, langSysRecord.LangSys, font["GSUB"].table, gsubfeatures, scriptTag)
for scriptRecord in font["GPOS"].table.ScriptList.ScriptRecord:
scriptTag = scriptRecord.ScriptTag
if scriptRecord.Script.DefaultLangSys is not None:
_prepareFeatureLangSys('dflt', scriptRecord.Script.DefaultLangSys, font["GPOS"].table, gposfeatures, scriptTag)
for langSysRecord in scriptRecord.Script.LangSysRecord:
_prepareFeatureLangSys(langSysRecord.LangSysTag, langSysRecord.LangSys, font["GPOS"].table, gposfeatures, scriptTag)
suggestion = 0
while True:
foundAny = False
suggestion = suggestion + 1
if suggestion > 10:
raise ValueError("More than 10 suggestions not supported")
print("\nLooking for suggestion %i..." % suggestion)
thisSuggestion = ot.JstfPriority()
if ("exs%1i" % suggestion) in gsubfeatures:
foundAny = True
lookups = gsubfeatures["exs%1i" % suggestion]["DFLT"]["dflt"]
print("Found extension substitution feature exs%i (lookups=%s)" % (suggestion, ",".join([str(x) for x in lookups])))
ext = ot.ExtensionEnableGSUB()
setattr(ext, "GSUBLookupIndex",lookups)
setattr(thisSuggestion, "ExtensionEnableGSUB", ext)
if ("exp%1i" % suggestion) in gposfeatures:
foundAny = True
lookups = gposfeatures["exs%1i" % suggestion]["DFLT"]["dflt"]
print("Found extension positioning feature exp%i (lookups=%s)" % (suggestion, ",".join([str(x) for x in lookups])))
ext = ot.ExtensionEnableGPOS()
setattr(ext, "GPOSLookupIndex",lookups)
setattr(thisSuggestion, "ExtensionEnableGPOS", ext)
if ("shs%1i" % suggestion) in gsubfeatures:
foundAny = True
lookups = gsubfeatures["shs%1i" % suggestion]["DFLT"]["dflt"]
print("Found shrinkage substitution feature shs%i (lookups=%s)" % (suggestion, ",".join([str(x) for x in lookups])))
ext = ot.ShrinkageEnableGSUB()
setattr(ext, "GSUBLookupIndex",lookups)
setattr(thisSuggestion, "ShrinkageEnableGSUB", ext)
if ("shp%1i" % suggestion) in gposfeatures:
foundAny = True
lookups = gposfeatures["shs%1i" % suggestion]["DFLT"]["dflt"]
print("Found shrinkage positioning feature shp%i (lookups=%s)" % (suggestion, ",".join([str(x) for x in lookups])))
ext = ot.ShrinkageEnableGPOS()
setattr(ext, "GPOSLookupIndex",lookups)
setattr(thisSuggestion, "ShrinkageEnableGPOS", ext)
if not foundAny:
print("Not found, ending here")
break
if not (hasattr(deflang,"JstfPriority")):
setattr(deflang, "JstfPriority", [])
newList = getattr(deflang,"JstfPriority") + [thisSuggestion]
setattr(deflang, "JstfPriority", getattr(deflang,"JstfPriority") + [thisSuggestion])
setattr(rec, "JstfScript", script)
jstfTable.table.JstfScriptRecord.append(rec)
font["JSTF"] = jstfTable
print("Saving "+output)
font.save(output)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment