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

Curly and colored underlines #1145

Closed
egmontkob opened this issue Dec 12, 2017 · 22 comments · Fixed by #3921
Closed

Curly and colored underlines #1145

egmontkob opened this issue Dec 12, 2017 · 22 comments · Fixed by #3921
Assignees
Milestone

Comments

@egmontkob
Copy link

egmontkob commented Dec 12, 2017

This is originally a feature of Kitty, now also adopted by VTE (GNOME Terminal and friends). Technically two separate features, but they mostly make sense together, e.g. for spell checking.

Apparently vim and neovim have already / are about to support these, see e.g. vim undercurl, vim color, neovim.


The new SGR 4:3 (\e[4:3m) attribute, strictly with a colon as separator, was introduced to start a curly underline.

In the mean time, 4:0, 4:1 and 4:2 were also added as aliases for the standard 24 (turn off all kinds of underlining), 4 (single underline) and 21 (double underline), respectively.

At some point in the future, probably 4:4 and 4:5 could also stand for dotted and dashed underlines in some order (these are the five types of underlining supported by HTML/CSS).


The new SGR 58 and 59 sequences specify the color of the underline, following the pattern of 38 and 39. That is, 58;5;idx for an entry of the 256-color palette, or 58;2;r;g;b for direct RGB. There's no shortcut notation for the first 16 entries (corresponding to SGR 30-37 and 90-97), use the 256-color mode with indices of 0-15 instead.

59 reverts to the default, that is, the underline's color auto-following the text color.

In case you're short of bits, I believe it's okay to drop some precision, e.g. store only 4 bits per color channel. We were also considering this in the VTE bug.

Update: In VTE we ended up approximating the truecolor underline to 4+5+4 bits (R, G, B respectively). This means: 8+8+8+1 = 25 bits for foreground (in addition to truecolor, we have to be able to denote palette and default values too). Same for background. 4+5+4+1 = 14 bits for underline (again, an extra bit to denote palette and default values). That's a total of 64 bits for the colors, stored in an int64 (bold, italic etc. are additional bits are next to this in another variable). With multiplication instead of bitpacking, it could have been 5+5+5 bits for the underline. Of couse it's not a necessity to put all the color and no other attributes in the same integer, we just decided it's nice this way (and results in some performance improvement).

@Tyriar Tyriar added type/enhancement Features or improvements to existing features and removed type/feature labels Apr 4, 2018
@egmontkob
Copy link
Author

(As per kovidgoyal/kitty#226 (comment), mintty decided that 4:4 is dotted underline, and 4:5 is dashed.)

@Tyriar
Copy link
Member

Tyriar commented Oct 7, 2019

If someone wants to try put a PR together for this you can search the repo for "underline" to give you an idea of what has to happen. Basically support the escape sequence, enable storing it in the buffer and then support drawing it using the renderers.

@jwhitley
Copy link

jwhitley commented Mar 5, 2020

As an aside, I note that both neovim and tmux now fully support undercurl in current releases as of this writing. It's pretty great when used with coc.nvim for LSP support, plus the appropriate undercurl highlight definitions.

@jerch
Copy link
Member

jerch commented Mar 5, 2020

We should implement this, its very useful for editors, debuggers etc. 😸
Any takers?

There are 3 gems hidden here though:

  • extend sequence handlers to support those new sub parameters - easy one
  • extend the buffer to hold needed information - tricky one, we have currently only 5 spare bits per cell (in total, not per channel lol), we either need to extend the buffer by another uint32 (increases memory usage from 12 to 16 bytes per cell) or some other trick to store that information.
  • extend renderers to support those new line styles - not sure about the complexity of this one, the self drawing renderers (canvas, webgl) have different constraints than the DOM renderer. When I played with text-underline in different browsers the other day it was a nightmare - the support for this is mostly non-existent. The DOM renderer might be the hard part here.

@egmontkob
Copy link
Author

the support for this is mostly non-existent

Really? Even two years ago, when I played with it, Firefox and Chromium supported curly and colored underlines.

(Chromium's one is much uglier, though. It can only draw entire periods, the last, partial period of the curly underline is completely omitted. It also stops and restart the curly underline on every DOM element boundary.)

Maybe even for browsers you could resort to some background image-like drawing? :)

Re bits: I've updated the original post.

@jerch
Copy link
Member

jerch commented Mar 6, 2020

@egmontkob

Really? Even two years ago, when I played with it, Firefox and Chromium supported curly and colored underlines.

Yeah, well its a mixed picture. I tested it with dashed/dotted, which leads to really ugly output or no underlines at all. Support for curly might be better. This needs some testing beforehand, still my guess is that we will have to resort to "drawing-by-hand" here to avoid box borders artefacts and such.

Re bits: I've updated the original post.

Thx for clarification. Yeah the attribute storage is an issue of its own, I tested several different approaches during the buffer rework. My favorite approach, storing them in a separate RB tree, led to lousy performance due to the needed additional tree lookup, while storing them in continous memory in the cells array itself is much much faster. Memory sacrifice for performance, as always. But with the underlines I wonder, if we should go that path again for a simple reason - underlines are very rare, thus the memory is wasted most of the time. Therefore a bit-notion in the cell and the additional lookup into a foreign storage might be sufficient here.

Edit: I wonder if the reduced underline palettes in VTE will create you color matching issues later on - the colors will slightly differ due to the reduced resolution, thus ppl cannot use FG/BG coloring tricks to temporarily hide those lines. Unlikely and rather an edge case, I know, but still a valid assumption from the sequence definitions.

@jwhitley
Copy link

jwhitley commented Mar 7, 2020

FYI, there's a Chromium bug related to fixing the really poor rendering of text-decoration: underline wavy. That was blocked by the advent of "LayoutNG", which has apparently now landed.

So theoretically, it might be possible to avoid a custom renderer (for Chrome*, at least) if someone could be prodded into fixing that issue.

A cursory check via MDN's text-decoration page in FF vs Chrome shows that Firefox's wavy underline rendering is way better than Chromium's. Does FF also exhibit box boundary problems?

@jwhitley
Copy link

jwhitley commented Mar 7, 2020

So theoretically, it might be possible to avoid a custom renderer (for Chrome*, at least) if someone could be prodded into fixing that issue.

Clarification: there's a good argument based on use cases like this one that wavy underline should render the wave angle based on a notion of absolute/viewport coordinates(?), not box-relative coordinates. If FF "does the right thing" on that point, it's additional ammunition to make that argument re: the Chromium issue.

@egmontkob
Copy link
Author

[jerch] VTE ... ppl cannot use FG/BG coloring tricks to temporarily hide those lines

Whoever uses underline color = bg color to hide the underline, rather than disabling it: screw them! :)

(I mean: you have a valid point, we haven't thought of it. I guess we'll extend to 8+8+8 bits if it really becomes an issue.)

This leads us to the next question: if the underline crosses the letter, whose color should matter? VTE draws the underline fist, followed by the letter, so that the letter's color wins. That is, an underline color that equals to the background is a no-op (except for the color reduction). If we picked the other drawing order, it would be different. Followup question: what to do with skip-ink? It would not only be a PITA for us to implement, but I don't even like the end result, I prefer not to skip. :) All up to you to decide, I guess.

