Files
qingjian/docs/design/aux-code.md
T
yenharvey d54dbf57e4 feat(platform): 随包笔画表按每字首笔给表外的词算码
- 笔画表只收了基础词库的词,自造词、导入词库与学到的词在辅码态会被藏起来;装随包 stroke.qj 时挂上与 dict-convert 打包同一条词码规则(每字首笔,四字以上取前三字与末字),表外的词现算
- 首右不做:sysrf.cn 的私有方案,字根图无授权、自己生成也对不上官方码;设计文档记下理由
- 待办记下「整句 + 辅码」,先不做
2026-10-20 14:39:25 +08:00

164 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 辅码
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`:排期与进度。