* feat(engine,server,windows,ui-html,installer): U 模式重构为部件拆字反查 (#971) 把 U 模式从 Unicode 十六进制码位输入改成部件拆字反查:Shift+U 后输入各 组成部分的拼音连写(晶 = ririri)即可出字,候选上反显注音。 - 引擎:新增 local_modes/radical_query(前缀匹配、字频降序),数据表 engine/radical/decomposition.txt 与生成脚本;LocalInputMode::Unicode 改为 Radical;删除 unicode_query。 - Server:g_radical_mode_triggered + IsRadicalCompositionActive + 候选分发, 用 cpp-pinyin 反显注音;删除 Unicode 码位路径。 - 前端 TSF:删除 U 模式的十六进制/数字特例与影子预判,按普通组合处理。 - 配置:utility.unicode_mode 改为 utility.radical_mode。 - 部署:assets 登记 radical_decomposition,安装器与 CI 拷表。 数据来源 mirtlecn/rime-radical-pinyin (GPL-3.0),授权见 engine/radical/NOTICE.md。 * feat(server): U 模式反显拼音改为带调风格(jīng) * fix(server,installer): 修复 U 模式 CI(拆字表打包夹具与 clang-format) * fix(engine,server): 修正 U 模式按键错位、重复候选与配置迁移 - Server 在 U 模式不再吞掉撇号、分号、斜杠、大写字母等键:TSF 已把它们记进按键缓冲, 吞掉又不重同步会让两侧串和光标错位。照常插入,含非 a-z 的码查不出候选 - 拆字查询全码命中排在仅前缀命中之前,同一个字的几种拆法只出一次 - 升级合并把旧键 utility.unicode_mode 的值带到 utility.radical_mode,读取时也兜底旧键 - 生成脚本只收汉字,重建拆字表去掉 abc 码下的 51 行数字、字母与符号 - 开着 U 模式时在后台预热拆字表与 cpp-pinyin 词典,避开首个按键的冷加载 - Server 按会话的 RuntimePaths 查拆字表,删掉走全局数据目录的便捷重载 - cpp-pinyin 初始化合并到 utils/hanzi_pinyin,设置页导入注音共用;U 模式与快捷短语的收键分支合并 --------- Co-authored-by: fanlusky <1730976608@qq.com>
123 lines
4.5 KiB
Python
123 lines
4.5 KiB
Python
#!/usr/bin/env python3
|
|
"""Build the U 模式部件拆字反查表 from the upstream Rime dictionary.
|
|
|
|
输入是 mirtlecn/rime-radical-pinyin 发布的 `radical_pinyin.dict.yaml`,数据行格式为
|
|
`<汉字>\t<部件拼音>(撇号分隔)\t<字频>`(例如 `晶\tri'ri'ri\t106`)。我们把它压成
|
|
引擎运行时用的三列表 `<部件拼音连写>\t<汉字>\t<字频>`,整个文件按字频降序排列,
|
|
这样查询端做前缀匹配时天然先取高频字。
|
|
|
|
上游表随发布版本变化,重建时必须记录版本;`--version` 只是做一个显式校验,避免
|
|
拿旧表静默覆盖。上游来源与授权见 ../NOTICE.md。
|
|
|
|
用法:
|
|
python build_decomposition.py --source radical_pinyin.dict.yaml \
|
|
--output ../decomposition.txt [--version 2026.06.18]
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
|
|
def is_han_character(char: str) -> bool:
|
|
"""CJK 统一汉字(基本区、扩展 A 及第二、三平面的扩展区)、兼容汉字和〇。
|
|
|
|
上游表在 `abc` 码下收了一批数字、拉丁/希腊字母和数学符号,它们没有部件可拆,不该出现在
|
|
拆字候选里。第二、三平面整体按汉字放行,新的扩展区不用逐个登记。
|
|
"""
|
|
code = ord(char)
|
|
return (
|
|
code == 0x3007
|
|
or 0x3400 <= code <= 0x4DBF
|
|
or 0x4E00 <= code <= 0x9FFF
|
|
or 0xF900 <= code <= 0xFAFF
|
|
or 0x20000 <= code <= 0x3FFFF
|
|
)
|
|
|
|
|
|
def parse_source(path: Path) -> tuple[str, list[tuple[str, str, int]]]:
|
|
"""Return (upstream version, entries). Entries are (char, code_without_apostrophe, weight)."""
|
|
version = ""
|
|
entries: list[tuple[str, str, int]] = []
|
|
seen: set[tuple[str, str]] = set()
|
|
in_body = False
|
|
for raw in path.read_text(encoding="utf-8").splitlines():
|
|
line = raw.rstrip("\n")
|
|
if not in_body:
|
|
# YAML 头部以 `...` 结束,其后才是数据行。
|
|
if line.strip() == "...":
|
|
in_body = True
|
|
elif line.startswith("version:"):
|
|
version = line.split(":", 1)[1].strip().strip('"')
|
|
continue
|
|
if not line or line.startswith("#"):
|
|
continue
|
|
parts = line.split("\t")
|
|
# 数据列数不固定(部分行有多列变体),前三列固定是 汉字 / 码 / 字频。
|
|
if len(parts) < 3:
|
|
continue
|
|
char, code, weight = parts[0], parts[1], parts[2]
|
|
if len(char) != 1 or not is_han_character(char):
|
|
continue
|
|
try:
|
|
rank = int(weight)
|
|
except ValueError:
|
|
continue
|
|
normalized = code.replace("'", "").replace(" ", "")
|
|
if (
|
|
not normalized.isascii()
|
|
or not normalized.isalpha()
|
|
or not normalized.islower()
|
|
):
|
|
# 只保留纯小写拉丁字母的码:拆字输入只可能是 a-z。
|
|
continue
|
|
key = (normalized, char)
|
|
if key in seen:
|
|
continue
|
|
seen.add(key)
|
|
entries.append((char, normalized, rank))
|
|
return version, entries
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("--source", required=True, type=Path)
|
|
parser.add_argument("--output", required=True, type=Path)
|
|
parser.add_argument("--version", default="")
|
|
args = parser.parse_args()
|
|
|
|
if not args.source.is_file():
|
|
print(f"source not found: {args.source}", file=sys.stderr)
|
|
return 1
|
|
|
|
version, entries = parse_source(args.source)
|
|
if not version:
|
|
print("could not read upstream version from source header", file=sys.stderr)
|
|
return 1
|
|
if args.version and version != args.version:
|
|
print(
|
|
f"upstream version mismatch: expected {args.version}, got {version}",
|
|
file=sys.stderr,
|
|
)
|
|
return 1
|
|
|
|
# 字频降序;同频按码、字稳定排序,保证可复现。
|
|
entries.sort(key=lambda item: (-item[2], item[1], item[0]))
|
|
|
|
header = [
|
|
"# U 模式部件拆字反查表",
|
|
"# 格式:<部件拼音连写>\\t<汉字>\\t<字频>(整表按字频降序)",
|
|
f"# 来源:mirtlecn/rime-radical-pinyin {version},授权见 NOTICE.md",
|
|
"# 由 scripts/build_decomposition.py 生成,请勿手改。",
|
|
]
|
|
lines = header + [f"{code}\t{char}\t{rank}" for char, code, rank in entries]
|
|
args.output.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
|
print(f"wrote {len(entries)} entries to {args.output} (upstream {version})")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|