Printing Magento currencies and prices on a PDF

This is a short little post, primarily just to remind me. We need to convert to html entities or symbols like $ and £ won’t print. There are lots of ways of getting the price from the product object, but getFinalPrice seems to be the simplest.

$raw_price = $this->getProduct()->getFinalPrice(); // string "£3,295.00"
$price =  htmlspecialchars(Mage::helper('core')->currency($raw_price,true,false));
Advertisements

Google QR Codes and Zend_PDF

Now that we can generate QR codes using the Google Chart API, and we have a controller action that serves it up for us with the correct .PNG header (see my previous post), we need to get it where it’s of any use – on paper!

So following on from my last post about Zend_Pdf, we can now do the following to get it in our PDF:

//draw qr code
 $target = 'http://example.co.uk/download/qr?target=http://www.example.co.uk/properties/view/id/'.$prop->getID();
 $img = file_get_contents($target); 
 file_put_contents(APPLICATION_PATH.'/uploads/tmp/'.$prop->getID().'-qr.png',$img);
 $image = Zend_Pdf_Image::imageWithPath(APPLICATION_PATH.'/uploads/tmp/'.$prop->getID().'-qr.png');
 $page->drawImage($image, 13, 13, 94,94);
 unlink(APPLICATION_PATH.'/uploads/tmp/'.$prop->getPid().'-qr.png');

The first line of code is the url of our download controllers QR action.  file_get_contents stores the image in memory, which we then save into a temp folder using file_put_contents. These commands are the equivalent of a lot of fopen etc, so use them if you don’t already!  This example uses my property object to make a dynamic filename, but this isn’t necessary, but I don’t need to tell you that!

We then use Zend_Pdf to load our png, and when drawing an image, the numbers in the argument are x1, y1, x2, y2 being bottom left and top right corners respectively. Finally, ditch the temp file with the unlink command. Have Fun!

Zend_PDF with templates

Zend_PDF rocks! It makes drawing a PDF almost child’s play. The main time eater though is the trial and error with the co-ordinates. So I stopped all this nonsense, and used Illustrator.

In adobe Illustrator, set the ruler origin to the bottom left of the page. Now in the transform pallette you can get the XY co-ordinates! Once you have created a mock up of the PDF, save a copy minus all the dynamic stuff, which we’ll drop in from our controller action.

First up, ditch the view and layout:

//disable the layout
 Zend_Layout::getMvcInstance()->disableLayout(); 
 $this->_helper->viewRenderer->setNoRender();

Next we create the Zend_PDF and templates, and set default fonts and colours:

//create a zend pdf
 $template = Zend_Pdf::load(APPLICATION_PATH.'/layouts/pdfs/proplist.pdf');
 $this->_pdf = new Zend_Pdf();
 $extractor = new Zend_Pdf_Resource_Extractor();
//set font
 $font = Zend_Pdf_Font::fontWithName(Zend_Pdf_Font::FONT_HELVETICA);
 $bold = Zend_Pdf_Font::fontWithName(Zend_Pdf_Font::FONT_HELVETICA_BOLD);
//set colors
 $black = new Zend_Pdf_Color_Html('#000000');
 $red = new Zend_Pdf_Color_Html('#ED1F24');
 $green = new Zend_Pdf_Color_Html('#009444');
 $blue = new Zend_Pdf_Color_Html('#0069B5');

We will have so many records per page, so the next bit goes in a for loop:

//add page
 $page = $extractor->clonePage($template->pages[0]);

 //get properties for this page
 $props = $this->_helper->paginator($rows,6);

We can set colours and fonts like this:

$page->setFillColor($black);
$page->setFont($bold, 11);

Add the page to the pdf like this

$pdf->pages[] = $page;

I created a function for getting the coordinates of each element, which adjusts depending which record on the page we are dealing with. This could be tweaked to suit your own project.

