Generating Invoices with Zend_Pdf

The PDF format is currently the most used format to exchange documents. If you provide your website users with printable versions of invoices, event tickets and other similar documents, you’ll most likely want to generate them as PDFs on the fly. In this article you will see how you can use Zend_Pdf to auto-generate PDF invoices.

Getting Started

Zend_Pdf allows you to create new PDF documents or load existing ones and modify them. While creating a new document isn’t that difficult, it makes more sense to create a template of the invoice with the information that doesn’t change (company name, address, logo, etc.). It’s better to design your invoice in a program like Microsoft Word or LibreOffice Writer and export it as a PDF. Then you can update the PDF with Zend_Pdf with customer and transaction-specific information. You’ll save coding time because you don’t have to position every element, and it will take less time for the PDF to be generated.

This is what the invoice template I will be using in this article looks like. Your invoice can look different depending on your needs, but the concepts in this article will remain the same.

invoice template

If this is the first time you’re using Zend Framework, download the latest version from www.zendframework.com, upload and unpack the compressed file on your server, and use the following lines at the top of your script to configure the class autoloader. The autoloader will let you create new objects without having to explicitly include all the required files in your code.

<?php
define("ZF_PATH", realpath("/path/to/zf/library/"));
set_include_path(get_include_path() . PATH_SEPARATOR . ZF_PATH);
require_once "Zend/Loader/Autoloader.php";
$loader = Zend_Loader_Autoloader::getInstance();

Remember to change the value of the ZF_PATH constant to the path where you uploaded the copy of the Zend Framework library.

Editor Note Oct 14 2012: The accompanying code on GitHub has been updated to use Composer for installing Zend Framework. As ZF2 is not backwards compatible with ZF1, the version has been locked at 1.11.11. Please refer to the code for suitable ZF_PATH.

Loading the PDF Template

Zend_Pdf allows you to load an existing PDF document with the static load() method.

<?php
// load the invoice
$invoice = Zend_Pdf::load("/path/to/invoice-template.pdf");

The load() method reads the file at the given path and returns an instance of a Zend_Pdf object which manages everything related to the PDF.

Zend_Pdf represents each page of the PDF as instances of the Zend_Pdf_Page object. Its public field pages is an array of the objects in the same order the pages are found in the document. You can benefit from this behavior if you have a PDF document with many pages and you want to reorder its pages; whichever order the Zend_Pdf_Page objects are in the pages array, that is the order the pages will be rendered in the final document.

<?php
// access the first page
$page = $invoice->pages[0];

Specifying Fonts and Colors

When you write text to the PDF page, you are actually drawing shapes that looks like text, and these shapes are defined by the chosen font. So before writing/drawing text, you must specify which font you want to use. The Zend_Pdf_Font class is used to create a font resource which you then use to specify the font used when placing text.

There are two ways to load a font. The first is to use the static method fontWithName() and provide one of the following constants that represent the 14 standard fonts that all PDF viewers support:

  • Zend_Pdf_Font::FONT_COURIER (_BOLD, _ITALIC, _BOLD_ITALIC)
  • Zend_Pdf_Font::FONT_HELVETICA (_BOLD, _ITALIC, _BOLD_ITALIC)
  • Zend_Pdf_Font::FONT_TIMES (_BOLD, _ITALIC, _BOLD_ITALIC)
  • Zend_Pdf_Font::FONT_SYMBOL
  • Zend_Pdf_Font::FONT_ZAPFDINGBATS

The second way is to provide the path to the font file that resides on your file system to the static method fontWithPath(). Make sure that the font is a TrueType font or else fontWithPath() will throw a Zend_Pdf_Exception.

<?php
//loading a font by its name
$font = Zend_Pdf_Font::fontWithName(Zend_Pdf_Font::FONT_TIMES_BOLD);

//load font from file system
$font = Zend_Pdf_Font::fontWithPath("/path/to/myfont.ttf");

The font resource is then passed to the Zend_Pdf_Page‘s setFont() instance method as the first parameter, and the font size measured in points as the second parameter (1 point = 1/72 inches). The represented font will then be used whenever text is drawn to that page.

<?php
$page->setFont($font, 12);

