Improved PDF generation with RE Framework RE_Pdf

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.