<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Damien Clauzon</title>
    <description>The latest articles on DEV Community by Damien Clauzon (@clauzond).</description>
    <link>https://dev.to/clauzond</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1228902%2F7c9db911-0982-4b8b-b9cd-94851f4917d0.png</url>
      <title>DEV Community: Damien Clauzon</title>
      <link>https://dev.to/clauzond</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/clauzond"/>
    <language>en</language>
    <item>
      <title>How to Simply Generate a PDF From HTML in Symfony With WeasyPrint</title>
      <dc:creator>Damien Clauzon</dc:creator>
      <pubDate>Tue, 12 Dec 2023 14:00:00 +0000</pubDate>
      <link>https://dev.to/theodo/how-to-simply-generate-a-pdf-from-html-in-symfony-with-weasyprint-3l16</link>
      <guid>https://dev.to/theodo/how-to-simply-generate-a-pdf-from-html-in-symfony-with-weasyprint-3l16</guid>
      <description>&lt;h2&gt;
  
  
  A thousand PDF generation solutions, but which one to choose?
&lt;/h2&gt;

&lt;p&gt;Recently, I was faced with a problem that many developers fear and avoid: &lt;strong&gt;generating a PDF document&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;There are lots of libraries handling this, the problem is knowing &lt;strong&gt;which one best fits your project&lt;/strong&gt;. For my specific use case, I had to generate printable documents from HTML in a Symfony application.&lt;/p&gt;

&lt;p&gt;Let's see why &lt;strong&gt;WeasyPrint&lt;/strong&gt; fits this job perfectly, and what you need to know before using it on your own.&lt;/p&gt;

&lt;h2&gt;
  
  
  What about Snappy and wkhtmltopdf, the recommendation of Symfony?
&lt;/h2&gt;

&lt;p&gt;When I first looked for solutions to generate a PDF document in a Symfony application, I was amazed to find that &lt;a href="https://symfonycasts.com/screencast/mailer/snappy-wkhtmltopdf" rel="noopener noreferrer"&gt;a SymfonyCast&lt;/a&gt; was made about this. It features the PHP library &lt;a href="https://github.com/KnpLabs/snappy" rel="noopener noreferrer"&gt;Snappy&lt;/a&gt;, which is essentially a wrapper around &lt;strong&gt;&lt;a href="https://wkhtmltopdf.org/" rel="noopener noreferrer"&gt;wkhtmltopdf&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;There are unfortunately &lt;strong&gt;many problems&lt;/strong&gt; with this library, some of which are presented &lt;a href="https://wkhtmltopdf.org/status.html#summary" rel="noopener noreferrer"&gt;on the website&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Its CSS interpreter &lt;strong&gt;does not allow flexbox or grid&lt;/strong&gt;, but only &lt;a href="https://developer.mozilla.org/fr/docs/Web/CSS/WebKit_Extensions" rel="noopener noreferrer"&gt;webkit extensions&lt;/a&gt; which are very limited.&lt;/li&gt;
&lt;li&gt;  The last update of its engine (QtWebKit4) was in 2015, and it was dropped from most community repositories, &lt;a href="https://wiki.alpinelinux.org/wiki/Release_Notes_for_Alpine_3.15.0" rel="noopener noreferrer"&gt;including Alpine Linux&lt;/a&gt;. It has &lt;a href="https://blogs.gnome.org/mcatanzaro/2022/11/04/stop-using-qtwebkit/" rel="noopener noreferrer"&gt;several critical security issues&lt;/a&gt; that will never be patched, mostly about remote code execution when using untrusted HTML.&lt;/li&gt;
&lt;li&gt;  When choosing a library for a project, it is important to consider its &lt;strong&gt;maintenance status&lt;/strong&gt;. In this case, wkhtmltopdf has stopped being maintained in 2020 and there are &lt;strong&gt;no plans to continue development&lt;/strong&gt;. Therefore, most issues about Snappy will never get fixed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As a consequence,  &lt;strong&gt;I highly discourage you from using it&lt;/strong&gt; as it is no longer a viable solution for generating PDF documents.&lt;/p&gt;

&lt;p&gt;I was disappointed to learn that it was unusable, but fortunately, many of its users found a drop-in replacement and fell in love with it: &lt;strong&gt;&lt;a href="https://weasyprint.org/" rel="noopener noreferrer"&gt;WeasyPrint&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is WeasyPrint?
&lt;/h2&gt;

