使用Zend Framework中的 Zend_Pdf来创建pdf文档

Haru 和 PDFlib 这二个php扩展提供了完整的api来操作pdf文档
另外还有很多开源代码,可以操作pdf。
这篇文章主要讲解Zend Framework 中的 Zend_Pdf组件。
Zend_Pdf 是纯PHP实现的一套程序,不依赖于其它的任何外部库文件
所以在虚拟主机上用起来是蛮合适的。
Zend_Pdf 可以对PDF进行绝大部分的操作,比如添加/删除页面,插入文件和图片,
绘图,更改PDF文档的元信息(update document meta-data)等等。
下面就以一个简单的例子开始吧:
<?php
// include auto-loader class
require_once 'Zend/Loader/Autoloader.php';

// register auto-loader
$loader = Zend_Loader_Autoloader::getInstance();

try {
  // create PDF
  $pdf = new Zend_Pdf();

  // create A4 page
  $page = new Zend_Pdf_Page(Zend_Pdf_Page::SIZE_A4);

  // define font resource
  $font = Zend_Pdf_Font::fontWithName(Zend_Pdf_Font::FONT_HELVETICA);

  // set font for page
  // write text to page
  $page->setFont($font, 24)
       ->drawText('That which we call a rose,', 72, 720)
       ->drawText('By any other name would smell as sweet.', 72, 620);

  // add page to document
  $pdf->pages[] = $page;

  // save as file
  $pdf->save('example.pdf');
  echo 'SUCCESS: Document saved!';
} catch (Zend_Pdf_Exception $e) {
  die ('PDF error: ' . $e->getMessage());  
} catch (Exception $e) {
  die ('Application error: ' . $e->getMessage());    
}

程序一开始就引入了Zend auto-loader,它会自动include所需的类文件。
程序其它部分的流程就不解释了,直接看注释就可以明白了。
drawText函数的后两个参数是初始写入位置的X轴坐标和Y轴坐标。
注意:Zend_Pdf系统使用 Postscript geometry。也就是说,字体的单位是pt。1pt=1/72 inch;(0,0)原点位于页面的左下脚。

1.jpg 

值得提一下的是, 通过Zend_Pdf_Font既可以使用内置的14种字体,比如Zend_Pdf_Font::FONT_HELVETICA,也可以使用外部的字体文件。
看代码:
<?php
// include auto-loader class
require_once 'Zend/Loader/Autoloader.php';

// register auto-loader
$loader = Zend_Loader_Autoloader::getInstance();

try {
  // create PDF
  $pdf = new Zend_Pdf();

  // create A4 page
  $page = new Zend_Pdf_Page(Zend_Pdf_Page::SIZE_A4);

  // define font resource
  $font = Zend_Pdf_Font::fontWithPath('/tmp/comic.ttf');

  // set font for page
  // write text to page
  $page->setFont($font, 24)
       ->drawText('That which we call a rose,', 72, 720)
       ->drawText('By any other name would smell as sweet.', 72, 620);

  // add page to document
  $pdf->pages[] = $page;

  // save as file
  $pdf->save('example.pdf');
  echo 'SUCCESS: Document saved!';
} catch (Zend_Pdf_Exception $e) {
  die ('PDF error: ' . $e->getMessage());  
} catch (Exception $e) {
  die ('Application error: ' . $e->getMessage());    
}

这个程序与前面的基本相同,只是通过Zend_Pdf_Font::fontWithPath来引入外部字体。
再多说二句,drawText的第四个参数,可以指定文字的字符集,Zend_Pdf会自动帮你转化成所需的字符集。省得自己用iconv了。
外部字体文件会被自动压缩,然后嵌入到pdf文件里面,所以生成出来的pdf文件会占用更多的磁盘空间。

2.jpg 

如果想插入一张图片到pdf文件里面,只需要调用drawImage(),并提供以下参数即可:
一个Zend_Pdf_Image资源
图像左下角横坐标
图像左下角纵坐标
图像右上角横坐标
图像右上角纵坐标
暂时只支持JPEG,PNG,TIFF格式的图片。
另外:使用JPEG图片的话需要GD库。使用PNG图片的话,需要ZLIB库。
看代码吧:
<?php
// include auto-loader class
require_once 'Zend/Loader/Autoloader.php';

// register auto-loader
$loader = Zend_Loader_Autoloader::getInstance();

try {
  // create PDF
  $pdf = new Zend_Pdf();

  // create A4 page
  $page = new Zend_Pdf_Page(Zend_Pdf_Page::SIZE_A4);

  // define image resource
  $image = Zend_Pdf_Image::imageWithPath('IMG_0497.jpg');

  // write image to page
  $page->drawImage($image, 50, 50, 400, 400);

  // add page to document
  $pdf->pages[] = $page;

  // save as file
  $pdf->save('example.pdf');
  echo 'SUCCESS: Document saved!';
} catch (Zend_Pdf_Exception $e) {
  die ('PDF error: ' . $e->getMessage());  
} catch (Exception $e) {
  die ('Application error: ' . $e->getMessage());    
}

3.jpg 

上面讲的drawText有一个很大的缺点:文字太长的话,不会自动换行!
关于这一点,想必用过GD库的imagestring和imagettftext的都深有体会吧。
幸运的是,Nico Edtinger为Zend_Pdf添加了一个新的功能解决这个问题。
http://framework.zend.com/wiki/display/ZFPROP/Zend_Pdf+text+drawing+improvements+-+Nico+Edtinger
很长的字符串会自动换行,也不会拆开某个单词,而且还可以设置左对齐或者右对齐,实在是很强大的。
看代码:
<?php
// include auto-loader class
require_once 'Zend/Loader/Autoloader.php';

// register auto-loader
$loader = Zend_Loader_Autoloader::getInstance();

// define long string
$str = "Mary had a little lamb. It's fleece was white as snow. And everywhere that Mary went, the lamb was sure to go";

try {
  // create PDF
  $pdf = new Zend_Pdf();

  // create A4 page
  $page = new Zend_Pdf_Page(Zend_Pdf_Page::SIZE_A4);

  // define font resource
  $font = Zend_Pdf_Font::fontWithPath('/tmp/comic.ttf');

  // set font
  $page->setFont($font, 14);

  // wrap lines of text
  // start at (10,600) and use a block of dimensions 500x500
  $page->drawTextBlock($str, 10, 600, 500, 500, Zend_Pdf_Page::ALIGN_LEFT);  

  // wrap lines of text
  // start at (10,500) and use a block of dimensions 200x300
  $page->setFont($font, 20);
  $page->drawTextBlock($str, 10, 500, 200, 300, Zend_Pdf_Page::ALIGN_RIGHT);  

  // add page to document
  $pdf->pages[] = $page;

  // save as file
  $pdf->save('example.pdf');
  echo 'SUCCESS: Document saved!';  
} catch (Zend_Pdf_Exception $e) {
  die ('PDF error: ' . $e->getMessage());  
} catch (Exception $e) {
  die ('Application error: ' . $e->getMessage());    
}
drawTextBlock参数的说明:
  $page->drawTextBlock($str, 10, 600, 500, 500, Zend_Pdf_Page::ALIGN_LEFT);
字符串$str,
起点横坐标10,
起点纵坐标600,
文本框的宽度500,
文本框的高度500,
选项 Zend_Pdf_Page::ALIGN_LEFT 左对齐。

4.jpg 

此外,Zend_Pdf提供了一整套的函数来画线,画圆,以及其它图形。
来看一下画线的例子:
<?php
// include auto-loader class
require_once 'Zend/Loader/Autoloader.php';

// register auto-loader
$loader = Zend_Loader_Autoloader::getInstance();

try {
  // create PDF
  $pdf = new Zend_Pdf();

  // create A4 page
  $page = new Zend_Pdf_Page(Zend_Pdf_Page::SIZE_A4);

  // define font resource
  $font = Zend_Pdf_Font::fontWithName(Zend_Pdf_Font::FONT_HELVETICA);

  // set font for page
  $page->setFont($font, 10);

  // draw a line at the top of the page
  $page->setLineColor(new Zend_Pdf_Color_Rgb(0,0,0));
  $page->drawLine(10, 790, ($page->getWidth()-10), 790);

  // draw another line near the bottom of the page
  $page->drawLine(10, 25, ($page->getWidth()-10), 25);

  // define image resource
  $image = Zend_Pdf_Image::imageWithPath('logo.jpg');

  // write image to page
  $page->drawImage($image, 25, 800, ($image->getPixelWidth()+25), (800+$image->getPixelHeight()));

  // add footer text   
  $page->drawText('Copyright My Company 2010. All rights reserved.', ($page->getWidth()/3), 10);

  // add page to document
  $pdf->pages[] = $page;

  // save as file
  $pdf->save('example.pdf');
  echo 'SUCCESS: Document saved!';
} catch (Zend_Pdf_Exception $e) {
  die ('PDF error: ' . $e->getMessage());  
} catch (Exception $e) {
  die ('Application error: ' . $e->getMessage());    
}
使用drawLine(),提供一下起点和终点的坐标即可画出一条线。
线条颜色可以使用Zend_Pdf_Page->setLineColor()来控制,它接受一个Zend_Pdf_Color 类型的对象做为参数。可以使用RGB,CMYK,16进制或者HTML颜色来定义Zend_Pdf_Color

5.jpg 

再来画几个矩形框吧
<?php
// include auto-loader class
require_once 'Zend/Loader/Autoloader.php';

// register auto-loader
$loader = Zend_Loader_Autoloader::getInstance();

try {
  // create PDF
  $pdf = new Zend_Pdf();

  // create A4 page
  $page = new Zend_Pdf_Page(Zend_Pdf_Page::SIZE_A4);

  // set line color
  $page->setLineColor(new Zend_Pdf_Color_Rgb(0,0,0));

  // draw a face
  $page->drawRectangle(100, 300, 500, 700, Zend_Pdf_Page::SHAPE_DRAW_STROKE);

  // draw the left eye
  $page->setFillColor(new Zend_Pdf_Color_Rgb(255,0,0));
  $page->drawRectangle(150, 600, 200, 650);

  // draw the right eye
  $page->setFillColor(new Zend_Pdf_Color_Rgb(255,0,0));
  $page->drawRectangle(400, 600, 450, 650);

  // draw the nose
  $page->setFillColor(new Zend_Pdf_Color_Rgb(0,255,0));
  $page->drawRectangle(260, 450, 340, 550);

  // draw the mouth
  $page->setFillColor(new Zend_Pdf_Color_Rgb(0,0,255));
  $page->drawRectangle(200, 350, 400, 400);

  // add page to document
  $pdf->pages[] = $page;

  // save as file
  $pdf->save('example.pdf');
  echo 'SUCCESS: Document saved!';
} catch (Zend_Pdf_Exception $e) {
  die ('PDF error: ' . $e->getMessage());  
} catch (Exception $e) {
  die ('Application error: ' . $e->getMessage());    
}

drawRectangle()函数根据提供的左下角坐标和右上角坐标,可以画一个矩形出来。
最后一个参数描述了这个矩形的样式:outline(stroked),filled or both.
thesetLineColor()可以定义矩形边框的颜色。setFillColor()可以定义矩形的填充颜色。

6.jpg 

再来看如何画其它的形状(椭圆,多边形):
<?php
// include auto-loader class
require_once 'Zend/Loader/Autoloader.php';

// register auto-loader
$loader = Zend_Loader_Autoloader::getInstance();

try {
  // create PDF
  $pdf = new Zend_Pdf();

  // create A4 page
  $page = new Zend_Pdf_Page(Zend_Pdf_Page::SIZE_A4);

  // set line color
  $page->setLineColor(new Zend_Pdf_Color_Rgb(0,0,0));

  // draw a face
  $page->drawRectangle(100, 300, 500, 700, Zend_Pdf_Page::SHAPE_DRAW_STROKE);

  // draw the left eye
  $page->setFillColor(new Zend_Pdf_Color_Rgb(255,0,0));
  $page->drawRectangle(150, 600, 200, 650);

  // draw the right eye
  $page->setFillColor(new Zend_Pdf_Color_Rgb(255,0,0));
  $page->drawRectangle(400, 600, 450, 650);

  // draw the nose
  $page->setFillColor(new Zend_Pdf_Color_Rgb(0,255,0));
  $page->drawCircle(300, 500, 50);

  // draw the mouth
  $page->setFillColor(new Zend_Pdf_Color_Rgb(0,0,255));
  $page->drawEllipse(200, 350, 400, 400);

  // draw a hat
  $page->setFillColor(new Zend_Pdf_Color_Rgb(250,250,0));
  $page->drawPolygon(array(125,300,475), array(700,800,700));

  // add page to document
  $pdf->pages[] = $page;

  // save as file
  $pdf->save('example.pdf');
  echo 'SUCCESS: Document saved!';
} catch (Zend_Pdf_Exception $e) {
  die ('PDF error: ' . $e->getMessage());  
} catch (Exception $e) {
  die ('Application error: ' . $e->getMessage());    
}

