简体   繁体   中英

Convert html with styling (css) to pdf using PHP

I'm having some problems with my code! I am trying to convert my html invoice to a pdf invoice. After a payment is made the invoice is generated by the code below. Then the pdf file gets stored on the server and sent to the user by mail. This is the invoice I'm using: invoice . But when I convert the html to pdf the styles are not applied and the images don't show.

This is my PHP code to convert the html to a pdf using DomPDF:

<?php
require 'vendor/autoload.php';
// reference the Dompdf namespace

$files = glob("/invoices/*.pdf");

foreach ($files as $file) include_once($file);

// instantiate and use the dompdf class
$dompdf = new Dompdf();
$dompdf->loadHtml($html);
$dompdf->setPaper('A4', 'portrait'); //landscape / portrait

$options = new Options();
$options->set('isRemoteEnabled', true);
$options->set('defaultFont', 'OpenSans');

// Render the HTML as PDF
$dompdf->render();

$output = $dompdf->output();
file_put_contents($_SERVER['DOCUMENT_ROOT'] . "/invoices/" . $order_id . ".pdf", $output);

I think my problem lies with the HTML code.

The problem with the css

I've tried a couple of things but none of these worked for me: Here's what I tried:

Try 1

I've tried adding the css with a link tag like this:

$html.= '<link type="text/css" href="/absolute/path/to/pdf.css" rel="stylesheet" />'; I've also tried adding the link like a normal (in the head of the file), but both these times I didn't got it working.

Try 2

I've also tried putting the css in a style tag in the head like this:

<style>
    /* my css here */
</style>

This didn't work eighter. I'm running out of ideas for the css .

The problem with the images

When the html code contains a img like this:

<img src="/path/to/file" alt="" />

The image doesn't show up. Not even when I change the src to https://pathtoimg.com/path/to/file/img.png

Hopefully, you know the solution to the image and the css problems I'm struggling with! Thanks already for your effort!

Edit

I'm open to suggestions for other libraries or another approach. I still haven't found a stable solution so I may be looking in the wrong direction. If there are any other solutions out there please let me know!

this won't be a full answer but I can at least help you with some of your problems regarding the images and explain why Dompdf won't work with the invoice .

The issues

First of all, looking at your code, you also seem to use outdated functions set(..) and $options is also not passed to Dompdf . New examples can be found in the develop branch So we can clear the first problems with the following:

// Include autoloader
require 'vendor/autoload.php';
// reference the Dompdf namespace

$files = glob("/invoices/*.pdf");

foreach ($files as $file) include_once($file);

// Reference the Dompdf namespace
use Dompdf\Dompdf;
//Also reference the options namespace
use Dompdf\Options;

//First initialise the options
$options = new Options();
//This way of setting options is new see: https://github.com/dompdf/dompdf/issues/2181

$options->setIsRemoteEnabled(true);
$options->setDefaultFont('OpenSans');
// Instantiate and use the dompdf class with the options
$dompdf = new Dompdf($options);

// Load content from html file
$html = ...; //file_get_contents("invoice.html");
$dompdf->loadHtml($html);
$dompdf->setPaper('A4', 'portrait'); //landscape / portrait



// Render the HTML as PDF
$dompdf->render();
$output = $dompdf->output();
file_put_contents($_SERVER['DOCUMENT_ROOT'] . "/invoices/" . $order_id . ".pdf", $output);

With this code we get the following result:

(Used a jsFiddle to prevent the images from taking up too much space. Now they are inline)

 <img src="https://i.stack.imgur.com/86xNQ.png" width="600px" /> <img src="https://i.stack.imgur.com/coyyn.png" width="600px" />

This way, at least the logo is rendered. So what is left? Looking at the result, only part of the html is rendered properly. So the css files are in fact working. But the only problem here is that the invoice uses, among other properties, flex like display: flex and such. This, however, is not supported, yet, as can be seen in this GitHub issue . That is why not all of the elements will be rendered properly.

As of now, a possible solution would be to take a screenshot with html2canvas and 'convert' it into a pdf with jsPdf as can be seen in this SO answer . But I'm not sure if that is a possibility for your use case.

Possible solution

Anyhow, since Snappy has no support for flex as well , I have created an adapted example for this specific case with html2canvas (which does support flex) and jsPdf .

First of all on, for example, invoice.php add the following php function on top of the page. This function toBase64 will convert any 'foreign' svg to a local base64 string, which can be handled by html2canvas :

function toBase64($url) {
  $data = file_get_contents($url);
  $base64 = 'data:image/svg+xml;base64,' . base64_encode($data);
  return $base64;
}

To only make a pdf of the actual invoice, add id="invoice" to <div class="card-body p-5 p-md-7 p-lg-11"> Which seems to be the container of the invoice.

