index.html:
<!doctype html>
<html>
<head>
<link rel="stylesheet" href="node_modules/xterm/dist/xterm.css" />
<script src="node_modules/xterm/dist/xterm.js"></script>
</head>
<body>
<div id="terminal"></div>
<script>
var options = {
rows: 30,
cols: 80,
fontFamily: '"Courier New", "DejaVu Sans Mono", "Everson Mono", FreeMono, "Andale Mono", monospace',
fontSize: 12,
convertEol: true
};
var term = new Terminal(options);
term.open(document.getElementById('terminal'));
term.write(' โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n');
term.write(' โ โ\n');
term.write(' โ โโโโโโโโโโโโโโโโโฆโโโโโโโโโคโโโโโโโโโ โ\n');
term.write(' โ โ โ โ โ โ\n');
term.write(' โ โ โ โ โ โ\n');
</script>
</body>
</html>

There are several related issues but it does not help:
https://github.com/xtermjs/xterm.js/issues/475
https://github.com/xtermjs/xterm.js/issues/992
I think this depends on the font being used, I can't repro using the canvas (default) renderer.
@Tyriar how can I share the needed info?
Can you try using this font? Both your fontFamily and Hack work for me on my Windows 10 box.
I have tried the following on my home Windows 10 64 bit machine in Yandex Browser (based on Chromium):
<!doctype html>
<html>
<head>
<link rel='stylesheet' href='//cdn.jsdelivr.net/npm/[email protected]/build/web/hack.css'>
<link rel="stylesheet" href="node_modules/xterm/dist/xterm.css" />
<script src="node_modules/xterm/dist/xterm.js"></script>
</head>
<body>
<div id="terminal"></div>
<script>
var options = {
rows: 30,
cols: 80,
fontFamily: 'Hack',
fontSize: 12,
convertEol: true
};
var term = new Terminal(options);
term.open(document.getElementById('terminal'));
term.write(' โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n');
term.write(' โ โ\n');
term.write(' โ โโโโโโโโโโโโโโโโโฆโโโโโโโโโคโโโโโโโโโ โ\n');
term.write(' โ โ โ โ โ โ\n');
term.write(' โ โ โ โ โ โ\n');
</script>
</body>
</html>
Here is the result:

The same on Chrome:

Also I tried to print in Visual Studio Code with node:

FYI: Some terminals, including gnome-terminal (vte) and konsole manually draw the U+2500..257F (or even up to 259F) characters, rather than taking them from the font, in order to provide beautifully connected look.
Coincidentally both of these emulators took pretty similar technical approach: define the look of (most of) these glyphs as a 5x5 bitmap with uneven "pixel" sizes that are computed runtime based on font size.
@egmontkob Lulz, I remember doing it likewise in my old emulator. Always wondered why xterm.js does not show that behavior, but never investigated.
@Tyriar Just did a few tests with different fonts with chrome and FF with these results - I see in every engine with every font listed in the demo a tiny space (tested in canvas and DOM renderer). The space itself varies with font size and the font itself, FF is one pixel off with a bigger gap (we have that pixel offset in FF also at other places/issues).
Conclusion: All of my tested fonts show spaces (from hardly visible to a prominent gap), therefore I think that the glyphs simply dont cover the height completely (just a guess, have not inspected single glyphs with a font tool). The differences in spaces are mostly font renderer related that tries to round/align it to some given font size.
Furthermore I dont see any gap in vscode (not even the slightest, tested with mc). Not sure what vscode does differently here, maybe a general fix can borrow that? Beside drawing own glyphs as suggested by @egmontkob we might get away with a simpler approach for DOM - just draw those codepoints in question at slightly bigger font size. For the other renderers where we maintain canvas glyph states anyway @egmontkob's approach might get better results.
Perhaps vscode (or at least my setup) is just lucky with its font size, family, window zoom level, etc.
we might get away with a simpler approach for DOM - just draw those codepoints in question at slightly bigger font size
We don't and should not imo clip the cells in the DOM renderer so I don't think this is an option.
Provided it doesn't add too much code* we could hardcode our own glyphs for these ๐, it would be good if it was consistent with the DOM renderer though.
* I have doubts on this
Now that #2572 has been marked as dup, this bug is definitely about U+2500..257F "Box Drawing" and U+2580..259F "Block Elements" too.
Also keep an eye on forthcoming (Unicode 13) U+1FB00 "Symbols for Legacy Computing", see e.g. VTE 189.
@egmontkob Lol, I guess those newish glyphs are meant to align perfectly as well? Geez, who needs a font renderer, if one can do it the hard way...
I'd love to understand the reasons none of the fonts gets it right. But I don't have time and motivation to investigate.
One interesting thing this could do that fonts couldn't is fill the entire cell when a line height is specified.
Additional funny behavior (vscode terminal):

