Skip to content
21 changes: 21 additions & 0 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ pub fn build(b: *std.Build) void {
exe.subsystem = .windows;
}
attachWindowChrome(b, exe);
attachTextPlatform(b, exe);
exe.root_module.addWin32ResourceFile(.{
.file = b.path("assets/shellowo.rc"),
.include_paths = &.{b.path("assets")},
Expand All @@ -37,6 +38,7 @@ pub fn build(b: *std.Build) void {
.target = target,
.optimize = optimize,
.backend = .sdl3,
.sdl3_ttf = true,
.system_include_path = system_include_path,
.system_framework_path = system_framework_path,
.library_path = library_path,
Expand Down Expand Up @@ -159,6 +161,7 @@ pub fn build(b: *std.Build) void {
});
attachNativeDeps(b, tests, native_deps);
attachWindowChrome(b, tests);
attachTextPlatform(b, tests);
tests.root_module.addImport("dvui", dvui_dep.module("dvui_sdl3"));
tests.root_module.addAnonymousImport("shellowo-ssh-status-script", .{
.root_source_file = b.path("assets/script/ssh_status_linux.sh"),
Expand Down Expand Up @@ -239,6 +242,24 @@ fn attachWindowChrome(b: *std.Build, compile: *std.Build.Step.Compile) void {
}
}

fn attachTextPlatform(b: *std.Build, compile: *std.Build.Step.Compile) void {
switch (compile.root_module.resolved_target.?.result.os.tag) {
.macos => {
compile.root_module.addCSourceFile(.{
.file = b.path("src/backends/text/platform_fonts_macos.c"),
.flags = &.{},
});
compile.root_module.addCSourceFile(.{
.file = b.path("src/backends/text/platform_emoji_macos.m"),
.flags = &.{"-fobjc-arc"},
});
compile.root_module.linkFramework("CoreFoundation", .{});
compile.root_module.linkFramework("CoreText", .{});
},
else => {},
}
}

const NativeDeps = struct {
mbedcrypto: *std.Build.Step.Compile,
libssh2: *std.Build.Step.Compile,
Expand Down
4 changes: 2 additions & 2 deletions build.zig.zon
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
.minimum_zig_version = "0.16.0",
.dependencies = .{
.dvui = .{
.url = "git+https://github.com/david-vanderson/dvui?ref=main#9707de2c7e9408b9326bb3c2c83ca0aa603fddca",
.hash = "dvui-0.5.0-dev-AQFJmbbs9gAKq7l6sNTZ5958PkPBH_dJ7rlbc4D7oYMs",
.url = "https://codeload.github.com/HissingRat/dvui/tar.gz/aa20ca2b1d27c91e82640cedebafe57157524d3d",
.hash = "dvui-0.5.0-dev-AQFJmZ4nTwEV5QgY8zlsVF-_s923qRRlUbbC_2sKCWnx",
},
// Keep SDL's Linux platform metadata eager at the application root.
// Without this, SDL's second-level lazy dependency can be absent while
Expand Down
6 changes: 6 additions & 0 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ Shellow 第一版要解决的是“原生桌面远程工作台”的核心闭环
- 远程文件编辑器:加载、查找/替换、UTF-8/BOM/ASCII 与换行识别、远端冲突检查、临时文件原子替换和未保存关闭确认
- 系统信息面板雏形与进程/网络快照
- 设置与主题系统基础:`owoConfig.json`、Light/Dark、窗口/布局尺寸和下载路径持久化
- SDL3_ttf + FreeType + HarfBuzz shaped-text backend;DVUI 测量、绘制、
TextLayout hit testing、caret/selection 与 TextEntry cluster 边界共享同一
layout source,Zed Mono + Noto CJK fallback chain 已接入
- 三平台 CI 构建、nightly/tag Release、macOS `.app` 基础打包

尚未具备:
Expand Down Expand Up @@ -141,6 +144,8 @@ Shellow/
backends/
ssh/
libssh2.zig
text/
sdl_ttf.zig
terminal/
libvterm.zig
libvterm_shim.c
Expand All @@ -161,6 +166,7 @@ Shellow/
transfers/
progress_store.zig
ui/
fonts.zig
foundation/
widgets/
layouts/
Expand Down
57 changes: 52 additions & 5 deletions docs/integration-inventory.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@
| `dvui` | 原生 UI、窗口 app lifecycle、widgets | `build.zig`, `src/main.zig` | Shellow 主界面走 DVUI,不用 Electron/WebView 替代主 UI。 |
| `dvui_sdl3` | SDL3 backend | `build.zig` | 通过 `b.dependency("dvui", .{ .backend = .sdl3 })` 导入。 |

当前 DVUI 使用 `HissingRat/dvui` fork 的固定 commit。fork 基于原 Shellowo
pin 的 DVUI commit,增加了可选 `TextEngine` 契约,使字体测量、绘制、
TextLayout 命中和 TextEntry cluster 边界可以由同一 shaped-text backend
提供。未安装 text engine 时仍保留 DVUI 原始字体路径。

维护原则:

- 新 UI 优先使用 DVUI widget 和布局能力。
Expand Down Expand Up @@ -36,7 +41,49 @@
通过 `src/platform/macos_window_chrome.m` 调整 SDL 创建的原生
`NSWindow`,保留系统交通灯和 fullscreen 行为;平台 handle 不进入产品 UI。

## 3. SSH / SFTP
## 3. Text Shaping: SDL3_ttf / FreeType / HarfBuzz

当前实现:

- SDL3_ttf 3.2.2 源码随 Shellowo 的 DVUI fork 固定,构建时启用 HarfBuzz。
- SDL3_ttf 使用 DVUI 同一份 SDL3 3.4.4 renderer/backend。
- FreeType 使用 DVUI 已有的固定依赖,HarfBuzz 源码随 fork 固定。
- `src/backends/text/sdl_ttf.zig` 是 Shellowo-owned backend,负责
`TTF_TextEngine`、字体、fallback、按需 `TTF_Text` layout 生命周期以及
metrics/emoji texture cache。
- Zed Mono Extended 的 regular/bold/italic 是主字体,Noto Sans CJK SC
通过 SDL3_ttf fallback chain 提供中文 glyph。
- macOS 通过 `src/backends/text/platform_fonts_macos.c` 使用 CoreText
发现 Apple Color Emoji、Apple Symbols 和系统 cascade 字体;这些系统
字体作为 SDL3_ttf fallback chain 的后续候选,用于 emoji、符号和更广
Unicode 覆盖。Windows/Linux 保留同一 Shellowo-owned 边界,后续分别接
DirectWrite/known fonts 与 Fontconfig。
- macOS 还通过 `src/backends/text/platform_emoji_macos.m` 提供
AppKit-backed emoji bitmap overlay。普通文字的 geometry 仍来自
SDL3_ttf;overlay 只在 render 阶段补 Apple Color Emoji 这类当前
FreeType/SDL_ttf 构建无法直接 rasterize 的彩色 glyph。
- DVUI 的测量、绘制、鼠标命中、caret、selection 和 TextEntry
cluster movement 使用同一个 text engine。
- terminal 继续由 libvterm cell grid 决定列宽、选择和 cursor;启用 shaped
backend 时终端 glyph 按 cell 绘制,避免 ligature 改变占用列数。

维护原则:

- Raw `TTF_Font`、`TTF_Text` 和 `TTF_TextEngine` 只允许出现在
`src/backends/text/`。
- 平台字体发现只能返回候选路径/元数据;UI 不直接调用 CoreText、
DirectWrite 或 Fontconfig。
- emoji overlay 不得成为第二套测量/命中系统;如果未来引入通用 emoji
atlas,必须显式接入 TextEngine 的 layout、caret、selection 和 hit
testing。
- 不允许重新引入“绘制走 SDL3_ttf、测量走 DVUI”的双几何路径。
- 新增字体 fallback 时必须保持主字体和 fallback 的 size/style 一致。
- 更新 SDL3_ttf、FreeType、HarfBuzz 或 DVUI fork 后必须执行
`zig build test` 和 `zig build`,并复测编辑器、IME、terminal cell 对齐。
- SDL3_ttf renderer text engine 是 window/renderer scoped;必须在 SDL
renderer 销毁之前释放。

## 4. SSH / SFTP

当前实现:

Expand Down Expand Up @@ -75,7 +122,7 @@
| `third_party/libssh2-1.11.1` | vendored libssh2 1.11.1 source。 |
| `third_party/mbedtls-3.6.6` | vendored mbedTLS 3.6.6 source for libssh2 crypto backend。 |

## 4. Terminal Emulator
## 5. Terminal Emulator

当前实现:

Expand Down Expand Up @@ -109,7 +156,7 @@
| `src/backends/terminal/libvterm_shim.c` | C shim,负责把 libvterm bitfield cell/color 数据转成 Zig 可直接消费的 plain struct。 |
| `third_party/libvterm-0.3.3` | vendored libvterm 0.3.3 source。 |

## 5. 本地存储
## 6. 本地存储

当前实现:

Expand All @@ -125,7 +172,7 @@
- 平台安全存储
- 无 Master Password 模式的发布级凭据策略

## 6. 新依赖准入规则
## 7. 新依赖准入规则

新增或替换第三方项目时,至少补齐:

Expand All @@ -135,7 +182,7 @@
4. 如果改变分层边界,同步更新 `docs/architecture.md` 或 `docs/decisions/`。
5. 跑 `zig build`。

## 7. 打包与发布
## 8. 打包与发布

当前实现:

Expand Down
211 changes: 211 additions & 0 deletions docs/plans/active/sdl3-ttf-font-system.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
# SDL3_ttf Font System

Implementation status: complete; awaiting user visual and functional
acceptance.

## Background

Shellowo currently uses DVUI's built-in font cache and codepoint renderer. Text
measurement, line wrapping, rendering, selection geometry, mouse hit testing,
caret placement, and editor cursor movement are all derived from that path.

Replacing only glyph drawing would create two geometry sources: SDL3_ttf would
shape the visible text while DVUI would continue measuring individual Unicode
codepoints. Ligatures, combining marks, fallback fonts, emoji sequences, and
bidirectional text would then produce incorrect selections and cursor
positions.

The new system therefore treats SDL3_ttf's shaped text as the single geometry
source for flow text and editable text. Terminal rendering remains a fixed cell
grid: libvterm owns terminal columns, while SDL3_ttf supplies font metrics and
glyph rendering constrained to those columns.

## Goals

- Use SDL3_ttf with FreeType and HarfBuzz for application text.
- Keep one shaped layout result for measurement, wrapping, rendering, hit
testing, caret geometry, and selection geometry.
- Support explicit font fallback instead of switching an entire string to the
CJK font when any non-ASCII byte is present.
- Preserve DVUI widgets and immediate-mode layout.
- Keep raw SDL3_ttf handles inside a Shellowo-owned backend.
- Preserve the terminal's libvterm cell geometry and PTY resize contract.
- Keep the DVUI changes isolated in `HissingRat/dvui`.

## Non-goals

- Replacing DVUI as the UI framework.
- Changing SSH, SFTP, transfer, storage, or session runtime behavior.
- Implementing a new terminal emulator.
- Packaging or signing the macOS application in this change.
- Completing visual acceptance without user review.

## Architecture

```text
DVUI Font / TextLayoutWidget / TextEntryWidget
|
v
dvui.TextEngine contract
|
v
Shellowo SDL3_ttf text backend
| |
v v
TTF_Text layout SDL_Renderer draw
|
+-- measure and wrap
+-- point to UTF-8 byte boundary
+-- UTF-8 range to selection rectangles
+-- UTF-8 offset to caret rectangle
+-- previous and next cluster boundary
```

The text backend is window-scoped because a renderer text engine is associated
with an SDL renderer. Font data and layout caches remain backend-owned.

## DVUI Fork Work

Baseline: the commit currently pinned by Shellowo,
`9707de2c7e9408b9326bb3c2c83ca0aa603fddca`.

Required changes:

1. Add a public `TextEngine` contract.
2. Allow a window to install an optional text engine.
3. Route `Font.textSizeEx()` through the installed engine.
4. Route deferred `renderText()` commands through the installed engine.
5. Route TextLayout point hit testing and caret/selection geometry through the
same layout source.
6. Route TextEntry left/right movement and backspace/delete through shaped
cluster boundaries.
7. Preserve the current renderer as the fallback when no engine is installed.
8. Add contract and fallback regression tests.

## Shellowo Backend Work

New boundary:

```text
src/backends/text/sdl_ttf.zig
```

Responsibilities:

- Initialize and quit SDL3_ttf.
- Create one renderer text engine per SDL/DVUI window.
- Open embedded fonts from memory.
- Build normal, bold, and italic face variants.
- Attach the CJK face as fallback.
- Create and release `TTF_Text` layouts on demand, while caching reusable font
faces, simple metrics, font heights, and emoji textures.
- Expose measurement, drawing, hit testing, caret, selection, and cluster
boundaries through `dvui.TextEngine`.
- Apply and restore the SDL renderer clip rectangle around direct text draws.
- Destroy text layouts, fonts, and engines before the SDL renderer is destroyed.

## Build and Dependency Work

- Add SDL3_ttf 3.2.2 source as a pinned third-party dependency in the DVUI fork.
- Build it statically against the same SDL3 used by DVUI.
- Build and link pinned FreeType and HarfBuzz dependencies.
- Disable optional SVG/color-font dependencies for the first integration.
- Update `docs/integration-inventory.md`.
- Pin Shellowo's DVUI dependency to the pushed `HissingRat/dvui` commit.

## UI Font Policy

The primary application family remains Zed Mono Extended. The first fallback is
Noto Sans CJK SC.

The old `needsCjkFont()` whole-string switch is removed from layout decisions.
Mixed strings are shaped as one logical string with fallback handled by the
font backend. Existing baseline helpers may remain temporarily only where the
terminal grid needs an explicit visual correction.

## Editable Text Policy

- External selection state continues to use UTF-8 byte offsets.
- Mouse hit testing returns a valid shaped cluster boundary.
- Left/right movement advances between shaped cluster boundaries.
- Backspace/Delete remove one shaped cluster.
- Selection rectangles come from the shaped layout.
- Wrapped-line vertical navigation uses shaped caret positions.
- Search remains byte-based, but scrolling to a match queries the actual text
layout instead of independently estimating line breaks.

## Remote Editor Work

- Keep DVUI TextEntry storage and editing behavior.
- Replace `visualYOfOffset()`'s independent `Font.textSizeEx()` wrapping loop
with a caret/layout query from the active text engine.
- Invalidate layout cache on text, width, font, scale, or wrap changes.
- Preserve the 64 MiB editor limit and current search/replace behavior.

## Terminal Grid Policy

The terminal is not flow text.

- libvterm cell width remains the source of columns and cursor positions.
- PTY resize, selection, mouse hit testing, and cursor rectangles continue to
use `TerminalMetrics`.
- `TerminalMetrics` obtains glyph height and monospace advance from SDL3_ttf.
- Terminal text is shaped per style run, but each run is constrained to the
cell span provided by libvterm.
- Ligatures must not alter the occupied terminal column count.
- Wide characters continue to occupy the width reported by libvterm.
- IME composition is drawn with the same font backend at the terminal cursor
cell.

## Tests

Automated checks should cover:

- Text engine fallback when no custom engine is installed.
- Measurement and render dispatch through the same engine.
- UTF-8 byte boundaries for combining marks and multi-codepoint clusters.
- Point hit testing and caret geometry.
- Selection rectangles over shaped clusters.
- Mixed Latin/CJK fallback.
- Wrapped editor caret lookup.
- Terminal monospace metrics and unchanged grid hit testing.

The implementation includes DVUI dispatch/fallback coverage plus Shellowo
UTF-8 fallback-boundary tests. Geometry cases that require an active renderer
remain in the manual acceptance matrix below.

Manual user acceptance should cover:

- Latin ligatures such as `fi`.
- Combining marks.
- Chinese and Latin mixed text.
- Emoji and ZWJ sequences where supported by the configured fonts.
- Arabic/RTL geometry.
- Mouse selection, keyboard selection, cursor movement, and deletion.
- Remote editor wrapping and search navigation.
- Terminal ASCII, CJK, Powerline, IME, cursor, and selection alignment.

## Delivery Sequence

1. Fork DVUI and branch from the Shellowo-pinned commit.
2. Implement and test the text engine contract.
3. Push the DVUI branch.
4. Add SDL3_ttf, FreeType, and HarfBuzz to Shellowo.
5. Implement the Shellowo text backend and window lifecycle.
6. Replace whole-string font switching with fallback chains.
7. Align TextLayout/TextEntry and the remote editor.
8. Align terminal metrics and rendering without changing cell geometry.
9. Run `zig build test`.
10. Run `zig build`.
11. Push both repositories for user visual and functional acceptance.

## Completion Criteria

- Shellowo uses the `HissingRat/dvui` fork.
- SDL3_ttf is initialized and used for application text.
- Measurement and drawing share SDL3_ttf shaping.
- Editable text uses shaped hit testing and cluster boundaries.
- Remote editor search scrolling uses actual layout geometry.
- Terminal grid dimensions and cursor positioning remain stable.
- `zig build test` passes.
- `zig build` passes.
Loading
Loading