Texte, Bidi et IME
Des octets aux clusters de graphèmes, des paragraphes bidirectionnels à la composition IME : zenit traite le texte comme une capacité du système, pas comme une rangée de glyphes posés point de code par point de code.
Une ligne, quatre couches
Le stockage, les limites d’édition, la résolution de direction et le shaping de la plateforme ont chacun une seule autorité, et produisent ensemble des lignes visuelles que l’on peut hit-tester et sélectionner. Aucune couche ne décide à la place d’une autre.
src/text_coresrc/text_core/grapheme.zigsrc/i18n/bidi.zigsrc/render/text_renderer.zigGraphèmes, pas points de code
Un emoji famille compte sept scalaires Unicode (quatre personnes et trois ZWJ), é peut être e suivi d’un accent aigu combinant, et un drapeau se compose de deux indicateurs régionaux. Pour l’utilisateur, chacun est un seul caractère. zenit utilise les clusters de graphèmes étendus comme atome pour le déplacement du curseur, la suppression, la sélection et la troncature à la capacité.
Texte bidirectionnel
Le texte est stocké dans l’ordre logique et affiché dans l’ordre visuel. UAX #9 résout d’abord un niveau d’enchâssement pour chaque caractère, puis la règle L2 inverse les runs du niveau le plus élevé vers le bas. Ci-dessous, abc אבג 123 se trouve dans un paragraphe LTR : l’hébreu est résolu au niveau 1 et les chiffres qui le suivent au niveau 2.
Le résolveur UAX #9 est une implémentation portable propre à zenit, dans src/i18n/bidi.zig, avec des tables de propriétés générées à partir de données Unicode 17.0.0 figées. Sur macOS, le shaping final des glyphes et la liaison RTL restent confiés à CoreText un run entier à la fois — le moteur de rendu de texte ne découpe jamais un run RTL point de code par point de code.
Conformité Unicode 17
Les tables de propriétés d’exécution sont générées à partir de données Unicode 17.0.0 figées dans le dépôt ; les fichiers de test officiels sont vendorisés sans modification sous vendor/unicode/17.0.0 et figés par SHA-256 dans SHA256SUMS. Ici, « pris en charge » ne veut pas dire une poignée de tests unitaires sur des emoji — cela veut dire exécuter les suites officielles complètes.
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 vérifie que les tables Zig générées correspondent aux données ; test-text-core exécute tous les cas de GraphemeBreakTest ; test-bidi-conformance exécute les deux fichiers bidi officiels complets (jusqu’à la règle L2 d’UAX #9) et fait aussi partie de zig build test-headless.
L'IME, citoyen de première classe
L’IME, ce n’est pas les caractères finaux déguisés en frappe clavier. Les événements de la plateforme conservent les phases : ime_preedit transporte le texte en composition plus un offset de curseur à l’intérieur, et ime_commit transporte le texte final. Pendant la composition, Input et Textarea affichent un texte marqué souligné, mais le buffer canonique ne change qu’au commit. Il n’y a pas d’événement « annuler » distinct : un preedit vide annule la composition.
ime_preedit / ime_commit conservent la phase ; un preedit vide annuleime_phase, ime_preedit_len, buffer, cursor_pos, anchorscripts/verify_ime.sh parcourt tout le chemin NSTextInputClient avec les sources de saisie Pinyin / japonais du systèmeTester l'IME
Le client E2E expose les deux phases comme des RPC distinctes, et inputState relit l’état réel d’un champ par son 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 compositionConsultez Harness E2E pour le workflow complet du harness.


