Topics

Accessibility Diagnostic Service Certificates and Reports Now Available in PDF!

  • column

Liberogic's accessibility diagnostic service previously provided certificates and reports in HTML format. In response to requests for PDF certificates, we are now able to issue certificates and reports in PDF format!

Web Accessibility Inspection Certificate in PDF

Accessibility Conformance Report in PDF (VPAT / ACR)

To make this possible, we built a system that automatically outputs PDFs alongside HTML during the build process.

🛠️ PDF Generation Foundation: Puppeteer and Environment Setup

We adopted Puppeteer, which can drive a headless Chromium browser, as our PDF generation tool. Since our HTML is built with Astro, integrating Puppeteer was relatively smooth.

1. Securing a Local Server Rendering Environment

Puppeteer uses Chromium's print functionality, but loading local HTML files directly via file:// causes a problem where the layout breaks.

To work around this, we set up the following environment.

  • Temporary web server: After the build, a temporary local web server (http-server) serving the dist directory was launched as a child process of Node.js.
  • Stable environment: By having Puppeteer access the site through the server (http://localhost:8080), we ensured a stable rendering environment identical to the browser.

Environment setup and server startup code (excerpt)

// プロジェクト定数からドメインを取得し、未設定なら 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. Applying Japanese Fonts

The local server environment used for PDF generation has no Japanese fonts installed, which causes the fonts embedded in the PDF to render incorrectly.

  • Font workaround: Just before PDF generation, the script dynamically inserts a web font reference and its applied styles into the DOM. This enables PDF output with Japanese fonts. Since the HTML side uses no web fonts in order to prioritize speed, this dynamic insertion ensures the fonts are applied only to the PDF.

Dynamic font insertion code (excerpt)

// 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);

🚨 Biggest Challenge: URL References in PDF Links

The most difficult issue was with links between PDFs. Links embedded in the PDF continued to point to the local development URL (http://localhost:8080) even after deployment. This happens because Chromium retains the base URI from when the HTML was loaded and uses it as the base for PDF links.

Forcing Absolute Link Paths

To resolve this, I forcibly rewrote the links to full absolute paths based on the deployed site, using the following steps.

  1. Using the deployment domain: Retrieve the deployment domain (e.g., https://example.com) from the project constant SITE_URL.
  2. Building and replacing absolute URLs: Using Node.js, I retrieve the HTML content and rewrite the original link (/accessibility_report/top/) into a complete URL based on the retrieved domain (e.g., https://example.com/accessibility_report/pdf/acr-top.pdf).
  3. Reapplying to the DOM: By reapplying the rewritten HTML to Chromium via page.setContent(), I reliably fixed the links embedded in the PDF to the intended deployed domain.

Link Rewriting Code (Excerpt)

// 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
});

🎉 Summary

This task had its challenges because of the somewhat unusual requirement to link between PDFs, but setting up Puppeteer itself was fairly smooth. The build is also fast, and I found it to be a very handy tool!

The key points for this implementation are as follows:

  • Ensuring Stability Through Local Server Rendering
  • Japanese support via dynamic web font injection
  • Links between PDFs through absolute path conversion

With PDF support, our service has become significantly more convenient. We will continue to pursue the quality of our accessibility audit service to deliver lasting value to our clients!

About the author

Starting out in DTP and moving into the web, he now handles markup, frontend, direction, and accessibility alike. A "master of all trades" who has been versatile since Liberogic's early days and is now a walking encyclopedia of the company. Lately he has been hooked on exploring efficiency through prompts, asking, "Can't we rely more on AI for accessibility work?" Both his skills and his thinking are still evolving.

Ayumu Futamata

IAAP Certified Web Accessibility Specialist (WAS) / Markup Engineer / Frontend Engineer / Web Director

View this staff member's articles

Case Studies