[jwhitley] LayoutNG ... So theoretically, it might be possible to avoid a custom renderer (for Chrome*, at least) if someone could be prodded into fixing that issue.

The announcement article you linked has a section "Joining across element boundaries", what it demonstrates seems to require pretty much the same architecture as the one needed for continuous undercurl. Let's hope they'll address it soon.

Another possibility could be if CSS had a text-decoration-underline-wavelength, or Chrome chose the font width (at least for monospace fonts), in that case the current problems its implementation has would not be visible with monospace fonts. Of course these are unlikely to happen.

@jerch
Copy link
Member

jerch commented Mar 8, 2020

Whoever uses underline color = bg color to hide the underline, rather than disabling it: screw them! :)

Haha yepp, still ppl gonna do funky things and think it was a smart move ;)

This leads us to the next question: if the underline crosses the letter ...
Followup question: what to do with skip-ink? ...

Wasnt even aware of the skip-ink capability. Well both questions dive deep into typesetting realms, which I have no educated opinion about - can only tell you if something pleases my eyes lol. How would a typesetter put a colored, not "skip-inked" underline here, above or below? I cannot really answer that, in hand-written documents an underline would end up above most of the time (ppl would write the text first and underline afterwards, not the way around, but this is more a technical restriction not knowing the runwidth beforehand). Which gives the impression that the underline has a stronger meaning (strikes through letters), where an underline below is a weaker metaphor (letters strike through the line). Skip-ink looks indeed nice, I think ppl would use that mostly for the looks in the hand-written case (strike-through always looks intrusive). Thus the stronger vs. weaker metaphor might not be intended at all.

Transferred to the static nature of a terminal - I think below is the better way to handle it (if skip-ink is no option) to not disturb the letter flow to much. Skip-ink seems to be the holy grail here (for my eyes at least), but since we are used to underlines touching letters (+20ys staring at word documents or hyperlinks in browsers), not a big deal. Not sure how you implemented the glyph output in VTE, maybe a matrix transformation adding a hard glow/shadow could be used as erase mask? In general I think below is just fine. If we care enough we might look into skip-ink. Just abit scared that it might open the next can of worms.

About the buffer needs: I think we should put this information into a separate storage, something like ExtendedAttributes, that lives on a single buffer line. I see two main advantages here compared to directly extending the cell array:

  • only uses memory, if there are extended attributes defined for a cell
  • can be used for yet to come features, like @egmontkob's hyperlink sequence (to hold a pointer to the url, whatsoever)

Only downsides I see are the higher implementation needs (all cell actions must respect it) and higher memory and runtime costs for a cell, that holds those attributes (rare case). Gonna try to do a PR addressing the core changes first.

@egmontkob
Copy link
Author

Transferred to the static nature of a terminal - I think below is the better way to handle it (if skip-ink is no option) to not disturb the letter flow to much.

I agree. The question reminds me of the "end of town" road signs in my country where usually the diagonal strikethrough is in the front, sometimes making it non-obvious what the exact name of the town is.

Not sure how you implemented the glyph output in VTE, maybe a matrix transformation adding a hard glow/shadow could be used as erase mask?

We don't have skip-ink in VTE, we just draw the underline first, followed by the letter. But if we were to implement skip-ink, we'd probably print the underline, then a boldified version of the letter with the background color (or the letter a couple of times, at small offsets in different directions from the desired position, which I think is effectively the same as your matrix idea), then finally the letter as desired.

If we care enough we might look into skip-ink. Just abit scared that it might open the next can of worms.

Like, for example: especially if the underline is wider than 1px and the underline crosses the letter at a sharp angle, the erase mask approach results in pointy underline ends. An even nicer looking result could imitate hand-drawing and lifting up the pen, by making the end always a rounded semi-circle. Probably extremely hard to implement; needs to be done inside the font renderer, or with the rendered variant at a higher resolution. And might not make too much sense, unless the letters in the font also imitate hand drawing. Let's quickly close this can of worms :)

@jerch
Copy link
Member

jerch commented Mar 8, 2020

An even nicer looking result could imitate hand-drawing and lifting up the pen, by making the end always a rounded semi-circle.

How about flourishes at the beginning and the end? We also have a strong need for medieval majuscles in the terminal. 🤣

@egmontkob
Copy link
Author

Definitely! 😉

@jerch
Copy link
Member

jerch commented Mar 8, 2020

Made a quick hack on the DOM renderer with #2751 to get a first impression. Well, thats what I get in Chrome:

  • font-size: 14px
    image

  • font-size: 16px
    image

  • font-size: 20px
    image

In comparison to FF:

  • font-size: 14px
    image

  • font-size: 16px
    image

  • font-size: 20px
    image

Urgh, thats really bad for both. While FF still manages to output something useful (only shows box border artefacts) chrome is totally off. It cannot render curly/dashed/dotted reliably depending on the selected font size. A nightmare...

The CSS classes use text-decoration in the shorthand notation text-decoration: underline wavy|dotted|dashed|double;.

So thats where we start from with the DOM renderer, not quite promising...

@jwhitley
Copy link

jwhitley commented Mar 9, 2020

So thats where we start from with the DOM renderer, not quite promising...

I was a bit afraid of that from rather less in-depth testing, just faffing with the MDN text-decoration page and FF/Chrome inspectors. At least at larger sizes, FF seemed much better than Chrome for underline wavy. Chrome is just.. questionable as heck.

@jerch
Copy link
Member

jerch commented Mar 9, 2020

@jwhitley What Chrome does is def. too unreliable, not showing the wavy line for certain font sizes is a full showstopper, the artefacts in dashed and dotted are also bad. FF does alot better, still the box border issues would lead to very poor output and should not be used. I think FF would do alot better if we would not put every char into its own box, but if we change that in the DOM renderer, we will re-introduce old grid-alignment bugs. To me it seems we cannot get this working with CSS styles. But before resorting to self-drawing - feel free to mess around with the DOM renderer and PR #2751, maybe you have a better idea how to get away cheaper.

@jerch
Copy link
Member

jerch commented Mar 9, 2020

Early self-drawing approach with DOM renderer:

grafik

The underline is actually a 3px high background image on the line, thus text will print on top. It seems easier to get complex underlines working with this approach, will see.

@Tyriar
Copy link
Member

Tyriar commented Apr 6, 2020

The underline is actually a 3px high background image on the line, thus text will print on top.

@jerch maybe we should shift the underline down a little if the user has lineHeight > 1?

@jerch jerch self-assigned this Apr 7, 2020
@jerch
Copy link
Member

jerch commented Apr 7, 2020

@Tyriar Yes looks like. Well I have currently no time to work on this, so things have to wait. Ofc if anyone wants to step in, that would be great. Still gonna add myself to this issue to not lose track.

@Tyriar
Copy link
Member

Tyriar commented Jul 23, 2022

@Tyriar
Copy link
Member

Tyriar commented Jul 23, 2022

Test echo:

echo -e '\x1b[4:0m4:0 none\x1b[0m \x1b[4:1m4:1 straight\x1b[0m \x1b[4:2m4:2 double\x1b[0m \x1b[4:3m4:3 curly\x1b[0m \x1b[4:4m4:4 dotted\x1b[0m \x1b[4:5m4:5 dashed\x1b[0m'

Kitty v0.24.1 rendering:

Screen Shot 2022-07-23 at 6 57 17 am

xterm.js current state:

DOM:

Screen Shot 2022-07-23 at 6 58 46 am

Canvas:

Screen Shot 2022-07-23 at 6 59 35 am

Webgl:

Screen Shot 2022-07-23 at 6 59 49 am

@Tyriar
Copy link
Member

Tyriar commented Jul 23, 2022

Test echo for colors:

echo -e '\x1b[4;58:5:203mred underline (256)\x1b[0m \x1b[4;58:2:0:255:0:0mred underline (true color)\x1b[0m'

@Tyriar Tyriar added this to the 4.20.0 milestone Jul 23, 2022
@Tyriar Tyriar self-assigned this Jul 23, 2022
@Tyriar Tyriar modified the milestones: 4.20.0, 5.0.0 Jul 28, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants