Texto, Bidi e IME
De los bytes a los clústeres de grafemas, los párrafos bidireccionales y la composición del IME: zenit trata el texto como una capacidad del sistema, no como una fila de glifos colocados por punto de código.
Una línea, cuatro capas
El almacenamiento, los límites de edición, la resolución de dirección y el shaping de la plataforma tienen cada uno una única autoridad, y juntos producen líneas visuales en las que puedes hacer hit-test y seleccionar. Ninguna capa decide por otra.
src/text_coresrc/text_core/grapheme.zigsrc/i18n/bidi.zigsrc/render/text_renderer.zigGrafemas, no puntos de código
Un emoji de familia son siete escalares Unicode (cuatro personas y tres ZWJ), é puede ser e más un acento agudo combinante, y una bandera son dos indicadores regionales. Para el usuario, cada uno es un solo carácter. zenit usa clústeres de grafemas extendidos como átomo para el movimiento del cursor, el borrado, la selección y el truncado por capacidad.
Texto bidireccional
El texto se almacena en orden lógico y se muestra en orden visual. UAX #9 primero resuelve un nivel de incrustación para cada carácter y luego la regla L2 invierte los tramos desde el nivel más alto hacia abajo. Abajo, abc אבג 123 está en un párrafo LTR: el hebreo resuelve al nivel 1 y los dígitos que lo siguen al nivel 2.
El resolvedor UAX #9 es una implementación portable propia de zenit en src/i18n/bidi.zig, con tablas de propiedades generadas a partir de datos fijados de Unicode 17.0.0. En macOS, el shaping final de glifos y la unión RTL se siguen entregando a CoreText un tramo completo a la vez: el renderizador de texto nunca divide un tramo RTL por punto de código.
Conformidad con Unicode 17
Las tablas de propiedades en tiempo de ejecución se generan a partir de datos de Unicode 17.0.0 fijados en el repositorio; los archivos de prueba oficiales se incluyen sin modificar en vendor/unicode/17.0.0 y se fijan por SHA-256 en SHA256SUMS. Aquí, “compatible” no significa un puñado de tests unitarios de emoji: significa ejecutar las suites oficiales completas.
python3 tools/generate_grapheme_data.py --check
python3 tools/generate_bidi_data.py --check
(cd vendor/unicode/17.0.0 && shasum -a 256 -c SHA256SUMS)
zig build test-text-core
zig build test-bidi-conformance--check confirma que las tablas Zig generadas coinciden con los datos; test-text-core ejecuta todos los casos de GraphemeBreakTest; test-bidi-conformance ejecuta los dos archivos bidi oficiales completos (hasta la regla L2 de UAX #9) y también forma parte de zig build test-headless.
IME como ciudadano de primera clase
El IME no son los caracteres finales disfrazados de pulsación de tecla. Los eventos de la plataforma conservan las fases: ime_preedit lleva el texto en composición más un offset del cursor dentro de él, e ime_commit lleva el texto final. Durante la composición, Input y Textarea renderizan texto marcado subrayado, pero el buffer canónico solo cambia al hacer commit. No hay un evento “descartar” aparte: un preedit vacío cancela la composición.
ime_preedit / ime_commit conservan la fase; un preedit vacío cancelaime_phase, ime_preedit_len, buffer, cursor_pos, anchorscripts/verify_ime.sh recorre la ruta completa de NSTextInputClient con las fuentes de entrada Pinyin / japonés del sistemaProbar el IME
El cliente E2E expone ambas fases como RPC separadas, e inputState lee de vuelta el estado real de un input por su test id.
import { imeCommit, imePreedit, inputState } from "./client";
// The input must already have focus (e.g. click it first).
await imePreedit("nihongo");
let s = await inputState("story.input.name");
// s.ime_phase === "composing", s.ime_preedit_len === 7, s.buffer === ""
await imeCommit("日本語");
s = await inputState("story.input.name");
// s.buffer === "日本語", s.ime_preedit_len === 0
await imePreedit(""); // empty preedit cancels an active compositionConsulta Harness E2E para ver el flujo completo del harness.


