Skip to main content

By clicking Submit, you agree to the developerWorks terms of use.

The first time you sign into developerWorks, a profile is created for you. Select information in your profile (name, country/region, and company) is displayed to the public and will accompany any content you post. You may update your IBM account at any time.

All information submitted is secure.

  • Close [x]

The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerworks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

By clicking Submit, you agree to the developerWorks terms of use.

All information submitted is secure.

  • Close [x]

developerWorks Community:

  • Close [x]

Understanding the Zend Framework, Part 5: Creating PDF files

Building the perfect reader

Nicholas Chase (nicholas@nicholaschase.com), Developer/Writer, Studio B
Nicholas Chase, a Studio B author, has been involved in Web site development for companies such as Lucent Technologies, Sun Microsystems, Oracle, and the Tampa Bay Buccaneers. Nick has been a high school physics teacher, a low-level radioactive waste facility manager, an online science fiction magazine editor, a multimedia engineer, and an Oracle instructor. More recently, he was the Chief Technology Officer of an interactive communications firm in Clearwater, Florida, USA, and is the author of several books on Web development, including XML Primer Plus (Sams). He's currently trying to buy a farm so he and his wife can raise alpacas and chickens. He loves to hear from readers and can be reached at: nicholas@nicholaschase.com.

Summary:  In previous parts of this "Understanding the Zend Framework" series, you created the basic application, the Chomp online feed reader, using the open source PHP Zend Framework. In this tutorial, you use the Zend Framework's PDF capabilities to generate a PDF document based on entries the user has saved.

View more content in this series

Date:  18 Jan 2011 (Published 01 Aug 2006)
Level:  Intermediate PDF:  A4 and Letter (999 KB | 35 pages)Get Adobe® Reader®

Comments:  

Adding text

You have the decorations in place, so it's time to add the text.

Displaying the headlines

To add headlines to the page, you first create a new style, as shown in Listing 14.


Listing 14. Displaying headlines
...
        foreach($entries as $row){
            $title = $row['feedname'];
            $entrydata = $row['entrydata'];
            if($row['channelname'] != '')
            {
                $title = "$title > " . $row['channelname'];
            }

            $headlineStyle = new Zend_Pdf_Style();
            $headlineStyle->setFillColor(
                   new Zend_Pdf_Color_RGB(0.9, 0, 0));
            $headlineStyle->setFont(
                   new Zend_Pdf_Font_Standard(
                        Zend_Pdf_Const::\
                        FONT_HELVETICA_BOLD), 18);

            $page->setStyle($headlineStyle);
                 ->drawText($title, 48, $topPos - 148);

        }

        $pdf->pages[0] = ($page);

        header('Content-type: application/pdf');
        echo $pdf->render();

    }
...

The creation of the style mirrors what you did before, with a few small differences. For one thing, you set the fill color to red, and you're using 18-point type instead of 36-point type. Notice also that you can set the style four-page, multiple times; the most recently set style takes precedence. Finally, you add the text. If you render the PDF, the results might not be quite what you expect (see Figure 3).


Figure 3. Displaying the headlines
Displaying the PDF

As far as the PDF is concerned, you placed all the text in the same position, so rather than placing it sequentially, it just followed your instructions. You'll have to do something about that.


Positioning lines

To prevent the lines of text from overlapping, you need to calculate how far apart you want them and update the position information (see Listing 15).


Listing 15. Preventing overlap
...
        $db = Zend_Registry::get('db');
        $select = $db->select();
        $select->from('savedentries', '*');
        $select->where("username=?", $username);
        $sql = $select->__toString();
        $entries = $db->fetchAll($sql);

        $startPos = $topPos - 120;

        foreach($entries as $row){
            $title = $row['feedname'];
            $entrydata = $row['entrydata'];
            if($row['channelname'] != '')
            {
                $title = "$title > " . $row['channelname'];
            }

            $headlineStyle = new Zend_Pdf_Style();
            $headlineStyle->setFillColor(
                  new Zend_Pdf_Color_RGB(0.9, 0, 0));
            $headlineStyle->setFont(
                  new Zend_Pdf_Font::fontWithName(
                        Zend_Pdf_Font::HELVETICA_BOLD), 18);

            $page->setStyle($headlineStyle);
                 ->drawText($title, 48, $startPos);
            $startPos = $startPos - 24;
        }

        $pdf->pages[0] = ($page);

        header('Content-type: application/pdf');
        echo $pdf->render();

    }
...

Because the position is set explicitly, all you have to do is track it. You start with the position 120 points (just under two inches) below the top of the page and move the position down 24 points each time you output a line of text. Still, the output is not quite ideal (see Figure 4).


Figure 4. Positioning lines
Positioning lines

You still have to figure out how to manually perform a line wrap for lines that are too long for the page.


Breaking text into lines

To wrap text at the edge of the page, you need to break a single line down into multiple lines and display each of them separately (see Listing 16).


Listing 16. Wrapping lines of text
...
        foreach($entries as $row){
            $title = $row['feedname'];
            $entrydata = $row['entrydata'];
            if($row['channelname'] != '')
            {
                $title = "$title > " . $row['channelname'];
            }

            $headlineStyle = new Zend_Pdf_Style();
            $headlineStyle->setFillColor(
                   new Zend_Pdf_Color_RGB(0.9, 0, 0));
            $headlineStyle->setFont(
                   Zend_Pdf_Font::fontWithName(
                        Zend_Pdf_Font::HELVETICA_BOLD), 18);

            $page->setStyle($headlineStyle);
            $title = strip_tags($title );
            $title = wordwrap($title , 55, '\n');

            $headlineArray = explode('\n', $title );

            foreach ($headlineArray as $line) {
                $line = ltrim($line);
                $page->drawText($line, 48, $startPos);
                $startPos = $startPos - 24;
            }

        }

        $pdf->pages[0] = ($page);

        header('Content-type: application/pdf');
        echo $pdf->render();

    }
...

Starting at the top, you take the title and make sure it does not include any HTML tags. Once you know that, you can use the PHP wordwrap() function to break the text down to lines that are no more than a specific length — in this case, 55 characters. The wordwrap() function simply inserts a character (in this case, the newline character) where the line would normally wrap based on this length. In other words, if a word would cause the line to be longer than 55 characters, this function inserts the newline character before that word.

This, however, does not solve the entire problem because the newline character is rendered by the PDF as a space, not as a new line. However, with the newline character strategically placed throughout the string, you can use the explode() function to turn this single string into an array of strings. Once you have the array, you can loop through each member, trimming any leading spaces and moving the start position 24 points down after each line.

The results are more like one would expect to see (see Figure 5).


Figure 5. Wrapping lines of text
Wrapping lines of text

Displaying body text

Adding the body text for each entry is virtually identical to adding the headlines (see Listing 17).


Listing 17. Displaying the body of the entry
...
            $headlineArray = explode('\n', $title );

            foreach ($headlineArray as $line) {
                $line = ltrim($line);
                $page->drawText($line, 48, $startPos);
                $startPos = $startPos - 24;
            }

            $articleStyle = new Zend_Pdf_Style();
            $articleStyle->setFillColor(
                  new Zend_Pdf_Color_RGB(0, 0, 0));
            $articleStyle->setFont(
                  Zend_Pdf_Font::fontWithName(
                        Zend_Pdf_Font::HELVETICA_BOLD), 12);
            $page->setStyle($articleStyle);

            $entrydata = strip_tags($entrydata);
            $entrydata = wordwrap($entrydata, 90, '\n');

            $articleArray = explode('\n', $entrydata);

            foreach ($articleArray as $line) {
                $page->drawText($line, 48, $startPos);
                $startPos = $startPos - 16;
            }
            $startPos = $startPos - 16;

        }

        $pdf->pages[0] = ($page);

        header('Content-type: application/pdf');
        echo $pdf->render();

    }
...

Again, you first create the style, this time using black instead of red and 12-point type instead of 18-point type. Because the text is smaller, you can fit 90 characters per line, rather than just 55. Also, after you display the article text, you move the pointer down an additional 16 points to set it off from the next article (see Figure 6).


Figure 6. Displaying the body text
Displaying the body text

Detecting the end of the page

Unfortunately, not all is well yet. Not only does the PDF not automatically wrap the text at the end of the line but it also does not automatically create a new page when you reach the end of this one (see Figure 7).


Figure 7. Detecting the end of the page
Detecting the end of the page

You're going to have to manually create a new page when you detect that you're getting close to the bottom of this one (see Listing 18).


