This article details the entire process of dynamically constructing PDF files using PHP. Experiment with open source tools like Free PDF Library (FPDF) or PDFLib-Lite, and control PDF content formatting with PHP code.
Sometimes you need to control exactly how the page you want to print is rendered. In this case, HTML is no longer the best choice. PDF files give you complete control over how the page is rendered and how text, graphics, and images appear on the page. Unfortunately, the API used to build PDF files is not a standard part of the PHP toolkit. Now you need to provide a little help.
When you search the web for PDF support for PHP, the first thing you may find is the commercial PDFLib library and its open source version, PDFLib-Lite. These are great libraries, but the commercial versions are quite expensive. The lite version of the PDFLib library is only distributed as the original version, and this limitation occurs when you try to install the lite version in a hosted environment.
Another option is the Free PDF Library (FPDF), which is native PHP, requires no compilation, and is completely free, so you won't see the watermark like you do in the unlicensed version of PDFLib. This free PDF library is exactly what I'll be using in this article.
We will use scores from a women’s roller derby competition to demonstrate the process of dynamically building a PDF file. These scores are obtained from the Web and converted into XML. Listing 1 shows a sample XML data file.
Listing 1. XML data
<events> <event name="Beast of the East 2011"> <game score1="88" team1="Toronto Gore-Gore Rollergirls" team2="Montreal La Racaille" score2="11"> <game score1="58" team1="Toronto Death Track Dolls" team2="Montreal Les Contrabanditas" score2="49"> ... </game></game></event> <event name="Dustbowl Invitational 2011"> ... </event> <event name="The Great Yorkshire Showdown 2011"> ... </event> </events>
The root element of XML is an events tag. The data is grouped by events, with each event containing multiple matches. Within the events tag is a series of event tags, and within these tags there are multiple game tags. These game tags contain the names of the two teams participating in the game and their scores during the game.
Listing 2 shows the PHP code used to read XML.
<?php function getResults() { $xml = new DOMDocument(); $xml->load('events.xml'); $events = array(); foreach($xml->getElementsByTagName('event') as $event) { $games = array(); foreach($event->getElementsByTagName('game') as $game) { $games []= array( 'team1' => $game->getAttribute('team1'), 'score1' => $game->getAttribute('score1'), 'team2' => $game->getAttribute('team2'), 'score2' => $game->getAttribute('score2') ); } $events []= array( 'name' => $event->getAttribute('name'), 'games' => $games ); } return $events; } ?>
This script implements a getResults function to read an XML file into a DOM document. Then use DOM calls to iterate over all event and game tags to build an event array. Each element in this array is a hash table containing an array of event names and competition items. Structures are basically in-memory versions of XML structures.
To test the effectiveness of this script, we will build an HTML export page, use the getResults function to read the file, and then output the data in the form of a series of HTML tables. Listing 3 shows the PHP code used for this test.
Listing 3. Result HTML page
<?php include_once('getresults.php'); $results = getResults(); foreach( $results as $event ) { ?> <h1><?php echo( $event['name'] ) ?></h1> <?php foreach( $event['games'] as $game ) { $s1 = (int)$game['score1']; $s2 = (int)$game['score2']; ?> <?php } ?> <table><tbody><tr> <td style="font-weight:<?php echo( ( $s1 > $s2 ) ? 'bold' : 'normal') ?>"> <?php echo( $game['team1'] ) ?></td> <td><?php echo( $s1 ) ?></td> <td style="font-weight:<?php echo( ( $s2 > $s1 ) ? 'bold' : 'normal') ?>"> <?php echo( $game['team2'] ) ?></td> <td><?php echo( $s2 ) ?></td> </tr></tbody></table> <?php } ?>
With the code getresults.php, the XML data file is uploaded to the web server and you can view the HTML results, similar to Figure 1.
Figure 1. Competition results in HTML format
In this result, the winning team is bolded so you can see which team won which game.
Build PDF
Once you have your data, you should focus on building the PDF file. The first step is to download the FPDF library and install it in the same directory as your existing application fileset. In fact, you can install it anywhere you like as long as it's in the PHP library path. Keep track of where you put the font directory, because you need to set 'FPDF_FONTPATH', as shown in Listing 4.
List 4. PDF Hello World
<?php define('FPDF_FONTPATH','/Library/WebServer/Documents/derby/font/'); require( 'fpdf.php' ); $pdf = new FPDF(); $pdf->SetFont('Arial','',72); $pdf->AddPage(); $pdf->Cell(40,10,"Hello World!",15); $pdf->Output(); ?>
This script is actually a "Hello World", but in PDF format instead of HTML. The first thing this script does is set the location of the FPDF font directory using the define statement. Then use the require statement to introduce the FPDF library. This script creates an FPDF object from the library, sets the font, adds a page, then uses the Cell method to put some text on the page and outputs the PDF.
Figure 2 shows the results when everything is normal.
Figure 2. Hello World in PDF format
If you don’t see the PDF, you may want to run this script from the command line to see if the fpdf.php file is missing or there are other issues.
Now that the PDF renders fine, it's time to merge it with the skiing results file and see what can be dynamically generated. Listing 5 shows the first version of this merge operation.
Listing 5. First PDF showing results
<?php define('FPDF_FONTPATH','/Library/WebServer/Documents/derby/font/'); require( 'fpdf.php' ); require( 'getresults.php' ); class PDF extends FPDF { function EventTable($event) { $this->Cell(40,10,$event['name'],15); $this->Ln(); } } $pdf = new PDF(); $pdf->SetFont('Arial','',48); foreach( getResults() as $event ) { $pdf->AddPage(); $pdf->EventTable($event); } $pdf->Output(); ?>
我们没有从外部扩展 FPDF 类别,而是使用我们自己的 PDF 子类来扩展 FPDF 类别。在这些子类内,我们创建了一个名为 EventTable 的新方法,为给定事件构建了一个结果表。在这种情况下,我们从小处着手,只输出了事件名称。该名称位于脚本底部,包装在 foreach 循环中,该脚本为每个事件添加一个页面,然后调用 EventTable 方法。
可在 图 3 中看到这段脚本的输出。
图 3. 动态 PDF 的第一个版本
向下滚动页面,以展示每个事件都在自己的页面上。此处的下一步操作是开始将结果添加到页面。
构建结果表
在构建 PDF 文件时,构建无表结构就像构建 HTML 一样简单。构建表的方法是构建许多宽度、字体、填充颜色、行颜色等各不相同的单元。
清单 6 展示了设置表的标题栏的添加代码。
清单 6. 添加结果表标题
<?php define('FPDF_FONTPATH','/Library/WebServer/Documents/derby/font/'); require( 'fpdf.php' ); require( 'getresults.php' ); class PDF extends FPDF { function EventTable($event) { $this->SetFont('','B','24'); $this->Cell(40,10,$event['name'],15); $this->Ln(); $this->SetXY( 10, 45 ); $this->SetFont('','B','10'); $this->SetFillColor(128,128,128); $this->SetTextColor(255); $this->SetDrawColor(92,92,92); $this->SetLineWidth(.3); $this->Cell(70,7,"Team 1",1,0,'C',true); $this->Cell(20,7,"Score 1",1,0,'C',true); $this->Cell(70,7,"Team 2",1,0,'C',true); $this->Cell(20,7,"Score 2",1,0,'C',true); $this->Ln(); } } $pdf = new PDF(); $pdf->SetFont('Arial','',10); foreach( getResults() as $event ) { $pdf->AddPage(); $pdf->EventTable($event); } $pdf->Output(); ?>
此处的添加代码用于设置字体、颜色和行宽。然后它将呈现包含四个标题列的几个单元格。然后调用 Ln 方法(该方法与回车键等效)启用一个新行。
在浏览器中查看这段脚本时,可以看到类似 图 4 的内容。
图 4. 包含表的标题行的页面
在 图 4 中,标题以白色文本呈现在灰色背景上。这种格式有助于将其与呈现在标题下面的数据进行区分。要呈现比赛结果,请在 清单 7 中添加以下代码。
清单 7. 添加完整的结果表
<?php define('FPDF_FONTPATH','/Library/WebServer/Documents/derby/font/'); require( 'fpdf.php' ); require( 'getresults.php' ); class PDF extends FPDF { function EventTable($event) { $this->SetFont('','B','24'); $this->Cell(40,10,$event['name'],15); $this->Ln(); $this->SetFont('','B','10'); $this->SetFillColor(128,128,128); $this->SetTextColor(255); $this->SetDrawColor(92,92,92); $this->SetLineWidth(.3); $this->Cell(70,7,"Team 1",1,0,'C',true); $this->Cell(20,7,"Score 1",1,0,'C',true); $this->Cell(70,7,"Team 2",1,0,'C',true); $this->Cell(20,7,"Score 2",1,0,'C',true); $this->Ln(); $this->SetFillColor(224,235,255); $this->SetTextColor(0); $this->SetFont(''); $fill = false; foreach($event['games'] as $game) { $this->SetFont('Times',((int)$game['score1']>(int)$game['score2'])?'BI':''); $this->Cell(70,6,$game['team1'],'LR',0,'L',$fill); $this->Cell(20,6,$game['score1'],'LR',0,'R',$fill); $this->SetFont('Times',((int)$game['score1']<(int)$game['score2'])?'BI':''); $this->Cell(70,6,$game['team2'],'LR',0,'L',$fill); $this->Cell(20,6,$game['score2'],'LR',0,'R',$fill); $this->Ln(); $fill =! $fill; } $this->Cell(180,0,'','T'); } } $pdf = new PDF(); $pdf->SetFont('Arial','',10); foreach( getResults() as $event ) { $pdf->AddPage(); $pdf->EventTable($event); } $pdf->Output(); ?>
除了标题行之外,在 EventTable 方法中还有一个 foreach 循环,它将在每个比赛上进行迭代。图 5 显示了用于此用途的代码。
图 5. 包含结果表的 PDF
$fill 变量可通过切换来改变表中每行的颜色。优胜队的名称和得分用加粗、斜体字体表示,这样可以清晰显示它们。还需注意的是,字体从标题的 Arial 字体更改成了显示比赛内容所用的 Times 字体。
要完成示例代码,则需要添加一些图形。
使用图形进行修饰
向 PDF 添加图像非常容易。首先需要从 Web 抓取一个图像。我抓取了一个旱滑参赛队的徽标,并将其存储为 PNG 格式的图像。 此后,我一直使用 清单 8 中的新代码。
清单 8. 添加徽标图像
<?php define('FPDF_FONTPATH','/Library/WebServer/Documents/derby/font/'); require( 'fpdf.php' ); require( 'getresults.php' ); class PDF extends FPDF { function EventTable($event) { $this->Image('logo.png',5,5,33); $this->SetXY( 40, 15 ); $this->SetFont('','B','24'); $this->Cell(40,10,$event['name'],15); $this->Ln(); $this->SetXY( 10, 45 ); $this->SetFont('','B','10'); $this->SetFillColor(128,128,128); $this->SetTextColor(255); $this->SetDrawColor(92,92,92); $this->SetLineWidth(.3); $this->Cell(70,7,"Team 1",1,0,'C',true); $this->Cell(20,7,"Score 1",1,0,'C',true); $this->Cell(70,7,"Team 2",1,0,'C',true); $this->Cell(20,7,"Score 2",1,0,'C',true); $this->Ln(); $this->SetFillColor(224,235,255); $this->SetTextColor(0); $this->SetFont(''); $fill = false; foreach($event['games'] as $game) { $this->SetFont('Times',((int)$game['score1']>(int)$game['score2'])?'BI':''); $this->Cell(70,6,$game['team1'],'LR',0,'L',$fill); $this->Cell(20,6,$game['score1'],'LR',0,'R',$fill); $this->SetFont('Times',((int)$game['score1']<(int)$game['score2'])?'BI':''); $this->Cell(70,6,$game['team2'],'LR',0,'L',$fill); $this->Cell(20,6,$game['score2'],'LR',0,'R',$fill); $this->Ln(); $fill =! $fill; } $this->Cell(180,0,'','T'); } } $pdf = new PDF(); $pdf->SetFont('Arial','',10); foreach( getResults() as $event ) { $pdf->AddPage(); $pdf->EventTable($event); } $pdf->Output(); ?>
清单 8中的关键方法是 Image 方法,它为图像、位置和宽度选取一个文件名称。所有其它参数都是可选的,因此您只指定您想要的信息便可。
到 SetXY 的一些新调用会将文本和表左右移动到适当的位置,防止其覆盖图像。
图 6 显示了这段脚本的输出结果。
图 6. 带有徽标图像的已完成的 PDF
该 PDF 库还提供了其他方法来呈现图形、添加流文本、添加超链接、管理页边距和方向等结构,您可以完全控制您的 PDF 文件。
结束语
使用合适的工具,通过 PHP 构建 PDF 文件是非常容易的。这种方法非常适用于打印发x票或票据,或填写表单,以及需要严格控制内容布局的任何项目。