De accessibility-diagnosedienst van Liberogic leverde certificaten en rapporten tot nu toe in HTML-formaat. Op verzoek van klanten die graag een certificaat in PDF-formaat ontvangen, kunnen we nu certificaten en rapporten in PDF-formaat uitgeven!
PDF-certificaat voor webaccessibility-audit
PDF-rapport over naleving van accessibility-eisen (VPAT / ACR)
Hiervoor hebben we een systeem gebouwd dat tijdens het buildproces naast de HTML-output automatisch een PDF genereert.
🛠️ Basis voor PDF-generatie: Puppeteer en omgevingsopzet
Als PDF-generatietool hebben we Puppeteer gekozen, waarmee je een Chromium-browser headless kunt aansturen. Omdat de HTML oorspronkelijk met Astro is opgebouwd, verliep de integratie van Puppeteer relatief soepel.
1. Een renderomgeving met lokale server opzetten
Puppeteer gebruikt de printfunctie van Chromium, maar wanneer je een lokaal HTML-bestand (file://) rechtstreeks laadt, raakt de lay-out vaak in de war.
Om dit te omzeilen hebben we de volgende omgeving opgezet.
- Tijdelijke webserver: Na de build wordt een tijdelijke lokale webserver (
http-server) als Node.js-childproces gestart, die dedist-map host. - Stabiele omgeving: Doordat Puppeteer via de server toegang krijgt tot
http://localhost:8080, wordt een stabiele renderomgeving gewaarborgd die identiek is aan die van de browser.
Code voor de opzet van de omgeving en het starten van de server (fragment)
// プロジェクト定数からドメインを取得し、未設定なら 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. Toepassen van Japanse lettertypen
De lokale serveromgeving waarin de PDF's worden gegenereerd, beschikt niet over Japanse lettertypen. Hierdoor worden de in de PDF ingesloten lettertypen onjuist weergegeven.
- Oplossing voor het lettertype: Direct voordat de PDF wordt gegenereerd, voegt het script dynamisch de verwijzing naar het webfont en de bijbehorende stijlen toe aan de DOM. Hierdoor is PDF-uitvoer met Japanse lettertypen mogelijk. De HTML gebruikt bewust geen webfont om de snelheid te verhogen; door deze dynamische toevoeging wordt het lettertype alleen in de PDF toegepast.
Code voor het dynamisch toevoegen van het lettertype (fragment)
// 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);
🚨 Grootste uitdaging: probleem met URL-verwijzingen in links binnen de PDF
Het meest lastige was de links tussen PDF's. De in de PDF ingesloten links verwezen na de deployment nog steeds naar de URL van de lokale ontwikkelomgeving (http://localhost:8080). Dit gebeurt doordat Chromium de basis-URI van het geladen HTML-bestand als uitgangspunt voor de PDF-links behoudt.
Dwingen van absolute paden voor links
Om dit op te lossen, hebben we links na de deployment via de volgende stappen geforceerd herschreven naar volledige absolute paden.
- Gebruik van het deploymentdomein: Het deploymentdomein (bijvoorbeeld
https://example.com) wordt opgehaald uit de projectconstanteSITE_URL. - Opbouwen en vervangen van absolute URL's: Met Node.js wordt de HTML-inhoud opgehaald en wordt de oorspronkelijke link (
/accessibility_report/top/) herschreven naar een volledige URL op basis van het opgehaalde domein (bijvoorbeeldhttps://example.com/accessibility_report/pdf/acr-top.pdf). - Opnieuw toepassen in de DOM: Door de herschreven HTML opnieuw in Chromium toe te passen (via
page.setContent()), zijn de links in de PDF betrouwbaar vastgelegd op het beoogde deploymentdomein.
Code voor het herschrijven van links (fragment)
// 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
});
🎉 Samenvatting
Omdat we een iets specifieke eis hadden, namelijk het linken tussen PDF's, kostte dit wat moeite, maar de implementatie van Puppeteer zelf verliep vrij soepel. Ook de build is snel, en we vonden het een zeer handige tool!
De belangrijkste punten van deze aanpak zijn:
- Zorgen voor stabiliteit door rendering via een lokale server
- Japanstalige ondersteuning via dynamische injectie van webfonts
- Links tussen PDF's via absolute paden
Met de ondersteuning voor PDF verbetert het gemak van onze service aanzienlijk. We blijven streven naar een hoogwaardige accessibility-auditservice die waarde biedt voor onze klanten!
Van DTP naar het web: al snel werkte hij aan markup, frontend, projectregie en accessibility, en beheerst hij alles. Actief bij Liberogic sinds de beginjaren en nu een levende encyclopedie binnen het bedrijf. Recentelijk verdiept hij zich, met prompts als hulpmiddel, in de vraag: "Kunnen we voor accessibility niet meer op AI leunen?" Technologie en denkwijze blijven zich ontwikkelen.
Ayumu Futamata
IAAP-gecertificeerd webaccessibility-specialist (WAS) / Markup-engineer / Frontend-engineer / Webdirecteur