&lt;p&gt;WeasyPrint is a solution for &lt;strong&gt;creating PDF documents from HTML&lt;/strong&gt;. It has a lot of features regarding pagination which are easy to use and predictable. You can find &lt;a href="https://doc.courtbouillon.org/weasyprint/stable/api_reference.html#supported-features" rel="noopener noreferrer"&gt;the list of all supported features&lt;/a&gt; on their website, as well as examples of complex PDFs such as &lt;a href="https://weasyprint.org/#samples" rel="noopener noreferrer"&gt;reports, invoices, books, and more&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk1jg6erqi26y1dw51prt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk1jg6erqi26y1dw51prt.png" title="A ticket generated by WeasyPrint" alt="A ticket generated by WeasyPrint" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It utilizes &lt;strong&gt;its own visual rendering engine for HTML and CSS&lt;/strong&gt; aiming to support web standards for printing that is implemented in Python. However, &lt;strong&gt;it does not support JavaScript execution&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The advantage of this approach is that it can be used as a &lt;a href="https://doc.courtbouillon.org/weasyprint/stable/first_steps.html#command-line-interface" rel="noopener noreferrer"&gt;command-line tool&lt;/a&gt; &lt;strong&gt;with other languages&lt;/strong&gt;, just like &lt;a href="https://github.com/pontedilana/WeasyPrintBundle" rel="noopener noreferrer"&gt;the bundle for Symfony&lt;/a&gt; that I will present in this article!&lt;/p&gt;

&lt;h3&gt;
  
  
  You can easily do pagination
&lt;/h3&gt;

&lt;p&gt;WeasyPrint uses its CSS layout engine designed for pagination, supporting parts of the &lt;a href="https://www.w3.org/Style/CSS/current-work" rel="noopener noreferrer"&gt;CSS specifications&lt;/a&gt; written by the W3C. Most of the flex layout &lt;a href="https://doc.courtbouillon.org/weasyprint/stable/api_reference.html#css-flexible-box-layout-module-level-1" rel="noopener noreferrer"&gt;is implemented&lt;/a&gt;, and there is also a ton of supported features for paginated documents:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Support for the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@page" rel="noopener noreferrer"&gt;@page at-rule&lt;/a&gt; to specify the &lt;strong&gt;document size, orientation, margin&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;  Support for &lt;strong&gt;&lt;a href="https://www.w3.org/TR/CSS21/page.html#page-breaks" rel="noopener noreferrer"&gt;page breaks&lt;/a&gt;&lt;/strong&gt; with the CSS property  &lt;code&gt;page-break-before&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;  Support for &lt;strong&gt;&lt;a href="https://www.w3.org/TR/css-gcpm-3/#footnotes" rel="noopener noreferrer"&gt;footnotes&lt;/a&gt;&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;  Support for &lt;strong&gt;page numbers&lt;/strong&gt; and &lt;strong&gt;&lt;a href="https://www.w3.org/TR/css-gcpm-3/#document-page-selectors" rel="noopener noreferrer"&gt;page selectors&lt;/a&gt;&lt;/strong&gt; (you can even code &lt;a href="https://github.com/Kozea/WeasyPrint/pull/652#issuecomment-403276559" rel="noopener noreferrer"&gt;a table of contents&lt;/a&gt;!)&lt;/li&gt;
&lt;li&gt;  Support for &lt;strong&gt;fetching fonts&lt;/strong&gt; with the usual syntax &lt;code&gt;&amp;lt;link href="https://www.example.com/font" rel="stylesheet" /&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But there are unfortunately cons to this approach, as it is not a browser engine some CSS features are missing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  The &lt;strong&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_grid_layout" rel="noopener noreferrer"&gt;grid layout&lt;/a&gt;&lt;/strong&gt; (sob)&lt;/li&gt;
&lt;li&gt;  The &lt;strong&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/gap" rel="noopener noreferrer"&gt;gap property&lt;/a&gt;&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;CSS filters&lt;/strong&gt;, including shadows&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;CSS for SVG&lt;/strong&gt;, therefore using the &lt;code&gt;&amp;lt;img /&amp;gt;&lt;/code&gt; tag is better suited than including your SVG with the inline &lt;code&gt;&amp;lt;svg /&amp;gt;&lt;/code&gt; tag to avoid the sanitizing step&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="http://test.weasyprint.org/" rel="noopener noreferrer"&gt;A test suite&lt;/a&gt; is available for verifying the correct implementation of CSS guidelines.&lt;/p&gt;

&lt;h3&gt;
  
  
  You can generate high-quality documents
&lt;/h3&gt;

