-
Notifications
You must be signed in to change notification settings - Fork 300
WebGL Renderer Ligatures #2560
WebGL Renderer Ligatures #2560
Conversation
…s a fixed reference to Fira Code on my machine for now)
…-renderer-ligatures
@cryza this is awesome 😍 🎉, thanks for what looks like a lot of work and the detailed explanation re how you went about implementing it. Just gave it a try locally and I'm getting: Which relates to this function: I was trying it out using PS: sorry for almost immediately piling in there with an issue 😆 |
@akin909 Thanks for trying it out so quickly!
Until then, you can try to use |
@cryza using Not sure how Also wasn't getting many [OpenType] logs the error seemed to occur before much actually happened. |
@akin909 I made the code for finding the right font file a bit smarter, so it should now also work with |
Just had a go with the most recent changes, I now get Seems to correctly find the fonts but run out of space, tried a couple of fonts |
browser/src/Renderer/WebGLRenderer/TextRenderer/LigatureGrouper/OpenTypeLigatureGrouper.ts
Outdated
Show resolved
Hide resolved
browser/src/Renderer/WebGLRenderer/TextRenderer/LigatureGrouper/OpenTypeLigatureGrouper.ts
Show resolved
Hide resolved
Very cool work guys! One thing you might look at sometime would be a new project we've been building on top of fontkit and font-manager already: textkit. It's a full text layout engine in JS. It does font loading and substitution, line wrapping, complex script itemization, and more. It handles run grouping similar to your It's already being used in react-pdf for their text layout needs. Aside from that, there isn't much docs yet so you can check out a small example of how the API works here: https://github.com/foliojs/textkit/blob/master/temp.js |
@devongovett Cool, thanks for the hint about textkit! I would never have found that repo on my own :) I just had a quick look at it: If I understood it correctly, the PDF renderer takes the glyph paths from the glyph run and renders them to the context just like they are described, right? @akin909 Thanks for testing again :) |
@cryza the PDF renderer doesn't render the glyph paths using fontkit, it just places the glyphs in the PDF using native PDF text setting operators. This ensures that the text is selectable, copyable, etc. Unfortunately, as I mentioned above, canvas doesn't have a way to render text by glyph id instead of characters without some hacks, so that would be the main challenge. |
@cryza I'm now noting an error -> And no text is being rendered in the buffer -> Btw |
It works! 🎉 no console errors, no crashes ⭐️ |
I ran some more performance tests to find out if the ligatures add a notable amount of delay when typing. While there is of course a bit of added time per frame, my personal perception tells me that this is not noticeable. The profiler also shows that less than 5% of the computation time is spent on the ligatures. I think there are other places that we can and should optimize first before e.g. going asynchronous with the ligature resolution. This means I would vouch for merging this PR, given that the CI passes and someone approves :) |
@cryza been running this a little bit, and just noticed that all the text that was previously italicised is now bold, I know somewhere in this PR you changed how the renderer handles bolding text and I'm wondering if that might be a cause? I don't think its a major blocker since there this PR adds a lot of other things but thought I should raise it |
Codecov Report
@@ Coverage Diff @@
## master #2560 +/- ##
==========================================
+ Coverage 44.93% 45.29% +0.36%
==========================================
Files 352 361 +9
Lines 14336 14555 +219
Branches 1863 1903 +40
==========================================
+ Hits 6442 6593 +151
- Misses 7669 7738 +69
+ Partials 225 224 -1
Continue to review full report at Codecov.
|
Thanks for the thorough testing, @akin909 ! You were right, for normal font weights the renderer tried to write a broken style string to the canvas which was blocked and resulted in the glyph re-using the previous style instead - which was the bold style for all pre-rendered glyphs. I was able to fix the style string calculation now :) Also big thanks to @CrossR for testing this on Windows! Apparently the font matching algorithm within |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@cryza tried it out and the bolding and italics now render correctly otherwise been running a version of the branch locally and haven't had any errors or crashes and what I understand of the code changes look good to me
This introduces the feature to render ligatures when using the WebGL renderer.
It also makes the WebGL renderer use the
editor.fontWeight
setting and dynamically make bold text 300 units heavier based on that setting.The ligatures were made working with the following strategy, visualized with the example of the
=>
ligature inside the snippetconst noop = () => {}
that many coding fonts provide:const
token, one group for the following, differently colored function name, one for the=
and()
each and one for the=>
. SeegroupCells.ts
ICellGroup
s and group the characters inside the group if they will produce a ligature. Simply speaking, we map["=", ">"]
to["=>"]
but keep["c", "o", "n", "s", "t"]
unchanged. That is done like so insideLigatureGrouper.ts
:editor.fontFamily
setting and usefont-manager
to find the corresponding actual font file on the host systemfs
and parse it usingfontkit
fontkit
to map all the characters to so-called glyphs, which is essentially an index into the list of different versions of all characters that the font can display. Our data for=>
is now essentially something like[{ glyphId: 69, contextGroup: A }, { glyphId: 82, contextGroup: B }]
fontkit
'sapplyFeatures
to apply ligature substitution on the glyphs. Namely, this means applying the OpenType featurescalt
,rclt
,liga
,dlig
,clig
. See Wikipedia for a nice explanation of what these are. This changes our glyph data to something like[{ glyphId: 103, contextGroup: A }, { glyphId: 107, contextGroup: A }]
ICellGroup
individually by sending them to theGlyphAtlas
as a concatenated string.TextRenderer
use theGlyphAtlas
for rendering the actual text just like before, only now the glyphs also might be ligatures that represent several characters.Since the OpenType processing is a very resource-heavy task, each distinct sequence of characters that has been processed is cached inside the
LigatureGrouper
. That means that each different token will only ever run through that algorithm once.Huge shoutout to @devongovett for his great work on
fontkit
andfont-manager
and the fact that he made this possible as easily as it was now! Devon, I hope you're ok with us copy-pasting a lot of the logic from yourOTProcessor
and yourGSUBProcessor
. I didn't open a PR on fontkit because I think that our requirements of tracking thecontextGroup
s are really specific and not relevant to other users of fontkit. If you think otherwise, I'll gladly open a PR!Fixes #2161