Improved PDF generation with RE Framework RE_Pdf

Posted on March 20, 2009
Tags: , ,

I’ve previously written a post on creating a PDF generator class using Zend_Pdf, which detailed some Zend_Pdf usage and introduced a class which could be used to create PDFs easily based on an XML configuration file or such.

Zend_Pdf, while generally quite good, has one big issue: It does not support word wrapping text!

There’s a new and still a bit obscure framework called RE Framework, which has an excellent PDF component - it includes support for word wrapping, better support for PDF templates, image loading etc.

Let’s check out how to use RE_Pdf to improve the earlier CU_PdfGenerator class!

The XML configuration

Let’s first modify the code which parses the XML file. We will also add a new feature: Setting dimensions for a text box.

RE_Pdf shares a similar interface as Zend_Pdf, so for this part the rewrite will be quite simple: We’ll just replace Zend_ with RE_:

$xml = simplexml_load_file('path/to/definition.xml');
$fontSize = (int)$xml->font['size'];
$fontFamily = (string)$xml->font['family'];
 
$generator = CU_PdfGenerator::create()
           ->setTemplate((string)$xml->base)
           ->setFont(RE_Pdf_Font::fontWithName($fontFamily), $fontSize);
 
foreach($xml->elements[0] as $element) {
    $key = (string)$element['key'];

    //New code to add support for width/height of textboxes!
    $w = 0;
    $h = 0;
    if(isset($element['width'])) {
        $w = (int)$element['width'];
        $h = (int)$element['height'];
    }
 
    $generator->setPosition($key, (int)$element['x'], (int)$element['y'], $w, $h);
    if(isset($element->font)) {
        $family = $fontFamily;
        $size = $fontSize;
 
        if(isset($element->font['family'])) {
            $family = (string)$element->font['family'];
        }
 
        if(isset($element->font['size'])) {
            $size = (int)$element->font['size'];
        }
 
        $font = RE_Pdf_Font::fontWithName($family);
        $generator->setFontForKey($key, $font, $size);
    }
}

So instead of creating new Zend_Pdf_Fonts, we use RE_Pdf_Font. We also added new code which grabs width and height from the XML configuration.

As a refresher, let’s have a look at the XML config too:

<pdf>
  <base>data/template.pdf</base>
  <font family="Helvetica" size="12" />
  <elements>
    <element key="firstname" x="10" y="10" />
    <element key="lastname" x="10" y="50">
      <font size="16" />
    </element>
    <element key="description" x="10" y="90" width="100" height="150" />
  </elements>
</pdf>

If you compare this to the old example, we’ve added a new element which also includes new attributes width and height.

Next, we’ll modify the actual generator class itself to support word-wrap.

Modifying the generator class

Again, we’re going to replace all references to Zend_Pdf_Font with RE_Pdf_Font. We’ll also modify the setPosition method to accept a width and a height. Finally, the getPdf() method needs to be modified to use RE_Pdf’s classes.

You can get the original code here.

You can just do a search and replace for Zend_ and replace with RE_ to start. That will cover all other places except the two methods we will look at next:

public function setPosition($key, $x, $y, $width = 0, $height = 0) {
  $this->_positions[$key] = array($x, $y, $width, $height);
  return $this;
}

Now setPosition accepts width and height - by default they are both 0, meaning that if they’re unset, there will be no word wrap or anything.

public function getPdf() {
  $pdf = new RE_Pdf_Document();

  //Need to load the template PDF file as an image:
  $template = RE_Pdf_Image::imageWithPath($this->_template);

  //We select the first page of the template as that's what we want to use
  $template->selectPage(1);

  for($i = 0, $dataCount = count($this->_data); $i < $dataCount; $i++) {
    //Need to draw the template on the new page for it to display:
    $page = $pdf->newPage();
    $template->draw($page, new RE_Rect(new RE_Point(), 
            new RE_Point($template->getPixelWidth(), $template->getPixelHeight())));
            
    $values = $this->_data[$i];
    foreach($values as $key => $value) {
      $font = $this->getFontForKey($key);
      $size = $this->getFontSizeForKey($key);
      if($font != null) {
        $page->setFont($font, $size);
      }
      else {
        $page->setFont($this->_font, $this->_fontSize);
      }
                    
      $position = $this->getPosition($key);
      if($position == null) {
        continue;
      }

      //Store position in variables to make it clearer                  
      list($x, $y, $width, $height) = $position;
      $yFromTop = $page->getHeight() - $y;

      //If there's no width, we draw text as a single line:
      if($width === 0) {                
        $page->drawText($value, $x, $yFromTop);
      }
      else {
        //otherwise we use RE's layoutmanager to create a wrapping text box:
        $text = new RE_Pdf_Text($value);
        RE_Pdf_LayoutManager::drawText($text, $page, new RE_Rect(
          $x, $yFromTop - $height, $width, $height
        ));
      }
    }
  }
                    
  return $pdf;
}

So there!

Now we can draw word wrapping textboxes in our PDF by simply adding a width and height to the XML configuration. Does it get any easier than this?

RE_Pdf supports various things that Zend_Pdf doesn’t. In my opinion, it’s easier to use than FPDF as well - I couldn’t find a way to use a template PDF using FPDF for example.

You can learn more about RE from their site, or by joining their IRC channel, #refw on irc.freenode.net. They are working on improving the documentation, too. When they get that done, they’ll have easily the best PDF component.

Comments or questions?

If you have any comments or questions about this post, feel free to email me to jani@codeutopia.net, or use any of the other methods on the contact page.