To use the php function effectively, change the <img> tag with the logo to this:

<img class="mb-2" src="<?= toBase64('https://streamlabs.nyc3.cdn.digitaloceanspaces.com/assets/svg/logos/logo-short.svg')?>" alt="Logo">

Then change the onclick function of the "print" button like so:

<button type="button" class="btn btn-info transition-3d-hover" onclick="showPDF();">
          <i class="fas fa-print mr-2"></i>
          Print
        </button>

At last, add this javascript function at the bottom of the page:

function showPDF() {
   //Create screenshot of body
   html2canvas(document.getElementById('invoice'), {scale: 3,scrollY: -window.scrollY}).then(canvas => {
      var imgData = canvas.toDataURL('image/jpeg');
      //add the image to, and create the pdf
      var pdf = new jsPDF('P', 'pt', [canvas.width, canvas.height]);
      pdf.addImage(imgData, 'jpeg', 0, 0, canvas.width, canvas.height);

      //set the pdfFinal to be later downloaded
      //convert the pdf to a base64 string to make it uploadable
      let output = btoa(pdf.output());
      $.ajax({
         method: "POST",
         url: "upload.php",
         data: {data: output, fileName: 'order-123.pdf'},
      }).done(function(data) {
         console.log('all done');
         //view errors
         console.log(data);
      });
        
        //Then, let the client print the document. 
        window.print();
      });
}

This basically takes a screenshot of the invoice, creates a base64 string of it, and puts the base64 string in the pdf and uploads it to the server. In this example upload.php is the page which actually saves the pdf on the server like so:

if(isset($_POST['data'])){
  $data = base64_decode($_POST['data']);
  if (isset($_POST['fileName'])) {
    $file = $_POST['fileName'];
  } else {
    $file = "order-" . rand(10,1000) . ".pdf";
  }
 file_put_contents($file, $data);
 echo "Succes";
} else {
  echo "No Data Sent";
}
exit();

It will create the following pdf:

 Chrome and firefox <img src="https://i.stack.imgur.com/m4gBc.png" width="600px" /> Safari <img src="https://i.stack.imgur.com/w2x8K.png" width="600px" />

As you can see, it works better on chrome and firefox than on safari, because fontawesome and html2canvas don't work quite well together on safari (don't know why).

I hope I could clear up some things. If you have any questions, please comment!

Edit

Because I couldn't find any other 'Dom to pdf' on the serverside which support 'flex' I've created an example which works on the client side.

I used mpdf before, so decided to give it a try. Using xampp, php 7.4.8 on my windows 10

 composer require mpdf/mpdf

Code

<?php
//Fadil Eldin
//8/16/2020
require_once __DIR__ . '/vendor/autoload.php';
$mpdf = new \Mpdf\Mpdf();
$html = file_get_contents('https://gostreamlabs.com/front/html/pages/invoice.html');
$mpdf->WriteHTML("");
$mpdf->WriteHTML($html);
$mpdf->Output();
?>

Output 在此处输入图像描述

second page在此处输入图像描述

Make sure Your HTML start with
<!DOCTYPE html>
and also contain the following tag in head
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>

Update Dompdf script with following

$dompdf = new Dompdf();
$dompdf->setPaper('A4', 'portrait');
$dompdf->set_option( 'isHtml5ParserEnabled', true);
$dompdf->set_option( 'isRemoteEnabled', true);
$dompdf->set_option( 'defaultMediaType', 'all' );
$dompdf->set_option( 'isFontSubsettingEnabled', true );
$dompdf->set_option('defaultFont', 'OpenSans');
$dompdf->loadHtml( $html, 'UTF-8' );
$dompdf->render();
// Attachment value 1 for download and 0 for preview PDF
//$dompdf->stream( 'testing.pdf', array( 'Attachment' => 1 ) ); 
$output = $dompdf->output();
file_put_contents($_SERVER['DOCUMENT_ROOT'] . "/invoices/" . $order_id . ".pdf", $output);

Please include $dompdf->set_base_path('localhost/exampls/style.css');

<?php
require_once "dompdf/dompdf_config.inc.php";
$dompdf = new DOMPDF();

$html ="
<html>
<head>
<link type="text/css" href="localhost/exampls/style.css" rel="stylesheet" />
</head>
<body>
<table>
  <tr >
    <td class='abc'>testing table</td>
    </tr>
  <tr>
   <td class='def'>Testng header</td>
  </tr>
</table>
</body>
</html>";

$dompdf->load_html($html);
$dompdf->render();
$dompdf->set_base_path('localhost/exampls/style.css');
$dompdf->stream("hello.pdf");
?>

Did you try converting your images to base64 and then add as url? It would make it independent to urls as well.

this can help you to convert to base64 How to convert an image to base64 encoding?

for CSS i tried with and it works for me.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM