本次提交包含多项更新: 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. 为大量页面添加统一自适应返回按钮
299 lines
10 KiB
Dart
299 lines
10 KiB
Dart
/// ============================================================
|
||
/// 闲言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);
|
||
}
|