From 0be3b6764024f8eaa8d34835be3aa87a95a2b5e1 Mon Sep 17 00:00:00 2001 From: thomas Date: Tue, 28 Jul 2026 08:42:08 +0800 Subject: [PATCH] fix(render): stop italic CJK rendering as unrelated CJK on Windows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every character came out as a different character, one for one, consistently — it read as a broken locale or a mangled encoding, and it was neither. Hack, the bundled default, has no CJK, so those cells are shaped through the font-fallback chain. gpui's Windows backend then threw away the face DirectWrite shaped the run with and looked a fresh one up by family, weight and style. That round trip mapped DirectWrite's italic to oblique — the enum is numbered OBLIQUE = 1, ITALIC = 2, and the mapping had them the other way around — so an italic fallback face resolved to a request for an oblique one, and a family with no oblique face (Maple Mono NF CN, first in our Windows chain) came back as its upright face instead. The glyph indices were right; the outlines they indexed belonged to a different face, at a fixed glyph-id skew. Fixed upstream in our gpui fork by registering the face DirectWrite actually chose rather than re-deriving one, which also closes a latent use-after-free in the same cache: it keyed fonts by a raw pointer to a face nothing held a reference to, so a released face could be aliased by any later allocation. Bumps the fork pin; no tty7 code changes. Covered there by two tests in `gpui_windows::direct_write` — one asserting a shaped run's glyphs round-trip through the font id the run reports, one asserting every font-face cache key is owned by the font it maps to. Co-Authored-By: Claude Opus 5 (1M context) --- CHANGELOG.md | 17 +++++++++++++++++ Cargo.lock | 50 +++++++++++++++++++++++++------------------------- Cargo.toml | 21 +++++++++++++++------ 3 files changed, 57 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 478bb9c1..ea70d003 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,23 @@ All notable changes to tty7 are documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Fixed + +- **Italic CJK rendered as unrelated CJK on Windows** — every character came out + as a different character, one for one, consistently, so it read as a broken + locale or a mangled encoding. It was neither. Hack, the bundled default, has no + CJK, so those cells are shaped by the font-fallback chain; gpui's Windows + backend then threw away the face DirectWrite shaped with and looked a fresh one + up by family, weight and style. That round trip mapped DirectWrite's *italic* + to *oblique* — the two are numbered the other way around in the API — and a + family with no oblique face resolved to its upright one. The glyph indices were + right; the outlines they were pointing into belonged to a different face. Fixed + in our gpui fork by rasterizing the face DirectWrite actually chose, which also + closes a latent use-after-free in the same cache: it keyed fonts by a raw + pointer to a face nothing held a reference to. + ## [26.7.5] - 2026-07-27 ### Added diff --git a/Cargo.lock b/Cargo.lock index f63f5adf..605e3ebf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1369,7 +1369,7 @@ dependencies = [ [[package]] name = "collections" version = "0.1.0" -source = "git+https://github.com/l0ng-ai/zed?branch=tty7#24ed55f4cf1c6f90c2fb4a1db0f33aca949a7f02" +source = "git+https://github.com/l0ng-ai/zed?branch=tty7#87ce3e2d5c16d5704a45196a761c8809e4a386c7" dependencies = [ "gpui_util", "indexmap", @@ -1927,7 +1927,7 @@ dependencies = [ [[package]] name = "derive_refineable" version = "0.1.0" -source = "git+https://github.com/l0ng-ai/zed?branch=tty7#24ed55f4cf1c6f90c2fb4a1db0f33aca949a7f02" +source = "git+https://github.com/l0ng-ai/zed?branch=tty7#87ce3e2d5c16d5704a45196a761c8809e4a386c7" dependencies = [ "proc-macro2", "quote", @@ -3096,7 +3096,7 @@ dependencies = [ [[package]] name = "gpui" version = "0.2.2" -source = "git+https://github.com/l0ng-ai/zed?branch=tty7#24ed55f4cf1c6f90c2fb4a1db0f33aca949a7f02" +source = "git+https://github.com/l0ng-ai/zed?branch=tty7#87ce3e2d5c16d5704a45196a761c8809e4a386c7" dependencies = [ "accesskit", "anyhow", @@ -3287,7 +3287,7 @@ dependencies = [ [[package]] name = "gpui_linux" version = "0.1.0" -source = "git+https://github.com/l0ng-ai/zed?branch=tty7#24ed55f4cf1c6f90c2fb4a1db0f33aca949a7f02" +source = "git+https://github.com/l0ng-ai/zed?branch=tty7#87ce3e2d5c16d5704a45196a761c8809e4a386c7" dependencies = [ "accesskit", "accesskit_unix", @@ -3338,7 +3338,7 @@ dependencies = [ [[package]] name = "gpui_macos" version = "0.1.0" -source = "git+https://github.com/l0ng-ai/zed?branch=tty7#24ed55f4cf1c6f90c2fb4a1db0f33aca949a7f02" +source = "git+https://github.com/l0ng-ai/zed?branch=tty7#87ce3e2d5c16d5704a45196a761c8809e4a386c7" dependencies = [ "accesskit", "accesskit_macos", @@ -3385,7 +3385,7 @@ dependencies = [ [[package]] name = "gpui_macros" version = "0.1.0" -source = "git+https://github.com/l0ng-ai/zed?branch=tty7#24ed55f4cf1c6f90c2fb4a1db0f33aca949a7f02" +source = "git+https://github.com/l0ng-ai/zed?branch=tty7#87ce3e2d5c16d5704a45196a761c8809e4a386c7" dependencies = [ "heck 0.5.0", "proc-macro2", @@ -3396,7 +3396,7 @@ dependencies = [ [[package]] name = "gpui_platform" version = "0.1.0" -source = "git+https://github.com/l0ng-ai/zed?branch=tty7#24ed55f4cf1c6f90c2fb4a1db0f33aca949a7f02" +source = "git+https://github.com/l0ng-ai/zed?branch=tty7#87ce3e2d5c16d5704a45196a761c8809e4a386c7" dependencies = [ "console_error_panic_hook", "gpui", @@ -3409,7 +3409,7 @@ dependencies = [ [[package]] name = "gpui_shared_string" version = "0.1.0" -source = "git+https://github.com/l0ng-ai/zed?branch=tty7#24ed55f4cf1c6f90c2fb4a1db0f33aca949a7f02" +source = "git+https://github.com/l0ng-ai/zed?branch=tty7#87ce3e2d5c16d5704a45196a761c8809e4a386c7" dependencies = [ "schemars", "serde", @@ -3419,7 +3419,7 @@ dependencies = [ [[package]] name = "gpui_util" version = "0.1.0" -source = "git+https://github.com/l0ng-ai/zed?branch=tty7#24ed55f4cf1c6f90c2fb4a1db0f33aca949a7f02" +source = "git+https://github.com/l0ng-ai/zed?branch=tty7#87ce3e2d5c16d5704a45196a761c8809e4a386c7" dependencies = [ "anyhow", "log", @@ -3428,7 +3428,7 @@ dependencies = [ [[package]] name = "gpui_web" version = "0.1.0" -source = "git+https://github.com/l0ng-ai/zed?branch=tty7#24ed55f4cf1c6f90c2fb4a1db0f33aca949a7f02" +source = "git+https://github.com/l0ng-ai/zed?branch=tty7#87ce3e2d5c16d5704a45196a761c8809e4a386c7" dependencies = [ "anyhow", "console_error_panic_hook", @@ -3452,7 +3452,7 @@ dependencies = [ [[package]] name = "gpui_wgpu" version = "0.1.0" -source = "git+https://github.com/l0ng-ai/zed?branch=tty7#24ed55f4cf1c6f90c2fb4a1db0f33aca949a7f02" +source = "git+https://github.com/l0ng-ai/zed?branch=tty7#87ce3e2d5c16d5704a45196a761c8809e4a386c7" dependencies = [ "anyhow", "bytemuck", @@ -3481,7 +3481,7 @@ dependencies = [ [[package]] name = "gpui_windows" version = "0.1.0" -source = "git+https://github.com/l0ng-ai/zed?branch=tty7#24ed55f4cf1c6f90c2fb4a1db0f33aca949a7f02" +source = "git+https://github.com/l0ng-ai/zed?branch=tty7#87ce3e2d5c16d5704a45196a761c8809e4a386c7" dependencies = [ "accesskit", "accesskit_windows", @@ -3800,7 +3800,7 @@ dependencies = [ [[package]] name = "http_client" version = "0.1.0" -source = "git+https://github.com/l0ng-ai/zed?branch=tty7#24ed55f4cf1c6f90c2fb4a1db0f33aca949a7f02" +source = "git+https://github.com/l0ng-ai/zed?branch=tty7#87ce3e2d5c16d5704a45196a761c8809e4a386c7" dependencies = [ "anyhow", "async-compression", @@ -3825,7 +3825,7 @@ dependencies = [ [[package]] name = "http_client_tls" version = "0.1.0" -source = "git+https://github.com/l0ng-ai/zed?branch=tty7#24ed55f4cf1c6f90c2fb4a1db0f33aca949a7f02" +source = "git+https://github.com/l0ng-ai/zed?branch=tty7#87ce3e2d5c16d5704a45196a761c8809e4a386c7" dependencies = [ "rustls", "rustls-platform-verifier", @@ -4946,7 +4946,7 @@ checksum = "7ebb8d8732c6a6df3d8f032a82911cfc747e00efb95cc46e8d0acd5b5b88570c" [[package]] name = "media" version = "0.1.0" -source = "git+https://github.com/l0ng-ai/zed?branch=tty7#24ed55f4cf1c6f90c2fb4a1db0f33aca949a7f02" +source = "git+https://github.com/l0ng-ai/zed?branch=tty7#87ce3e2d5c16d5704a45196a761c8809e4a386c7" dependencies = [ "anyhow", "bindgen", @@ -6044,7 +6044,7 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "perf" version = "0.1.0" -source = "git+https://github.com/l0ng-ai/zed?branch=tty7#24ed55f4cf1c6f90c2fb4a1db0f33aca949a7f02" +source = "git+https://github.com/l0ng-ai/zed?branch=tty7#87ce3e2d5c16d5704a45196a761c8809e4a386c7" dependencies = [ "collections", "serde", @@ -7021,7 +7021,7 @@ dependencies = [ [[package]] name = "refineable" version = "0.1.0" -source = "git+https://github.com/l0ng-ai/zed?branch=tty7#24ed55f4cf1c6f90c2fb4a1db0f33aca949a7f02" +source = "git+https://github.com/l0ng-ai/zed?branch=tty7#87ce3e2d5c16d5704a45196a761c8809e4a386c7" dependencies = [ "derive_refineable", ] @@ -7064,7 +7064,7 @@ checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" [[package]] name = "reqwest_client" version = "0.1.0" -source = "git+https://github.com/l0ng-ai/zed?branch=tty7#24ed55f4cf1c6f90c2fb4a1db0f33aca949a7f02" +source = "git+https://github.com/l0ng-ai/zed?branch=tty7#87ce3e2d5c16d5704a45196a761c8809e4a386c7" dependencies = [ "anyhow", "bytes", @@ -7589,7 +7589,7 @@ dependencies = [ [[package]] name = "scheduler" version = "0.1.0" -source = "git+https://github.com/l0ng-ai/zed?branch=tty7#24ed55f4cf1c6f90c2fb4a1db0f33aca949a7f02" +source = "git+https://github.com/l0ng-ai/zed?branch=tty7#87ce3e2d5c16d5704a45196a761c8809e4a386c7" dependencies = [ "async-task", "backtrace", @@ -8396,7 +8396,7 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "sum_tree" version = "0.1.0" -source = "git+https://github.com/l0ng-ai/zed?branch=tty7#24ed55f4cf1c6f90c2fb4a1db0f33aca949a7f02" +source = "git+https://github.com/l0ng-ai/zed?branch=tty7#87ce3e2d5c16d5704a45196a761c8809e4a386c7" dependencies = [ "heapless", "log", @@ -9775,7 +9775,7 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "util" version = "0.1.0" -source = "git+https://github.com/l0ng-ai/zed?branch=tty7#24ed55f4cf1c6f90c2fb4a1db0f33aca949a7f02" +source = "git+https://github.com/l0ng-ai/zed?branch=tty7#87ce3e2d5c16d5704a45196a761c8809e4a386c7" dependencies = [ "anyhow", "async-fs", @@ -9814,7 +9814,7 @@ dependencies = [ [[package]] name = "util_macros" version = "0.1.0" -source = "git+https://github.com/l0ng-ai/zed?branch=tty7#24ed55f4cf1c6f90c2fb4a1db0f33aca949a7f02" +source = "git+https://github.com/l0ng-ai/zed?branch=tty7#87ce3e2d5c16d5704a45196a761c8809e4a386c7" dependencies = [ "perf", "quote", @@ -11620,7 +11620,7 @@ dependencies = [ [[package]] name = "zlog" version = "0.1.0" -source = "git+https://github.com/l0ng-ai/zed?branch=tty7#24ed55f4cf1c6f90c2fb4a1db0f33aca949a7f02" +source = "git+https://github.com/l0ng-ai/zed?branch=tty7#87ce3e2d5c16d5704a45196a761c8809e4a386c7" dependencies = [ "anyhow", "chrono", @@ -11665,7 +11665,7 @@ dependencies = [ [[package]] name = "ztracing" version = "0.1.0" -source = "git+https://github.com/l0ng-ai/zed?branch=tty7#24ed55f4cf1c6f90c2fb4a1db0f33aca949a7f02" +source = "git+https://github.com/l0ng-ai/zed?branch=tty7#87ce3e2d5c16d5704a45196a761c8809e4a386c7" dependencies = [ "tracing", "tracing-subscriber", @@ -11676,7 +11676,7 @@ dependencies = [ [[package]] name = "ztracing_macro" version = "0.1.0" -source = "git+https://github.com/l0ng-ai/zed?branch=tty7#24ed55f4cf1c6f90c2fb4a1db0f33aca949a7f02" +source = "git+https://github.com/l0ng-ai/zed?branch=tty7#87ce3e2d5c16d5704a45196a761c8809e4a386c7" [[package]] name = "zune-core" diff --git a/Cargo.toml b/Cargo.toml index 9d076654..b1b0a15f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -319,12 +319,21 @@ lto = "thin" codegen-units = 1 # ---- gpui fork ------------------------------------------------------------ -# Our `tty7` branch (cut from the pinned upstream rev, one commit on top) carries -# a single patch: `prefers_ime_for_printable_keys` takes the keystroke, so an -# input handler can answer per key instead of per view. tty7 needs it for -# Option-as-Meta — macOS routes ⌥-chords to the IME whenever a CJK input source -# is active, and without the keystroke there is no way to decline just those -# chords (see `terminal::input::prefers_ime_for_printable_keys`, issue #177). +# Our `tty7` branch (cut from the pinned upstream rev, two commits on top) carries: +# +# 1. `prefers_ime_for_printable_keys` takes the keystroke, so an input handler can +# answer per key instead of per view. tty7 needs it for Option-as-Meta — macOS +# routes ⌥-chords to the IME whenever a CJK input source is active, and without +# the keystroke there is no way to decline just those chords (see +# `terminal::input::prefers_ime_for_printable_keys`, issue #177). +# +# 2. gpui's Windows backend rasterizes a font-fallback run with the face +# DirectWrite actually shaped it with, instead of re-deriving one from the +# face's family/weight/style. The round trip mapped DirectWrite's italic to +# oblique, so italic CJK — which every pane reaches through the fallback chain, +# Hack having no CJK — drew a *different* face's outlines at the shaped glyph +# indices. Every character rendered as an unrelated character, one for one, +# which reads as mojibake rather than as a font bug. # # Patching by source rather than editing the `gpui`/`gpui_platform` pins above is # deliberate: `gpui-component` declares its own `gpui` from the upstream URL, and