Convertendo HTML/PHP para PDF

Um recurso que temos que desenvolver em muitos sistemas são relatórios, em muitos casos exportando-os em PDF. Existem diversas bibliotecas para gerar PDF em PHP, inclusive já teve post aqui a muito tempo atrás, mas o controle do estilo desta biblioteca deixava a desejar. Então nada melhor que utilizar uma tecnologia que conhecemos bem para a apresentação como HTML/CSS e simplesmente converter para PDF, para isto temos o componente DOMPDF.

Primeiramente baixamos a biblioteca pelo composer.

composer require dompdf/dompdf

Então criamos um script para gerar um PDF bem básico.

<?php
require('./vendor/autoload.php');

use Dompdf\Dompdf;

$dompdf = new Dompdf();
$dompdf->loadHtml('<h1>Olá Mundo</h1><p>O básico do DOMPDF</p>');
$dompdf->setPaper('A4');
$dompdf->render();
$dompdf->stream('documento.pdf',['Attachment'=>false] );

Começamos na linha 2 carregando o autoload do composer para poder acessar a biblioteca. Na linha 6 criamos um objeto Dompdf este objeto que será responsável por converter o HTML para PDF.

Na linha 7 utilizamos o método loadHtml para carregar o HTML que iremos utilizar para gerar o PDF. O código HTML aceita o uso de CSS, seja inline, incorporado ou em arquivo externo. Podemos também utilizar o método loadHtmlFile para carregar um arquivo HTML para transformar em PDF, mas este método não interpreta PHP, tornando este método não muito utilizado já que fica limitado a um arquivo estático. Veremos mais adiante como ter um uso parecido com o do método loadHtmlFile mas que interprete PHP.

Na linha 8 configuramos o tamanho da folha do PDF através do método setPaper, que aceita os formatos de comuns de papel: ‘letter’‘legal’‘A4’, ‘A3’, etc. Como segundo parâmetro recebe a orientação da página, que por padrão é portrait(retrato), e podemos mudar landscape(paisagem).

Na linha 9 executamos o render que irá converter o HTML para os dados em PDF, podemos visualizar a resposta chamando o método stream, que aceita como argumento o nome do arquivo e um array de opções, entra elas 'Attachment' que por padrão é true, fazendo que o arquivo seja obrigatoriamente baixado, para podermos visualizar o PDF no navegador configuramos a opção 'Attachment' para false.

Carregando um arquivo PHP para PDF

Como visto acima podemos carregar o HTML para transformar em PDF através de dois métodos, loadHtml e loadHtmlFile, o método loadHtmlFile não é muito conveniente para relatórios e etc, pois ele só carrega HTML estático, não sendo possível carregar uma página com código PHP através dele, e loadHtml só carrega uma string, e como sabemos escrever um HTML dentro de uma string não é a coisa mais divertida do mundo, melhor sendo o HTML escrito em um arquivo separado. Podemos resolver este problema utilizando a função ob_start e um require. Mas antes vamos ver uma pagina simples de relatório.

<?php
require('./vendor/autoload.php');
use BotecoDigital\UserDAO;
$dao = new UserDAO();
$users = $dao->all();
?>
<!DOCTYPE html>
<html lang="pt-br">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Relatório de Usuários</title>
    <style>
        @page{
            margin: 150px 50px ;
        }
        body{
            font-family: 'Verdana', sans-serif;
            margin:0px;
            padding:0px;
        }
        .header{
            position: fixed;
            left: 0;
            right:0;
            top: -100px;
            height: 50px;
            padding: 10px;
            background: #333;
            margin-bottom:100px;
            text-align: center;
        }
        .header img{
            height: 50px;
        }
        .footer{
            position: fixed;
            left: 0;
            right:0;
            bottom:0;
            background: #333;
            color:#FFF;
            text-align: center;
            padding: 10px;
        }
        h1{
            text-align: center;
        }
        table{
            width: 100%;
            border:1px solid #333;
            padding: 5px;
        }
        table tr th{
            background: #333;
            color:#FFF;
            padding:5px;
        }
        table tr:nth-child(even) td{
            background: #EEE;
        }
        .image{
            text-align: center;
        }
        .image img{
            border: 1px solid #CCC;
            padding:3px;
            margin:5px;
        }
    </style>
</head>
<body>

<header class="header">
    <img src="https://www.botecodigital.dev.br/wp-content/themes/boteco_v4/img/logob.png" alt="" height="50">
</header>

<h1>Relatório de Usuários</h1>

<table>
    <tr>
        <th>Foto</th>
        <th>Nome</th>
        <th>Sobrenome</th>
        <th>E-mail</th>
        <th>Username</th>
    </tr>

    <?php foreach ($users as $user) { ?>
    <tr>
        <td class="image"><img src="<?php echo $user->getFoto() ?>" alt=""></td>
        <td><?php echo $user->getNome() ?></td>
        <td><?php echo $user->getSobrenome() ?></td>
        <td><?php echo $user->getEmail() ?></td>
        <td><?php echo $user->getUsername() ?></td>
    </tr>
    <?php } ?>
</table>

<footer class="footer">
    Gerado em <?php echo (new DateTime())->format('d/m/Y h:i:s')?>
</footer>
    
</body>
</html>

Como podemos ver esta é uma página simples de relatório, com um pouco de CSS(regras suportadas). Pegamos os dados através de um componente de acesso a dados na linha 5 e os listamos em uma tabela. Mas temos algumas regras diferentes por exemplo a regra @page que é uma regra para a impressão, nela podemos configurar as margens de todas as páginas do documento PDF.

Também utilizamos a regra position: fixed no cabeçalho e no rodapé com os devidos left, right, top, bottom. Com o position definido para fixed, ele irá colocar o cabeçalho e o rodapé em cada página do documento PDF.

Quanto a imagens vale salientar que caminhos relativos podem gerar alguns problemas, podendo o caminho absoluto de imagens ser uma melhor alternativa. Caso a imagem que deseje adicionar não pode ser servida via http de forma aberta, também é possível inserir uma imagem inline simplesmente carregando-a via PHP e convertendo para base64. Veja o exemplo.


&lt;img src="data:image/png;base64,&lt;?php echo base64_encode(file_get_contents('logo.png'))?>" alt="" height="50">

Veja o exemplo em html da página acima(pelas regras CSS o cabeçalho só vai aparecer no PDF).

Agora vamos ao código para carregar este arquivo PHP e transformar em PDF.

<?php
require('./vendor/autoload.php');
use Dompdf\Dompdf;
$dompdf = new Dompdf(['enable_remote'=>true]);

ob_start();
require(__DIR__ . '/relatorio.php');
$html = ob_get_clean();

$dompdf->loadHtml($html);
$dompdf->setPaper('A4');
$dompdf->render();

$dompdf->stream('documento.pdf',['Attachment'=>false] );

Este código é bastante semelhante ao inicial, mas na criação do componente Dompdf passamos um array de opções, e neste caso, configurando a opção enable_remote para true, isso deve ser feito para que ele possa acessar as imagens com caminho absoluto.

Na linha 6 chamamos a função ob_start esta função inicializa o cache de saída, ou seja, a partir dela todo conteudo que seria “exibido” será armazenado em cache. Com o cache de saída ativado utilizamos a função require para carregar e interpretar relatório que criamos acima, sendo o resultado armazenado em vez de exibido.

Na linha 7 chamamos a função ob_get_clean que retorna todo o conteúdo do cache de saída e o limpa. Armazenamos este conteúdo em um variável e o passamos para o método loadHtml. O resto segue sem alteração do exemplo inicial.

Veja o exemplo do pdf.

E um detalhe importante, se desejarmos alguma fonte diferente como as Google Fonts, vamos precisar configurar um diretório para as fontes no componente Dompdf, isto é feito através do array de opções. Depois é só usar as fontes normalmente no documento HTML.

$dompdf = new Dompdf([
    'enable_remote'=>true,
    'fontDir' => './fonts',
    'fontCache' => './fonts',
    'tempDir' => './fonts',
]);

Bom era isso espero que criar arquivos PDF tenha ficado um pouco mais fácil, para mim ficou 🙂

T++!