Listing 18. Detecting the end of the page
...
        $sql = $select->__toString();
        $entries = $db->fetchAll($sql);

        $startPos = $topPos - 120;

        foreach($entries as $row){
            $title = $row['feedname'];
            $entrydata = $row['entrydata'];
            if($row['channelname'] != '')
            {
                $title = "$title > " . $row['channelname'];
            }

            if ($startPos < 72){
                //start a new page
            }

            $headlineStyle = new Zend_Pdf_Style();
            $headlineStyle->setFillColor(new Zend_Pdf_Color_RGB(0.9, 
0, 0));
            $headlineStyle->setFont(
 Zend_Pdf_Font::fontWithName(Zend_Pdf::HELVETICA_BOLD), 18);

            $page->setStyle($headlineStyle);
            $title = strip_tags($title );
            $title = wordwrap($title , 55, '\n');

            $headlineArray = explode('\n', $title );

            foreach ($headlineArray as $line) {
                $line = ltrim($line);
                $page->drawText($line, 48, $startPos);
                $startPos = $startPos - 24;
            }

            $articleStyle = new Zend_Pdf_Style();
            $articleStyle->setFillColor(new Zend_Pdf_Color_RGB(0, 0, 0));
            $articleStyle->setFont(
 Zend_Pdf_Font::fontWithName(Zend_Pdf_Font::HELVETICA_BOLD), 12);
            $page->setStyle($articleStyle);

            $entrydata = strip_tags($entrydata);
            $entrydata = wordwrap($entrydata, 90, '\n');

            $articleArray = explode('\n', $entrydata);

            foreach ($articleArray as $line) {

                if ($startPos < 48){
                     //start a new page
                }

                $page->drawText($line, 48, $startPos);
                $startPos = $startPos - 16;

            }
            $startPos = $startPos - 16;

        }

        $pdf->pages[0] = $page;

        header('Content-type: application/pdf');
        echo $pdf->render();

    }
...

Here you are performing two tests. First, before you display a new story, you make sure that there is at least an inch of page left. If not, you will start a new page. (You'll see how to do that in the moment.) Second, before you display each line of article text, you make sure that there is at least two-thirds of an inch left on the page.

Now let's look at actually creating the new page.


Creating a new page

Consider three issues when creating a new page: creating the page, adding the page to the document, and making sure everything appears properly (see Listing 19).


Listing 19. Creating a new page
...
        foreach($entries as $row){
            $title = $row['feedname'];
            $entrydata = $row['entrydata'];
            if($row['channelname'] != '')
            {
                $title = "$title > " . $row['channelname'];
            }

            if ($startPos < 72){
                 array_push($pdf->pages, $page);
                 $page = new Zend_Pdf_Page(
                         Zend_Pdf_Page::SIZE_LETTER);
                 $startPos = $pageHeight - 48;
            }

            $headlineStyle = new Zend_Pdf_Style();
            $headlineStyle->setFillColor(
                 new Zend_Pdf_Color_RGB(0.9, 0, 0));
            $headlineStyle->setFont(
                     Zend_Pdf_Font::fontWithName(
                     Zend_Pdf_Font::HELVETICA_BOLD), 18);

            $page->setStyle($headlineStyle);
            $title = strip_tags($title );
            $title = wordwrap($title , 55, '\n');

            $headlineArray = explode('\n', $title );

            foreach ($headlineArray as $line) {
                $line = ltrim($line);
                $page->drawText($line, 48, $startPos);
                $startPos = $startPos - 24;
            }

            $articleStyle = new Zend_Pdf_Style();
            $articleStyle->setFillColor(
                 new Zend_Pdf_Color_RGB(0, 0, 0));
            $articleStyle->setFont(
                     Zend_Pdf_Font::fontWithName(
                     Zend_Pdf_Font::HELVETICA_BOLD), 12);
            $page->setStyle($articleStyle);

            $entrydata = strip_tags($entrydata);
            $entrydata = wordwrap($entrydata, 90, '\n');

            $articleArray = explode('\n', $entrydata);

            foreach ($articleArray as $line) {

                if ($startPos < 48){

                    array_push($pdf->pages, $page);
                     $page = new Zend_Pdf_Page(
                          Zend_Pdf_Page::SIZE_LETTER);
                     $articleStyle = new Zend_Pdf_Style();
                     $articleStyle->setFillColor(
                          new Zend_Pdf_Color_RGB(0, 0, 0));
                     $articleStyle->setFont(
                          Zend_Pdf_Font::fontWithName(
                          Zend_Pdf_Font::HELVETICA_BOLD), 12);
                     $page->setStyle($articleStyle);

                     $startPos = $pageHeight - 48;

                }
                $page->drawText($line, 48, $startPos);
                $startPos = $startPos - 16;

            }
            $startPos = $startPos - 16;

        }

        array_push($pdf->pages, $page);

        header('Content-type: application/pdf');
        echo $pdf->render();

    }
...

Because the pages for the PDF document are simply an array, you can use the array_push() function to add the current page to the end of the document. You can then create a new page (using the same variable name for convenience) and reset the starting position to two-thirds of an inch below the top of the page.

