Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Dompdf Font Loader Bundle Laravel Package

jbtronics/dompdf-font-loader-bundle

A Symfony bundle that simplifies using custom fonts with Dompdf. Configure your font directories or assets and it automatically registers them, so generated PDFs can use custom fonts without manual font installation or @font-face setup.

View on GitHub
Deep Wiki
Context7

Getting Started

Read this first: this package is a Symfony bundle, not a Laravel package. It will install via Composer, but nothing in Laravel will auto-discover or boot it. There is no service provider, no config publish tag, no facade. Your job as a Laravel developer is to decide whether you want (a) the underlying idea — pre-generating dompdf font metrics during a build/deploy step — or (b) the actual code inside the bundle, wired manually.

Given the package has 2 stars, 0 dependents, and a single maintainer, treat it as a reference implementation of a pattern rather than a dependency you install and forget.

What it actually does

dompdf needs .ufm/.ufm + .php metric files for every custom TTF before it can measure text. FontMetrics::registerFont() parses the TTF at runtime, which is slow (tens to hundreds of ms per face) and happens on the first PDF render in every process — including every queue worker and every FPM child. This bundle's contribution is: declare your font files in config, then hook cache:warmup so the metric files are generated once, ahead of time, into dompdf's font cache directory.

That's the entire value proposition. In Laravel terms, it's "move font registration out of the request/worker and into php artisan."

Minimal steps

composer require jbtronics/dompdf-font-loader-bundle

Then read, in this order, before writing any code:

  1. src/DependencyInjection/Configuration.php — the exact config tree (font family → file path → style/weight). Do not guess the keys.
  2. The cache warmer class (implements Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface) — this is the code you'll be re-implementing as an Artisan command.
  3. The loader service in src/ — this is the class you'll actually bind in a service provider.
  4. README.md for the config sample you'll translate.

First use case

You have a branded invoice PDF. dompdf currently renders it in Helvetica because your @font-face/font-family: Inter silently falls back. You want Inter (regular + bold) embedded and measured correctly, without paying TTF parsing on each render.

Steps:

  1. Put the TTFs somewhere stable and readable at runtime, e.g. resources/fonts/inter/Inter-Regular.ttf and Inter-Bold.ttf.
  2. Decide the font cache directory (storage/fonts or storage/app/dompdf-fonts) and make sure it's writable and persistent — it is a cache, but regenerating it is expensive.
  3. Generate the metrics once, in a deploy/CI step, not at runtime.
  4. Point dompdf at the same cache dir at render time and reference the family in CSS as font-family: 'Inter', sans-serif;.

The critical constraint: the warmup step and the render step must agree on the font cache directory and the font file paths. Path is part of the cache key in dompdf's metrics index. A path that differs between your deploy container and your web container is the #1 failure mode.

If you're on barryvdh/laravel-dompdf, you already have config/dompdf.php with font_dir, font_cache, and chroot. Set all three deliberately, then treat the bundle as documentation for how to populate font_cache.

Implementation Patterns

Pattern 1 — Manual service binding (no Symfony container)

If you want the bundle's loader logic, bind it yourself in a provider:

// app/Providers/DompdfFontServiceProvider.php
public function register(): void
{
    $this->app->singleton(DompdfFontLoader::class, function ($app) {
        return new DompdfFontLoader(
            fontDir: resource_path('fonts'),
            // confirm the real constructor signature against src/
        );
    });
}

Confirm the constructor against src/ — Symfony bundles often take a ContainerInterface or an iterable $fonts tag-collection, neither of which exists in Laravel. If the constructor expects tagged services, pass a plain array.

Pattern 2 — Artisan command as the cache warmer

This is the pattern most worth copying, and it's ~20 lines:

class WarmDompdfFonts extends Command
{
    protected $signature = 'dompdf:fonts:warm';

    public function handle(): int
    {
        $metrics = (new FontMetrics(config('dompdf.font_cache')));
        $metrics->setFontDir(config('dompdf.font_dir'));

        foreach (config('dompdf.custom_fonts', []) as $family => $faces) {
            foreach ($faces as $style => $path) {
                $metrics->registerFont(
                    [$family, $style],   // e.g. ['Inter', 'bold']
                    $path,
                );
            }
        }

        $this->info('Dompdf font metrics warmed.');
        return self::SUCCESS;
    }
}

