Files
2026-06-18 15:02:28 +08:00

54 lines
2.3 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/// 中西文混排自动加间隙(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');
}