发新话题
打印

Zend 框架学习之创建 PDF 文件篇

输出 PDF

输出 PDF 与输出样例文件稍有不同,因为 Zend 框架的部分设置涉及到要确保将所有的请求传送到 index.php 文件。这意味着我们不能只将文件保存到服务器上供用户下载。(是的,我们可以做一些服务器配置方面的调整,但我们的目的是要保持它的简单性。)

可供选择的一个方案是,生成 PDF 后将它输出到用户的浏览器中。


清单 13. 输出 PDF
复制内容到剪贴板
代码:
...
        foreach($entries as $row){
            $title = $row['feedname'];
            $entrydata = $row['entrydata'];
            if($row['channelname'] != '')
            {
                $title = "$title > " . $row['channelname'];
            }
//            echo '<p>'.$title.'<br />';
//            echo $entrydata.'</p>';

        }

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

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

    }
...
由底部开始,render() 函数输出了 PDF 文件的实际文本,但如果要正确地解释该文件,浏览器还需要识别组成 PDF 文件的文本。通常,当浏览器下载一个 PDF 时,服务器根据文件的扩展名发送内容类型。因为没有文件,也就没有文件的扩展名,所以我们要用标题对其进行手动设置。

在我们使用标题之前,先要确保在这之前并未输出任何东西,所以我们移除了之前发送给页面的语句。

刷新您的浏览器(参见图 2),这样就可以看到真实的 PDF 文档了(假设您已经安装了 Adobe Acrobat 阅读器)。

TOP

添加文本

做好了装饰,现在该添加文本了。

显示摘要

要给页面添加摘要,先要创建一个新样式,如下所示。


清单 14. 显示摘要
复制内容到剪贴板
代码:
...
        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);

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

        }

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

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

    }
...
样式的创建与我们之前的做法类似,但略有不同。其中的一点不同是,我们将填充色设为红色,用 18 点取代了 36 点。请注意,也可以将样式设置为 4 页面,并可多次设置;最近设置的样式优先。最后是添加文本。如果此时显示 PDF,结果也许不那么令人满意。
就目前谈及的 PDF 而言,它只是遵照了我们的指示,将所有的文本设置到了同样的位置,而不是按顺序放置。为此我们不得不做一些改动。

TOP

放置行

为阻止文本行的重叠,我们需要计算所需的行距并更新位置信息。


清单 15. 防止重叠
复制内容到剪贴板
代码:
...
        $db = Zend::registry('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_Standard(
                        Zend_Pdf_Const::FONT_HELVETICA_BOLD), 18);

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

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

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

    }
...
由于显式地设置了位置,现在只需要对其进行跟踪即可。我们从页面顶部向下 120 点(仅比两英寸小)的位置开始,在每次输出一行文本后,向下移动 24 个点。输出仍不是很理想。

我们还需要解决行的长度超过页面宽度时如何执行人工换行的问题。

TOP

将文本分解为多行

想要在页面边缘使文本换行,则需要将单个的行分解为多行并分别显示出来。


清单 16. 文本的换行
复制内容到剪贴板
代码:
...
        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);
            $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();

    }
...
从顶部开始,我们得到了标题并确保它不包含任何 HTML 标记。了解这些之后,就可以使用 PHP wordwrap() 函数将文本分解为不超过特定长度(在本例中为 55 个字符)的行。wordwrap() 函数根据此长度在通常要换行的地方插入一个字符(本例中为换行符号)。换言之,如果一个单词导致其所在行超过 55 个字符,那么这个函数就会在该单词前插入一个换行符号。

然而,这并未解决所有问题,因为换行符号被 PDF 显示为一个空格,而不是一个新行。但通过将换行符号策略性地贯穿字符串放置,就可以使用 explode() 函数将单一字符串转化为字符串数组。有了这个数组,就能够实现其成员数组的循环,去掉所有开头部分的空格并在每一行后将起始位置向下移动 24 个点。

这次的结果比较令人满意。

TOP

显示正文

为每个条目添加正文与添加摘要实质上是一致的。


