BY-NC-SA: tonx

Automating form creation with Zend_PDF

closeThe world moved on since this content was published 3 years 10 months 6 days ago. Exercise diligent care when following any instructions and see opinions in the time they were written. If you must have an updated version, please ask kindly through the contact page.

For my job, I need to fill out a form monthly. Till a short while ago, I opened Microsoft Publisher for this (the form was designed in this app), filled out the fields, saved as .pub file, export as PDF file and finally mailed the form. Given that the contents of the form are fully computable in 99% of the cases, there had to be a way to automate it. After some digging around, I settled on a work-flow where I would create a PDF of an empty form once, use that as a template and use Zend_Pdf to ‘paste’ the text on top of it, saving the end result as a new PDF.

While you do have a nice empty form, where you can easily visually identify where the content needs to go, the computer lacks eyes and only knows how many points there are in your page. To get the text pixel perfect (well, point perfect technically), you will have to try a lot, and optimize on the next run. Repeat a 100 times (depending on the amount of content, and how many fields you have).

To aid myself in getting a good start, I decided I needed to have a visual grid on the form, so I could get a rough idea of where what content needed to be placed. The code for that is as follows:

$input = '/tmp/empty-form.pdf';
$output = '/tmp/finished-form.pdf';
$pdf = Zend_Pdf::Load($input); //Open $input file as a new Zend_Pdf document
$page = $pdf->pages[0]; //we have only 1 page, so we can hard-code this
$page->addFont(Zend_Pdf::Courier, 9); //use a fixed width font at size 9
$width = $page->getWidth();
$height = $page->getHeight();
for($n = 0; $n &lt; $height; $n +=100) {
    for($m = 0; $m &lt; $width; $m += 100) {
        $page->drawText(ceil($n / 100) . '-' . ceil($m / 100), $m, $n); //write the x and y positions / 100 (else the page is too crowded) at position x and y)

When you open this PDF, it shows numbers like ’0-0′ to ’8-5′ on your page (if you use A4 format, else your end numbers can differ, but the idea is the same). With these codes, you will spot immediately that Zend_Pdf starts in the bottom-left corner, instead of at the top-left. Remember this for off-setting! Now that you have the rough offsets (multiply the digit by 100 to get the points offset), you can use your eyes or a ruler to get a close idea of at which x and y position your content needs to appear. With a couple of tries per field, you should now have the content exactly where it should be.

But then there is variable content… For left-aligned text, you can use a substr() call, to make sure the text fits in the allotted space. Other alignments are harder. For right-alignment and center-alignment, you will need to know how much points a character is wide. Luckily, we have Google for the rescue, and others have done the calculations for us. In the case of Courier, we quickly find that a character is ’0.6 * fontheight(size)_in_points’. So, a fontheight (or fontsize) of 9 means the character is 5.4 points wide. For left alignment, this gives the following offset:

$x = 452; //random x for demonstration - text will have to end at this position
$y = 180; //random y for demonstration
$foo = 'foobar';
$pdf->drawText($foo, $x-(strlen($foobar)*5.4), $y);

If you view the outputted PDF, you will see that the text starts strlen($foobar) characters before point $x, and will end exactly at point $x. Isn’t computing a good thing?

Now center-alignment. With the above code, you can probably already guess it, but just for completeness sake:

$x = 452; //random x for demonstration - this is the exact middle of the content area
$y = 180; //random y for demonstration
$foo = 'foo';
$test = 'o';
$pdf->drawText($test, $x-(strlen($test)*2.7), $y); //position text 'length*half_character_width' before the exact center
$y = 170; // line below previous $y
$pdf->drawText($test, $x-(strlen($foobar)*2.7), $y;

If you view the outputted PDF, you will notice that the o of $test and the first o of $foobar are exactly below each other. The f and the last o align at (respectively) the left and the right of the center. Perfect alignment. Since this logic may be a bit harder to grasp, here’s the logic in human terms:

Imagine the center of the content area as a vertical line. Any size on the left has to be the exact same size on the right:
1 char: We offset by -half_a_char, so the first half of the character is before the line, the second half is after the line -> perfectly centered.
2 char: We offset by -2*half_a_char = -1char, so the first character is before the line, the second is after the line -> perfectly centered.
3 char: We offset by -3*half_a_char = -1.5char, one and a half char is before the line, one and a half after the line -> perfectly centered.
etcetera, etcetera.

With 2 hours and close to a 100 tries, I automated the creation of the form, including a small web-form where I can select values from a drop-down list in case the form does vary this one time. With this effort, I saved myself roughly 200 EURO in bi-annual license costs (difference between 2 Microsoft Office packages – whether or not the package includes Publisher (and Access)), and 15 minutes of time per month. The generated PDF get’s moved to the general NAS, together with all the other iterations of the form. From there, I can attach it to an email and send it off.

I hope this write-up helps you in any way. If it does, please leave a comment below. If you have any questions, feel free to contact me.


Filed Under: Tutorial

Tags: , , , , , , ,

Released: on Jun 17, 2010 under a Creative Commons Attribution-NoDerivs (CC-BY-ND) licenseCC-BY-ND

Comments (1)

Trackback URL | Comments RSS Feed

  1. Dan Dorman says:

    I’ve found working with Zend_Pdf to be a real grind; that “visual grid” idea is a great tip. I’ll have to include that next time I’m generatin’ PDFs.