From a7405ea8ed4eccc3210657f7d8a90dce3743a976 Mon Sep 17 00:00:00 2001 From: Da Shen Date: Mon, 25 May 2026 16:22:20 +0800 Subject: [PATCH 1/6] =?UTF-8?q?[0158]=20=E6=B7=BB=E5=8A=A0=20Cyrillic=20?= =?UTF-8?q?=E5=AD=97=E4=BD=93=E8=B7=AF=E7=94=B1=E4=BB=BB=E5=8A=A1=E6=96=87?= =?UTF-8?q?=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.7 --- devel/0158.md | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 devel/0158.md diff --git a/devel/0158.md b/devel/0158.md new file mode 100644 index 0000000000..8bc8a398ad --- /dev/null +++ b/devel/0158.md @@ -0,0 +1,59 @@ +# [0158] Cyrillic 字母在 smart_font 中的路由 + +## 1 相关文档 +- [dddd.md](dddd.md) - 任务文档模板 + +## 2 任务相关的代码文件 +- `src/Graphics/Fonts/smart_font.hpp` +- `src/Graphics/Fonts/smart_font.cpp` +- `tests/Graphics/Fonts/smart_font_test.cpp` + +## 3 如何测试 + +### 3.1 确定性测试(单元测试) +```bash +xmake b smart_font_test +xmake r smart_font_test +``` + +### 3.2 非确定性测试(文档验证) +打开 `TeXmacs/tests/tmu/0158.tmu` 文档,验证所有俄语字母(大写和小写)能够正确渲染: +```bash +xmake b stem +xmake r stem +``` +在 Mogan 中打开 `TeXmacs/tests/tmu/0158.tmu`,检查表格中的 33 个俄语字母是否正确显示。 + +## 4 如何提交 + +提交前执行以下最少步骤: + +```bash +xmake b smart_font_test +xmake r smart_font_test +``` + +## 5 What + +针对 Cyrillic Unicode range(U+0400 到 U+04FF)做特殊处理,使 `sys-chinese` 字体能够路由 Cyrillic 字母到对应字体。 + +1. 在 `smart_font_bis` 中,将 `sys-chinese` 展开后的 family 字符串添加 `cyrillic=` 前缀 +2. `in_unicode_range` 已通过 lolly 的 `unicode_get_range` 原生支持 `cyrillic` range,无需额外修改 +3. 添加单元测试验证 Cyrillic 字符不被当作 CJK,且 `sys-chinese` 展开后包含 `cyrillic=` 前缀 + +## 6 Why + +Cyrillic 字母之前没有单独的路由机制,在某些场景下无法正确渲染。通过为 `sys-chinese` 添加 `cyrillic=` 前缀,可以将 Cyrillic 字母路由到指定字体(目前与 CJK 使用同一字体,后续可独立配置)。 + +## 7 How + +`sys-chinese` 展开逻辑从: +``` +cjk=,roman +``` +修改为: +``` +cjk=,cyrillic=,roman +``` + +这样 `smart_font_rep::resolve` 在处理 Cyrillic 字符时,会匹配 `cyrillic=` 这条 family token,从而调用 `closest_font` 查找对应字体。`in_unicode_range` 中 `range == got` 的分支已天然支持 `cyrillic` range。 From 69f700bd2c58f79f303bb824dd7d47bc2eaff466 Mon Sep 17 00:00:00 2001 From: Da Shen Date: Mon, 25 May 2026 16:22:27 +0800 Subject: [PATCH 2/6] =?UTF-8?q?[0158]=20=E5=AE=9E=E7=8E=B0=20Cyrillic=20?= =?UTF-8?q?=E5=AD=97=E6=AF=8D=E5=9C=A8=20smart=5Ffont=20=E4=B8=AD=E7=9A=84?= =?UTF-8?q?=E8=B7=AF=E7=94=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将 sys-chinese 展开为 cjk=,cyrillic=,roman - 暴露 in_unicode_range 并添加单元测试 - 添加俄语字母渲染测试文档 0158.tmu Co-Authored-By: Claude Opus 4.7 --- TeXmacs/tests/tmu/0158.tmu | 14 ++++++++++++ src/Graphics/Fonts/smart_font.cpp | 4 ++-- src/Graphics/Fonts/smart_font.hpp | 2 ++ tests/Graphics/Fonts/smart_font_test.cpp | 29 ++++++++++++++++++++++++ 4 files changed, 47 insertions(+), 2 deletions(-) create mode 100644 TeXmacs/tests/tmu/0158.tmu diff --git a/TeXmacs/tests/tmu/0158.tmu b/TeXmacs/tests/tmu/0158.tmu new file mode 100644 index 0000000000..576c262435 --- /dev/null +++ b/TeXmacs/tests/tmu/0158.tmu @@ -0,0 +1,14 @@ +> + +> + +<\body> + ||||||>||||>||||>||||>||||>||||>||||>||||>||||>||||>||||>||||>||||>||||>||||>||||>||||>||||>||||>||||>||||>||||>||||>||||>||||>||||>||||>||||>||||>||||>||||>||||>||||>||||>>>> + + +<\initial> + <\collection> + + + + diff --git a/src/Graphics/Fonts/smart_font.cpp b/src/Graphics/Fonts/smart_font.cpp index 4d222cc41a..3ea4296c38 100644 --- a/src/Graphics/Fonts/smart_font.cpp +++ b/src/Graphics/Fonts/smart_font.cpp @@ -397,7 +397,7 @@ is_cjk_punct (string_u8 c) { return set->contains (c); } -static bool +bool in_unicode_range (string c, string range) { string uc= strict_cork_to_utf8 (c); if (N (uc) == 0) return false; @@ -1784,7 +1784,7 @@ smart_font_bis (string family, string variant, string series, string shape, if (starts (family, "sys-")) { if (family == "sys-chinese") { string name= default_chinese_font_name (); - family = "cjk=" * name * ",roman"; + family = "cjk=" * name * ",cyrillic=" * name * ",roman"; } if (family == "sys-japanese") { string name= default_japanese_font_name (); diff --git a/src/Graphics/Fonts/smart_font.hpp b/src/Graphics/Fonts/smart_font.hpp index 9e44ede7b8..d0b9a46481 100644 --- a/src/Graphics/Fonts/smart_font.hpp +++ b/src/Graphics/Fonts/smart_font.hpp @@ -159,4 +159,6 @@ struct smart_font_rep : font_rep { SI get_wide_correction (string s, int mode); }; +bool in_unicode_range (string c, string range); + #endif diff --git a/tests/Graphics/Fonts/smart_font_test.cpp b/tests/Graphics/Fonts/smart_font_test.cpp index 7024836304..845c6954f6 100644 --- a/tests/Graphics/Fonts/smart_font_test.cpp +++ b/tests/Graphics/Fonts/smart_font_test.cpp @@ -40,6 +40,8 @@ private slots: void test_cursor_position_iii (); void test_performance (); void test_math_performance (); + void test_in_unicode_range_cyrillic (); + void test_sys_chinese_cyrillic (); }; void @@ -188,5 +190,32 @@ TestSmartFont::test_math_performance () { fn->get_extents (math_text, ex); } +void +TestSmartFont::test_in_unicode_range_cyrillic () { + // Cyrillic 字符不应该被当作 CJK + QVERIFY (!in_unicode_range ("<#400>", "cjk")); + QVERIFY (!in_unicode_range ("<#4FF>", "cjk")); + // Cyrillic 字符应该属于 cyrillic range + QVERIFY (in_unicode_range ("<#400>", "cyrillic")); + QVERIFY (in_unicode_range ("<#4FF>", "cyrillic")); + // Latin 字符不应该属于 cyrillic range + QVERIFY (!in_unicode_range ("a", "cyrillic")); +} + +void +TestSmartFont::test_sys_chinese_cyrillic () { + // sys-chinese 展开后应该包含 cyrillic= 前缀 + font fn= smart_font ("sys-chinese", "rm", "medium", "right", 10, 600); + smart_font_rep* fn_rep = (smart_font_rep*) fn.rep; + bool has_cyrillic= false; + for (int i= 0; i < N (fn_rep->family_tokens); i++) { + if (starts (fn_rep->family_tokens[i], "cyrillic=")) { + has_cyrillic= true; + break; + } + } + QVERIFY (has_cyrillic); +} + QTEST_MAIN (TestSmartFont) #include "smart_font_test.moc" From 35edaa1ffe23cd1acfac831f800f35357629169e Mon Sep 17 00:00:00 2001 From: Da Shen Date: Mon, 25 May 2026 17:27:41 +0800 Subject: [PATCH 3/6] wip --- devel/0158.md | 35 +++++++++++++++--------- src/Graphics/Fonts/smart_font.cpp | 13 ++++++++- tests/Graphics/Fonts/smart_font_test.cpp | 34 +++++++++++++++++------ 3 files changed, 59 insertions(+), 23 deletions(-) diff --git a/devel/0158.md b/devel/0158.md index 8bc8a398ad..4d4801fcd5 100644 --- a/devel/0158.md +++ b/devel/0158.md @@ -35,25 +35,34 @@ xmake r smart_font_test ## 5 What -针对 Cyrillic Unicode range(U+0400 到 U+04FF)做特殊处理,使 `sys-chinese` 字体能够路由 Cyrillic 字母到对应字体。 +针对 Cyrillic Unicode range(U+0400 到 U+04FF)做全局 fallback 处理,使所有文档中的 Cyrillic 字母都能路由到 `default_chinese_font_name()` 对应的字体。 -1. 在 `smart_font_bis` 中,将 `sys-chinese` 展开后的 family 字符串添加 `cyrillic=` 前缀 -2. `in_unicode_range` 已通过 lolly 的 `unicode_get_range` 原生支持 `cyrillic` range,无需额外修改 -3. 添加单元测试验证 Cyrillic 字符不被当作 CJK,且 `sys-chinese` 展开后包含 `cyrillic=` 前缀 +1. 在 `smart_font_rep::resolve(string c)` 中,当主字体不支持且字符属于 Cyrillic range 时,fallback 到 `cyrillic=` +2. 移除了 `smart_font_bis` 中 `sys-chinese` 的特殊 `cyrillic=` 前缀处理(由全局 fallback 统一覆盖) +3. `in_unicode_range` 保持导出并在测试中验证其对 Cyrillic range 的支持 +4. 添加单元测试验证: + - `roman` 文档中的 Cyrillic 字符能正确 fallback + - `sys-chinese` 文档中的 Cyrillic 字符也能正确路由 ## 6 Why -Cyrillic 字母之前没有单独的路由机制,在某些场景下无法正确渲染。通过为 `sys-chinese` 添加 `cyrillic=` 前缀,可以将 Cyrillic 字母路由到指定字体(目前与 CJK 使用同一字体,后续可独立配置)。 +Cyrillic 字母之前没有全局的路由机制,在某些非中文文档中无法正确渲染。通过在 `resolve` 中添加全局 fallback,所有文档中的 Cyrillic 字符都能统一路由到指定字体(目前使用 `default_chinese_font_name()`,后续可独立配置)。 ## 7 How -`sys-chinese` 展开逻辑从: -``` -cjk=,roman -``` -修改为: -``` -cjk=,cyrillic=,roman +修改 `smart_font_rep::resolve(string c)`,在主字体检查之后、emoji 处理之后添加 Cyrillic fallback 逻辑: + +```cpp +// Fallback Cyrillic characters to default Chinese font +if (range == "cyrillic") { + string chinese_name= default_chinese_font_name (); + if (chinese_name != "roman") { + for (int attempt= 1; attempt <= FONT_ATTEMPTS; attempt++) { + int nr= resolve (c, "cyrillic=" * chinese_name, attempt); + if (nr >= 0) return nr; + } + } +} ``` -这样 `smart_font_rep::resolve` 在处理 Cyrillic 字符时,会匹配 `cyrillic=` 这条 family token,从而调用 `closest_font` 查找对应字体。`in_unicode_range` 中 `range == got` 的分支已天然支持 `cyrillic` range。 +同时恢复 `sys-chinese` 的展开逻辑为:`cjk=,roman` diff --git a/src/Graphics/Fonts/smart_font.cpp b/src/Graphics/Fonts/smart_font.cpp index 3ea4296c38..116a2a4721 100644 --- a/src/Graphics/Fonts/smart_font.cpp +++ b/src/Graphics/Fonts/smart_font.cpp @@ -1182,6 +1182,17 @@ smart_font_rep::resolve (string c) { } } + // Fallback Cyrillic characters to default Chinese font + if (range == "cyrillic") { + string chinese_name= default_chinese_font_name (); + if (chinese_name != "roman") { + for (int attempt= 1; attempt <= FONT_ATTEMPTS; attempt++) { + int nr= resolve (c, "cyrillic=" * chinese_name, attempt); + if (nr >= 0) return nr; + } + } + } + if (math_kind != 0) { string upc= substitute_upright (c); if (upc != "" && fn[SUBFONT_MAIN]->supports (upc)) { @@ -1784,7 +1795,7 @@ smart_font_bis (string family, string variant, string series, string shape, if (starts (family, "sys-")) { if (family == "sys-chinese") { string name= default_chinese_font_name (); - family = "cjk=" * name * ",cyrillic=" * name * ",roman"; + family = "cjk=" * name * ",roman"; } if (family == "sys-japanese") { string name= default_japanese_font_name (); diff --git a/tests/Graphics/Fonts/smart_font_test.cpp b/tests/Graphics/Fonts/smart_font_test.cpp index 845c6954f6..81c73c9934 100644 --- a/tests/Graphics/Fonts/smart_font_test.cpp +++ b/tests/Graphics/Fonts/smart_font_test.cpp @@ -41,6 +41,7 @@ private slots: void test_performance (); void test_math_performance (); void test_in_unicode_range_cyrillic (); + void test_roman_cyrillic_fallback (); void test_sys_chinese_cyrillic (); }; @@ -202,19 +203,34 @@ TestSmartFont::test_in_unicode_range_cyrillic () { QVERIFY (!in_unicode_range ("a", "cyrillic")); } +void +TestSmartFont::test_roman_cyrillic_fallback () { + // roman 文档中的 Cyrillic 字符应该 fallback 到 default_chinese_font_name + font fn= smart_font ("roman", "rm", "medium", "right", 10, 600); + smart_font_rep* fn_rep= (smart_font_rep*) fn.rep; + string c = utf8_to_cork ("А"); + int nr = fn_rep->resolve (c); + QVERIFY (nr >= 0); + string chinese_name= default_chinese_font_name (); + if (chinese_name != "roman") { + // 确认没有 fallback 到 roman 的 ecrm 字体 + QVERIFY (!occurs ("ecrm", fn_rep->fn[nr]->res_name)); + } +} + void TestSmartFont::test_sys_chinese_cyrillic () { - // sys-chinese 展开后应该包含 cyrillic= 前缀 + // sys-chinese 文档中的 Cyrillic 字符应该正确路由 font fn= smart_font ("sys-chinese", "rm", "medium", "right", 10, 600); - smart_font_rep* fn_rep = (smart_font_rep*) fn.rep; - bool has_cyrillic= false; - for (int i= 0; i < N (fn_rep->family_tokens); i++) { - if (starts (fn_rep->family_tokens[i], "cyrillic=")) { - has_cyrillic= true; - break; - } + smart_font_rep* fn_rep= (smart_font_rep*) fn.rep; + string c = utf8_to_cork ("А"); + int nr = fn_rep->resolve (c); + QVERIFY (nr >= 0); + string chinese_name= default_chinese_font_name (); + if (chinese_name != "roman") { + // 确认没有 fallback 到 roman 的 ecrm 字体 + QVERIFY (!occurs ("ecrm", fn_rep->fn[nr]->res_name)); } - QVERIFY (has_cyrillic); } QTEST_MAIN (TestSmartFont) From 73e42a979dad95604899237d04a54a2541d71f4d Mon Sep 17 00:00:00 2001 From: Da Shen Date: Mon, 25 May 2026 17:31:09 +0800 Subject: [PATCH 4/6] wip --- TeXmacs/tests/tmu/0158.en.tmu | 14 ++++++++++++++ TeXmacs/tests/tmu/{0158.tmu => 0158.zh.tmu} | 0 2 files changed, 14 insertions(+) create mode 100644 TeXmacs/tests/tmu/0158.en.tmu rename TeXmacs/tests/tmu/{0158.tmu => 0158.zh.tmu} (100%) diff --git a/TeXmacs/tests/tmu/0158.en.tmu b/TeXmacs/tests/tmu/0158.en.tmu new file mode 100644 index 0000000000..f94228c3e4 --- /dev/null +++ b/TeXmacs/tests/tmu/0158.en.tmu @@ -0,0 +1,14 @@ +> + +> + +<\body> + ||||||>||||>||||>||||>||||>||||>||||>||||>||||>||||>||||>||||>||||>||||>||||>||||>||||>||||>||||>||||>||||>||||>||||>||||>||||>||||>||||>||||>||||>||||>||||>||||>||||>||||>>>> + + +<\initial> + <\collection> + + + + diff --git a/TeXmacs/tests/tmu/0158.tmu b/TeXmacs/tests/tmu/0158.zh.tmu similarity index 100% rename from TeXmacs/tests/tmu/0158.tmu rename to TeXmacs/tests/tmu/0158.zh.tmu From f0f2b055c9a3b2e282acf82f4a1dcfd0127c1505 Mon Sep 17 00:00:00 2001 From: Da Shen Date: Mon, 25 May 2026 17:33:57 +0800 Subject: [PATCH 5/6] wip --- tests/Graphics/Fonts/smart_font_test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Graphics/Fonts/smart_font_test.cpp b/tests/Graphics/Fonts/smart_font_test.cpp index 81c73c9934..6c7a1c882b 100644 --- a/tests/Graphics/Fonts/smart_font_test.cpp +++ b/tests/Graphics/Fonts/smart_font_test.cpp @@ -206,7 +206,7 @@ TestSmartFont::test_in_unicode_range_cyrillic () { void TestSmartFont::test_roman_cyrillic_fallback () { // roman 文档中的 Cyrillic 字符应该 fallback 到 default_chinese_font_name - font fn= smart_font ("roman", "rm", "medium", "right", 10, 600); + font fn= smart_font ("roman", "rm", "medium", "right", 10, 600); smart_font_rep* fn_rep= (smart_font_rep*) fn.rep; string c = utf8_to_cork ("А"); int nr = fn_rep->resolve (c); From 7ae4a881e6c980e3ae88e77f876abe08b9defca4 Mon Sep 17 00:00:00 2001 From: Da Shen Date: Mon, 25 May 2026 17:48:34 +0800 Subject: [PATCH 6/6] =?UTF-8?q?[0158]=20=E6=9B=B4=E6=96=B0=E4=BB=BB?= =?UTF-8?q?=E5=8A=A1=E6=96=87=E6=A1=A3=EF=BC=8C=E8=A1=A5=E5=85=85=E4=B8=AD?= =?UTF-8?q?=E8=8B=B1=E6=96=87=E6=B5=8B=E8=AF=95=E6=96=87=E6=A1=A3=E8=AF=B4?= =?UTF-8?q?=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.7 --- devel/0158.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/devel/0158.md b/devel/0158.md index 4d4801fcd5..3287b1fcb8 100644 --- a/devel/0158.md +++ b/devel/0158.md @@ -17,12 +17,15 @@ xmake r smart_font_test ``` ### 3.2 非确定性测试(文档验证) -打开 `TeXmacs/tests/tmu/0158.tmu` 文档,验证所有俄语字母(大写和小写)能够正确渲染: +打开以下两个测试文档,验证所有俄语字母(大写和小写)能够正确渲染: +- `TeXmacs/tests/tmu/0158.en.tmu` — 英文文档(`british` 语言) +- `TeXmacs/tests/tmu/0158.zh.tmu` — 中文文档(`chinese` 语言) + ```bash xmake b stem xmake r stem ``` -在 Mogan 中打开 `TeXmacs/tests/tmu/0158.tmu`,检查表格中的 33 个俄语字母是否正确显示。 +在 Mogan 中分别打开上述两个文档,检查表格中的 33 个俄语字母是否正确显示。 ## 4 如何提交