使用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)原点位于页面的左下脚。
值得提一下的是, 通过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文件会占用更多的磁盘空间。
如果想插入一张图片到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());
}
上面讲的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 左对齐。
此外,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
再来画几个矩形框吧
<?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()可以定义矩形的填充颜色。
再来看如何画其它的形状(椭圆,多边形):
<?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坐标组成的数组。
再来看另外一个例子,画饼图的:
<?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());
}
艾,这个饼图,不知道中间那段计算直角边长的方法有没有办法简化一些。
继续吧。
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());
}
很多时候,我们需要用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());
}
也没什么好解释的。
为多页面的pdf文件创建书签(bookmark,目录)
就是下面的效果:
代码:
<?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),就是下面的东西:
<?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