Skip to content
Merged
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
53 changes: 53 additions & 0 deletions devel/0126.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# [0126] 优化 smart_font 字体加载性能

## 1 相关文档
- [dddd.md](dddd.md) - 任务文档模板

## 2 任务相关的代码文件
- `src/Graphics/Fonts/smart_font.cpp`
- `src/Graphics/Fonts/smart_font.hpp`
- `tests/Graphics/Fonts/smart_font_test.cpp`

## 3 如何测试

### 3.1 确定性测试(单元测试)
```bash
xmake b smart_font_test
xmake r smart_font_test
```

### 3.2 非确定性测试(文档验证)
```bash
# 构建并运行字体相关测试,确保所有用例通过
xmake b smart_font_test && xmake r smart_font_test
```

## 4 如何提交

提交前执行以下最少步骤:

```bash
xmake b smart_font_test
xmake r smart_font_test
```

## 5 What

通过缓存避免 smart_font 字体加载过程中的重复函数调用,优化性能。

1. 添加 `maybe_initialize_font` 辅助函数,避免对已初始化的子字体重复调用 `initialize_font`
2. 在构造函数中缓存 `trimmed_tokenize(family, ",")` 结果(`family_tokens`),避免 `resolve(string c)` 和 `is_italic_prime` 中重复分割字符串
3. 在构造函数中缓存 `logical_font` 结果(`given_font`),避免 `resolve(c, fam, attempt)` 中重复计算
4. 缓存 `is_italic_prime` 的结果,避免对同一字体的斜体引号判断重复计算
5. 新增 `test_performance` 和 `test_math_performance` 回归测试

## 6 Why

在字体加载过程中,通过添加日志发现很多函数会被重复调用。例如解析数学模式下 24 个希腊字母时,`initialize_font` 被调用了 21 次,虽然大部分调用的子字体已经初始化过。`trimmed_tokenize(family, ",")` 和 `logical_font` 也在每次 `resolve` 时被重复计算,造成不必要的性能开销。

## 7 How

- `maybe_initialize_font(int nr)`:在调用 `initialize_font` 前检查 `fn[nr]` 是否已初始化,只有未初始化时才执行实际的字体创建逻辑
- `family_tokens`:在 `smart_font_rep` 构造函数中将 `trimmed_tokenize(family, ",")` 结果保存为成员变量,供 `resolve(string c)` 和 `is_italic_prime` 直接使用
- `given_font`:在构造函数中将 `logical_font(family, variant, series, rshape)` 结果保存为成员变量,供 `resolve(c, fam, attempt)` 中的 `is_wanted` 调用直接使用
- `is_italic_prime` 缓存:添加 `italic_prime_cached` 和 `italic_prime_result` 成员变量,首次计算后缓存结果
77 changes: 45 additions & 32 deletions src/Graphics/Fonts/smart_font.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -625,14 +625,17 @@ smart_font_rep::smart_font_rep (string name, font base_fn, font err_fn,
fn[SUBFONT_MAIN] = adjust_subfont (base_fn);
fn[SUBFONT_ERROR]= adjust_subfont (err_fn);
this->copy_math_pars (base_fn);
family_tokens = trimmed_tokenize (family, ",");
given_font = logical_font (family, variant, series, rshape);
italic_prime_cached= false;
if (shape == "mathitalic" || shape == "mathupright" || shape == "mathshape") {
if (is_math_family (mfam)) {
rshape= "right";
if (shape == "mathupright") this->copy_math_pars (base_fn);
else {
tree key= tuple ("math", mfam, variant, series, rshape);
int nr = sm->add_font (key, REWRITE_MATH);
initialize_font (nr);
maybe_initialize_font (nr);
this->copy_math_pars (fn[nr]);
fn[SUBFONT_MAIN]= fn[nr];
}
Expand All @@ -645,7 +648,7 @@ smart_font_rep::smart_font_rep (string name, font base_fn, font err_fn,
if (math_kind == 2) this->copy_math_pars (base_fn);
else {
italic_nr= sm->add_font (tuple ("fast-italic"), REWRITE_NONE);
initialize_font (italic_nr);
maybe_initialize_font (italic_nr);
this->copy_math_pars (fn[italic_nr]);
}
(void) sm->add_font (tuple ("special"), REWRITE_SPECIAL);
Expand Down Expand Up @@ -836,7 +839,7 @@ smart_font_rep::advance (string s, int& pos, string& r, int& nr) {
}

if (count == 1 && nr != -1 && fn_index == nr) {
if (N (fn) <= nr || is_nil (fn[nr])) initialize_font (nr);
maybe_initialize_font (nr);
if (!fn[nr]->supports (s (start, end))) break;
pos= end;
}
Expand All @@ -851,7 +854,7 @@ smart_font_rep::advance (string s, int& pos, string& r, int& nr) {
}
r= s (start, pos);
if (nr < 0) return;
if (N (fn) <= nr || is_nil (fn[nr])) initialize_font (nr);
maybe_initialize_font (nr);
if (sm->fn_rewr[nr] != REWRITE_NONE) r= rewrite (r, sm->fn_rewr[nr]);
}
if (DEBUG_VERBOSE) {
Expand Down Expand Up @@ -911,10 +914,9 @@ smart_font_rep::resolve (string c, string fam, int attempt) {
}
array<string> a= trimmed_tokenize (fam, "=");
if (N (a) >= 2) {
array<string> given= logical_font (family, variant, series, rshape);
fam = a[1];
array<string> b = tokenize (a[0], " ");
bool ok = is_wanted (c, fam, b, given);
fam = a[1];
array<string> b = tokenize (a[0], " ");
bool ok= is_wanted (c, fam, b, given_font);
if (!ok) {
return -1;
}
Expand All @@ -930,7 +932,7 @@ smart_font_rep::resolve (string c, string fam, int attempt) {
if (cfn->supports (c)) {
tree key= tuple ("subfont", fam);
int nr = sm->add_font (key, REWRITE_NONE);
initialize_font (nr);
maybe_initialize_font (nr);
return sm->add_char (key, c);
}
}
Expand All @@ -957,41 +959,41 @@ smart_font_rep::resolve (string c, string fam, int attempt) {
if (cfn->supports (c) || c == "<#200B>") {
tree key= tuple (fam, variant, series, rshape, "1");
int nr = sm->add_font (key, REWRITE_NONE);
initialize_font (nr);
maybe_initialize_font (nr);
return sm->add_char (key, c);
}
}

if (fam == "roman" && range == "greek") {
tree key= tuple ("greek", fam, variant, series, rshape);
int nr = sm->add_font (key, REWRITE_NONE);
initialize_font (nr);
maybe_initialize_font (nr);
return sm->add_char (key, c);
}
if (fam == "roman" && range == "latin") {
tree key= tuple ("latin", fam, variant, series, rshape);
int nr = sm->add_font (key, REWRITE_NONE);
initialize_font (nr);
maybe_initialize_font (nr);
return sm->add_char (key, c);
}
if (is_math_family (fam)) {
tree key= tuple ("math", fam, variant, series, rshape);
int nr = sm->add_font (key, REWRITE_MATH);
initialize_font (nr);
maybe_initialize_font (nr);
if (fn[nr]->supports (rewrite (c, REWRITE_MATH)))
return sm->add_char (key, c);
}
if ((fam == "roman" || fam == "cyrillic") && N (c) > 1) {
tree key= tuple ("cyrillic", fam, variant, series, rshape);
int nr = sm->add_font (key, REWRITE_CYRILLIC);
initialize_font (nr);
maybe_initialize_font (nr);
if (fn[nr]->supports (rewrite (c, REWRITE_CYRILLIC)))
return sm->add_char (key, c);
}
if (c == "<#3000>") {
tree key= tuple ("ignore");
int nr = sm->add_font (key, REWRITE_IGNORE);
initialize_font (nr);
maybe_initialize_font (nr);
return sm->add_char (key, c);
}
if (N (c) == 7 && starts (c, "<bbb-") && !occurs ("TeX Gyre", mfam)) {
Expand All @@ -1009,14 +1011,14 @@ smart_font_rep::resolve (string c, string fam, int attempt) {
vw = max (vw, 0.25 * lw);
tree key = tuple ("poor-bbb", as_string (hw), as_string (vw));
int nr = sm->add_font (key, REWRITE_POOR_BBB);
initialize_font (nr);
maybe_initialize_font (nr);
return sm->add_char (key, c);
}
}
if (starts (c, "<it-") && ends (c, ">")) {
tree key= tuple ("it");
int nr = sm->add_font (key, REWRITE_ITALIC);
initialize_font (nr);
maybe_initialize_font (nr);
return sm->add_char (key, c);
}
if (fam == mfam && !is_italic_font (mfam)) {
Expand All @@ -1025,7 +1027,7 @@ smart_font_rep::resolve (string c, string fam, int attempt) {
if (virtually_defined (c, emu_names[i])) {
tree key= tuple ("emulate", emu_names[i]);
int nr = sm->add_font (key, REWRITE_NONE);
initialize_font (nr);
maybe_initialize_font (nr);
if (fn[nr]->supports (c)) return sm->add_char (key, c);
}
}
Expand All @@ -1045,7 +1047,7 @@ smart_font_rep::resolve (string c, string fam, int attempt) {
if (cfn->supports (c) || c == "<#200B>") {
tree key= tuple (fam, v, series, rshape, as_string (a));
int nr = sm->add_font (key, REWRITE_NONE);
initialize_font (nr);
maybe_initialize_font (nr);
return sm->add_char (key, c);
}
}
Expand All @@ -1056,12 +1058,18 @@ smart_font_rep::resolve (string c, string fam, int attempt) {
bool
smart_font_rep::is_italic_prime (string c) {
if (c != "'" && c != "`") return false;
array<string> a= trimmed_tokenize (family, ",");
string s= "<#2B9>";
if (italic_prime_cached) return italic_prime_result;
string s= "<#2B9>";
if (c == "`") s= "<backprime>";
for (int i= 0; i < N (a); i++)
if (resolve (s, a[i], 1) >= 0) return false;
return true;
bool result= true;
for (int i= 0; i < N (family_tokens); i++)
if (resolve (s, family_tokens[i], 1) >= 0) {
result= false;
break;
}
italic_prime_cached= true;
italic_prime_result= result;
return result;
}

extern bool has_poor_rubber;
Expand All @@ -1079,7 +1087,7 @@ smart_font_rep::resolve_rubber (string c, string fam, int attempt) {
if (goal == "." || goal == "<nobracket>") {
tree key= tuple ("ignore");
int nr = sm->add_font (key, REWRITE_IGNORE);
initialize_font (nr);
maybe_initialize_font (nr);
return sm->add_char (key, c);
}
if (has_poor_rubber) {
Expand All @@ -1101,7 +1109,7 @@ smart_font_rep::resolve_rubber (string c, string fam, int attempt) {
if (bnr >= 0 && bnr < N (fn) && !is_nil (fn[bnr])) {
tree key= tuple ("rubber", as_string (bnr));
int nr = sm->add_font (key, REWRITE_NONE);
initialize_font (nr);
maybe_initialize_font (nr);
// cout << fn[nr]->res_name << " supports " << c << "? "
// << fn[nr]->supports (c) << LF;
if (fn[nr]->supports (c)) return sm->add_char (key, c);
Expand Down Expand Up @@ -1138,7 +1146,7 @@ smart_font_rep::resolve (string c) {
debug_fonts << "Main subfont of " << cork_to_utf8 (c) << " is "
<< fn[SUBFONT_MAIN]->res_name << LF;
}
array<string> a= trimmed_tokenize (family, ",");
array<string> a= family_tokens;

// Special handling for emoji characters - bypass font-family restrictions
string range= get_unicode_range (c);
Expand Down Expand Up @@ -1167,7 +1175,7 @@ smart_font_rep::resolve (string c) {
if (!is_nil (cfn) && cfn->supports (c)) {
tree key= tuple ("emoji-font", parts[1]);
int nr = sm->add_font (key, REWRITE_NONE);
initialize_font (nr);
maybe_initialize_font (nr);
return sm->add_char (key, c);
}
}
Expand All @@ -1179,22 +1187,22 @@ smart_font_rep::resolve (string c) {
if (upc != "" && fn[SUBFONT_MAIN]->supports (upc)) {
tree key= tuple ("up");
int nr = sm->add_font (key, REWRITE_UPRIGHT);
initialize_font (nr);
maybe_initialize_font (nr);
return sm->add_char (key, c);
}
string ugc= substitute_upright_greek (c);
if (ugc != "" && fn[SUBFONT_MAIN]->supports (ugc)) {
tree key= tuple ("upright-greek");
int nr = sm->add_font (key, REWRITE_UPRIGHT_GREEK);
initialize_font (nr);
maybe_initialize_font (nr);
return sm->add_char (key, c);
}
if (is_greek (c) && use_italic_greek (a) && shape != "mathupright") {
string gc= substitute_italic_greek (c);
if (gc != "" && fn[SUBFONT_MAIN]->supports (gc)) {
tree key= tuple ("italic-greek");
int nr = sm->add_font (key, REWRITE_ITALIC_GREEK);
initialize_font (nr);
maybe_initialize_font (nr);
return sm->add_char (key, c);
}
// cout << "Found " << c << " in greek\n";
Expand Down Expand Up @@ -1371,7 +1379,7 @@ smart_font_rep::initialize_font (int nr) {
fn[nr] = poor_bbb_font (sfn, pw, ph, 1.5 * pw);
}
else if (a[0] == "rubber" && N (a) == 2 && is_int (a[1])) {
initialize_font (as_int (a[1]));
maybe_initialize_font (as_int (a[1]));
fn[nr]= adjust_subfont (rubber_font (fn[as_int (a[1])]));
// fn[nr]= adjust_subfont (rubber_unicode_font (fn[as_int (a[1])]));
}
Expand All @@ -1388,6 +1396,11 @@ smart_font_rep::initialize_font (int nr) {
}
}

void
smart_font_rep::maybe_initialize_font (int nr) {
if (N (fn) <= nr || is_nil (fn[nr])) initialize_font (nr);
}

static int
get_ex (string family, string variant, string series, string shape,
int attempt) {
Expand Down
9 changes: 7 additions & 2 deletions src/Graphics/Fonts/smart_font.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,12 @@ struct smart_font_rep : font_rep {
int math_kind;
int italic_nr;

array<font> fn;
smart_map sm;
array<font> fn;
smart_map sm;
array<string> family_tokens;
array<string> given_font;
bool italic_prime_cached;
bool italic_prime_result;

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

font make_rubber_font (font base) override;
Expand Down
27 changes: 27 additions & 0 deletions tests/Graphics/Fonts/smart_font_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ private slots:
void test_get_right_slope ();
void test_latin_modern_math_italic_greek ();
void test_cursor_position_iii ();
void test_performance ();
void test_math_performance ();
};

void
Expand Down Expand Up @@ -161,5 +163,30 @@ TestSmartFont::test_cursor_position_iii () {
STACK_DELETE_ARRAY (xpos);
}

void
TestSmartFont::test_performance () {
font fn= smart_font ("sys-chinese", "rm", "medium", "right", 10, 600);

// Trigger a lot of character resolutions with repeated characters
string long_text= "The quick brown fox jumps over the lazy dog. "
"The quick brown fox jumps over the lazy dog. "
"The quick brown fox jumps over the lazy dog.";
metric ex;
fn->get_extents (long_text, ex);
}

void
TestSmartFont::test_math_performance () {
font fn=
smart_font ("Latin Modern Math", "rm", "medium", "mathitalic", 10, 600);

// Trigger math character resolutions
string math_text= "<alpha><beta><gamma><delta><epsilon><zeta><eta><theta>"
"<iota><kappa><lambda><mu><nu><xi><omicron><pi><rho><sigma>"
"<tau><upsilon><phi><chi><psi><omega>";
metric ex;
fn->get_extents (math_text, ex);
}

QTEST_MAIN (TestSmartFont)
#include "smart_font_test.moc"
Loading