terminal
                                
                                 terminal copied to clipboard
                                
                                    terminal copied to clipboard
                            
                            
                            
                        Support for custom box drawing and powerline glyphs
This adds support for drawing our own box drawing, block element, and basic Powerline (U+E0Bx) glyphs in AtlasEngine.
This PR consists of 4 parts:
- AtlasEngine was refactored to simplify _drawGlyphbecause I've wanted to do that for ~1 year now and never got a chance. Well, now I'm doing it and you all will review it muahahaha. The good news is that it removes a goto usage that even for my standards was rather dangerous. Now it's gone and the risk with it.
- AtlasEngine was further refactored to properly parse out text that
we want to handle different from regular text. Previously, we only
did that for soft fonts, but now we want to do that for a lot more,
so a refactor was in order. The new code is still extremely
disgusting, because I now stuff wchar_ts into an array that's intended for glyph indices, but that's the best way to make it fast and not blow up the complexity of the code even further.
- Finally this adds a huge LUT for all the aforementioned glyphs. The LUT has 4 "drawing instruction" entries per glyph which describe the shape (rectangle, circle, lines, etc.) and the start/end coord. With a lot of bit packing each entry is only 4 bytes large.
- Finally-finally a customGlyphssetting was added to the font object and it defaults totrue.
Closes #5897
Validation Steps Performed
- RenderingTests with soft fonts ✅
- All the aforementioned glyphs ✅
- ...with color ✅
- customGlyphssetting can be toggled on and off ✅
(Note: The look of 3 block shading glyphs was further improved in #16760)
Zooming in on my phone-
weird artifact in these
I believe these are supposed to be connected (the single should connect both parts of the double); this is true of all single double corner intersections (?)
Hmm... The former happens due to this bit of code: https://github.com/microsoft/terminal/blob/2e8657b9dc14f9b77156769cc6a68d568513e2c5/src/renderer/atlas/BackendD3D.cpp#L1482-L1487
My intention there is to snap the given line to the nearest pixel center. This is particularly important for 1px wide lines because they otherwise appears as a 2px wide smudged line. But I should only do that when the lines are vertical/horizontal and only for the axis that is perpendicular to the line. I'll fix this in a minute.
The 2nd one is correct I believe. This is from the Unicode spec for the block:
Should be better now. :) (I'll collapse the comments as the images are rather large on Desktop.)
2588-258A may be in the wrong order. The UTF8 "torture test" has them in an ascending triangle and it's in the wrong shape!
As for the second, I was thinking of 2552, 2553 and the glyphs similar to them, where a single meets a double and forms a corner (rather than a T):
The single line bridges both parts of the double line, rather than stopping at the first intersection.
Pending: Hook it up to WT
Based on the build artifacts, it works perfectly in WT already!
Oooh... I see. Interesting. Cascadia never connects them through for any of the glyphs whereas the reference design does for all of them. My implementation is a mix of both. What do you think: Should we always connect them or never?
Based on the build artifacts, it works perfectly in WT already!
Oh, I meant that I need to make it a setting.
Should we always connect them or never?
I think that there isn't one answer for all single-double intersections.
I think that we should connect them for any L-shaped intersection, and not connect them for any T-shaped intersection. I believe that is the standard for the old CP437 box drawing shapes too.
"`n  ╠╡╢╣╤╥╦╧╨╩╪╫╬╭╮╰╯`e[41;92m░▒▓`e[m`n`n`e#6 ╠╡╢╣╤╥╦╧╨╩╪╫╬╭╮╰╯`e[41;92m░▒▓`e[m`n`n`e#3 ╠╡╢╣╤╥╦╧╨╩╪╫╬╭╮╰╯`e[41;92m░▒▓`e[m`n`e#4 ╠╡╢╣╤╥╦╧╨╩╪╫╬╭╮╰╯`e[41;92m░▒▓`e[m`n"
I think that we should connect them for any L-shaped intersection, and not connect them for any T-shaped intersection. I believe that is the standard for the old CP437 box drawing shapes too.
Yeah I just checked and this seems to be true (re: CP437). But when I just went ahead to make the necessary changes I noticed that Consolas also doesn't connect the lines. In fact, even ╪ and ╫ aren't "struck through". Since I personally also kind of find it more visually pleasing when they're not struck through, I'll for now change just these two to match the design of Consolas and Cascadia, since that will be super easy to do. I'm happy to change the L and X shaped glyphs once proper review starts. I think it's also not unlikely that j4james[^1] will want to chime in. 😅
[^1]: Edit; If you're reading this: I didn't want to @ you since I don't want to bother you unnecessarily. 🙂 (Especially not on a weekend.)
I haven't really looked at the code because I'm out of depth when it comes to anything to do with the Atlas renderer, but I'm thrilled to see that we're getting actual patterned renderings for the U+259x codepoints!
As for the single-double intersections, I don't feel strongly about it, but my preference would be to match the Unicode reference glyphs.
- 
For the L shaped connections (like ╙or╘) , the single line should go all the way to the end so it "seals off" the double line.
- 
For the T shaped connections, if the single line is the "top" of the T (like ╥or╡), it should seal off the double line, but if the double line is the "top" (like╤or╢), the single line should not intersect the double.
- 
For + shaped connections (like ╪or╫), the single line should always intersect the double line.
I think these glyphs are correct in Cascadia Code, but not in Consolas.
Getting Cut off at the bottom, or is it just the screenshot 🤔
I think it gets cut off. It's probably due to rounding errors. I'm also not 100% sure that my math is completely correct.
I think the loss of the "glyph retry" is because you split up allocation into a separate phase which could be independently retried after resetting the atlas?
Yep! Got it 100% right. This was previously not possible because the outer font-face hashmap had a key of (font-face, line-rendition). This meant that if we rasterized a double-height glyph we would insert 2 glyphs into the hashmap (upper and lower half) which could resize the outer hashmap and end up invalidating all pointers.
NOW I use just (font-face) as the font-face hashmap key and so this can't possibly happen anymore. The line-rendition entries are now simply stored in a fixed inner
til::linear_flat_set<AtlasGlyphEntry> glyphs[4]
nit: reword the commit title to be in imperative form (e.g. it can complete the sentence, "This commit will ...")
Hey @phmajerus, you might be interested in this! The screenshot of all glyphs above is a bit outdated, since we've fixed the double/single intersections, but other than that this is ready to go in.
Wondered if you had thoughts/concerns as an ANSI artist :)
Hey @DHowett, thanks for including me in the discussion. I'm not really an ANSI artist, more of an 8-bit nostalgic, but I do have some comments about your approach.
While I understand the reason to try to get away from font glyphs, I'm concerned about all the other block-sized characters that should be supported in the future. The box drawing and block elements you decided to render are only a tiny fraction of all the characters that need to fill their cell and join seamlessly. We have sextants, octants, triangles, and semicircles, lot of patterns inherited from legacy 8×8 pixels character sets, and they all need to join precisely with each other. That means you have over 400 other pseudopixels glyphs, and I didn't even count all the mosaic patterns from legacy computers yet.
Here are just a few:
For the box drawing, there are at least 69 more connecting lines, angles, and diagonals.
And even if you decided to custom-draw all those, if you ever plan to support really all legacy scenarios, such as less common characters ROM found in some terminals and 8-bit computers, you'll have even more characters that need to join with those and each other, think about MouseText elements from the Apple II for example. All of these will be standardized in Unicode 16.0, and more may be coming later.
Now imagine you did the work to custom-draw all of those, then you'll still leave some more block-sized characters behind. Take the large type pieces, they don't need to join with pseudopixels and box drawing, but they do need to join seamlessly with each other, and while there are standard shapes for those provided by Unicode, they are ugly and actually don't join properly with each other, so they really are just enough to recognize them, but you need to improve them for large text to work. So several fonts will provide variations of those, some even reinterpreting their original design to give them more identity, with design hints reminiscent of the standard-sized characters.
For example, here is my current work on large type pieces for Cascadia, with rounded corners:
While the original design uses beveled corners, because it was modern in the 70s when HP originally designed them for its terminals:
(This is using https://github.com/be5invis/Iosevka font)
These cannot be custom-drawn without losing on consistency and font design.
To me it seems there are way too many glyphs to handle them all, and picking a custom-drawn solution might mean you spend less effort on trying to reduce the artifacts of the renderer when joining full-block characters.
I have even more concerns coming in an upcoming post, because Unicode only standardize glyphs intents, not their actual design, and they use the same codepoint for characters that have the same intent on several legacy systems, but which actually have incompatible designs if you want the terminal to be able to render their original semigraphics contents properly. In short, the same code point will have different pseudopixels patterns depending on the original system, and users may switch between a few fonts depending on what they are doing or which app they launch in different terminal profiles. Imagine I build a version of the Commodore Basic interpreter as a Win32 exe and use it from the terminal, I want a font that is compatible with PETSCII, but if I have another profile for an Apple II Basic interpreter, I need another font, because some of the same characters have to look different.
And all of this is a real possibility, and one of the goals of the hundreds of semigraphics characters coming in Unicode 16. I literally spent the last few weeks preparing 563 new semigraphics characters for Cascadia, the first batch being pending right now (https://github.com/microsoft/cascadia-code/pull/708), all of which are block-sized and need to seamlessly join.
[...] might mean you spend less effort on trying to reduce the artifacts of the renderer when joining full-block characters.
Are you aware of an approach that does this properly? I'd be happy to implement it!
(BTW I'm not sure if it's relevant to your argument, but the PR makes this a setting. It'll be next to the font size setting.)
@lhecker I don't have a perfect solution and I do appreciate the thought and work you've put into trying to improve the situation.
One of the problem I'm concerned with is consistency, especially with pseudo-pixels mosaics, quadrants reuse existing half-block patterns, sextants reuse half-blocks as well, octants reuse half-blocks, quadrants, and eights fills, a new set of eights are providing horizontal and vertical lines, as well as horizontal and vertical fills from the top and right to complement existing ones from the bottom and left,...
All those need to share their coordinates system with glyphs that are part of the older box drawing and block elements, so if you custom-draw the oldest ones and not the new ones, they won't align properly, we already see such problem today because the coordinates are not unified:
I'm not against an option to make the base box drawing and block elements look nicer, but I don't think it is the ultimate solution.
Is there any way with the DirectWrite renderer to adjust the glyph size for rendering? If designed properly, all the hundreds of semigraphics of a font should be based on the Full Block U+2588 bounding box. The only solution I can think of that would make it perfect is to retrieve the bounding box of the Full Block, round it to its nearest pixels boundary, so it snaps into the grid, and feed that scaling back into the font renderer to adjust all glyphs accordingly.
Then you don't need to handle some characters in a special way, which is impossible since a font may include PUA characters that are also block-sized. It would also be the only solution I can think of that would make it possible to have block-sized ligatures.
For example, here is a version of my large type pieces with ligatures to further round the corners:
It looks nice when the full block size is pretty much a round number of pixels, but at some sizes, it gets misaligned.
I believe this is because sequences of 2 or 3 blocks are replaced by double or triple width blocks using ligatures, and if the full block width wasn't a round number of pixels, the individual characters get snapped to the nearest pixel while the ligature group doesn't, ending up in off by 1 or 2 pixels alignment issues on the right.
If the renderer could find out the separate width and height scaling adjustments needed to snap the full block to its cell, and feed these adjustments back into the font renderer, I believe you could achieve perfect seamlessly connection of all the box drawing, pseudo-pixels, large font, and all semigraphics. No more seams or overlaps, no special table of which characters are block-sized, no special handling of "the most important" box drawing and block element characters at the expense of all the others,...
Do you think something like that is possible? It is a very special case so maybe DirectWrite doesn't support that, but since you're lucky enough to be the same company, could it be possible if it isn't supported to get a customized version of DirectWrite that supports those adjustments scaling to render the terminal characters grid perfectly?
If you want to test your rendering changes with those new glyphs, here are some more resources.
This is a custom version of Cascadia with the characters I'm adding: Cascadia-PHM 2024-02-22.zip It contains sextants, octants, diagonals, large type pieces, and several other legacy computing characters.
Here are the charts, note I didn't do everything yet:
Here is an explanation and test document for large type pieces: HowTo Large Type Pieces.txt
The Cascadia Code version includes ligatures for several of the corners combinations of large type pieces.
The large type pieces are probably the trickiest semigraphics I'm encountered, if you can render those you can probably render everything.
As usual, you can find more pseudo-pixels test files at https://github.com/PhMajerus/ANSI-art/tree/main/Unicode
If you try it out, run the following: echo G1swOzM4OzU7MTZtICAg4paC4paC4paC8Jy6o/CctZEKIOKWlxtbNDg7NTsyMDht4pabG1s0ODs1OzIxNG3wn66CICDwn66C8Jy0phtbNDlt8Jy0tgog8Jy3lRtbMzg7NTsyMDg7NDg7NTsyMTRt4paMICAbWzM4OzU7MTZt4paMIOKWjBtbNDlt4paMG1swOzNtIFRoYW5rChtbMDszODs1OzE2bfCctpYbWzQ4OzU7MjA4beKWmCAbWzM4OzU7MjA4OzQ4OzU7MjE0bfCcuqMgIBtbMzg7NTsxOTdt8Jy0s/CctKsbWzQ5OzM4OzU7MTZt4paMG1swOzNtICB5b3UhChtbMDszODs1OzE2bfCctKEbWzQ4OzU7MjA4bfCctL7wnLSn8Jy0kBtbMzg7NTsyMDg7NDg7NTsyMTRt8Jy2u/CcuqMgG1szODs1OzE2bfCct5MbWzQ5beKWmArilpcbWzQ4OzU7MTIzbfCctIIbWzQ4OzU7Mjdt8Jy6qBtbNDg7NTsxMjM7Mzg7NTsyMG3wnLSCG1s0ODs1OzIzMTszODs1OzE2bfCctIUbWzQ4OzU7Mjdt8Jy1hRtbNDg7NTsxMjM7Mzg7NTsyMG3wnLSCG1s0ODs1OzIzMTszODs1OzE2bfCctIUbWzQ5beKWlgrwnLqr8J+ugvCfroLwn66C8J+ugvCfroLwn66C8J+ugvCcuqgbW20K | base64 -d
The only solution I can think of that would make it perfect is to retrieve the bounding box of the Full Block, round it to its nearest pixels boundary, so it snaps into the grid, and feed that scaling back into the font renderer to adjust all glyphs accordingly.
The old "DxRenderer" did this for the basic box drawing glyphs. If you currently have "AtlasEngine" enabled in the Rendering settings, you could try disabling it and seeing how it looks then. I think the old DxRenderer could still be fine tuned a bit, but it's probably about as good as it gets. But first this...
Then you don't need to handle some characters in a special way, which is impossible since a font may include PUA characters that are also block-sized.
From my past experience triaging user feedback for Windows Terminal, I believe I need to disagree, even if I agree that you're fundamentally correct. There are two problems I need to prevent if I were to do what you suggest:
- A large number of fonts I've been seeing on here have a limited coverage of the box drawing blocks. Most notably, since it's the most widely used monospace font on Windows, Consolas has no coverage for most glyphs in the block elements block. Even with DxRenderer's glyph scaling, this still looks somewhat inconsistent (primarily due to the blur on contemporary <150 DPI displays).
- A large number of people are quite vocal about perfect old-school-style bitmap fonts. These fonts only render correctly if we don't distort them in any way, but since they're still fonts, their size is not necessarily exactly perfect. This means that we must avoid scaling them by accident even if their U+2588 is larger than the cell size.
This PR solves both problems. Not in a way that I'd like since I fundamentally agree with you, but rather in a way that I think is proven to work, given that other popular terminals do the same thing.
I'm open to brainstorm alternative approaches we can add to our text renderer, but I believe this PR is the most solid approach we can have and the one we should have if not at least as a fallback.
For instance, given what you recommended, I think we should add another toggle that allows users to scale box drawing glyphs according to the size of the U+2588 glyph. This would make Cascadia work perfectly, while not breaking fonts such as TerminusTTF again.
@lhecker I think many fonts have been designed by looking at their results in whichever terminal the font designer used daily. It's not your job to accommodate those, and I do agree your solution as a fallback is a good fix. It was never my intention to come and try to say this commit was a bad idea, but just to provide a point of view that it won't solve everything either.
Now about the possibility to scale and snap-to-grid every glyph according to U+2588.
This to me sounds like a perfect solution, because a font that is geometrically correct in its design will always have all the semigraphics using a unified coordinates grid, and therefore all their glyphs will be sized perfectly to fit together. It would also help me a lot as right now I have two sets of coordinates to work with in Cascadia, so each of those glyphs I added are actually two, some with trigonometric functions to get the proper equivalent coordinates on the two grids (GDI and DWrite). So it would half the work required to add semigraphics to Cascadia.
It seems like it would also please the users who want pixel-perfect old-school bitmap fonts. If you can scale width and height to snap glyphs to the grid, can you provide an alternative font size configuration in the settings to let users set the font size in pixels width and height instead of points? If you can calculate the width and height scaling for the renderer accordingly, and as long as the user choose a pixels-width-and-height size that is a multiple of their font design grid, all the pseudo-pixels would be perfectly aligned with no distortion or blurry edges. So again, if their font is geometrically correct, they'll get pixel-perfect rendering.
Finally, just to further make the point that U+2588 should be the reference to scale all glyphs, diagonal box-drawing such as ╱:U+2571, ╲:U+2572, ╳:U+2573, and the plenty of new diagonals snapping to the middle of each borders seen in the new legacy computing charts in my post above, all need to extend slightly outside their bounding box to join perfectly with each other. So the only way to find out how to snap them properly to the terminal grid is to infer their intended cell from U+2588, because their own bounding box will extend outside that cell.
If you want to test your rendering changes with those new glyphs, here are some more resources.
This is a custom version of Cascadia with the characters I'm adding: Cascadia-PHM 2024-02-22.zip It contains sextants, octants, diagonals, large type pieces, and several other legacy computing characters.
Here are the charts, note I didn't do everything yet:



Here is an explanation and test document for large type pieces: HowTo Large Type Pieces.txt
The Cascadia Code version includes ligatures for several of the corners combinations of large type pieces.
The large type pieces are probably the trickiest semigraphics I'm encountered, if you can render those you can probably render everything.
As usual, you can find more pseudo-pixels test files at https://github.com/PhMajerus/ANSI-art/tree/main/Unicode
If you try it out, run the following:
echo G1swOzM4OzU7MTZtICAg4paC4paC4paC8Jy6o/CctZEKIOKWlxtbNDg7NTsyMDht4pabG1s0ODs1OzIxNG3wn66CICDwn66C8Jy0phtbNDlt8Jy0tgog8Jy3lRtbMzg7NTsyMDg7NDg7NTsyMTRt4paMICAbWzM4OzU7MTZt4paMIOKWjBtbNDlt4paMG1swOzNtIFRoYW5rChtbMDszODs1OzE2bfCctpYbWzQ4OzU7MjA4beKWmCAbWzM4OzU7MjA4OzQ4OzU7MjE0bfCcuqMgIBtbMzg7NTsxOTdt8Jy0s/CctKsbWzQ5OzM4OzU7MTZt4paMG1swOzNtICB5b3UhChtbMDszODs1OzE2bfCctKEbWzQ4OzU7MjA4bfCctL7wnLSn8Jy0kBtbMzg7NTsyMDg7NDg7NTsyMTRt8Jy2u/CcuqMgG1szODs1OzE2bfCct5MbWzQ5beKWmArilpcbWzQ4OzU7MTIzbfCctIIbWzQ4OzU7Mjdt8Jy6qBtbNDg7NTsxMjM7Mzg7NTsyMG3wnLSCG1s0ODs1OzIzMTszODs1OzE2bfCctIUbWzQ4OzU7Mjdt8Jy1hRtbNDg7NTsxMjM7Mzg7NTsyMG3wnLSCG1s0ODs1OzIzMTszODs1OzE2bfCctIUbWzQ5beKWlgrwnLqr8J+ugvCfroLwn66C8J+ugvCfroLwn66C8J+ugvCcuqgbW20K | base64 -d
it works:
But I miss the NERD glyphs, could you provide one version of CaskaydiaCove instead?
This PR works with any font and you can just use CaskaydiaCove. :)
This PR works with any font and you can just use CaskaydiaCove. :)
Idk then, maybe the font is missing some glyphs to make the image complete:
Just so we're on the same page...
You are testing a font which is under development by PhMajerus, which you found on a pull request about changing how box/line/shade characters are rendered for the purposes of testing, and asking for PhMajerus to provide you a version of the under-development font that has also been patched to contain "Nerd Font" characters.
Lhecker is telling you that the changes in this PR apply to any font; to be clear though: this PR will not make glyphs that exist only in PhMajerus' under-development font show up in all fonts.
If you're looking for a font that contains sextants, octants, diagonals and large type pieces as well as Nerd Font glyphs, you will need to wait for Cascadia to officially release an update for those legacy computing symbols.
Just so we're on the same page...
You are testing a font which is under development by PhMajerus, which you found on a pull request about changing how box/line/shade characters are rendered for the purposes of testing, and asking for PhMajerus to provide you a version of the under-development font that has also been patched to contain "Nerd Font" characters.
Lhecker is telling you that the changes in this PR apply to any font; to be clear though: this PR will not make glyphs that exist only in PhMajerus' under-development font show up in all fonts.
If you're looking for a font that contains sextants, octants, diagonals and large type pieces as well as Nerd Font glyphs, you will need to wait for Cascadia to officially release an update for those legacy computing symbols.
Yep, that's what i meant with i wish there was a merge(remix?) between Cascadia Mono by PhMajerus and CaskaydiaCove Mono by NerdFonts.
Yep, that's what i meant with i wish there was a merge(remix?) between Cascadia Mono by PhMajerus and CaskaydiaCove Mono by NerdFonts.
Not quite yet, but maybe soon :eyes:
