ZF-7147: when drawing text, allow to align text on left/right/center it
Description
Hi,
I felt the need to subclass Zend_Pdf_Page::drawText() to add possibility to align text on right (instead of left as default behaviour) or to center it horizontally. It would be real great to have it handled natively by ZF, it would increase a lot flexibility on pdf generation.
Below is the code used (it requires to be able to retrieve width of text to be drawn and makes use of code presented in ZF-7146).
<?php
require_once 'Zend/Pdf.php';
/**
* Represent an order/bill pdf
*/
class Library_Pdf_Base extends Zend_Pdf
{
/**
* Align text at left of provided coordinates
*/
const TEXT_ALIGN_LEFT = 'left';
/**
* Align text at right of provided coordinates
*/
const TEXT_ALIGN_RIGHT = 'right';
/**
* Center-text horizontally within provided coordinates
*/
const TEXT_ALIGN_CENTER = 'center';
/**
* Extension of basic draw-text function to allow it to vertically center text
*
* @param Zend_Pdf_Page $page
* @param string $text
* @param int $x1
* @param int $y1
* @param int $x2
* @param int $position
* @param string $encoding
* @return self
*/
public function drawText(Zend_Pdf_Page $page, $text, $x1, $y1, $x2 = null, $position = self::TEXT_ALIGN_LEFT, $encoding = null)
{
$bottom = $y1; // could do the same for vertical-centering
switch ($position) {
case self::TEXT_ALIGN_LEFT:
$left = $x1;
break;
case self::TEXT_ALIGN_RIGHT:
$text_width = $this->getTextWidth($text, $page->getFont(), $page->getFontSize());
$left = $x1 - $text_width;
break;
case self::TEXT_ALIGN_CENTER:
if (null === $x2) {
throw new Exception("Cannot center text horizontally, x2 is not provided");
}
$text_width = $this->getTextWidth($text, $page->getFont(), $page->getFontSize());
$box_width = $x2 - $x1;
$left = $x1 + ($box_width - $text_width) / 2;
break;
default:
throw new Exception("Invalid position value \"$position\"");
}
// display multi-line text
foreach (explode(PHP_EOL, $text) as $i => $line) {
$page->drawText($line, $left, $bottom - $i * $page->getFontSize(), $encoding);
}
return $this;
}
/**
* Return length of generated string in points
*
* @param string $string
* @param Zend_Pdf_Resource_Font $font
* @param int $font_size
* @return double
*/
public function getTextWidth($text, Zend_Pdf_Resource_Font $font, $font_size)
{
$drawing_text = iconv('', 'UTF-16BE', $text);
$characters = array();
for ($i = 0; $i < strlen($drawing_text); $i++) {
$characters[] = (ord($drawing_text[$i++]) << 8) | ord ($drawing_text[$i]);
}
$glyphs = $font->glyphNumbersForCharacters($characters);
$widths = $font->widthsForGlyphs($glyphs);
$text_width = (array_sum($widths) / $font->getUnitsPerEm()) * $font_size;
return $text_width;
}
}
So far it did the job for me. We could even go further and add an $y2 argument to the function to allow to center text vertically too. This is extremely useful when drawing table cells, if we want to center our text within cell (both horizontally & vertically).
Along with ZF-7145 which extend ::drawText() to render multi-line text, it improves a lot text-drawing functionnalities of Zend_Pdf.
Regards, Remy
Comments
Posted by Remy Damour (remy215) on 2009-06-27T17:43:27.000+0000
normally I would have extended Zend_Pdf_Page instead of Zend_Pdf, but as stated in http://framework.zend.com/issues/browse/ZF-7144, subclasses of Zend_Pdf_Page are hard to transparently use with Zend_Pdf => as a quick fix I subclassed Zend_Pdf and thus add extra first argument to function call (Zend_Pdf_Page $page).
Posted by Christopher Weldon (neraath) on 2009-10-11T12:23:54.000+0000
I'm gonna have to say this particular implementation is very useful. I just implemented it on a project and it seems to be working fairly well. There are some standards violations in it that require correction, but I'm definitely voting to include this on the next minor release.
Posted by Fernando Morgenstern (fernando_mm) on 2009-11-12T07:46:14.000+0000
Are there any plans to include this on the next ZF version? It is indeed really useful!
Posted by Quintin Venter (quintinventer) on 2010-03-15T08:24:56.000+0000
This code helped me a lot when I needed to create pdfs with aligned text. Thanks Remy.
I got a text wrapper from the following blog http://blog.alfbox.net/index.php/2008/….
With these two examples I could create pdfs with wrapping text that could also align left, right and center. There was a minor issue with the center text as a very long string would calculate a negative value for the left x coordinate.
I had to move the loop at the bottom of the function up to make up for this so that each individual line would get recalculated.
Example code to follow:
Posted by John Bertucci (fozzyuw) on 2010-05-18T07:02:40.000+0000
Just a note, while Remy's code is very useful, it is not multi-byte character safe. Special characters such as the trademark sign will throw off the size calculation and the alignment won't line-up anymore.
Posted by John Bertucci (fozzyuw) on 2010-05-18T08:06:01.000+0000
Just as a follow up (as I work to understand how this Zend_PDF works with regards to fonts and widths) it appears it might not be Remy's code, per say, that's at fault, but the Zend_Pdf_Resource_Font class/functions.
I ran two test words "T E S T" and "T É S T" and looked that the results of the functions "glyphNumbersForCharacters" and "widthsForGlyphs" that Zend_Pdf_Resource_Font returns:
T 53 611 T 53 611 1 278 1 278 E 38 667 É 293 722 122 1000 1 278 1 278 S 52 667 S 52 667 1 278 1 278 T 53 611 T 53 611
total width 3390 4390
What you can see is, special characters (I've tried this on É (E with accent acute), © (copyright), ™ (trademark), • (bullet), and they all have similar results.
Both functions see these special characters as multiple characters. In the above example, 'É' is assigned a glyph number 293 and 122. The first glyph's size is larger than the non-accented "E" by 55. On top of that, it adds an additional 1000 width by adding that extra glyph space.
End result... Remy's code breaks because because of these special characters. The issue seems to be with the Zend_Pdf_Resource_Font class and these functions. Logically, Remy's code works. However, it could be misunderstanding what it should expect from the glyph functions or the glyph functions aren't working right.
I would think, for a special character like 'É', you should get a different glyph number (293 instead of 38) but it should have the same width of 'E' and there shouldn't be an extra glyph added.
I've not looked at how these functions work, I'll be doing that next, but I'm not sure I'll know what's going on at that point. There might need to be a different way to determine the size of a given string.
Posted by Karl Mikkelsen (kingkarl85) on 2010-06-28T18:29:38.000+0000
i don't think you should extend drawText as this will break all existing code hooking into (param changes).
I would be inclined to call this drawTextBox and link in the multi line code and introducte a $y2 for the height component.
This would make things very flexible as you would be using the regular draw text to produce the line.