diff --git a/PDF_FIX_DOCUMENTATION.md b/PDF_FIX_DOCUMENTATION.md new file mode 100644 index 0000000..623e0cc --- /dev/null +++ b/PDF_FIX_DOCUMENTATION.md @@ -0,0 +1,103 @@ +# PDF乱码问题修复说明 / PDF Garbled Text Fix + +## 问题描述 (Problem Description) + +PDF导出时中文字符显示为乱码,因为jsPDF默认使用的helvetica字体不支持中文字符。 + +When exporting PDFs, Chinese characters appear garbled because jsPDF's default helvetica font doesn't support Chinese characters. + +## 解决方案 (Solution) + +添加了对中文字体(Noto Sans SC)的支持,通过以下方式加载: + +Added support for Chinese font (Noto Sans SC) by loading it through: + +1. **主CDN源** (Primary CDN): jsDelivr +2. **备用CDN源** (Fallback CDN): unpkg +3. **本地字体文件** (Local font file): `./fonts/NotoSansSC-normal.js` + +## 技术实现 (Technical Implementation) + +### 字体加载机制 (Font Loading Mechanism) + +```javascript +// 1. 尝试从多个CDN源加载字体 +// Try loading font from multiple CDN sources +loadChineseFont(pdf) -> tryLoadFont(url, pdf) + +// 2. 成功加载后注册字体到jsPDF +// Register font with jsPDF after successful loading +pdf.addFileToVFS('NotoSansSC-Regular.ttf', window.NotoSansSCRegular); +pdf.addFont('NotoSansSC-Regular.ttf', 'NotoSansSC', 'normal'); + +// 3. 使用中文字体渲染文本 +// Use Chinese font for text rendering +pdf.setFont('NotoSansSC', 'normal'); +``` + +### 特性 (Features) + +- ✅ 自动字体加载 (Automatic font loading) +- ✅ 多源备份 (Multiple fallback sources) +- ✅ 超时保护 (Timeout protection - 5 seconds) +- ✅ 优雅降级 (Graceful degradation to helvetica) +- ✅ 可选本地托管 (Optional local hosting) + +## 使用方法 (Usage) + +### 默认使用 (Default Usage) + +无需配置,点击"Download PDF"按钮即可自动使用中文字体。 + +No configuration needed, just click "Download PDF" button to automatically use Chinese font. + +### 本地托管字体 (Local Font Hosting) + +如需更高可靠性,可以本地托管字体文件: + +For better reliability, you can host the font file locally: + +1. 下载并转换字体 (Download and convert font) +2. 保存到 `fonts/NotoSansSC-normal.js` +3. 详见 `fonts/README.md` + +See `fonts/README.md` for detailed instructions. + +## 测试 (Testing) + +在支持中文的浏览器中: + +In a browser with Chinese support: + +1. 访问应用 (Visit the application) +2. 加载示例数据或上传Excel (Load sample data or upload Excel) +3. 点击"Download PDF" (Click "Download PDF") +4. 打开PDF检查中文显示 (Open PDF and verify Chinese text) + +## 兼容性 (Compatibility) + +- ✅ 支持所有现代浏览器 (All modern browsers) +- ✅ 支持离线使用(使用本地字体)(Offline use with local font) +- ✅ 向后兼容(失败时使用helvetica)(Backward compatible with helvetica fallback) + +## 故障排除 (Troubleshooting) + +### 中文仍然乱码 (Chinese still garbled) + +1. 检查浏览器控制台错误 (Check browser console for errors) +2. 确认网络可访问CDN (Ensure CDN is accessible) +3. 尝试使用本地字体文件 (Try using local font file) + +### 字体加载缓慢 (Font loading slow) + +1. 字体文件较大 (3-8 MB),首次加载需要时间 +2. 建议使用本地托管以提高速度 +3. 可考虑字体子集化以减小文件大小 + +Font file is large (3-8 MB), initial loading takes time. Consider local hosting or font subsetting. + +## 相关文件 (Related Files) + +- `js/app.js` - PDF生成逻辑 (PDF generation logic) +- `fonts/README.md` - 字体配置说明 (Font setup guide) +- `fonts/NotoSansSC-normal.js` - 本地字体占位符 (Local font placeholder) diff --git a/fonts/NotoSansSC-normal.js b/fonts/NotoSansSC-normal.js new file mode 100644 index 0000000..78acb4f --- /dev/null +++ b/fonts/NotoSansSC-normal.js @@ -0,0 +1,19 @@ +/** + * Placeholder for Chinese Font - Noto Sans SC + * + * This is a placeholder file. To enable local Chinese font support: + * + * 1. Download Noto Sans SC Regular from Google Fonts + * 2. Convert it using jsPDF font converter: + * https://rawgit.com/MrRio/jsPDF/master/fontconverter/fontconverter.html + * 3. Replace this file with the generated output + * + * See README.md in this directory for detailed instructions. + */ + +// Uncomment and replace with actual font data: +// (function() { +// window.NotoSansSCRegular = "base64_encoded_font_data_here..."; +// })(); + +console.warn('Local Chinese font file is a placeholder. Using CDN or helvetica fallback.'); diff --git a/fonts/README.md b/fonts/README.md new file mode 100644 index 0000000..0a58b7a --- /dev/null +++ b/fonts/README.md @@ -0,0 +1,54 @@ +# Chinese Font for PDF Generation + +This directory is intended to contain the Chinese font file for PDF generation. + +## Quick Setup (Recommended) + +The application will automatically try to load the Chinese font from CDN sources. No manual setup is required for basic usage. + +## Local Font Setup (Optional) + +If you want to host the font file locally for better reliability: + +1. Download Noto Sans SC Regular font from [Google Fonts](https://fonts.google.com/noto/specimen/Noto+Sans+SC) + +2. Convert the font to jsPDF format using the font converter: + - Visit: https://peckconsulting.s3.amazonaws.com/fontconverter/fontconverter.html + - Upload the `NotoSansSC-Regular.ttf` file + - Set font name to: `NotoSansSC` + - Set style to: `normal` + - Click "Create" and download the generated `.js` file + +3. Save the generated file as `NotoSansSC-normal.js` in this directory + +4. The application will automatically use the local font file as a fallback + +## Font Subsetting (Advanced) + +To reduce file size, you can create a subset containing only the characters you need: + +1. Use a font subsetting tool like [fonttools](https://github.com/fonttools/fonttools): + ```bash + pip install fonttools + pyftsubset NotoSansSC-Regular.ttf \ + --unicodes="U+4E00-U+9FFF" \ # Common Chinese characters + --output-file="NotoSansSC-Regular-subset.ttf" + ``` + +2. Convert the subset font using the jsPDF font converter + +3. Replace `NotoSansSC-normal.js` with the subset version + +## Troubleshooting + +- If Chinese characters appear garbled in PDFs, check browser console for font loading errors +- Ensure the font file is properly converted for jsPDF (not a regular TTF file) +- The font file should define `window.NotoSansSCRegular` variable +- Font file size is typically 3-8 MB for full font, or 1-3 MB for subset + +## CDN Sources + +The application automatically tries these CDN sources in order: +1. jsDelivr (primary) +2. unpkg (fallback) +3. Local file (if available) diff --git a/js/app.js b/js/app.js index 1127d9f..d08e058 100644 --- a/js/app.js +++ b/js/app.js @@ -306,6 +306,85 @@ // ===================== PDF Generation ===================== + let chineseFontLoaded = false; + let fontName = 'helvetica'; + + /** + * Load Chinese font for PDF generation + * Tries multiple CDN sources for reliability + */ + async function loadChineseFont(pdf) { + if (chineseFontLoaded) return; + + // List of CDN URLs to try (in order of preference) + const fontUrls = [ + 'https://cdn.jsdelivr.net/gh/sunnylqm/jsPDF@master/dist/fonts/NotoSansSC-Regular-normal.js', + 'https://unpkg.com/jspdf-font-noto-sans-sc@1.0.0/NotoSansSC-Regular-normal.js', + './fonts/NotoSansSC-normal.js' // Local fallback + ]; + + for (const fontUrl of fontUrls) { + try { + const success = await tryLoadFont(fontUrl, pdf); + if (success) { + console.log('Chinese font loaded successfully from:', fontUrl); + return; + } + } catch (err) { + console.warn('Failed to load font from:', fontUrl, err); + } + } + + console.warn('All font sources failed, using fallback helvetica font'); + chineseFontLoaded = true; // Prevent retrying + } + + /** + * Try to load font from a specific URL + */ + function tryLoadFont(url, pdf) { + return new Promise((resolve) => { + const script = document.createElement('script'); + script.src = url; + + const timeout = setTimeout(() => { + script.remove(); + resolve(false); + }, 5000); // 5 second timeout + + script.onload = () => { + clearTimeout(timeout); + try { + if (window.NotoSansSCRegular) { + pdf.addFileToVFS('NotoSansSC-Regular.ttf', window.NotoSansSCRegular); + pdf.addFont('NotoSansSC-Regular.ttf', 'NotoSansSC', 'normal'); + // Note: Using same font for bold style. For true bold, load separate bold font file. + pdf.addFont('NotoSansSC-Regular.ttf', 'NotoSansSC', 'bold'); + fontName = 'NotoSansSC'; + chineseFontLoaded = true; + script.remove(); + resolve(true); + } else { + script.remove(); + resolve(false); + } + } catch (err) { + console.warn('Font registration failed:', err); + script.remove(); + resolve(false); + } + }; + + script.onerror = () => { + clearTimeout(timeout); + script.remove(); + resolve(false); + }; + + document.head.appendChild(script); + }); + } + async function handleDownloadPDF() { if (radarItems.length === 0) { alert('No data to export yet. Load Excel or sample data first.'); @@ -339,6 +418,9 @@ const margin = 15; const contentWidth = pageWidth - margin * 2; + // Load Chinese font + await loadChineseFont(pdf); + const sections = RadarData.QUADRANTS; for (let si = 0; si < sections.length; si++) { @@ -358,7 +440,7 @@ let y = margin + chartSize + 8; const rgb = hexToRgb(color); pdf.setFontSize(18); - pdf.setFont('helvetica', 'bold'); + pdf.setFont(fontName, 'bold'); pdf.setTextColor(rgb.r, rgb.g, rgb.b); pdf.text(sectionName, margin, y); y += 3; @@ -385,7 +467,7 @@ // Ring title pdf.setFontSize(14); - pdf.setFont('helvetica', 'bold'); + pdf.setFont(fontName, 'bold'); pdf.setTextColor(80, 80, 80); pdf.text(ring, margin, y); y += 2; @@ -411,7 +493,7 @@ pdf.setFillColor(rgb.r, rgb.g, rgb.b); pdf.circle(margin + 3, y - 1.5, 3, 'F'); pdf.setFontSize(7); - pdf.setFont('helvetica', 'bold'); + pdf.setFont(fontName, 'bold'); pdf.setTextColor(255, 255, 255); const idStr = String(item.id); const idWidth = pdf.getTextWidth(idStr); @@ -419,7 +501,7 @@ // Item name pdf.setFontSize(11); - pdf.setFont('helvetica', 'bold'); + pdf.setFont(fontName, 'bold'); pdf.setTextColor(50, 50, 50); let nameStr = item.name; if (item.movement === 'new') nameStr += ' ▲ New'; @@ -429,7 +511,7 @@ // Description pdf.setFontSize(9); - pdf.setFont('helvetica', 'normal'); + pdf.setFont(fontName, 'normal'); pdf.setTextColor(80, 80, 80); descLines.forEach((line) => { if (y > pageHeight - margin) {