Files
xianyan/scripts/verify_fonts.dart
Developer 85d856f0ed chore: 完成多模块迭代优化与依赖更新
本次提交包含多项更新:
1. 更新file_picker依赖到11.0.0-ohos.1版本
2. 清理SecureStorage、Catcher2配置冗余代码
3. 优化鸿蒙系统下HomeWidget调用方式
4. 重构编辑器导航栏图标与页面路由引用
5. 修复边框样式、简化空值判断逻辑
6. 移除冗余系统UI样式配置
7. 新增共享组件导出与自适应返回按钮
8. 批量替换路由引用为app_routes
9. 标记过时通知服务并补充注释
10. 新增引导页扫一扫功能卡片
11. 完善沉浸式状态栏配置逻辑
12. 为大量页面添加统一自适应返回按钮
2026-05-22 23:14:38 +08:00

299 lines
10 KiB
Dart
Raw 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.
/// ============================================================
/// 闲言APP — 字体下载URL验证脚本
/// 创建时间: 2026-05-22
/// 更新时间: 2026-05-22
/// 作用: 验证所有在线字体URL的可访问性和文件格式有效性
/// 上次更新: 初始创建验证8个字体+备用URL的下载和文件头校验
/// 运行方式: dart run Scripts/verify_fonts.dart
/// ============================================================
import 'dart:io';
import 'dart:typed_data';
/// 字体数据定义(与 font_models.dart 保持同步)
const onlineFontData = [
(
'霞鹜文楷',
'LXGWWenKai',
'https://raw.githubusercontent.com/lxgw/LxgwWenKai/main/fonts/LXGWWenKai-Regular.ttf',
'🖋️',
),
(
'阿里巴巴普惠体',
'AlibabaPuHuiTi',
'https://puhuiti.oss-cn-hangzhou.aliyuncs.com/AlibabaPuHuiTi-3/AlibabaPuHuiTi-3-55-Regular/AlibabaPuHuiTi-3-55-Regular.ttf',
'💼',
),
(
'站酷快乐体',
'ZCOOLKuaiLe',
'https://raw.githubusercontent.com/googlefonts/zcool-kuaile/main/fonts/ttf/ZCOOLKuaiLe-Regular.ttf',
'🎉',
),
(
'站酷小薇',
'ZCOOLXiaoWei',
'https://raw.githubusercontent.com/googlefonts/zcool-xiaowei/main/fonts/ZCOOLXiaoWei-Regular.ttf',
'🌸',
),
(
'思源黑体',
'NotoSansSC',
'https://raw.githubusercontent.com/notofonts/noto-cjk/main/Sans/OTF/SimplifiedChinese/NotoSansCJKsc-Regular.otf',
'📐',
),
(
'思源宋体',
'NotoSerifSC',
'https://raw.githubusercontent.com/notofonts/noto-cjk/main/Serif/OTF/SimplifiedChinese/NotoSerifCJKsc-Regular.otf',
'📜',
),
(
'更纱黑体',
'SarasaGothicSC',
'https://raw.githubusercontent.com/be5invis/Sarasa-Gothic/main/fonts/SarasaGothicSC-Regular.ttf',
'🎯',
),
(
'文泉驿微米黑',
'WenQuanYiMicroHei',
'https://raw.githubusercontent.com/niclas/wqy-microhei-font/master/wqy-microhei.ttc',
'✒️',
),
];
const fontFallbackUrls = <String, List<String>>{
'LXGWWenKai': [
'https://cdn.jsdelivr.net/gh/lxgw/LxgwWenKai@v1.501/fonts/LXGWWenKai-Regular.ttf',
'https://github.com/lxgw/LxgwWenKai/releases/download/v1.501/LXGWWenKai-Regular.ttf',
],
'AlibabaPuHuiTi': [
'https://fonts.alicdn.com/font/AlibabaPuHuiTi-3/AlibabaPuHuiTi-3-55-Regular.ttf',
],
'ZCOOLKuaiLe': [
'https://cdn.jsdelivr.net/gh/googlefonts/zcool-kuaile@main/fonts/ttf/ZCOOLKuaiLe-Regular.ttf',
],
'ZCOOLXiaoWei': [
'https://cdn.jsdelivr.net/gh/googlefonts/zcool-xiaowei@main/fonts/ZCOOLXiaoWei-Regular.ttf',
],
'NotoSansSC': [
'https://cdn.jsdelivr.net/gh/notofonts/noto-cjk@main/Sans/OTF/SimplifiedChinese/NotoSansCJKsc-Regular.otf',
],
'NotoSerifSC': [
'https://cdn.jsdelivr.net/gh/notofonts/noto-cjk@main/Serif/OTF/SimplifiedChinese/NotoSerifCJKsc-Regular.otf',
],
'SarasaGothicSC': [
'https://github.com/be5invis/Sarasa-Gothic/releases/download/v1.0.19/SarasaGothicSC-Regular.ttf',
],
'WenQuanYiMicroHei': [
'https://cdn.jsdelivr.net/gh/niclas/wqy-microhei-font@master/wqy-microhei.ttc',
],
};
/// 校验字体文件头是否为有效 TTF/OTF/TTC
bool isValidFontFile(Uint8List bytes) {
if (bytes.length < 4) return false;
final h = bytes;
final isTtf = (h[0] == 0x00 && h[1] == 0x01 && h[2] == 0x00 && h[3] == 0x00) ||
(h[0] == 0x74 && h[1] == 0x72 && h[2] == 0x75 && h[3] == 0x65);
final isOtf = h[0] == 0x4F && h[1] == 0x54 && h[2] == 0x54 && h[3] == 0x4F;
final isTtc = h[0] == 0x74 && h[1] == 0x74 && h[2] == 0x63 && h[3] == 0x66;
return isTtf || isOtf || isTtc;
}
/// 检测字体格式类型
String detectFontType(Uint8List bytes) {
if (bytes.length < 4) return '未知(数据不足)';
final h = bytes;
if (h[0] == 0x00 && h[1] == 0x01 && h[2] == 0x00 && h[3] == 0x00) return 'TTF';
if (h[0] == 0x74 && h[1] == 0x72 && h[2] == 0x75 && h[3] == 0x65) return 'TTF(true)';
if (h[0] == 0x4F && h[1] == 0x54 && h[2] == 0x54 && h[3] == 0x4F) return 'OTF';
if (h[0] == 0x74 && h[1] == 0x74 && h[2] == 0x63 && h[3] == 0x66) return 'TTC';
return '未知(0x${h[0].toRadixString(16)}${h[1].toRadixString(16)}${h[2].toRadixString(16)}${h[3].toRadixString(16)})';
}
/// 下载字体并验证
Future<_FontVerifyResult> verifyFontUrl(
String name,
String fontFamily,
String url,
HttpClient client,
) async {
final result = _FontVerifyResult(name: name, fontFamily: fontFamily, url: url);
try {
final request = await client.getUrl(Uri.parse(url));
final response = await request.close();
result.statusCode = response.statusCode;
if (response.statusCode != 200) {
result.success = false;
result.error = 'HTTP ${response.statusCode}';
return result;
}
final bytes = await consolidateBytes(response);
result.fileSize = bytes.length;
result.fontType = detectFontType(bytes);
result.validFormat = isValidFontFile(bytes);
result.success = result.validFormat;
if (!result.validFormat) {
result.error = '文件头非 TTF/OTF/TTC 格式,检测为: ${result.fontType}';
}
} catch (e) {
result.success = false;
result.error = e.toString();
}
return result;
}
/// 合并 HttpClientResponse 的字节流
Future<Uint8List> consolidateBytes(HttpClientResponse response) async {
final builder = BytesBuilder();
await for (final chunk in response) {
builder.add(chunk);
}
return builder.toBytes();
}
/// 验证结果
class _FontVerifyResult {
_FontVerifyResult({
required this.name,
required this.fontFamily,
required this.url,
});
final String name;
final String fontFamily;
final String url;
bool success = false;
int? statusCode;
int? fileSize;
String? fontType;
bool validFormat = false;
String? error;
String get fileSizeStr {
if (fileSize == null) return 'N/A';
if (fileSize! < 1024) return '$fileSize B';
if (fileSize! < 1024 * 1024) return '${(fileSize! / 1024).toStringAsFixed(1)} KB';
return '${(fileSize! / (1024 * 1024)).toStringAsFixed(1)} MB';
}
}
Future<void> main() async {
print('═══════════════════════════════════════════════════════════');
print(' 闲言APP — 字体下载URL验证工具');
print(' 运行时间: ${DateTime.now().toIso8601String()}');
print('═══════════════════════════════════════════════════════════');
print('');
final client = HttpClient();
client.connectionTimeout = const Duration(seconds: 30);
client.userAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36';
final results = <_FontVerifyResult>[];
print('━━━ 阶段1: 主URL验证 ━━━');
print('');
for (final font in onlineFontData) {
final name = font.$1;
final fontFamily = font.$2;
final url = font.$3;
stdout.write(' $name ($fontFamily)... ');
final result = await verifyFontUrl(name, fontFamily, url, client);
results.add(result);
if (result.success) {
print('✅ 通过 [${result.fontType}] ${result.fileSizeStr}');
} else {
print('❌ 失败 [${result.error}]');
}
}
print('');
print('━━━ 阶段2: 备用URL验证 ━━━');
print('');
for (final entry in fontFallbackUrls.entries) {
final fontFamily = entry.key;
final fallbacks = entry.value;
final fontName = onlineFontData
.where((f) => f.$2 == fontFamily)
.map((f) => f.$1)
.firstOrNull ??
fontFamily;
for (int i = 0; i < fallbacks.length; i++) {
final url = fallbacks[i];
stdout.write(' $fontName 备用#${i + 1}... ');
final result = await verifyFontUrl(fontName, fontFamily, url, client);
results.add(result);
if (result.success) {
print('✅ 通过 [${result.fontType}] ${result.fileSizeStr}');
} else {
print('❌ 失败 [${result.error}]');
}
}
}
client.close();
print('');
print('═══════════════════════════════════════════════════════════');
print(' 验证结果汇总');
print('═══════════════════════════════════════════════════════════');
print('');
final primaryResults = results.sublist(0, onlineFontData.length);
final fallbackResults = results.sublist(onlineFontData.length);
print(' 主URL结果:');
for (final r in primaryResults) {
final icon = r.success ? '' : '';
print(' $icon ${r.name}${r.success ? '${r.fontType} ${r.fileSizeStr}' : r.error}');
}
print('');
print(' 备用URL结果:');
for (final r in fallbackResults) {
final icon = r.success ? '' : '';
print(' $icon ${r.name}${r.success ? '${r.fontType} ${r.fileSizeStr}' : r.error}');
}
final primaryPass = primaryResults.where((r) => r.success).length;
final primaryTotal = primaryResults.length;
final allPass = results.where((r) => r.success).length;
final allTotal = results.length;
print('');
print(' 主URL: $primaryPass/$primaryTotal 通过');
print(' 全部: $allPass/$allTotal 通过');
print('');
final failedPrimary = primaryResults.where((r) => !r.success).toList();
if (failedPrimary.isNotEmpty) {
print(' ⚠️ 以下字体主URL不可用需要更新:');
for (final r in failedPrimary) {
final hasFallback = fallbackResults.any(
(f) => f.fontFamily == r.fontFamily && f.success,
);
print(' - ${r.name}: ${r.error} ${hasFallback ? '(有可用备用URL)' : '(⚠️ 无可用备用URL!)'}');
}
} else {
print(' 🎉 所有字体主URL验证通过');
}
print('');
print('═══════════════════════════════════════════════════════════');
exit(failedPrimary.isNotEmpty ? 1 : 0);
}