diff --git a/TeXmacs/tests/1166.scm b/TeXmacs/tests/1166.scm new file mode 100644 index 0000000000..c34aa4ec51 --- /dev/null +++ b/TeXmacs/tests/1166.scm @@ -0,0 +1,88 @@ +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; MODULE : 1166.scm +;; DESCRIPTION : 集成测试:文本模式字符的逻辑字体与物理字体解析。 +;; COPYRIGHT : (C) 2026 Mogan STEM +;; +;; PURPOSE +;; [1166] 钉死文本模式下 Unicode 码位 0–255 的字体解析契约(后续逐步扩大 +;; 码位范围): +;; 1. 逻辑字体:当前环境 font/font-family/font-series/font-shape 为 +;; roman rm medium right(默认文档、文本模式)。 +;; 2. 物理字体:每个码位经 smart font 按覆盖度解析出的实际叶子字体 +;; (physical-font-for-string,真实排版路径量得): +;; - 大多数码位 -> ec:ecrm10@600(随仓库分发的 Cork 字体) +;; - 128–160 及部分 Latin-1 符号 -> unicode:cmunrm10@600 +;; (TeXmacs/fonts/opentype/cm-unicode 随仓库分发,跨机器稳定) +;; - 181 (µ) -> gr:grmn1000@600(希腊 tfm) +;; 3. 不可渲染识别:无字体覆盖的码位(如 U+10FFFE)落到 error 字体, +;; res_name 以 "error-" 开头。 +;; +;; 码位区间以 font-test-segments 参数化;扩大范围时追加区间并扩展 +;; expected-physical-font 的例外表即可。 +;; +;; USAGE +;; xmake b stem +;; xmake r 1166 # headless,同步断言全量执行 +;; MOGAN_TEST_GUI=1 xmake r 1166 # 真实 GUI,同样同步执行 +;; +;; 注意:本测试全程同步(physical-font-for-string 是同步 bridge), +;; headless 与 GUI 模式都会真正执行断言。 +;; +;; This software falls under the GNU general public license version 3 or later. +;; It comes WITHOUT ANY WARRANTY whatsoever. For details see LICENSE. +;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(import (liii check)) +(import (liii unicode)) + +(check-set-mode! 'report-failed) + +;; 待覆盖的码位区间(闭区间)。后续扩大范围时在此追加。 +(define font-test-segments '((0 . 255))) + +;; 解析到 unicode:cmunrm10@600 的码位(0–255 内)。 +(define cmunrm-codepoints + '(128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 + 147 148 149 150 151 152 153 154 155 156 157 158 159 160 162 164 165 166 169 + 170 172 173 174 176 177 178 179 182 183 185 186 188 189 190 215 247)) + +;; 0–255 的物理字体期望:默认 Cork 字体,例外见上。 +(define (expected-physical-font n) + (cond ((== n 181) "gr:grmn1000@600") + ((member n cmunrm-codepoints) "unicode:cmunrm10@600") + (else "ec:ecrm10@600"))) + +;; 码位 -> tm 内部字符串(cork;超出 cork 的由 utf8->cork 转 <#XXXX>)。 +(define (codepoint->tmstring n) + (utf8->cork (utf8-string (integer->char n)))) + +(define (test-logical-font) + ;; 前置:默认环境为文本模式。 + (check (get-env "mode") => "text") + (check (get-env "font") => "roman") + (check (get-env "font-family") => "rm") + (check (get-env "font-series") => "medium") + (check (get-env "font-shape") => "right")) + +(define (test-physical-fonts lo hi) + (do ((n lo (+ n 1))) + ((> n hi)) + (check (physical-font-for-string (codepoint->tmstring n)) + => (expected-physical-font n)))) + +(define (test-unrenderable-detection) + ;; U+10FFFE 无字体覆盖,须落到 error 字体(res_name 前缀 error-)。 + (let ((f (physical-font-for-string (codepoint->tmstring #x10FFFE)))) + (check-true (and (> (string-length f) 6) + (string=? (substring f 0 6) "error-"))))) + +(tm-define (test_1166) + (test-logical-font) + (for (seg font-test-segments) + (test-physical-fonts (car seg) (cdr seg))) + (test-unrenderable-detection) + (check-report) + (quit-TeXmacs) +) ;tm-define diff --git a/devel/1166.md b/devel/1166.md new file mode 100644 index 0000000000..ed91b36267 --- /dev/null +++ b/devel/1166.md @@ -0,0 +1,70 @@ +# [1166] 文本模式字符的逻辑字体与物理字体集成测试 + +## 1 任务描述 + +为文本模式的字符新增集成测试:给定 Unicode 码位范围(首段 0–255,后续逐步扩大), +钉死每个字符在文本模式下解析到的逻辑字体与物理字体,并能识别出无法渲染 +(落到 error 字体)的字符。 + +## 2 任务相关的代码文件 + +### 新增 +- `TeXmacs/tests/1166.scm` — GUI 集成测试(`xmake r 1166`) +- `devel/1166.md` — 本文档 + +### 修改 +- `src/Edit/Editor/edit_typeset.hpp` / `edit_typeset.cpp` — 新增 + `physical_font_for_string`:在当前文档环境(文本模式)下排版给定字符串, + 返回叶子物理字体的 `res_name`(无法渲染时为 `error-` 前缀) +- `src/Scheme/Glue/glue_editor.lua` — 注册 scheme 绑定 `physical-font-for-string` + +## 3 设计说明 + +- **逻辑字体**:由当前环境变量给出(`font` / `font-family` / `font-series` / + `font-shape`),scheme 侧用现有 `get-env` 读取,无需新 bridge。 +- **物理字体**:逻辑字体经 `find_font` / smart font 按字符覆盖度解析出的实际 + 字体(如 `ec:ecrm10@600`、`unicode:NotoSerif-Regular10@600`)。字符无字体 + 覆盖时落到 error 字体,`res_name` 以 `error-` 开头。 +- bridge 复用 `print_snippet` 的临时文档排版路径(`new_document` + + `typeset_as_box` + `get_leaf_font`),量的是真实排版结果而非内部缓存。 +- 测试以码位区间参数化,期望表按区间分段维护,便于后续逐步扩大覆盖范围。 + +## 4 如何测试 + +```bash +xmake b stem +xmake r 1166 # headless 冒烟 +MOGAN_TEST_GUI=1 xmake r 1166 # 真实 GUI,执行断言链 +``` + +## 5 改动记录 + +### 2026-08-04 + +- **What**:新增文本模式字符字体解析集成测试(码位 0–255),及配套 + `physical-font-for-string` bridge。 +- **Why**:钉死逻辑字体→物理字体的解析契约,识别无字体覆盖落到 error + 字体的字符,为后续扩大 Unicode 覆盖范围提供回归基线。 +- **How**: + - `edit_typeset_rep::physical_font_for_string` 复用 `print_snippet` 的 + 临时文档排版路径(`new_document` + `typeset_as_box`),递归下钻到 + TEXT_BOX 叶子取字体;叶子挂的是 smart font,再经新增的虚函数 + `font_rep::get_subfont`(`smart_font_rep` 用 `advance` 解析到实际 + 子字体)得到物理字体 `res_name`;无覆盖时经 SUBFONT_ERROR 得到 + `error-` 前缀名。 + - `editor_rep` 新增纯虚 `physical_font_for_string`(glue 经 + `get_current_editor()->` 调用,走 editor_rep 虚表)。 + - 测试数据:0–255 中 200 码位 → `ec:ecrm10@600`,55 码位 → + `unicode:cmunrm10@600`(128–160 及部分 Latin-1 符号),181 (µ) → + `gr:grmn1000@600`;三类字体均随仓库分发,跨机器稳定。 +- **涉及文件**: + - `src/Edit/Editor/edit_typeset.hpp` / `edit_typeset.cpp`(bridge + + `find_leaf_font` 辅助) + - `src/Edit/editor.hpp`(纯虚声明) + - `src/Graphics/Fonts/font.hpp` / `font.cpp`(`get_subfont` 默认实现) + - `src/Graphics/Fonts/smart_font.hpp` / `smart_font.cpp`(`get_subfont` + 覆写) + - `src/Scheme/Glue/glue_editor.lua`(scheme 绑定) + - `TeXmacs/tests/1166.scm`(集成测试,headless/GUI 均同步执行断言) +- **验证**:`xmake r 1166` 与 `MOGAN_TEST_GUI=1 xmake r 1166` 均 + 262 correct / 0 failed。 diff --git a/src/Edit/Editor/edit_typeset.cpp b/src/Edit/Editor/edit_typeset.cpp index 018f2307b6..4321220d0b 100644 --- a/src/Edit/Editor/edit_typeset.cpp +++ b/src/Edit/Editor/edit_typeset.cpp @@ -17,12 +17,14 @@ #include "file.hpp" #include "iterator.hpp" #include "merge_sort.hpp" +#include "new_document.hpp" #include "new_style.hpp" #include "observers.hpp" #include "tm_buffer.hpp" #include "tm_timer.hpp" #include "tree_modify.hpp" #include "tree_observer.hpp" +#include "typesetter.hpp" #include using namespace moebius; @@ -617,6 +619,37 @@ edit_typeset_rep::get_env_string (string var) { return as_string (get_env_value (var)); } +static font +find_leaf_font (box b) { + if (is_nil (b)) return font (); + if (b->get_type () == TEXT_BOX) return b->get_leaf_font (); + int n= N (b); + for (int i= 0; i < n; i++) { + font f= find_leaf_font (b[i]); + if (!is_nil (f)) return f; + } + return font (); +} + +string +edit_typeset_rep::physical_font_for_string (string s) { + // 复用 print_snippet 的临时文档排版路径,量真实排版结果: + // 叶子物理字体的 res_name;字符无字体覆盖时为 "error-" 前缀 + path temp_root= new_document (); + temp_root << 0; + assign (subtree (et, temp_root), tree (s)); + typeset_prepare (); + env->style_init_env (); + env->update (); + box b= typeset_as_box (env, tree (s), reverse (temp_root)); + delete_document (path_up (temp_root)); + font fn= find_leaf_font (b); + if (is_nil (fn)) return ""; + // 文本盒挂的是 smart font,需按字符下钻到实际物理子字体 + font sub= fn->get_subfont (s); + return is_nil (sub) ? string ("") : sub->res_name; +} + string edit_typeset_rep::get_init_string (string var) { return as_string (get_init_value (var)); diff --git a/src/Edit/Editor/edit_typeset.hpp b/src/Edit/Editor/edit_typeset.hpp index 8c19dbf055..915fef3018 100644 --- a/src/Edit/Editor/edit_typeset.hpp +++ b/src/Edit/Editor/edit_typeset.hpp @@ -79,6 +79,7 @@ class edit_typeset_rep : virtual public editor_rep { color get_env_color (string var_name); color get_init_color (string var_name); language get_env_language (); + string physical_font_for_string (string s); int get_page_count (); string get_page_number_text (int page_index); int get_current_page (); diff --git a/src/Edit/editor.hpp b/src/Edit/editor.hpp index 864872a66e..eebc93ca63 100644 --- a/src/Edit/editor.hpp +++ b/src/Edit/editor.hpp @@ -320,6 +320,7 @@ class editor_rep : public simple_widget_rep { virtual color get_env_color (string var_name) = 0; virtual color get_init_color (string var_name) = 0; virtual language get_env_language () = 0; + virtual string physical_font_for_string (string s) = 0; virtual int get_page_count () = 0; virtual string get_page_number_text (int page_index) = 0; virtual int get_current_page () = 0; diff --git a/src/Graphics/Fonts/font.cpp b/src/Graphics/Fonts/font.cpp index c42c0bf292..bdfaae325c 100644 --- a/src/Graphics/Fonts/font.cpp +++ b/src/Graphics/Fonts/font.cpp @@ -551,6 +551,12 @@ use_poor_rubber (font fn) { !starts (fn->res_name, "stix-"); } +font +font_rep::get_subfont (string s) { + (void) s; + return font (this); +} + font font_rep::make_rubber_font (font fn) { string name= locase_all (fn->res_name); diff --git a/src/Graphics/Fonts/font.hpp b/src/Graphics/Fonts/font.hpp index 49ad257bb8..955ae260dc 100644 --- a/src/Graphics/Fonts/font.hpp +++ b/src/Graphics/Fonts/font.hpp @@ -148,6 +148,7 @@ struct font_rep : rep { void copy_math_pars (font fn); virtual font make_rubber_font (font base); + virtual font get_subfont (string s); virtual bool supports (string c) = 0; virtual void get_extents (string s, metric& ex)= 0; virtual void get_extents (string s, metric& ex, bool ligf); diff --git a/src/Graphics/Fonts/smart_font.cpp b/src/Graphics/Fonts/smart_font.cpp index 1b60f91d14..40d0b3a9cb 100644 --- a/src/Graphics/Fonts/smart_font.cpp +++ b/src/Graphics/Fonts/smart_font.cpp @@ -868,6 +868,17 @@ smart_font_rep::advance (string s, int& pos, string& r, int& nr) { } } +font +smart_font_rep::get_subfont (string s) { + int pos= 0, nr= -1; + string r; + advance (s, pos, r, nr); + if (nr < 0 || nr >= N (fn)) return font (); + maybe_initialize_font (nr); + if (is_nil (fn[nr])) return font (); + return fn[nr]->get_subfont (r); +} + bool is_italic_font (string master) { return contains (string ("italic"), master_features (master)); diff --git a/src/Graphics/Fonts/smart_font.hpp b/src/Graphics/Fonts/smart_font.hpp index d0b9a46481..64ed0a46cd 100644 --- a/src/Graphics/Fonts/smart_font.hpp +++ b/src/Graphics/Fonts/smart_font.hpp @@ -125,6 +125,7 @@ struct smart_font_rep : font_rep { int adjusted_dpi (string fam, string var, string ser, string sh, int att); font make_rubber_font (font base) override; + font get_subfont (string s) override; bool supports (string c); void get_extents (string s, metric& ex); diff --git a/src/Scheme/Glue/glue_editor.lua b/src/Scheme/Glue/glue_editor.lua index f8a92d2365..cbf66f5823 100644 --- a/src/Scheme/Glue/glue_editor.lua +++ b/src/Scheme/Glue/glue_editor.lua @@ -389,6 +389,14 @@ function main() "string" } }, + { + scm_name = "physical-font-for-string", + cpp_name = "physical_font_for_string", + ret_type = "string", + arg_list = { + "string" + } + }, { scm_name = "get-env-tree", cpp_name = "get_env_value",