Add an "Export to PDF" Button and Define the Font for Exporting to PDF
This commit is contained in:
@@ -10,6 +10,7 @@ import html
|
|||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
import base64
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Dict, List
|
from typing import Any, Dict, List
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
@@ -74,6 +75,7 @@ class HTMLRenderer:
|
|||||||
self.toc_rendered = False
|
self.toc_rendered = False
|
||||||
self.hero_kpi_signature: tuple | None = None
|
self.hero_kpi_signature: tuple | None = None
|
||||||
self._lib_cache: Dict[str, str] = {}
|
self._lib_cache: Dict[str, str] = {}
|
||||||
|
self._pdf_font_base64: str | None = None
|
||||||
|
|
||||||
# 初始化图表验证和修复器
|
# 初始化图表验证和修复器
|
||||||
self.chart_validator = create_chart_validator()
|
self.chart_validator = create_chart_validator()
|
||||||
@@ -97,6 +99,11 @@ class HTMLRenderer:
|
|||||||
"""获取第三方库文件的目录路径"""
|
"""获取第三方库文件的目录路径"""
|
||||||
return Path(__file__).parent / "libs"
|
return Path(__file__).parent / "libs"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_font_path() -> Path:
|
||||||
|
"""返回PDF导出所需字体的路径"""
|
||||||
|
return Path(__file__).parent / "assets" / "fonts" / "SourceHanSerifSC-Medium.otf"
|
||||||
|
|
||||||
def _load_lib(self, filename: str) -> str:
|
def _load_lib(self, filename: str) -> str:
|
||||||
"""
|
"""
|
||||||
加载指定的第三方库文件内容
|
加载指定的第三方库文件内容
|
||||||
@@ -123,6 +130,22 @@ class HTMLRenderer:
|
|||||||
print(f"警告: 读取库文件 {filename} 时出错: {e}")
|
print(f"警告: 读取库文件 {filename} 时出错: {e}")
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
def _load_pdf_font_data(self) -> str:
|
||||||
|
"""加载PDF字体的Base64数据,避免重复读取大型文件"""
|
||||||
|
if self._pdf_font_base64 is not None:
|
||||||
|
return self._pdf_font_base64
|
||||||
|
font_path = self._get_font_path()
|
||||||
|
try:
|
||||||
|
data = font_path.read_bytes()
|
||||||
|
self._pdf_font_base64 = base64.b64encode(data).decode("ascii")
|
||||||
|
return self._pdf_font_base64
|
||||||
|
except FileNotFoundError:
|
||||||
|
logger.warning("PDF字体文件缺失:%s", font_path)
|
||||||
|
except Exception as exc:
|
||||||
|
logger.warning("读取PDF字体文件失败:%s (%s)", font_path, exc)
|
||||||
|
self._pdf_font_base64 = ""
|
||||||
|
return self._pdf_font_base64
|
||||||
|
|
||||||
# ====== 公共入口 ======
|
# ====== 公共入口 ======
|
||||||
|
|
||||||
def render(self, document_ir: Dict[str, Any]) -> str:
|
def render(self, document_ir: Dict[str, Any]) -> str:
|
||||||
@@ -221,6 +244,8 @@ class HTMLRenderer:
|
|||||||
str: head片段HTML。
|
str: head片段HTML。
|
||||||
"""
|
"""
|
||||||
css = self._build_css(theme_tokens)
|
css = self._build_css(theme_tokens)
|
||||||
|
pdf_font_b64 = self._load_pdf_font_data()
|
||||||
|
pdf_font_literal = json.dumps(pdf_font_b64)
|
||||||
|
|
||||||
# 加载第三方库
|
# 加载第三方库
|
||||||
chartjs = self._load_lib("chart.js")
|
chartjs = self._load_lib("chart.js")
|
||||||
@@ -262,6 +287,10 @@ class HTMLRenderer:
|
|||||||
<style>
|
<style>
|
||||||
{css}
|
{css}
|
||||||
</style>
|
</style>
|
||||||
|
<script>
|
||||||
|
// 预载 PDF 字体 Base64 数据,后续由 jspdf addFileToVFS 使用
|
||||||
|
window.pdfFontData = {pdf_font_literal};
|
||||||
|
</script>
|
||||||
<script>
|
<script>
|
||||||
document.documentElement.classList.remove('no-js');
|
document.documentElement.classList.remove('no-js');
|
||||||
document.documentElement.classList.add('js-ready');
|
document.documentElement.classList.add('js-ready');
|
||||||
@@ -330,7 +359,7 @@ class HTMLRenderer:
|
|||||||
<div class="header-actions">
|
<div class="header-actions">
|
||||||
<button id="theme-toggle" class="action-btn" type="button">🌗 主题切换</button>
|
<button id="theme-toggle" class="action-btn" type="button">🌗 主题切换</button>
|
||||||
<button id="print-btn" class="action-btn" type="button">🖨️ 打印</button>
|
<button id="print-btn" class="action-btn" type="button">🖨️ 打印</button>
|
||||||
<!-- <button id="export-btn" class="action-btn" type="button">⬇️ 导出PDF</button> -->
|
<button id="export-btn" class="action-btn" type="button">⬇️ 导出PDF</button>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
""".strip()
|
""".strip()
|
||||||
@@ -2793,6 +2822,15 @@ function exportPdf() {
|
|||||||
}
|
}
|
||||||
showExportOverlay('正在导出PDF,请稍候...');
|
showExportOverlay('正在导出PDF,请稍候...');
|
||||||
const pdf = new jspdf.jsPDF('p', 'mm', 'a4');
|
const pdf = new jspdf.jsPDF('p', 'mm', 'a4');
|
||||||
|
try {
|
||||||
|
if (window.pdfFontData) {
|
||||||
|
pdf.addFileToVFS('SourceHanSerifSC-Medium.otf', window.pdfFontData);
|
||||||
|
pdf.addFont('SourceHanSerifSC-Medium.otf', 'SourceHanSerif', 'normal');
|
||||||
|
pdf.setFont('SourceHanSerif');
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('Custom PDF font setup failed, fallback to default', err);
|
||||||
|
}
|
||||||
const pageWidth = pdf.internal.pageSize.getWidth();
|
const pageWidth = pdf.internal.pageSize.getWidth();
|
||||||
const pxWidth = Math.max(target.scrollWidth, document.documentElement.scrollWidth);
|
const pxWidth = Math.max(target.scrollWidth, document.documentElement.scrollWidth);
|
||||||
const restoreButton = () => {
|
const restoreButton = () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user