{"record":{"id":"581c5a856312a29c","repo":"BookStackApp/BookStack","slug":"failed-to-create-required-font-data-at-expecteduf","errorCode":null,"errorMessage":"Failed to create required font data at $expectedUfm, Ensure this location is writable by the web server","messagePattern":"Failed to create required font data at \\$expectedUfm, Ensure this location is writable by the web server","errorType":"exception","errorClass":"PdfExportException","httpStatus":null,"severity":"error","filePath":"app/Exports/PdfGenerator.php","lineNumber":103,"sourceCode":"    protected function getUserDomPdfFontFamilies(): array\n    {\n        $fontStore = storage_path('fonts/dompdf');\n        if (!is_dir($fontStore)) {\n            return [];\n        }\n\n        $fontFamilies = [];\n        $fontFiles = glob($fontStore . DIRECTORY_SEPARATOR . '*.ttf');\n        foreach ($fontFiles as $fontFile) {\n            $fontFileName = basename($fontFile, '.ttf');\n            $expectedUfm = $fontStore . DIRECTORY_SEPARATOR . $fontFileName . '.ufm';\n            if (!file_exists($expectedUfm)) {\n                $font = Font::load($fontFile);\n                $font->parse();\n                try {\n                    $font->saveAdobeFontMetrics($expectedUfm);\n                } catch (\\Exception $exception) {\n                    throw new PdfExportException(\"Failed to create required font data at $expectedUfm, Ensure this location is writable by the web server\");\n                }\n            }\n\n            $nameParts = explode('-', $fontFileName);\n            if (count($nameParts) === 1 || $nameParts[1] === 'Regular') {\n                $nameParts[1] = 'Normal';\n            }\n\n            $family = trim(strtolower(preg_replace('/([A-Z])/', ' $1', $nameParts[0])));\n            $variation = Str::snake($nameParts[1]);\n            if (!isset($fontFamilies[$family])) {\n                $fontFamilies[$family] = [];\n            }\n\n            $fontFamilies[$family][$variation] = $fontStore . DIRECTORY_SEPARATOR . $fontFileName;\n        }\n\n        return $fontFamilies;","sourceCodeStart":85,"sourceCodeEnd":121,"githubUrl":"https://github.com/BookStackApp/BookStack/blob/18f8469a1c72f8cc8497e9372635e6dea5028071/app/Exports/PdfGenerator.php#L85-L121","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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.","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.","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.","Check disk space and mount flags (df -h, mount | grep storage) to rule out a full or read-only filesystem.","If the font keeps failing, remove it from storage/fonts/dompdf to unblock PDF exports."],"exampleFix":"// before (shell)\n# storage/fonts/dompdf not writable -> 'Failed to create required font data at .../DejaVuSans.ufm'\n\n// after\nsudo chown -R www-data:www-data storage/fonts/dompdf\nsudo chmod -R u+rwX storage/fonts/dompdf","handlingStrategy":"validation","validationCode":"<?php\n$fontDir = storage_path('fonts/dompdf');\n$ok = is_dir($fontDir) && is_writable($fontDir);\nif ($ok) {\n    foreach (glob($fontDir . '/*.ttf') as $ttf) {\n        $ufm = substr($ttf, 0, -4) . '.ufm';\n        if (!file_exists($ufm) && !is_writable(dirname($ufm))) { $ok = false; break; }\n    }\n}\nvar_dump($ok); // false => fix permissions/fonts before exporting PDFs","typeGuard":null,"tryCatchPattern":"try {\n    $pdf = $pdfGenerator->fromHtml($html);\n} catch (\\BookStack\\Exceptions\\PdfExportException $e) {\n    if (str_contains($e->getMessage(), 'Failed to create required font data at')) {\n        // message names the exact .ufm path; repair perms for that dir or drop the bad .ttf\n        Log::error('Font metric generation failed', ['msg' => $e->getMessage()]);\n    }\n    throw $e;\n}","preventionTips":["Check is_writable(storage_path('fonts/dompdf')) as a health check before enabling custom fonts.","Only accept genuine TTF uploads (reject OTF/WOFF renamed to .ttf).","Pre-generate .ufm files during deployment as the runtime user.","Monitor disk space; full disks silently break saveAdobeFontMetrics."],"tags":["php","fontlib","filesystem-permissions","pdf-export","fonts"],"backgroundTag":"storage-directory-not-writable","analyzedSha":"18f8469a1c72f8cc8497e9372635e6dea5028071","analyzedAt":"2026-09-02T19:49:33.068Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-10T02:17:09.455Z"}