tonsky / FiraCode

Free monospaced font with programming ligatures
SIL Open Font License 1.1
77.29k stars 3.1k forks source link

Opentype implementation #1169

Open aaronbell opened 3 years ago

aaronbell commented 3 years ago

I've been diving into the FiraCode OpenType updates in preparation to update Cascadia Code's support, and was surprised to find that you'd switched all of the LIG substitutions for glyph-specific .spacer substitutions. And I haven't been able to figure out exactly why... Is there a specific reason? Personally, I'd rather not include a set of empty glyphs in the font when a single one is sufficient.

Second, I wanted to propose an alternate way of building the OpenType. At current, you are using backwards-looking rules that are implemented only for a given ligature. I think it would make sense to build them as forward looking, where one ligature replaces another. For example:

lookup less_bar {
  ignore sub less less' bar;
  ignore sub less' bar bar bar bar;
  ignore sub less' bar bar greater;
  sub less_bar.liga bar'  by LIG;
  sub less'         bar   by less_bar.liga;
} less_bar;

lookup less_bar_bar {
  sub less_bar_bar.liga LIG   bar'  by LIG;
  sub less_bar.liga'    LIG   bar   by less_bar_bar.liga;
} less_bar_bar;

In this example, less bar is replaced by less bar bar, rather than having a ligature specifically related to less bar and one specifically related to less bar bar. That allows us to dump a bunch of duplicate ignore statements to avoid the ligature from showing up as they're already being handled in less bar. The same code in FiraCode is:

lookup less_bar_bar {
  ignore sub less less' bar bar;
  ignore sub less' bar bar bar;
  ignore sub less' bar bar greater;
  sub less.spacer bar.spacer bar' by less_bar_bar.liga;
  sub less.spacer bar'       bar  by bar.spacer;
  sub less'       bar        bar  by less.spacer;
} less_bar_bar;

lookup less_bar {
  ignore sub less less' bar;
  ignore sub less' bar bar;
  sub less.spacer bar' by less_bar.liga;
  sub less'       bar  by less.spacer;
} less_bar;

As you can see, for these two lookups my approach has a savings of 3 lines of code, which should help with performance. And as far as I can tell, this approach also does not require the use of glyph-specific spacing glyphs.

If you're interested, I can provide you with the updated calt feature for this approach once I finish testing all the various use cases :).

tonsky commented 3 years ago

switched all of the LIG substitutions for glyph-specific .spacer substitutions

I was experimenting with shaping performance by getting rid of all of the lookups. If I remember correctly, it was not significant enough, or was too complicated. At the moment, LIG can be used safely instead of all the spacers.

build them as forward looking, where one ligature replaces another

Entirely possible, yes. I think right now all the groups are independent, and can come in any order, which is easier to work with. There was a point where they were order-dependent, but I guess it was harder to reason when there became too many of them.

aaronbell commented 3 years ago

I was experimenting with shaping performance by getting rid of all of the lookups. If I remember correctly, it was not significant enough, or was too complicated. At the moment, LIG can be used safely instead of all the spacers.

Ah, great.

Entirely possible, yes. I think right now all the groups are independent, and can come in any order, which is easier to work with. There was a point where they were order-dependent, but I guess it was harder to reason when there became too many of them.

Yeah, I understand what you mean. The big benefit of this 'swapping' approach, though, is that it centralizes nearly all of the ignores to the first step in the chain as the later ligatures cannot occur without the earlier ones happening first. So if there's a bug, it is easier to know exactly where to go rather than having to jump around and try to figure out what might be overriding what. Also, it means that slotting in a new ligature is pretty easy—it just hooks into the ligature one up in the chain.

A tool that y'all might find useful is Crowbar which allows for simple OpenType debugging by showing you the glyph string and how each opentype feature influences it.

In any case, as I mentioned previously, I'm happy to share the calt feature once I've fully tested it.

kenmcd commented 3 years ago

@aaronbell FYI @tonsky tests are documented here: Time to shape text with different calt code https://github.com/tonsky/FiraCode/blob/master/docs/calt_performance.md

aaronbell commented 3 years ago

@kenmcd Thanks! Though that seems more related to calt structure (standard subs vs lookups vs lookups with ignores) than what I was inquiring about (use of a single LIG vs per glyph spacers)

kenmcd commented 3 years ago

@aaronbell Yeah, I was not sure if that was exactly what you guys were discussing, but thought it might be related. I am still an OpenType beginner. Which is why I watch these Fira Code issues, and the Cascadia Code issues too!