It’s not mandatory to set a color for text before drawing it; black will be used by default if none is specified. But if do you want to use a certain color, you can specify one with one of the Zend_Pdf_Color* objects. Zend_Pdf supports three different color spaces; you can use Gray Scale, RGB, or CMYK. Also, you can specify colors using HTML-style notation.

<?php
// Gray Scale colors range from 0.0 (black) to 1.0 (white)
$color = new Zend_Pdf_Color_GrayScale(0.7);

// RGB uses 3 float values from 0.0 to 1.0 for each color component
$color = new Zend_Pdf_Color_Rgb($r,$g,$b);

// CMYK uses 4 float values from 0.0 to 1.0 for each color component
$color = new Zend_Pdf_Color_Cmyk($c,$m,$y,$k);

// HTML uses any valid color name or hex notation
$color = new Zend_Pdf_Color_HTML("blue");
$color = new Zend_Pdf_Color_HTML("#FF52ED");

The color is then passed to the setFillColor() method of the Zend_Pdf_Page instance.

<?php
$page->setFillColor($color)

Adding Content

Drawing text on a page is done using Zend_Pdf_Page‘s drawText() instance method. It takes a string of text, and the X and Y coordinates where you want the text to be placed. The coordinates are measured in points, and the origin 0,0 is placed at the bottom-left corner of the page. An increasing X value moves the position towards the right, and an increasing Y value moves the position up.

PDF X,Y coords

Typically you’ll retrieve and calculate information to put on the invoice from a database, but for the sake of example I’ve come up with the following:

<?php
$customerName = "Angelina Jolie";
$invoiceId = "DF-00025786423";

// items in the array are product description,
// quantity purchased, unit price, and total price
$items = array(array("Golden Globe Polish", 1, 25.50, 25.50),
               array("Trophy Shelf", 2, 180.00, 360.00),
               array("DIY Tattoo Kit", 1, 149.99, 149.99));

$subtotal = 535.49;
$discount = 10;
$amountDue = 481.94;

The trickiest part is finding the correct location to place the text on the page and you may require some trial and error even if you’ve meticulously measured your distances. The good news is that since the template elements are static, once you know what coordinates to use they won’t change.

<?php
// specify font
$fontBold = Zend_Pdf_Font::fontWithName(Zend_Pdf_Font::FONT_TIMES_BOLD);
$page->setFont($fontBold, 12);

// specify color
$color = new Zend_Pdf_Color_HTML("navy");
$page->setFillColor($color);

$page->drawText($customerName, 110,641);
// another font
$fontNormal = Zend_Pdf_Font::fontWithName(Zend_Pdf_Font::FONT_TIMES);
$page->setFont($fontNormal, 12);

// invoice information
$page->drawText($invoiceId, 420,642);
$page->drawText(date("M d, Y"), 420,628);
$page->drawText('$' . number_format($subtotal, 2), 510,143);
$page->drawText($discount . "%", 510,123);
$page->drawText('$' . number_format($amountDue, 2), 510,103);

// purchase items
$posY = 560;
foreach ($itmes as $item) {
    $page->drawText($item[0], 50, $posY);
    $page->drawText($item[1], 350, $posY);
    $page->drawText(number_format($item[2], 2), 430, $posY);
    $page->drawText(number_format($item[3], 2), 510, $posY);
    $posY -= 22.7;
}

Rendering the Invoice

After adding the information to the template, you can either save it to a different file (be careful not to overwrite your template!) or send it to the browser. To save it, you use Zend_Pdf‘s instance method save().

<?php
$invoice->save($pathToFile);

Using the instance method render() will return the PDF document as a string. This is probably what you want if you are generating a document on the fly and want the user to download it without saving copy of the document on your server first. You can output the correct HTTP headers so the browser will know how to handle the file, and then send the document string.

<?php
// instruct browser to download the PDF
header("Content-Type: application/x-pdf");
header("Content-Disposition: attachment; filename=invoice-". date("Y-m-d-H-i") . ".pdf");
header("Cache-Control: no-cache, must-revalidate");

// output the PDF
echo $invoice->render();

Summary

Now you know the basics of using Zend_PDF to create PDF documents, though there are still many more features available than the ones I’ve mentioned in this article. The source code for this article is available on GitHub if you’d like to clone it and experiment. And of course, feel free to leave comments with any problems you encounter.

Image via Christina DeRidder / Shutterstock

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • http://www.georgedina.info George Dina

    When made an invoice generator for a client I used fpdf.
    This is way cooler, it supports PDF templates.

    • http://fawadafr.com Fawad Rashidi

      I agree. This method is much better than fpdf. Thank you very much Ahmed Shreef for the great article. I look forward to your future articles.

  • Guillaume Maïssa

    If you need to use unicode font with your PDF (for japanese characters for instance), Zend_Pdf is unable to partially incorporate the font: you need to integer it all and not only the code of the characters used.

  • Erwin

    Hello,
    Nice tutorial, for some reason the pdf gets rendered upside down, but the used template is correct. Do you know the reason of this?
    Erwin

    • http://shreef.com Ahmed Shreef

      I don’t know why, still you can rotate the page using Zend_Pdf_Page’s rotate() method.

      rotate($x, $y, $angle);
      
  • http://www.dreamtree.co.in jnanendra

    Thanks for help.

  • Rabiek

    First of all great work.
    So if you would for example get the items from the database, so how would you build the array ? I mean the coordinates for each item dynamically!!
    One more thing is when you have a long list of items, I guess you have to count how many items do you have !! if count($item)> 30 then use template long_templ.pdf

    • RabieK

      One more thing, how do I add a break line to Description as a wrapped text (n and wouldn’t work )

      • http://shreef.com Ahmed Shreef

        hey Rabiek,
        “So if you would for example get the items from the database, so how would you build the array ? I mean the coordinates for each item dynamically!!”
        You have to use the font object to know the width of each char you will draw. this will help you know when you have to start writing to the next line. check the following example.

        $font = Zend_Pdf_Font::fontWithName(Zend_Pdf_Font::FONT_TIMES_BOLD);
        
        $startX = 0;
        $startY = 0;
        $page-&gt;setFont($font , $fontSize);
        
        $x = $startX;
        for ($i = 0; $i &lt; strlen($text); $i++) {
            $page-&gt;drawText($text[$i], $x, $startY);
            $width = $font-&gt;widthForGlyph( $font-&gt;glyphNumberForCharacter($text[$i]) );
            $x += $width / $font-&gt;getUnitsPerEm() * $fontSize;
        }
        

        “One more thing is when you have a long list of items, I guess you have to count how many items you have !! if count($item)> 30 then use template long_templ.pdf”
        exactly, or just write the extra items to one more invoice, depending on your business.

        “One more thing, how do I add a break line to Description as a wrapped text (n and wouldn’t work )”
        This won’t working, you have to start writing to a new line whenever you know that the current line is full. the previous example explains the idea :)

  • Anand Singh

    Great Article.

  • Jason Warren

    I’m trying to get my information out of my database as follows:
    $items = array(array(mysqli_fetch_array(mysqli_query($dbc, “CALL getOrderContents_again($orderId)”), MYSQLI_ASSOC)));

    This is producing some major errors and won’t let me produce the pdf. What am I doing wrong?

  • Aj

    Hi, I followed you tutorial and is very helpful. However, I have encountered a problem that am failing to solve. When you save the document, alter the code and save again, the saved document is properly updated. However, when I use the render method, alter the source code and render again, the new changes are not reflecting. Unless I changed the entire folder name and supply a new url, only then do hte new changes reflect. What do I need to do to make the render automatically change the display when I provide different client details? Looking forward to your response.

  • Aja

    Ohhhh…. have found the solution. It turns out firefox does reload pages that are run directly from the server so for it to force refresh, have to use the post method

  • Ron hiit

    Do you have a Facebook fan page for your site?
    Ron

  • http://www.ilduca69.it ilduca69

    How about including this metod in a wordpress based website?

    • http://shreef.com Ahmed Shreef

      it gonna be as simple as creating a typical wordpress plugin. nothing special is needed.

  • Rick

    this is great stuff – but can it render images? some invoices need a company icon. I have been using reportLab, which is pretty good and can do images. Just wondering if this might be simpler.

    • http://shreef.com Ahmed Shreef

      Rick, sure it can and here is an example
      $image = Zend_Pdf_Image::imageWithPath(‘image.jpg’);
      $page->drawImage($image, 100, 100, 400, 300);

  • John

    Hi ,
    First of all great tutorial on generating pdf files at the programming side.Whether this set up is possible at codeigniter framework or did you wrote any libraries for generating pdf for the codeigniter framework?