Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 103 additions & 0 deletions PDF_FIX_DOCUMENTATION.md
Original file line number Diff line number Diff line change
@@ -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)
19 changes: 19 additions & 0 deletions fonts/NotoSansSC-normal.js
Original file line number Diff line number Diff line change
@@ -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.');
54 changes: 54 additions & 0 deletions fonts/README.md
Original file line number Diff line number Diff line change
@@ -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)
92 changes: 87 additions & 5 deletions js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.');
Expand Down Expand Up @@ -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++) {
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -411,15 +493,15 @@
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);
pdf.text(idStr, margin + 3 - idWidth / 2, y - 0.5);

// 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';
Expand All @@ -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) {
Expand Down