Topics

無障礙診斷服務的證書和報告已支援 PDF 格式!

  • column

Liberogic 的無障礙診斷服務原本以 HTML 格式提供證書和報告,但為了滿足「希望以 PDF 格式提供證書」的需求,現在已可以發行 PDF 格式的證書和報告

PDF 無障礙檢查證書

PDF 無障礙相容性報告(VPAT / ACR)

為實現此功能,我們在構建流程中建立了一個系統,在輸出 HTML 的同時自動生成 PDF。

🛠️ PDF 化的基礎:Puppeteer 和環境構築

作為 PDF 生成工具,我們採用了可以無頭操作 Chromium 瀏覽器的 Puppeteer。由於 HTML 原本已用 Astro 構築,Puppeteer 的導入相對順利。

1. 利用本機伺服器確保渲染環境

Puppeteer 利用 Chromium 的列印功能,但直接讀取本機 HTML 檔案(file://)會出現 版面配置破損的問題。

為了避免這個問題,我們構建了以下環境。

  • 臨時網路伺服器:啟動臨時本地網路伺服器(http-server)作為 Node.js 子程序,用於託管建置後的 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 連結的基點。

強制連結為完整絕對路徑

為了解決此問題,我們採用以下步驟,將連結強制改寫為部署後的完整絕對路徑。

  1. 使用部署目標網域: 從專案常數(SITE_URL)取得部署目標網域(例如:https://example.com)。
  2. 建構並替換絕對網址: 使用 Node.js 取得 HTML 內容,將原始連結(/accessibility_report/top/)改寫為以取得的網域為基點的完整網址(例如:https://example.com/accessibility_report/pdf/acr-top.pdf)。
  3. 重新套用至 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 支援,本服務的便利性大幅提升。我們將持續追求為客戶提供具價值的 accessibility 診斷服務品質!

本文作者

從 DTP 跨足 Web 世界,轉眼間便掌握了標記語言、frontend 開發、專案指導,以及 accessibility 等各項技能——是名真正的「技術高人」。自 Liberogic 創立初期便展現多才多藝的能力,如今儼然成為公司內的活字典。最近正沉迷於利用 AI 提示詞探索「accessibility 對應能否更多依靠 AI?」的效率化研究。技術與思維都在不斷進化中。

Futa(二)

IAAP 認證網頁無障礙專家(WAS)/ 標記語言工程師 / Frontend 工程師 / 網頁總監

查看此員工的文章

信心十足的團隊體制與迅速的應對能力是我們的優勢

Liberogic 擁有經驗豐富的人員積極推進專案,因而獲得客戶的高度評價。
我們恰當地安排專案經理和總監,致力於順利推進整個專案。 我們避免不必要的全面投入而導致成本增加,而是採用適材適所配置資源的方式,因此在業務把握到估價制作與提交的速度上也備受好評。

請注意,我們不積極進行 SES 形式的駐場業務。

Slack、Teams、Redmine、Backlog、Asana、Jira、Notion、Google Workspace、Zoom、Webex 等幾乎所有主要的專案管理工具和聊天工具都可供您使用。

您是否在網頁無障礙服務方面遇到困難?

案例分析