Laravel Vapor + Docker, DomPDF not rendering custom fonts, works locally, not on staging

392 views Asked by At

I am having some trouble getting custom fonts working with DomPDF and Laravel Vapor. (Localy the fonts work)

These are my CSS font definitions (fonts are stored inside public/fonts)

@font-face {
            font-family: caveat;
            font-style: normal;
            font-weight: normal;
            src: url('{{ public_path('fonts/caveat.ttf') }}') format('truetype');
        }

        @font-face {
            font-family: Arial;
            font-style: normal;
            font-weight: normal;
            src: url('{{ public_path('fonts/Arial.ttf') }}') format('truetype');
        }

This is my dockerfile

FROM laravelphp/vapor:php81

RUN apk add imagemagick imagemagick-dev php81-pecl-imagick \
&& pecl install imagick \
&& docker-php-ext-enable imagick

COPY . /var/task

Locally these fonts Arial and CaveAt get rendering fine in the PDF. However after deployment to staging, the font in the PDF falls back to Helvetica

I probably need to add some commands to the dockerfile in order to get it working. My question is: What commands?

3

There are 3 answers

3
sietse85 On BEST ANSWER

First a big thanks to the answer of VonC for his long and useful answer. It did not solve my particular issue but it's good information. I highly recommend giving it a read even if my solution works for you.

My answer is perhaps not the most recommended solution, but it works well for Laravel Vapor environments (and my specific case).

How I solved it:

fonts stored in /public/fonts

public/fonts/Arial.ttf
public/fonts/caveat.ttf

adjustments in config/dompdf.php (Works with Laravel Homestead && Serverless Vapor has a useable /tmp folder that is alive per request and can be used to write temporary files to. Creating folders within /tmp in Dockerfile does not seem to work )

    "font_dir" => '/tmp/',
    "font_cache" => '/tmp/',
    "temp_dir" => '/tmp/',

CSS font definitions

@font-face {
    font-family: caveat;
    font-style: normal;
    font-weight: normal;
    src: url('{{ asset('fonts/caveat.ttf') }}') format('truetype');
}

@font-face {
    font-family: Arial;
    font-style: normal;
    font-weight: normal;
    src: url('{{ asset('fonts/Arial.ttf') }}') format('truetype');
}

Dockerfile

FROM laravelphp/vapor:php81

RUN apk add imagemagick imagemagick-dev php81-pecl-imagick \
&& pecl install imagick \
&& docker-php-ext-enable imagick

COPY . /var/task

I cannot express how happy I was to finally see my PDF render correctly after deployment.

7
VonC On

You can try and adjust your Dockerfile to make sure the custom fonts are accessible to DomPDF in the Laravel Vapor environment.
Make sure the public/fonts directory is correctly copied to the Docker image. That can be done with the COPY command in your Dockerfile;
Some dependencies might be required to render the fonts correctly. Install font libraries like fontconfig and ttf-dejavu.
Sometimes, Laravel's cache can cause issues. Clear the cache after deployment (however, in a fresh Docker build, especially when the entire application is being copied over, this cache might already be clear, making this step unnecessary).

Having changed public_path to asset in my CSS font-face I get an exception! fopen(/tmp/storage/fonts//caveat_normal_f6e1cc15c8d04d0f3332e905eeff9f57.ufm): Failed to open stream: No such file or directory. So I try to go from here and see if I can create this folder and make it writable for the process.

That should mean indicates that DomPDF is attempting to create or access a font metrics file (*.ufm) for your custom font in the /tmp/storage/fonts/ directory, but is unable to do so due to the directory either not existing or not being writable.

it seemed installing any font packages in the Dockerfile was not neccesary. It worked when I used the correct /tmp folder in the dompdf config file. That was the most important part.
Also, since COPY . /var/task copies all, it also copies the public folder AFAIK.

Try and modify your Dockerfile to explicitly create the /tmp/storage/fonts/ directory. That can be done using the RUN command to create the directory and set appropriate permissions.

DomPDF needs write access to this directory to store font metric files. Set the permissions so that the web server process running Laravel can write to this directory.

If /tmp is ephemeral, which it is often in Docker and cloud environments, you might want to use a different directory that is guaranteed to persist between requests. You can configure DomPDF to use a different directory for its font cache.

Your Dockerfile would be:

FROM laravelphp/vapor:php81

# Install necessary packages
# [previous installation commands] 

# Create and set permissions for the font cache directory
RUN mkdir -p /tmp/storage/fonts && chmod -R 775 /tmp/storage/fonts

# Copy the entire application to /var/task
# [previous COPY commands] 

# Set the necessary permissions for Laravel storage
RUN chmod -R 775 /var/task/storage

# Clear Laravel cache (if necessary)
# [optional cache clearing command] 

In your Laravel application, you need to make sure DomPDF is configured to use this directory for font caching. Check the DomPDF configuration file or wherever you configure DomPDF settings in your Laravel application. You might need to set the font cache directory explicitly.

If /tmp is not suitable for persistent storage, choose a different location and update both the Dockerfile and the DomPDF configuration accordingly.

3
TEFO On

The thing about vapor is all asset files in public directory will be moved to s3 bucket that vapor created to use it with cloudfront as CDN. Therefor you cannot find your public files in within your domain unless you define the files in public files in your vapor config file.

So you have two options to fix this issue:

  1. Use the cloudfront (recommended).
  2. Move file into public and change the asset url.

I highly discourage using the second option since it will cause inconsistence performance issue because of lambda function cold start which make site slower and therefor costs more money.

Also Cloudfront is a CDN so its nice to have it for asset files.

For fixing it with cloudfront just remove public_path within the url and check the file actually exists in your bucket.

More info: https://docs.vapor.build/projects/deployments.html#urls-within-css