diff --git a/.gitignore b/.gitignore index 8424758..c570d77 100644 --- a/.gitignore +++ b/.gitignore @@ -65,6 +65,8 @@ docs/.docusaurus /desktop/ui/i18n.slint /desktop/ui/version.slint +/desktop/assets/fonts/source/ +/desktop/assets/fonts/.tools/ /dev /bak/ .idea/ diff --git a/Makefile b/Makefile index f6b0677..3a468f8 100644 --- a/Makefile +++ b/Makefile @@ -28,6 +28,15 @@ orbien: desktop-dev: cargo run -p orbien-desktop +desktop-font-subset: + python3 scripts/gen-desktop-font-subset.py + +desktop-font-subset-tiny: + python3 scripts/gen-desktop-font-subset.py --regular-only + +desktop-font-subset-full: + python3 scripts/gen-desktop-font-subset.py --full + desktop-build: cargo build --release -p orbien-desktop diff --git a/Makefile.md b/Makefile.md index e4d7fb6..21ce2b8 100644 --- a/Makefile.md +++ b/Makefile.md @@ -2,19 +2,22 @@ Run from the repository root. Build artifacts go to `target/`; packages go to `dist/`. -| Command | Description | -|---------|-------------| -| `make web` | Build the dashboard UI into `server/assets` | -| `make build` | Debug-build `orbien-server` and `orbien` | -| `make release` | Run `web`, then release-build server and client | -| `make orbien-server` | Run `web`, then release-build the server | -| `make orbien` | Release-build the client | -| `make desktop-dev` | Run the desktop app (dev) | -| `make desktop-build` | Release-build the desktop app | -| `make desktop-app` / `make desktop-dmg` | Package macOS `.app` / `.dmg` | -| `make desktop-windows` | Package Windows exe / zip (run on Windows) | -| `make desktop-deb` | Package Linux `.deb` (run on Debian/Ubuntu) | -| `make package` | Run `release`, then copy binaries and `conf/` to `dist/` | -| `make test` | Run workspace tests | -| `make fmt` | Format Rust code | -| `make clean` | Clean `target/` and frontend install/build outputs | +| Command | Description | +|-----------------------------------------|----------------------------------------------------------| +| `make web` | Build the dashboard UI into `server/assets` | +| `make build` | Debug-build `orbien-server` and `orbien` | +| `make release` | Run `web`, then release-build server and client | +| `make orbien-server` | Run `web`, then release-build the server | +| `make orbien` | Release-build the client | +| `make desktop-dev` | Run the desktop app (dev) | +| `make desktop-font-subset` | Rebuild embedded fonts (i18n + GB2312 L1, ~1.8 MB) | +| `make desktop-font-subset-tiny` | Regular-only fonts (~0.9 MB) | +| `make desktop-font-subset-full` | Full SC regional fonts (~16 MB) | +| `make desktop-build` | Release-build the desktop app | +| `make desktop-app` / `make desktop-dmg` | Package macOS `.app` / `.dmg` | +| `make desktop-windows` | Package Windows exe / zip (run on Windows) | +| `make desktop-deb` | Package Linux `.deb` (run on Debian/Ubuntu) | +| `make package` | Run `release`, then copy binaries and `conf/` to `dist/` | +| `make test` | Run workspace tests | +| `make fmt` | Format Rust code | +| `make clean` | Clean `target/` and frontend install/build outputs | diff --git a/desktop/assets/app-icons/README.md b/desktop/assets/app-icons/README.md deleted file mode 100644 index d539c6b..0000000 --- a/desktop/assets/app-icons/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# App icons - -| File | Used by | -|------|---------| -| `icon.icns` | macOS `.app` / `.dmg` (`pack-desktop-macos.sh`) | -| `icon.ico` | Windows exe (`desktop/build.rs`) | -| `128x128.png` | Window icon (`ui/app.slint`) + Linux deb | -| `64x64.png` | Linux deb | -| `32x32.png` | Linux deb | diff --git a/desktop/assets/fonts/NotoSansSC-Bold.subset.otf b/desktop/assets/fonts/NotoSansSC-Bold.subset.otf new file mode 100644 index 0000000..788310a Binary files /dev/null and b/desktop/assets/fonts/NotoSansSC-Bold.subset.otf differ diff --git a/desktop/assets/fonts/NotoSansSC-Regular.subset.otf b/desktop/assets/fonts/NotoSansSC-Regular.subset.otf new file mode 100644 index 0000000..788310a Binary files /dev/null and b/desktop/assets/fonts/NotoSansSC-Regular.subset.otf differ diff --git a/desktop/assets/fonts/OFL.txt b/desktop/assets/fonts/OFL.txt new file mode 100644 index 0000000..1c9f432 --- /dev/null +++ b/desktop/assets/fonts/OFL.txt @@ -0,0 +1,93 @@ +Copyright 2014-2021 Adobe (http://www.adobe.com/), with Reserved Font Name 'Source' + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/desktop/assets/fonts/subset-manifest.json b/desktop/assets/fonts/subset-manifest.json new file mode 100644 index 0000000..21cee5d --- /dev/null +++ b/desktop/assets/fonts/subset-manifest.json @@ -0,0 +1,3814 @@ +{ + "font_family": "Noto Sans SC", + "license": "SIL Open Font License 1.1 (see OFL.txt)", + "mode": "i18n-gb2312-level1-regular-only", + "char_count": 3871, + "files": { + "NotoSansSC-Regular.subset.otf": 952192, + "NotoSansSC-Bold.subset.otf": 952192 + }, + "chars": [ + " ", + "!", + "\"", + "#", + "%", + "&", + "'", + "(", + ")", + "*", + "+", + ",", + "-", + ".", + "/", + ":", + ";", + "=", + "?", + "@", + "[", + "\\", + "]", + "_", + "{", + "}", + "·", + "—", + "…", + "、", + "。", + "《", + "》", + "「", + "」", + "『", + "』", + "【", + "】", + "一", + "丁", + "七", + "万", + "丈", + "三", + "上", + "下", + "不", + "与", + "丑", + "专", + "且", + "世", + "丘", + "丙", + "业", + "丛", + "东", + "丝", + "丢", + "两", + "严", + "丧", + "个", + "丫", + "中", + "丰", + "串", + "临", + "丸", + "丹", + "为", + "主", + "丽", + "举", + "乃", + "久", + "么", + "义", + "之", + "乌", + "乍", + "乎", + "乏", + "乐", + "乒", + "乓", + "乔", + "乖", + "乘", + "乙", + "九", + "乞", + "也", + "习", + "乡", + "书", + "买", + "乱", + "乳", + "乾", + "了", + "予", + "争", + "事", + "二", + "于", + "亏", + "云", + "互", + "五", + "井", + "亚", + "些", + "亡", + "亢", + "交", + "亥", + "亦", + "产", + "亨", + "亩", + "享", + "京", + "亭", + "亮", + "亲", + "人", + "亿", + "什", + "仁", + "仅", + "仆", + "仇", + "今", + "介", + "仍", + "从", + "仑", + "仓", + "仔", + "仕", + "他", + "仗", + "付", + "仙", + "仟", + "代", + "令", + "以", + "仪", + "们", + "仰", + "仲", + "件", + "价", + "任", + "份", + "仿", + "企", + "伊", + "伍", + "伎", + "伏", + "伐", + "休", + "众", + "优", + "伙", + "会", + "伞", + "伟", + "传", + "伤", + "伦", + "伪", + "伯", + "估", + "伴", + "伶", + "伸", + "伺", + "似", + "佃", + "但", + "位", + "低", + "住", + "佐", + "佑", + "体", + "何", + "余", + "佛", + "作", + "你", + "佣", + "佩", + "佬", + "佯", + "佰", + "佳", + "使", + "侄", + "侈", + "例", + "侍", + "侗", + "供", + "依", + "侠", + "侣", + "侥", + "侦", + "侧", + "侨", + "侩", + "侮", + "侯", + "侵", + "便", + "促", + "俄", + "俊", + "俏", + "俐", + "俗", + "俘", + "保", + "俞", + "信", + "俩", + "俭", + "修", + "俯", + "俱", + "俺", + "倍", + "倒", + "倔", + "倘", + "候", + "倚", + "借", + "倡", + "倦", + "倪", + "债", + "值", + "倾", + "假", + "偏", + "做", + "停", + "健", + "偶", + "偷", + "偿", + "傀", + "傅", + "傈", + "傍", + "傣", + "储", + "催", + "傲", + "傻", + "像", + "僚", + "僧", + "僳", + "僵", + "僻", + "儒", + "儡", + "儿", + "允", + "元", + "兄", + "充", + "兆", + "先", + "光", + "克", + "免", + "兑", + "兔", + "党", + "兜", + "兢", + "入", + "全", + "八", + "公", + "六", + "兰", + "共", + "关", + "兴", + "兵", + "其", + "具", + "典", + "兹", + "养", + "兼", + "兽", + "冀", + "内", + "冈", + "冉", + "册", + "再", + "冒", + "冕", + "冗", + "写", + "军", + "农", + "冠", + "冤", + "冬", + "冯", + "冰", + "冲", + "决", + "况", + "冶", + "冷", + "冻", + "净", + "凄", + "准", + "凉", + "凋", + "凌", + "减", + "凑", + "凛", + "凝", + "几", + "凡", + "凤", + "凭", + "凯", + "凰", + "凳", + "凶", + "凸", + "凹", + "出", + "击", + "函", + "凿", + "刀", + "刁", + "刃", + "分", + "切", + "刊", + "刑", + "划", + "列", + "刘", + "则", + "刚", + "创", + "初", + "删", + "判", + "刨", + "利", + "别", + "刮", + "到", + "制", + "刷", + "券", + "刹", + "刺", + "刻", + "刽", + "剁", + "剂", + "剃", + "削", + "前", + "剐", + "剑", + "剔", + "剖", + "剥", + "剧", + "剩", + "剪", + "副", + "割", + "剿", + "劈", + "力", + "劝", + "办", + "功", + "加", + "务", + "劣", + "动", + "助", + "努", + "劫", + "励", + "劲", + "劳", + "势", + "勃", + "勇", + "勉", + "勋", + "勒", + "勘", + "募", + "勤", + "勺", + "勾", + "勿", + "匀", + "包", + "匆", + "匈", + "化", + "北", + "匙", + "匝", + "匠", + "匡", + "匣", + "匪", + "匹", + "区", + "医", + "匿", + "十", + "千", + "升", + "午", + "卉", + "半", + "华", + "协", + "卑", + "卒", + "卓", + "单", + "卖", + "南", + "博", + "卜", + "卞", + "占", + "卡", + "卢", + "卤", + "卧", + "卫", + "卯", + "印", + "危", + "即", + "却", + "卵", + "卷", + "卸", + "卿", + "厂", + "厄", + "厅", + "历", + "厉", + "压", + "厌", + "厕", + "厘", + "厚", + "原", + "厢", + "厦", + "厨", + "厩", + "去", + "县", + "叁", + "参", + "又", + "叉", + "及", + "友", + "双", + "反", + "发", + "叔", + "取", + "受", + "变", + "叙", + "叛", + "叠", + "口", + "古", + "句", + "另", + "只", + "叫", + "召", + "叭", + "叮", + "可", + "台", + "史", + "右", + "叶", + "号", + "司", + "叹", + "叼", + "吁", + "吃", + "各", + "合", + "吉", + "吊", + "同", + "名", + "后", + "吏", + "吐", + "向", + "吓", + "吕", + "吗", + "君", + "吝", + "吞", + "吟", + "吠", + "否", + "吧", + "吨", + "吩", + "含", + "听", + "吭", + "吮", + "启", + "吱", + "吴", + "吵", + "吸", + "吹", + "吻", + "吼", + "吾", + "呀", + "呆", + "呈", + "告", + "呐", + "呕", + "员", + "呛", + "呜", + "呢", + "周", + "味", + "呵", + "呸", + "呻", + "呼", + "命", + "咀", + "咆", + "咋", + "和", + "咎", + "咏", + "咐", + "咒", + "咕", + "咖", + "咙", + "咨", + "咬", + "咯", + "咱", + "咳", + "咸", + "咽", + "哀", + "品", + "哄", + "哆", + "哇", + "哈", + "哉", + "响", + "哎", + "哑", + "哗", + "哟", + "哥", + "哦", + "哨", + "哩", + "哪", + "哭", + "哮", + "哲", + "哺", + "哼", + "唁", + "唆", + "唇", + "唉", + "唐", + "唤", + "唬", + "售", + "唯", + "唱", + "唾", + "啃", + "啄", + "商", + "啊", + "啡", + "啤", + "啥", + "啦", + "啪", + "啮", + "啸", + "啼", + "喀", + "喂", + "善", + "喇", + "喉", + "喊", + "喘", + "喜", + "喝", + "喧", + "喳", + "喷", + "喻", + "嗅", + "嗓", + "嗜", + "嗡", + "嗣", + "嗽", + "嘉", + "嘎", + "嘘", + "嘛", + "嘱", + "嘲", + "嘴", + "嘶", + "嘻", + "嘿", + "噎", + "器", + "噪", + "噬", + "噶", + "嚎", + "嚏", + "嚣", + "嚷", + "嚼", + "囊", + "囚", + "四", + "回", + "因", + "团", + "囤", + "园", + "困", + "囱", + "围", + "固", + "国", + "图", + "圃", + "圆", + "圈", + "土", + "圣", + "在", + "圭", + "地", + "场", + "圾", + "址", + "均", + "坊", + "坍", + "坎", + "坏", + "坐", + "坑", + "块", + "坚", + "坛", + "坝", + "坞", + "坟", + "坠", + "坡", + "坤", + "坦", + "坪", + "坯", + "坷", + "垂", + "垃", + "垄", + "型", + "垒", + "垛", + "垢", + "垣", + "垦", + "垫", + "垮", + "埂", + "埃", + "埋", + "城", + "埔", + "域", + "埠", + "培", + "基", + "堂", + "堆", + "堑", + "堕", + "堡", + "堤", + "堪", + "堰", + "堵", + "塌", + "塑", + "塔", + "塘", + "塞", + "填", + "境", + "墅", + "墒", + "墓", + "墙", + "增", + "墟", + "墨", + "墩", + "壁", + "壕", + "壤", + "士", + "壬", + "壮", + "声", + "壳", + "壶", + "壹", + "处", + "备", + "复", + "夏", + "夕", + "外", + "多", + "夜", + "够", + "大", + "天", + "太", + "夫", + "央", + "夯", + "失", + "头", + "夷", + "夸", + "夹", + "夺", + "奄", + "奇", + "奈", + "奉", + "奋", + "奎", + "奏", + "契", + "奔", + "奖", + "套", + "奠", + "奢", + "奥", + "女", + "奴", + "奶", + "奸", + "她", + "好", + "如", + "妄", + "妆", + "妇", + "妈", + "妊", + "妒", + "妓", + "妖", + "妙", + "妥", + "妨", + "妮", + "妹", + "妻", + "姆", + "始", + "姐", + "姑", + "姓", + "委", + "姚", + "姜", + "姥", + "姨", + "姬", + "姻", + "姿", + "威", + "娃", + "娄", + "娇", + "娘", + "娜", + "娟", + "娠", + "娥", + "娩", + "娱", + "娶", + "婆", + "婉", + "婚", + "婪", + "婴", + "婶", + "婿", + "媒", + "媚", + "媳", + "嫁", + "嫂", + "嫉", + "嫌", + "嫡", + "嫩", + "子", + "孔", + "孕", + "字", + "存", + "孙", + "孜", + "孝", + "孟", + "季", + "孤", + "学", + "孩", + "孪", + "孰", + "孵", + "孺", + "孽", + "宁", + "它", + "宅", + "宇", + "守", + "安", + "宋", + "完", + "宏", + "宗", + "官", + "宙", + "定", + "宛", + "宜", + "宝", + "实", + "宠", + "审", + "客", + "宣", + "室", + "宦", + "宪", + "宫", + "宰", + "害", + "宴", + "宵", + "家", + "容", + "宽", + "宾", + "宿", + "寂", + "寄", + "寅", + "密", + "寇", + "富", + "寐", + "寒", + "寓", + "寝", + "寞", + "察", + "寡", + "寥", + "寨", + "寸", + "对", + "寺", + "寻", + "导", + "寿", + "封", + "射", + "将", + "尉", + "尊", + "小", + "少", + "尔", + "尖", + "尘", + "尚", + "尝", + "尤", + "尧", + "就", + "尸", + "尹", + "尺", + "尼", + "尽", + "尾", + "尿", + "局", + "屁", + "层", + "居", + "屈", + "屉", + "届", + "屋", + "屎", + "屏", + "屑", + "展", + "属", + "屠", + "屡", + "履", + "屯", + "山", + "屹", + "屿", + "岁", + "岂", + "岔", + "岗", + "岛", + "岩", + "岭", + "岳", + "岸", + "岿", + "峙", + "峡", + "峦", + "峨", + "峪", + "峭", + "峰", + "峻", + "崇", + "崎", + "崔", + "崖", + "崩", + "崭", + "嵌", + "巍", + "川", + "州", + "巡", + "巢", + "工", + "左", + "巧", + "巨", + "巩", + "巫", + "差", + "己", + "已", + "巳", + "巴", + "巷", + "巾", + "币", + "市", + "布", + "帅", + "帆", + "师", + "希", + "帐", + "帕", + "帖", + "帘", + "帚", + "帛", + "帜", + "帝", + "带", + "帧", + "席", + "帮", + "常", + "帽", + "幂", + "幅", + "幌", + "幕", + "幢", + "干", + "平", + "年", + "并", + "幸", + "幻", + "幼", + "幽", + "广", + "庄", + "庆", + "庇", + "床", + "序", + "庐", + "库", + "应", + "底", + "店", + "庙", + "庚", + "府", + "庞", + "废", + "度", + "座", + "庭", + "庶", + "康", + "庸", + "廉", + "廊", + "廓", + "廖", + "延", + "廷", + "建", + "开", + "异", + "弃", + "弄", + "弊", + "式", + "弓", + "引", + "弗", + "弘", + "弛", + "弟", + "张", + "弥", + "弦", + "弧", + "弯", + "弱", + "弹", + "强", + "归", + "当", + "录", + "彝", + "形", + "彤", + "彦", + "彩", + "彪", + "彬", + "彭", + "彰", + "影", + "役", + "彻", + "彼", + "往", + "征", + "径", + "待", + "很", + "徊", + "律", + "徐", + "徒", + "得", + "徘", + "御", + "循", + "微", + "德", + "徽", + "心", + "必", + "忆", + "忌", + "忍", + "志", + "忘", + "忙", + "忠", + "忧", + "快", + "忱", + "念", + "忻", + "忽", + "忿", + "怀", + "态", + "怂", + "怎", + "怒", + "怔", + "怕", + "怖", + "怜", + "思", + "怠", + "急", + "性", + "怨", + "怪", + "怯", + "总", + "恃", + "恋", + "恍", + "恐", + "恒", + "恕", + "恢", + "恤", + "恨", + "恩", + "恫", + "恬", + "恭", + "息", + "恰", + "恳", + "恶", + "恼", + "恿", + "悄", + "悉", + "悍", + "悔", + "悟", + "悠", + "患", + "悦", + "您", + "悬", + "悯", + "悲", + "悸", + "悼", + "情", + "惊", + "惋", + "惑", + "惕", + "惜", + "惟", + "惠", + "惦", + "惧", + "惨", + "惩", + "惫", + "惭", + "惮", + "惯", + "惰", + "想", + "惶", + "惹", + "惺", + "愁", + "愈", + "愉", + "意", + "愚", + "感", + "愤", + "愧", + "愿", + "慈", + "慌", + "慎", + "慑", + "慕", + "慢", + "慧", + "慨", + "慰", + "慷", + "憋", + "憎", + "憨", + "憾", + "懂", + "懈", + "懊", + "懒", + "懦", + "戈", + "戊", + "戌", + "戍", + "戎", + "戏", + "成", + "我", + "戒", + "或", + "战", + "戚", + "截", + "戮", + "戳", + "戴", + "户", + "房", + "所", + "扁", + "扇", + "手", + "才", + "扎", + "扑", + "扒", + "打", + "扔", + "托", + "扛", + "扣", + "扦", + "执", + "扩", + "扫", + "扬", + "扭", + "扮", + "扯", + "扰", + "扳", + "扶", + "批", + "扼", + "找", + "承", + "技", + "抄", + "抉", + "把", + "抑", + "抒", + "抓", + "投", + "抖", + "抗", + "折", + "抚", + "抛", + "抠", + "抡", + "抢", + "护", + "报", + "抨", + "披", + "抬", + "抱", + "抵", + "抹", + "押", + "抽", + "抿", + "拂", + "拄", + "担", + "拆", + "拇", + "拈", + "拉", + "拌", + "拍", + "拎", + "拐", + "拒", + "拓", + "拔", + "拖", + "拘", + "拙", + "招", + "拜", + "拟", + "拢", + "拣", + "拥", + "拦", + "拧", + "拨", + "择", + "括", + "拭", + "拯", + "拱", + "拳", + "拴", + "拷", + "拼", + "拽", + "拾", + "拿", + "持", + "挂", + "指", + "按", + "挎", + "挑", + "挖", + "挚", + "挛", + "挝", + "挞", + "挟", + "挠", + "挡", + "挣", + "挤", + "挥", + "挨", + "挪", + "挫", + "振", + "挺", + "挽", + "捂", + "捅", + "捆", + "捉", + "捌", + "捍", + "捎", + "捏", + "捐", + "捕", + "捞", + "损", + "捡", + "换", + "捣", + "捧", + "据", + "捶", + "捷", + "捻", + "掀", + "掂", + "掇", + "授", + "掉", + "掌", + "掏", + "掐", + "排", + "掖", + "掘", + "掠", + "探", + "掣", + "接", + "控", + "推", + "掩", + "措", + "掳", + "掷", + "掸", + "掺", + "揉", + "揍", + "描", + "提", + "插", + "揖", + "握", + "揣", + "揩", + "揪", + "揭", + "援", + "揽", + "搀", + "搁", + "搂", + "搅", + "搏", + "搐", + "搓", + "搔", + "搜", + "搞", + "搪", + "搬", + "搭", + "携", + "搽", + "摄", + "摆", + "摇", + "摈", + "摊", + "摔", + "摘", + "摧", + "摩", + "摸", + "摹", + "撂", + "撅", + "撇", + "撑", + "撒", + "撕", + "撞", + "撤", + "撩", + "撬", + "播", + "撮", + "撰", + "撵", + "撼", + "擂", + "擅", + "操", + "擎", + "擒", + "擞", + "擦", + "攀", + "攒", + "攘", + "攫", + "支", + "收", + "改", + "攻", + "放", + "政", + "故", + "效", + "敌", + "敏", + "救", + "敖", + "教", + "敛", + "敝", + "敞", + "敢", + "散", + "敦", + "敬", + "数", + "敲", + "整", + "敷", + "文", + "斋", + "斌", + "斑", + "斗", + "料", + "斜", + "斟", + "斡", + "斤", + "斥", + "斧", + "斩", + "断", + "斯", + "新", + "方", + "施", + "旁", + "旅", + "旋", + "族", + "旗", + "无", + "既", + "日", + "旦", + "旧", + "旨", + "早", + "旬", + "旭", + "旱", + "时", + "旷", + "旺", + "昂", + "昆", + "昌", + "明", + "昏", + "易", + "昔", + "星", + "映", + "春", + "昧", + "昨", + "昭", + "是", + "昼", + "显", + "晃", + "晋", + "晌", + "晒", + "晓", + "晕", + "晚", + "晤", + "晦", + "晨", + "普", + "景", + "晰", + "晴", + "晶", + "智", + "晾", + "暂", + "暇", + "暑", + "暖", + "暗", + "暮", + "暴", + "曙", + "曝", + "曰", + "曲", + "曳", + "更", + "曹", + "曼", + "曾", + "替", + "最", + "月", + "有", + "朋", + "服", + "朔", + "朗", + "望", + "朝", + "期", + "木", + "未", + "末", + "本", + "札", + "术", + "朱", + "朴", + "朵", + "机", + "朽", + "杀", + "杂", + "权", + "杆", + "杉", + "李", + "杏", + "材", + "村", + "杖", + "杜", + "束", + "杠", + "条", + "来", + "杨", + "杭", + "杯", + "杰", + "松", + "板", + "极", + "构", + "枉", + "析", + "枕", + "林", + "枚", + "果", + "枝", + "枢", + "枣", + "枪", + "枫", + "枯", + "架", + "枷", + "柄", + "柏", + "某", + "柑", + "柒", + "染", + "柔", + "柜", + "柞", + "柠", + "查", + "柬", + "柯", + "柱", + "柳", + "柴", + "柿", + "栅", + "标", + "栈", + "栋", + "栏", + "树", + "栓", + "栖", + "栗", + "校", + "株", + "样", + "核", + "根", + "格", + "栽", + "桂", + "桃", + "桅", + "框", + "案", + "桌", + "桐", + "桑", + "桓", + "桔", + "档", + "桥", + "桨", + "桩", + "桶", + "梁", + "梅", + "梆", + "梗", + "梢", + "梦", + "梧", + "梨", + "梭", + "梯", + "械", + "梳", + "检", + "棉", + "棋", + "棍", + "棒", + "棕", + "棘", + "棚", + "棠", + "森", + "棱", + "棵", + "棺", + "椅", + "植", + "椎", + "椒", + "椭", + "椰", + "椽", + "椿", + "楔", + "楚", + "楞", + "楷", + "楼", + "概", + "榆", + "榔", + "榜", + "榨", + "榴", + "榷", + "槐", + "槛", + "槽", + "樊", + "樟", + "模", + "横", + "樱", + "橇", + "橙", + "橡", + "橱", + "檀", + "檄", + "檬", + "欠", + "次", + "欢", + "欣", + "欧", + "欲", + "欺", + "款", + "歇", + "歉", + "歌", + "止", + "正", + "此", + "步", + "武", + "歧", + "歪", + "歹", + "死", + "歼", + "殃", + "殆", + "殉", + "殊", + "残", + "殖", + "殴", + "段", + "殷", + "殿", + "毁", + "毅", + "毋", + "母", + "每", + "毒", + "比", + "毕", + "毖", + "毗", + "毙", + "毛", + "毡", + "毫", + "毯", + "氏", + "民", + "氓", + "气", + "氖", + "氛", + "氟", + "氢", + "氦", + "氧", + "氨", + "氮", + "氯", + "氰", + "水", + "永", + "汀", + "汁", + "求", + "汇", + "汉", + "汐", + "汕", + "汗", + "汛", + "汝", + "汞", + "江", + "池", + "污", + "汤", + "汪", + "汰", + "汲", + "汹", + "汽", + "汾", + "沁", + "沂", + "沃", + "沈", + "沉", + "沏", + "沙", + "沛", + "沟", + "没", + "沤", + "沥", + "沦", + "沧", + "沪", + "沫", + "沮", + "河", + "沸", + "油", + "治", + "沼", + "沽", + "沾", + "沿", + "泄", + "泅", + "泉", + "泊", + "泌", + "法", + "泛", + "泞", + "泡", + "波", + "泣", + "泥", + "注", + "泪", + "泰", + "泳", + "泵", + "泻", + "泼", + "泽", + "洁", + "洋", + "洒", + "洗", + "洛", + "洞", + "津", + "洪", + "洱", + "洲", + "活", + "洼", + "洽", + "派", + "流", + "浅", + "浆", + "浇", + "浊", + "测", + "济", + "浏", + "浑", + "浓", + "浙", + "浚", + "浦", + "浩", + "浪", + "浮", + "浴", + "海", + "浸", + "涂", + "涅", + "消", + "涉", + "涌", + "涎", + "涕", + "涛", + "涝", + "涟", + "涡", + "涣", + "涤", + "润", + "涧", + "涨", + "涩", + "涪", + "涯", + "液", + "涵", + "涸", + "淀", + "淄", + "淆", + "淋", + "淌", + "淑", + "淖", + "淘", + "淡", + "淤", + "淫", + "淬", + "淮", + "深", + "淳", + "混", + "淹", + "添", + "清", + "渊", + "渍", + "渐", + "渔", + "渗", + "渝", + "渠", + "渡", + "渣", + "渤", + "温", + "渭", + "港", + "渴", + "游", + "渺", + "湃", + "湍", + "湖", + "湘", + "湛", + "湾", + "湿", + "溃", + "溅", + "溉", + "源", + "溜", + "溢", + "溪", + "溯", + "溶", + "溺", + "滁", + "滇", + "滋", + "滑", + "滓", + "滔", + "滚", + "滞", + "满", + "滤", + "滥", + "滦", + "滨", + "滩", + "滴", + "漂", + "漆", + "漏", + "漓", + "演", + "漠", + "漫", + "漱", + "漳", + "漾", + "潍", + "潘", + "潜", + "潞", + "潦", + "潭", + "潮", + "澄", + "澈", + "澎", + "澜", + "澡", + "澳", + "激", + "濒", + "瀑", + "灌", + "火", + "灭", + "灯", + "灰", + "灵", + "灶", + "灸", + "灼", + "灾", + "灿", + "炉", + "炊", + "炎", + "炒", + "炔", + "炕", + "炙", + "炬", + "炭", + "炮", + "炯", + "炳", + "炸", + "点", + "炼", + "炽", + "烁", + "烂", + "烃", + "烈", + "烘", + "烙", + "烛", + "烟", + "烤", + "烦", + "烧", + "烩", + "烫", + "烬", + "热", + "烯", + "烷", + "烹", + "烽", + "焉", + "焊", + "焕", + "焙", + "焚", + "焦", + "焰", + "然", + "煌", + "煎", + "煞", + "煤", + "照", + "煮", + "煽", + "熄", + "熊", + "熏", + "熔", + "熙", + "熟", + "熬", + "燃", + "燎", + "燕", + "燥", + "爆", + "爪", + "爬", + "爱", + "爵", + "父", + "爷", + "爸", + "爹", + "爽", + "片", + "版", + "牌", + "牙", + "牛", + "牟", + "牡", + "牢", + "牧", + "物", + "牲", + "牵", + "特", + "牺", + "犀", + "犁", + "犊", + "犬", + "犯", + "状", + "犹", + "狂", + "狄", + "狈", + "狐", + "狗", + "狙", + "狞", + "狠", + "狡", + "独", + "狭", + "狮", + "狰", + "狱", + "狸", + "狼", + "猎", + "猖", + "猛", + "猜", + "猩", + "猪", + "猫", + "献", + "猴", + "猾", + "猿", + "獭", + "玄", + "率", + "玉", + "王", + "玖", + "玛", + "玩", + "玫", + "环", + "现", + "玲", + "玻", + "珊", + "珍", + "珐", + "珠", + "班", + "球", + "琅", + "理", + "琉", + "琐", + "琢", + "琳", + "琴", + "琵", + "琶", + "琼", + "瑚", + "瑞", + "瑟", + "瑰", + "瑶", + "璃", + "瓜", + "瓢", + "瓣", + "瓤", + "瓦", + "瓮", + "瓶", + "瓷", + "甄", + "甘", + "甚", + "甜", + "生", + "甥", + "用", + "甩", + "甫", + "甭", + "田", + "由", + "甲", + "申", + "电", + "男", + "甸", + "画", + "畅", + "界", + "畏", + "畔", + "留", + "畜", + "略", + "畦", + "番", + "畴", + "畸", + "疆", + "疏", + "疑", + "疗", + "疙", + "疚", + "疟", + "疡", + "疤", + "疥", + "疫", + "疮", + "疯", + "疲", + "疵", + "疹", + "疼", + "疽", + "疾", + "病", + "症", + "痈", + "痉", + "痊", + "痒", + "痔", + "痕", + "痘", + "痛", + "痞", + "痢", + "痪", + "痰", + "痴", + "痹", + "瘁", + "瘟", + "瘤", + "瘦", + "瘩", + "瘪", + "瘫", + "瘴", + "瘸", + "癌", + "癣", + "癸", + "登", + "白", + "百", + "皂", + "的", + "皆", + "皇", + "皋", + "皑", + "皖", + "皮", + "皱", + "皿", + "盂", + "盅", + "盆", + "盈", + "益", + "盎", + "盏", + "盐", + "监", + "盒", + "盔", + "盖", + "盗", + "盘", + "盛", + "盟", + "目", + "盯", + "盲", + "直", + "相", + "盼", + "盾", + "省", + "眉", + "看", + "真", + "眠", + "眨", + "眩", + "眯", + "眶", + "眷", + "眺", + "眼", + "着", + "睁", + "睛", + "睡", + "督", + "睦", + "睫", + "睬", + "睹", + "瞄", + "瞅", + "瞎", + "瞒", + "瞥", + "瞧", + "瞩", + "瞪", + "瞬", + "瞳", + "瞻", + "矗", + "矛", + "矢", + "矣", + "知", + "矩", + "矫", + "短", + "矮", + "石", + "矽", + "矾", + "矿", + "码", + "砂", + "砌", + "砍", + "砒", + "研", + "砖", + "砚", + "砧", + "砰", + "破", + "砷", + "砸", + "砾", + "础", + "硅", + "硒", + "硕", + "硝", + "硫", + "硬", + "确", + "硷", + "硼", + "碉", + "碌", + "碍", + "碎", + "碑", + "碗", + "碘", + "碟", + "碧", + "碰", + "碱", + "碳", + "碴", + "碾", + "磁", + "磅", + "磊", + "磋", + "磐", + "磕", + "磨", + "磷", + "磺", + "礁", + "示", + "礼", + "社", + "祁", + "祈", + "祖", + "祝", + "神", + "祟", + "祥", + "票", + "祭", + "祷", + "祸", + "禁", + "禄", + "福", + "禹", + "离", + "禽", + "禾", + "秀", + "私", + "秃", + "秆", + "秉", + "秋", + "种", + "科", + "秒", + "秘", + "租", + "秤", + "秦", + "秧", + "秩", + "积", + "称", + "秸", + "移", + "秽", + "稀", + "程", + "稍", + "税", + "稗", + "稚", + "稠", + "稳", + "稻", + "稼", + "稽", + "稿", + "穆", + "穗", + "穴", + "究", + "穷", + "空", + "穿", + "突", + "窃", + "窄", + "窍", + "窑", + "窒", + "窖", + "窗", + "窘", + "窜", + "窝", + "窟", + "窥", + "窿", + "立", + "竖", + "站", + "竞", + "竟", + "章", + "竣", + "童", + "竭", + "端", + "竹", + "竿", + "笆", + "笋", + "笑", + "笔", + "笛", + "符", + "笨", + "第", + "笺", + "笼", + "等", + "筋", + "筏", + "筐", + "筑", + "筒", + "答", + "策", + "筛", + "筷", + "筹", + "签", + "简", + "箍", + "箔", + "箕", + "算", + "管", + "箩", + "箭", + "箱", + "篆", + "篇", + "篓", + "篙", + "篡", + "篮", + "篱", + "篷", + "簇", + "簧", + "簿", + "籍", + "米", + "类", + "籽", + "粉", + "粒", + "粕", + "粗", + "粘", + "粟", + "粤", + "粥", + "粪", + "粮", + "粱", + "粳", + "粹", + "精", + "糊", + "糕", + "糖", + "糙", + "糜", + "糟", + "糠", + "糯", + "系", + "紊", + "素", + "索", + "紧", + "紫", + "累", + "絮", + "繁", + "纂", + "纠", + "红", + "纤", + "约", + "级", + "纪", + "纫", + "纬", + "纯", + "纱", + "纲", + "纳", + "纵", + "纶", + "纷", + "纸", + "纹", + "纺", + "纽", + "线", + "练", + "组", + "绅", + "细", + "织", + "终", + "绊", + "绍", + "绎", + "经", + "绑", + "绒", + "结", + "绕", + "绘", + "给", + "绚", + "络", + "绝", + "绞", + "统", + "绢", + "绣", + "绥", + "绦", + "继", + "绩", + "绪", + "续", + "绰", + "绳", + "维", + "绵", + "绷", + "绸", + "综", + "绽", + "绿", + "缀", + "缄", + "缅", + "缆", + "缉", + "缎", + "缓", + "缔", + "缕", + "编", + "缘", + "缚", + "缝", + "缠", + "缨", + "缩", + "缮", + "缴", + "缸", + "缺", + "罐", + "网", + "罕", + "罗", + "罚", + "罢", + "罩", + "罪", + "置", + "署", + "羊", + "羌", + "美", + "羔", + "羚", + "羞", + "羡", + "群", + "羹", + "羽", + "翁", + "翅", + "翌", + "翔", + "翘", + "翟", + "翠", + "翰", + "翱", + "翻", + "翼", + "耀", + "老", + "考", + "者", + "而", + "耍", + "耐", + "耕", + "耗", + "耘", + "耙", + "耪", + "耳", + "耶", + "耸", + "耻", + "耽", + "耿", + "聂", + "聊", + "聋", + "职", + "联", + "聘", + "聚", + "聪", + "肃", + "肄", + "肆", + "肇", + "肉", + "肋", + "肌", + "肖", + "肘", + "肚", + "肛", + "肝", + "肠", + "股", + "肢", + "肤", + "肥", + "肩", + "肪", + "肮", + "肯", + "育", + "肺", + "肾", + "肿", + "胀", + "胁", + "胃", + "胆", + "背", + "胎", + "胖", + "胚", + "胜", + "胞", + "胡", + "胯", + "胰", + "胳", + "胶", + "胸", + "胺", + "能", + "脂", + "脆", + "脉", + "脊", + "脏", + "脐", + "脑", + "脓", + "脖", + "脚", + "脯", + "脱", + "脸", + "脾", + "腆", + "腊", + "腋", + "腐", + "腑", + "腔", + "腕", + "腥", + "腮", + "腰", + "腹", + "腺", + "腻", + "腾", + "腿", + "膀", + "膊", + "膏", + "膘", + "膛", + "膜", + "膝", + "膨", + "膳", + "臀", + "臂", + "臃", + "臆", + "臣", + "自", + "臭", + "至", + "致", + "臻", + "臼", + "舀", + "舅", + "舆", + "舌", + "舍", + "舒", + "舔", + "舜", + "舞", + "舟", + "航", + "般", + "舰", + "舱", + "舵", + "舶", + "舷", + "船", + "艇", + "艘", + "良", + "艰", + "色", + "艳", + "艺", + "艾", + "节", + "芋", + "芍", + "芒", + "芜", + "芝", + "芥", + "芦", + "芬", + "芭", + "芯", + "花", + "芳", + "芹", + "芽", + "苇", + "苍", + "苏", + "苑", + "苔", + "苗", + "苛", + "苞", + "苟", + "若", + "苦", + "苫", + "苯", + "英", + "苹", + "茁", + "茂", + "范", + "茄", + "茅", + "茎", + "茧", + "茨", + "茫", + "茬", + "茵", + "茶", + "茸", + "茹", + "荆", + "草", + "荐", + "荒", + "荔", + "荚", + "荡", + "荣", + "荤", + "荧", + "荫", + "药", + "荷", + "莆", + "莉", + "莎", + "莫", + "莱", + "莲", + "获", + "莹", + "莽", + "菇", + "菊", + "菌", + "菏", + "菜", + "菠", + "菩", + "菱", + "菲", + "萄", + "萌", + "萍", + "萎", + "萝", + "萤", + "营", + "萧", + "萨", + "落", + "著", + "葛", + "葡", + "董", + "葫", + "葬", + "葱", + "葵", + "蒂", + "蒋", + "蒙", + "蒜", + "蒲", + "蒸", + "蓄", + "蓉", + "蓑", + "蓖", + "蓝", + "蓟", + "蓬", + "蔑", + "蔓", + "蔗", + "蔚", + "蔡", + "蔫", + "蔬", + "蔷", + "蔼", + "蔽", + "蕉", + "蕊", + "蕴", + "蕾", + "薄", + "薛", + "薪", + "薯", + "藉", + "藏", + "藐", + "藕", + "藤", + "藩", + "藻", + "蘑", + "蘸", + "虎", + "虏", + "虐", + "虑", + "虚", + "虞", + "虫", + "虱", + "虹", + "虽", + "虾", + "蚀", + "蚁", + "蚂", + "蚊", + "蚌", + "蚕", + "蚜", + "蚤", + "蛀", + "蛆", + "蛇", + "蛊", + "蛋", + "蛔", + "蛙", + "蛛", + "蛤", + "蛮", + "蛰", + "蛹", + "蛾", + "蜀", + "蜂", + "蜒", + "蜕", + "蜗", + "蜘", + "蜜", + "蜡", + "蝇", + "蝉", + "蝎", + "蝗", + "蝴", + "蝶", + "融", + "螟", + "螺", + "蟹", + "蠕", + "蠢", + "血", + "衅", + "行", + "衍", + "衔", + "街", + "衙", + "衡", + "衣", + "补", + "表", + "衫", + "衬", + "衰", + "衷", + "袁", + "袄", + "袋", + "袍", + "袒", + "袖", + "袜", + "被", + "袭", + "袱", + "裁", + "裂", + "装", + "裔", + "裕", + "裙", + "裤", + "裳", + "裴", + "裸", + "裹", + "褂", + "褐", + "褒", + "褥", + "褪", + "襄", + "襟", + "西", + "要", + "覆", + "见", + "观", + "规", + "觅", + "视", + "览", + "觉", + "角", + "解", + "触", + "言", + "詹", + "誉", + "誊", + "誓", + "警", + "譬", + "计", + "订", + "讣", + "认", + "讥", + "讨", + "让", + "讫", + "训", + "议", + "讯", + "记", + "讲", + "讳", + "讶", + "许", + "讹", + "论", + "讼", + "讽", + "设", + "访", + "诀", + "证", + "评", + "诅", + "识", + "诈", + "诉", + "诊", + "诌", + "词", + "译", + "试", + "诗", + "诚", + "诛", + "话", + "诞", + "诡", + "询", + "诣", + "该", + "详", + "诧", + "诫", + "诬", + "语", + "误", + "诱", + "诲", + "说", + "诵", + "请", + "诸", + "诺", + "读", + "诽", + "课", + "谁", + "调", + "谅", + "谆", + "谈", + "谊", + "谋", + "谍", + "谎", + "谐", + "谓", + "谗", + "谚", + "谜", + "谢", + "谣", + "谤", + "谦", + "谨", + "谩", + "谬", + "谭", + "谰", + "谱", + "谴", + "谷", + "豁", + "豆", + "豌", + "象", + "豢", + "豪", + "豫", + "豹", + "豺", + "貉", + "貌", + "贝", + "贞", + "负", + "贡", + "财", + "责", + "贤", + "败", + "账", + "货", + "质", + "贩", + "贪", + "贫", + "贬", + "购", + "贮", + "贯", + "贰", + "贱", + "贴", + "贵", + "贷", + "贸", + "费", + "贺", + "贼", + "贾", + "贿", + "赁", + "赂", + "赃", + "资", + "赊", + "赋", + "赌", + "赎", + "赏", + "赐", + "赔", + "赖", + "赘", + "赚", + "赛", + "赞", + "赠", + "赡", + "赢", + "赣", + "赤", + "赦", + "赫", + "走", + "赴", + "赵", + "赶", + "起", + "趁", + "超", + "越", + "趋", + "趟", + "趣", + "足", + "趴", + "趾", + "跃", + "跋", + "跌", + "跑", + "距", + "跟", + "跨", + "跪", + "路", + "跳", + "践", + "跺", + "踊", + "踌", + "踏", + "踞", + "踢", + "踩", + "踪", + "蹄", + "蹈", + "蹋", + "蹦", + "蹬", + "蹭", + "蹲", + "蹿", + "躁", + "躇", + "身", + "躬", + "躯", + "躲", + "躺", + "车", + "轧", + "轨", + "轩", + "转", + "轮", + "软", + "轰", + "轴", + "轻", + "载", + "轿", + "较", + "辅", + "辆", + "辈", + "辉", + "辊", + "辐", + "辑", + "输", + "辕", + "辖", + "辗", + "辙", + "辛", + "辜", + "辞", + "辟", + "辣", + "辨", + "辩", + "辫", + "辰", + "辱", + "边", + "辽", + "达", + "迁", + "迂", + "迄", + "迅", + "过", + "迈", + "迎", + "运", + "近", + "返", + "还", + "这", + "进", + "远", + "违", + "连", + "迟", + "迢", + "迪", + "迫", + "迭", + "述", + "迷", + "迸", + "迹", + "追", + "退", + "送", + "适", + "逃", + "逆", + "选", + "逊", + "透", + "逐", + "递", + "途", + "逗", + "通", + "逛", + "逝", + "逞", + "速", + "造", + "逢", + "逮", + "逸", + "逻", + "逼", + "逾", + "遁", + "遂", + "遇", + "遍", + "遏", + "道", + "遗", + "遣", + "遥", + "遭", + "遮", + "遵", + "避", + "邀", + "邑", + "邓", + "邢", + "那", + "邦", + "邪", + "邮", + "邯", + "邱", + "邵", + "邹", + "邻", + "郁", + "郊", + "郎", + "郑", + "郝", + "郡", + "郧", + "部", + "郭", + "郴", + "郸", + "都", + "鄂", + "鄙", + "酉", + "酋", + "酌", + "配", + "酒", + "酗", + "酚", + "酝", + "酞", + "酣", + "酥", + "酪", + "酬", + "酮", + "酱", + "酵", + "酶", + "酷", + "酸", + "酿", + "醇", + "醉", + "醋", + "醒", + "醚", + "醛", + "采", + "釉", + "释", + "里", + "重", + "野", + "量", + "金", + "釜", + "鉴", + "针", + "钉", + "钎", + "钒", + "钓", + "钙", + "钝", + "钞", + "钟", + "钠", + "钡", + "钢", + "钥", + "钦", + "钧", + "钨", + "钩", + "钮", + "钱", + "钳", + "钵", + "钻", + "钾", + "铀", + "铁", + "铂", + "铃", + "铅", + "铆", + "铜", + "铝", + "铡", + "铣", + "铬", + "铭", + "铰", + "铱", + "铲", + "银", + "铸", + "铺", + "链", + "销", + "锁", + "锄", + "锅", + "锈", + "锋", + "锌", + "锐", + "锑", + "锗", + "错", + "锚", + "锡", + "锣", + "锤", + "锥", + "锦", + "锨", + "锭", + "键", + "锯", + "锰", + "锹", + "锻", + "镀", + "镁", + "镇", + "镊", + "镍", + "镐", + "镑", + "镜", + "镣", + "镭", + "镰", + "镶", + "长", + "门", + "闪", + "闭", + "问", + "闯", + "闰", + "闲", + "间", + "闷", + "闸", + "闹", + "闺", + "闻", + "闽", + "阀", + "阁", + "阂", + "阅", + "阉", + "阎", + "阐", + "阑", + "阔", + "阜", + "队", + "阮", + "防", + "阳", + "阴", + "阵", + "阶", + "阻", + "阿", + "陀", + "附", + "际", + "陆", + "陇", + "陈", + "陋", + "陌", + "降", + "限", + "陕", + "陛", + "陡", + "院", + "除", + "陨", + "险", + "陪", + "陵", + "陶", + "陷", + "隅", + "隆", + "隋", + "随", + "隐", + "隔", + "隘", + "隙", + "障", + "隧", + "隶", + "难", + "雀", + "雁", + "雄", + "雅", + "集", + "雇", + "雌", + "雍", + "雏", + "雕", + "雨", + "雪", + "零", + "雷", + "雹", + "雾", + "需", + "霄", + "震", + "霉", + "霍", + "霓", + "霖", + "霜", + "霞", + "露", + "霸", + "霹", + "青", + "靖", + "静", + "靛", + "非", + "靠", + "靡", + "面", + "革", + "靳", + "靴", + "靶", + "鞋", + "鞍", + "鞘", + "鞠", + "鞭", + "韦", + "韧", + "韩", + "韭", + "音", + "韵", + "韶", + "页", + "顶", + "顷", + "项", + "顺", + "须", + "顽", + "顾", + "顿", + "颁", + "颂", + "预", + "颅", + "领", + "颇", + "颈", + "颊", + "颐", + "频", + "颓", + "颖", + "颗", + "题", + "颜", + "额", + "颠", + "颤", + "颧", + "风", + "飘", + "飞", + "食", + "餐", + "饥", + "饭", + "饮", + "饯", + "饰", + "饱", + "饲", + "饵", + "饶", + "饺", + "饼", + "饿", + "馁", + "馅", + "馆", + "馈", + "馋", + "馏", + "馒", + "首", + "香", + "马", + "驭", + "驮", + "驯", + "驰", + "驱", + "驳", + "驴", + "驶", + "驹", + "驻", + "驼", + "驾", + "骂", + "骄", + "骆", + "骇", + "骋", + "验", + "骏", + "骑", + "骗", + "骚", + "骡", + "骤", + "骨", + "骸", + "髓", + "高", + "鬃", + "鬼", + "魁", + "魂", + "魄", + "魏", + "魔", + "鱼", + "鲁", + "鲍", + "鲜", + "鲤", + "鲸", + "鳃", + "鳖", + "鳞", + "鸟", + "鸡", + "鸣", + "鸥", + "鸦", + "鸭", + "鸯", + "鸳", + "鸵", + "鸽", + "鸿", + "鹃", + "鹅", + "鹊", + "鹏", + "鹤", + "鹰", + "鹿", + "麓", + "麦", + "麻", + "黄", + "黍", + "黎", + "黑", + "黔", + "默", + "鼎", + "鼓", + "鼠", + "鼻", + "齐", + "齿", + "龄", + "龋", + "龙", + "龚", + "龟", + "!", + "(", + ")", + ",", + ":", + ";", + "?" + ] +} diff --git a/desktop/build.rs b/desktop/build.rs index 0743642..ebc8ad1 100644 --- a/desktop/build.rs +++ b/desktop/build.rs @@ -25,8 +25,14 @@ fn main() { } generate_slint_version(&version); - generate_slint_i18n(); - slint_build::compile("ui/app.slint").expect("slint compile failed"); + let zh = generate_slint_i18n(); + verify_font_assets(&zh); + slint_build::compile_with_config( + "ui/app.slint", + slint_build::CompilerConfiguration::new() + .embed_resources(slint_build::EmbedResourcesKind::EmbedFiles), + ) + .expect("slint compile failed"); } fn generate_slint_version(version: &str) { @@ -57,7 +63,7 @@ fn embed_macos_plist(version: &str) { ); } -fn generate_slint_i18n() { +fn generate_slint_i18n() -> BTreeMap { let manifest = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); let zh_path = manifest.join("i18n/zh_CN.properties"); let en_path = manifest.join("i18n/en_US.properties"); @@ -78,6 +84,46 @@ fn generate_slint_i18n() { let out = manifest.join("ui/i18n.slint"); fs::write(&out, render_slint(&keys, &zh, &en)).expect("write ui/i18n.slint"); + zh +} + +fn verify_font_assets(zh: &BTreeMap) { + let manifest = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); + let fonts = manifest.join("assets/fonts"); + for name in [ + "NotoSansSC-Regular.subset.otf", + "NotoSansSC-Bold.subset.otf", + "OFL.txt", + "subset-manifest.json", + ] { + let path = fonts.join(name); + assert!( + path.is_file(), + "missing font asset {} — run: make desktop-font-subset", + path.display() + ); + println!("cargo:rerun-if-changed={}", path.display()); + } + + let meta = + fs::read_to_string(fonts.join("subset-manifest.json")).expect("read subset-manifest.json"); + if meta.contains("\"i18n-gb2312") { + for (key, value) in zh { + for ch in value.chars().filter(|c| needs_embedded_cjk_coverage(*c)) { + let needle = format!("\"{ch}\""); + assert!( + meta.contains(&needle), + "zh_CN key '{key}' uses '{ch}' missing from font subset; \ + run: make desktop-font-subset" + ); + } + } + } +} + +fn needs_embedded_cjk_coverage(ch: char) -> bool { + matches!(ch as u32, 0x3000..=0x303F | 0x4E00..=0x9FFF | 0xFF00..=0xFFEF) + || matches!(ch, '…' | '·' | '—') } fn parse_properties(raw: &str) -> BTreeMap { diff --git a/desktop/ui/app.slint b/desktop/ui/app.slint index 5b04725..25b4c41 100644 --- a/desktop/ui/app.slint +++ b/desktop/ui/app.slint @@ -1,3 +1,5 @@ +import "../assets/fonts/NotoSansSC-Regular.subset.otf"; +import "../assets/fonts/NotoSansSC-Bold.subset.otf"; import { Palette } from "std-widgets.slint"; import { Theme } from "theme.slint"; import { Sidebar, AppPage } from "sidebar.slint"; @@ -14,6 +16,8 @@ import { AppMeta } from "version.slint"; export { AppPage, TunnelRow, LogLine, Tr, AboutDialog, AppMeta, Theme } export component AppWindow inherits Window { title: "Orbien Desktop"; + default-font-family: "Noto Sans SC"; + default-font-size: 13px; icon: @image-url("../assets/app-icons/128x128.png"); preferred-width: 960px; preferred-height: 640px; diff --git a/desktop/ui/pages/logger.slint b/desktop/ui/pages/logger.slint index 93526ae..010b3c8 100644 --- a/desktop/ui/pages/logger.slint +++ b/desktop/ui/pages/logger.slint @@ -140,7 +140,7 @@ component LogRow inherits Rectangle { text: root.line.pre; color: root.body-color; font-size: 12px; - font-family: "Menlo, Monaco, Consolas, monospace"; + font-family: "Cascadia Mono, Consolas, Menlo, Monaco, monospace"; vertical-alignment: center; overflow: elide; horizontal-stretch: 0; @@ -159,7 +159,7 @@ component LogRow inherits Rectangle { color: #fef3c7; font-size: 12px; font-weight: 700; - font-family: "Menlo, Monaco, Consolas, monospace"; + font-family: "Cascadia Mono, Consolas, Menlo, Monaco, monospace"; vertical-alignment: center; } } @@ -169,7 +169,7 @@ component LogRow inherits Rectangle { text: root.line.post; color: root.body-color; font-size: 12px; - font-family: "Menlo, Monaco, Consolas, monospace"; + font-family: "Cascadia Mono, Consolas, Menlo, Monaco, monospace"; vertical-alignment: center; overflow: elide; horizontal-stretch: 1; @@ -182,7 +182,7 @@ component LogRow inherits Rectangle { text: root.line.text; color: root.line.level == 0 ? root.body-color : root.tag-color; font-size: 12px; - font-family: "Menlo, Monaco, Consolas, monospace"; + font-family: "Cascadia Mono, Consolas, Menlo, Monaco, monospace"; vertical-alignment: center; overflow: elide; horizontal-stretch: 1; diff --git a/desktop/ui/pages/tunnel/fields.slint b/desktop/ui/pages/tunnel/fields.slint index a28e805..7e7856e 100644 --- a/desktop/ui/pages/tunnel/fields.slint +++ b/desktop/ui/pages/tunnel/fields.slint @@ -14,7 +14,6 @@ export component FormField inherits Rectangle { x: 0; y: 0; width: parent.width; - height: 18px; text: root.label; color: Theme.text-secondary; font-size: 13px; @@ -47,7 +46,6 @@ export component FormPathField inherits Rectangle { x: 0; y: 0; width: parent.width; - height: 18px; text: root.label; color: Theme.text-secondary; font-size: 13px; @@ -94,7 +92,6 @@ export component FormCombo inherits Rectangle { x: 0; y: 0; width: parent.width; - height: 18px; text: root.label; color: Theme.text-secondary; font-size: 13px; diff --git a/scripts/gen-desktop-font-subset.py b/scripts/gen-desktop-font-subset.py new file mode 100644 index 0000000..1873b85 --- /dev/null +++ b/scripts/gen-desktop-font-subset.py @@ -0,0 +1,210 @@ +#!/usr/bin/env python3 +"""Build embedded Noto Sans SC subsets for orbien-desktop. + + python3 scripts/gen-desktop-font-subset.py # i18n + GB2312 L1 (~1.8 MB) + python3 scripts/gen-desktop-font-subset.py --regular-only + python3 scripts/gen-desktop-font-subset.py --full # full SC regional (~16 MB) +""" + +from __future__ import annotations + +import argparse +import json +import sys +import tempfile +import urllib.request +import zipfile +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +DESKTOP = ROOT / "desktop" +FONTS = DESKTOP / "assets" / "fonts" +SOURCE = FONTS / "source" +TOOLS = FONTS / ".tools" +I18N_ZH = DESKTOP / "i18n" / "zh_CN.properties" +MANIFEST = FONTS / "subset-manifest.json" + +CDN = "https://cdn.jsdelivr.net/gh/notofonts/noto-cjk@main/Sans/SubsetOTF/SC" +SRC_REGULAR = f"{CDN}/NotoSansSC-Regular.otf" +SRC_BOLD = f"{CDN}/NotoSansSC-Bold.otf" +OFL_URL = "https://cdn.jsdelivr.net/gh/google/fonts@main/ofl/notosanssc/OFL.txt" +FONTTOOLS_WHL = ( + "https://files.pythonhosted.org/packages/py3/f/fonttools/" + "fonttools-4.59.0-py3-none-any.whl" +) + +OUT_REGULAR = "NotoSansSC-Regular.subset.otf" +OUT_BOLD = "NotoSansSC-Bold.subset.otf" +FAMILY = "Noto Sans SC" + + +def download(url: str, dest: Path) -> None: + dest.parent.mkdir(parents=True, exist_ok=True) + print(f"fetch {dest.name}") + with urllib.request.urlopen(url, timeout=180) as resp: + dest.write_bytes(resp.read()) + + +def ensure_fonttools() -> None: + try: + import fontTools + + return + except ImportError: + pass + + TOOLS.mkdir(parents=True, exist_ok=True) + if not (TOOLS / "fontTools").is_dir(): + whl = TOOLS / "fonttools.whl" + if not whl.exists(): + download(FONTTOOLS_WHL, whl) + with zipfile.ZipFile(whl) as zf: + zf.extractall(TOOLS) + sys.path.insert(0, str(TOOLS)) + import fontTools + + +def i18n_chars() -> set[str]: + chars: set[str] = set() + for line in I18N_ZH.read_text(encoding="utf-8").splitlines(): + line = line.strip() + if not line or line.startswith("#") or "=" not in line: + continue + value = line.split("=", 1)[1].strip().replace("\\n", "\n").replace("\\t", "\t") + chars.update(value) + return chars + + +def gb2312_level1() -> set[str]: + out: set[str] = set() + for zone in range(0xB0, 0xD8): + for point in range(0xA1, 0xFF): + try: + ch = bytes([zone, point]).decode("gb2312") + except UnicodeDecodeError: + continue + if len(ch) == 1: + out.add(ch) + return out + + +def target_chars() -> set[str]: + chars = set(chr(c) for c in range(0x20, 0x7F)) + chars.update(",、。()…·—「」『』【】《》!?:;") + return chars | i18n_chars() | gb2312_level1() + + +def write_manifest(mode: str, chars: set[str], files: dict[str, int]) -> None: + listed = sorted(c for c in chars if ord(c) > 0x7F) + MANIFEST.write_text( + json.dumps( + { + "font_family": FAMILY, + "license": "SIL Open Font License 1.1 (see OFL.txt)", + "mode": mode, + "char_count": len(chars), + "files": files, + "chars": listed, + }, + indent=2, + ensure_ascii=False, + ) + + "\n", + encoding="utf-8", + ) + + +def ensure_sources() -> tuple[Path, Path]: + SOURCE.mkdir(parents=True, exist_ok=True) + regular = SOURCE / "NotoSansSC-Regular.otf" + bold = SOURCE / "NotoSansSC-Bold.otf" + if not regular.exists(): + download(SRC_REGULAR, regular) + if not bold.exists(): + download(SRC_BOLD, bold) + return regular, bold + + +def subset(src: Path, dest: Path, unicodes_file: Path) -> int: + from fontTools import subset as ftsubset + + ftsubset.main( + [ + str(src), + f"--output-file={dest}", + f"--unicodes-file={unicodes_file}", + "--layout-features=kern,liga", + "--name-IDs=*", + "--name-legacy", + "--name-languages=*", + "--notdef-outline", + "--recommended-glyphs", + "--recalc-bounds", + ] + ) + return dest.stat().st_size + + +def build_subset(regular_only: bool) -> None: + ensure_fonttools() + regular_src, bold_src = ensure_sources() + chars = target_chars() + print(f"subsetting {len(chars)} codepoints") + + with tempfile.NamedTemporaryFile("w", encoding="utf-8", suffix=".txt", delete=False) as f: + for c in sorted(chars, key=ord): + f.write(f"U+{ord(c):04X}\n") + unicodes = Path(f.name) + + files: dict[str, int] = {} + try: + regular_out = FONTS / OUT_REGULAR + bold_out = FONTS / OUT_BOLD + files[OUT_REGULAR] = subset(regular_src, regular_out, unicodes) + print(f" {OUT_REGULAR}: {files[OUT_REGULAR] / 1024:.0f} KB") + + if regular_only: + bold_out.write_bytes(regular_out.read_bytes()) + files[OUT_BOLD] = bold_out.stat().st_size + mode = "i18n-gb2312-level1-regular-only" + print(f" {OUT_BOLD}: copy of Regular") + else: + files[OUT_BOLD] = subset(bold_src, bold_out, unicodes) + mode = "i18n-gb2312-level1" + print(f" {OUT_BOLD}: {files[OUT_BOLD] / 1024:.0f} KB") + finally: + unicodes.unlink(missing_ok=True) + + ofl = FONTS / "OFL.txt" + if not ofl.exists(): + download(OFL_URL, ofl) + + write_manifest(mode, chars, files) + print(f"total {sum(files.values()) / 1024 / 1024:.2f} MB") + + +def build_full() -> None: + files: dict[str, int] = {} + for name, url in [(OUT_REGULAR, SRC_REGULAR), (OUT_BOLD, SRC_BOLD)]: + dest = FONTS / name + download(url, dest) + files[name] = dest.stat().st_size + download(OFL_URL, FONTS / "OFL.txt") + write_manifest("noto-cjk-sc-regional", i18n_chars(), files) + + +def main() -> None: + p = argparse.ArgumentParser(description=__doc__) + p.add_argument("--full", action="store_true", help="full SC regional (~16 MB)") + p.add_argument("--regular-only", action="store_true", help="one weight only (~0.9 MB)") + args = p.parse_args() + + if args.full: + build_full() + else: + build_subset(regular_only=args.regular_only) + print(f"wrote {MANIFEST}") + + +if __name__ == "__main__": + main()