For me, changing font sizes, using the previously mentioned hack font does not help, the spacing remains. However, judging from the gif above, the block characters seems to be placed incorerctly. There is one pixel gap at the bottom and the difference between 7/8 and 8/8 (full) blocks seems smaller than between the other ones.
On macOS (Retina screen, devicePixelRatio=2) + Chrome, using the Hack font gives me small vertical gaps like this:

But after poking around I noticed that turning BaseRenderLayer._clipRow into a no-op renders them correctly:

which leads me to believe that this might be a clipping issue.
@guregu the canvas renderer by design must clip the rows or it will end up having artifacts showing up, but anyway the plan is to remove the canvas renderer in favor of DOM and webgl (basically superior in every way to canvas).
This is still marked as help wanted and maybe it can/should only be done in the webgl renderer in which case it would be a matter of handling those particular characters specially when drawing the char to the texture atlas:
FWIW I've managed to fix it without messing with the clipping.
The problem wasn't the clipping, but using middle as the textBaseline caused it to be slightly misaligned.
Changing the textBaseline to bottom totally fixed the issue for me.
Diff: https://github.com/xtermjs/xterm.js/compare/master...guregu:master
I can send a PR if it looks good, but I'm not sure if this will screw something else up.
Is there a particular reason it was set to middle before?
I noticed that it sets normal text ever so slightly lower, but it looks equivalent to how Terminal.app renders text.
Here's a screenshot of the code running from the start of this PR without the fix:

Here it is with the fix applied:

@guregu previously the webgl renderer used top, but it ended up making the text top aligned:

I fear this will happen if we use bottom
See https://github.com/xtermjs/xterm.js/issues/2613, https://github.com/xtermjs/xterm.js/issues/2645, https://github.com/microsoft/vscode/issues/95813
It's also not perfect even ignoring the bottom alignment problem (notice the notches on the horizontal line):

I think manually drawing perfect glyphs is the way to go.
Btw @nojus297 output pretty much looks like an 0.5 px offset issue. I stumbled over antialiasing issues with a 0.5 offset myself the other day, when I messed around with self-drawn curly underlines. Not sure if this applies here at all (still not used to the renderer code lol).
Would could check your zoom level as well to make sure it's 1. Should we maybe be bumping it down 0.5px when the font size is odd? ๐ค
I took screenshots of both my changes and the unchanged version and ran a diff on them. Normal text actually renders exactly the same. The only difference is the box drawing characters.

(the magenta is the differing part)
Basically the crux of this issue is that, for some reason, text that takes up the entire height of a cell gets pushed up a few pixels and clipped out when using middle as the textBaseline.
It's easy to verify this if you highlight it.

^ unfixed version, you can see that there's a few empty pixels at the bottom of the โ
Regarding the notches on the horizontal line, Terminal.app appears to have the same problem. Maybe it's a font issue? No idea.
Screenshot from Terminal.app:

However, even with the changes I still see some weirdness, like using the cursor causes tiny black notches to show up. This doesn't happen on the veritcal ones. Maybe related to the other notches?
This is from xterm.js, and happens on both the changed and unchanged versions:

