1
0
mirror of https://github.com/emilk/egui.git synced 2026-09-03 07:10:04 -04:00

Integrate harfrust for text shaping (#8031)

* Related to #56 (Improve text — tracking issue)

## Summary

This PR integrates [harfrust](https://crates.io/crates/harfrust) (a
pure-Rust port of HarfBuzz) into epaint's text layout pipeline,
replacing the character-by-character glyph positioning with proper
OpenType text shaping.

### What this enables

- **GPOS kerning**: most modern fonts only ship kerning in GPOS tables
(not the legacy `kern` table). Pairs like "AV", "VA", "AT" are now
properly tightened.
- **GSUB substitutions**: ligatures (fi, fl), contextual alternates, and
other OpenType features.
- **Combining marks**: diacritics (e.g. ɔ̃) are positioned via anchor
tables instead of being rendered as standalone replacement glyphs.

### Before/After

#### Kerning, etc.

<img width="838" height="726" alt="before_main"
src="https://github.com/user-attachments/assets/f0f26d5f-b117-43a6-b39c-ea40d2e73836"
/>

<img width="838" height="726" alt="after_harfrust"
src="https://github.com/user-attachments/assets/d983e5da-486c-4f39-bd4f-5782a90c6b39"
/>

 #### Ligatures

<img width="1117" height="698" alt="before_closeup"
src="https://github.com/user-attachments/assets/7a3b08b4-cf6f-45b7-98ba-07c473cd3b02"
/>

<img width="1117" height="698" alt="after_closeup"
src="https://github.com/user-attachments/assets/6cfc5f21-d32f-4f09-be0c-59c8c553d44f"
/>

### Architecture

The shaping integrates into the existing pipeline without changing the
public API:

1. **`Font::segment_into_runs`** — segments text into contiguous runs by
font face (grapheme-cluster aware, never splits combining sequences)
2. **`FontFace::shape_text`** — calls harfrust to shape each run,
returning glyph IDs + positioned advances/offsets
3. **`layout_shaped_run`** — emits `Glyph` structs from the shaping
output, with NOTDEF fallback to other font faces for missing glyphs
4. **Buffer recycling** — `FontsImpl` pools a `harfrust::UnicodeBuffer`
to avoid per-layout allocations

### Disclaimer

I'm far from being a good Rust programmer. Claude Code did most of the
heavy lifting here. I did my best and used my limited knowledge to avoid
making too many mistakes. If this PR isn't up to quality standards,
please don't hesitate to close it.

## Test plan

- [x] `cargo test -p epaint` — all 18 text tests pass, including 6 new
ones
- [x] `cargo clippy -p epaint --all-features` — clean
- [x] `cargo fmt` — clean
- [ ] Snapshot tests need regeneration (expected: shaping changes glyph
positions)
- New tests added:
- `test_gpos_kerning` — verifies GPOS kerning tightens "AV", "VA", "AT"
pairs
- `test_combining_diacritics` — combining tilde doesn't add extra width
  - `test_shaping_basic_latin` — sanity check for Latin text
  - `test_shaping_empty_string` — empty input doesn't panic
  - `test_shaping_multiple_newlines` — newline splitting works correctly
  - `test_shaping_mixed_font_fallback` — Latin + emoji in same string

---------

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
This commit is contained in:
Gautier Cailly
2026-04-06 14:25:04 +02:00
committed by GitHub
parent 33e89e33be
commit 16cad760a5
158 changed files with 998 additions and 502 deletions

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:cf21fe763e9762bca1b0f486e29a6024efcbc106a7f1ac195104acd0621cf8db
size 45107
oid sha256:9c0bf76e2a4d60fd4ba302b2217beaa4b0627614ff7d23294c7f5e6b81a028c7
size 45061

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2f09338e652b965cc9ae7bbb261845cd9c15d79f3d15f3c5b5326ef6d163b606
size 86885
oid sha256:de7db1296bdd127dfd827c6d1cc1a5a840391c3abb558be07faeb7e6612ed6e8
size 86830

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e298244953653e46875053b12b4fe06ee692cb58fc131233ac4172677f0f8b44
size 118961
oid sha256:645519e1e4c196b985b6b422ddf1ba51e7e92d1b0c80972318dcf44dab3022d3
size 118907

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6b9b36acf821cca71f97a3c8468fb925561f3bc2030742aef1e3c1d9e69ccc6f
size 51419
oid sha256:465a3aa235ea69bce6650cf6e100ffc719bba67baa0cb13d7015a0fc54d53d9a
size 51376

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f5ad7a37546d48fc5426c32534a1c452fd0bf8280346dbe6e67ac26f17f3ba8a
size 54626
oid sha256:aac429243686d35096760eb1b7c6a2bcad1c1d00a3341a858a6c71aaff2cf128
size 54582

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c0b61e9d1c2bcbf891a7acd4f3c1d2bd7524133d8165e7e7984998670de5a085
size 55090
oid sha256:8afb610d9e86b324db22849457eb23419c4f3c240e5ee951a13ce8901003bca8
size 55053

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a2e4975e9328a6d72f2c932daddfbb00cebdb2249aceb53f667d4060a1c0ea8a
size 36006
oid sha256:8e1a958b753fee4bec405a74dd7727e5c477c946484ed086c5c4ffee058dd5e8
size 35960

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ac6f9adeef92be9f69cb288ccafda8d522b8c3cde64352cd5369ae63668240c0
size 35973
oid sha256:1887d632d0994efec3dd7cecc41e3ad460c109baa1012d69e005cdc916e3cd24
size 35925