&lt;p&gt;WeasyPrint supports many PDF features that make it a great tool for generating &lt;strong&gt;high-quality documents&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  generation of &lt;strong&gt;PDF/A documents&lt;/strong&gt;, which is the ISO-standardized version allowing original formatting across different devices&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;bookmarks&lt;/strong&gt; generated by heading elements (&lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt; to &lt;code&gt;&amp;lt;h6&amp;gt;&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;hyperlinks&lt;/strong&gt;, internal to the document such as a table of contents, or external such as a website link&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;vector images&lt;/strong&gt; and &lt;strong&gt;text&lt;/strong&gt;, meaning that you can zoom in without compression artifacts; this is especially useful for generating and including bar codes and QR codes on your PDF&lt;/li&gt;
&lt;li&gt;  basic support for &lt;strong&gt;PDF forms&lt;/strong&gt; (at the moment only text inputs, text areas, and checkboxes)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Furthermore, WeasyPrint &lt;strong&gt;can find any font installed on your system&lt;/strong&gt; with the help of &lt;code&gt;fontconfig&lt;/code&gt;, which is useful to avoid fetching fonts from external stylesheets whenever you generate a PDF. You can check what fonts are installed on your system with &lt;code&gt;fc-list&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Generate your first PDF with WeasyPrint
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Rendering HTML with Twig
&lt;/h3&gt;

&lt;p&gt;The first step before generating the PDF is &lt;strong&gt;writing the HTML&lt;/strong&gt;. To generate the HTML string, we will use the &lt;a href="https://twig.symfony.com/" rel="noopener noreferrer"&gt;Twig&lt;/a&gt; template engine, which is the default one in Symfony. It comes with tons of features such as &lt;a href="https://symfony.com/doc/current/templates.html" rel="noopener noreferrer"&gt;inheritance, blocks, filters, functions, and more&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can install Twig with the following &lt;a href="https://symfony.com/components/Twig%20Bundle" rel="noopener noreferrer"&gt;bundle&lt;/a&gt; provided by Symfony.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer require symfony/twig-bundle
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The bundle comes with a configuration file located in &lt;code&gt;config/packages/twig.yaml&lt;/code&gt;, providing a default path for your templates. You can &lt;a href="https://symfony.com/doc/current/templates.html#configuration" rel="noopener noreferrer"&gt;read more about the template engine here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Create a template in the &lt;code&gt;templates&lt;/code&gt; directory of your Symfony application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- base.html.twig --&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"UTF-8"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Hello world&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
            &lt;span class="nc"&gt;.title&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#dcb03f&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="nl"&gt;text-align&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;h1&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"title"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Hello world!&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The HTML can now be generated from this template using the &lt;code&gt;\Twig\Environment-&amp;gt;render&lt;/code&gt; method. To do this, &lt;strong&gt;write a simple controller&lt;/strong&gt; that returns the HTML:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// MyPdfController.php&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Symfony\Bundle\FrameworkBundle\Controller\AbstractController&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Symfony\Component\HttpFoundation\Response&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Symfony\Component\Routing\Annotation\Route&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Twig\Environment&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PdfController&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;AbstractController&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kt"&gt;Environment&lt;/span&gt; &lt;span class="nv"&gt;$twig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;


    &lt;span class="na"&gt;#[Route('/my-pdf-controller', name: 'my-pdf-controller')]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;pdfController&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;Response&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;twig&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'base.html.twig'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$html&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can now test the endpoint by visiting &lt;code&gt;/my-pdf-controller&lt;/code&gt; in your browser. You should see your HTML rendered!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fteiy0yjrw8qjk4gwwtup.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fteiy0yjrw8qjk4gwwtup.png" title="A basic HTML page" alt="A basic HTML page" width="632" height="213"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Never trust user HTML!
&lt;/h3&gt;

&lt;p&gt;As with any other tool, &lt;strong&gt;you should avoid using untrusted HTML, CSS, and images&lt;/strong&gt;, because WeasyPrint has access to local files and can make network requests. &lt;strong&gt;Always sanitize user inputs!&lt;/strong&gt; This can be done with the filter &lt;a href="https://twig.symfony.com/doc/3.x/filters/escape.html" rel="noopener noreferrer"&gt;escape&lt;/a&gt; in Twig. You can find a list of other possible attacks with advice to avoid them on &lt;a href="https://doc.courtbouillon.org/weasyprint/stable/first_steps.html#security" rel="noopener noreferrer"&gt;the WeasyPrint documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Performance is not the strength of WeasyPrint, meaning that &lt;strong&gt;heavy HTML files will increase generation time&lt;/strong&gt;. You should &lt;strong&gt;always compress images&lt;/strong&gt; before attaching them, as they are not compressed by default. Generating a 50-page-long PDF &lt;a href="https://github.com/Kozea/WeasyPrint/issues/545" rel="noopener noreferrer"&gt;may take up to a minute&lt;/a&gt; in extreme cases, although multi-page documents generated on my project take fewer than 2 seconds to generate.&lt;/p&gt;

&lt;h3&gt;
  
  
  Generating PDF from your HTML
&lt;/h3&gt;

&lt;p&gt;You will need to install the &lt;code&gt;weasyprint&lt;/code&gt; binary &lt;strong&gt;separately from the Symfony bundle&lt;/strong&gt;, as WeasyPrint does not have a native PHP implementation.&lt;/p&gt;

&lt;p&gt;This step may depend on your distribution and/or environment, see the &lt;a href="https://doc.courtbouillon.org/weasyprint/stable/first_steps.html#installation" rel="noopener noreferrer"&gt;installation page&lt;/a&gt; for reference. If you use Docker with an Alpine distribution, you can install it by adding the following lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Dockerfile (Alpine distribution)&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;apk add &lt;span class="nt"&gt;--no-cache&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    weasyprint &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="c"&gt;# used to find and configure fonts&lt;/span&gt;
    fontconfig \
    # used to render TrueType fonts
    freetype \
    # used as a default font
    ttf-dejavu \
    ;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Do not forget to install a default font on your system!&lt;/strong&gt; This is useful to have as a fallback, otherwise the binary may crash when attempting to render text. On Alpine Linux, you can install the &lt;code&gt;ttf-dejavu&lt;/code&gt; package to avoid this issue.&lt;/p&gt;

&lt;p&gt;You can then install &lt;a href="https://github.com/pontedilana/WeasyPrintBundle" rel="noopener noreferrer"&gt;the bundle&lt;/a&gt; that allows running WeasyPrint from our Symfony application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer require pontedilana/weasyprint-bundle
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inject the newly installed service in your controller, and modify the response to return a PDF instead of HTML:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// MyPdfController.php&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Pontedilana\PhpWeasyPrint\Pdf&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Pontedilana\WeasyprintBundle\WeasyPrint\Response\PdfResponse&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Symfony\Bundle\FrameworkBundle\Controller\AbstractController&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Symfony\Component\HttpFoundation\Response&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Symfony\Component\HttpFoundation\ResponseHeaderBag&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Symfony\Component\Routing\Annotation\Route&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Twig\Environment&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PdfController&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;AbstractController&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kt"&gt;Environment&lt;/span&gt; &lt;span class="nv"&gt;$twig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kt"&gt;Pdf&lt;/span&gt; &lt;span class="nv"&gt;$weasyPrint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="na"&gt;#[Route('/pdf', name: 'pdf')]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;Response&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;twig&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'base.html.twig'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$pdfContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;weasyPrint&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getOutputFromHtml&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$html&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PdfResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$pdfContent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;fileName&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'file.pdf'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;contentType&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'application/pdf'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;contentDisposition&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ResponseHeaderBag&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;DISPOSITION_INLINE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="c1"&gt;// or download the file instead of displaying it in the browser with&lt;/span&gt;
            &lt;span class="c1"&gt;// contentDisposition: ResponseHeaderBag::DISPOSITION_ATTACHMENT,&lt;/span&gt;
            &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;You are now ready to generate a basic PDF in your application!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fms3zh8rzm5bqzxgkd59t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fms3zh8rzm5bqzxgkd59t.png" title="A PDF generated from the basic HTML" alt="A PDF generated from the basic HTML" width="632" height="627"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Going further: what about the other PDF generation libraries?
&lt;/h2&gt;

&lt;p&gt;Working on my project with WeasyPrint was a breeze: it is easy to set up, its repository is well maintained, it satisfies all the needs I had for pagination and it allowed me to generate high-quality PDF documents from re-usable Twig templates.&lt;/p&gt;

&lt;p&gt;However, WeasyPrint is not the only solution available, and there might be other solutions that suit your use case better:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  If WeasyPrint does not fit your needs, you can find &lt;a href="https://print-css.rocks/" rel="noopener noreferrer"&gt;a comparison of other HTML to PDF libraries here&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  If you also want to convert Markdown or LibreOffice formats, &lt;a href="https://gotenberg.dev/" rel="noopener noreferrer"&gt;the self-hosted API Gotenberg&lt;/a&gt; is worth checking out&lt;/li&gt;
&lt;li&gt;  If you want to convert an existing page, need to use JavaScript, or want your PDF rendered by a browser, take a look at &lt;a href="https://blog.theodo.com/2021/10/pdf-generation-react-puppeteer/" rel="noopener noreferrer"&gt;this article about Puppeteer&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>tutorial</category>
      <category>php</category>
      <category>symfony</category>
      <category>pdf</category>
    </item>
  </channel>
</rss>
