fix: pixel-identical rendering (z2d stroker port) and last P3 items

Resolves the 3 P3 items in docs/rewrite/notes/review-rv12-codex.md
(final-Z15.md):
- Sprite strokes: tiny-skia is replaced by a port of z2d 0.10's stroke
  plotter (sprite/stroke.rs) plus z2d's arc segmentation and truncating
  integer compositing, feeding the ported 4x4 multisample rasterizer.
  All 36 reference sprite sheets now match Ghostty's byte for byte (test
  tolerance 0), and `just visual-parity` with a production build shows
  0 differing pixels (max delta 0) against the installed Ghostty 1.3.1
  for the test page (block and bar cursor) and the tabs+splits scenario,
  terminal area and chrome alike. tiny-skia dependency removed.
- zig_error maps errno values per target via libc (Linux, Darwin/BSD)
  and Win32 codes the way Zig std does on Windows.
- theme=. / theme=.. report "it is a directory" via a port of
  std.fs.path.basename, matching Ghostty.

Test Plan:
- cargo test --workspace --no-fail-fast (3498 passed, exit 0)
- cargo build --profile production && just visual-parity
- just check-macos (both), just check-windows, just check-wasm
This commit is contained in:
ddidderr committed 2026-10-12 14:20:49 +02:00
1 parent 7dd54833b9
commit 6befc09cc8
10 files changed
+1339 -191

No files matched your search

