From 824cd6a9f820e08b5eefd1127e46a25ac0825fb4 Mon Sep 17 00:00:00 2001 From: John Mager Date: Sat, 20 Jun 2020 19:36:55 -0400 Subject: [PATCH 1/5] Added support for longer ligatures - Breaking out the ligature substitution rules like this also allows for easy logical extensions, to handle edge cases as FiraCode does. --- Scripts/features.py | 65 ++++++++++++++++++++++++--------------------- 1 file changed, 35 insertions(+), 30 deletions(-) diff --git a/Scripts/features.py b/Scripts/features.py index c0180ed..307004b 100644 --- a/Scripts/features.py +++ b/Scripts/features.py @@ -44,36 +44,41 @@ def rule(liga): [LIG f i] LIG [ f f i] LIG } """ - if len(liga) == 2: - return dedent('''\ - lookup {0}_{1} {{ - ignore sub {0} {0}' {1}; - ignore sub {0}' {1} {1}; - sub {0}' {1} by LIG; - sub LIG {1}' by {0}_{1}.liga; - }} {0}_{1}; - ''').format(*liga) - elif len(liga) == 3: - return dedent('''\ - lookup {0}_{1}_{2} {{ - ignore sub {0} {0}' {1} {2}; - ignore sub {0}' {1} {2} {2}; - sub {0}' {1} {2} by LIG; - sub LIG {1}' {2} by LIG; - sub LIG LIG {2}' by {0}_{1}_{2}.liga; - }} {0}_{1}_{2}; - ''').format(*liga) - elif len(liga) == 4: - return dedent('''\ - lookup {0}_{1}_{2}_{3} {{ - ignore sub {0} {0}' {1} {2} {3}; - ignore sub {0}' {1} {2} {3} {3}; - sub {0}' {1} {2} {3} by LIG; - sub LIG {1}' {2} {3} by LIG; - sub LIG LIG {2}' {3} by LIG; - sub LIG LIG LIG {3}' by {0}_{1}_{2}_{3}.liga; - }} {0}_{1}_{2}_{3}; - ''').format(*liga) + # 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]])), + ] + + 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) def indent(text, prefix): From bb0e4dc3af343f97e42f68f32524ee2d3702fe1d Mon Sep 17 00:00:00 2001 From: John Mager Date: Sat, 20 Jun 2020 20:02:41 -0400 Subject: [PATCH 2/5] 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 --- Scripts/features.py | 113 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 112 insertions(+), 1 deletion(-) diff --git a/Scripts/features.py b/Scripts/features.py index 307004b..6117c2d 100644 --- a/Scripts/features.py +++ b/Scripts/features.py @@ -3,6 +3,7 @@ # Adapted from https://github.com/tonsky/FiraCode/blob/master/gen_calt.clj from textwrap import dedent +from collections import defaultdict import tempfile @@ -44,7 +45,7 @@ def rule(liga): [LIG f i] LIG [ f f i] LIG } """ - # ignores: + # standard ignores: # ignore sub {0} {0}' {1}; # ignore sub {0}' {1} {1}; rules = [ @@ -52,6 +53,9 @@ def rule(liga): 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; @@ -81,5 +85,112 @@ def ignore(prefix=None, head=None, suffix=None): 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')) From 2508b90ce991e9217240db93479a5ff4788999c4 Mon Sep 17 00:00:00 2001 From: John Mager Date: Sun, 21 Jun 2020 03:31:23 +0000 Subject: [PATCH 3/5] Fixes bug in potentially cyclic ligatures * fixes issue #133 --- Scripts/features.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Scripts/features.py b/Scripts/features.py index 6117c2d..38c9583 100644 --- a/Scripts/features.py +++ b/Scripts/features.py @@ -53,6 +53,12 @@ def rule(liga): ignore(head=liga[0], suffix=(liga[1:] + [liga[-1]])), ] + # careful with repeats: + # #133 ->->->->, /**/**/**/, etc. + if len(liga) > 2 and liga[0] == liga[-1]: + rules.append(ignore([liga[-2]], liga[0], liga[1:])) + rules.append(ignore(head=liga[0], suffix=(liga[1:] + [liga[1]]))) + # hardcoded ignores, i.e. `<||>` rules.extend(ignores[tuple(liga)]) From 7506577e3734e011e3ee29e6e4fe8070cc05ef24 Mon Sep 17 00:00:00 2001 From: John Mager Date: Sat, 20 Jun 2020 20:24:45 -0400 Subject: [PATCH 4/5] Protects some sequences for being ligaturized --- Scripts/features.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/Scripts/features.py b/Scripts/features.py index 38c9583..bbab79b 100644 --- a/Scripts/features.py +++ b/Scripts/features.py @@ -59,6 +59,16 @@ def rule(liga): rules.append(ignore([liga[-2]], liga[0], liga[1:])) rules.append(ignore(head=liga[0], suffix=(liga[1:] + [liga[1]]))) + # Don't cut into `prefix` to complete a ligature. + # i.e. regex `(?=`> is not `(?`=>. + rules.extend( + [ + ignore(prefix[:-n], liga[0], liga[1:]) + for prefix in ignore_prefixes + for n in range(1, len(liga)) + if prefix[-n:] == liga[:n] + ] + ) # hardcoded ignores, i.e. `<||>` rules.extend(ignores[tuple(liga)]) @@ -198,5 +208,17 @@ ignores = defaultdict( ) +ignore_prefixes = [ + ["parenleft", "question", "colon"], + # Regexp lookahead/lookbehind + ["parenleft", "question", "equal"], + ["parenleft", "question", "less", "equal"], + ["parenleft", "question", "exclam"], + ["parenleft", "question", "less", "exclam"], + # PHP Date: Sat, 20 Jun 2020 19:54:02 -0400 Subject: [PATCH 5/5] Skip generating ignores for certain ligatures. - Logic ripped from FiraCode, not currently applicable to Fantasque Sans - Some potential ligatures ( <*>, <$> ), can be arbitrarily extended while still making sense: ( <<<$>>>, etc). Standard 'ignore' rules will prevent this from displaying, so we can skip those for certain ligatures. --- Scripts/features.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/Scripts/features.py b/Scripts/features.py index bbab79b..265ecf9 100644 --- a/Scripts/features.py +++ b/Scripts/features.py @@ -45,13 +45,17 @@ def rule(liga): [LIG f i] LIG [ f f i] LIG } """ + rules = [] # 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]])), - ] + if tuple(liga) not in skip_ignores: + rules.extend( + [ + ignore(prefix=liga[:1], head=liga[0], suffix=liga[1:]), + ignore(head=liga[0], suffix=(liga[1:] + [liga[-1]])), + ] + ) # careful with repeats: # #133 ->->->->, /**/**/**/, etc. @@ -220,5 +224,14 @@ ignore_prefixes = [ ] +# DO NOT generate ignores at all +skip_ignores = { + # # <<*>> <<+>> <<$>> + # ("less", "asterisk", "greater"), + # ("less", "plus", "greater"), + # ("less", "dollar", "greater"), +} + + def indent(text, prefix): return '\n'.join(prefix + line for line in text.split('\n'))