# 辅码 issue #8:联想里没有的词要翻很多页;加辅码(拼音后补码筛选)能把第一次打句子的效率提上来,选过一次的词靠既有学习在下次纯拼音就往前排。 本文是这份需求的设计来源:**要做什么、为什么这么做、数据从哪来、许可怎么算**。实现按 `docs/plan/todo.md` 排期。 ## 术语 | 词 | 意思 | |---|---| | 辅码 | 拼音之后键入的、用来缩小候选范围的码。形态是「拼音 + 触发键 + 码」。 | | 触发键 | 进入辅码态的那个键。可配置,缺省 `;`。 | | 码段 | 输入串里触发键之后的字母,与拼音段分开记账、分开画。 | | 码表 | 「词 → 码」的映射数据,导入与管理的单位。 | | 原生表 | 随包分发、开箱可用的码表。v1 只有笔画。 | | 引导导入 | 不随包带表,由用户自己从官方渠道取得码表后一键导入。第三方形码(小鹤形、自然码形)走这条路。 | ## 辅码的语义(Core) ### 触发 - 配置项 `[general] aux_code_key`,缺省 `;`;也想跟雾凇(反引号 + 偏旁部首拼音)或万象(`/` 前置引导符)对齐的用户改这一个键即可。 - **优先级规则(与现有行为共存)**:当前 preedit 能完整切分成音节、且敲下的字符是配置的触发键 → 进辅码态;否则维持现有语义(`;` 仍按中文标点转全角、不会转全角的符号仍进英文直输段——会转全角的标点先把高亮候选上屏再补标点——翻页键仍是翻页键)。 - 微软 / 搜狗 / 紫光双拼里 `;` 是 `ing` 的韵母键:**能构成合法音节就当韵母,否则当触发键**。`x;` 是 xing(`;` 被韵母吃掉),再敲一个 `;` 才进辅码态。 ### 过滤 - **词级(含单字)**:码只筛词候选,不改选词逻辑,也不改整句转换。 - **逐键即筛**:码段每多一个字母就重新筛一次,不要求敲满该词的码位;敲到满意即可上屏。 - **无码词在辅码态直接隐藏**:码表没覆盖的词不出候选;码段删空后**停在辅码态**——`;` 仍在、候选全部回来,空码段再按一次退格才退出(`[general] aux_code_keep_empty`,缺省开;关掉则删空即回纯拼音态)。 - **筛空时候选窗口收起**,只剩拼音行。 ### 学习(issue 里「下一次不用辅码也能联想出来」就落在这) 不新造机制:辅码态选中的词按**输入串**记进 `user-choices.tsv`(词级排序里同输入串选过的优先),并计入词频与个人 n-gram。 下次同样的拼音,这个词自然往前排。也就是说辅码只需解决「第一次怎么少翻页」。 ### 与整句、简拼的关系 v1 只支持**全拼 + 辅码**、**双拼 + 辅码**。简拼 + 辅码、整句转换 + 辅码(形码逐字约束进 Viterbi 词图)都留到后续,见「后续与不做」。 ## 码表 ### 格式:Rime `.dict.yaml` 沿用已有的 Rime 词库导入传统(`crates/qingjian-dictionary/src/import/rime.rs` 已能把 `.dict.yaml` 转成 `.qj`),码表用它同一套外形: - 头部 `---` … `...` 是 meta(`name` / `version` / `sort`),正文每行 `词\t码[\t权重]`,`#` 是注释。 - `columns` **必须解析**:缺省是 `text` / `code` / `weight` 三列,但真实存在 `columns: [text, weight]` 的纯词表——照缺省解析会把词频当码,静默写坏数据。解析按 `columns` 列表的下标定位。 - `import_tables` 表示**跨文件合表**(一本码表由 base + stem + extended 拼成):导入时一并读入,否则数据不完整。 - 「有词无码」的行**要报错或警告**,不能像词库导入那样静默跳过(码表少一行就是少一个词可用,用户得知道)。 - 导入完成给一份统计:读入多少条、多少词有码、多少词无码、跳过多少行。 ### 索引:独立码索引,两段式查询 码表**不能复用词库的存储**: - 词库的 `Slot{text_start, frequency, text_len, reserved}` 没有码字段; - 键空间会串台——`de` / `he` / `bu` / `ge` 这类两位码同时是合法拼音音节,同一个索引里既当码又当拼音,约束互相污染。 所以码表落成**自己的索引**(码 → 词集合),查询是两段式:先用拼音段查词库得到候选集合,再用码段在该索引里取交集、按码命中重排。码表文件与词库文件一样进用户数据目录,单独管理。 ### 原生表:只带笔画 第三方形码表都不随包,理由在「许可」一节(首右同理:sysrf.cn 的私有方案,字根图无授权,自己生成也对不上官方码,2026-10 决定不做)。随包的只有笔画: - **键位**:`h` 横、`s` 竖、`p` 撇、`n` 捺(含点)、`z` 折——与大陆厂商(搜狗 / QQ)的笔画输入一致,键盘手位在拼音区。 - **取码**:单字「前 4 笔 + 末笔」;词组每字首笔(二字 2 码、三字 3 码、四字以上 = 前三字首笔 + 末字首笔)。 - **归一化**:点与捺合并为 `n`、提并入横。 - **数据管线**:以 **CNS11643 全字庫「筆順資料」**(`CNS_strokes_sequence.txt`,官方定义 1 橫 / 2 豎 / 3 撇 / 4 點 / 5 折)作字形来源,经官方 CNS→Unicode 对照表映射;其上叠加**大陆序覆盖表**——CNS 是台湾标准,与大陆规范按部件成片不一致(艹:台 4 笔首笔竖 / 陆 3 笔首笔横;辶 4/3、阝 3/2),一级字抽样 299 字里 31 例笔画数不符(10.4%)。**混合表不可接受**:rime-stroke 就是双轨(花 = 艹 4 画台湾序、节 = 艹 3 画大陆序),大陆用户按学校笔顺敲不出 花 这类字。 - **工具**:`tools/dict-convert` 新增 `stroke` 子命令,输入 `--cns-seq`(笔顺表)、`--cns-map`(CNS→Unicode 对照)、`--cns-count`(可选,与笔画数自洽过滤,建议 `|d| ≤ 1`)、`--filter`(字表白名单,取通用规范字表)、`--prc-rules`(大陆序覆盖),输出 `字\t[1-5]{n}`(8,105 字约 40–80 KB)。原始 zip 不入库,走 `data/` 的既有惯例只留生成产物。 ### 导入 - **「导入码表…」**:接受 Rime `.dict.yaml`,走系统打开文件对话框 / 文件选择器;失败时在窗口底部红字一行说明(例如「这个文件里没有识别到码表(需要词 + 码两列)」)。 - 导入完成给一份统计:读入多少条、多少词有码、多少词无码、跳过多少行。 - 引导导入的文案要写清:码表由用户自己取得(例如小鹤形的官方码表),青简不随包分发第三方形码表。 ### 设置界面(两个壳都要做) 同一组设置项在两个平台的设置界面各出现一次,写进同一份 `config.toml`(配置文件是唯一事实源): | 设置项 | macOS(偏好设置,AppKit) | Windows(设置程序,WinUI 3) | |---|---|---| | 触发键 | 录制按钮,缺省 `;` | 录制控件,同一缺省 | | 候选上显示码 | 勾选框,缺省不勾 | 同左 | | 辅码方案 | 下拉框,同一时间只用一套(几套叠着筛,同一个字母在不同方案里意思不同,筛不干净;Rime 的万象、雾凇也是选定一套) | 同左 | | 码表列表 | 随包笔画不可移除;导入的显示名字 · 条数 · 许可,可移除 | 同结构的一页 | | 导入码表… | 系统打开文件对话框 | 系统文件选择器 | 设置项命名、文案与失败提示两平台保持一致;Core 只提供配置项与状态,界面归壳。 ## 候选窗口与输入串 - **输入串(preedit)**:码段用**淡色 + 下划线**画法、`;` 不突出——与既有段样式同一套视觉词汇(敲的拼音正常色、剩余拼音画淡、被纠错改掉的画删除线)。Core 把码段作为新的段类型(`MarkedKind`)给出,两个壳各自落到自己的载体:macOS 走行内 marked text 与窗口拼音行,Windows 走 `protocol` 的 `PreeditSegment`(TSF DLL 交给系统)。 - **候选**:辅码态只出命中码的词(无码词隐藏);敲码筛空时候选窗口收起,只剩拼音行。 - **显示码开关打开时**:码用方括号**紧跟在候选词后面**(`1 鹤[rbm] crane`,渲染器 `Row.code`,小字淡色):码是词本身的属性,不进右侧的 annotation;译文仍是候选词的单条 annotation(`docs/design/architecture.md`)。开着时不用进辅码态:纯拼音打字与辅码态空码段也把词的**首条码**挂上(边打边认码),筛码时显示命中的那条。竖排会比纯译文挤、横排更难放下,这是这个开关的已知代价(缺省关)。 - 窗口布局、视觉层级、翻页键等沿用 `docs/design/candidate-ui.md`;**绘制归各壳**(macOS 自绘 NSPanel,Windows Server 自绘候选窗与状态条)。渲染器 spike(`crates/qingjian-render`,分支 `renderer-spike`)落地后两个平台都只贴图。 ## 配置 | 键 | 缺省 | 说明 | |---|---|---| | `[general] aux_code_key` | `";"` | 触发键,可改反引号或 `/` | | `[general] aux_code_show` | `false` | 候选上是否显示码(与译文拼接;开着时未进辅码态也显示词的首条码) | | `[general] aux_code_keep_empty` | `true` | 码段删空后是否留在辅码态(空码段再按一次退格才退出);`false` 删空即回拼音态 | | `[aux_code] enabled` | `false` | 辅码总开关:关掉整条辅码线(`;` 回到原生行为);开着但没有可用码表也不进辅码态 | | `[aux_code] scheme` | `""` | 用哪一套(码表文件名或辅码方案 id),同一时间只用一套;空着或找不到时用第一套没被关掉的(随包笔画在最前) | | `[aux_code] disabled` | `[]` | 老配置逐张关掉的码表;现在只在 `scheme` 没选时用来跳过,设置程序不再写 | ## 分层与落点 | 层 | 放什么 | |---|---| | `qingjian-core` | 码段解析(触发键消歧、码段与拼音段分开记账)、码索引的两段式查询、过滤与重排、`MarkedKind` 新段;码表查询走 trait,不认识文件 | | `qingjian-dictionary` | 码表解析(`.dict.yaml` + `columns` / `import_tables` 规则)、独立码索引与落盘、导入统计 | | `qingjian-platform` | 配置项、码表与方案的目录与开关 | | 平台壳 | macOS:`apps/macos`(IMK 偏好设置页、候选窗自绘);Windows:`apps/windows/settings`(WinUI 3 设置页)与 `apps/windows/server`(自绘候选窗与状态条)。两边都只做两件事:把码段画出来、把设置界面画出来 | 平台层不出现排序逻辑、词库访问或翻译调用(`docs/design/architecture.md` 的架构约束)。 ## 测试与评测 - **Core 单测**:码段解析与消歧(`x;` = xing、再 `;` 才进辅码)、逐键即筛、无码词隐藏、码段删空两态(缺省停在辅码态、`aux_code_keep_empty = false` 回拼音态)、码表 `columns` / `import_tables` / 「有词无码」三类边界。 - **学习闭环**:辅码态选词后,同输入串(纯拼音)首选命中——用 `apps/cli --replay` 在现有输入日志上验。 - **评测**:导入时报告「多少词有码 / 无码」,用码表覆盖率与 `--eval-text` 的整句命中率当两把尺子。 - 排序、整句、纠错的改动一律先跑 `--replay` 与 `--eval-text`(仓库既有规矩)。 - **Windows**:本机只 `cargo check --target x86_64-pc-windows-gnu`;真编译与真机测试在 Windows 机器上做(Server + TSF DLL + 设置程序)。 - **macOS**:IMK 不能 `cargo run` 验证,要 `apps/macos/scripts/bundle.sh --install` 装到本机;`osascript` 往 TextEdit 发按键读回文本可做端到端。 ## 许可 - 项目是 **GPL-3.0-or-later**(`LICENSE`、`Cargo.toml`)。 - **小鹤形**(小鹤音形的形码部分):官网声明「未经本站允许,不得用于任何商业目的」+ All Rights Reserved,**无开放许可**,不能随包;要内置须先取得作者书面许可。 - **自然码形**:作者坚持商业发行,形码无授权记录,不能随包。 - **笔画(CNS11643 筆順資料)**:台湾数位发展部开放数据,许可为「政府資料開放授權條款第 1 版」或「OFL-1.1」二选一,可再分发、含商用,**需署名**——随包时在「关于」页与数据目录署名,沿用 `assets/levels`、`assets/glossary` 的做法。 - **Make Me a Hanzi / hanzi-writer-data**:PRC 笔顺,只作开发期差异定位,**不随包**(其笔顺数据是 Arphic Public License)。 - **第三方码表不随包**:码表由用户自己从官方渠道取得后导入;这是工程偏好(文件小、可控、不带上游维护负担),不是许可禁令。 - 用户导入第三方码表是用户自己的行为,不构成再分发。 ## 后续与不做 **平台范围**:本设计对两个现役平台一视同仁——macOS(`apps/macos`)与 Windows(`apps/windows/{server,tsf,settings}`)的壳与设置界面都要接;逻辑全在 Core,Linux 未开工,落地时走同一套通路,不需要改 Core。 **落地分工**(按 `docs/contributing.md`「一个 PR 只做一件事、只碰一个平台」):三步可以独立合,只有一个平台机器的人也能完成自己那一步—— | 步 | 范围 | 验证方式 | |---|---|---| | 1 | **Core**:码表解析、独立码索引、码段与两段式查询(单独一个 PR,不碰壳) | `cargo test -p qingjian-core` / `-p qingjian-dictionary`、`apps/cli` 手测、`--replay` | | 2 | **Windows 壳**:Server / TSF 的 preedit 分段与候选绘制、`apps/windows/settings`(WinUI 3)的「辅码」页 | Windows 机器上 `cargo check --target x86_64-pc-windows-gnu` + 真机部署(Server + DLL + 设置程序) | | 3 | **macOS 壳**:IMK 的 marked text 分段、候选窗绘制、偏好设置页 | macOS 机器上 `bundle.sh --install` + 真机输入验证 | 第 1 步是后两步共同的前置;**第 2、3 步之间没有依赖**——先合哪一个都行,只有一个平台的贡献者只做自己那一步,另一平台由别的贡献者或维护者补。 **后续(未定,需要时另开设计)**:简拼 + 辅码;整句转换 + 辅码(形码逐字约束进词图);码表导入后的生效时机与热切换。 **不做**:五笔原生表;社区码表在线下载 / 同步;内置任何第三方形码表。 ## 参考 - `docs/design/candidate-ui.md`:候选窗口与按键约定(本文的码段与注记沿用它的视觉词汇)。 - `docs/design/architecture.md`:Core 与平台层的划分、翻译是单条 annotation 的约束。 - 渲染器 spike(分支 `renderer-spike` 上的 `crates/qingjian-render` 与 `docs/design/rendering.md`):候选窗一帧 + 主题 → 位图,各平台只贴图。 - `docs/notes/crate-notes.md`:各 crate / app / tool 的实现要点(改了实现要同步那里)。 - `docs/plan/todo.md`:排期与进度。