Anyway, I agree rendering these characters specially is the way to go. Just trying to find the reason this is happening in the first place.
I had some friends test out my fix on Windows and it actually makes things worse ๐
Looks like special casing it is the only surefire way. Sorry about that.
The cursor thing is probably a separate issue. Clearing the whole canvas during clearCursor fixes it. Seems like the horizontal box-drawing character is drawing a bit wider than the cell width?
@guregu thanks for looking into it.
Seems like the horizontal box-drawing character is drawing a bit wider than the cell width?
Maybe, we allow that. Something that may work is to clip the character glyph to the size of a cell only for these box drawing characters?
I've done some experimenting with manually drawing certain characters.
Demo page: https://roguelike.solutions/xterm/xtermtest.html
Code is rough, but available here: https://github.com/xtermjs/xterm.js/compare/master...guregu:box-drawing
Currently, it's two parts, one is boxDrawingLineSegments which borrows an algorithm from iTerm2. It defines 7 kind of arbitrary vertical and horizontal anchor points per character cell and strokes lines or curves corresponding to them. This works for almost all of the box-drawing lines, but doesn't work for the dotted line characters which require more anchor points. Having precise control over drawing the lines also means we don't need to clip horizontally to avoid spillover. I'm trying to think of a better way to represent these that will also work with the dotted lines.
Perhaps instead of splitting cells into 7 somewhat-arbitrary points, instead we can define the locations like "left", "center", "center minus n devicePixelRatio pixels", etc.
Also, iTerm2 is GPL2 and I tried to write it to be as original as possible and avoid license issues, but I'm not sure where the figurative line is drawn here. This is another reason to use a fancier original algorithm. Consider the current implementation a proof of concept.
Next is boxDrawingBoxes (needs a better name ๐
) which splits a character cell into eighths and fills in rectangles corresponding to them. This is sufficient to draw the solid block characters, including new ones from the Symbols for Legacy Computing block. This should be enough to support the quadrant characters too, but in order to support sextants it will need to split characters into sixths. Allowing a divisor to be defined would be enough to support most block-like characters.
I don't yet support the polygon characters from the Symbols for Legacy Computing block, but using similar techniques should work for them. I'm not sure how to go about drawing the shade characters like โ โ โ, but maybe they could be special-cased. There's also the question of how far we want to go with this.
To clean the code up, maybe we could define an interface for drawing segments, and its implementors could be lines, curves, n-th boxes, etc. This is all very experimental, but if it seems like a good idea I'd be happy to contribute (and add support for the WebGL renderer).
This is all very experimental, but if it seems like a good idea I'd be happy to contribute (and add support for the WebGL renderer).
@guregu nice work, a contribution would be awesome, you just need to make sure it's not too derivative of the iTerm2 algorithm as GPL2 is sticky. I'd suggest only doing this for the webgl renderer (not canvas as it's going to be scrapped). Definitely based the lines on devicePixelRatios so the width is great on all monitors.
but using similar techniques should work for them. I'm not sure how to go about drawing the shade characters like โ โ โ
These should be relatively easy to do but this could easily be deferred as well. The main problem in this area are the solid ones.
Most helpful comment
I've done some experimenting with manually drawing certain characters.
Demo page: https://roguelike.solutions/xterm/xtermtest.html
Code is rough, but available here: https://github.com/xtermjs/xterm.js/compare/master...guregu:box-drawing
Currently, it's two parts, one is
boxDrawingLineSegmentswhich borrows an algorithm from iTerm2. It defines 7 kind of arbitrary vertical and horizontal anchor points per character cell and strokes lines or curves corresponding to them. This works for almost all of the box-drawing lines, but doesn't work for the dotted line characters which require more anchor points. Having precise control over drawing the lines also means we don't need to clip horizontally to avoid spillover. I'm trying to think of a better way to represent these that will also work with the dotted lines.Perhaps instead of splitting cells into 7 somewhat-arbitrary points, instead we can define the locations like "left", "center", "center minus n devicePixelRatio pixels", etc.
Also, iTerm2 is GPL2 and I tried to write it to be as original as possible and avoid license issues, but I'm not sure where the figurative line is drawn here. This is another reason to use a fancier original algorithm. Consider the current implementation a proof of concept.
Next is
boxDrawingBoxes(needs a better name ๐ ) which splits a character cell into eighths and fills in rectangles corresponding to them. This is sufficient to draw the solid block characters, including new ones from the Symbols for Legacy Computing block. This should be enough to support the quadrant characters too, but in order to support sextants it will need to split characters into sixths. Allowing a divisor to be defined would be enough to support most block-like characters.I don't yet support the polygon characters from the Symbols for Legacy Computing block, but using similar techniques should work for them. I'm not sure how to go about drawing the shade characters like โ โ โ, but maybe they could be special-cased. There's also the question of how far we want to go with this.
To clean the code up, maybe we could define an interface for drawing segments, and its implementors could be lines, curves, n-th boxes, etc. This is all very experimental, but if it seems like a good idea I'd be happy to contribute (and add support for the WebGL renderer).