本文詳細介紹使用 PHP 動態(tài)構建 PDF 文件的整個過程。使用免費 PDF 庫 (FPDF) 或 PDFLib-Lite 等開源工具進行實驗,并使用 PHP 代碼控制 PDF 內容格式。
有時您需要準確控制要打印的頁面的呈現方式。在這種情況下,HTML 就不再是最佳選擇了。PDF 文件使您能夠完全控制頁面的呈現方式,以及文本、圖形和圖像在頁面上的呈現方式。遺憾的是,用來構建 PDF 文件的 API 不屬于 PHP 工具包的標準部件。現在您需要提供一點幫助。
當您在網絡上搜索,尋找對 PHP 的 PDF 支持時,您首先發(fā)現的可能是商業(yè) PDFLib 庫及其開源版本 PDFLib-Lite。 這些都是很好的庫,但是商業(yè)版本相當昂貴。PDFLib 庫的精簡版本庫僅作為原始版本分發(fā),當您嘗試在托管環(huán)境下安裝精簡版本時,就會出現這個限制問題。
另一種選擇是免費 PDF 庫 (FPDF),它是本機 PHP,無需要進行任何編譯,是完全免費的,因此,您不會像在未許可版本的 PDFLib 中那樣看到水印。這個免費的 PDF 庫正是我在本文中會用到的庫。
我們將使用女子旱滑比賽的得分來演示動態(tài)構建 PDF 文件的過程。這些得分是從 Web 中獲得并被轉換成 XML。清單 1 顯示了一個示例 XML 數據文件。
清單 1. XML 數據
1
2
3
4
5
6
7
8
9
10
11
12
13
|
< 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 > |
XML 的根元素是一個 events 標記。按事件對數據進行分組,每個事件都包含多個比賽。在 events 標記內,是一系列的 event 標記,在這些標記中有多個 game 標記。 這些 game 標記中包含參加比賽的兩個隊的名稱以及他們在比賽中的得分。
清單 2 展示了用來讀取 XML 的 PHP 代碼。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
<?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 ; } ?> |
這段腳本實現了一個 getResults 函數,以便將 XML 文件讀入 DOM 文檔。然后使用 DOM 調用遍歷所有 event 和 game 標記,以構建一個事件陣列。該數列內的每個元素都是一個散列表,包含事件名稱和比賽項目的陣列。結構基本上是 XML 結構的內存版。
為了測試這個腳本的作用,將構建一個 HTML 導出頁面,使用 getResults 函數讀取文件,然后以一系列 HTML 表的形式輸出數據。清單 3 顯示了該測試所用的 PHP 代碼。
清單 3. 結果 HTML 頁面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
<? 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 } ?> |
通過代碼 getresults.php,XML 數據文件被上傳到 Web 服務器,您可以查看 HTML 結果,這與 圖 1 類似。
圖 1. HTML 格式的競賽結果
在該結果中,對獲勝隊使用了粗體,以便查看哪支隊贏得了哪場比賽。
構建 PDF
獲得數據之后,應將重點放在構建 PDF 文件上。第一步是下載 FPDF 庫,然后將其安裝在與現有應用文件集相同的目錄中。實際上,只要是在 PHP 庫路徑中,您可以將它安裝在任何您喜歡的地方。追蹤您放置字體目錄的地方,因為您需要設置 ‘FPDF_FONTPATH',如 清單 4 所示。
清單 4. PDF Hello World
1
2
3
4
5
6
7
8
9
10
11
|
<?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(); ?> |
這段腳本實際上是一個 “Hello World”,但采用的是 PDF 格式而不是 HTML。這段腳本執(zhí)行的第一個操作是使用 define 語句設置 FPDF 字體目錄的位置。然后使用 require 語句引入 FPDF 庫。這段腳本從該庫創(chuàng)建了一個 FPDF 對象,設置字體,添加一個頁面,然后使用 Cell 方法將一些文本放在該頁面上,并輸出 PDF。
圖 2 展示了一切都正常情況下的結果。
圖 2. PDF 格式的 Hello World
如果沒有看到 PDF,那么您可能想在命令行運行這段腳本,查看是否丟失了 fpdf.php 文件或者存在其他問題。
既然 PDF 呈現正常,那么現在應該將其與旱滑結果文件合并,并查看可以動態(tài)生成哪些內容。清單 5 展示了該合并操作的第一個版本。
清單 5. 顯示結果的首版 PDF
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
<?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 類別。在這些子類內,我們創(chuàng)建了一個名為 EventTable 的新方法,為給定事件構建了一個結果表。在這種情況下,我們從小處著手,只輸出了事件名稱。該名稱位于腳本底部,包裝在 foreach 循環(huán)中,該腳本為每個事件添加一個頁面,然后調用 EventTable 方法。
可在 圖 3 中看到這段腳本的輸出。
圖 3. 動態(tài) PDF 的第一個版本
向下滾動頁面,以展示每個事件都在自己的頁面上。此處的下一步操作是開始將結果添加到頁面。
構建結果表
在構建 PDF 文件時,構建無表結構就像構建 HTML 一樣簡單。構建表的方法是構建許多寬度、字體、填充顏色、行顏色等各不相同的單元。
清單 6 展示了設置表的標題欄的添加代碼。
清單 6. 添加結果表標題
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
<?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 中,標題以白色文本呈現在灰色背景上。這種格式有助于將其與呈現在標題下面的數據進行區(qū)分。要呈現比賽結果,請在 清單 7 中添加以下代碼。
清單 7. 添加完整的結果表
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
|
<?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 循環(huán),它將在每個比賽上進行迭代。圖 5 顯示了用于此用途的代碼。
圖 5. 包含結果表的 PDF
$fill 變量可通過切換來改變表中每行的顏色。優(yōu)勝隊的名稱和得分用加粗、斜體字體表示,這樣可以清晰顯示它們。還需注意的是,字體從標題的 Arial 字體更改成了顯示比賽內容所用的 Times 字體。
要完成示例代碼,則需要添加一些圖形。
使用圖形進行修飾
向 PDF 添加圖像非常容易。首先需要從 Web 抓取一個圖像。我抓取了一個旱滑參賽隊的徽標,并將其存儲為 PNG 格式的圖像。 此后,我一直使用 清單 8 中的新代碼。
清單 8. 添加徽標圖像
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
|
<?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 文件是非常容易的。這種方法非常適用于打印發(fā)x票或票據,或填寫表單,以及需要嚴格控制內容布局的任何項目。