Skip to content

Commit 0fed6ff

Browse files
da-liiiclaude
andauthored
[0126] 优化 smart_font 字体加载性能 (#3361)
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 1680587 commit 0fed6ff

4 files changed

Lines changed: 132 additions & 34 deletions

File tree

devel/0126.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# [0126] 优化 smart_font 字体加载性能
2+
3+
## 1 相关文档
4+
- [dddd.md](dddd.md) - 任务文档模板
5+
6+
## 2 任务相关的代码文件
7+
- `src/Graphics/Fonts/smart_font.cpp`
8+
- `src/Graphics/Fonts/smart_font.hpp`
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+
```bash
21+
# 构建并运行字体相关测试,确保所有用例通过
22+
xmake b smart_font_test && xmake r smart_font_test
23+
```
24+
25+
## 4 如何提交
26+
27+
提交前执行以下最少步骤:
28+
29+
```bash
30+
xmake b smart_font_test
31+
xmake r smart_font_test
32+
```
33+
34+
## 5 What
35+
36+
通过缓存避免 smart_font 字体加载过程中的重复函数调用,优化性能。
37+
38+
1. 添加 `maybe_initialize_font` 辅助函数,避免对已初始化的子字体重复调用 `initialize_font`
39+
2. 在构造函数中缓存 `trimmed_tokenize(family, ",")` 结果(`family_tokens`),避免 `resolve(string c)``is_italic_prime` 中重复分割字符串
40+
3. 在构造函数中缓存 `logical_font` 结果(`given_font`),避免 `resolve(c, fam, attempt)` 中重复计算
41+
4. 缓存 `is_italic_prime` 的结果,避免对同一字体的斜体引号判断重复计算
42+
5. 新增 `test_performance``test_math_performance` 回归测试
43+
44+
## 6 Why
45+
46+
在字体加载过程中,通过添加日志发现很多函数会被重复调用。例如解析数学模式下 24 个希腊字母时,`initialize_font` 被调用了 21 次,虽然大部分调用的子字体已经初始化过。`trimmed_tokenize(family, ",")``logical_font` 也在每次 `resolve` 时被重复计算,造成不必要的性能开销。
47+
48+
## 7 How
49+
50+
- `maybe_initialize_font(int nr)`:在调用 `initialize_font` 前检查 `fn[nr]` 是否已初始化,只有未初始化时才执行实际的字体创建逻辑
51+
- `family_tokens`:在 `smart_font_rep` 构造函数中将 `trimmed_tokenize(family, ",")` 结果保存为成员变量,供 `resolve(string c)``is_italic_prime` 直接使用
52+
- `given_font`:在构造函数中将 `logical_font(family, variant, series, rshape)` 结果保存为成员变量,供 `resolve(c, fam, attempt)` 中的 `is_wanted` 调用直接使用
53+
- `is_italic_prime` 缓存:添加 `italic_prime_cached``italic_prime_result` 成员变量,首次计算后缓存结果

src/Graphics/Fonts/smart_font.cpp

Lines changed: 45 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -625,14 +625,17 @@ smart_font_rep::smart_font_rep (string name, font base_fn, font err_fn,
625625
fn[SUBFONT_MAIN] = adjust_subfont (base_fn);
626626
fn[SUBFONT_ERROR]= adjust_subfont (err_fn);
627627
this->copy_math_pars (base_fn);
628+
family_tokens = trimmed_tokenize (family, ",");
629+
given_font = logical_font (family, variant, series, rshape);
630+
italic_prime_cached= false;
628631
if (shape == "mathitalic" || shape == "mathupright" || shape == "mathshape") {
629632
if (is_math_family (mfam)) {
630633
rshape= "right";
631634
if (shape == "mathupright") this->copy_math_pars (base_fn);
632635
else {
633636
tree key= tuple ("math", mfam, variant, series, rshape);
634637
int nr = sm->add_font (key, REWRITE_MATH);
635-
initialize_font (nr);
638+
maybe_initialize_font (nr);
636639
this->copy_math_pars (fn[nr]);
637640
fn[SUBFONT_MAIN]= fn[nr];
638641
}
@@ -645,7 +648,7 @@ smart_font_rep::smart_font_rep (string name, font base_fn, font err_fn,
645648
if (math_kind == 2) this->copy_math_pars (base_fn);
646649
else {
647650
italic_nr= sm->add_font (tuple ("fast-italic"), REWRITE_NONE);
648-
initialize_font (italic_nr);
651+
maybe_initialize_font (italic_nr);
649652
this->copy_math_pars (fn[italic_nr]);
650653
}
651654
(void) sm->add_font (tuple ("special"), REWRITE_SPECIAL);
@@ -836,7 +839,7 @@ smart_font_rep::advance (string s, int& pos, string& r, int& nr) {
836839
}
837840

838841
if (count == 1 && nr != -1 && fn_index == nr) {
839-
if (N (fn) <= nr || is_nil (fn[nr])) initialize_font (nr);
842+
maybe_initialize_font (nr);
840843
if (!fn[nr]->supports (s (start, end))) break;
841844
pos= end;
842845
}
@@ -851,7 +854,7 @@ smart_font_rep::advance (string s, int& pos, string& r, int& nr) {
851854
}
852855
r= s (start, pos);
853856
if (nr < 0) return;
854-
if (N (fn) <= nr || is_nil (fn[nr])) initialize_font (nr);
857+
maybe_initialize_font (nr);
855858
if (sm->fn_rewr[nr] != REWRITE_NONE) r= rewrite (r, sm->fn_rewr[nr]);
856859
}
857860
if (DEBUG_VERBOSE) {
@@ -911,10 +914,9 @@ smart_font_rep::resolve (string c, string fam, int attempt) {
911914
}
912915
array<string> a= trimmed_tokenize (fam, "=");
913916
if (N (a) >= 2) {
914-
array<string> given= logical_font (family, variant, series, rshape);
915-
fam = a[1];
916-
array<string> b = tokenize (a[0], " ");
917-
bool ok = is_wanted (c, fam, b, given);
917+
fam = a[1];
918+
array<string> b = tokenize (a[0], " ");
919+
bool ok= is_wanted (c, fam, b, given_font);
918920
if (!ok) {
919921
return -1;
920922
}
@@ -930,7 +932,7 @@ smart_font_rep::resolve (string c, string fam, int attempt) {
930932
if (cfn->supports (c)) {
931933
tree key= tuple ("subfont", fam);
932934
int nr = sm->add_font (key, REWRITE_NONE);
933-
initialize_font (nr);
935+
maybe_initialize_font (nr);
934936
return sm->add_char (key, c);
935937
}
936938
}
@@ -957,41 +959,41 @@ smart_font_rep::resolve (string c, string fam, int attempt) {
957959
if (cfn->supports (c) || c == "<#200B>") {
958960
tree key= tuple (fam, variant, series, rshape, "1");
959961
int nr = sm->add_font (key, REWRITE_NONE);
960-
initialize_font (nr);
962+
maybe_initialize_font (nr);
961963
return sm->add_char (key, c);
962964
}
963965
}
964966

965967
if (fam == "roman" && range == "greek") {
966968
tree key= tuple ("greek", fam, variant, series, rshape);
967969
int nr = sm->add_font (key, REWRITE_NONE);
968-
initialize_font (nr);
970+
maybe_initialize_font (nr);
969971
return sm->add_char (key, c);
970972
}
971973
if (fam == "roman" && range == "latin") {
972974
tree key= tuple ("latin", fam, variant, series, rshape);
973975
int nr = sm->add_font (key, REWRITE_NONE);
974-
initialize_font (nr);
976+
maybe_initialize_font (nr);
975977
return sm->add_char (key, c);
976978
}
977979
if (is_math_family (fam)) {
978980
tree key= tuple ("math", fam, variant, series, rshape);
979981
int nr = sm->add_font (key, REWRITE_MATH);
980-
initialize_font (nr);
982+
maybe_initialize_font (nr);
981983
if (fn[nr]->supports (rewrite (c, REWRITE_MATH)))
982984
return sm->add_char (key, c);
983985
}
984986
if ((fam == "roman" || fam == "cyrillic") && N (c) > 1) {
985987
tree key= tuple ("cyrillic", fam, variant, series, rshape);
986988
int nr = sm->add_font (key, REWRITE_CYRILLIC);
987-
initialize_font (nr);
989+
maybe_initialize_font (nr);
988990
if (fn[nr]->supports (rewrite (c, REWRITE_CYRILLIC)))
989991
return sm->add_char (key, c);
990992
}
991993
if (c == "<#3000>") {
992994
tree key= tuple ("ignore");
993995
int nr = sm->add_font (key, REWRITE_IGNORE);
994-
initialize_font (nr);
996+
maybe_initialize_font (nr);
995997
return sm->add_char (key, c);
996998
}
997999
if (N (c) == 7 && starts (c, "<bbb-") && !occurs ("TeX Gyre", mfam)) {
@@ -1009,14 +1011,14 @@ smart_font_rep::resolve (string c, string fam, int attempt) {
10091011
vw = max (vw, 0.25 * lw);
10101012
tree key = tuple ("poor-bbb", as_string (hw), as_string (vw));
10111013
int nr = sm->add_font (key, REWRITE_POOR_BBB);
1012-
initialize_font (nr);
1014+
maybe_initialize_font (nr);
10131015
return sm->add_char (key, c);
10141016
}
10151017
}
10161018
if (starts (c, "<it-") && ends (c, ">")) {
10171019
tree key= tuple ("it");
10181020
int nr = sm->add_font (key, REWRITE_ITALIC);
1019-
initialize_font (nr);
1021+
maybe_initialize_font (nr);
10201022
return sm->add_char (key, c);
10211023
}
10221024
if (fam == mfam && !is_italic_font (mfam)) {
@@ -1025,7 +1027,7 @@ smart_font_rep::resolve (string c, string fam, int attempt) {
10251027
if (virtually_defined (c, emu_names[i])) {
10261028
tree key= tuple ("emulate", emu_names[i]);
10271029
int nr = sm->add_font (key, REWRITE_NONE);
1028-
initialize_font (nr);
1030+
maybe_initialize_font (nr);
10291031
if (fn[nr]->supports (c)) return sm->add_char (key, c);
10301032
}
10311033
}
@@ -1045,7 +1047,7 @@ smart_font_rep::resolve (string c, string fam, int attempt) {
10451047
if (cfn->supports (c) || c == "<#200B>") {
10461048
tree key= tuple (fam, v, series, rshape, as_string (a));
10471049
int nr = sm->add_font (key, REWRITE_NONE);
1048-
initialize_font (nr);
1050+
maybe_initialize_font (nr);
10491051
return sm->add_char (key, c);
10501052
}
10511053
}
@@ -1056,12 +1058,18 @@ smart_font_rep::resolve (string c, string fam, int attempt) {
10561058
bool
10571059
smart_font_rep::is_italic_prime (string c) {
10581060
if (c != "'" && c != "`") return false;
1059-
array<string> a= trimmed_tokenize (family, ",");
1060-
string s= "<#2B9>";
1061+
if (italic_prime_cached) return italic_prime_result;
1062+
string s= "<#2B9>";
10611063
if (c == "`") s= "<backprime>";
1062-
for (int i= 0; i < N (a); i++)
1063-
if (resolve (s, a[i], 1) >= 0) return false;
1064-
return true;
1064+
bool result= true;
1065+
for (int i= 0; i < N (family_tokens); i++)
1066+
if (resolve (s, family_tokens[i], 1) >= 0) {
1067+
result= false;
1068+
break;
1069+
}
1070+
italic_prime_cached= true;
1071+
italic_prime_result= result;
1072+
return result;
10651073
}
10661074

10671075
extern bool has_poor_rubber;
@@ -1079,7 +1087,7 @@ smart_font_rep::resolve_rubber (string c, string fam, int attempt) {
10791087
if (goal == "." || goal == "<nobracket>") {
10801088
tree key= tuple ("ignore");
10811089
int nr = sm->add_font (key, REWRITE_IGNORE);
1082-
initialize_font (nr);
1090+
maybe_initialize_font (nr);
10831091
return sm->add_char (key, c);
10841092
}
10851093
if (has_poor_rubber) {
@@ -1101,7 +1109,7 @@ smart_font_rep::resolve_rubber (string c, string fam, int attempt) {
11011109
if (bnr >= 0 && bnr < N (fn) && !is_nil (fn[bnr])) {
11021110
tree key= tuple ("rubber", as_string (bnr));
11031111
int nr = sm->add_font (key, REWRITE_NONE);
1104-
initialize_font (nr);
1112+
maybe_initialize_font (nr);
11051113
// cout << fn[nr]->res_name << " supports " << c << "? "
11061114
// << fn[nr]->supports (c) << LF;
11071115
if (fn[nr]->supports (c)) return sm->add_char (key, c);
@@ -1138,7 +1146,7 @@ smart_font_rep::resolve (string c) {
11381146
debug_fonts << "Main subfont of " << cork_to_utf8 (c) << " is "
11391147
<< fn[SUBFONT_MAIN]->res_name << LF;
11401148
}
1141-
array<string> a= trimmed_tokenize (family, ",");
1149+
array<string> a= family_tokens;
11421150

11431151
// Special handling for emoji characters - bypass font-family restrictions
11441152
string range= get_unicode_range (c);
@@ -1167,7 +1175,7 @@ smart_font_rep::resolve (string c) {
11671175
if (!is_nil (cfn) && cfn->supports (c)) {
11681176
tree key= tuple ("emoji-font", parts[1]);
11691177
int nr = sm->add_font (key, REWRITE_NONE);
1170-
initialize_font (nr);
1178+
maybe_initialize_font (nr);
11711179
return sm->add_char (key, c);
11721180
}
11731181
}
@@ -1179,22 +1187,22 @@ smart_font_rep::resolve (string c) {
11791187
if (upc != "" && fn[SUBFONT_MAIN]->supports (upc)) {
11801188
tree key= tuple ("up");
11811189
int nr = sm->add_font (key, REWRITE_UPRIGHT);
1182-
initialize_font (nr);
1190+
maybe_initialize_font (nr);
11831191
return sm->add_char (key, c);
11841192
}
11851193
string ugc= substitute_upright_greek (c);
11861194
if (ugc != "" && fn[SUBFONT_MAIN]->supports (ugc)) {
11871195
tree key= tuple ("upright-greek");
11881196
int nr = sm->add_font (key, REWRITE_UPRIGHT_GREEK);
1189-
initialize_font (nr);
1197+
maybe_initialize_font (nr);
11901198
return sm->add_char (key, c);
11911199
}
11921200
if (is_greek (c) && use_italic_greek (a) && shape != "mathupright") {
11931201
string gc= substitute_italic_greek (c);
11941202
if (gc != "" && fn[SUBFONT_MAIN]->supports (gc)) {
11951203
tree key= tuple ("italic-greek");
11961204
int nr = sm->add_font (key, REWRITE_ITALIC_GREEK);
1197-
initialize_font (nr);
1205+
maybe_initialize_font (nr);
11981206
return sm->add_char (key, c);
11991207
}
12001208
// cout << "Found " << c << " in greek\n";
@@ -1371,7 +1379,7 @@ smart_font_rep::initialize_font (int nr) {
13711379
fn[nr] = poor_bbb_font (sfn, pw, ph, 1.5 * pw);
13721380
}
13731381
else if (a[0] == "rubber" && N (a) == 2 && is_int (a[1])) {
1374-
initialize_font (as_int (a[1]));
1382+
maybe_initialize_font (as_int (a[1]));
13751383
fn[nr]= adjust_subfont (rubber_font (fn[as_int (a[1])]));
13761384
// fn[nr]= adjust_subfont (rubber_unicode_font (fn[as_int (a[1])]));
13771385
}
@@ -1388,6 +1396,11 @@ smart_font_rep::initialize_font (int nr) {
13881396
}
13891397
}
13901398

1399+
void
1400+
smart_font_rep::maybe_initialize_font (int nr) {
1401+
if (N (fn) <= nr || is_nil (fn[nr])) initialize_font (nr);
1402+
}
1403+
13911404
static int
13921405
get_ex (string family, string variant, string series, string shape,
13931406
int attempt) {

src/Graphics/Fonts/smart_font.hpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,12 @@ struct smart_font_rep : font_rep {
9999
int math_kind;
100100
int italic_nr;
101101

102-
array<font> fn;
103-
smart_map sm;
102+
array<font> fn;
103+
smart_map sm;
104+
array<string> family_tokens;
105+
array<string> given_font;
106+
bool italic_prime_cached;
107+
bool italic_prime_result;
104108

105109
smart_font_rep (string name, font base_fn, font err_fn, string family,
106110
string variant, string series, string shape, double sz,
@@ -117,6 +121,7 @@ struct smart_font_rep : font_rep {
117121
int resolve_rubber (string c, string fam, int attempt);
118122
int resolve (string c);
119123
void initialize_font (int nr);
124+
void maybe_initialize_font (int nr);
120125
int adjusted_dpi (string fam, string var, string ser, string sh, int att);
121126

122127
font make_rubber_font (font base) override;

tests/Graphics/Fonts/smart_font_test.cpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ private slots:
3838
void test_get_right_slope ();
3939
void test_latin_modern_math_italic_greek ();
4040
void test_cursor_position_iii ();
41+
void test_performance ();
42+
void test_math_performance ();
4143
};
4244

4345
void
@@ -161,5 +163,30 @@ TestSmartFont::test_cursor_position_iii () {
161163
STACK_DELETE_ARRAY (xpos);
162164
}
163165

166+
void
167+
TestSmartFont::test_performance () {
168+
font fn= smart_font ("sys-chinese", "rm", "medium", "right", 10, 600);
169+
170+
// Trigger a lot of character resolutions with repeated characters
171+
string long_text= "The quick brown fox jumps over the lazy dog. "
172+
"The quick brown fox jumps over the lazy dog. "
173+
"The quick brown fox jumps over the lazy dog.";
174+
metric ex;
175+
fn->get_extents (long_text, ex);
176+
}
177+
178+
void
179+
TestSmartFont::test_math_performance () {
180+
font fn=
181+
smart_font ("Latin Modern Math", "rm", "medium", "mathitalic", 10, 600);
182+
183+
// Trigger math character resolutions
184+
string math_text= "<alpha><beta><gamma><delta><epsilon><zeta><eta><theta>"
185+
"<iota><kappa><lambda><mu><nu><xi><omicron><pi><rho><sigma>"
186+
"<tau><upsilon><phi><chi><psi><omega>";
187+
metric ex;
188+
fn->get_extents (math_text, ex);
189+
}
190+
164191
QTEST_MAIN (TestSmartFont)
165192
#include "smart_font_test.moc"

0 commit comments

Comments
 (0)