Liberogic's accessibility diagnostic service provided certificates and reports in HTML format, but in response to requests for certificates in PDF format,Certificates and reports in PDF formatYou can now issue it!
PDF Web Accessibility Inspection Certificate
PDF Accessibility Conformance Report (VPAT/ACR)
To address this issue, we created a system that automatically outputs PDF at the same time as outputting HTML during the build process.
🛠️ PDF creation infrastructure: Puppeteer and environment construction
It can operate the Chromium browser headlessly as a PDF generation tool.PuppeteerSince HTML was originally built with Astro, the introduction of Puppeteer was relatively smooth.
1. Securing a rendering environment using a local server
Puppeteer uses Chromium's printing functionality, but it doesn't support local HTML files (file://) directly,The layout is brokenProblems will occur.
To avoid this, we created the following environment:
- Temporary web server:After the build
distA temporary local web server to host the directory (http-server) as a child process of Node.js. - Stable environment:Puppeteer via server (
http://localhost:8080) to ensure a stable rendering environment identical to that of a 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
Since the local server environment where the PDF is generated does not have Japanese fonts, the fonts embedded in the PDF will be incorrect.
- Font workaround:In the script, the web font reference and applied style are dynamically inserted into the DOM just before the PDF is generated. This makes it possible to output PDFs using Japanese fonts. Since the HTML side prioritizes speed and does not use web fonts, this dynamic insertion is 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);
🚨 The biggest challenge: URL reference issues in PDF links
The most difficult thing wasLinking between PDFsThe link embedded in the PDF still points to the URL of the local development environment (http://localhost:8080This occurs because Chromium keeps the base URI when loading the HTML as the base of the PDF link.
Force absolute paths for links
To resolve this issue, we forced the links to be rewritten to full absolute paths after deployment using the following steps:
- Use the target domain:Project constants (
SITE_URL) to the domain you are deploying to (e.g.https://example.com) is obtained. - Absolute URL construction and substitution:Get HTML content in Node.js and return the original link (
/accessibility_report/top/)of,Acquired domainThe full URL starting from (e.g.https://example.com/accessibility_report/pdf/acr-top.pdf) - Reapply to DOM:Reapply the rewritten HTML to Chromium (
page.setContent()) to embed links in the PDF.The domain after intended deploymentwas securely fastened to the
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 time, we had some difficulties due to the somewhat unusual requirement of linking between PDFs, but the introduction of Puppeteer itself was fairly smooth. The build is fast, and I found it to be a very useful tool!
The key points of this response are as follows:
- Rendering via a local server ensures stability
- Dynamic web font injection for Japanese language support
- Links between PDFs by making links absolute paths
Supporting PDF has greatly improved the convenience of our service. We will continue to strive to provide high-quality accessibility assessment services that are valuable to our customers!
He jumped from DTP to the web world and quickly became a "master of craftsmanship" with a mastery of markup, front-end design, direction, and accessibility. He's been active in a variety of fields since Liberogic's founding and is now a living dictionary within the company. Recently, he's been obsessed with exploring efficiency improvements using prompts, wondering, "Can we rely more on AI for accessibility?" His technology and thinking are still evolving.
Futa
IAAP Certified Web Accessibility Specialist (WAS) / Markup Engineer / Front-end Engineer / Web Director