xterm.js: Manually draw pixel-perfect glyphs for Box Drawing and Block Elements characters
Details
- Browser and browser version: Chrome 76.0.3809.132 (64 bit), FireFox 69.0 (64 bit)
- OS version: Windows 7 Pro 64 bit
- xterm.js version: ^3.14.5
Steps to reproduce
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
About this issue
- Original URL
- State: closed
- Created 5 years ago
- Reactions: 4
- Comments: 29 (18 by maintainers)
@meganrogge and I just finished this, here’s the result:
Canvas (1.1 line height, 1 letter spacing):
Webgl (same):
The setting is
customGlyphs
and is enabled by defaultI’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).
For the DOM renderer I’m not sure we want to add the complexity there of rendering custom glyphs. Especially since I want to make the canvas renderer its own addon which would mean we don’t even need the custom glyph code in the core xterm.js bundle.
I think underline styles and colors run into issues packing those bits in, there’s also the issue that they probably can’t just be added to the glyphs like this since they’re repeating patterns that would get cut off, ie.
Also special callout to @guregu whose work this builds upon ❤️
@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.
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.
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.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.@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…
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.