drawCircle()是用来画圆的,参数是圆心的坐标和半径。
drawEllipse()是用来画椭圆的,参数是此椭圆的外接(还是外切?)矩形的左下和右上角坐标。(an ellipse within a rectangular box)
drawPolygon()是用来画多边形的(n>=3)。第一个参数是所有顶点的X坐标组成的数组。第二个参数是所有顶点的Y坐标组成的数组。

7.jpg 

再来看另外一个例子,画饼图的:
<?php
// include auto-loader class
require_once 'Zend/Loader/Autoloader.php';

// register auto-loader
$loader = Zend_Loader_Autoloader::getInstance();

// define pie radius
$radius = 100;

// define pie values
$slices = explode(',', $_GET['data']);

// calculate sum of pie values
$sum = array_sum($slices);

// convert each slice into corresponding percentage of 360-degree circle
for ($y = 0; $y < sizeof($slices); $y++) {
  $degrees[$y] = ($slices[$y] / $sum) * 360;
}

try {
  // create PDF
  $pdf = new Zend_Pdf();

  // create A4 page
  $page = new Zend_Pdf_Page(Zend_Pdf_Page::SIZE_A4);

  // set font  
  $font = Zend_Pdf_Font::fontWithName(Zend_Pdf_Font::FONT_HELVETICA);
  $page->setFont($font, 14);

  $lastAngle = 90;  
  for ($z=0; $z<sizeof($slices); $z++)
  {
///////////////////////////////////////////////////////////////////////////
///注:原文中的下面这一段代码不全,我自己补上的。
///////////////////////////////////////////////////////////////////////////
   $dx = $dy = 0;
   if (0 <= $lastAngle && $lastAngle< 90)
   {
    $dy = $radius * sin(deg2rad($lastAngle));
    $dx = $radius * cos(deg2rad($lastAngle));

   }
   elseif (90 <= $lastAngle && $lastAngle< 180)
   {
    $dy = $radius * sin(deg2rad(180-$lastAngle));
    $dx = -1 * $radius * cos(deg2rad(180-$lastAngle));
   }
   elseif (180 <= $lastAngle && $lastAngle < 270)
   {
    $dy = -1 * $radius * sin(deg2rad($lastAngle-180));
    $dx = -1 * $radius * cos(deg2rad($lastAngle-180));
   }
   else
   {
    $dy = -1 * $radius * sin(deg2rad(360-$lastAngle));
    $dx = $radius * cos(deg2rad(360-$lastAngle));
   }
   $endX = 250+$dx;
   $endY = 250+$dy;
/////////////////////////////////////////////////////////////
/////////补充完毕。
/////////////////////////////////////////////////////////////
   $page->drawLine(250, 250, $endX, $endY);

    // reset the last angle value
    $lastAngle = $lastAngle + $degrees[$z]; 
  }  

  // draw the circle outline
  $page->drawCircle(250, 250, 100, Zend_Pdf_Page::SHAPE_DRAW_STROKE);

  // add page to document
  $pdf->pages[] = $page;

  // save document
  $pdf->save('example.pdf');
  echo 'SUCCESS: Document saved!';
} catch (Zend_Pdf_Exception $e) {
  die ('PDF error: ' . $e->getMessage());  
} catch (Exception $e) {
  die ('Application error: ' . $e->getMessage());    
}

艾,这个饼图,不知道中间那段计算直角边长的方法有没有办法简化一些。

8.jpg 

