13.2 Producing PDF
In this
section, we show you how to use
R&OS Ltd's
free PHP PDF creation library (we refer to this as
pdf-php throughout this section). The library
has two advantages over other approaches: it's free
and it doesn't require any additional PHP
configuration. What's more, it's
powerful and you can do most things you need, including producing
tables containing results from database queries and inserting images
into a document.
You can find out more about pdf-php from http://www.ros.co.nz/pdf and you can download
the source, documentation, and get involved in the project at
http://sourceforge.net/projects/pdf-php/.
Instructions for installing pdf-php are included in Appendix A through Appendix C.
13.2.1 Hello, world
Example 13-1
shows a simple PHP example.
Example 13-1. A simple example that produces Hello, world on an A4 page
<?php
require "class.ezpdf.php";
// Create a new PDF document
$doc =& new Cezpdf( );
// Add text to the document
$doc->ezText("Hello, world");
// Output the document
$doc->ezStream( );
?>
This PHP example produces the PDF document shown in Figure 13-1.
The base class for producing PDF files is
class.pdf.php
,
but we've used its extension
class.ezpdf.php
in our simple example. The extension has several useful utilities for
document creation and it still includes all the methods of the base
class, and so we recommend always using it instead.
In Example 13-1, it's assumed both
class files are in the same directory as the example code (or in a
directory set by the include_path directive in
your php.ini file). In addition,
it's assumed that the fonts are in a subdirectory of
the example directory named fonts; the fonts are
part of the pdf-php install package.
The constructor Cezpdf(
)
creates a new PDF document with the
default A4 paper size and the default portrait orientation. The
=& operator creates an instance of a class,
and returns a reference to the instance (and not the whole object
itself). The =& operator is a faster, more
memory efficient alternative to the = operator.
It's discussed in Chapter 4.
The ezText( )
method adds text to the
document at the current cursor position (which defaults to the
top-left corner; there are, however, top and left margins of 30
points, where a point is 1/72 of an inch), with the current font
(which defaults to Helvetica), and the current font size (which
defaults to 12). The ezStream(
)
method cleans up and then creates the
PDF output and sends it to the browser; this is a similar approach to
templates (as described in Chapter 7), where a
document is first prepared and later output using a method.
In many browsers, when a PDF document arrives as part of an HTTP
response, a window containing the PDF document will automatically
appear. However, in some browsers, the arrival of a PDF document will
cause a dialog box to pop up that asks whether to save or open the
PDF file. If you choose open and have a PDF viewer correctly
installed, you'll see the output. Now is a good time
to get yourself a PDF viewer, as you'll need it
throughout this chapter.
You might be wondering what the content of a
PDF
file looks like. The answer is that it's an ASCII
text file that contains instructions on how to render and present the
document content; however, it can be compressed, and so you might
find it isn't always readable with a text editor.
For a document containing graphics, the text can be a complicated
list of instructions about lines and points. However, for simple text
that's rendered using a font, it's
basically human-readable text that you could conceivably edit. For
example, here's the part of the file output from
Example 13-1 that creates the Hello, world message:
7 0 obj
<<
/Length 55 >>
stream
BT 30.000 800.330 Td /F1 10.0 Tf (Hello, world) Tj ET
endstream
endobj
13.2.2 A Full-Featured Document
Example 13-2
shows a more complex
example that makes use of many of the features of the pdf-php
library. The example prints the first two pages of Lewis
Carroll's Alice's
Adventures in Wonderland in a two-column format, with a
title on the first page and an image from the book.
Example 13-2. Formatting Alice's Adventures in Wonderland for printing
<?php
require "class.ezpdf.php";
require "alice.inc";
// Create a new PDF document
$doc =& new Cezpdf( );
// Use the Helvetica font for the headings
$doc->selectFont("./fonts/Helvetica.afm");
// Output the book heading and author
$doc->ezText("<u>Alice's Adventures in Wonderland</u>", 24,
array("justification"=>"center"));
$doc->ezText("by Lewis Carroll", 20, array("justification"=>"center"));
// Create a little bit of space
$doc->ezSetDy(-10);
// Output the chapter title
$doc->ezText(Chapter 1: Down the Rabbit-Hole", 18,
array("justification"=>"center"));
// Number the pages
$doc->ezStartPageNumbers(320, 15, 8,"",
"{PAGENUM} of {TOTALPAGENUM} pages");
// Create a little bit of space
$doc->ezSetDy(-30);
// Switch to two-column mode
$doc->ezColumnsStart(array("num"=>2, "gap"=>15));
// Use the Times-Roman font for the text
$doc->selectFont("./fonts/Times-Roman.afm");
// Include an image with a caption
$doc->ezImage("rabbit.jpg", "", "", "none");
$doc->ezText("<b>White Rabbit checking watch</b>",
12,array("justification"=>"center"));
// Create a little bit of space
$doc->ezSetDy(-10);
// Add chapter text to the document
$doc->ezText($text,10,array("justification"=>"full"));
// Output the document
$doc->ezStream( );
?>
The first page is shown rendered by the xpdf viewer in Figure 13-2.
The following code fragment from Example 13-2 sets
the font:
// Use the Helvetica font for the headings
$doc->selectFont("./fonts/Helvetica.afm");
We use Helvetica for the headings, and Times-Roman for the body of
the document.
The
available fonts are in the subdirectory fonts in the pdf-php install
package, and are passed to the selectFont(
)
method using their path and the full
file name. The font name must include the .afm
extension, and only .afm format files are
supported; however, there are free utilities, such as
t1utils and ttf2pt1, that
convert other font formats (such as .ttf) into
.afm files.
This next fragment outputs the headings:
// Output the book heading and author
$doc->ezText("<u>Alice's Adventures in Wonderland</u>", 24,
array("justification"=>"center"));
$doc->ezText("by Lewis Carroll", 20, array("justification"=>"center"));
The ezText( )
method has three parameters,
but only the first is mandatory. The first parameter is the text to
add to the document, and it can include simple HTML-like markup
elements such as <u> for underline and
<b> for bold. Text is output by the method,
and followed with a carriage return in the same way as
echo or print in PHP. The
second parameter is the font size to use (the default is 12), and the
third parameter is an array of options. In this example,
we've set the justification
parameter to center, but it can also be set to
left, right, or
full; we use full for the text.
The complete list of options is described later in Section 13.3
The ezSetDy( )
method is used to create space
between text and images. For example, the following fragment moves
the cursor down the page by 10 points:
// Create a little bit of space
$doc->ezSetDy(-10);
A negative value is downwards, and a positive value is upwards. In
PDF, the bottom-left-hand corner of a page is coordinate X=0, Y=0,
and the top-right has the maximum X and Y values. For an A4 page, the
top-right corner has a point value of X=595.28 and Y=841.89, and for
US letter of X=612.00 and Y=792.00.
The class includes the useful ezStartPageNumbers(
)
method for numbering pages. We use it
as follows:
// Number the pages
$doc->ezStartPageNumbers(320, 15, 8, "",
"{PAGENUM} of {TOTALPAGENUM} pages");
The first two parameters are the X and Y coordinates of where to put
the page number text, and the third parameter is the font size to
use; the first three parameters are mandatory. The optional fourth
parameter can be set to left or
right, and indicates whether to put the text to
the left or right of the X coordinate; by default, the text is
written to the left of the X coordinate. The optional fifth parameter
specifies how to present the page numbering; by default, it is
{PAGENUM} of {TOTALPAGENUM} but
we've set it to {PAGENUM} of {TOTALPAGENUM}
pages to get strings such as 1 of 2
pages. The optional sixth parameter is a page number and,
if it is supplied, the current page is numbered beginning with the
number.
We've presented the text of the book in a two column
newspaper-like format. This is achieved by calling the
ezColumnsStart(
)
method as follows:
// Switch to two-column mode
$doc->ezColumnsStart(array("num"=>2, "gap"=>15));
The method takes one optional parameter. The parameter is an array
that specifies the number of columns and the gap in points between
the columns. If the parameter is omitted, the number of columns
defaults to 2 and the gap to 10. The method ezColumnsStop(
) stops multi-column mode, but we don't
use it here because we're working with only one
chapter.
To include an image in the text, you can use the ezImage(
)
method. We use it to include a
picture of the white rabbit after the headings:
$doc->ezImage("rabbit.jpg", "", "", "none");
A great feature of this method is that it doesn't
require any additional configuration: you don't need
to install any graphics libraries (such as GD) and
it'll work on all platforms without modification.
The method takes six parameters. The first parameter is a mandatory
image file path and name, and only JPEG and PNG format images are
supported. All of the remaining parameters are optional. The second
parameter is the amount of padding in points to place around the
image, and it defaults to 5. The third parameter is the width of the
image, and the default is the image's actual width.
The fourth parameter is a resize value that controls how the image
fits in a column, and we've used
none so that the image isn't
resized at all. The fifth parameter specifies justification, and can
be set to left, right, or
center with a default of
center. The sixth parameter is a border to place
around the image, and defaults to none. More
details on all parameters are provided in Section 13.3.
After we've finished with headings and images, the
following fragment includes the text of the book into the PDF
document:
$doc->ezText($text,10,array("justification"=>"full"));
The variable $text contains the text of the book.
It is set in the
alice.inc
include file. Here are the first few lines
of alice.inc:
<?php
$text = "Alice was beginning to get very tired of sitting by her sister on
the bank, and of having nothing to do: once or twice she had peeped into
the book her sister was reading, but it had no pictures or conversations
in it, ... ";
Carriage returns and whitespace characters are preserved in the
output. So, for example, a carriage return creates a new line in the
PDF file; this is unlike HTML, where whitespace is ignored. The book
text itself is sourced from the Project Gutenberg homepage at
http://gutenberg.net.
13.2.3 A Database Example
Example 13-3
shows a script that produces a page
containing one customer's details from a
customer table. The
customer
table is discussed in Chapter 5 and created
with the following CREATE TABLE statement:
CREATE TABLE customer (
cust_id int(5) NOT NULL,
surname varchar(50),
firstname varchar(50),
initial char(1),
title_id int(3),
address varchar(50),
city varchar(50),
state varchar(20),
zipcode varchar(10),
country_id int(4),
phone varchar(15),
birth_date char(10),
PRIMARY KEY (cust_id)
) type=MyISAM;
The example also uses the titles lookup table
that contains title_id values and titles (such as
Mr. and Miss), and the countries lookup table
that contains country_id values and country names.
The output of Example 13-3 is shown in Figure 13-3.
Example 13-3. Producing customer information from the customer table
<?php
require "class.ezpdf.php";
require "db.inc";
$query = "SELECT * FROM customer, titles, countries
WHERE customer.title_id = titles.title_id
AND customer.country_id = countries.country_id
AND cust_id = 1";
if (!($connection = @ mysql_connect($hostName, $username, $password)))
die("Could not connect to database");
if (!(mysql_selectdb($databaseName, $connection)))
showerror( );
if (!($result = @ mysql_query($query, $connection)))
showerror( );
$row = mysql_fetch_array($result);
// Construct the title and name
$name = "{$row["title"]} {$row["firstname"]}";
if (!empty($row["initial"]))
$name .= " {$row["initial"]} ";
$name .= "{$row["surname"]}";
// Create a new PDF document
$doc =& new Cezpdf( );
// Use the Helvetica font
$doc->selectFont("./fonts/Helvetica.afm");
// Create a heading
$doc->ezText("<u>Customer Details for {$name}</u>",
14, array("justification"=>"center"));
// Create a little bit of space
$doc->ezSetDy(-15);
// Set up an array of customer information
$table = array(
array("Details"=>"Title and name",
"Value"=>$name),
array("Details"=>"Address",
"Value"=>"{$row["address"]} {$row["city"]} {$row["zipcode"]}"),
array("Details"=>"State and country",
"Value"=>"{$row["state"]} {$row["country"]}"),
array("Details"=>"Telephone",
"Value"=>$row["phone"]),
array("Details"=>"Date of birth",
"Value"=>$row["birth_date"]));
$doc->ezTable($table);
// Output the document
$doc->ezStream( );
?>
The database processing in Example 13-3 is similar to
that of most examples in previous chapters. The script queries and
retrieves the customer details for customer #1, including the
customer's title and country. The results are stored
in the array $row, and the array is then used as
the source of data for the PDF document.
The use of the pdf-php library is also similar to that in our
previous examples, with the exception that the customer details are
shown in a table using the ezTable(
)
method. The ezTable(
) method is a flexible tool that allows you to present
data in different table styles and to configure the column headings,
column widths, shading, borders, and alignment.
In this example, we only use the basic features of the
ezTable( ) method. First, we've
created an array that contains the data we want to display in the
table:
// Set up an array of customer information
$table = array(
array("Details"=>"Title and name",
"Value"=>$name),
array("Details"=>"Address",
"Value"=>"{$row["address"]} {$row["city"]} {$row["zipcode"]}"),
array("Details"=>"State and country",
"Value"=>"{$row["state"]} {$row["country"]}"),
array("Details"=>"Telephone",
"Value"=>$row["phone"]),
array("Details"=>"Date of birth",
"Value"=>$row["birth_date"]));
The array contains five elements, each of which is itself an array.
Each of these five inner arrays has two associatively-labeled
elements: Details and Value.
The Details element holds as a row label value
such as Title and name, and the
Value element holds the data that matches the
label.
The PDF table itself is created with the fragment:
$doc->ezTable($table);
The method creates a table with the number of rows equal to the
number of elements in the array (in our example, five rows). The
number of columns in the table is equal to the number of elements in
the inner arrays and, in our example, they each have two elements. By
default, column headings are taken from the associative-access keys,
and the data in the tables comes from the values. The default mode is
to create a bordered table with shading in every second row, and to
center the table in the output.
13.2.4 Creating a Report
Example 13-4
shows a script that produces a more
complex purchase report. The report is a table that lists the
customers in the winestore database, the number of orders
they've placed, the number of bottles of wine
they've bought, and the total dollar value of their
purchases. We also show totals at the end of each page, and an
overall total on the final page.
Example 13-4. A script to produce a customer purchasing report
<?php
require "class.ezpdf.php";
require "db.inc";
// Do the querying to produce the customer report
$query = "SELECT customer.cust_id, surname, firstname,
SUM(qty), SUM(price), MAX(order_id)
FROM customer, items
WHERE customer.cust_id = items.cust_id
GROUP BY customer.cust_id";
if (!($connection = @ mysql_connect($hostName, $username, $password)))
die("Could not connect to database");
if (!(mysql_selectdb($databaseName, $connection)))
showerror( );
if (!($result = @ mysql_query($query, $connection)))
showerror( );
// Now, create a new PDF document
$doc =& new Cezpdf( );
// Use the Helvetica font
$doc->selectFont("./fonts/Helvetica.afm");
// Number the pages
$doc->ezStartPageNumbers(320, 15, 8);
// Set up running totals and an empty array for the output
$counter = 0;
$table = array( );
$totalOrders = 0;
$totalBottles = 0;
$totalAmount = 0;
// Get the query rows, and put them in the table
while ($row = mysql_fetch_array($result))
{
// Counts the total number of rows output
$counter++;
// Add current query row to the array of customer information
$table[] = array(
"Customer #"=>$row["cust_id"],
"Name"=> "{$row["surname"]}, {$row["firstname"]}",
"Orders Placed"=>$row["MAX(order_id)"],
"Total Bottles"=>$row["SUM(qty)"],
"Total Amount"=>"\${$row["SUM(price)"]}");
// Update running totals
$totalOrders += $row["MAX(order_id)"];
$totalBottles += $row["SUM(qty)"];
$totalAmount += $row["SUM(price)"];
}
// Today's date is used in the table heading
$date = date("d M Y");
// Right-justify the numeric columns
$options = array("cols" =>
array("Total Amount" =>
array("justification" => "right"),
"Total Bottles" =>
array("justification" => "right"),
"Orders Placed" =>
array("justification" => "right")));
// Output the table with a heading
$doc->ezTable($table, "", "Customer Order Report for {$date}",
$options);
$doc->ezSetDy(-10);
// Show totals
$doc->ezText("Total customers: {$counter}");
$doc->ezText("Total orders: {$totalOrders}");
$doc->ezText("Total bottles: {$totalBottles}");
$doc->ezText("Total amount: \${$totalAmount}");
// Output the document
$doc->ezStream( );
?>
The first page of the output of Example 13-4 is shown
in Figure 13-4 and the final page of the output in
Figure 13-5.
The report makes use of two tables, the customer
table shown in the previous section and an items
table that's described in more detail in Chapter 5. The items table is
created with the following statement:
CREATE TABLE items (
cust_id int(5) NOT NULL,
order_id int(5) NOT NULL,
item_id int(3) NOT NULL,
wine_id int(4) NOT NULL,
qty int(3),
price decimal(5,2),
PRIMARY KEY (cust_id,order_id,item_id)
) type=MyISAM;
We only use the order_id (which is used to count
how many orders each customer has placed), qty
(quantity of wine ordered in bottles), and price
(per bottle price) attributes from the items
table in this section.
We use the following query in our report:
$query = "SELECT customer.cust_id, surname, firstname,
SUM(qty), SUM(price), MAX(order_id)
FROM customer, items
WHERE customer.cust_id = items.cust_id
GROUP BY customer.cust_id";
The query groups each customer's items together by
his unique cust_id, and we then discover the
customer's name, cust_id, the sum
of bottles sold using SUM(qty), the total value of
the sales using SUM(price), and the number of
orders placed using MAX(order_id). We run the
query using the usual MySQL functions, and then retrieve each row of
the results and add it to the PDF document as a table row.
We use the class constructor and the SelectFont(
), ezStartPageNumbers( ),
ezText( ), ezSetDy( ), and
ezStream( ) methods in the same way as in the
previous three sections.
Data comes from multiple rows in our database query results and is
displayed using the ezTable( ) method in this
example. To create the table, we first initialize an empty array
using:
$table = array( );
Then, for each row in the table, we add an element to that array that
is itself an array that contains five elements:
// Add to the array of customer information
$table[] = array(
"Customer #"=>$row["cust_id"],
"Name"=> "{$row["surname"]}, {$row["firstname"]}",
"Orders Placed"=>$row["MAX(order_id)"],
"Total Bottles"=>$row["SUM(qty)"],
"Total Amount"=>"\${$row["SUM(price)"]}");
The associative-access labels (Customer #,
Name, Orders Placed,
Total Bottles, and Total
Amount) are used as the column headings for the table, and
the customer data from the query is used to populate the rows.
The table itself is then output with the following fragment:
// Output the table
$doc->ezTable($table, "", "Customer Order Report for {$date}", $options);
We use the optional third parameter that adds a title to the table.
This is output on the first page of output. The fourth optional
parameter is also used in this example to right-justify the numeric
columns (so that the differences in magnitude are obvious and so the
decimal points line up). To do this, we create a nested array:
// Right-justify the numeric columns
$options = array("cols" =>
array("Total Amount" =>
array("justification" => "right"),
"Total Bottles" =>
array("justification" => "right"),
"Orders Placed" =>
array("justification" => "right")));
The outer array contains one element, with the associative key
'cols', and this indicates the option
we're setting (you can set more than 15 different
options for a table). It contains as a value another array that
contains as keys the names of the three columns we want to configure
('Total Amount', 'Total
Bottles', and 'Orders Placed'). Each of
these three elements has as its value yet another array, this time
with the column setting we want to change as the key
('justification') and what we want to set it to
('right'). This complex options parameter is
discussed in more detail in Section 13.3.
Finally, with the pages of tables complete, overall totals of
customers, orders, bottles, and sales are added using
ezText( ) and the whole document is output using
ezStream( ).
|
No comments:
Post a Comment