Skip to content

Commit d518a17

Browse files
authored
[0158] 实现 Cyrillic 字母在 smart_font 中的路由 (#3480)
1 parent 8b354c1 commit d518a17

6 files changed

Lines changed: 158 additions & 1 deletion

File tree

TeXmacs/tests/tmu/0158.en.tmu

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<TMU|<tuple|1.1.0|2026.2.5>>
2+
3+
<style|<tuple|generic|number-europe|preview-ref|british>>
4+
5+
<\body>
6+
<tabular|<tformat|<twith|table-hmode|min>|<twith|table-width|1par>|<cwith|1|-1|1|-1|cell-hyphen|t>|<table|<row|<cell|№>|<cell|Uppercase>|<cell|Lowercase>|<cell|Name>>|<row|<cell|1>|<cell|А>|<cell|а>|<cell|а>>|<row|<cell|2>|<cell|Б>|<cell|б>|<cell|бэ>>|<row|<cell|3>|<cell|В>|<cell|в>|<cell|вэ>>|<row|<cell|4>|<cell|Г>|<cell|г>|<cell|гэ>>|<row|<cell|5>|<cell|Д>|<cell|д>|<cell|дэ>>|<row|<cell|6>|<cell|Е>|<cell|е>|<cell|е>>|<row|<cell|7>|<cell|Ё>|<cell|ё>|<cell|ё>>|<row|<cell|8>|<cell|Ж>|<cell|ж>|<cell|жэ>>|<row|<cell|9>|<cell|З>|<cell|з>|<cell|зэ>>|<row|<cell|10>|<cell|И>|<cell|и>|<cell|и>>|<row|<cell|11>|<cell|Й>|<cell|й>|<cell|и краткое>>|<row|<cell|12>|<cell|К>|<cell|к>|<cell|ка>>|<row|<cell|13>|<cell|Л>|<cell|л>|<cell|эль>>|<row|<cell|14>|<cell|М>|<cell|м>|<cell|эм>>|<row|<cell|15>|<cell|Н>|<cell|н>|<cell|эн>>|<row|<cell|16>|<cell|О>|<cell|о>|<cell|о>>|<row|<cell|17>|<cell|П>|<cell|п>|<cell|пэ>>|<row|<cell|18>|<cell|Р>|<cell|р>|<cell|эр>>|<row|<cell|19>|<cell|С>|<cell|с>|<cell|эс>>|<row|<cell|20>|<cell|Т>|<cell|т>|<cell|тэ>>|<row|<cell|21>|<cell|У>|<cell|у>|<cell|у>>|<row|<cell|22>|<cell|Ф>|<cell|ф>|<cell|эф>>|<row|<cell|23>|<cell|Х>|<cell|х>|<cell|ха>>|<row|<cell|24>|<cell|Ц>|<cell|ц>|<cell|цэ>>|<row|<cell|25>|<cell|Ч>|<cell|ч>|<cell|че>>|<row|<cell|26>|<cell|Ш>|<cell|ш>|<cell|ша>>|<row|<cell|27>|<cell|Щ>|<cell|щ>|<cell|ща>>|<row|<cell|28>|<cell|Ъ>|<cell|ъ>|<cell|твёрдый знак>>|<row|<cell|29>|<cell|Ы>|<cell|ы>|<cell|ы>>|<row|<cell|30>|<cell|Ь>|<cell|ь>|<cell|мягкий знак>>|<row|<cell|31>|<cell|Э>|<cell|э>|<cell|э>>|<row|<cell|32>|<cell|Ю>|<cell|ю>|<cell|ю>>|<row|<cell|33>|<cell|Я>|<cell|я>|<cell|я>>>>>
7+
</body>
8+
9+
<\initial>
10+
<\collection>
11+
<associate|page-screen-margin|false>
12+
<associate|prog-scripts|maxima>
13+
</collection>
14+
</initial>

TeXmacs/tests/tmu/0158.zh.tmu

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<TMU|<tuple|1.1.0|2026.2.5>>
2+
3+
<style|<tuple|generic|chinese|table-captions-above|number-europe|preview-ref>>
4+
5+
<\body>
6+
<tabular|<tformat|<twith|table-hmode|min>|<twith|table-width|1par>|<cwith|1|-1|1|-1|cell-hyphen|t>|<table|<row|<cell|№>|<cell|Uppercase>|<cell|Lowercase>|<cell|Name>>|<row|<cell|1>|<cell|А>|<cell|а>|<cell|а>>|<row|<cell|2>|<cell|Б>|<cell|б>|<cell|бэ>>|<row|<cell|3>|<cell|В>|<cell|в>|<cell|вэ>>|<row|<cell|4>|<cell|Г>|<cell|г>|<cell|гэ>>|<row|<cell|5>|<cell|Д>|<cell|д>|<cell|дэ>>|<row|<cell|6>|<cell|Е>|<cell|е>|<cell|е>>|<row|<cell|7>|<cell|Ё>|<cell|ё>|<cell|ё>>|<row|<cell|8>|<cell|Ж>|<cell|ж>|<cell|жэ>>|<row|<cell|9>|<cell|З>|<cell|з>|<cell|зэ>>|<row|<cell|10>|<cell|И>|<cell|и>|<cell|и>>|<row|<cell|11>|<cell|Й>|<cell|й>|<cell|и краткое>>|<row|<cell|12>|<cell|К>|<cell|к>|<cell|ка>>|<row|<cell|13>|<cell|Л>|<cell|л>|<cell|эль>>|<row|<cell|14>|<cell|М>|<cell|м>|<cell|эм>>|<row|<cell|15>|<cell|Н>|<cell|н>|<cell|эн>>|<row|<cell|16>|<cell|О>|<cell|о>|<cell|о>>|<row|<cell|17>|<cell|П>|<cell|п>|<cell|пэ>>|<row|<cell|18>|<cell|Р>|<cell|р>|<cell|эр>>|<row|<cell|19>|<cell|С>|<cell|с>|<cell|эс>>|<row|<cell|20>|<cell|Т>|<cell|т>|<cell|тэ>>|<row|<cell|21>|<cell|У>|<cell|у>|<cell|у>>|<row|<cell|22>|<cell|Ф>|<cell|ф>|<cell|эф>>|<row|<cell|23>|<cell|Х>|<cell|х>|<cell|ха>>|<row|<cell|24>|<cell|Ц>|<cell|ц>|<cell|цэ>>|<row|<cell|25>|<cell|Ч>|<cell|ч>|<cell|че>>|<row|<cell|26>|<cell|Ш>|<cell|ш>|<cell|ша>>|<row|<cell|27>|<cell|Щ>|<cell|щ>|<cell|ща>>|<row|<cell|28>|<cell|Ъ>|<cell|ъ>|<cell|твёрдый знак>>|<row|<cell|29>|<cell|Ы>|<cell|ы>|<cell|ы>>|<row|<cell|30>|<cell|Ь>|<cell|ь>|<cell|мягкий знак>>|<row|<cell|31>|<cell|Э>|<cell|э>|<cell|э>>|<row|<cell|32>|<cell|Ю>|<cell|ю>|<cell|ю>>|<row|<cell|33>|<cell|Я>|<cell|я>|<cell|я>>>>>
7+
</body>
8+
9+
<\initial>
10+
<\collection>
11+
<associate|page-screen-margin|false>
12+
<associate|prog-scripts|maxima>
13+
</collection>
14+
</initial>

devel/0158.md

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# [0158] Cyrillic 字母在 smart_font 中的路由
2+
3+
## 1 相关文档
4+
- [dddd.md](dddd.md) - 任务文档模板
5+
6+
## 2 任务相关的代码文件
7+
- `src/Graphics/Fonts/smart_font.hpp`
8+
- `src/Graphics/Fonts/smart_font.cpp`
9+
- `tests/Graphics/Fonts/smart_font_test.cpp`
10+
11+
## 3 如何测试
12+
13+
### 3.1 确定性测试(单元测试)
14+
```bash
15+
xmake b smart_font_test
16+
xmake r smart_font_test
17+
```
18+
19+
### 3.2 非确定性测试(文档验证)
20+
打开以下两个测试文档,验证所有俄语字母(大写和小写)能够正确渲染:
21+
- `TeXmacs/tests/tmu/0158.en.tmu` — 英文文档(`british` 语言)
22+
- `TeXmacs/tests/tmu/0158.zh.tmu` — 中文文档(`chinese` 语言)
23+
24+
```bash
25+
xmake b stem
26+
xmake r stem
27+
```
28+
在 Mogan 中分别打开上述两个文档,检查表格中的 33 个俄语字母是否正确显示。
29+
30+
## 4 如何提交
31+
32+
提交前执行以下最少步骤:
33+
34+
```bash
35+
xmake b smart_font_test
36+
xmake r smart_font_test
37+
```
38+
39+
## 5 What
40+
41+
针对 Cyrillic Unicode range(U+0400 到 U+04FF)做全局 fallback 处理,使所有文档中的 Cyrillic 字母都能路由到 `default_chinese_font_name()` 对应的字体。
42+
43+
1.`smart_font_rep::resolve(string c)` 中,当主字体不支持且字符属于 Cyrillic range 时,fallback 到 `cyrillic=<default_chinese_font_name()>`
44+
2. 移除了 `smart_font_bis``sys-chinese` 的特殊 `cyrillic=` 前缀处理(由全局 fallback 统一覆盖)
45+
3. `in_unicode_range` 保持导出并在测试中验证其对 Cyrillic range 的支持
46+
4. 添加单元测试验证:
47+
- `roman` 文档中的 Cyrillic 字符能正确 fallback
48+
- `sys-chinese` 文档中的 Cyrillic 字符也能正确路由
49+
50+
## 6 Why
51+
52+
Cyrillic 字母之前没有全局的路由机制,在某些非中文文档中无法正确渲染。通过在 `resolve` 中添加全局 fallback,所有文档中的 Cyrillic 字符都能统一路由到指定字体(目前使用 `default_chinese_font_name()`,后续可独立配置)。
53+
54+
## 7 How
55+
56+
修改 `smart_font_rep::resolve(string c)`,在主字体检查之后、emoji 处理之后添加 Cyrillic fallback 逻辑:
57+
58+
```cpp
59+
// Fallback Cyrillic characters to default Chinese font
60+
if (range == "cyrillic") {
61+
string chinese_name= default_chinese_font_name ();
62+
if (chinese_name != "roman") {
63+
for (int attempt= 1; attempt <= FONT_ATTEMPTS; attempt++) {
64+
int nr= resolve (c, "cyrillic=" * chinese_name, attempt);
65+
if (nr >= 0) return nr;
66+
}
67+
}
68+
}
69+
```
70+
71+
同时恢复 `sys-chinese` 的展开逻辑为:`cjk=<name>,roman`

src/Graphics/Fonts/smart_font.cpp

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -397,7 +397,7 @@ is_cjk_punct (string_u8 c) {
397397
return set->contains (c);
398398
}
399399

400-
static bool
400+
bool
401401
in_unicode_range (string c, string range) {
402402
string uc= strict_cork_to_utf8 (c);
403403
if (N (uc) == 0) return false;
@@ -1182,6 +1182,17 @@ smart_font_rep::resolve (string c) {
11821182
}
11831183
}
11841184

1185+
// Fallback Cyrillic characters to default Chinese font
1186+
if (range == "cyrillic") {
1187+
string chinese_name= default_chinese_font_name ();
1188+
if (chinese_name != "roman") {
1189+
for (int attempt= 1; attempt <= FONT_ATTEMPTS; attempt++) {
1190+
int nr= resolve (c, "cyrillic=" * chinese_name, attempt);
1191+
if (nr >= 0) return nr;
1192+
}
1193+
}
1194+
}
1195+
11851196
if (math_kind != 0) {
11861197
string upc= substitute_upright (c);
11871198
if (upc != "" && fn[SUBFONT_MAIN]->supports (upc)) {

src/Graphics/Fonts/smart_font.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,4 +159,6 @@ struct smart_font_rep : font_rep {
159159
SI get_wide_correction (string s, int mode);
160160
};
161161

162+
bool in_unicode_range (string c, string range);
163+
162164
#endif

tests/Graphics/Fonts/smart_font_test.cpp

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ private slots:
4040
void test_cursor_position_iii ();
4141
void test_performance ();
4242
void test_math_performance ();
43+
void test_in_unicode_range_cyrillic ();
44+
void test_roman_cyrillic_fallback ();
45+
void test_sys_chinese_cyrillic ();
4346
};
4447

4548
void
@@ -188,5 +191,47 @@ TestSmartFont::test_math_performance () {
188191
fn->get_extents (math_text, ex);
189192
}
190193

194+
void
195+
TestSmartFont::test_in_unicode_range_cyrillic () {
196+
// Cyrillic 字符不应该被当作 CJK
197+
QVERIFY (!in_unicode_range ("<#400>", "cjk"));
198+
QVERIFY (!in_unicode_range ("<#4FF>", "cjk"));
199+
// Cyrillic 字符应该属于 cyrillic range
200+
QVERIFY (in_unicode_range ("<#400>", "cyrillic"));
201+
QVERIFY (in_unicode_range ("<#4FF>", "cyrillic"));
202+
// Latin 字符不应该属于 cyrillic range
203+
QVERIFY (!in_unicode_range ("a", "cyrillic"));
204+
}
205+
206+
void
207+
TestSmartFont::test_roman_cyrillic_fallback () {
208+
// roman 文档中的 Cyrillic 字符应该 fallback 到 default_chinese_font_name
209+
font fn= smart_font ("roman", "rm", "medium", "right", 10, 600);
210+
smart_font_rep* fn_rep= (smart_font_rep*) fn.rep;
211+
string c = utf8_to_cork ("А");
212+
int nr = fn_rep->resolve (c);
213+
QVERIFY (nr >= 0);
214+
string chinese_name= default_chinese_font_name ();
215+
if (chinese_name != "roman") {
216+
// 确认没有 fallback 到 roman 的 ecrm 字体
217+
QVERIFY (!occurs ("ecrm", fn_rep->fn[nr]->res_name));
218+
}
219+
}
220+
221+
void
222+
TestSmartFont::test_sys_chinese_cyrillic () {
223+
// sys-chinese 文档中的 Cyrillic 字符应该正确路由
224+
font fn= smart_font ("sys-chinese", "rm", "medium", "right", 10, 600);
225+
smart_font_rep* fn_rep= (smart_font_rep*) fn.rep;
226+
string c = utf8_to_cork ("А");
227+
int nr = fn_rep->resolve (c);
228+
QVERIFY (nr >= 0);
229+
string chinese_name= default_chinese_font_name ();
230+
if (chinese_name != "roman") {
231+
// 确认没有 fallback 到 roman 的 ecrm 字体
232+
QVERIFY (!occurs ("ecrm", fn_rep->fn[nr]->res_name));
233+
}
234+
}
235+
191236
QTEST_MAIN (TestSmartFont)
192237
#include "smart_font_test.moc"

0 commit comments

Comments
 (0)