Liberogic 的無障礙檢測服務原本以 HTML 格式提供證明書與報告,但我們聽取了「希望提供 PDF 格式證明書」的需求,現已可發行 PDF 格式的證明書與報告!
為此,我們建置了一套系統,在建置流程中輸出 HTML 的同時自動輸出 PDF。
🛠️ PDF 化的基礎:Puppeteer 與環境建置
作為 PDF 生成工具,我們採用了可以無頭模式操作 Chromium 瀏覽器的 Puppeteer。由於 HTML 原本就是以 Astro 建置,因此導入 Puppeteer 相對順利。
1. 透過本地伺服器確保渲染環境
Puppeteer 會利用 Chromium 的列印功能,但若直接讀取本地 HTML 檔案(file://),版面配置會跑掉,產生問題。
為了解決此問題,我們建置了以下環境。
- 臨時Web伺服器: 以Node.js子程序啟動一個臨時本機Web伺服器(
http-server),用於託管建置後的dist目錄。 - 穩定的環境: 讓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本身的導入相當順利。建置速度也很快,我覺得是非常便利的工具!
本次處理的重點如下:
- 透過本機伺服器進行渲染,確保穩定性
- 透過動態注入網頁字型以支援日文
- 透過絕對路徑化,實現 PDF 之間的連結
隨著 PDF 支援的推出,服務的便利性大幅提升。未來我們也將持續追求,為客戶提供更有價值的無障礙檢測服務品質!
從 DTP 踏入網頁世界,不知不覺間,標記、前端、專案指導與無障礙,樣樣精通的「技藝仙人」。自 Liberogic 創業初期就跨領域活躍,如今是公司內部的活字典般的存在。最近沉迷於運用提示詞提升效率的探索,想著「無障礙相關的工作,能不能更依賴 AI 呢?」技術與思考,仍在持續進化中
二俣 步
IAAP認證網頁無障礙專員(WAS) / 標記工程師 / 前端工程師 / 網站總監