dnkl/foot
41
2.0k
Fork
You've already forked foot
242

Ligature rendering support #2278

Open
barsmonster wants to merge 1 commit from barsmonster/foot:master into master
pull from: barsmonster/foot:master
merge into: dnkl:master
dnkl:master
dnkl:osc-5522
dnkl:sixel-heap-buffer-overflow
dnkl:releases/1.27
dnkl:releases/1.26
dnkl:releases/1.25
dnkl:releases/1.24
dnkl:multi-cursor
dnkl:releases/1.23
dnkl:pixman-16f-2
dnkl:releases/1.22
dnkl:releases/1.21
dnkl:releases/1.20
dnkl:releases/1.19
dnkl:releases/1.18
dnkl:releases/1.17
dnkl:releases/1.16
dnkl:releases/1.15
dnkl:releases/1.14
dnkl:releases/1.13
dnkl:releases/1.12
dnkl:releases/1.11
dnkl:releases/1.10
dnkl:releases/1.9
dnkl:releases/1.8
dnkl:releases/1.7
dnkl:releases/1.6
dnkl:releases/1.5
dnkl:releases/1.4
dnkl:releases/1.3
dnkl:releases/1.2
dnkl:releases/1.1
dnkl:releases/1.0
First-time contributor
Copy link

This PR adds support of ligatures in a way to keep default behavior/performance unchanged (using 2 render paths, with reusable code extracted into resolve_colors / composite_glyph).
Rendering of ligatures is indeed not trivial, and is indeed slower. Ligature rendering is cached by default, largest CPU sink is detection of what is ligature and what is not (only when ligatures are enabled).
I tried to keep code simple with minimal amount of memory allocations, so not everything that can be cached/reused is cached (especially as rendering is multithreaded). Personally though I do not notice any practical speed difference, and startup is still instant.

When we know it is 100% reliable and is not causing issues to any users - we can keep only liga code path by short-circuiting ligature detection when ligatures are disabled in unified code path. Currently dual render is kept separate to ensure 100% original behavior for non-liga mode.

For historical reference: #57


Adds tweak.ligatures option (default: no) for OpenType liga/calt
rendering with programming fonts. Ligatures are render-only —
the grid and copy/paste are unchanged.

Ligatures are rendered via new glyph-based API in fcft.

This PR adds support of ligatures in a way to keep default behavior/performance unchanged (using 2 render paths, with reusable code extracted into resolve_colors / composite_glyph). Rendering of ligatures is indeed not trivial, and is indeed slower. Ligature rendering is cached by default, largest CPU sink is detection of what is ligature and what is not (only when ligatures are enabled). I tried to keep code simple with minimal amount of memory allocations, so not everything that can be cached/reused is cached (especially as rendering is multithreaded). Personally though I do not notice any practical speed difference, and startup is still instant. When we know it is 100% reliable and is not causing issues to any users - we can keep only liga code path by short-circuiting ligature detection when ligatures are disabled in unified code path. Currently dual render is kept separate to ensure 100% original behavior for non-liga mode. For historical reference: https://codeberg.org/dnkl/foot/issues/57 --- Adds tweak.ligatures option (default: no) for OpenType liga/calt rendering with programming fonts. Ligatures are render-only — the grid and copy/paste are unchanged. Ligatures are rendered via new glyph-based API in fcft.
Author
First-time contributor
Copy link

I was using this script to test ligatures of various lengths with variety of colors and cursors.
Tested with Maple Mono font : https://font.subf.dev/en/download/ but should work with any.

While working on it I faced rendering bug in classical rendering path on CJK characters (when moving cursor over wide Chinese text to the left/right). I kept classical path as-is, but liga path should render these correctly in all cases.

image

I was using this script to test ligatures of various lengths with variety of colors and cursors. Tested with Maple Mono font : https://font.subf.dev/en/download/ but should work with any. While working on it I faced rendering bug in classical rendering path on CJK characters (when moving cursor over wide Chinese text to the left/right). I kept classical path as-is, but liga path should render these correctly in all cases. ![image](/attachments/36973354-32e2-4225-ad2b-29bf7f4cc6dd)
barsmonster force-pushed master from 551ac8eeb2
Some checks reported errors
builds.sr.ht/freebsd-x64 Job failed
to b8fffd772c
Some checks reported errors
builds.sr.ht/freebsd-x64 Job failed
2026年02月14日 06:24:30 +01:00
Compare
barsmonster force-pushed master from b8fffd772c
Some checks reported errors
builds.sr.ht/freebsd-x64 Job failed
to 8b33b2c4ad
All checks were successful
builds.sr.ht/freebsd-x64 Job completed
2026年02月14日 06:36:29 +01:00
Compare
Author
First-time contributor
Copy link

Had to rename forked branch to "foot" to make CI pass (folder name hardcoded) + forward declaration in header fix.

Had to rename forked branch to "foot" to make CI pass (folder name hardcoded) + forward declaration in header fix.
Author
First-time contributor
Copy link

Performance benchmark on DOOM fire:
Stock foot: 505fps
Foot+ligatures enabled: 499fps

Performance benchmark on DOOM fire: Stock foot: 505fps Foot+ligatures enabled: 499fps
First-time contributor
Copy link

Hi, just tested your PR and it is works well for me!

Context-aware ligatures do not seem to be rendered correctly, though.
I attach an example with Fira Code, but I tested also with Maple Code. The differences appear from the line 0x123.

Thanks!

Hi, just tested your PR and it is works well for me! Context-aware ligatures do not seem to be rendered correctly, though. I attach an example with Fira Code, but I tested also with Maple Code. The differences appear from the line `0x123`. Thanks!
barsmonster force-pushed master from 8b33b2c4ad
All checks were successful
builds.sr.ht/freebsd-x64 Job completed
to 373226852f
Some checks reported errors
builds.sr.ht/freebsd-x64 Job failed
2026年02月21日 13:02:43 +01:00
Compare
Author
First-time contributor
Copy link

Context-aware ligatures do not seem to be rendered correctly, though. I attach an example with Fira Code, but I tested also with Maple Code. The differences appear from the line 0x123.

Thanks, good catch! Original patch was working with what is available at fcft library, and it was no longer possible to stay as is.
I've submitted PR to fcft dnkl/fcft#116 and with it - ligature rendering code gets much simpler, and context-aware ligatures are now working.

> Context-aware ligatures do not seem to be rendered correctly, though. I attach an example with Fira Code, but I tested also with Maple Code. The differences appear from the line `0x123`. Thanks, good catch! Original patch was working with what is available at fcft library, and it was no longer possible to stay as is. I've submitted PR to fcft https://codeberg.org/dnkl/fcft/pulls/116 and with it - ligature rendering code gets much simpler, and context-aware ligatures are now working.
barsmonster force-pushed master from 373226852f
Some checks reported errors
builds.sr.ht/freebsd-x64 Job failed
to bbee6f51b1
Some checks reported errors
builds.sr.ht/freebsd-x64 Job failed
2026年02月21日 13:20:26 +01:00
Compare
First-time contributor
Copy link

This is so great! Many thanks for your work!
Looking forward for both PRs to be merged, since they do not change the default behaviour and performance (and have really minor impact on the latter when ligatures are enabled).

This is so great! Many thanks for your work! Looking forward for both PRs to be merged, since they do not change the default behaviour and performance (and have really minor impact on the latter when ligatures are enabled).
barsmonster force-pushed master from bbee6f51b1
Some checks reported errors
builds.sr.ht/freebsd-x64 Job failed
to 33a87696cb
Some checks reported errors
builds.sr.ht/freebsd-x64 Job failed
2026年02月21日 13:34:25 +01:00
Compare
barsmonster force-pushed master from 33a87696cb
Some checks reported errors
builds.sr.ht/freebsd-x64 Job failed
to 5a7ca8255d
Some checks reported errors
builds.sr.ht/freebsd-x64 Job failed
2026年02月21日 13:36:24 +01:00
Compare
barsmonster force-pushed master from 5a7ca8255d
Some checks reported errors
builds.sr.ht/freebsd-x64 Job failed
to 15d3f87a73
Some checks reported errors
builds.sr.ht/freebsd-x64 Job failed
2026年02月21日 13:45:24 +01:00
Compare
Author
First-time contributor
Copy link

