Liberogic 的无障碍诊断服务之前以 HTML 格式提供证书和报告,但考虑到「希望以 PDF 格式提供证书」的反馈,现在可以发行 PDF 格式的证书和报告!
为了实现这一需求,我们构建了一个系统,在构建过程中输出HTML的同时自动生成PDF。
🛠️ PDF 转换基础:Puppeteer 和环境配置
作为PDF生成工具,我们采用了Puppeteer来实现对Chromium浏览器的无头操作。由于HTML原本就是用Astro构建的,所以Puppeteer的集成相对顺利。
1. 确保本地服务器的渲染环境
Puppeteer 利用 Chromium 的打印功能,但直接读取本地 HTML 文件(file://)时,会出现布局错乱的问题。
为了避免这个问题,我构建了以下环境。
- 临时 Web 服务器:将构建后的
dist目录作为临时本地 Web 服务器(http-server)启动,作为 Node.js 的子进程运行。 - 稳定的环境:通过让 Puppeteer 经由服务器(
http://localhost:8080)访问,确保了与浏览器相同的稳定渲染环境。
环境构建和服务器启动代码(摘录)
// プロジェクト定数からドメインを取得し、未設定なら localhost:8080 を使用
const HOST_DOMAIN = SITE_URL || `http://localhost:${PORT}`;
const BUILD_ROOT = path.join(__dirname, 'dist');
const PORT = 8080;
// --- サーバーの起動ロジック ---
let serverProcess = exec(`npx http-server ${BUILD_ROOT} -p ${PORT} -s --silent`);
// サーバーが起動するまで待機
await new Promise((resolve) => setTimeout(resolve, 2000));
// --- Puppeteerのページアクセス ---
// Puppeteerは localhost:8080 にアクセスし、レンダリングを開始します
const serverUrl = `http://localhost:${PORT}/${REPORT_DIR}/${urlPath}`;
await page.goto(serverUrl, { waitUntil: 'networkidle0' });
2. 应用日文字体
用于生成 PDF 的本地服务器环境中没有日文字体,因此会出现嵌入在 PDF 中的字体显示异常的问题。
- 字体解决方案:在脚本中,在生成 PDF 之前动态地将 Web 字体的引用和应用样式插入到 DOM 中。这样就可以用日文字体输出 PDF。HTML 端优先考虑速度,不使用 Web 字体,因此通过这种动态插入方式只对 PDF 应用字体。
字体动态插入代码(摘录)
// Webフォントの動的挿入ロジック (page.evaluateでブラウザ側で実行)
await page.evaluate((fontUrl) => {
// <link>タグを生成してDOMに挿入
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = fontUrl;
document.head.appendChild(link);
// @media print スタイルを強制的に挿入し、フォントを適用
const printStyle = document.createElement('style');
printStyle.textContent = `
@media print {
html, body {
font-family: 'Noto Sans JP', sans-serif !important;
}
}
`;
document.head.appendChild(printStyle);
}, WEBFONTS_URL);
🚨 最大的挑战:PDF 内链接的 URL 引用问题
最困难的部分是 PDF 之间的链接。嵌入在 PDF 中的链接在部署后仍然引用本地开发环境的 URL(http://localhost:8080)。这是因为 Chromium 在加载 HTML 时会保留基础 URI 作为 PDF 链接的基点。
强制转换链接为绝对路径
为了解决这个问题,我通过以下步骤强制将链接转换为部署后的完整绝对路径。
- 获取部署目标域名: 从项目常数(
SITE_URL)中获取部署目标的域名(例:https://example.com)。 - 构建并替换绝对URL: 使用Node.js获取HTML内容,将原始链接(
/accessibility_report/top/)替换为以获取的域名为基础的完整URL(例:https://example.com/accessibility_report/pdf/acr-top.pdf)。 - 重新应用到DOM: 将替换后的HTML重新应用到Chromium(
page.setContent()),以确保嵌入PDF中的链接被固定为预期的部署后域名。
链接替换代码片段(摘录)
// 1. 無効化したいリンク(.link-ignore-pdf)を物理的に削除
const ignoreLinkRegex = new RegExp(`(<a\\\\s+[^>]*class=["'][^"']*${ignoreClass}[^"']*["'][^>]*>)(.*?)(<\\/a\\\\s*>)`, 'gi');
content = content.replace(ignoreLinkRegex, '$2'); // <a>タグ全体を中身のテキストに置換
// 2. 詳細ページへのリンクを絶対URLに書き換え
const detailLinkRegex = new RegExp(`href="${reportDirRootLink}([^/]+)\\/"`, 'g');
const detailPdfUrl = `${pdfAbsoluteUrl}/acr-top.pdf`; // 例
content = content.replace(detailLinkRegex, (match, slug) => {
// リンクを <http://localhost>... ではなく、<https://example.com/>... に強制置換
return `href="${pdfAbsoluteUrl}/acr-${slug}.pdf"`;
});
// 3. 最終的なコンテンツをブラウザに再適用し、PDF出力へ
await page.setContent(content, {
waitUntil: 'domcontentloaded',
baseURL: baseUrlForContent
});
🎉 总结
这次因为有PDF之间相互链接的特殊需求,花了一些功夫,但Puppeteer本身的集成过程相当顺利。构建速度也很快,这确实是一个非常便利的工具!
本次处理的要点如下:
- 通过本地服务器渲染确保稳定性
- 通过动态Web字体注入实现日语支持
- 通过链接绝对路径化实现PDF间的链接
通过PDF支持,服务的易用性得到了显著提升。我们将继续追求为客户提供有价值的无障碍诊断服务质量!
从DTP跨越到Web世界,不知不觉中已掌握标签、前端开发、创意指导、无障碍设计等各项技能的"技术高手"。从Liberogic创业初期就活跃至今,如今是公司内部的"活百科"。最近沉迷于"能否在无障碍适配上更多依赖AI?"这样的思考,正在探索借助AI提示词提高效率的方法。技术实力和思维方式都还在不断进化中
Futa
IAAP 认证网络无障碍专家 (WAS) / 标记语言工程师 / 前端工程师 / 网络总监