Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

display NerdFont icons without clipping (if followed by space) #1104

Closed
johnnovak opened this issue May 22, 2021 · 30 comments
Closed

display NerdFont icons without clipping (if followed by space) #1104

johnnovak opened this issue May 22, 2021 · 30 comments

Comments

@johnnovak
Copy link

Summary

This issue is about special NerdFont icons not being displayed correctly in mintty (and wsltty), which was raised a while ago in #979. However, I'm quite certain the root cause of the issue was not understood properly (neither by the raiser of the ticket, nor the maintainer), therefore a proper fix was never proposed or implemented.

As we'll see, once we understand the rationale behind these fonts and how they are displayed correctly in practically all other terminal emulators, the fix is quite simple.

Background

NerdFont icons are meant to be used in terminals and text editors that operate on a cell-grid of fixed-width monospaced characters (e.g. Vim). Most of these icons (but not all of them) are wider than the width of a single monospaced character. But (and this is crucial!) the cursor must be advanced only by the width of a single monospaced glyph after outputting such a special glyph, otherwise the output will be garbled in full-screen text editors!

To make things work correctly, NerdFonts employ a simple trick: the x-advance of these wide (usually 2-cells wide) glyphs is set to the width of a single monospaced glyph, while the glyph is being overdrawn into the next cell's area. Therefore the cursor is always advanced by a single monospaced character, which is a must for full-screen text editors such as Vim.

Most terminal apps let such glyphs be overdraw into the next cell without problems
nerdfont-test.txt
. The application that uses these special glyphs knows how to use them: it just needs to always put a space after these special glyphs, and that's it.

Current behaviour

First, we'll demonstrate how other terminal apps are handling these fonts correctly (just on Windows, but the situation is the same on OS X and many Linux terminal emulators).

We'll use the font Literation Mono Nerd Font Complete Windows Compatible for the investigation (WARNING: it's important to use this specific font, not the "Complete Mono" variants! Those are different as they always scale the icons proportionally down into the width of a single cell).

Windows Terminal (Windows 10, latest)

This is Vim with two split-views open:

  • to the left a tree-based file manager that uses NerdFont icons
  • to the right an example that clearly illustrates the usage of these glyphs

Note in the right example that if we put special wide glyphs in cells next to each other, they will overlap (which is expected). So the correct usage of these glyphs is to always put a space between them. The key thing is that the glyphs are not cropped to their containing cells; they're overdrawn into the next cell.

The font works as intended in Windows Terminal without any special config tweaking.

windows-terminal

Alacritty (Windows 10)

Same example in Alacritty. The font works as intended in Alacritty without any special config tweaking.

alacritty

mintty - attempt 1

In mintty, the icons are squashed horizontally by just setting the font. This is fixed by setting CharNarrowing to 100.

Font=LiterationMono NF
CharNarrowing=100

This fixes the horizontal squashing, but now the problem is that the glyphs are always clipped to their respective cells! If they would be allowed to be overdrawn into the cell to the right, everything would be perfect!

Results are exactly the same when defining the special font a secondary font:

Font=Liberation Mono
Font2=LiterationMono NF
FontChoice=Private:2
CharNarrowing=100

wsltty

Result are exactly the same with

mintty - attempt 2

Setting Charwidth=ambig-wide doesn't remedy the problem--in fact, it makes it a lot worse! As explained, the cursor must be advanced by the width of a single monospaced glyph after using these icons, otherwise the cell-grid of the fullscreen terminal app will be garbled.

The issue can be very clearly seen in the right example: the icons are wider than the width of a single cell, so the output is completely garbled (they don't line up with the "123456" rules anymore). This is not how these fonts were meant to be used.

But this is not a problem of the Charwidth=ambig-wide; by definition it is doing what it is supposed to do and it is not the right tool to solve this problem. I just showed it here for completeness because in #979 it was suggested that ambig-wide would solve this. No, it cannot do that; these glyphs should just be let to be overdrawn into the next cell; they should not be promoted to wider-than-a-single-cell glyphs!

wsltty-ambig-wide

wsltty-ambig-wide-2

Proposed solution

As most other terminal emulators on Windows and other platforms are handling these fonts as they were intended without any special tweaking required, the first proposed solution would be to just simply always let glyphs be overdrawn into the next cell on the right, and always honor the x-advance setting (so don't try to be clever and calculate the glyph width or something). This simply just means the cell to the right shouldn't be cleared first; if glyphs are overdrawn and overlapping, so be it (incidentally, this would also help with overdrawn italic characters).

If for some reason it is problematic to enable overdrawing all the time for all glyphs (although it seems like this is exactly what other terminal emulators are doing), maybe introduce an option to only enable this behaviour for some specific override fonts. For example:

Font=Liberation Mono
Font2=LiterationMono NF
Font2Overdraw=true
FontChoice=Private:2
CharNarrowing=100

(maybe CharNarrowing and FontChoice would be redundant too overdrawing is set to true)

@johnnovak
Copy link
Author

Also, here's the text file shown on the right-side example:

nerdfont-test.txt

@mintty
Copy link
Owner

mintty commented May 22, 2021

First, we agree about the x-advance width. That is a good point to start with.
Thanks for the detailed analysis which I'll look into.
I think as a first step it would be necessary to define a set of character ranges with "wide appearance", as not all Private Use characters appear to be wide; I think I stated that before somewhere. And that could also comprise some non-Private emoji symbols perhaps.
Then, for those, automatic char-narrowing could be disabled and overdraw rendering be applied.

@johnnovak
Copy link
Author

johnnovak commented May 22, 2021

Thanks for the detailed analysis which I'll look into.

Thanks for looking into the issue. mintty is pretty much perfect for me, except for this one last missing feature :)

I think as a first step it would be necessary to define a set of character ranges with "wide appearance", as not all Private Use characters appear to be wide; I think I stated that before somewhere. And that could also comprise some non-Private emoji symbols perhaps.

Sounds like a good plan. Probably this is the most flexible approach to be able to specify comma separated code point ranges in a configuration property (e.g. F0000-FFFFD,100000-10FFFD).

Please let me know if I can help with anything else to make this happen.

@mintty
Copy link
Owner

mintty commented Jun 10, 2021

Implementation for Private Use and other Symbol ranges was actually easy, but I wanted to extend the overhang mechanism to emojis on this occasion (which turned out to be more tricky).

@johnnovak
Copy link
Author

johnnovak commented Jun 11, 2021

Excellent news. Looks like you hardcoded the code point ranges, so you decided there will be no mechanism to specify user ranges? Do the hardcoded ranges actually cover the NerdFonts symbol range? (I know the feature shouldn't be dictated by one specific font, but maybe we'd need support for user defines ranges too, after all?)

@mintty
Copy link
Owner

mintty commented Jun 11, 2021

Private Use ranges are included, and also symbol ranges except those that have a well-determined shape, like box characters etc.

@johnnovak
Copy link
Author

Private Use ranges are included, and also symbol ranges except those that have a well-determined shape, like box characters etc.

Ah okay, that sounds good. Thanks again for this. Can't wait for the next release that will contain this improvement!

@mintty
Copy link
Owner

mintty commented Jun 12, 2021

Can't wait for the next release

You can easily build your own mintty from the repository, for testing. You just need cygwin packages gcc and make.

@johnnovak
Copy link
Author

Can't wait for the next release

You can easily build your own mintty from the repository, for testing. You just need cygwin packages gcc and make.

Giving it a go now.

@johnnovak
Copy link
Author

Okay, this is the results I got when replacing mintty.exe in my cygwin installation. When I tried replacing the exe for wsltty, that resulted in a crash, so could only test with cygwin.

Test 1 - Default CharNarrowing

default-char-narrowing

I think this is what I would expect in this case; "wide" characters are only overdrawn when there is a whitespace after them. Otherwise, if there's no space, they're scaled.

Test 2 - CharNarrowing=100

char-narrowing-100

With CharNarrowing=100, things get somewhat interesting. I think what's happening here is that "wide" characters are always overdrawn, regardless of whether there is a whitespace after them or not. I think the original idea was to clip them if there's no whitespace after them, no?

Note that on the left side the icons lining up with the "number 5 column" are actually being clipped, but only that single column of icons. This is most obvious when looking at the the folder and cog icons.

Test 3 - Moving the cursor in vim - Default CharNarrowing

vim-default-char-narrowing

In this test I'm using vim 8.2 that comes with cygwin by default (vi command), and I'm just moving the cursor around.

Note that the results are different from Test 1; after opening the file (cursor in the top-left corner), wide characters with a space after them are not expanded! But if I move the cursor over characters that have a space after them, from left to right (this is important), then they get expanded.

Test 4 - Moving the cursor in vim - CharNarrowing=100

vim-char-narrowing-100

The results are even weirded with CharNarrowing=100. After opening the file ((cursor in the top-left corner), the results are very similar to Test 2, the only difference is that the icons in the "number 5 column" are not clipped either (but as I mentioned there, I thought the behaviour in this case should be to clip all wide chars that are not followed by a space).

But when moving the cursor around, when going from right to left, from a space character to a "wide" character, the wide character always gets clipped when we're doing this at the end of the line. Then it also happens sometimes when we're in the middle of the line, going from right to left.

To add more confusion, the clipping sometimes happens when just moving the cursor from left to right (as you can see when I'm moving the cursor from left to right on the left side of the screen). But that is the "good clipping", the one I was expecting in the first place (on wide chars not followed by a space).


So, overall, what we have here is definitely a big improvement over the previous behaviour. And usually these characters are used for informational display only in non-editable areas where you don't move the cursor around (e.g. a file tree); in those case the displaying of them just works fine with what we have now (with CharNarrowing=100).

But looking at my cursor movement tests, something strange and slighlty random (?) is going on. It would be good to at least understand the issue, and maybe also provide a fix that makes the behaviour a bit more predictable, at the very least.

I hope I made the issue clear in my screencaps and my descriptions (I tried to explain it as precisely as I can). If you would like to test it yourself, I have attached the file I used in my tests. For the vim tests, just use the vi command that comes with cygwin by default.

And again, make sure to use the Literation Mono Nerd Font Complete Windows Compatible.ttf font for testing (there's another variant which has the word "Mono" twice in the name; that's not the one we need to use here!). That font will not show up in the mintty font selection GUI; you just have to manually edit the config file and use Font=LiterationMono NF.

Thanks again for your work, this is really great results so far and almost perfect. I'm just trying to help to get the last 10-20% of the feature right! 😄

Test file: nerdfont-test.txt

@mintty
Copy link
Owner

mintty commented Jun 13, 2021

Thank you very much for clear and concise testing.

About Test 2:
Overdrawn characters are written last in the line, from right to left (like italics) in order to accomplish the overlap. That's why column 5 is clipped when column 6 is written.
It's the purpose of the CharNarrowing feature to avoid such clipping, so if you disable it (=100), you'll get some artefacts.
Actually, for emojis, the right-to-left trick did not work so I applied a different mechanism there. I think I could also drop the right-to-left painting for non-emoji symbols now, with small value for the effort however.

About Test 3:
Analysing the actual output of vi reveals that it writes the  characters separately, each preceded by a cursor placement (unlike normal text which is written as one adjacent string). When moving the cursor right, it uses relative cursor positioning and final adds a space after column 6 explicitly, for whatever purpose. That explains the behaviour.
I deliberately distinguished explicit space from clear space (a distinction mintty already had) for this feature, on the assumption it would be good design to allow overdrawing only if the application has provided "space" for it explicitly.
Dropping this distinction, on the other hand, would make display more stable in such cases. You can easily test it by changing just line 2495 of term.c as follows:
#define IGNOVRHANG (FONTFAM_MASK | TATTR_CLEAR)

@mintty
Copy link
Owner

mintty commented Jun 13, 2021

You can make the kind of background (space, tab, clear) visible by configuration:
DispSpace=7
DispClear=14
DispTab=6

Also, no need to run the whole terminal in nerd font for nerd symbols; I use this:
FontChoice=Private:2
Font2=LiterationMono NF

@johnnovak
Copy link
Author

Thanks for the quick reply and the suggestions, I'll try them tomorrow.

@johnnovak
Copy link
Author

johnnovak commented Jun 15, 2021

I've had time to give this some more testing today, and I'm happy to report that there is a big improvement after applying the #define IGNOVRHANG (FONTFAM_MASK | TATTR_CLEAR) change.

With default CharNarrowing, the display is now 100% stable when moving the cursor, both in Vim and NeoVim.

With CharNarrowing=100, the clipping is flip-flopping a little bit, but I'd say this is a non-issue as those special NerdFont characters are meant to be used with a space after them.

It seems to me that dropping the explicit vs clear space distinction would make a big improvement in potentially all TUI interfaces where you can move the cursor around. I personally would definitely turn the distinction off, if it was an option. Now, the question is whether this should be an option at all, or should dropping the distinction just be hardcoded.

Up to you, but with "no distinction" the feature seems to be perfect now as far as I'm concerned 👍🏻

@mintty
Copy link
Owner

mintty commented Jun 23, 2021

I've tweaked the feature and fixed some interworking cases.
I had also tried to not apply the overhang feature at the cursor position, on the assumption that you may like to have a clearer view on the individual character then (something that is done with ligatures), but the resulting behaviour when moving the cursor over it is not really convincing; also it didn't work with emojis.
If you'd like to try that variant, uncomment line 2521 in term.c:

       && !at_cursor_pos(i, j)

@johnnovak
Copy link
Author

I've tweaked the feature and fixed some interworking cases.
I had also tried to not apply the overhang feature at the cursor position, on the assumption that you may like to have a clearer view on the individual character then (something that is done with ligatures), but the resulting behaviour when moving the cursor over it is not really convincing; also it didn't work with emojis.
If you'd like to try that variant, uncomment line 2521 in term.c:

       && !at_cursor_pos(i, j)

Sure, I'll do that today.

@johnnovak
Copy link
Author

johnnovak commented Jun 29, 2021

Sorry for the delay. I played around with it a bit and I definitely prefer the version with && !at_cursor_pos(i, j) commented out (so the current state of master). That way the behaviour is pretty much perfect in my tests.

When uncommented, the behaviour is a bit weird and not very useful, as you said. Plus because of the other problems you mentioned, I don't think it's worth it at all.

@mintty mintty changed the title No option to display NerdFont icons correctly as intended without clipping display NerdFont icons without clipping (if followed by space) Jul 23, 2021
@litesam
Copy link

litesam commented Aug 20, 2021

mintty/wsltty#257 (comment) what's the fix for this, (sorry for posting it in wrong issue thread)

@mintty
Copy link
Owner

mintty commented Aug 20, 2021

fix for this

For what? Which characters did you output?

@litesam
Copy link

litesam commented Aug 20, 2021

fix for this

For what? Which characters did you output?

the -> from the oh-my-zsh default theme. The arrow point is cut at the front.

@BrianInglis
Copy link

BrianInglis commented Aug 20, 2021

When you ask questions or raise issues about software you use, it is your responsibility to report the issue in a manner that can be tested and possibly reproduced without the (possibly defective other) software you use, using only the software from the project to which you are making the report, including all required information in your report, not expecting others to flip between issues to try to piece together the information, and possibly make incorrect assumptions from the pieces.
In this issue, are you displaying glyphs using NerdFonts, or are you using Cascadia Mono as in your other report?
So what character set are you using (e.g. UTF-8?), what characters and/or code points are you displaying (e.g. -><SP>~?), or if -> are not the actual characters but a representation of a single arrow character you are displaying, what is that character's code point (there are a lot of UTF-8 arrow characters), and which are not displaying correctly?
If your issue is not about descenders and not about NerdFonts, you should really create a new issue, and mention any similarity to previous issues.

@mintty
Copy link
Owner

mintty commented Aug 29, 2021

@johnnovak, I think there are a few emojis that should be exempted from this handling, so I'll restrict it for them;
thinking particularly of numeric emojis and country/regional indicator symbols that would look strange if a single or the last of them were wider:
#️0️9️

@BrianInglis
Copy link

Please validate as much as possible - some enclosed symbols require more space than you allow (using current Cygwin+mintty, xterm-256color, DejaVu Sans Mono 9, UTF-8/Emoji placement stretch): web rendering with same font is not clipped; some of these wide symbols are clipped in mintty, and some may be further restricted with changes: for examples, please see attached enclosed-symbols.log

@mintty
Copy link
Owner

mintty commented Aug 29, 2021

The exemption actually covers only numeric symbols in the ASCII range (plus those country indicators) which should suit your concern.

@BrianInglis
Copy link

Thank you; but still please have a look at the mintty renderings of characters in the attachment for the issues mentioned.
Having now been sufficiently l14ned and i18ned in Unicode, I expect any of the (soon) 65 sets of ten DIGIT characters having Nd Numeric digit property may be impacted, and possibly also any of the NUMBER or NUMERIC characters without explicit exemptions! ;^>

@johnnovak
Copy link
Author

johnnovak commented Aug 30, 2021

@johnnovak, I think there are a few emojis that should be exempted from this handling, so I'll restrict it for them;
thinking particularly of numeric emojis and country/regional indicator symbols that would look strange if a single or the last of them were wider:
#️0️9️

Fine by me, as long as it won't screw up the rendering in Vim :) Let me know when there's something new to retest.

@mintty
Copy link
Owner

mintty commented Aug 30, 2021

sets of digit characters

Most of them do not have emojis. This issue is only about emoji rendering.
ASCII digits have emojis. They are optionally rendered if you either append U+FE0F to them or use CSI 51 in mintty.

@mintty
Copy link
Owner

mintty commented Aug 30, 2021

This issue is only about emoji rendering.

This was not correct. Anyway, most digit ranges are unaffected.

@mintty
Copy link
Owner

mintty commented Sep 3, 2021

Released 3.5.1.

@mintty mintty closed this as completed Sep 3, 2021
mintty added a commit that referenced this issue Apr 19, 2024
mark emoji overhang in subsequent character position
not only if that is a space but also with setting EmojiPlacement=full (#1261);
drop exemptions (for numbers and flag letters) from emoji space expansion (#1104, 3.5.1)
@mintty
Copy link
Owner

mintty commented Apr 19, 2024

Enabled emoji space expansion for numbers and flag letters (dropped their exemption) to support fixing of #1261.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants