diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index d578f952..037cd606 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -7,11 +7,6 @@ on: schedule: - cron: '0 1 * * *' # UTC 01:00 = 北京时间 09:00 workflow_dispatch: - inputs: - skip_font_subset: - description: 'Skip font subset step' - type: boolean - default: true concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -86,162 +81,7 @@ jobs: echo "## ⏱️ Pre-check 耗时: ${duration}秒" >> $GITHUB_STEP_SUMMARY # ============================================================ - # Job 1: Font Subset (字体子集化 - 可选并行任务) - # ============================================================ - subset-fonts: - runs-on: ubuntu-latest - timeout-minutes: 15 - permissions: - contents: write - needs: pre-check - if: | - needs.pre-check.outputs.can_deploy == 'true' && - inputs.skip_font_subset != 'true' && - github.event_name == 'workflow_dispatch' - outputs: - font_updated: ${{ steps.update.outputs.font_updated }} - char_count: ${{ steps.stats.outputs.char_count }} - original_size: ${{ steps.stats.outputs.original_size }} - subset_size: ${{ steps.stats.outputs.subset_size }} - - steps: - - name: Start timing - id: start-subset - run: echo "start_time=$(date +%s)" >> $GITHUB_OUTPUT - - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Setup Python - uses: actions/setup-python@v5 - with: - python-version: '3.12' - - - name: Install dependencies - run: pip install fonttools brotli zopfli - - - name: Generate font subset - id: update - run: | - # 运行字体子集化脚本 - # 会自动扫描: - # 1. 静态文件 (content, layouts, public) - # 2. 本地 JSON 数据 (友链、朋友圈) - # 3. 远程 API (api.usj.cc 的友链、订阅源、朋友圈) - python scripts/subset-font-safe.py - - # 检查是否有变化 - git config --global user.name "GitHub Actions" - git config --global user.email "actions@github.com" - - git add themes/Ying/static/font/zql-v2-subset.woff2 themes/Ying/static/font/used_chars.txt - if ! git diff --staged --quiet; then - git commit -m "chore: update font subset [skip ci]" - git push origin HEAD:main - echo "font_updated=true" >> $GITHUB_OUTPUT - echo "✅ 字体子集已更新并推送" - else - echo "font_updated=false" >> $GITHUB_OUTPUT - echo "ℹ️ 字体子集无变化,跳过提交" - fi - - - name: Collect stats - id: stats - if: always() - run: | - CHAR_COUNT=$(wc -m < themes/Ying/static/font/used_chars.txt | tr -d ' ') - ORIGINAL_SIZE=$(stat -c%s themes/Ying/static/font/zql-v2.woff2) - SUBSET_SIZE=$(stat -c%s themes/Ying/static/font/zql-v2-subset.woff2) - echo "char_count=$CHAR_COUNT" >> $GITHUB_OUTPUT - echo "original_size=$ORIGINAL_SIZE" >> $GITHUB_OUTPUT - echo "subset_size=$SUBSET_SIZE" >> $GITHUB_OUTPUT - - - name: Create font subset summary - if: always() - run: | - ORIGINAL_SIZE_KB=$(echo "scale=1; ${{ steps.stats.outputs.original_size }} / 1024" | bc) - SUBSET_SIZE_KB=$(echo "scale=1; ${{ steps.stats.outputs.subset_size }} / 1024" | bc) - REDUCTION_KB=$(echo "scale=1; (${{ steps.stats.outputs.original_size }} - ${{ steps.stats.outputs.subset_size }}) / 1024" | bc) - PERCENTAGE=$(echo "scale=1; (${{ steps.stats.outputs.original_size }} - ${{ steps.stats.outputs.subset_size }}) * 100 / ${{ steps.stats.outputs.original_size }}" | bc) - - echo "## 🔤 字体子集化" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - if [ "${{ steps.update.outputs.font_updated }}" = "true" ]; then - echo "✅ **字体子集已更新**" >> $GITHUB_STEP_SUMMARY - else - echo "ℹ️ **字体子集无变化**" >> $GITHUB_STEP_SUMMARY - fi - - echo "" >> $GITHUB_STEP_SUMMARY - echo "### 📊 统计信息" >> $GITHUB_STEP_SUMMARY - echo "- **字符数:** ${{ steps.stats.outputs.char_count }}" >> $GITHUB_STEP_SUMMARY - echo "- **原始大小:** ${ORIGINAL_SIZE_KB} KB" >> $GITHUB_STEP_SUMMARY - echo "- **子集大小:** ${SUBSET_SIZE_KB} KB" >> $GITHUB_STEP_SUMMARY - echo "- **节省空间:** ${REDUCTION_KB} KB (${PERCENTAGE}%)" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "### 🌐 扫描的数据源" >> $GITHUB_STEP_SUMMARY - echo "- 静态文件: content, layouts, public" >> $GITHUB_STEP_SUMMARY - echo "- 本地 JSON: link_lite.json, friend_circle_data.json" >> $GITHUB_STEP_SUMMARY - echo "- 远程 API: api.usj.cc (links, feeds, articles)" >> $GITHUB_STEP_SUMMARY - - - name: Send font subset notification - if: steps.update.outputs.font_updated == 'true' - env: - TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }} - TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }} - FEISHU_WEBHOOK_URL: ${{ secrets.FEISHU_WEBHOOK_URL }} - run: | - ORIGINAL_SIZE_KB=$(echo "scale=1; ${{ steps.stats.outputs.original_size }} / 1024" | bc) - SUBSET_SIZE_KB=$(echo "scale=1; ${{ steps.stats.outputs.subset_size }} / 1024" | bc) - REDUCTION_KB=$(echo "scale=1; (${{ steps.stats.outputs.original_size }} - ${{ steps.stats.outputs.subset_size }}) / 1024" | bc) - PERCENTAGE=$(echo "scale=1; (${{ steps.stats.outputs.original_size }} - ${{ steps.stats.outputs.subset_size }}) * 100 / ${{ steps.stats.outputs.original_size }}" | bc) - - if [ -n "$TELEGRAM_BOT_TOKEN" ] && [ -n "$TELEGRAM_CHAT_ID" ]; then - MESSAGE="✅ **字体子集化完成** - - 📦 项目: ${{ github.repository }} - 🌿 分支: ${{ github.ref_name }} - - 📊 优化统计: - - 字符数: ${{ steps.stats.outputs.char_count }} - - 原始大小: ${ORIGINAL_SIZE_KB} KB - - 子集大小: ${SUBSET_SIZE_KB} KB - - 节省空间: ${REDUCTION_KB} KB (${PERCENTAGE}%) - - 🌐 数据源: - - 静态文件 + 本地JSON + 远程API" - - curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \ - -d chat_id="${TELEGRAM_CHAT_ID}" \ - -d text="${MESSAGE}" \ - -d parse_mode="Markdown" - fi - - if [ -n "$FEISHU_WEBHOOK_URL" ]; then - curl -s -X POST "${FEISHU_WEBHOOK_URL}" \ - -H "Content-Type: application/json" \ - -d '{ - "msg_type": "text", - "content": { - "text": "✅ **字体子集化完成**\n\n📦 项目: ${{ github.repository }}\n🌿 分支: ${{ github.ref_name }}\n\n📊 优化统计:\n- 字符数: ${{ steps.stats.outputs.char_count }}\n- 原始大小: '"${ORIGINAL_SIZE_KB}"' KB\n- 子集大小: '"${SUBSET_SIZE_KB}"' KB\n- 节省空间: '"${REDUCTION_KB}"' KB ('"${PERCENTAGE}"'%)\n\n🌐 数据源: 静态文件 + 本地JSON + 远程API" - } - }' - fi - - - name: End timing - id: end-subset - run: | - end_time=$(date +%s) - start_time=${{ steps.start-subset.outputs.start_time }} - duration=$((end_time - start_time)) - echo "duration=${duration}" >> $GITHUB_OUTPUT - echo "## ⏱️ Subset-fonts 耗时: ${duration}秒" >> $GITHUB_STEP_SUMMARY - - # ============================================================ - # Job 2: Build (构建) + # Job 1: Build (构建) # ============================================================ build: runs-on: ubuntu-latest @@ -566,7 +406,7 @@ jobs: timeout-minutes: 5 permissions: contents: read - needs: [pre-check, subset-fonts, build, deploy-edgeone, deploy-upyun] + needs: [pre-check, build, deploy-edgeone, deploy-upyun] if: always() && needs.build.result == 'success' && needs.deploy-edgeone.result == 'success' && needs.deploy-upyun.result == 'success' steps: @@ -729,7 +569,6 @@ jobs: echo "| 阶段 | 状态 |" >> $GITHUB_STEP_SUMMARY echo "|------|------|" >> $GITHUB_STEP_SUMMARY echo "| Pre-check | ${{ needs.pre-check.result == 'success' && '✅ 成功' || needs.pre-check.result == 'skipped' && '⏭️ 跳过' || '❌ 失败' }} |" >> $GITHUB_STEP_SUMMARY - echo "| Subset-fonts | ${{ needs.subset-fonts.result == 'success' && '✅ 成功' || needs.subset-fonts.result == 'skipped' && '⏭️ 跳过' || '❌ 失败' }} |" >> $GITHUB_STEP_SUMMARY echo "| Build | ${{ needs.build.result == 'success' && '✅ 成功' || '❌ 失败' }} |" >> $GITHUB_STEP_SUMMARY echo "| Deploy EdgeOne | ${EDGEONE_DISPLAY} |" >> $GITHUB_STEP_SUMMARY echo "| Deploy UpYun | ${UPYUN_DISPLAY} |" >> $GITHUB_STEP_SUMMARY @@ -745,7 +584,7 @@ jobs: timeout-minutes: 3 permissions: contents: read - needs: [pre-check, subset-fonts, build, deploy-edgeone, deploy-upyun, finalize] + needs: [pre-check, build, deploy-edgeone, deploy-upyun, finalize] if: always() && (needs.pre-check.result == 'failure' || needs.build.result == 'failure' || needs.deploy-edgeone.result == 'failure' || needs.deploy-upyun.result == 'failure' || needs.finalize.result == 'failure') steps: diff --git a/scripts/slice_font.py b/scripts/slice_font.py deleted file mode 100644 index 9bd708cd..00000000 --- a/scripts/slice_font.py +++ /dev/null @@ -1,205 +0,0 @@ -#!/usr/bin/env python3 -""" -字体分片工具 -将中文字体按 Unicode 区块分成多个小文件,配合 CSS unicode-range 使用 -浏览器按需加载,大幅提升首屏加载速度 -""" - -import os -import re -from fontTools.ttLib import TTFont -from fontTools.subset import Subsetter, Options - -FONT_SRC = 'themes/Ying/static/font/zql-v2.woff2' -FONT_OUT_DIR = 'themes/Ying/static/font/slices' -CSS_OUT_PATH = 'themes/Ying/assets/css/font-slices.css' - -CJK_UNIFIED_START = 0x4E00 -CJK_UNIFIED_END = 0x9FFF - -def generate_slices(num_slices=15): - """ - 将 CJK 统一汉字区分成多个分片 - 常用字区域(开头)分得细一些,生僻字区域分得粗一些 - """ - total_chars = CJK_UNIFIED_END - CJK_UNIFIED_START + 1 - - slices = [] - - # 策略:前30%常用区分得细,后70%生僻字区分得粗 - common_ratio = 0.3 - common_end = int(CJK_UNIFIED_START + total_chars * common_ratio) - - common_slices = int(num_slices * 0.6) # 60%的分片给常用字 - rare_slices = num_slices - common_slices - - # 常用字区分片 - common_range = common_end - CJK_UNIFIED_START - common_per_slice = common_range // common_slices - - for i in range(common_slices): - start = CJK_UNIFIED_START + i * common_per_slice - end = CJK_UNIFIED_START + (i + 1) * common_per_slice - 1 - if i == common_slices - 1: - end = common_end - slices.append((start, end, f'common-{i+1:02d}')) - - # 生僻字区分片 - rare_range = CJK_UNIFIED_END - common_end - rare_per_slice = rare_range // rare_slices - - for i in range(rare_slices): - start = common_end + 1 + i * rare_per_slice - end = common_end + (i + 1) * rare_per_slice - if i == rare_slices - 1: - end = CJK_UNIFIED_END - slices.append((start, end, f'rare-{i+1:02d}')) - - return slices - -def get_all_chars(font): - """获取字体中所有可用的字符""" - chars = set() - cmap = font.getBestCmap() - for codepoint in cmap: - if CJK_UNIFIED_START <= codepoint <= CJK_UNIFIED_END: - chars.add(codepoint) - return chars - -def subset_font(font_path, output_path, codepoints): - """生成子集字体""" - font = TTFont(font_path) - - options = Options() - options.flavor = 'woff2' - - subsetter = Subsetter(options=options) - subsetter.populate(unicodes=codepoints) - subsetter.subset(font) - - font.save(output_path) - font.close() - -def generate_css(slices_info, font_dir_rel='/font/slices'): - """生成 CSS 文件""" - css_parts = [] - - # ASCII + 标点符号(第一优先级,总是加载) - css_parts.append("""/** - * 字体分片 - 按需加载 - * 浏览器会根据页面实际使用的字符自动加载对应的分片 - */ -@font-face { - font-family: 'zql'; - src: url('/font/zql-v2.woff2') format('woff2'); - font-display: swap; - unicode-range: U+0000-00FF, U+2000-206F, U+3000-303F, U+FF00-FFEF; -} -""") - - for name, codepoints, size in slices_info: - if not codepoints: - continue - - # 生成 unicode-range - ranges = [] - sorted_points = sorted(codepoints) - range_start = sorted_points[0] - prev = sorted_points[0] - - for cp in sorted_points[1:]: - if cp == prev + 1: - prev = cp - else: - if range_start == prev: - ranges.append(f'U+{range_start:04X}') - else: - ranges.append(f'U+{range_start:04X}-{prev:04X}') - range_start = cp - prev = cp - - if range_start == prev: - ranges.append(f'U+{range_start:04X}') - else: - ranges.append(f'U+{range_start:04X}-{prev:04X}') - - unicode_range = ', '.join(ranges) - - css_parts.append(f"""@font-face {{ - font-family: 'zql'; - src: url('{font_dir_rel}/zql-{name}.woff2') format('woff2'); - font-display: swap; - unicode-range: {unicode_range}; -}} -""") - - return '\n'.join(css_parts) - -def main(): - print('🚀 开始字体分片...') - print(f'📄 源字体: {FONT_SRC}') - - if not os.path.exists(FONT_SRC): - print(f'❌ 源字体不存在: {FONT_SRC}') - return - - os.makedirs(FONT_OUT_DIR, exist_ok=True) - - # 读取源字体,获取所有字符 - print('🔍 读取源字体...') - font = TTFont(FONT_SRC) - all_chars = get_all_chars(font) - font.close() - print(f'✅ 共 {len(all_chars)} 个 CJK 字符') - - # 生成分片方案 - slices = generate_slices(num_slices=12) - print(f'📦 分成 {len(slices)} 个分片') - - # 处理每个分片 - slices_info = [] - - for start, end, name in slices: - # 获取这个区间内字体实际包含的字符 - slice_chars = [cp for cp in all_chars if start <= cp <= end] - - if not slice_chars: - print(f' ⏭️ 分片 {name} (U+{start:04X}-U+{end:04X}): 无字符,跳过') - slices_info.append((name, [], 0)) - continue - - output_path = os.path.join(FONT_OUT_DIR, f'zql-{name}.woff2') - - print(f' ✂️ 分片 {name} (U+{start:04X}-U+{end:04X}): {len(slice_chars)} 字') - subset_font(FONT_SRC, output_path, slice_chars) - - size = os.path.getsize(output_path) - slices_info.append((name, slice_chars, size)) - print(f' → {size/1024:.1f} KB') - - # 生成 CSS - print('\n📝 生成 CSS 文件...') - css = generate_css(slices_info) - - with open(CSS_OUT_PATH, 'w', encoding='utf-8') as f: - f.write(css) - - print(f'✅ CSS 已保存: {CSS_OUT_PATH}') - - # 统计 - total_size = sum(size for _, _, size in slices_info) - original_size = os.path.getsize(FONT_SRC) - - print('\n' + '='*50) - print('📊 统计信息') - print('='*50) - print(f'原始字体大小: {original_size/1024:.1f} KB') - print(f'所有分片总大小: {total_size/1024:.1f} KB') - print(f'分片数量: {len(slices_info)}') - print() - print('预估首屏加载: 50-150 KB (常用字 3-5 个分片)') - print('='*50) - print('✅ 字体分片完成!') - -if __name__ == '__main__': - main() diff --git a/scripts/subset-font-safe.py b/scripts/subset-font-safe.py deleted file mode 100644 index 71c8adf3..00000000 --- a/scripts/subset-font-safe.py +++ /dev/null @@ -1,371 +0,0 @@ -#!/usr/bin/env python3 -""" -字体子集化脚本 - 安全版本 -从HTML和CSS文件中提取实际使用的字符,生成优化的子集字体 -""" - -import os -import re -import sys -from fontTools.ttLib import TTFont -from fontTools.subset import Subsetter, Options - -def extract_chars_from_files(directories, extensions=('.html', '.md', '.css')): - """从文件中提取使用的字符""" - chars = set() - - for directory in directories: - if not os.path.exists(directory): - print(f"⚠️ 目录不存在,跳过: {directory}") - continue - - print(f"🔍 扫描目录: {directory}") - - for root, dirs, files in os.walk(directory): - for file in files: - if any(file.endswith(ext) for ext in extensions): - filepath = os.path.join(root, file) - try: - with open(filepath, 'r', encoding='utf-8') as f: - content = f.read() - - # 提取中文字符 - chinese_chars = re.findall(r'[一-鿿]', content) - chars.update(chinese_chars) - - # 提取中文标点 - cjk_punct = re.findall(r'[ -〿＀-￯]', content) - chars.update(cjk_punct) - - # 提取英文和数字 - ascii_chars = re.findall(r'[a-zA-Z0-9]', content) - chars.update(ascii_chars) - - # 提取常用英文标点 - en_punct = set('!@#$%^&*()_+-=[]{}|;:,.<>?/\\`~"\'-') - chars.update(en_punct) - - except Exception as e: - print(f"⚠️ 读取文件失败 {filepath}: {e}") - - return chars - -def extract_chars_from_json(json_files): - """从JSON数据文件中提取字符(用于友链、朋友圈等动态内容)""" - chars = set() - - import json - - for json_file in json_files: - if not os.path.exists(json_file): - print(f"⚠️ JSON文件不存在,跳过: {json_file}") - continue - - print(f"🔍 扫描JSON文件: {json_file}") - - try: - with open(json_file, 'r', encoding='utf-8') as f: - data = json.load(f) - - # 将整个JSON转换为字符串,提取所有字符 - content = json.dumps(data, ensure_ascii=False) - - # 提取中文字符 - chinese_chars = re.findall(r'[一-鿿]', content) - chars.update(chinese_chars) - - # 提取中文标点 - cjk_punct = re.findall(r'[ -〿＀-￯]', content) - chars.update(cjk_punct) - - # 提取英文和数字 - ascii_chars = re.findall(r'[a-zA-Z0-9]', content) - chars.update(ascii_chars) - - print(f" ✅ 从 {json_file} 提取了 {len(chinese_chars)} 个中文字符") - - except Exception as e: - print(f"⚠️ 读取JSON文件失败 {json_file}: {e}") - - return chars - -def fetch_and_extract_chars_from_api(urls): - """从API接口获取数据并提取字符(用于动态内容如友链、朋友圈)""" - chars = set() - - try: - import urllib.request - import json - import ssl - - # 创建不验证SSL的上下文(用于内网或自签名证书) - context = ssl._create_unverified_context() - - for url in urls: - print(f"🌐 尝试从API获取: {url}") - try: - req = urllib.request.Request( - url, - headers={ - 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' - } - ) - - with urllib.request.urlopen(req, context=context, timeout=15) as response: - data = response.read().decode('utf-8') - - try: - json_data = json.loads(data) - content = json.dumps(json_data, ensure_ascii=False) - - # 提取中文字符 - chinese_chars = re.findall(r'[一-鿿]', content) - chars.update(chinese_chars) - - print(f" ✅ 从API提取了 {len(chinese_chars)} 个中文字符") - except json.JSONDecodeError: - print(f" ⚠️ 响应不是有效的JSON,跳过") - - except urllib.error.URLError as e: - print(f" ❌ API请求失败: {e}") - except Exception as e: - print(f" ❌ 处理API响应失败: {e}") - - except ImportError: - print("⚠️ urllib不可用,跳过API扫描") - - return chars - -def subset_font(input_font_path, output_font_path, chars): - """生成子集字体""" - print(f"\n📦 加载字体: {input_font_path}") - - # 检查输入文件 - if not os.path.exists(input_font_path): - print(f"❌ 字体文件不存在: {input_font_path}") - return False - - # 加载原始字体 - try: - font = TTFont(input_font_path) - except Exception as e: - print(f"❌ 加载字体失败: {e}") - return False - - # 获取原始字符数 - cmap = font.getBestCmap() - original_count = len(cmap) if cmap else 0 - original_size = os.path.getsize(input_font_path) - - print(f"📊 原始字体信息:") - print(f" 字符数: {original_count}") - print(f" 文件大小: {original_size / 1024:.1f} KB") - - # 配置子集化选项 - options = Options() - if output_font_path.endswith('.woff2'): - options.flavor = 'woff2' - elif output_font_path.endswith('.woff'): - options.flavor = 'woff' - - options.desubroutinize = True - options.layout_features = ['kern', 'liga'] # 保留基本的排版特性 - - # 创建子集化器 - subsetter = Subsetter(options=options) - - # 填充字符集 - char_text = ''.join(chars) - print(f"\n✂️ 正在生成子集字体...") - print(f" 提取的字符数: {len(chars)}") - - try: - subsetter.populate(text=char_text) - subsetter.subset(font) - except Exception as e: - print(f"❌ 子集化失败: {e}") - return False - - # 保存子集字体 - try: - font.save(output_font_path) - except Exception as e: - print(f"❌ 保存字体失败: {e}") - return False - - # 获取子集信息 - subset_size = os.path.getsize(output_font_path) - subset_font_obj = TTFont(output_font_path) - subset_cmap = subset_font_obj.getBestCmap() - subset_count = len(subset_cmap) if subset_cmap else 0 - - # 计算优化效果 - reduction = original_size - subset_size - percentage = (reduction / original_size) * 100 if original_size > 0 else 0 - - print(f"\n✅ 子集化完成!") - print(f"📊 优化结果:") - print(f" 子集字符数: {subset_count}") - print(f" 子集文件大小: {subset_size / 1024:.1f} KB") - print(f" 减少: {reduction / 1024:.1f} KB ({percentage:.1f}%)") - - # 验证是否真的优化了 - if subset_size >= original_size: - print(f"\n⚠️ 警告: 子集字体反而变大了!") - print(f" 原因可能是提取了太多字符") - print(f" 建议检查扫描范围或使用更小的字符集") - return False - - return True - -def main(): - print("🔤 字体子集化工具(安全版本)") - print("=" * 50) - - # 配置路径 - font_dir = "themes/Ying/static/font" - input_font = os.path.join(font_dir, "zql-v2.woff2") - chars_file = os.path.join(font_dir, "used_chars.txt") - - # 检查输入文件 - if not os.path.exists(input_font): - print(f"❌ 字体文件不存在: {input_font}") - print(f" 请确保在博客根目录运行此脚本") - sys.exit(1) - - # 读取现有的字符列表(如果存在) - existing_chars = set() - if os.path.exists(chars_file): - with open(chars_file, 'r', encoding='utf-8') as f: - content = f.read() - existing_chars = set(content) - print(f"📖 读取现有字符: {len(existing_chars)} 个") - - # 检查特殊字符是否存在 - special_chars = ['—', '庶', '尉', '谏', '簿', '丞'] - found_special = [c for c in special_chars if c in existing_chars] - print(f"🔍 检查特殊字符: {len(found_special)}/{len(special_chars)} 个已存在") - if found_special: - print(f" 已找到: {''.join(found_special)}") - else: - print(f"⚠️ 现有字符文件不存在,将创建新文件") - - # 扫描目录 - scan_dirs = ["content", "layouts"] - - # 检查public目录是否存在 - if os.path.exists("public"): - scan_dirs.append("public") - print(f"✅ 找到public目录,将扫描构建后的HTML") - else: - print(f"⚠️ 未找到public目录,请先运行: hugo --destination=public") - print(f" 将只扫描content和layouts目录") - - print(f"\n🔍 扫描目录: {', '.join(scan_dirs)}") - - # 提取字符(静态文件) - new_chars = extract_chars_from_files(scan_dirs) - print(f"\n📝 从文件中提取了 {len(new_chars)} 个唯一字符") - - # 扫描动态数据文件(友链、朋友圈等) - json_data_files = [ - "themes/Ying/static/json/link_lite.json", # 友链数据 - "themes/Ying/static/json/friend_circle_data.json", # 朋友圈数据 - "themes/Ying/data/links.yaml", # 友链配置 - "themes/Ying/data/links.yml", # 友链配置(备用) - ] - - print(f"\n{'='*50}") - print(f"🔄 扫描动态数据文件(友链、朋友圈等)") - dynamic_chars = extract_chars_from_json(json_data_files) - print(f"📝 从动态数据中提取了 {len(dynamic_chars)} 个唯一字符") - - # 从API获取动态数据(用于CI环境) - api_urls = [ - "https://api.usj.cc/api/links?all=1", # 友链列表 - "https://api.usj.cc/api/feeds", # 订阅源列表 - "https://api.usj.cc/api/articles?limit=50", # 朋友圈文章 - ] - - # 如果有环境变量,追加额外的API地址 - if os.environ.get('FONT_SUBSET_API_URLS'): - extra_urls = os.environ['FONT_SUBSET_API_URLS'].split(',') - api_urls.extend(extra_urls) - print(f"\n{'='*50}") - print(f"🌐 从环境变量获取额外API地址") - print(f" 额外URLs: {', '.join(extra_urls)}") - - print(f"\n{'='*50}") - print(f"🌐 扫描远程API(友链、朋友圈等)") - print(f" 共 {len(api_urls)} 个API端点") - - api_chars = fetch_and_extract_chars_from_api(api_urls) - print(f"📝 从API提取了 {len(api_chars)} 个唯一字符") - dynamic_chars = dynamic_chars | api_chars - - # 合并静态和动态字符 - new_chars = new_chars | dynamic_chars - print(f"\n📊 合并后总字符数: {len(new_chars)}") - - # 合并字符(保留现有字符 + 新提取的字符) - all_chars = existing_chars | new_chars - print(f"📊 合并后字符数: {len(all_chars)}") - - # 验证特殊字符是否在合并后的字符集中 - special_chars = ['—', '庶', '尉', '谏', '簿', '丞'] - missing_special = [c for c in special_chars if c not in all_chars] - if missing_special: - print(f"⚠️ 警告:以下特殊字符不在合并后的字符集中: {''.join(missing_special)}") - print(f" 将强制添加这些字符...") - all_chars.update(special_chars) - else: - print(f"✅ 所有特殊字符都已保留在字符集中") - - if len(all_chars) == 0: - print("❌ 未找到任何字符,请检查扫描目录") - sys.exit(1) - - # 保存合并后的字符列表 - with open(chars_file, 'w', encoding='utf-8') as f: - sorted_chars = sorted(all_chars) - f.write(''.join(sorted_chars)) - print(f"💾 字符列表已保存到: {chars_file}") - - # 显示部分字符(用于验证) - sample_chars = sorted(list(all_chars))[:50] - print(f"📋 前50个字符: {''.join(sample_chars)}") - - # 生成woff2子集字体 - output_woff2 = os.path.join(font_dir, "zql-v2-subset.woff2") - print(f"\n{'='*50}") - print(f"🎯 生成 woff2 子集字体...") - success_woff2 = subset_font(input_font, output_woff2, all_chars) - - # 生成woff子集字体 - output_woff = os.path.join(font_dir, "zql-v2-subset.woff") - print(f"\n{'='*50}") - print(f"🎯 生成 woff 子集字体...") - success_woff = subset_font(input_font, output_woff, all_chars) - - # 总结 - print(f"\n{'='*50}") - if success_woff2 and success_woff: - print(f"🎉 所有子集字体生成成功!") - - # 显示最终文件大小 - print(f"\n📂 最终文件:") - for f in [output_woff2, output_woff]: - if os.path.exists(f): - size = os.path.getsize(f) - print(f" {os.path.basename(f)}: {size / 1024:.1f} KB") - - print(f"\n✅ 下一步:") - print(f" 1. 更新CSS字体声明(将zql-v2.woff2改为zql-v2-subset.woff2)") - print(f" 2. 重新构建Hugo: hugo server -D") - print(f" 3. 测试字体显示是否正常") - else: - print(f"⚠️ 部分字体生成失败或未优化") - print(f" 建议使用fallback方案(保留原始字体)") - -if __name__ == "__main__": - main() diff --git a/themes/Ying/layouts/partials/head.html b/themes/Ying/layouts/partials/head.html index 09f8b3c0..f8b3389f 100644 --- a/themes/Ying/layouts/partials/head.html +++ b/themes/Ying/layouts/partials/head.html @@ -75,8 +75,7 @@ })(); - - + {{ $echartsHead := resources.Get "js/libs/echarts.min.js" | resources.Fingerprint }} diff --git a/themes/Ying/static/font/slices/zql-common-01.woff2 b/themes/Ying/static/font/slices/zql-common-01.woff2 deleted file mode 100644 index 1a0bba27..00000000 Binary files a/themes/Ying/static/font/slices/zql-common-01.woff2 and /dev/null differ diff --git a/themes/Ying/static/font/slices/zql-common-02.woff2 b/themes/Ying/static/font/slices/zql-common-02.woff2 deleted file mode 100644 index 4576e5e0..00000000 Binary files a/themes/Ying/static/font/slices/zql-common-02.woff2 and /dev/null differ diff --git a/themes/Ying/static/font/slices/zql-common-03.woff2 b/themes/Ying/static/font/slices/zql-common-03.woff2 deleted file mode 100644 index c6405f06..00000000 Binary files a/themes/Ying/static/font/slices/zql-common-03.woff2 and /dev/null differ diff --git a/themes/Ying/static/font/slices/zql-common-04.woff2 b/themes/Ying/static/font/slices/zql-common-04.woff2 deleted file mode 100644 index 6a2a858f..00000000 Binary files a/themes/Ying/static/font/slices/zql-common-04.woff2 and /dev/null differ diff --git a/themes/Ying/static/font/slices/zql-common-05.woff2 b/themes/Ying/static/font/slices/zql-common-05.woff2 deleted file mode 100644 index 780bd492..00000000 Binary files a/themes/Ying/static/font/slices/zql-common-05.woff2 and /dev/null differ diff --git a/themes/Ying/static/font/slices/zql-common-06.woff2 b/themes/Ying/static/font/slices/zql-common-06.woff2 deleted file mode 100644 index dcdc8717..00000000 Binary files a/themes/Ying/static/font/slices/zql-common-06.woff2 and /dev/null differ diff --git a/themes/Ying/static/font/slices/zql-common-07.woff2 b/themes/Ying/static/font/slices/zql-common-07.woff2 deleted file mode 100644 index 9d95412f..00000000 Binary files a/themes/Ying/static/font/slices/zql-common-07.woff2 and /dev/null differ diff --git a/themes/Ying/static/font/slices/zql-rare-01.woff2 b/themes/Ying/static/font/slices/zql-rare-01.woff2 deleted file mode 100644 index 0450703c..00000000 Binary files a/themes/Ying/static/font/slices/zql-rare-01.woff2 and /dev/null differ diff --git a/themes/Ying/static/font/slices/zql-rare-02.woff2 b/themes/Ying/static/font/slices/zql-rare-02.woff2 deleted file mode 100644 index 995c42e4..00000000 Binary files a/themes/Ying/static/font/slices/zql-rare-02.woff2 and /dev/null differ diff --git a/themes/Ying/static/font/slices/zql-rare-03.woff2 b/themes/Ying/static/font/slices/zql-rare-03.woff2 deleted file mode 100644 index ef95c021..00000000 Binary files a/themes/Ying/static/font/slices/zql-rare-03.woff2 and /dev/null differ diff --git a/themes/Ying/static/font/slices/zql-rare-04.woff2 b/themes/Ying/static/font/slices/zql-rare-04.woff2 deleted file mode 100644 index 4be4294f..00000000 Binary files a/themes/Ying/static/font/slices/zql-rare-04.woff2 and /dev/null differ diff --git a/themes/Ying/static/font/slices/zql-rare-05.woff2 b/themes/Ying/static/font/slices/zql-rare-05.woff2 deleted file mode 100644 index 893bf8d0..00000000 Binary files a/themes/Ying/static/font/slices/zql-rare-05.woff2 and /dev/null differ diff --git a/themes/Ying/static/font/used_chars.txt b/themes/Ying/static/font/used_chars.txt deleted file mode 100644 index 82ea5c7c..00000000 --- a/themes/Ying/static/font/used_chars.txt +++ /dev/null @@ -1,2 +0,0 @@ - - !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~—、。〇《》「」【】一丁七万三上下不与丑专且世丘业东丝丞丢两严丧个中丰串临为主丽举久么义之乌乎乏乐乒乓乘乙九也习乡书买乱了予争事二于亏云互五井亚些亡交亦产享京亮亲人亿什仁仅今介仍从仓仔他仗付仙代令以仪们仰件价任份仿企伏伐休众优伙会伞传伤伦伪伫估伴伸似但位低住佐佑体何余佛作你佩佬佳使例供依侠侣侧便促俄俗保信俩俭修俱倍倒候借倦债值假偎偏做停健偶偷偿傅傍储催傲傻像僵僻儿兀允元兄充兆先光克免兑兔党兜入全八公六共关兴其具典养兼冀内册再冒冗写军农冠冤冥冬冰冲决况冷冻净准凉凌减凑凛几凡凭凳凶出击函刀刃分切划列刘则刚创初删判利别刮到制刷券刹刺刻剁剂前剔剖剥剧剩剪副割力劝办功加务劣动助努励劲劳势勃勇勉勒勤勾勿包匆匍匐匕化北匙匠匹区医匾匿十千升午半华协卓单卖南博占卡卢卦卧卫卯印危即却卷卸厂厅历厉压厌厕厘厚原厢厨去县参又及友双反发叒叔叕取受变叙叠口古句另叨只叫召可台史右叶号司叹吃各合吉同名后吐向吓吗吞吟否吧吨含听启吱吴吵吸吹吾呀呆呈告呕呗员呛呢呦周味呵呼命咄咋和咐咕咙咨咩咪咬咱咳咸咽哀品哄哆哇哈响哒哔哗哟哥哦哨哩哪哭哲唉唐唤唬售唯唱唾商啊啥啦啪啸喂善喉喊喜喝喧喵喷喻嗓嗖嗜嗡嗦嗨嗯嗽嘀嘎嘛嘱嘲嘴嘿器噩噪噶噼嚣囊四回因团园困围固国图圆圈土圣在地圳场圾址均坊坎坏坐坑块坚坛坞坟坠坡坦坷垃垄型垢垮埋城埔域培基堂堆堑堡堤堪堵塌塑塔塘塞填境墓墙增墨壁士壮声壳壶壹处备复夏夕外多夜够大天太夫夯失头夸夹夺奇奈奋奏契奔奖套奢女奶奸她好如妄妇妈妒妙妥妨妹妻始姐姑姓委姨姿威娃娘娜娱婆婚媒媚嫁嫉嫌嫖嫩子孔孕字存季孤学孩宁它宅守安宋完宏宗官定宛宜宝实宠审客宣室宫宰害宵家容宽宾宿寂寄密寇富寒寓寝察寸对寺寻导寿封射将尉尊小少尔尖尘尚尝尤尬就尴尸尺尼尽尾尿局屁层居屈届屋屎屏屑展属屠屯山岁岂岔岖岗岛岸峨峰崎崩嵌巍川州工左巧巨差己已巴巷巾币市布师希帖帘带帧席帮常帽幅幕幢干平年并幸幺幻幼幽广庄庆床序库应底店庙废度座庭庶康庸延建廿开异弃弄弈弊式引弟张弥弯弱弹强归当录形彩彰影彻彼往征径待很律徐徒徕得徙御循微德心必忆忍忐忑志忘忙忧快念忽怀态怎怒怔怕思怠怡急怦性怨怪怯总怼恋恍恐恒恢恨恩恭息恰恶恼悄悉悍悔悖悚悟患悦您悬悲情惊惑惕惜惠惧惨惫惬惯惰想惹愁愈愉意愚感愣愤愧愿慎慕慢慨憋憧憨憬憾懂懈懑懒懦懵戏成我或战戚截戳戴户戾房所扇手才扎扑扒打扔托扣执扩扫扬扰扳批扼找承技抄抉把抑抓投抖抗折抛抢护报抬抱抵抹抽担拆拉拌拍拒拓拔拖拘拙招拜拟拥拦拧拨择括拳拷拼拽拾拿持挂指按挑挖挚挡挣挤挥挨挫振挺捆捉捏捕捞损捡换捣捧据捷掂授掉掌掐排掘探接控推掩措掰掺描提插握揣揭搁搏搓搜搞搬搭携摁摄摆摇摊摔摘摧摩摸撑撒撞撤播撰撸撼擀擅操擎擦攀攒支收改攻放政故效敌敏救教敛敞敢散敬数敲整敷文斐斑斗料斜斤斥斧斩断斯新方施旁旅旋族旗无既日旦旧旨早旬时旷昂明易星映春昨是昵昼显晃晒晕晚晦晨普景晰晶智暂暇暑暖暗暮暴曦曲更曾替最月有朋服朗望朝期朦木未末本术朴朵机杀杂权杆李杏材村杜束杠条来杨杭杯杰東松板极构析枕林枚果枝枢枪枫枯架某柒染柔柜查柯柳柴柿栀标栈栋栏树栓校样核根格栽桂桃框案桌桑档桥桩桶梁梦梯械梳检棉棒棘棚森棵椅植椒楔楚楼概榜槛槽樊模横橙欠次欢欣欧欲欺款歉歌止正此步武歪死殉殊残段殿毁毅毋母每毒比毕毛毫氏民气氛氧水永汁求汇汉汕汗江池污汤汪汲沉沙沟没沦沮河油治沾沿泄泉泊泌法泛泡波泣泥注泪泯泰泳泽洁洋洒洗洛洞津洪洲活派流浅浇测济浏浑浓浩浪浮浴海浸涂消涉涌润涨涩涯液涵淀淋淘淡淫深淳混淹添清渐渗渠渡渣温港渲渴游湖湘湾湿溃源溢溯滋滑滙滚滞满滤滥滩滴漂漏漓演漫漱潘潜潦潭潮澎澡澳激瀑火灭灯灰灵灼灾炉炎炒炕炖炫炮炸点炼烁烂烈烟烤烦烧烫热烽焉焕焖無焦然煌煎照煲熄熊熙熟熬熵燕燥爆爬爱父爷爸爽片版牌牙牛物牲牵特牺犊犯状犹狂狐狗狠独狱狼猛猜猝猪猫献猴玄率玉王玛玩环现玻珍珠班球理琐琢瑞璃瓜瓢瓦瓶甚甜生甥用甩田由甲申电男画畅界留略番畴疆疏疑疗疙疫疮疯疲疼疾病症痍痒痕痘痛痪瘦瘩瘫瘾癌登白百的皇皑皮盆盈益监盒盖盗盘盛盟目盯盲直相盼盾省眉看真眠眨眩眯眶眼着睁睐睛睡督睹睿瞅瞎瞥瞬矛知矩短石矿码砌砍研砖砥破砺础硅硌硕硬确碌碍碎碑碗碟碰磁磕磨磷示礼社祈祖祝神票祭祸禁福离秀私秉秋种科秒租积称移稀程稍税稚稳稿究穷空穿突窃窄窍窒窗窝立站竞竟章童端竹笑笔符笨第笼等筋筑答策筛筷筹签简算管箭箱篇篝篡簧簿籍米类粉粒粗粘粹粽精糊糕糖糙糟系素索紧累繁纠红纤约级纪纭纯纱纲纳纷纸线练组细织终绍经绑绒结绕绘给绚络绝统继绩绪续绰绳维绷综绿缀缆缓编缘缚缝缠缩缭缴缺网罕罗罢罩罪置署羊美羞羡群羽翁翠翻翼耀老考者而耍耐耗耦耳耸耻耽聊职联聘聚聪聿肃肉肋肌肖肚肝肠股肢肤肥肩肯育肺肾肿胀背胖胚胜胞胡胧胶胸胺能脂脆脉脏脑脚脱脸脾腊腔腕腰腹腺腻腾腿膀膏膜膝膨臂臃自臭至致舅舍舒舞舟航般良艰色艳艾节芒芙芦芬芯花芳芹苍苏苗苛若苦英苹茂范茄茫茶草荐荒荡荣荤荫药荼莓莘莫获菌菜菩菲萊萌萎营萦萨落著葛葩葫葵蒂蒙蒜蒸蓝蔷蔽蕉蕴薄薅薇薛薪藏虎虐虑虚虫虹虽虾蚁蚂蚊蛇蛊蛋蛙蛮蛳蛾蜂蜕蜜蝇螃融螺蟹血行衍衔街衡衣补表衩衫衰衷袁袋袍被袭裁裂装裕裙裤裸裹褒褪襄西要覆见观规视览觉角解触言警计订认讨让讪训议讯记讲许论讽设访诀证评识诈诉诊词译试诗诙诚话诞诠询该详诧语误说诶请诸诺读课谁调谅谈谊谋谏谐谓谔谛谢谨谬谱谴谷豁豆象豫貌贝负贡财责败账货质贩贪贫贬购贯贴贵费贺贼资赋赏赖赘赚赛赞赢走赴赵赶起趁超越趋趟趣足跃跌跑距跟跨跪路跳践踏踢踩踪踵蹈蹊蹭蹲躁身躲躺车轨转轮软轰轻载较辅辆辈辉辐辑输辗辙辛辜辞辟辣辨辱边达迁迄迅过迎运近返还这进远违连迟迪迫迭述迷迹追退送适逃逆选逊透逐递途逗通逛逝速造逢進逻逼遇遍道遗遛遣遥遭遮遵避邀邃那邪邮邹邻郁郎郑部都鄂配酒酬酯酱酵酷酸醉醒采釉释里重野量金鉴针钉钓钛钟钢钥钩钮钱钵钻铁铜铠铭银铺链销锁锂锅锐错锚锢键锯锻镇镊镖镜镳长门闪闭问闲间闷闸闹闻阀阁阅阈阉阔阖队阱防阳阴阵阶阻阿附际陆陈陋陌降限陕陡院除险陪陶陷隆随隐隔隙障难雄雅集雏雕雨雪零雷雾需震霉霓霜霞露霸青静非靠面革鞋鞭韧韩韭音韵页顶项顺须顾顿预领颇颈频颗题颚颜额颤风飞食餐饥饭饮饰饱饲饺饼饿馅馆馈馋首香馨马驯驰驱驶驻驾驿骂骆验骑骗骚骤骨髌高鬼魂魅魔鱼鲜鲤鲫鳖鸟鸡鸣鸦鸭鸽鸿鹤麦麻黄黏黑默黛黢鼓鼠鼻齐齿龄龈龘龙鿿!()+,:;?`|~ノ \ No newline at end of file