Commit Graph

812 Commits

Author SHA1 Message Date
Mitchell Hashimoto
dbfc3eb679 Remove unused imports 2025-11-27 13:37:53 -08:00
Qwerasd
6d65abc489 fix(pkg/freetype): fully correct load flags
These now properly match the FreeType API- compared directly in the unit
tests against the values provided by the FreeType header itself.

This was ridiculously wrong before, like... wow.
2025-11-24 17:57:02 -07:00
Mitchell Hashimoto
2ecaf4a595 font/shaper: fix harfbuzz tests 2025-11-20 22:00:44 -08:00
Mitchell Hashimoto
3d56a3a02b font/shaper: remove old pre-renderstate logic 2025-11-20 22:00:44 -08:00
Mitchell Hashimoto
2d94cd6bbd font: update shaper to support new renderstate 2025-11-20 22:00:43 -08:00
Mitchell Hashimoto
5f3645433c font: round cell height from line height instead of ceiling (#9648)
This change should give more consistent results between high and low DPI
displays, and generally approximate the authorial intent of the metrics
a little better.

Also changed the cell height adjustment to prioritize the top or bottom
when adjusting by an odd number depending on whether the face is higher
or lower in the cell than it "should" be. This should make it easier for
users who have an issue with a glyph protruding from the cell to adjust
the height and resolve it.
2025-11-20 20:00:26 -10:00
Mitchell Hashimoto
ffe4afe538 fix(font/CoreText): make system fallback fonts work again (#9649)
The code that re-creates the font descriptor from scratch using the same
attributes also rubs off the magic dust that makes CoreText not throw a
fit at us for using a "hidden" system font (name prefixed with a dot) by
name when we use the descriptor. This means that a small subset of chars
that only have glyphs in these fallback system fonts like ".CJK Symbols
Fallback HK Regular" and ".DecoType Nastaleeq Urdu UI" would not be able
to be rendered, since when we requested the font with the non-magical
descriptor CoreText would complain in the console and give us Times New
Roman instead.

Using `CTFontDescriptorCreateCopyWithAttributes` to clear the charset
attribute instead of recreating from scratch makes the copy come out
magical, and CoreText lets us instantiate the font from it, yippee!

### ℹ️ For anyone who came to this PR from a search engine rabbit hole
trying to figure this out for your own code:
It seems like if you want to use one of these fonts, you have to avoid
creating a new descriptor from scratch with the name. To create a
descriptor with one of these names that CoreText won't get upset about,
you have to get it in one of these ways (there may be others but these
are ones that definitely work at time of writing):
- Get it from `CTFontCreateForString`(`WithLanguage`) with a character
only provided by that font.
- Get it from `CTFontCreateUIFontForLanguage` or any of the mechanisms
for generally receiving a system font that you don't necessarily have
full control over the exact choice of font with.
- Get it from a collection created with
`CTFontCollectionCreateMatchingFontDescriptors`
- Get it from `CTFontCreateWithGraphicsFont` ... and it seems that for
now, `CGFontCreateWithFontName` lets you use these "hidden" names
without complaining :)

I wouldn't trust any of these methods to necessarily *continue* working
in to the future, since Apple seems to be trying to dissuade
*intentional* use of any particular named hidden system font.
Realistically, the `CreateForString` option is the most likely to stick
around.

If all else fails, check FireFox or Chrome's source code to see how
they're solving it in current year.

And to help it get indexed for those looking for it...
<details>
<summary>CoreText's angry message</summary>

```
CoreText note: Client requested name ".CJKSymbolsFallbackHK-Regular", it will get TimesNewRomanPSMT rather than the intended font. All system UI font access should be through proper APIs such as CTFontCreateUIFontForLanguage() or +[NSFont systemFontOfSize:].
CoreText note: Set a breakpoint on CTFontLogSystemFontNameRequest to debug.
```

</details>
2025-11-20 19:59:34 -10:00
Mitchell Hashimoto
9955b43e0c Underline Drawing Fixes (#9654)
Reworked the undercurl and dotted underline to use z2d, the undercurl is
almost identical (slightly better, it has rounded end caps now) but
without the really complicated code, and the dotted underline is greatly
improved since it now draws as anti-aliased dots that are evenly spaced
instead of rectangles that might have uneven spacing at the wrong font
size.

Also fixes #9394 since I added code that makes sure that none of the
underlines is drawn off of the edge of the canvas (the padding only goes
so far, after all).

|Size|Adjustments|Before|After|
|-|-|-|-|
|32|none|<img width="640" height="964" alt="image"
src="https://github.com/user-attachments/assets/20e44d89-5825-496e-b197-bd1ca6c685f7"
/>|<img width="640" height="964" alt="image"
src="https://github.com/user-attachments/assets/b1fb8499-26fd-42ec-8186-1677ab29048b"
/>|
|11|none|<img width="234" height="358" alt="image"
src="https://github.com/user-attachments/assets/5db89f15-128c-4780-826c-a4f59026a0af"
/>|<img width="234" height="358" alt="image"
src="https://github.com/user-attachments/assets/c9a2855a-284e-46d8-b824-0ada2a0ac386"
/>|
|9|underline +8, overline -8|<img width="206" height="294" alt="image"
src="https://github.com/user-attachments/assets/7b3a6d7e-9bb5-4ebb-8881-57878553a47d"
/>|<img width="206" height="294" alt="image"
src="https://github.com/user-attachments/assets/3a8dc97e-7f71-4c6b-821c-49262e6ff9a9"
/>|
2025-11-20 11:09:05 -10:00
Qwerasd
3280cf7d34 font/sprite: rework dotted underline
Draw proper anti-aliased dots now instead of rectangles, thanks to z2d
this is very easy to do, and the results are very nice, no more weird
gaps in dotted underlines if your cell is the wrong number of pixels
across.
2025-11-20 13:36:09 -07:00
Qwerasd
81a6c24186 font/sprite: rework undercurl, fix out of bounds underlines
Use z2d to draw the undercurl instead of the manual raster code we had
before- the code was cool but unnecessarily complicated. Plus z2d lets
us have rounded caps on the undercurl which is neat.

Also make sure we won't draw off the canvas with our underlines-- the
canvas has padding but it's not infinite.
2025-11-20 12:48:39 -07:00
Qwerasd
1fd7606db6 font: round cell height from line height instead of ceiling
This change should give more consistent results between high and low DPI
displays, and generally approximate the authorial intent of the metrics
a little better.

Also changed the cell height adjustment to prioritize the top or bottom
when adjusting by an odd number depending on whether the face is higher
or lower in the cell than it "should" be. This should make it easier for
users who have an issue with a glyph protruding from the cell to adjust
the height and resolve it.
2025-11-20 10:16:11 -07:00
Qwerasd
a347406137 fix(font/CoreText): make system fallback fonts work again
The code that re-creates the font descriptor from scratch using the same
attributes also rubs off the magic dust that makes CoreText not throw a
fit at us for using a "hidden" system font (name prefixed with a dot) by
name when we use the descriptor. This means that a small subset of chars
that only have glyphs in these fallback system fonts like ".CJK Symbols
Fallback HK Regular" and ".DecoType Nastaleeq Urdu UI" would not be able
to be rendered, since when we requested the font with the non-magical
descriptor CoreText would complain in the console and give us Times New
Roman instead.

Using `CTFontDescriptorCreateCopyWithAttributes` to clear the charset
attribute instead of recreating from scratch makes the copy come out
magical, and CoreText lets us instantiate the font from it, yippee!
2025-11-19 22:03:37 -07:00
Daniel Wennberg
42c1345238 CoreText: Apply subpixel halign also when cell width < advance 2025-11-19 15:32:29 -08:00
Mitchell Hashimoto
410d79b151 Assorted Performance Enhancements (#9645)
A whole bunch of optimizations in hot paths in the IO processing areas
of our code (well, one of them covers everything). I validated that each
commit either improved one or more of our vtebench results, or improved
the time it takes to process 2 years worth (2.4GB) of data from
asciinema.

## vtebench
<img width="1278" height="903" alt="image"
src="https://github.com/user-attachments/assets/bad46777-4606-4870-b7d7-8df0c4bb3b39"
/>

(I decided to patch vtebench to report in nanoseconds instead of
milliseconds since clearly it was not designed for a machine as fast as
mine. Nanoseconds gives much more useful results when the numbers are
this low.)

Do note the *slight* regression in the "unicode" test, this is probably
because I added a branch hint in `Terminal.print` in order to optimize
for printing narrow characters, since they make up the vast majority of
characters typically printed in the terminal, but the vtebench "unicode"
test is pretty much all wide characters.

This shouldn't have a negative effect on users of CJK languages since
it's a *very* slight reduction in speed and they will still be printing
many narrow characters, especially in TUIs; spaces, box drawing
characters, symbols, punctuation, etc.

## asciinema processing
I wrote a program that uses libghostty to push 2 years worth (2.4GB) of
data from publicly uploaded asciinema recordings in to the terminal as
fast as possible- since it's just libghostty, there's no renderer
overhead happening, it's just the core terminal emulation, effectively
everything that io-reader thread does if it didn't have wait for the
renderer ever.

On main, this took roughly 26.1–26.7 seconds to process, on this branch
it takes just 18.4–18.6 seconds, that's a ~30% improvement in raw IO
processing speed when processing real world data!

## Summary of changes
In order of commits:
- Fixed a bug that I hit when trying to have Ghostty process all that
asciinema data, in certain bad cases it was possible to accidentally
insert the `0` hyperlink ID in to a page, which would then cause a
lockup in ReleaseFast mode when trying to clone that page since the
string alloc would try to iterate `1..0` to allocate 0 chunks.
- I noticed in profiling Ghostty that `std.debug.assert` was showing up
in the profile, which it should not have been since its doc comment
promises that it will be optimized out in ReleaseFast- but evidently
something is wrong with Zig, or that comment's promise is based on an
expectation from LLVM that it fails to meet - but either way, by
replacing all uses of `assert` with a version that is explicitly marked
`inline`, that function call overhead in tight loops and hotpaths is
avoided. This change alone accounts for like a third of the IO
processing time improvement, though it had minimal impact on vtebench
scores.
- I optimized the SGR parser somewhat by adding branch hints and
removing the `.reset_underline` action, replacing it with `.{ .underline
= .none }`.
- Gated a somewhat expensive assert in RefCountedSet behind a runtime
safety check.
- Improved the performance of `Style.eql` and `Style.hash` since these
are hot functions, called extremely frequently since adding styles to
the style set is a very common operation. Achieved this by making `eql`
less generic - explicitly comparing each part of the style rather than
looping over fields - and ordering checks from most likely to differ to
least likely to differ so that differences can be found as soon as
possible; and changed the hash from xxhash to simply folding the packed
struct down to 64 bits and then using `std.hash.int`. Also manually
inlined the code from `std.meta.activeTag` in `Packed.fromStyle`, since
profiling showed it in the callstack and it's a single cast so it really
should not have the function call overhead.
- Explicitly marked some trivial functions as inline, the optimizer
would already have been doing this (probably) but doing it explicitly
gives the optimizer more time to spend on other things. Added cold
branch hints to "should be impossible" and error-returning paths that
should be very rare, and unlikely branch hints to a lot of "invalid"
paths- to optimize for receiving valid data.
- Removed a branch in the parser csi param action, just unconditionally
multiply by 10 before adding digit value, even if it's the first digit.
This codepath is rarely hit since we have a fast path for this in the
stream code, but the stream code already has this optimization so I just
copied it over.
- `CharsetState.charsets` used to be an `EnumArray`, but the
layout/access logic for that was less-than-ideal, and the access
functions were not inlining-- and these are very hot since we access
this for every single print, so I wrote a bespoke struct to hold that
info instead, gained a couple percent of IO perf with that.
- Added branch hints based on the data I derived from the asciinema
dump, which gave big boost to vtebench results, especially for the
cursor movement and dense cells tests (which makes sense, since cursor
movement and setting attributes both got `likely` hints :p) -- data at
https://github.com/qwerasd205/asciinema-stats
- This is probably the most invasive change in this PR: I removed the
dirty bitset from `Page` and replaced it with a dirty flag on each row,
for the majority of operations this is faster to write, since the row
being dirtied is probably already loaded and probably will be written to
for other changes as well. This gave a couple percent IO processing
improvement. The only exception is scrolling-type operations, which are
extremely efficient by just moving rows around with a single memmov, so
looping through the rows to mark each dirty slows them down, and indeed
after this change the scrolling benchmarks in vtebench regressed,
*however*...
- Added a "full page dirty" flag on `Page`, which is set when an
operation is performed that dirties most or all the rows in the page,
which is used for scrolling-type operations. This *does* make the dirty
tracking slightly less precise for these operations, but with the
caching and stuff we do in the renderer, I don't think `rebuildCells` is
a bottleneck, so rebuilding a few extra rows shouldn't hurt. After this
change, all the scrolling benchmarks in vtebench improved drastically.
- Tiny micro-improvements to RefCountedSet; streamlined the control flow
in `lookup`, added an unlikely branch hint in `insert` for the branch
that resurrects dead items since dead items aren't that common.
- Improve SGR parser performance again by using `@call(.always_inline`
to explicitly inline calls to `StaticBitSet.isSet` (for the separator
list), since I noticed they weren't being inlined, causing function call
overhead in a hotpath.
- I noticed that `clearGrapheme` and `clearHyperlink` would check every
cell in the row after they were done in order to update the
`grapheme`/`hyperlink` flag on the row if there were none left, which
isn't great since `clearCells` called these functions for multiple cells
in the same row back-to-back, which leads to a ton of excess work. I
separated the flag updating parts of these functions out and called them
only if necessary (if the cells being cleared were the full row then the
flag could unconditionally be set to false) and only after all the cells
were cleared. This gave a nice improvement to IO processing since
clearCells is evidently a very hot function.
- Removed inline annotations on `Page.clearGrapheme` and
`Page.clearHyperlink` in favor of inlining directly at the one callsite
that benefited from inlining, this improved IO processing speed.
- Inlined trivial function `Charset.table`.
- Inlined `size.getOffset` and `size.intFromBase` as they are both
trivial pointer math that often benefits from surrounding context.

---

If you'd like me to separate out the trivial improvements (branch hints,
inline annotations, 1-line changes) from the functionality-changing ones
(pretty much just the changes to dirty tracking), just let me know!
2025-11-19 12:53:48 -10:00
Qwerasd
801a399f41 clarify comment 2025-11-19 11:10:24 -07:00
Charles Nicholson
45b8ce842e Cell width calculation from ceil to round, fix horizontal spacing 2025-11-19 10:56:29 -07:00
Qwerasd
6d5b4a3426 perf: replace std.debug.assert with inlined version
See doc comment in `quirks.zig` for reasoning
2025-11-17 12:13:56 -07:00
Qwerasd
985e1a3cea test(shaper/coretext): test non-monotonic CoreText output 2025-11-16 12:39:10 -07:00
Qwerasd
712cc9e55c fix(shaper/coretext): handle non-monotonic runs by sorting 2025-11-16 12:39:10 -07:00
Mitchell Hashimoto
4ba00dbe89 fix harfbuzz 2025-11-14 15:48:06 -08:00
Mitchell Hashimoto
368f4f565a terminal: Screen opts is a structure 2025-11-14 14:10:37 -08:00
Jeffrey C. Ollie
c965afe066 fix typo in shaper 2025-11-09 12:11:03 -06:00
Daniel Wennberg
ad9f9dc11e font: Default to light hinting in FreeType 2025-10-17 15:04:04 -07:00
Daniel Wennberg
65f73f5d20 font: Apply adjust-icon-height to both large and small icons (#9160)
As pointed out in #9156, an unintended consequence of all the work to
get icon sizing right is that `adjust-icon-height` now only applies to
the small icons you get when the next cell is not whitespace. Large
icons are unaffected.

With this PR, `adjust-icon-height` affects the maximum height of every
symbol specifying the `.icon` constraint height, regardless of
constraint width. This includes most Nerd Font icons, but excludes emoji
and other unicode symbols, and also excludes terminal graphics-oriented
Nerd Font symbols such as Powerline symbols.

In the following screenshots, **Baseline** is without
`adjust-icon-height`, while **Before** and **After** are with
`adjust-icon-height = -25%`.

**Baseline**
<img width="711" height="95" alt="Screenshot 2025-10-11 at 23 28 20"
src="https://github.com/user-attachments/assets/7499db4d-75a4-4dbd-b107-8cb5849e31a3"
/>

**Before** (only small icons affected)
<img width="711" height="95" alt="Screenshot 2025-10-11 at 23 20 12"
src="https://github.com/user-attachments/assets/9afd9fbf-ef25-44cc-9d8e-c39a69875163"
/>


**After** (both small and large icons affected, but not emoji)
<img width="711" height="95" alt="Screenshot 2025-10-11 at 23 21 05"
src="https://github.com/user-attachments/assets/90999f59-3b43-4684-9c8e-2c3c1edd6d18"
/>
2025-10-12 07:31:54 -07:00
Daniel Wennberg
2e34f4e0e5 fix(font): Additional scale group tweaks (#9152)
Of course #9142 would require a minor follow-up!

* Scale groups can cut across patch sets, but not across fonts. We had
some scale group mixing between Font Awesome and the weather symbols,
which is removed by this PR.[^cp_table_full]
* There's one case where a scale group includes a glyph that's not part
of any patch sets, just for padding out the group bounding box.
Previously, an unrelated glyph from a different font would be pulled in.
Now we use an appropriate stand-in. (See code comment for details.)
* I noticed overlaps weren't being split between each side of the
bounding box, they were added to both sides, resulting in twice as much
padding as specified.

Screenshots showing the extra vertical padding for progress bar elements
due to the second bullet point:

**Before**
<img width="191" height="42" alt="Screenshot 2025-10-11 at 15 33 54"
src="https://github.com/user-attachments/assets/cf288cce-86d3-46fd-ae86-18e5c274b0e4"
/>

**After**
<img width="191" height="42" alt="Screenshot 2025-10-11 at 15 33 20"
src="https://github.com/user-attachments/assets/7ac799c7-bf50-4e65-a74a-f8a2c42d2441"
/>

[^cp_table_full]: Forming and using the merged `cp_table_full` table
should have been a red flag. Such a table doesn't make sense, it would
be a one-to-many map. You need the names of the original fonts to
disambiguate.
2025-10-11 19:48:08 -07:00
Daniel Wennberg
4af93975ed font(fix): Extract and apply Nerd Font codepoint mapping table (#9142)
Fixes #9076

**Before** 
<img width="128" height="57" alt="Screenshot 2025-10-11 at 00 07 09"
src="https://github.com/user-attachments/assets/a6b416d5-dae1-4cea-a836-00640ceaf39b"
/>

**After**
<img width="128" height="57" alt="Screenshot 2025-10-11 at 00 07 31"
src="https://github.com/user-attachments/assets/7d2df7b1-4767-4e2d-84d2-8301da5c6602"
/>

These screenshots show the chevrons mentioned in
https://github.com/ghostty-org/ghostty/discussions/7820#discussioncomment-14617170,
which should be scaled as a group but were not until this PR.

The added code downloads each individual symbol font file from the Nerd
Fonts github repo (making sure to get the version corresponding to the
vendored `font-patcher.py`) and iterates over all of them to build the
correct and complete codepoint mapping table. The table is saved to
`nerd_font_codepoint_tables.py`, which `nerd_font_codegen.py` will reuse
if possible instead of downloading the font files again.

I'm not going to utter any famous last words or anything, but... after
this, I don't think the number of remaining issues with icon
scaling/alignment is _large._
2025-10-11 13:12:13 -07:00
Daniel Wennberg
6bc60b6c64 Add comprehensive constraint tests 2025-10-03 22:30:52 -07:00
Daniel Wennberg
5360aeb8aa Fix botched cherry-pick from #8990 2025-10-03 22:20:45 -07:00
Daniel Wennberg
78f1bf1807 Handle font_patcher codepoint range overlaps 2025-10-03 13:50:11 -07:00
Daniel Wennberg
44951d9b1c Handle font_patcher codepoint offsets 2025-10-03 13:50:11 -07:00
Daniel Wennberg
d07237fff5 Skip patchsets and codepoints not in SymbolsNF 2025-10-03 13:50:11 -07:00
Daniel Wennberg
a1b7ea2e71 Add font_patcher's grouped vs individual alignment 2025-10-03 13:50:11 -07:00
Daniel Wennberg
50bdd3bac6 Align stretched glyphs to cell, not face 2025-10-03 13:46:36 -07:00
Daniel Wennberg
32f8c71be3 Always clamp scaled glyph to cell
Also take padding into account for centered alignment, necessary since
our constraint type allows asymmetric padding.
2025-10-03 13:44:22 -07:00
Daniel Wennberg
7acf617763 fix(font): Anchor scaling at bounding box center 2025-10-03 13:44:22 -07:00
Daniel Wennberg
f245574087 Fix comment 2025-10-03 12:53:46 -07:00
Daniel Wennberg
5c129205a5 Use correct and consistent pre-constraint glyph rect
In Freetype, measure rect after emboldening, so constraints apply to the
true glyph size like in CoreText.

In CoreText, don't let font smoothing affect the rect (only the canvas).
2025-10-03 12:53:46 -07:00
Daniel Wennberg
96fbff681b Center before quantizing bitmap glyphs 2025-10-03 12:53:46 -07:00
Mitchell Hashimoto
87b77e1980 ci: cleanup 2025-10-03 07:10:43 -07:00
Mitchell Hashimoto
d59d754e29 Zig 0.15: zig build GTK exe 2025-10-03 07:10:43 -07:00
Mitchell Hashimoto
cb295b84a0 Zig 0.15: zig build test 2025-10-03 07:10:43 -07:00
Mitchell Hashimoto
dfa2354fec Implement and use generic approx equality tester (#8979)
Seems like there needs to be a general, easy-to-use solution for
approximate equality testing of containers holding floats (see, e.g.,
https://github.com/ghostty-org/ghostty/pull/8563#pullrequestreview-3281357931).
How's this?
2025-10-03 07:10:16 -07:00
Qwerasd
efc6e0d673 fix(font/coretext): always prevent shaper from emitting rtl
The solution we had before worked in most cases but there were some
which caused problems still. This is what HarfBuzz's CoreText shaper
backend does, it uses a CTTypesetter with the forced embedding level
attribute. This fixes the failure case I found that was causing non-
monotonic outputs which can have all sorts of unexpected results, and
causes a crash in Debug modes because we assert the monotonicity while
rendering.
2025-10-02 15:32:21 -06:00
Qwerasd
d6063428bd font/coretext: tiny shaper improvements
Reduce potential allocation while processing glyphs by ensuring capacity
in the buffer ahead of time and also using CTRunGet*Ptr functions first
and only allocating for those if that didn't work (it should almost
always work in practice.)
2025-10-02 14:06:58 -06:00
Daniel Wennberg
26b70e3125 Implement and use generic approx equality tester 2025-09-30 12:38:29 -07:00
Mitchell Hashimoto
d261c0a60b Merge branch 'main' into jacob/uucode 2025-09-30 06:52:07 -07:00
Mitchell Hashimoto
0bddaed53b fix(font): Improve FreeType glyph measurements and add unit tests for face metrics (#8738)
Follow-up to #8720 adding

* Two improvements to FreeType glyph measurements:
- Ensuring that glyphs are measured with the same hinting as they are
rendered, ref
[#8720#issuecomment-3305408157](https://github.com/ghostty-org/ghostty/pull/8720#issuecomment-3305408157);
- For outline glyphs, using the outline bbox instead of the built-in
metrics, like `renderGlyph()`.
* Basic unit tests for face metrics and their estimators, using the
narrowest and widest fonts from the resource directory, Cozette Vector
and Geist Mono.

---

I also made one unrelated change to `freetype.zig`, replacing
`@alignCast(@ptrCast(...))` with `@ptrCast(@alignCast(...))` on line
173. Autoformatting has been making this change on every save for weeks,
and reverting the hunk before each commit is getting old, so I hope it's
OK that I use this PR to upstream this decree from the formatter.
2025-09-29 12:24:42 -07:00
Daniel Wennberg
e3ebdc7975 Rewrite constraint code for improved icon scaling/alignment 2025-09-29 12:09:21 -07:00
Jacob Sandlund
7386dae079 use unicode.graphemeBreak in src/font/shaper/web_canvas.zig 2025-09-23 09:43:28 -04:00
Jacob Sandlund
b01770c21c Merge remote-tracking branch 'upstream/main' into jacob/uucode 2025-09-23 09:36:41 -04:00