Fixes#143
There were multiple issues with the previous calculation. First, we
expected dash width COULD be negative and protected against it, but our
dash width calculation type was unsigned! With the proper font metrics,
this led to an underflow safey panic.
Second, as part of the dash width calculation, we are tryign to downstep
the size of the gaps so we can try to fit dashes within a cell. But we
were not using those adjusted gap sizes. With the proper font metrics,
this could lead to an assertion failure seen in #143.
This fixes the calculations. They produce the same numbers, but do so in
a more Zig-idiomatic way while fixing the above two issues.
WezTerm claims this is an emerging de-facto standard for terminal emulator identification:
a103b6d97a/config/src/config.rs (L1526-L1529)
One example of usage in the wild is neovim doing capability detection:
f050aaabbb/src/nvim/tui/tui.c (L206-L211)
Ghostty now reports this:
$echo $TERM_PROGRAM
ghostty
$echo $TERM_PROGRAM_VERSION
0.1.0-main+aa08f3c
I think it's really nice that the commit hash is included, as users can provide this in issue reports. WezTerm does the same.
I use these variables in my tui library in addition to $TERM and $COLORTERM for capability detection, which is what motivated this PR.
When font shaping grapheme clusters, we erroneously used the font index
of a font that only matches the first codepoint in the cell. This led to the
combining characters being [usually] unknown and rendering as boxes.
For a grapheme, we must find a font face that has a glyph for _all codepoints_
in the grapheme.
This also fixes an issue where we now properly render the unicode replacement
character if we can't find a font satisfying a codepoint.
This matches Kitty behavior on both macOS and Linux. In certain keyboard
modes and Kitty keyboard modes, the behavior changes but those already
matched (tested).
We previously never set the focused pointer to null. I thought this
would be fine because a `hasSurface` check would say it doesn't exist.
But I didn't account for the fact that a deleted surface followed very
quickly by a new surface would free the pointer, then the allocation
would reuse the very same pointer, making `hasSurface` return a false
positive.
Well, technically, hasSurface is not wrong, the surface exists, but its
not really the same surface, its just a surface that happens to have the
same pointer as a previously freed surface.
Co-authored-by: Will Pragnell <wpragnell@gmail.com>
font: Improve quirks mode performance, DeferredFace, Group refactor
This was motivated by #331. I realized with #331 that this added a lot of overhead on every render frame, because for each text run being rendered, we were (1) reloading the font name (unknown performance cost on macOS due to closed source, looks like [it ain't exactly free in Freetype](https://sourcegraph.com/github.com/freetype/freetype@4d8db130ea4342317581bab65fc96365ce806b77/-/blob/src/base/ftsnames.c?L44)) (2) doing multiple string compares (3) multiple function call overhead. And realistically, quirks mode will be _rare_, so paying a very active cost for a rare thing is 💩 .
In investigating that I finally shaved some yaks I've been wanting to shave on this path... the end result is that overall we should be doing [very] slightly less work no matter what and using slightly less memory. And on the renderer path, we're doing _way_ less work.
- All loaded font `Face` structures must also check and cache their quirks mode settings. This makes #331 a single boolean check per text run which is effectively free compared to the actual shaping task. The cost is one additional byte on the face structure (well, whatever the alignment is).
- `DeferredFace` now only represents an _unloaded_ face (that can be loaded multiple times). This lowers memory usage significantly (`Face` was large) per deferred face.
- `DeferredFace.name()` now takes a buffer for output instead of just failing on no static space, weird decision. No performance impact but makes this function more robust.
- `Group.addFace` now takes a tagged union representing either a deferred or loaded face, this gets rid of all the weird `DeferredFace.initLoaded` wrappers that were unnecessary and dumb. No real performance impact.
- When a font face is loaded in `Group` (lots of things trigger this), the DeferredFace it came from is now deinitialized immediately. This should save on memory significantly because we free all the font discovery information. We can do this because a deferred face is only ever loaded _once_ when its part of a `Group`, and the group owns the deferred face.
- Auto-italicize moves into `Group` and is no longer duplicated between Deferred and normal Face. No performance change, less code.