/// 中西文混排自动加间隙(W3C clreq §3.2.2)。 /// /// 规范要求:横排时汉字与西文字母/数字之间留约 1/4 em 间隙。CSS 有 /// `text-autospace`,Flutter 没有对应能力,因此在文本层插入 /// U+2009 THIN SPACE(约 1/5 em,视觉接近规范建议,且远比全角空格克制)。 /// /// 规则(与 pangu 习惯一致): /// - 汉字 ↔ 字母/数字 相邻 → 插 thin space(「下降2.2%」→「下降 2.2%」); /// - 西文 run 末尾的 % ‰ 视作 run 的一部分(「2.2%的降幅」→「2.2% 的降幅」); /// - 全角标点(,。:等)自带空隙,不加; /// - markdown 版本额外处理强调/行内码边界(「至**138美元**」→「至 **138美元**」), /// 并跳过 fenced code block;表格管道符、链接 URL 天然不受影响。 library; const String _thin = '\u2009'; // THIN SPACE // CJK 统一表意文字(基本区 + 扩展 A + 兼容区) const String _han = r'㐀-䶿一-鿿豈-﫿'; // 西文边界字符(字母/数字);西文侧在右边界额外允许 % ‰ 收尾 const String _west = r'A-Za-z0-9'; final RegExp _hanWest = RegExp('([$_han])([$_west])'); final RegExp _westHan = RegExp('([$_west%‰])([$_han])'); // 强调/行内码标记夹在边界中间:汉**西 / 西**汉 final RegExp _hanMarkWest = RegExp('([$_han])(\\*{1,3}|`)([$_west])'); final RegExp _westMarkHan = RegExp('([$_west%‰])(\\*{1,3}|`)([$_han])'); /// 纯文本版:用于标题、副题、简介等 Text 控件。 String cjkAutoSpace(String text) { var s = text; s = s.replaceAllMapped(_hanWest, (m) => '${m[1]}$_thin${m[2]}'); s = s.replaceAllMapped(_westHan, (m) => '${m[1]}$_thin${m[2]}'); return s; } /// markdown 版:跳过 fenced code block,并处理强调/行内码边界。 String cjkMarkdownAutoSpace(String md) { final lines = md.split('\n'); var inFence = false; for (var i = 0; i < lines.length; i++) { final t = lines[i].trimLeft(); if (t.startsWith('```') || t.startsWith('~~~')) { inFence = !inFence; continue; } if (inFence) continue; var s = lines[i]; s = s.replaceAllMapped(_hanMarkWest, (m) => '${m[1]}$_thin${m[2]}${m[3]}'); s = s.replaceAllMapped(_westMarkHan, (m) => '${m[1]}${m[2]}$_thin${m[3]}'); lines[i] = cjkAutoSpace(s); } return lines.join('\n'); }