The alphabet by ear - Says each letter by its name (keyboard/letter_names.json), never the bare key a voice reads as a word, and the app's own words in the language. The names are data with a review: drafted, checked by an independent back-reading and a register check, disputes decided and checked again (rounds 2-3), then a voice check with espeak-ng (tools/keyboard/check_letter_names.py). 43 of 45 languages agreed; Amharic (ኸ) and Tagalog (z, v, e) are not, and there it says it is not ready yet. The readers were AI models, not native speakers, and the table says so. - Its own speech path (tts::say_cue): the device voice of the language, no text_for_speech, and never over or cancelling the person's words; after Speak the next cue waits until the message has been said. It does not start without a voice that speaks the language, and says why. - Offers a letter's marks, a kana's forms and an Ethiopic consonant's orders after it; reads a lone suggestion as the word; Stop is in the last group; a key reaches it from every keyboard. Each choice records whether a tap relayed it; "only my own switch or look" is a setting; a message spelled with relayed choices is learned only when the person keeps it (Item::Learn) with their own yes. A panel open takes the switch first. Keyboards - National layouts from published standards, both layers, read from xkeyboard-config (tools/keyboard/build_layouts.py): InScript (IS 13194) for the nine Indic languages, Kedmanee (TIS 820-2538) for Thai, CRULP phonetic for Urdu; Shift's layer is a page at the same positions. - Defaults follow the access method (kbd::suggested): most used letters first for a switch, colour groups (or alphabetical, large keys) for gaze; a key size setting. A Board key leaves text mode. Amharic syllable pages have Back. Malayalam signs shown bare (WebKit drew two circles). - Pinyin: each candidate covers its own letters, ranked by the likeliest split (不 + 舒服, not 部署 + 副); ▸▸ pages through them; capitals read as the letters; no conversion during a device IME's composition; a failed download is said once and retried with back-off; the note says exactly what it does. Access and fit - With the on-screen keys, the box never takes a switch's Space or Enter, and a chosen suggestion no longer moves the focus into it. - A look must leave a key before it can choose again. - Keys never shrink below 24 x 32 px: on a phone on its side the box goes beside the keys, and the keyboard scrolls with the scan's highlight kept in view; the colour frame's text keeps its end in view; the notes fold to a line; the colour marks sit on badges with 4.5:1 ink; toggles show in dark and high-contrast; dir=auto on the box and what by-ear shows. - Phrases page: Add to bar and Recent. Unreadable keyboard settings are not overwritten at start. First letters keeps where it reads from when text mode comes back. Credits: Settings -> Keyboard lists every word list's source and licence; the pinyin licence file carries pypinyin's, pinyin-data's, phrase-pinyin-data's and the Unicode notices in full (the generator pins pypinyin 0.55.0); the compose table names the Unicode data it comes from. SPEC §12 and spec/keyboard.md updated. Co-Authored-By: Claude Opus 5.5 <[email protected]>
45 lines
1.9 KiB
Python
45 lines
1.9 KiB
Python
#!/usr/bin/env python3
|
|
"""Write aac-core's keyboard/compose_table.rs: every precomposed letter of the
|
|
Latin, Greek and Cyrillic blocks whose canonical decomposition is one letter
|
|
and one combining mark, as (letter, mark, composed) — the composition step of
|
|
NFC for the letters a keyboard puts a mark on (edit::mark). Characters Unicode
|
|
excludes from composition are left out, as NFC leaves them.
|
|
|
|
usage: python3 tools/keyboard/build_compose.py (from aac/aac-rs)
|
|
"""
|
|
import sys
|
|
import unicodedata
|
|
from pathlib import Path
|
|
|
|
OUT = Path(__file__).resolve().parents[2] / "crates/aac-core/src/keyboard/compose_table.rs"
|
|
RANGES = [(0x00C0, 0x024F), (0x0370, 0x03FF), (0x0400, 0x04FF), (0x1E00, 0x1EFF), (0x1F00, 0x1FFF)]
|
|
|
|
rows = []
|
|
for lo, hi in RANGES:
|
|
for n in range(lo, hi + 1):
|
|
c = chr(n)
|
|
d = unicodedata.decomposition(c)
|
|
if not d or d.startswith("<"):
|
|
continue
|
|
parts = [chr(int(x, 16)) for x in d.split()]
|
|
if len(parts) != 2 or not unicodedata.combining(parts[1]):
|
|
continue
|
|
# A composition exclusion (or a singleton) does not survive NFC.
|
|
if unicodedata.normalize("NFC", parts[0] + parts[1]) != c:
|
|
continue
|
|
rows.append((ord(parts[0]), ord(parts[1]), n))
|
|
rows.sort()
|
|
body = "\n".join(f" (0x{a:04X}, 0x{m:04X}, 0x{c:04X})," for a, m, c in rows)
|
|
OUT.write_text(f"""//! Generated by `tools/keyboard/build_compose.py` (Unicode {unicodedata.unidata_version}); do not edit by hand.
|
|
//!
|
|
//! Every precomposed Latin, Greek and Cyrillic letter that NFC composes from
|
|
//! one letter and one combining mark: `(letter, mark, composed)`, sorted.
|
|
//! Derived from the Unicode Character Database, Copyright © 1991-2026
|
|
//! Unicode, Inc., under the Unicode License v3
|
|
//! (https://www.unicode.org/license.txt).
|
|
|
|
pub(super) const COMPOSE: [(u32, u32, u32); {len(rows)}] = [
|
|
{body}
|
|
];
|
|
""", encoding="utf-8")
|
|
print(f"{len(rows)} compositions -> {OUT}", file=sys.stderr)
|