Now, that's sufficient for your headline check because you can simply set the style on the page after is created. For your check within the body of the article, it's a little more complicated because you may wind up in the situation in which you are creating a page in the middle of the array, and the new page won't have the $articleStyle style set properly. Therefore, in addition to adding the current page to the array and creating a new page, you also have to reset the style. Again, you reset the starting position to two-thirds of an inch below the top of the page.

Finally, you change the way the initial page gets added to the document. Before, you did all of your processing, then added the current page as the first page. In this case, you don't know if it is the first page, so you simply push it onto the array, instead.

The result is a properly paginated document, shown in Figure 8.


Figure 8. Creating a new page
Creating a new page

Although the page is properly paginated, it is missing a border.


Cleaning up the new pages

To make sure your pages are uniform, you can create a function and use it to create all of your pages (see Listing 20).


Listing 20. Creating a function for new pages
...
        $view->unsubFeeds = $unsubFeeds;
        echo $view->render('feeds.php');
    }

        private function newPdfPage(){

        $page = new Zend_Pdf_Page(Zend_Pdf_Page::SIZE_LETTER);

        $style = new Zend_Pdf_Style();
        $style->setLineColor(new Zend_Pdf_Color_RGB(0.9, 0, 0));
        $style->setFillColor(new Zend_Pdf_Color_GrayScale(0.2));
        $style->setLineWidth(3);
        $style->setFont(
		             Zend_Pdf_Font::fontWithName(
                     Zend_Pdf_Font::HELVETICA_BOLD), 32);

        $page->setStyle($style);

        $pageHeight = $page->getHeight();
        $pageWidth = $page->getWidth();

        $page->drawRectangle(18, $pageHeight - 18, $pageWidth - 18,
                             18, Zend_Pdf_Page::SHAPEDRAW_STROKE);

        return $page;

    }

    public function createPdfAction()
    {

        require_once 'Zend/Pdf.php';

        $pdf = new Zend_Pdf();
        $page = $this->newPdfPage();
        $chompImage = new Zend_Pdf_Image_JPEG(
                            'E:\sw\public_html\chomp.jpg');

        $pageHeight = $page->getHeight();
        $pageWidth = $page->getWidth();
        $imageHeight = 72;
        $imageWidth = 72;
...
        foreach($entries as $row){
            $title = $row['feedname'];
            $entrydata = $row['entrydata'];
            if($row['channelname'] != '')
            {
                $title = "$title > " . $row['channelname'];
            }

            if ($startPos < 72){
                 array_push($pdf->pages, $page);
                 $page = $this->newPdfPage();
                 $startPos = $pageHeight - 48;
            }

            $headlineStyle = new Zend_Pdf_Style();
            $headlineStyle->setFillColor(new Zend_Pdf_Color_RGB(0.9, 0,
 0));
            $headlineStyle->setFont(
 Zend_Pdf_Font::fontWithName(Zend_PdfFont::HELVETICA_BOLD), 18);

            $page->setStyle($headlineStyle);
            $title = strip_tags($title );
            $title = wordwrap($title , 55, '\n');
...
            $entrydata = strip_tags($entrydata);
            $entrydata = wordwrap($entrydata, 90, '\n');

            $articleArray = explode('\n', $entrydata);

            foreach ($articleArray as $line) {

                if ($startPos < 48){

                     array_push($pdf->pages, $page);
                     $page = $this->newPdfPage();
                     $articleStyle = new Zend_Pdf_Style();
                     $articleStyle->setFillColor(
                           new Zend_Pdf_Color_RGB(0, 0, 0));
                     $articleStyle->setFont(
                           Zend_Pdf_Font::fontWithName(
                   Zend_Pdf_Font::HELVETICA_BOLD), 12);
                     $page->setStyle($articleStyle);

                     $startPos = $pageHeight - 48;

                }
                $page->drawText($line, 48, $startPos);
                $startPos = $startPos - 16;

            }
            $startPos = $startPos - 16;

        }

        array_push($pdf->pages, $page);

        header('Content-type: application/pdf');
        echo $pdf->render();

    }
...

There really isn't any new code here. All you've done is rearrange things so that the newPdfPage() function includes all the common elements, such as the basic style and the border. You then replace each instance of creating a new page with a call to this function and add any additional processing (such as the image or headlines and articles) to the returned object.

Your pages are now uniform, as shown in Figure 9.


Figure 9. Creating a function for new pages
Creating a function for new pages

4 of 9 | Previous | Next

Comments



static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Open source
ArticleID=148418
TutorialTitle=Understanding the Zend Framework, Part 5: Creating PDF files
publish-date=01182011
author1-email=nicholas@nicholaschase.com
author1-email-cc=dwxed@us.ibm.com