BookStackApp/BookStack · error · PdfExportException

Failed to create required font data at $expectedUfm, Ensure

Error message

Failed to create required font data at $expectedUfm, Ensure this location is writable by the web server

What it means

Thrown by getUserDomPdfFontFamilies in app/Exports/PdfGenerator.php when FontLib's saveAdobeFontMetrics() cannot write the generated .ufm (Adobe Font Metrics) file for a .ttf font found in storage/fonts/dompdf. The scan runs lazily during PDF export: for each font without an existing .ufm it parses the TTF and writes metrics next to it. Failure means the target path is not writable by the PHP process, or the font failed to parse into valid metrics.

Source

Thrown at app/Exports/PdfGenerator.php:103

    protected function getUserDomPdfFontFamilies(): array
    {
        $fontStore = storage_path('fonts/dompdf');
        if (!is_dir($fontStore)) {
            return [];
        }

        $fontFamilies = [];
        $fontFiles = glob($fontStore . DIRECTORY_SEPARATOR . '*.ttf');
        foreach ($fontFiles as $fontFile) {
            $fontFileName = basename($fontFile, '.ttf');
            $expectedUfm = $fontStore . DIRECTORY_SEPARATOR . $fontFileName . '.ufm';
            if (!file_exists($expectedUfm)) {
                $font = Font::load($fontFile);
                $font->parse();
                try {
                    $font->saveAdobeFontMetrics($expectedUfm);
                } catch (\Exception $exception) {
                    throw new PdfExportException("Failed to create required font data at $expectedUfm, Ensure this location is writable by the web server");
                }
            }

            $nameParts = explode('-', $fontFileName);
            if (count($nameParts) === 1 || $nameParts[1] === 'Regular') {
                $nameParts[1] = 'Normal';
            }

            $family = trim(strtolower(preg_replace('/([A-Z])/', ' $1', $nameParts[0])));
            $variation = Str::snake($nameParts[1]);
            if (!isset($fontFamilies[$family])) {
                $fontFamilies[$family] = [];
            }

            $fontFamilies[$family][$variation] = $fontStore . DIRECTORY_SEPARATOR . $fontFileName;
        }

        return $fontFamilies;

View on GitHub (pinned to 18f8469a1c)

Solutions

  1. Grant write permission on storage/fonts/dompdf to the web server user: chown -R www-data:www-data storage/fonts/dompdf; chmod -R u+rwX storage/fonts/dompdf.
  2. Validate/re-upload the offending .ttf named in the message — if Font::parse() fails the metrics can never be saved; ensure it is a genuine TTF file.
  3. Pre-generate the .ufm manually once (e.g. with a small PHP script using FontLib) as a user with write access, so the export path never needs to write.
  4. Check disk space and mount flags (df -h, mount | grep storage) to rule out a full or read-only filesystem.
  5. If the font keeps failing, remove it from storage/fonts/dompdf to unblock PDF exports.

Example fix

// before (shell)
# storage/fonts/dompdf not writable -> 'Failed to create required font data at .../DejaVuSans.ufm'

// after
sudo chown -R www-data:www-data storage/fonts/dompdf
sudo chmod -R u+rwX storage/fonts/dompdf
Defensive patterns

Strategy: validation

Validate before calling

<?php
$fontDir = storage_path('fonts/dompdf');
$ok = is_dir($fontDir) && is_writable($fontDir);
if ($ok) {
    foreach (glob($fontDir . '/*.ttf') as $ttf) {
        $ufm = substr($ttf, 0, -4) . '.ufm';
        if (!file_exists($ufm) && !is_writable(dirname($ufm))) { $ok = false; break; }
    }
}
var_dump($ok); // false => fix permissions/fonts before exporting PDFs

Try / catch

try {
    $pdf = $pdfGenerator->fromHtml($html);
} catch (\BookStack\Exceptions\PdfExportException $e) {
    if (str_contains($e->getMessage(), 'Failed to create required font data at')) {
        // message names the exact .ufm path; repair perms for that dir or drop the bad .ttf
        Log::error('Font metric generation failed', ['msg' => $e->getMessage()]);
    }
    throw $e;
}

Prevention

When it happens

Trigger: Calling fromHtml() with the dompdf engine while storage/fonts/dompdf exists and contains a .ttf whose matching .ufm does not exist yet, and Font::load()/parse()/saveAdobeFontMetrics() throws — i.e. first-time metric generation for a user-uploaded custom font with an unwritable directory or an invalid font file.

Common situations: User has just uploaded a custom font via admin settings and the web server user lacks write access to storage/fonts/dompdf; the .ttf is corrupt or an unsupported format (e.g. OTF/WOFF renamed to .ttf); read-only container filesystems; SELinux denials; disk full.

Related errors


AI-assisted analysis of BookStackApp/BookStack@18f8469a1c (2026-09-02). Data as JSON: /api/errors/581c5a856312a29c. Report an issue: GitHub.