Issue with Embedding Custom Fonts in PDF using Spatie Browsershot in Laravel
Sarim_Shahid
Full Stack Developer · 2024-01-15
Hello, I am working with Spatie Browsershot in a Laravel application to generate PDFs from HTML content. The issue I'm encountering is that custom fonts (specifically, NotoNastaliqUrdu-Regular) are not being embedded in the PDF when I either stream or download it. However, when viewed in a browser, the font is applied correctly. I have tried multiple approaches, including using
Spatie Browsershot wraps headless Chrome to produce pixel-perfect PDFs, but font embedding brings its own set of filesystem and browser policy constraints. If the font renders in a normal browser but vanishes in PDF output, the cause usually involves path resolution, permissions, or CSS specificity inside a headless rendering context. This post offers a systematic troubleshooting path.
Font Path Resolution
Browsershot runs Chrome in a separate process that may not share your application's environment variables or working directory. Use absolute paths to font files rather than relative paths. If you install fonts through Laravel Mix or Vite, resolve their public paths with the mix() or vite() helpers before passing them into your blade view. The Chrome process must be able to read the font files directly from disk.
Chrome and Puppeteer Permissions
Check that the user running your PHP or queue process can read the font directory. In Docker containers, confirm the font files are copied into the image during build and that the working user has read access. Headless Chrome sandboxing may block font access if seccomp or namespace policies are too restrictive. Running with --no-sandbox in development can confirm whether policy is the cause, but avoid this flag in production.
CSS Techniques
Declare fonts with src: local(...), url(...) and format('truetype') or format('woff2'). Provide multiple formats to maximize compatibility. Use font-display: swap to avoid invisible text during rendering. Ensure the font-family name matches exactly between CSS and inline styles, because Browsershot may evaluate CSS differently from your regular browser.
Reporting and Dashboard Context
Once fonts render correctly, you can generate branded reports and embed them into admin experiences. Multi-application monitoring dashboards often need formatted documents; guidance for these integrations appears in Analytics dashboard, where PDF export considerations are woven into the broader operational reporting strategy.
Conclusion
Fix font embedding by aligning filesystem visibility, absolute paths, and CSS font-face syntax. Validate with a headless Chrome process that mimics production, and preserve browser and PDF outputs side by side for regression testing.
Related Posts
- Analytics dashboard Issue with Embedding Custom Fonts in PDF using Spatie Browsershot in Laravel
- Get Inserted Id from DB::insert()
These related posts cover dashboard PDF reporting, font troubleshooting, and insert-heavy workflows in Laravel.
Font Discovery in Headless Chrome
Chromium uses a multi-step font matching process: it reads CSS font-family declarations, matches against available system and web fonts, applies language/script matching, and falls back if needed. In headless mode, Chromium may not have access to user-installed fonts from the host OS depending on sandboxing. On Linux containers, the default set of system fonts is minimal. If your font is Noto Nastaliq Urdu, you must ensure it's installed inside the container or available via a web URL that Chromium can fetch.
Browsershot runs a Node.js process that may use its own Puppeteer Chromium download. This Chromium instance shares the host filesystem but may be sandboxed. Test font availability by connecting to the running Puppeteer instance and evaluating document.fonts.check('12px "Noto Nastaliq Urdu"') in page context. If it returns false, the font isn't loaded.
Alternative PDF Approaches
If Browsershot continues to fail, consider using a server-side PDF library like TCPDF or mPDF with explicit font embedding via setFont() and addFont(). These libraries give you direct control over font files but produce less pixel-perfect layouts. For complex designs, Wkhtmltopdf with explicit --enable-local-file-access and --user-style-sheet options can help.
See Analytics dashboard for generating reports with consistent typography, and Laravel Octane benchmark comparing Swoole, OpenSwoole, RoadRunner, FrankenPHP if you need to scale PDF generation under load.
Troubleshooting Checklist
If custom fonts still fail in PDF, follow this checklist: (1) Confirm the font file loads in the page by opening DevTools > Network and filtering for font files. (2) Verify the font-face src URL is reachable from the headless Chrome instance (try curl from the server). (3) Check for CORS errors in DevTools console; if found, add appropriate headers or base64-encode. (4) Ensure the font format (WOFF2, TTF) is supported by the Chromium version in your Puppeteer/Chrome install. (5) Try a simpler HTML page with just the font and text to isolate the issue.
If the issue persists, upgrade Browsershot and Puppeteer to latest versions. Puppeteer occasionally changes how fonts are loaded with sandbox tightening. On Linux, ensure fontconfig is installed and has a fonts.conf that includes your custom font directory. Run fc-cache -fv after adding fonts.
Production PDF Pipelines
For high-volume PDF generation, decouple rendering from the request/response cycle. Queue the PDF job and notify the user when ready. Limit concurrent Puppeteer processes to avoid memory exhaustion. Monitor CPU and RAM usage; a single Chromium instance can consume 200MB+. Use lightweight alternatives like WeasyPrint or headless Firefox if font rendering issues persist.
See Analytics dashboard for including PDF reports in admin interfaces, and Laravel Octane benchmark comparing Swoole, OpenSwoole, RoadRunner, FrankenPHP for scaling CPU-heavy workloads.