继续吧。
Zend_Pdf还引入了样式(Style)的概念。
所谓的样式就是预先定义好的线条颜色,填充颜色,字体之类的集合。
这个东西最大的好处就是,
当你在多种不同样式之间切换,一个函数setStyle()就搞定了,
不用每次都去重新设定线条颜色,填充颜色之类的。
下面的例子定义了二个样式:cerulean和crimson。然后进行了样式切换。
<?php
// include auto-loader class
require_once 'Zend/Loader/Autoloader.php';

// register auto-loader
$loader = Zend_Loader_Autoloader::getInstance();

try {
  // create PDF
  $pdf = new Zend_Pdf();

  // create A4 page
  $page = new Zend_Pdf_Page(Zend_Pdf_Page::SIZE_A4);

  // define a style  
  $cerulean = new Zend_Pdf_Style();
  $cerulean->setFillColor(new Zend_Pdf_Color_Rgb(0, 161, 224));
  $cerulean->setLineColor(new Zend_Pdf_Color_Rgb(0, 161, 224));
  $cerulean->setLineWidth(5);
  $font = Zend_Pdf_Font::fontWithName(Zend_Pdf_Font::FONT_HELVETICA);
  $cerulean->setFont($font, 32);  

  // define another style
  $crimson = new Zend_Pdf_Style();
  $crimson->setFillColor(new Zend_Pdf_Color_Rgb(194, 0, 0));
  $crimson->setLineColor(new Zend_Pdf_Color_Rgb(194, 0, 0));
  $crimson->setLineWidth(2);
  $font = Zend_Pdf_Font::fontWithName(Zend_Pdf_Font::FONT_TIMES_ROMAN);
  $crimson->setFont($font, 20);  

  // write text to page 
  // using different styles
  $page->setStyle($cerulean)
       ->drawText('That which we call a rose,', 72, 720);

  $page->setStyle($crimson)
       ->drawText('By any other name would smell as sweet.', 72, 620);

  // add page to document
  $pdf->pages[] = $page;

  // send to browser as download
  header("Content-Disposition: attachment; filename=example.pdf");
  header('Content-Type: application/pdf');
  echo $pdf->render();
} catch (Zend_Pdf_Exception $e) {
  die ('PDF error: ' . $e->getMessage());  
} catch (Exception $e) {
  die ('Application error: ' . $e->getMessage());    
}

9.jpg 

很多时候,我们需要用table表格来表现一些数据
But em,
Zend_Pdf 不支持直接画table。
如果我们手绘表格的话,计算量未免太大,一旦要做改动的话,也很难处理。
幸好,ursh开发了一个非官方的table组件;
http://sourceforge.net/projects/zendpdftable/
虽然是beta版的。
下载之后,扔到Zend Framework的library目录里面。
看以下的代码:
<?php
// include auto-loader class
require_once 'Zend/Loader/Autoloader.php';

// register auto-loader
$loader = Zend_Loader_Autoloader::getInstance();

try {
  // set up database access parameters  
  $params = array ('host'     => '127.0.0.1',
                   'username' => 'user',
                   'password' => 'pass',
                   'dbname'   => 'world');

  // configure adapter and query database                   
  $db = Zend_Db::factory('PDO_MYSQL', $params);
  $stmt = $db->query('SELECT Name, Code, Region FROM country LIMIT 0, 150');

  // create PDF
  $pdf = new My_Pdf_Document('example.pdf', '.');

  // create page
  $page = $pdf->createPage();

  // define font resource
  $font = Zend_Pdf_Font::fontWithName(Zend_Pdf_Font::FONT_HELVETICA);

  // set font
  $page->setFont($font, 24);

  // create table
  $table = new My_Pdf_Table(3);

  // iterate over record set
  // set up table content
  while ($record = $stmt->fetch()) {
    $row = new My_Pdf_Table_Row();
    $cols = array();
    foreach ($record as $k => $v) {
      $col = new My_Pdf_Table_Column();
      $col->setText($v);
      $cols[] = $col;      
    }
    $row->setColumns($cols);
    $row->setFont($font, 14);
    $row->setBorder(My_Pdf::TOP, new Zend_Pdf_Style());
    $row->setBorder(My_Pdf::BOTTOM, new Zend_Pdf_Style());
    $row->setBorder(My_Pdf:EFT, new Zend_Pdf_Style());
    $row->setCellPaddings(array(10,10,10,10));
    $table->addRow($row);
  }

  // add table to page
  $page->addTable($table, 0, 0);

  // add page to document
  $pdf->addPage($page);

  // save as file
  $pdf->save();
  echo 'SUCCESS: Document saved!';  
} catch (Zend_Pdf_Exception $e) {
  die ('PDF error: ' . $e->getMessage());  
} catch (Exception $e) {
  die ('Application error: ' . $e->getMessage());    
}