Ok, now there should be no unrelated changes remaining. Build will stop failing when dnkl/fcft#116 is merged.
Retested performance - unchanged, within 1% of pre-patch performance.

Ok, now there should be no unrelated changes remaining. Build will stop failing when https://codeberg.org/dnkl/fcft/pulls/116 is merged. Retested performance - unchanged, within 1% of pre-patch performance.
barsmonster force-pushed master from 15d3f87a73
Some checks reported errors
builds.sr.ht/freebsd-x64 Job failed
to 0afc64a3cb
Some checks reported errors
builds.sr.ht/freebsd-x64 Job failed
2026年02月23日 12:14:04 +01:00
Compare
barsmonster force-pushed master from 0afc64a3cb
Some checks reported errors
builds.sr.ht/freebsd-x64 Job failed
to 65f1edb044
Some checks reported errors
builds.sr.ht/freebsd-x64 Job failed
ci/woodpecker/pull_request_metadata/woodpecker Pipeline is pending approval
2026年02月23日 12:16:01 +01:00
Compare
Author
First-time contributor
Copy link

Last change fixes character rendering order to be right->left in ligature mode, like in original code - which is required for NerdFont rendering without clipping.

NerdFont icons are still decentered (but exactly the same as before ligature patch), it's one hell of a rabbit hole. No easy way to render them centered without major hacks - current foot rendering renders them "as font creators envisioned".

Last change fixes character rendering order to be right->left in ligature mode, like in original code - which is required for NerdFont rendering without clipping. NerdFont icons are still decentered (but exactly the same as before ligature patch), it's one hell of a rabbit hole. No easy way to render them centered without major hacks - current foot rendering renders them "as font creators envisioned".
First-time contributor
Copy link

This patch causes my terminal to crash when using cat on binary files. I should probably use strings instead.. But this is a still a regression.

This patch causes my terminal to crash when using `cat` on binary files. I should probably use strings instead.. But this is a still a regression.
Author
First-time contributor
Copy link

@alliumm wrote in #2278 (comment):

This patch causes my terminal to crash when using cat on binary files. I should probably use strings instead.. But this is a still a regression.

Nice catch, I like /dev/random test for terminals )

