How to embed images in Twig
Recently I had to create PDF files in a Symfony project using Twig, however this is not as easy as it seems, for example you need a special CSS file adapted to print.
Another obstacle is images, I didn’t have any problem including images from an external source, the problem came when I had to use images from the same project. In this post I explain why using images is a tricky task and how I solved this problem.
This is the stack I used to generate PDF files:
- Symfony 6 - Web framework.
- Twig 3 - To generate the content of PDF files.
- KnpSnappyBundle - To convert Twig view to a PDF file.
I will not explain how to generate PDF files, I assume you already know how to use Twig and KnpSnappyBundle.
Using assets
to include an image (didn’t work)
The naive solution is treat a PDF file as any other Twig view, simply
use assets
function to include an image.
<img src="{{ asset('avatar.png') }}"/>
This didn’t work because I was in a Dockerized environment, outside the
container the project’s url was something
like http://web.project.localhost
, but within the Docker container this URL
does not exist and means nothing, therefore the image was never found.
Using special asset configuration (it didn’t work either)
As told before, the url http://web.project.localhost
is not resolved when
requested inside the container, so what kind of URL is always valid inside a
container ? The answer is a loopback address, that’s how I had the idea to
use 127.0.0.1
to load my images, so I had to configure a special asset
package in framework.yaml
file.
#config/packages/framework.yaml
assets:
packages:
attachments-pdf:
base_urls: 'http://127.0.0.1/attachments'
To generate an absolute url using attachments-pdf
package:
<img src="{{ asset('avatar.png', 'attachments-pdf') }}"/>
This would have worked except that, in order to view an image, I needed to be logged in to the website, image was never displayed because instead of an image I was receiving the login page.
Embedding images in Twig
So finally I came up to the idea to embed images directly inside PDF file, I wasn’t so keen about this solution because it’s not a drop-in solution, it requires some configuration to work properly.
To embed an image within any HTML file you can use DATA URIs instead of an image url, this is the syntax that DATA URI follows:
data:<mimetype>;<encoding>,<data>
Here an example taken from Wikipedia:
<img src="data:image/png;base64,iVBORw0KGgoAAA
ANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4
//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU
5ErkJggg==" alt="Red dot"/>
Luckily for me, somebody already created a
the data_uri
filter
to create DATA URIs, this filter is not installed by default, so the following
packages are required twig/html-extra
and twig/extra-bundle
.
composer require twig/html-extra twig/extra-bundle
To avoid any problems with paths I decided to use absolute paths, in consequence
I configured this in framework.yaml
, I used base_path
instead
of base_urls
.
#config/packages/framework.yaml
assets:
packages:
attachments-pdf:
- base_urls: 'http://127.0.0.1/attachments'
+ base_path: '/app/assets/attachments'
Next, I needed a way to read the image content, this can be easily done in PHP
with file_get_contents
function, because there is no similar functionality in
Twig I had to write my own file_get_contents
Twig filter. You can use make
bundle to create a Twig extension, our custom filter will be located inside.
bin/console make:twig-extension FileExtension
Then we create file_get_contents
Twig filter, as you can see our Twig function
will call PHP’s file_get_contents
function.
<?php // src/Twig/FileExtension.php
namespace App\Twig;
use Twig\Extension\AbstractExtension;
use Twig\TwigFilter;
use Twig\TwigFunction;
use function file_get_contents;
class FileExtension extends AbstractExtension
{
public function getFilters(): array
{
return [
new TwigFunction('file_get_contents', file_get_contents(...)),
];
}
}
Finally, putting everything together:
<img src="{{ asset('avatar.png', 'attachments-pdf') | file_get_contents | data_uri }}"/>
How it works ?
- First
asset('avatar.png', 'attachments-pdf')
will generate an absolute path, in our example this will be/app/assets/attachments/avatar.png
. Remember we configured this inframework.yaml
. - Then
file_get_contents
filter will receive the file path and return the content ofavatar.png
. - Finally, we pass the image content to
data_uri
filter, this filter is very handy because it will do all the hard work for us: generate a valid DATA URI syntax, detect the mime type and convert the image to base 64.
Conclusion
I explained my journey trying to display images in a PDF file, I tested many solutions and I decided to use DATA URIs.
In order to use DATA URIs I had to:
- Install
data_uri
Twig filter. - Configure assets packages in
framework.yaml
. - Create
file_get_contents
Twig filter.
I think using DATA URIs is the best solution when you are creating PDF files, this will allow you to add images in any environment, dockerized or not.