Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 25 additions & 1 deletion src/Graphics/Fonts/smart_font.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1151,6 +1151,16 @@ use_italic_greek (array<string> a) {
return count <= 1;
}

// 单个 Unicode 字符的码点;多字符串、空串或 TeXmacs 专用标记(<sqrt> 等)返回 -1
static int
unicode_code_of (string c) {
string uc= strict_cork_to_utf8 (c);
if (N (uc) == 0) return -1;
int pos = 0;
int code= decode_from_utf8 (uc, pos);
return (pos == N (uc)) ? code : -1;
}

int
smart_font_rep::resolve (string c) {
if (DEBUG_VERBOSE) {
Expand All @@ -1163,6 +1173,11 @@ smart_font_rep::resolve (string c) {

// Special handling for emoji characters - bypass font-family restrictions
string range= get_unicode_range (c);
// U+20E3 COMBINING ENCLOSING KEYCAP 与 0-9、#、* 组成键帽序列(如 U+0030 U+20E3)。
// lolly 把 U+2000-U+23FF 整体归为 mathsymbols,键帽因此走数学字体链;默认
// 数学字体没有该字形,最终渲染为 <#20E3> 占位符(#4539)。它实际是 emoji
// 修饰符,应与 emoji 共用回退链。
bool emoji_chain= (range == "emoji") || (unicode_code_of (c) == 0x20E3);
// 如果设置了字体,就优先使用当前设置的字体
if (fn[SUBFONT_MAIN]->supports (c)) {
bool prefer_italic_greek= false;
Expand All @@ -1178,7 +1193,7 @@ smart_font_rep::resolve (string c) {
}
}

if (range == "emoji") {
if (emoji_chain) {
for (int i= 0; i < N (a); i++) {
array<string> parts= trimmed_tokenize (a[i], "=");
if (N (parts) >= 2 && parts[0] == "emoji") {
Expand Down Expand Up @@ -1220,6 +1235,15 @@ smart_font_rep::resolve (string c) {
maybe_initialize_font (nr);
return sm->add_char (key, c);
}
// 最后兜底自带的 Asana Math:其 cmap 含 U+20E3 等组合用符号
font asana_fn=
closest_font ("Asana Math", "rm", "medium", "right", sz, dpi, 1);
if (!is_nil (asana_fn) && asana_fn->supports (c)) {
tree key= tuple ("emoji-font", "Asana Math");
int nr = sm->add_font (key, REWRITE_NONE);
maybe_initialize_font (nr);
return sm->add_char (key, c);
}
}

// Fallback Cyrillic characters to default Chinese font
Expand Down
31 changes: 31 additions & 0 deletions tests/Graphics/Fonts/smart_font_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ private slots:
void test_noto_sans_symbols2_font_support ();
void test_misc_symbols_fallback ();
void test_misc_symbols_fallback_to_symbols2 ();
void test_keycap_combining_font_support ();
void test_keycap_combining_fallback ();
};

void
Expand Down Expand Up @@ -326,5 +328,34 @@ TestSmartFont::test_misc_symbols_fallback_to_symbols2 () {
QVERIFY (occurs ("NotoSansSymbols2", fn_rep->fn[nr]->res_name));
}

void
TestSmartFont::test_keycap_combining_font_support () {
// U+20E3 COMBINING ENCLOSING KEYCAP 必须至少被一款自带回退字体支持,
// 否则 emoji 回退链无处可去(4539 的前提:Asana Math cmap 含 U+20E3)
font asana_fn=
closest_font ("Asana Math", "rm", "medium", "right", 10, 600, 1);
font noto_fn=
closest_font ("Noto Sans Symbols", "rm", "medium", "right", 10, 600, 1);
font noto2_fn=
closest_font ("Noto Sans Symbols2", "rm", "medium", "right", 10, 600, 1);
bool supported= (!is_nil (asana_fn) && asana_fn->supports ("<#20E3>")) ||
(!is_nil (noto_fn) && noto_fn->supports ("<#20E3>")) ||
(!is_nil (noto2_fn) && noto2_fn->supports ("<#20E3>"));
QVERIFY (supported);
}

void
TestSmartFont::test_keycap_combining_fallback () {
// U+20E3 落在 lolly 的 mathsymbols 区间(U+2000-U+23FF),此前走数学字体链,
// 默认数学字体无该字形,渲染为 <#20E3> 占位符(4539)。
// 现应与 emoji 共用回退链,路由到某款符号字体而非 error 字体。
font fn= smart_font ("sys-chinese", "rm", "medium", "right", 10, 600);
smart_font_rep* fn_rep= (smart_font_rep*) fn.rep;
int nr = fn_rep->resolve ("<#20E3>");
QVERIFY (nr >= 0);
QVERIFY (!occurs ("error", fn_rep->fn[nr]->res_name));
QVERIFY (fn_rep->fn[nr]->supports ("<#20E3>"));
}

QTEST_MAIN (TestSmartFont)
#include "smart_font_test.moc"