Run it in composer.json's post-install-cmd/post-deploy, or as a discrete deploy step — not in a php artisan migrate-adjacent hook that might run on every node concurrently against a shared volume.

Pattern 3 — Deploy-time, not boot-time

Treat warmup as a build artifact. In CI:

composer install --no-dev
php artisan dompdf:fonts:warm   # writes into storage/fonts

Then ship storage/fonts into the image, or run the warm step once per release on a single node before draining traffic. Avoid warming in a queue worker's boot() — you'll get a thundering herd of file writes on the same .ufm paths.

Pattern 4 — Per-document registration when you must

If you can't control the deploy step (shared hosting, Heroku-ish ephemeral filesystems), fall back to registering fonts once per process via a memoized singleton, and accept the first-request cost:

$this->app->singleton('dompdf.fonts.registered', function () { /* register */ return true; });

Do this before $pdf->loadHTML(), never after — metrics are consumed during layout.

Pattern 5 — Family naming discipline

Map config keys to exactly what appears in your CSS. font-family: 'Inter' must match the registered family and the @font-face font-family value. If your CSS says Inter, sans-serif and metrics are registered under inter, you get a silent fallback with correct-looking output — the worst kind of bug.

Pattern 6 — Testing

Assert on the output, not on file existence: render a small HTML fixture with a custom glyph (e.g. an em-dash and a €) and check $pdf->output() contains the font name in the embedded font list. A metrics file existing does not mean dompdf is using it.

Gotchas and Tips

  • Symfony ≠ Laravel. The bundle's Flex recipe, bundles.php registration, and cache:warmup hook have no equivalent. Everything is manual. Budget for that up front.
  • dompdf TTFs only. .otf with CFF outlines, .woff, and .woff2 will fail or silently misbehave. Convert to TTF with fonttools/ttfautohint first. Variable fonts are not reliably supported — instantiate static weights.
  • Font cache directory must be writable and stable. Ephemeral container filesystems mean you pay the warmup cost on every cold start; read-only filesystems mean registration throws. Choose one and configure it explicitly via font_cache (dompdf 2.x) or the fontDir option.
  • Paths are part of the cache identity. resources/fonts/Inter-Regular.ttf and /var/www/html/resources/fonts/Inter-Regular.ttf produce different cache entries. Normalize to absolute paths inside the command and never interpolate base_path() inconsistently between environments.
  • chroot will bite you. dompdf's chroot restricts file access. If fonts live outside it, registration fails with a confusing "file not found"-ish error. Either put fonts inside chroot or widen it — widening is a security tradeoff, so prefer the former.
  • Silent fallback is the default failure mode. No exception, no warning — just Helvetica. Log the registered families at debug level after warmup and add a smoke test that asserts the embedded font names.
  • Bold/italic need separate registrations. dompdf does not synthesize or interpolate between weights. ['Inter','bold'] and ['Inter','normal'] are distinct entries; registering only regular gives you faux-bold or a fallback.
  • Memory on large families. CJK and icon fonts (Font Awesome, Noto Sans CJK) can be 10–20 MB each; parsing them inside a max_execution_time: 30 web request is how you get 500s that only appear on the first request after deploy. This is exactly what the warmup step exists to prevent — don't skip it.
  • Version pinning. With 0 dependents and a recent-only release, pin the exact version and read the diff before upgrading. The bundle's surface is small enough that forking the useful 50 lines into app/Support/ is a defensible choice for a production Laravel app — MIT license permits it, and you lose nothing but the Symfony wiring you weren't using anyway.
  • Extension point worth knowing: everything routes through dompdf's FontMetrics. Once you understand registerFont([$family, $style], $path) and where font_cache points, you no longer need this package — that's the takeaway.
  • Don't warm fonts in composer install on autoscaling groups. N nodes × concurrent write to the same .ufm is a race. Warm once, or warm into a per-node temp dir and let it be cold-start cost.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib