Zend Pdf Cell

Update! (3/31/2008): Now works with Zend Framework 1.5!
Update! (3/19/2008): I have submitted an official proposal for this to be included into the Zend Framework.
Update! (1/12/09): Dominik Deobald was kind enough to supply a UTF character encoding patch and bug fix. I have attached his fix as a .patch file at the end of the post.

I have recently been working with Zend_PDF to create PDF documents. One of the basic requirements for my project is the ability to center text within a screen. Needless to say, I was quite disappointed when I came to find out that Zend_Pdf doesn’t currently have any type of text layout support except for the exact position to place it.

I created a small extension to Zend_Pdf, and building off of an idea from FPDF, I created Zend_Pdf_Cell. Zend_Pdf_Cell is not currently supported by Zend, and is not officially in the Zend Framework, although I have posted my code for them to view it and give feedback (and hopefully have it incorporated into Zend Pdf). My first try I misunderstood parts of Zend_Pdf, but I have gone back and fixed it.

Features

These features have been most used by myself and mostly work.
* The ability to create a cell and place text in it.
* Specify the width and height of a cell
* Position a cell (one or more of these combined)
** To the left
** To the right
** At the bottom
** At the top
** Centered horizontally
** Centered vertically
* Align text within a cell
** Left
** Right
** Centered
** (To be done later) Justify
* Format different parts of the text in different fonts

Experimental

I just recently put these abilities in, so they may not fully work or even work properly.
* Create a border around the cell
* Word wrap text around the cell

Installation

To install this, just place the Cell.php file in Zend/Pdf/, then in your php file, just add:

include_once('Zend/Pdf/Cell.php');

Examples

The following is an example of how to use the Zend Pdf Cell:

pages[] =new Zend_Pdf_Page(Zend_Pdf_Page::SIZE_A4);
 $font=Zend_Pdf_Font::fontWithName(Zend_Pdf_Font::FONT_TIMES_ITALIC);     
 $pdf->pages[0]->setFont($font,12); 
 //Creates a cell in the specified page
 $cell=new Zend_Pdf_Cell($pdf->pages[0]);

 //adds a cell in the upper left with "Hello World"
 $cell->addText("Hello World");
 $cell->write();

 //creates a cell in the center of the page
 //To do top and right, then you would
 //or together POSITION_RIGHT and
 //POSITION_TOP.
 $cell=new Zend_Pdf_Cell($pdf->pages[0],
                                    Zend_Pdf_Cell::POSITION_CENTER_X |
                                    Zend_Pdf_Cell::POSITION_CENTER_Y);
 //add a 1 pixel border
 $cell->setBorder(1);
 //align to the right
 $cell->addText("The quick brown fox jumped over the lazy dog",
                        Zend_Pdf_Cell::ALIGN_RIGHT);
 $cell->write();
?>

If you have any questions or wish to report problems, please use the comment box below. I would also like to hear if you have successfully implemented this!

Files

Cell.php – Zend Framework 1.0.*
Cell.php – Zend Framework 1.5.*
(Updated 1/12/08)
UTF character patch by Dominik

This entry was posted in PHP. Bookmark the permalink.

39 Responses to Zend Pdf Cell

  1. Hi there. Thanks for this class.

    Under PHP 5.2.1 I get this:

    is_a(): Deprecated. Please use the instanceof operator Line 500

    Not too severe!

  2. Chaning line 500 onwards to:

    if (!($this->_page instanceof Zend_Pdf_Page)) {
    throw new Zend_Pdf_Exception(“The PDF page that the cell is attempting to write to is not a valid page.”);
    }
    if (!($this->_font instanceof Zend_Pdf_Resource_Font)) {
    throw new Zend_Pdf_Exception(‘No font has been set’);
    }

    Fixes this!

  3. Logan says:

    Thanks for pointing this out! I just fixed it. That’s what I get for not displaying notices.

    Please let me know if you come across any other issues.

  4. Pingback: PHPDeveloper.org

  5. Pingback: developercast.com » Logan Buesching’s Blog: Zend_Pdf_Cell

  6. This is a good idea but you should not name this class Zend_Pdf_Cell but instead use your own top level as written in the documentation.

    Your class should be called XXXX_Pdf_Cell.

  7. Mike DS says:

    Thanks for a nice extension to the Zend_Pdf, there seem to be a couple of bugs though, when I try to create a cell with content bigger than the cell is width, the script ends up in an infinite loop (_wordWrap calling addText, addText calling _wordWrap again, etc.) I get a “Segmentation fault”. For a simple text, addText is called more than 2000 times before it crashes.

    Any help on this would be greatly appreciated, especially from the writer himself.

    I’m trying to figure it out for myself now, but after a couple of hours of debugging, I’m still not very close to the solution it appears…

  8. Mike DS says:

    Hi again, this is my version of the _wordWrap function (it adds the lines not word by word, but line by line so it has better performance, it also lessens recursion).

    Here it goes:

    private function _wordWrap(&$section) {
    if ($this->isAutoWidth()) {
    $maxWidth=$this->_page->getWidth();
    } else {
    $maxWidth=$this->_width;
    }
    $lineWidth=$this->_text[$this->_lineNumber]['width'];
    //if adding this section does not overflow borders
    if ($lineWidth+$section['width'] _makeTextSection(array_shift($splitSection));
    $currentLine = ”;
    $currentWidth = $lineWidth;
    while (true)
    {
    $currentWidth += $currentWord['width'];

    if ($currentWidth > $maxWidth || $currentWord['text'] == null) //reset and go to new line
    {
    $this->addText($currentLine.’ ‘);

    if ($currentWord['text'] == null)
    break;

    $currentWidth = 0;
    $currentLine = ”;
    $this->newLine();
    continue;
    }

    $currentLine.= $currentWord['text'].’ ‘;
    $currentWord = $this->_makeTextSection(array_shift($splitSection));
    }
    return true;
    }

  9. Mike DS says:

    Hi Logan, I’ve found the solution to the bug (yes, it was a bug, it was not my error :-D).

    The main problem is that you split up each word ( explode(‘ ‘,$section['text'] ), afterwards you put it back together, so far so good.
    But then a test is done on the length of the word, it is tested without a trailing space (which is how it is going to be inserted).
    If the line is too big, it will be added and a new line will be created. The problem here is that we did not calculate the text as is, but the text without the spaces (one space character for each word), upon returning to the _wordWrap function it is in some cases (especially in texts with lots of spaces, or long line – ie. small fonts) possible that the space makes the line wider than the allowed maximum.
    So it will try to break it up again, and add it again as a line (again without the spaces), so we get into an endless loop that crashes the apache thread.
    So, the obvious solution is to calculate the width including the spaces.
    I’ve updated my code, if you wish I do the same for your code just mail me, but if you do it yourself you’ll get the credit ;-).
    I’ll post my new version of the _wordWrap function in a couple of minutes.

  10. Mike DS says:

    This is the new version:

    private function _wordWrap(&$section) {
    if ($this->isAutoWidth()) {
    $maxWidth=$this->_page->getWidth();
    } else {
    $maxWidth=$this->_width;
    }
    $lineWidth=$this->_text[$this->_lineNumber]['width'];

    if ($lineWidth+$section['width'] _makeTextSection(array_shift($splitSection).’ ‘);
    $currentLine = ”;
    $currentWidth = $lineWidth;

    while (true && $i $maxWidth || empty($splitSection)) //reset and go to new line
    {
    $this->addText($currentLine);

    if (empty($splitSection))
    break;

    $currentWidth = 0;
    $currentLine = ”;
    $this->newLine();
    continue;
    }
    $currentLine.= $currentWord['text'];
    $currentWord = $this->_makeTextSection(array_shift($splitSection).’ ‘);
    }

    return true;
    }

  11. Logan says:

    Mike,

    Thanks a lot for catching this issue. I hadn’t yet gotten the chance to look into this yet, but I appreciate you taking the time to fix it. I’ll be sure to add your name to the authors list and update the code when I get back to work on Friday.

  12. Mike DS says:

    No problem Logan, you did all the work on the class, it was the least any user can do for another person’s code.

    Anyway, this is the version I’ve been using for a while and seems good. (I seem to have accidentally posted an older version)

    private function _wordWrap(&$section) {
    if ($this->isAutoWidth()) {
    $maxWidth=$this->_page->getWidth();
    } else {
    $maxWidth=$this->_width;
    }
    $lineWidth=$this->_text[$this->_lineNumber]['width'];

    if ($lineWidth+$section['width'] _makeTextSection(array_shift($splitSection).’ ‘);
    $currentLine = ”;
    $currentWidth = $lineWidth;

    while (true && $i $maxWidth || empty($splitSection)) //reset and go to new line
    {
    $this->addText($currentLine);

    if (empty($splitSection))
    break;

    $currentWidth = 0;
    $currentLine = ”;
    $this->newLine();
    continue;
    }
    $currentLine.= $currentWord['text'];
    $currentWord = $this->_makeTextSection(array_shift($splitSection).’ ‘);
    }

    return true;
    }

  13. Mike DS says:

    Okay, it appears that it was the correct code anyway, but I think some sort of striptags function on your site is cutting into it. I’ve mailed you the proper code…

  14. Logan, any known issues with character encoding iso-8859-1? I’ve updated an application from using Zend_Pdf::drawText() to Zend_Pdf_Cell::addText(),write() and the result is that the browser launches the download dialog with my index.php file. That doesn’t happen when the text has no accents and the char-encoding is ommited. Good work.

  15. Logan says:

    Marcello,

    Currently, I haven’t yet tested this code with any other character encoding. I’m not quite sure why it would do that. I will be back in my testing environment on Friday, so I can take a look at it then with different character encodings.

    -Logan

  16. Mike De Smet says:

    Marcello,

    Around line 603 I made this change to make the character encodings work for me:

    $this->_page->drawText($this->_text[$i][$j]['text'],$currentX ,$currentY/* ,$this->_text[$i][$j]['encoding'] */ , ‘ISO-8859-1′);

    (original code commented out)

    This is quite a dirty hack though and won’t always work, experiment with the ISO* value, and replace by UTF-8, etc.

  17. raph says:

    Thanks !!

  18. Mike De Smet says:

    Hi Logan,

    Can you give us a status update about the possibility of including the Zend_PDF_Cell in the Zend Framework?

    Thanks,
    Mike

  19. erwanpia says:

    Hi, is your cell code working with ZEND 1.5 ?

  20. Logan says:

    @Mike
    I am currently writing a proposal and still need to sign the CLA. In order to submit it though, you will also need to sign the NDA because some code I have is yours. I will be in the office next week to finish these tasks up.

    @erwanpia
    I have not tested it with 1.5, but will be in the office next week to test it. I doubt that there will be any problems whatsoever though.

  21. wladik says:

    Hi,
    I’m trying to get it working with latest Zend Framework 1.5.1 But when I try draw Cell i end with this error: “Fatal error: Call to a member function glyphNumbersForCharacters() on a non-object in Zend\Pdf\Cell.php on line 604″. When I use built-in font or with any TTF font is this same error. Is it because of incompatibility with ZF 1.5 ? Thanks for replying..

  22. Logan says:

    wladik,

    They seemed to have made a property that was public into protected, and added a public method to access it.

    I updated the links, one for the 1.0 framework, and another for the 1.5 framework.

    Thanks for pointing this out!

  23. Bernhard says:

    Hello!

    “Mike DS” wrote “I’ve mailed you the proper code…” above.

    Can you please publish the corrected code on the page or make it downloadable since I get the same error and since I cannot copy&paste “Mike DS”‘s code because some operators are missing.

    Thanks!

  24. marcis says:

    Hi Logan,

    Good job with this class!! Thank you.

    I think there’s a bug in the ‘height’ property of the lines in a cell. I’ll try to explain.

    It’s initialized to 0 in the ‘_initializeLine’ method:

    $this->_text[$this->_lineNumber]['height']=0;

    And it’s only updated in the ‘addText’ method if ‘isAutoHeight’ returns true

    if ($this->isAutoHeight()) {

    BUT it’s used in the ‘write’ method to recalculate the $currentY variable:

    $currentY-=$this->_text[$i]['height'];

    So, if you fix the height for a cell then all their lines will be drawn at the same $currentY. It means that they “overwrite themselves”.

    Bye

  25. marcis says:

    Hi again!!

    I’m interested in Mike DS’s update too. :)

  26. Logan says:

    Bernhard and Marcis

    The code that Mike DS sent was for fixing some of the word wrap functionality that had a bug in it, which I am pretty sure is fixed in the downloads that are available now.

    About the height problem, I do understand where you are coming from. I actually had never used the class in a fixed height scenario, so I will have to look into fixing it.

    -Logan

  27. Mike De Smet says:

    Hi Logan,

    I read I have to sign or complete something or whatever, can you please mail me the details and I will do that straight away.

    Greets,
    Mike

  28. Bernhard says:

    Hi!

    1.) @logan: Okay, thanks.
    2.) @logan: I think you have a little bug in the version for ZF 1.5.

    In the “write” method you have to calculate the border like
    ####
    $this->_page->drawRectangle($left,$top,$right,$top – ($bottom – $top) );

    ###
    and not like you are doing with
    ###
    $this->_page->drawRectangle($right,$top,$left,$bottom);
    ###

    Thank you,
    Bernhard

  29. Bernhard says:

    Hi!

    Another comment. It seems like memory handling should be improved. I just wanted to but the following text into a cell:

    ###
    Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nunc ac ante sed ante imperdiet auctor. Fusce dignissim, magna eu feugiat tincidunt, nibh metus tincidunt augue, quis ullamcorper lorem pede a ante. Proin congue nisl a arcu. Donec et elit. Etiam ac eros nec metus molestie aliquam. Nullam vestibulum molestie magna. In varius quam in nulla luctus tristique. Nam et eros. Sed vitae sem a velit mattis dapibus. Sed blandit, sapien auctor adipiscing viverra, purus urna fermentum wisi, id luctus tortor augue et ligula. In quis libero. Sed urna arcu, malesuada in, adipiscing vitae, vehicula vitae, magna. Phasellus sit amet nisl at erat aliquet eleifend. Quisque malesuada porta elit. Nulla nec orci ac leo posuere eleifend. Aliquam ultrices vulputate velit. Vestibulum vitae ipsum. Vestibulum pede erat, cursus nec, porttitor ac, accumsan ut, neque.
    ###

    But I get an error like
    ###
    Fatal error: Allowed memory size of 8388608 bytes exhausted (tried to allocate 3540 bytes) in /xxx/__3rd_party/ZendFramework/Zend/Pdf/Cell.php on line 175
    ###

    I know I can increase the memory limit, but I think this “short” text should not consume 8MB of RAM.

    Do you know what the problem is (since it is your class and you might know WHERE to look)?

    Thank you,
    Bernhard

  30. Mike De Smet says:

    Hi Bernhard,

    As I have encountered the same problem, I have mailed Logan a patch for this bug. I hope he releases/publishes it soon. I can’t post it here as the stripslashes function he calls on each post will corrupt the code. Leave your mail address on here and I’ll send it to you by mail…

    Greets,
    Mike De Smet

  31. i’ll also need this patch? could you post it here?

  32. David M says:

    Not working with ZF 1.6.
    Was able to fix it by removing
    “cmap->”
    from line 604.

  33. ianaré says:

    Thanks for putting this out there, it works OK on Zend 1.5.3.

    Couple things though :

    1) You should automatically drop a line when encountering the newline character.

    2) Absolute cell positioning option would be very helpful.

  34. Dominik says:

    Hi Logan. I really like your class, but I’m afraid it still needs quite a lot of bugfixes until it can be considered “production ready”. It works good enough for my requirements, though.

    I had to include some fixes for international character support, though. When an “UTF-8″ string has to be “wordwrapped” it’s loosing characters.

    I have changed some functions to fix that bug. Going to send the fixed file to you by email.

  35. Mila76 says:

    The patch have an error:

    from:
    public function getHeight() {
    return $this->_height;
    if ($this->isAutoWidth())

    to:
    public function getHeight() {
    return $this->_height;
    if ($this->isAutoHeight())

    I have many crash with small cell and large word
    i send new ideas to logan email

  36. chuck says:

    I’m getting the same error as wladik above (“Fatal error: Call to a member function glyphNumbersForCharacters() on a non-object in Zend\Pdf\Cell.php on line 604) using either the 1.0 or 1.5 versions of Cell.php with ZF 1.7.

  37. chuck says:

    Actually, never mind… I’m getting the same error with ZF 1.6 and ZF 1.5, using the 1.5 version of Cell. Something else is going on.

  38. chuck says:

    Okay, I can confirm that Cell seems to work fine with ZF 1.7, it was an error on my part: be sure to set the page font /before/ passing the page to the Cell constructor, folks.

    Is there any way you could show us an example of how to explicitly set one coordinate of the cell’s position while using one of the class’s positioning constants for the other (i.e. I need centered text at y=440)?

  39. Vinz says:

    It’s a very bad thing to put class attributes in private mode.
    This class is useless for me, since I can’t access attributes from a subclass…

    Private mode is very restrictive and I use protected mode as often as I can.

Comments are closed.