10.jpg 

也没什么好解释的。

为多页面的pdf文件创建书签(bookmark,目录)
就是下面的效果:
11.jpg 

代码:
<?php
// include auto-loader class
require_once 'Zend/Loader/Autoloader.php';

// register auto-loader
$loader = Zend_Loader_Autoloader::getInstance();

try {
  // create PDF
  $pdf = new Zend_Pdf();

  // create root node of outline tree
  $pdf->outlines[0] = Zend_Pdf_Outline::create('Table of Contents', null);

  // add pages to document
  // and page links to outline
  for ($x=1; $x<=10; $x++) {
    $page = new Zend_Pdf_Page(Zend_Pdf_Page::SIZE_A4);
    $pdf->pages[] = $page;
    $destination{$x} = Zend_Pdf_Destination_Fit::create($page);
    $pdf->setNamedDestination('page_'.$x, $destination{$x});    
    $pdf->outlines[0]->childOutlines[] = Zend_Pdf_Outline::create('Page '.$x, $pdf->getNamedDestination('page_'.$x));    
  }

  // save documents
  $pdf->save('example.pdf');    
  echo 'SUCCESS: Document saved!';
} catch (Zend_Pdf_Exception $e) {
  die ('PDF error: ' . $e->getMessage());  
} catch (Exception $e) {
  die ('Application error: ' . $e->getMessage());    
}
这个也不讲了,看看代码吧。
    $destination{$x} = Zend_Pdf_Destination_Fit::create($page);
    $pdf->setNamedDestination('page_'.$x, $destination{$x});    
    $pdf->outlines[0]->childOutlines[] = Zend_Pdf_Outline::create('Page '.$x, $pdf->getNamedDestination('page_'.$x));

这里可以简化成:
    $pdf->outlines[0]->childOutlines[] = Zend_Pdf_Outline::create('Page '.$x, Zend_Pdf_Destination_Fit::create($page));

最后讲一下如何设置pdf文件的元信息(meta-data),就是下面的东西:
12.jpg 

<?php
// include auto-loader class
require_once 'Zend/Loader/Autoloader.php';

// register auto-loader
$loader = Zend_Loader_Autoloader::getInstance();

try {
  // create PDF
  $pdf = new Zend_Pdf();

  // create A4 page
  $page = new Zend_Pdf_Page(Zend_Pdf_Page::SIZE_A4);

  // set font  
  $font = Zend_Pdf_Font::fontWithName(Zend_Pdf_Font::FONT_HELVETICA);
  $page->setFont($font, 14);

  // write text to page 
  $page->drawText('That which we call a rose,', 72, 720)
       ->drawText('By any other name would smell as sweet.', 72, 620);

  // add page to document
  $pdf->pages[] = $page;

  // set document properties
  $pdf->properties['Author'] = 'William Shakespeare';
  $pdf->properties['Title'] = 'Romeo and Juliet';
  $pdf->properties['Subject'] = 'Act II, Scene II';
  $pdf->properties['Keywords'] = 'shakespeare, romeo, juliet, capulet, montague';
  $pdf->properties['CreationDate'] = "D:201007210634Z00'00'";
  $pdf->properties['ModDate'] = "D:201008150634Z00'00'";

  // save as file
  $pdf->save('example.pdf');
  echo 'SUCCESS: Document saved!';
} catch (Zend_Pdf_Exception $e) {
  die ('PDF error: ' . $e->getMessage());  
} catch (Exception $e) {
  die ('Application error: ' . $e->getMessage());    
}
只要设置Zend_Pdf的properties数组里的相关属性即可。

文章就到这里了。
其它更多的功能,比如pdf文件与用户交互啦,就靠您自己去测试了。

Zend_Pdf官方文档:
http://framework.zend.com/manual/zh/zend.pdf.html

posted @ 2011-01-07 15:10  春哥也编程  阅读(2879)  评论(0编辑  收藏  举报