+201 -31
View File
@@ -2,35 +2,133 @@
//! `error.FileNotFound`), following Zig std's posix errno mapping for the
//! calls Ghostty makes (`openat`, `fstat`, `realpath`, `execve`, ...).
/// Zig error name (without the `error.` prefix) for a Linux errno.
/// Zig error name (without the `error.` prefix) for a platform error code:
/// a POSIX `errno` on unix targets (values come from `libc`, so Linux and
/// Darwin/BSD numbering are both right) or a Win32 `GetLastError` code on
/// Windows (the `CreateProcessW` / file-open mapping of Zig std).
#[must_use]
#[cfg(unix)]
pub fn errno_name(errno: i32) -> &'static str {
use libc::{
EACCES,
EAGAIN,
EBUSY,
EEXIST,
EFBIG,
EILSEQ,
EISDIR,
ELOOP,
EMFILE,
ENAMETOOLONG,
ENFILE,
ENODEV,
ENOENT,
ENOMEM,
ENOSPC,
ENOTDIR,
ENXIO,
EOPNOTSUPP,
EOVERFLOW,
EPERM,
EROFS,
ESRCH,
ETXTBSY,
};
match errno {
1 => "PermissionDenied", // EPERM
2 => "FileNotFound", // ENOENT
3 => "ProcessNotFound", // ESRCH
6 | 19 => "NoDevice", // ENXIO, ENODEV
11 => "WouldBlock", // EAGAIN
12 => "SystemResources", // ENOMEM
13 => "AccessDenied", // EACCES
16 => "DeviceBusy", // EBUSY
17 => "PathAlreadyExists", // EEXIST
20 => "NotDir", // ENOTDIR
21 => "IsDir", // EISDIR
23 => "SystemFdQuotaExceeded", // ENFILE
24 => "ProcessFdQuotaExceeded", // EMFILE
26 => "FileBusy", // ETXTBSY
27 | 75 => "FileTooBig", // EFBIG, EOVERFLOW
28 => "NoSpaceLeft", // ENOSPC
30 => "ReadOnlyFileSystem", // EROFS
36 => "NameTooLong", // ENAMETOOLONG
40 => "SymLinkLoop", // ELOOP
84 => "InvalidWtf8", // EILSEQ
95 => "FileLocksNotSupported", // EOPNOTSUPP
EPERM => "PermissionDenied",
ENOENT => "FileNotFound",
ESRCH => "ProcessNotFound",
ENXIO | ENODEV => "NoDevice",
EAGAIN => "WouldBlock",
ENOMEM => "SystemResources",
EACCES => "AccessDenied",
EBUSY => "DeviceBusy",
EEXIST => "PathAlreadyExists",
ENOTDIR => "NotDir",
EISDIR => "IsDir",
ENFILE => "SystemFdQuotaExceeded",
EMFILE => "ProcessFdQuotaExceeded",
ETXTBSY => "FileBusy",
EFBIG | EOVERFLOW => "FileTooBig",
ENOSPC => "NoSpaceLeft",
EROFS => "ReadOnlyFileSystem",
ENAMETOOLONG => "NameTooLong",
ELOOP => "SymLinkLoop",
EILSEQ => "InvalidWtf8",
EOPNOTSUPP => "FileLocksNotSupported",
_ => "Unexpected",
}
}
/// See the unix variant; this is the Windows (Win32 error code) table.
#[must_use]
#[cfg(windows)]
#[allow(non_upper_case_globals)] // windows-sys spells two constants `..._64k`
pub fn errno_name(code: i32) -> &'static str {
use windows_sys::Win32::Foundation::{
ERROR_ACCESS_DENIED,
ERROR_AUTODATASEG_EXCEEDS_64k,
ERROR_BAD_EXE_FORMAT,
ERROR_BAD_FORMAT,
ERROR_COMMITMENT_LIMIT,
ERROR_DIRECTORY,
ERROR_DISK_FULL,
ERROR_DYNLINK_FROM_INVALID_RING,
ERROR_EXE_MACHINE_TYPE_MISMATCH,
ERROR_EXE_MARKED_INVALID,
ERROR_FILE_NOT_FOUND,
ERROR_FILENAME_EXCED_RANGE,
ERROR_INFLOOP_IN_RELOC_CHAIN,
ERROR_INVALID_EXE_SIGNATURE,
ERROR_INVALID_MINALLOCSIZE,
ERROR_INVALID_MODULETYPE,
ERROR_INVALID_NAME,
ERROR_INVALID_SEGDPL,
ERROR_INVALID_STACKSEG,
ERROR_INVALID_STARTING_CODESEG,
ERROR_IOPL_NOT_ENABLED,
ERROR_ITERATED_DATA_EXCEEDS_64k,
ERROR_PATH_NOT_FOUND,
ERROR_RELOC_CHAIN_XEEDS_SEGLIM,
ERROR_RING2SEG_MUST_BE_MOVABLE,
ERROR_SHARING_VIOLATION,
};
match code as u32 {
ERROR_FILE_NOT_FOUND | ERROR_PATH_NOT_FOUND | ERROR_DIRECTORY => "FileNotFound",
ERROR_ACCESS_DENIED => "AccessDenied",
ERROR_INVALID_NAME => "InvalidName",
ERROR_FILENAME_EXCED_RANGE => "NameTooLong",
ERROR_SHARING_VIOLATION => "FileBusy",
ERROR_COMMITMENT_LIMIT => "SystemResources",
ERROR_DISK_FULL => "NoSpaceLeft",
ERROR_BAD_FORMAT
| ERROR_INVALID_STARTING_CODESEG
| ERROR_INVALID_STACKSEG
| ERROR_INVALID_MODULETYPE
| ERROR_INVALID_EXE_SIGNATURE
| ERROR_EXE_MARKED_INVALID
| ERROR_BAD_EXE_FORMAT
| ERROR_ITERATED_DATA_EXCEEDS_64k
| ERROR_INVALID_MINALLOCSIZE
| ERROR_DYNLINK_FROM_INVALID_RING
| ERROR_IOPL_NOT_ENABLED
| ERROR_INVALID_SEGDPL
| ERROR_AUTODATASEG_EXCEEDS_64k
| ERROR_RING2SEG_MUST_BE_MOVABLE
| ERROR_RELOC_CHAIN_XEEDS_SEGLIM
| ERROR_INFLOOP_IN_RELOC_CHAIN
| ERROR_EXE_MACHINE_TYPE_MISMATCH => "InvalidExe",
_ => "Unexpected",
}
}
/// Targets without a native error-code table (wasm).
#[must_use]
#[cfg(not(any(unix, windows)))]
pub fn errno_name(_code: i32) -> &'static str {
"Unexpected"
}
/// `error.Name` text for an I/O error.
#[must_use]
pub fn io_error_name(err: &std::io::Error) -> String {
@@ -45,19 +143,91 @@ pub fn io_error_name(err: &std::io::Error) -> String {
}
}
#[cfg(all(test, unix))]
#[cfg(test)]
mod tests {
use super::*;
#[cfg(unix)]
fn n(e: i32) -> String {
io_error_name(&std::io::Error::from_raw_os_error(e))
}
#[cfg(unix)]
#[test]
fn names() {
let n = |e| io_error_name(&std::io::Error::from_raw_os_error(e));
assert_eq!(n(2), "error.FileNotFound");
assert_eq!(n(20), "error.NotDir");
assert_eq!(n(13), "error.AccessDenied");
assert_eq!(n(21), "error.IsDir");
assert_eq!(n(36), "error.NameTooLong");
assert_eq!(n(40), "error.SymLinkLoop");
fn libc_constants_map() {
assert_eq!(n(libc::ENOENT), "error.FileNotFound");
assert_eq!(n(libc::ENOTDIR), "error.NotDir");
assert_eq!(n(libc::EACCES), "error.AccessDenied");
assert_eq!(n(libc::EISDIR), "error.IsDir");
assert_eq!(n(libc::ENAMETOOLONG), "error.NameTooLong");
assert_eq!(n(libc::ELOOP), "error.SymLinkLoop");
assert_eq!(n(libc::EAGAIN), "error.WouldBlock");
assert_eq!(n(libc::EOPNOTSUPP), "error.FileLocksNotSupported");
assert_eq!(n(9999), "error.Unexpected");
}
#[cfg(any(target_os = "linux", target_os = "android"))]
#[test]
fn linux_table() {
for (code, name) in [
(1, "PermissionDenied"),
(2, "FileNotFound"),
(11, "WouldBlock"),
(13, "AccessDenied"),
(20, "NotDir"),
(21, "IsDir"),
(36, "NameTooLong"),
(40, "SymLinkLoop"),
(75, "FileTooBig"),
(84, "InvalidWtf8"),
(95, "FileLocksNotSupported"),
] {
assert_eq!(errno_name(code), name);
}
}
#[cfg(target_vendor = "apple")]
#[test]
fn darwin_table() {
for (code, name) in [
(1, "PermissionDenied"),
(2, "FileNotFound"),
(13, "AccessDenied"),
(20, "NotDir"),
(21, "IsDir"),
(35, "WouldBlock"),
(62, "SymLinkLoop"),
(63, "NameTooLong"),
(84, "FileTooBig"),
(92, "InvalidWtf8"),
(102, "FileLocksNotSupported"),
] {
assert_eq!(errno_name(code), name);
}
// Linux-only numbers must not leak through.
assert_eq!(errno_name(36), "Unexpected");
assert_eq!(errno_name(40), "Unexpected");
}
#[cfg(windows)]
#[test]
fn windows_table() {
for (code, name) in [
(2, "FileNotFound"),
(3, "FileNotFound"),
(5, "AccessDenied"),
(32, "FileBusy"),
(112, "NoSpaceLeft"),
(123, "InvalidName"),
(193, "InvalidExe"),
(206, "NameTooLong"),
(267, "FileNotFound"),
(1455, "SystemResources"),
(87, "Unexpected"),
] {
assert_eq!(errno_name(code), name);
}
let e = std::io::Error::from_raw_os_error(5);
assert_eq!(io_error_name(&e), "error.AccessDenied");
}
}