14
0
mirror of https://github.com/belluzj/fantasque-sans.git synced 2024-11-05 17:01:32 +01:00
fantasque-sans/Scripts/features.py
John Mager bb0e4dc3af Allows hardcoding sequences to ignore ligatures
- Certain operator combinations - especially those involving equals
 signs - look wrong when some of them form ligatures.

 - All current rules from FiraCode were ported over, but many are
 currently irrelevant for this font. Those are commented out of the
 file.

 - Fixes issue #118
2020-06-21 15:00:57 -04:00

197 lines
6.9 KiB
Python

# Generate features for ligatures
#
# Adapted from https://github.com/tonsky/FiraCode/blob/master/gen_calt.clj
from textwrap import dedent
from collections import defaultdict
import tempfile
def update_features(font):
"""Find ligatures in the font and generate features for them."""
# [ ["dash" "greater" "greater"] ... ]
ligas = [name[:-len('.liga')].split('_')
for name in font if name.endswith('.liga') and
font[name].isWorthOutputting()]
rules = '\n\n'.join(rule(liga)
for liga in sorted(ligas, key=lambda l: -len(l)))
fea_code = dedent('''\
languagesystem DFLT dflt;
languagesystem latn dflt;
languagesystem grek dflt;
languagesystem cyrl dflt;
feature calt {{
{}
}} calt;
''').format(indent(rules, ' '))
# print(fea_code) # DEBUG
# Add the dummy "LIG" glyph
lig = font.createChar(-1, 'LIG')
lig.width = font['space'].width
with tempfile.NamedTemporaryFile(suffix='.fea') as f:
f.write(fea_code)
f.seek(0)
font.mergeFeature(f.name)
def rule(liga):
"""
[f f i] => { [LIG LIG i] f_f_i.liga
[LIG f i] LIG
[ f f i] LIG }
"""
# standard ignores:
# ignore sub {0} {0}' {1};
# ignore sub {0}' {1} {1};
rules = [
ignore(prefix=liga[:1], head=liga[0], suffix=liga[1:]),
ignore(head=liga[0], suffix=(liga[1:] + [liga[-1]])),
]
# hardcoded ignores, i.e. `<||>`
rules.extend(ignores[tuple(liga)])
name = "_".join(liga)
# substitution logic
# sub {0}' {1} by LIG;
# sub LIG {1}' by {0}_{1}.liga;
for i in range(len(liga)):
init = _join(["LIG" for lig in liga[:i]])
tail = _join(liga[i + 1 :])
replace = "LIG" if (i + 1 < len(liga)) else (name + ".liga")
rules.append("sub{0} {1}'{2} by {3};".format(init, liga[i], tail, replace))
# put it all together
lines = (
["lookup " + name + " {"] + [" " + r for r in rules] + ["}} {0};".format(name)]
)
return "\n".join(lines)
def _join(items):
return (" " + " ".join(items)) if items else ""
def ignore(prefix=None, head=None, suffix=None):
""" don't substitute `head` if it's surrounded by `prefix` and `suffix` """
assert head
pref = _join(prefix)
rest = _join(suffix)
return "ignore sub{0} {1}'{2};".format(pref, head, rest)
ignores = defaultdict(
list,
{
("slash", "asterisk"): [
"ignore sub slash' asterisk slash;",
"ignore sub asterisk slash' asterisk;",
],
("asterisk", "slash"): [
"ignore sub slash asterisk' slash;",
"ignore sub asterisk' slash asterisk;",
],
# ("asterisk", "asterisk"): [
# "ignore sub slash asterisk' asterisk;",
# "ignore sub asterisk' asterisk slash;",
# ],
# ("asterisk", "asterisk", "asterisk"): [
# "ignore sub slash asterisk' asterisk asterisk;",
# "ignore sub asterisk' asterisk asterisk slash;",
# ],
# <||>
("less", "bar", "bar"): ["ignore sub less' bar bar greater;"],
("bar", "bar", "greater"): ["ignore sub less bar' bar greater;"],
# # :>=
# ("colon", "greater"): ["ignore sub colon' greater equal;"],
# # {|}
# ("braceleft", "bar"): ["ignore sub braceleft' bar braceright;"],
# ("bar", "braceright"): ["ignore sub braceleft bar' braceright;"],
# # [|]
# ("bracketleft", "bar"): ["ignore sub bracketleft' bar bracketright;"],
# ("bar", "bracketright"): ["ignore sub bracketleft bar' bracketright;"],
# # <*>>> <+>>> <$>>>
# ("greater", "greater", "greater"): [
# "ignore sub [asterisk plus dollar] greater' greater greater;"
# ],
# # <<<*> <<<+> <<<$>
# ("less", "less", "less"): ["ignore sub less' less less [asterisk plus dollar];"],
# # =:=
# ("colon", "equal"): ["ignore sub equal colon' equal;"],
# =!=
("exclam", "equal"): ["ignore sub equal exclam' equal;"],
# =!==
("exclam", "equal", "equal"): ["ignore sub equal exclam' equal equal;"],
# =<= <=< <=> <=| <=: <=! <=/
("less", "equal"): [
"ignore sub equal less' equal;",
"ignore sub less' equal [less greater bar colon exclam slash];",
],
# >=<
# =>= >=> >=< >=| >=: >=! >=/
("greater", "equal"): [
"ignore sub equal greater' equal;",
"ignore sub greater' equal [less greater bar colon exclam slash];",
],
# <*>> <+>> <$>>
# >>->> >>=>>
("greater", "greater"): [
# "ignore sub [asterisk plus dollar] greater' greater;",
# "ignore sub [hyphen equal] greater' greater;",
# "ignore sub greater' greater [hyphen equal];",
],
# <<*> <<+> <<$>
# <<-<< <<=<<
("less", "less"): [
# "ignore sub less' less [asterisk plus dollar];",
# "ignore sub [hyphen equal] less' less;",
# "ignore sub less' less [hyphen equal];",
],
# ||-|| ||=||
("bar", "bar"): [
"ignore sub [hyphen equal] bar' bar;",
"ignore sub bar' bar [hyphen equal];",
],
# # <--> >--< |--|
# ("hyphen", "hyphen"): [
# "ignore sub [less greater bar] hyphen' hyphen;",
# "ignore sub hyphen' hyphen [less greater bar];",
# ],
# # <---> >---< |---|
# ("hyphen", "hyphen", "hyphen"):
# "ignore sub [less greater bar] hyphen' hyphen hyphen;",
# "ignore sub hyphen' hyphen hyphen [less greater bar];",
# ],
("equal", "equal"): [ # ==
# "ignore sub bracketleft equal' equal;", # [==
# "ignore sub equal' equal bracketright;",# ==]
"ignore sub equal [colon exclam] equal' equal;", # =:== =!==
# "ignore sub [less greater bar slash] equal' equal;", # <== >== |== /==
# "ignore sub equal' equal [less greater bar slash];", # ==< ==> ==| ==/
"ignore sub equal' equal [colon exclam] equal;", # ==:= ==!=
],
# [===[ ]===]
# [=== ===]
# <===> >===< |===| /===/ =:=== =!=== ===:= ===!=
("equal", "equal", "equal"): [
# "ignore sub bracketleft equal' equal equal;",
# "ignore sub equal' equal equal bracketright;",
"ignore sub equal [colon exclam] equal' equal equal;",
"ignore sub [less greater bar slash] equal' equal equal;",
# "ignore sub equal' equal equal [less greater bar slash];",
"ignore sub equal' equal equal [colon exclam] equal;",
],
# #118 https://
("slash", "slash"): ["ignore sub colon slash' slash;"],
},
)
def indent(text, prefix):
return '\n'.join(prefix + line for line in text.split('\n'))