private function getCoordinates($row, $elem)
 {
 $xCoords = array(
 'header' => 21,
 'avail_date' => 419,
 'price' => 541,
 'summary' => 21,
 'hol_dep' => 70,
 'dep_date' => 128,
 'board_up' => 217,
 'keys' => 267,
 'tax_band' => 333,
 'on_web' => 388,
 'ref' => 437,
 'images' => 560,
 'condition' => 59,
 'move_date' => 130,
 'gas_safety' => 221 ,
 'hwsa' => 271 ,
 'let_fee' => 327,
 'man_fee' => 391,
 'landlord' => 452,
 'lha' => 551,
 'comment' => 64
 );

 $yCoords = array(
 'header' => 759,
 'avail_date' => 759,
 'price' => 759,
 'summary' => 739,
 'hol_dep' => 692,
 'dep_date' => 692,
 'board_up' => 692,
 'keys' => 692,
 'tax_band' => 692,
 'on_web' => 692,
 'ref' => 692,
 'images' => 692,
 'condition' => 680,
 'move_date' => 680,
 'gas_safety' => 680,
 'hwsa' => 680,
 'let_fee' => 680,
 'man_fee' => 680,
 'landlord' => 680,
 'lha' => 680,
 'comment' => 667
 );

 $z = array();
 $z['x'] = $xCoords[$elem];

 switch($row)
 {
 case 1:
 default:
 $z['y'] = $yCoords[$elem];
 break;
 case 2:
 $z['y'] = $yCoords[$elem] - 127.5;
 break;
 case 3:
 $z['y'] = $yCoords[$elem] - (127.5 * 2);
 break;
 case 4:
 $z['y'] = $yCoords[$elem] - (127.5 * 3);
 break;
 case 5:
 $z['y'] = $yCoords[$elem] - (127.5 * 4);
 break;
 case 6:
 $z['y'] = $yCoords[$elem] - (127.5 * 5);
 break;
 }
 return $z;
 }

Finally we can draw the text like this:

$text = '?';
 $page->setFillColor($blue);
 $z = $this->getCoordinates($row, 'condition');
 $page->drawText($text, $z['x'], $z['y'],'UTF-8');

Other things include fitting text to a specific width, or making a paragraph of multi lines:

private function widthForStringUsingFontSize($string, $font, $fontSize)
 {
 $drawingString = iconv('UTF-8', 'UTF-16BE//IGNORE', $string);
 //$drawingString = $string;
 $characters = array();
 for ($i = 0; $i < strlen($drawingString); $i++) {
 $characters[] = (ord($drawingString[$i++]) << 8 ) | ord($drawingString[$i]);
 }
 $glyphs = $font->glyphNumbersForCharacters($characters);
 $widths = $font->widthsForGlyphs($glyphs);
 $stringWidth = (array_sum($widths) / $font->getUnitsPerEm()) * $fontSize;
 return $stringWidth;
 }
private function getFontSizeForWidth($text,$font,$desiredSize,$maxWidth)
 {
 do 
 {
 $stringWidth = $this->widthForStringUsingFontSize($text, $font, $desiredSize);
 $desiredSize = $desiredSize - 0.5; 
 }
 while($stringWidth > $maxWidth);
 return $desiredSize + 0.5;
 }
private function getWrappedText($string, $font, $fontsize,$max_width)
 {
 $wrappedText = '' ;
 $lines = explode("\n",$string) ;
 foreach($lines as $line) {
 $words = explode(' ',$line) ;
 $word_count = count($words) ;
 $i = 0 ;
 $wrappedLine = '' ;
 while($i < $word_count)
 {
 /* if adding a new word isn't wider than $max_width,
 we add the word */
 if($this->widthForStringUsingFontSize($wrappedLine.' '.$words[$i]
 ,$font
 , $fontsize) < $max_width) {
 if(!empty($wrappedLine)) {
 $wrappedLine .= ' ' ;
 }
 $wrappedLine .= $words[$i] ;
 } else {
 $wrappedText .= $wrappedLine."\n" ;
 $wrappedLine = $words[$i] ;
 }
 $i++ ;
 }
 $wrappedText .= $wrappedLine."\n" ;
 }
 return $wrappedText ;
 }

To align text to the right, we subtract the width:

$width = $this->widthForStringUsingFontSize($text, $bold, 12);
 $z = $this->getCoordinates($row, 'price');
 $z['x'] = $z['x'] - $width;

And if we need wrapped text:

$text = $this->getWrappedText($text, $font,10, 550);

Awesome! Another what used to be pain in the arse task is now a piece of cake (or zf rather lol)!