With ligatures=yes - issue is confirmed, and now fixed with updated fix in fcft (dnkl/fcft#116 (comment)). Now It can render /dev/random for extended time.
With ligatures=no it was not crashing, as it was using original code path.

@alliumm wrote in https://codeberg.org/dnkl/foot/pulls/2278#issuecomment-11025695: > This patch causes my terminal to crash when using `cat` on binary files. I should probably use strings instead.. But this is a still a regression. Nice catch, I like /dev/random test for terminals ) With ligatures=yes - issue is confirmed, and now fixed with updated fix in fcft (https://codeberg.org/dnkl/fcft/pulls/116#issuecomment-11052791). Now It can render /dev/random for extended time. With ligatures=no it was not crashing, as it was using original code path.
First-time contributor
Copy link

Have been running this without issue for a couple days now, thank you so much for this! For anyone stuck trying to build (also seemingly reason for ci failing). Make sure you change the subprojects/fcft.wrap file to point to the fork made by @barsmonster .

Have been running this without issue for a couple days now, thank you so much for this! For anyone stuck trying to build (also seemingly reason for ci failing). Make sure you change the `subprojects/fcft.wrap` file to point to the fork made by @barsmonster .
First-time contributor
Copy link

When is this expected to be merged?

When is this expected to be merged?
Author
First-time contributor
Copy link

@ManbirJudge wrote in #2278 (comment):

When is this expected to be merged?

It might not get merged : Foot was designed to be simple and fast, and this code is quite complex and does cause small performance hit (although barely above noise level on my tests).

What will help to increase the chances - is to build it yourself, throw everything at it and find where it breaks (slow performance, incorrect rendering e.t.c).

At the end, if current code will be considered too complex, it can be simplified by always rendering in Ligature mode - but this has to be tested extremely well.

@ManbirJudge wrote in https://codeberg.org/dnkl/foot/pulls/2278#issuecomment-11540857: > When is this expected to be merged? It might not get merged : Foot was designed to be simple and fast, and this code is quite complex and does cause small performance hit (although barely above noise level on my tests). What will help to increase the chances - is to build it yourself, throw everything at it and find where it breaks (slow performance, incorrect rendering e.t.c). At the end, if current code will be considered too complex, it can be simplified by always rendering in Ligature mode - but this has to be tested extremely well.
First-time contributor
Copy link

The only issue I've found is that ligatures don't update when deleting characters. They stay in their previous state until they are updated by an adjacent character.

The only issue I've found is that ligatures don't update when deleting characters. They stay in their previous state until they are updated by an adjacent character.
barsmonster force-pushed master from 65f1edb044
Some checks reported errors
builds.sr.ht/freebsd-x64 Job failed
ci/woodpecker/pull_request_metadata/woodpecker Pipeline is pending approval
to ade745f303
Some checks reported errors
ci/woodpecker/pr/woodpecker Pipeline is pending approval
builds.sr.ht/freebsd-x64 Job failed
2026年03月31日 13:34:15 +02:00
Compare
Author
First-time contributor
Copy link

@alliumm wrote in #2278 (comment):

re updated by an adjacent character.

Please re-test, it now re-renders broken ligatures. It also forced to re-render few characters for changed non-ligature runs (as we do not store what is ligature and what is not), but no measurable performance degradation.

@alliumm wrote in https://codeberg.org/dnkl/foot/pulls/2278#issuecomment-11826462: > re updated by an adjacent character. Please re-test, it now re-renders broken ligatures. It also forced to re-render few characters for changed non-ligature runs (as we do not store what is ligature and what is not), but no measurable performance degradation.
First-time contributor
Copy link

@barsmonster wrote in #2278 (comment):

Please re-test

That seems to have fixed it. Thanks for all the work you've been putting in to this! <3

@barsmonster wrote in https://codeberg.org/dnkl/foot/pulls/2278#issuecomment-12472497: > Please re-test That seems to have fixed it. Thanks for all the work you've been putting in to this! <3
First-time contributor
Copy link

That seems to have introduced a new issue. This one is hard to explain so I attached a recording of it happening. I type .., which renders as a ligature, but then when i type / after it the .. stops rendering as a ligature.

That seems to have introduced a new issue. This one is hard to explain so I attached a recording of it happening. I type `..`, which renders as a ligature, but then when i type `/` after it the `..` stops rendering as a ligature.
First-time contributor
Copy link

Interesting, I don't see that. For me it works as expected. Could it be a font issue? I tested with Fira Code and Maple Mono.

Interesting, I don't see that. For me it works as expected. Could it be a font issue? I tested with `Fira Code` and `Maple Mono`.
Author
First-time contributor
Copy link

@alliumm wrote in #2278 (comment):

That seems to have introduced a new issue. This one is hard to explain so I attached a recording of it happening. I type .., which renders as a ligature, but then when i type / after it the .. stops rendering as a ligature.

Which font are you using? (with link preferably). Preliminarily, I do not see the issue, but ligatures in fonts might have unexpected limitations (i.e. intended - by font designers - to work only in specific context)

@alliumm wrote in https://codeberg.org/dnkl/foot/pulls/2278#issuecomment-12533445: > That seems to have introduced a new issue. This one is hard to explain so I attached a recording of it happening. I type `..`, which renders as a ligature, but then when i type `/` after it the `..` stops rendering as a ligature. Which font are you using? (with link preferably). Preliminarily, I do not see the issue, but ligatures in fonts might have unexpected limitations (i.e. intended - by font designers - to work only in specific context)
First-time contributor
Copy link

That recording is using Maple Mono (specifically https://github.com/subframe7536/maple-font/releases/download/v7.9/MapleMono-NF-unhinted.zip).

I actually just tried again and can't reproduce it either. This never happened to me on the previous revision though, and I've been daily driving that the whole time. I will try to find a reliable reproducer if I encounter this again.

That recording is using Maple Mono (specifically https://github.com/subframe7536/maple-font/releases/download/v7.9/MapleMono-NF-unhinted.zip). I actually just tried again and can't reproduce it either. This never happened to me on the previous revision though, and I've been daily driving that the whole time. I will try to find a reliable reproducer if I encounter this again.
Author
First-time contributor
Copy link

I did some more stress testing by launching foot cat /dev/random in 8 instances in parallel (on Ubuntu 25.10) with the intention to leave it crunching overnight. Unfortunately, in this case gnome-shell leaks memory, and after 113Gb it was clear it will not survive even 1 hour. But all 8 ligature foot's were all stable with no memory leaks. image

I did some more stress testing by launching foot cat /dev/random in 8 instances in parallel (on Ubuntu 25.10) with the intention to leave it crunching overnight. Unfortunately, in this case gnome-shell leaks memory, and after 113Gb it was clear it will not survive even 1 hour. But all 8 ligature foot's were all stable with no memory leaks. ![image](/attachments/7c9f9c20-be45-4770-9333-83c5526ccc40)
barsmonster force-pushed master from ade745f303
Some checks reported errors
ci/woodpecker/pr/woodpecker Pipeline is pending approval
builds.sr.ht/freebsd-x64 Job failed
to 4858b3db7b
Some checks reported errors
ci/woodpecker/pr/woodpecker Pipeline is pending approval
builds.sr.ht/freebsd-x64 Job failed
2026年05月21日 17:52:14 +02:00
Compare
Author
First-time contributor
Copy link

Rebased, url rendering retested with and without ligature rendering.

Rebased, url rendering retested with and without ligature rendering.
First-time contributor
Copy link

I thought dnkl didn't want to support ligatures in foot? #57 (comment)

I thought dnkl didn't want to support ligatures in foot? https://codeberg.org/dnkl/foot/issues/57#issuecomment-6018901
Author
First-time contributor
Copy link

@chjlsch wrote in #2278 (comment):

I thought dnkl didn't want to support ligatures in foot? #57 (comment)

Yes, this is historically known. But now we have a working patch used by more and more people with no issues.
Main reasoning against ligatures is code complexity. Complexly increase will be minimized if we keep ligature-only path instead of maintaining both modes.

@chjlsch wrote in https://codeberg.org/dnkl/foot/pulls/2278#issuecomment-16607165: > I thought dnkl didn't want to support ligatures in foot? #57 (comment) Yes, this is historically known. But now we have a working patch used by more and more people with no issues. Main reasoning against ligatures is code complexity. Complexly increase will be minimized if we keep ligature-only path instead of maintaining both modes.
Some checks reported errors
ci/woodpecker/pr/woodpecker Pipeline is pending approval
builds.sr.ht/freebsd-x64 Job failed
This pull request can be merged automatically.
This branch is out-of-date with the base branch
You are not authorized to merge this pull request.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u master:barsmonster-master
git switch barsmonster-master

Merge

Merge the changes and update on Forgejo.
git switch master
git merge --no-ff barsmonster-master
git switch barsmonster-master
git rebase master
git switch master
git merge --ff-only barsmonster-master
git switch barsmonster-master
git rebase master
git switch master
git merge --no-ff barsmonster-master
git switch master
git merge --squash barsmonster-master
git switch master
git merge --ff-only barsmonster-master
git switch master
git merge barsmonster-master
git push origin master
Sign in to join this conversation.
No reviewers
Milestone
Clear milestone
No items
No milestone
Projects
Clear projects
No items
No project
Assignees
Clear assignees
No assignees
6 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
dnkl/foot!2278
Reference in a new issue
dnkl/foot
No description provided.
Delete branch "barsmonster/foot:master"

Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?