清单 17. 显示条目主体
复制内容到剪贴板
代码:
...
            $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(
                  new Zend_Pdf_Font_Standard(
                        Zend_Pdf_Const::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();

    }
...
我们首先要再一次创建样式,这次使用黑色代替红色,12 点代替 18 型。由于文本比较小,一行可以放置 90 个字符,而不仅是 55 个字符。而且,在显示了全文后,我们将指针额外向下移动了 16 个点,以便与下一篇文章分隔开。

TOP

检测页尾

不幸的是,一切并不尽如人意。PDF 不仅不能在行末为文本自动换行,而且也不能在页末自动创建一个新的页面。
当检测到接近页尾时,我们需要手动创建一个新页面。

清单 18. 检测页尾
复制内容到剪贴板
代码:
...
        $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(new
Zend_Pdf_Font_Standard(Zend_Pdf_Const::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(new
Zend_Pdf_Font_Standard(Zend_Pdf_Const::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();

    }
...
我们在这执行了两个测试。第一个测试是,在展文章前,需要先确保至少还有一英寸页面剩余。如果没有,就需要创建一个新页面。(我们即将探讨如何创建新页面。)第二,在展示文章的每一行时,需要先确保至少有 2/3 英寸的页面剩余。

现在让我们来看一下实际创建新页面的方式。

TOP

创建新页面

创建新页面时要考虑三件事:创建页面、将页面添加到文档中以及确保一切显示正常。


清单 19. 创建新页面
复制内容到剪贴板
代码:
...
        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_Const::PAGESIZE_LETTER);
                 $startPos = $pageHeight - 48;
            }

            $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);
            $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(
                 new Zend_Pdf_Font_Standard(
                     Zend_Pdf_Const::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_Const::PAGESIZE_LETTER);
                     $articleStyle = new Zend_Pdf_Style();
                     $articleStyle->setFillColor(
                          new Zend_Pdf_Color_RGB(0, 0, 0));
                     $articleStyle->setFont(
                          new Zend_Pdf_Font_Standard(
                        Zend_Pdf_Const::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();

    }
...
由于 PDF 文档的页面只是一个数组,所以我们能够使用 array_push() 函数将当前页面添加到文档末端。然后就可以创建一个新页面(为方便起见,使用相同的变量名),并将起始位置重置于页面顶部向下 2/3 英寸的位置。

现在,检查摘要的条件已经充分,因为我们能够在页面创建后轻易地设置其样式。而对正文的检查,会稍复杂一些,因为我们也许会遇到某些情况而停止操作,比如说在数组中部创建了页面而新页面的 $articleStyle 样式并未设置好。因此,除了将当前页面添加到数组并创建一个新页面以外,还需要重置样式。我们再一次将起始位置设为页面顶部向下 2/3 英寸的位置。

最后,我们改变了将初始页面添加到文档的方式。在此这前,我们完成了所有的处理,然后将当前页面添加为首页。在这种情况下,我们并不知道这是否是第一页,因此我们只是把它放入数组。

结果产生了一个适当地增加了页码的文档
尽管页面被适当地添加了页码,但它仍缺少一个边框。

TOP

整理新页面

为确保页面统一,我们要创建一个函数并使用它来创建所有的页面。


清单 20. 为新页面创建函数
复制内容到剪贴板
代码:
...
        $view->unsubFeeds = $unsubFeeds;
        echo $view->render('feeds.php');
    }

    private function newPdfPage(){

        $page = new Zend_Pdf_Page(Zend_Pdf_Const::PAGESIZE_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(new Zend_Pdf_Font_Standard(
                     Zend_Pdf_Const::FONT_HELVETICA_BOLD), 32);

        $page->setStyle($style);

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

        $page->drawRectangle(18, $pageHeight - 18, $pageWidth - 18,
                             18, Zend_Pdf_Const::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(new
Zend_Pdf_Font_Standard(Zend_Pdf_Const::FONT_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(
                           new Zend_Pdf_Font_Standard(
                   Zend_Pdf_Const::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();

    }
...
这里实际上并没有新的代码。我们只是进行了重新排列,以便 newPdfPage() 函数能包含所有公共元素,如基本样式和边框。然后,将每个创建新页面的实例替换为对此函数的调用,并为返回的对象添加任何额外的处理(诸如图片、摘要和文章)。

TOP

选择条目和重用文档

基本的系统已经完成。我们需要对其添加一些修饰。在这一小节中,我们让用户能够自主选择将哪一个已保存条目包含到文档中,并将其放入已有文件中,而不是创建一个新文件。

更好的条目管理方式:添加 ID

在合理地处理条目之前,我们需要一种更好的引用方式。之前的做法是,创建一个结构,可以通过提要名(有时通过 URL)引用结构的条目,但当我们越来越重视数据管理,每一个条目都很需要一个针对引用的主键。

为此,登录到 MySQL 并执行清单 21 中的命令。


清单 21. 为表添加新列
                    
ALTER TABLE `savedentries` ADD `id` INT NOT NULL
AUTO_INCREMENT PRIMARY KEY;



该命令添加了一个名为 id 的新的主键,并指定当表中添加新行时,此字段将提供一个新的、惟一的、递增的值。

TOP

允许用户选择条目

下一步是允许用户选择哪一个条目将成为 PDF 的一部分。为此,我们要为已保存条目页面添加第二个表单。打开 viewSaveEntries.php 文件并做如下更改。


清单 22. 为 viewSaveEntries.php 文件添加 PDF 生成选项
复制内容到剪贴板
代码:
...
         echo "<input type='hidden' name='type' value='$type'/>";
         echo "<td><a href='$link'>$title</a></td>";
         echo "<td><input type='submit'
value='delete'/></td>";
         echo "<td><a href=
'/feed/fullText?feedTitle=$feedTitleamp;channelTitle=$channelTitle'
>$entrysaved</a></td></form></tr>\n";
     }
    ?>
  </table><br>
  <h3>Generate a PDF:</h3>
  <form method='POST' action='/feed/createPdf'>
  <table>
    <tr>
      <td>Feed > Title (Click to View)
        amp;nbsp;amp;nbsp;amp;nbsp;amp;nbsp;amp;nbsp;amp;nbsp;</td>
      <td>Add to PDF</td>
    </tr>
<?php
     foreach ($this->entries as $row) {
         $link = $row['link'];
         $channelTitle = $row['channelname'];
         $feedTitle = $row['feedname'];
         $title = "$feedTitle";
         $entrysaved = '';
         if($row['entrysaved'] == 'true')
         {
             $entrysaved = 'Full Text';
         }
         if ($row['channelname'] != '')
         {
             $title = "$title > $channelTitle";
             $type = 'rssFeed';
         } else {
             $type = 'webPage';
         }
         $id = $row['id'];
         echo "<tr><td><a href='$link'>$title</a></td>";
         echo "<td><input type='checkbox' name='$id' " .
              "checked='checked'></td></tr>\n";
     }
?>
  </table>
  <input type='Submit' value='Create PDF'>
  </form>
</body>
</html>
这是一个通用的 HTML 表单,每一个 id 值都对应一个复选框。

TOP

发新话题