PDFs with PHP part 2

This tutorial is intended for the PHP programmer who needs to incorporate PDF generation in a script without using external libraries such as PDFlib (often unavailable due to licensing restrictions or lack of funds). This tutorial is the second of two parts, and builds on what was covered in the first part. Therefore, if you have not yet gone through Part 1, you are advised to do so (or at least read through it), before going through this tutorial (Part 2). Apart from what was dealt with in Part 1, no knowledge of PDF file structure is required to understand this tutorial, as all references are explained.

We saw in Part 1 how PDF files are, after all, just plain text files, with specific markup syntax that describes what should happen to objects within the document, such as text and images. We shall now further examine this syntax, to allow us to create a more complete PDF document (i.e more than simple text).

Learning Objectives
With the combined knowledge from Part 1, and from the current tutorial (Part 2), you should be able to put together a simple PDF class that can:
  • Add colors to your PDF documents;
  • Add lines, rectangles and circles;
  • Insert JPEG images.

Prerequisites
You need to have a fully functional PHP install (either PHP 4 or PHP 5), and a running web server to output the PDF file from your script.

Acrobat Reader, XPDF, or equivalent is required, to display the results of your work.

You do not need any external library, either separate or compiled into PHP, to generate your PDF files.

How It Works
We already have the class from Part 1 of this tutorial. In Part 2 we will just add a few more class variables and methods to it.

We shall review the various methods and features of the PDF language, and then finally put it all together as one class.

New Class Variables
In addition to the class variables we introduced in Part 1, we will also need the following.
var $_fill_color = ’0 g’;  // Color used on text and fills.
var $_draw_color = ’0 G’;  // Line draw color.
var $_line_width = 1;      // Drawing line width.
var $_images = array();    // An array of used images.
In the PDF specifications, black is the default value for fill and draw colors, and the default line width is 1 point. Therefore we will use these values as our class defaults for the above variables.

{mospagebreak title=Color}
We have two different color settings to consider. The $_fill_color variable refers to the “non-stroke” color which is applied to fills and text. The $_draw_color variable is the “stroke” color applied to lines.

The following function will set the fill color:
[code]
function setFillColor($cs = 'rgb', $c1, $c2 = 0, $c3 = 0, $c4 = 0)
{
$cs = strtolower($cs);
 if ($cs = 'rgb') {
  /* Using a three component RGB color. */
  $this->_fill_color = sprintf('%.3f %.3f %.3f rg', $c1, $c2, $c3);
 
}
elseif ($cs = 'cmyk') {
        /* Using a four component CMYK color. */
        $this->_fill_color = sprintf('%.3f %.3f %.3f %.3f k', $c1, $c2, $c3, $c4);
    } else {
        /* Grayscale one component color. */
        $this->_fill_color = sprintf('%.3f g', $c1);
    }
    /* If document started output to buffer. */
    if ($this->_page > 0) {
        $this->_out($this->_fill_color);
    }
}
[/code]
Notice that we need to pass varying number of color components depending on the colorspace selected: one for grayscale, or several for full color.

Grayscale colors are represented by a single 3 decimal place number followed by the letter ‘g’. 0 is black, 1 is white, with a whole lot of shades of gray in between.

For example:
’0.000 g’ = black, eg. setFillColor(‘gray’, 0)
’0.200 g’ = 20% gray, eg. setFillColor(‘gray’, 0.2)
’0.800 g’ = 80% gray, eg. setFillColor(‘gray’, 0.8)

{mospagebreak title=RGB Color}
Selecting ‘rgb’ and passing three color components gives us full color, represented by 3 separate 3-decimal-place numbers, followed by the letters ‘rg’.
So, for example:
’0.800 0.400 0.200 rg’ = purple-ish color. eg. setFillColor(‘rgb’, 0.8, 0.4, 0.2)

A second, very similar function sets the drawing color:
[code]
function setDrawColor($cs = 'rgb', $c1, $c2 = 0, $c3 = 0,
                      $c4 = 0)
{   
    $cs = strtolower($cs);
    if ($cs = 'rgb') {
        $this->_draw_color = sprintf('%.3f %.3f %.3f RG',
                                     $c1, $c2, $c3);
    } elseif ($cs = 'cmyk') {
        $this->_draw_color = sprintf('%.3f %.3f %.3f %.3f K',
                                     $c1, $c2, $c3, $c4);
    } else {
        $this->_draw_color = sprintf('%.3f G', $c1);
    }   
    /* If document started output to buffer. */
    if ($this->_page > 0) {
        $this->_out($this->_draw_color);
    }
}
[/code]
Note that the only difference between these two functions is the lowercase/uppercase syntax. ‘g’, ‘rg’, and ‘k’ specify fill color, while ‘G’, ‘RG’, and ‘K’, specify draw color.

That’s it! Calling either of these two functions will set the colors for any subsequent text or drawing output to your PDF document, and preserve that setting between pages.

{mospagebreak title=Line Drawing}
All that is required for drawing a line in PDF is a starting point and an end point. The below function takes an x1/y1 start, and an x2/y2 end point.

The actual PDF syntax to achieve this is:
  • ’x1 y1 m’ – move the current x/y position to x1/y1;
  • ’x2 y2 l’ – continue in a straight line to x2/y2;
  • ’S’ – set a “stroke” on the resulting path.
As in Part 1, we compensate for PDF’s inverted y scale by subtracting the user supplied $y value from the $this->_h value for page height, to obtain the vertical distance from the bottom of the page.
[code]
function line($x1, $y1, $x2, $y2)
{   
    $this->_out(sprintf('%.2f %.2f m %.2f %.2f l S', $x1, $this->_h - $y1, $x2, $this->_h - $y2));
}
[/code]
Rectangles
A rectangle function requires a starting x/y point, a width, and a height. A fifth parameter defines the rectangle style, which can be ‘f’ for filled, ‘d’ for drawn, or ‘fd’ (or ‘df’, the order doesn’t matter) for both filled and drawn.

Similar to the line drawing function, the PDF output for the rectangle involves creating a rectangular path and applying a path-painting operator to it:
• ’x y w h re’ – the rectangular path;
• ’f', ‘S’ or ‘B’ – the path-painting operators.
[code]
function rect($x, $y, $width, $height, $style = '')
{
    $style = strtolower($style);
    if ($style == 'f') {
        $op = 'f';      // Style is fill only.
    } elseif ($style == 'fd' || $style == 'df') {
        $op = 'B';      // Style is fill and stroke.
    } else {
        $op = 'S';      // Style is stroke only.
    }

    $this->_out(sprintf('%.2f %.2f %.2f %.2f re %s', $x, $this->_h - $y, $width, -$height, $op));
}
[/code]
Note that the end result of calling a drawn and filled rectangle (‘fd’) is exactly the same as first creating a filled rectangle (‘f’) and then creating a drawn one (‘d’).

{mospagebreak title=Circles}
Drawing a circle is slightly more complex. There is no native PDF syntax to draw a circle. However, luckily, PDF can draw not only straight lines, but also complex cubic Bezier curves. Using a combination of these we can create our own function to draw a circle.

The following is a fairly good general introduction to Bezier curves in PDF, which you can use to create complex patterns or drawings, not only circles.
[code]
function circle($x, $y, $r, $style = '')
{
    $style = strtolower($style);
    if ($style == 'f') {
        $op = 'f';      // Style is fill only.
    } elseif ($style == 'fd' || $style == 'df') {
        $op = 'B';      // Style is fill and stroke.
    } else {
        $op = 'S';      // Style is stroke only.
    }

    $y = $this->_h - $y;                 // Adjust y value.
    $b = $r * 0.552;                     // Length of the Bezier
                                         // controls.
    /* Move from the given origin and set the current point
     * to the start of the first Bezier curve. */
    $c = sprintf('%.2f %.2f m', $x - $r, $y);
    $x = $x - $r;
    /* First circle quarter. */
    $c .= sprintf(' %.2f %.2f %.2f %.2f %.2f %.2f c',
                  $x, $y + $b,           // First control point.
                  $x + $r - $b, $y + $r, // Second control point.
                  $x + $r, $y + $r);     // Final point.
    /* Set x/y to the final point. */
    $x = $x + $r;
    $y = $y + $r;
    /* Second circle quarter. */
    $c .= sprintf(' %.2f %.2f %.2f %.2f %.2f %.2f c',
                  $x + $b, $y,
                  $x + $r, $y - $r + $b,
                  $x + $r, $y - $r);
    /* Set x/y to the final point. */
    $x = $x + $r;
    $y = $y - $r;
    /* Third circle quarter. */
    $c .= sprintf(' %.2f %.2f %.2f %.2f %.2f %.2f c',
                  $x, $y - $b,
                  $x - $r + $b, $y - $r,
                  $x - $r, $y - $r);
    /* Set x/y to the final point. */
    $x = $x - $r;
    $y = $y - $r;
    /* Fourth circle quarter. */
    $c .= sprintf(' %.2f %.2f %.2f %.2f %.2f %.2f c %s',
                  $x - $b, $y,
                  $x - $r, $y + $r - $b,
                  $x - $r, $y + $r,
                  $op);
    /* Output the whole string. */
    $this->_out($c);
}
[/code]
Note that the process is essentially one of creating four joined arcs, each one starting from where the previous one finished.

PDF is “aware” of the current x/y position after having drawn an object. This means that you can concatenate several draw objects, following each new object on from where the last one left over, without having to explicitly move to the new x/y start position.

Line Width

Since we have introduced lines and drawings, it will be good to have a function to control the line width of our drawings. This function sets our class variable $this->_line_width and outputs it to the buffer, if there is an open document.
[code]
function setLineWidth($width)
{
    $this->_line_width = $width;
    if ($this->_page > 0) {
        $this->_out(sprintf('%.2f w', $width));
    }
}
[/code]

{mospagebreak title=Page Add Modifications}
The only code we still need to add for colors and drawings are some checks in the addPage() function. These make sure that the colors and line widths, set before any page is added, are actually inserted into the buffer, and that they are remembered from page to page.

For the addPage() function, introduced in Part 1, the three if() checks appear below, at the bottom of the function:
[code]
function addPage()
{
    $this->_page++;                    // Increment page count.
    $this->_pages[$this->_page] = '';  // Start the page buffer.
    $this->_state = 2;                 // Set state to page
                                       // opened.
    /* Check if font has been set before this page. */
    if ($this->_font_family) {
        $this->setFont($this->_font_family, $this->_font_style, $this->_font_size);
    }
    /* Check if fill color has been set before this page. */
    if ($this->_fill_color != '0 g') {
        $this->_out($this->_fill_color);
    }   
    /* Check if draw color has been set before this page. */
    if ($this->_draw_color != '0 G') {
        $this->_out($this->_draw_color);
    }
    /* Check if line width has been set before this page. */
    if ($this->_line_width != 1) {
        $this->_out($this->_line_width);
    }
}
[/code]
Images
The last step in this tutorial will be a brief look at images: specifically the inserting of the JPEG image type (since it is the simplest one to explain).
[code]
function image($file, $x, $y, $width = 0, $height = 0)
{
    if (!isset($this->_images[$file])) {
        /* First use of requested image, get the extension. */
        if (($pos = strrpos($file, '.')) === false) {
            die(sprintf('Image file %s has no extension and no type was specified', $file));
        }
        $type = strtolower(substr($file, $pos + 1));

        /* Check the image type and parse. */
        if ($type == 'jpg' || $type == 'jpeg') {
            $info = $this->_parseJPG($file);
        } else {
            die(sprintf('Unsupported image file type: %s', $type));
        }
        /* Set the image object id. */
        $info['i'] = count($this->_images) + 1;
        /* Set image to array. */
        $this->_images[$file] = $info;
    } else {
        $info = $this->_images[$file];          // Known image, retrieve
                                                // from array.
    }

    /* If not specified, do automatic width and height
     * calculations, either setting to original or
     * proportionally scaling to one or the other given
     * dimension. */
    if (empty($width) && empty($height)) {
        $width = $info['w'];
        $height = $info['h'];
    } elseif (empty($width)) {
        $width = $height * $info['w'] / $info['h'];
    } elseif (empty($height)) {
        $height = $width * $info['h'] / $info['w'];
    }

    $this->_out(sprintf('q %.2f 0 0 %.2f %.2f %.2f cm /I%d Do Q', $width, $height, $x, $this->_h - ($y + $height), $info['i']));
}
[/code]

{mospagebreak title=File Checking}
To keep this example simple the file checking is rather crude. We guess the image type according to what its extension is. Ideally we should be using a better way to check the image type, regardless of extension.

The _parseJPG() method does a further check to make sure we are dealing with a JPEG file.

We will need to add a few other functions to handle the inserting of images. First of all there is the _parseJPG() method we saw in the function above:
[code]
function _parseJPG($file)
{   
    /* Extract info from the JPEG file. */
    $img = @getimagesize($file);
    if (!$img) {
        die(sprintf('Missing or incorrect image file: %s', $file));
    }
    /* Check if dealing with an actual JPEG. */
    if ($img[2] != 2) {
        die(sprintf('Not a JPEG file: %s', $file));
    }
    /* Get the image colorspace. */
    if (!isset($img['channels']) || $img['channels'] == 3) {
        $colspace = 'DeviceRGB';
    } elseif ($img['channels'] == 4) {
        $colspace = 'DeviceCMYK';
    } else {
        $colspace = 'DeviceGray';
    }
    $bpc = isset($img['bits']) ? $img['bits'] : 8;

    /* Read the whole file. */
    $f = fopen($file, 'rb');
    $data = fread($f, filesize($file));
    fclose($f);

    return array('w' => $img[0], 'h' => $img[1], 'cs' => $colspace, 'bpc' => $bpc, 'f' => 'DCTDecode', 'data' => $data);
}
[/code]
All that this function does is to check that the file format is JPEG, get the colorspace, and read the file data. It returns an array containing width, height, colorspace, bits, filter (always ‘DCTDecode’ for JPEG), and the actual data.

Our _putResources() function that we set up in Part 1 of this tutorial, now needs to add images as well. This is how the modified function should look. Note the added call to _putImages(), the extra parameters in the ‘ProcSet’ line, and the loop to output the image objects.
[code]
function _putResources()
{
    $this->_putFonts();              // Output any fonts.
    $this->_putImages();             // Output any images.

    /* Resources are always object number 2. */
    $this->_offsets[2] = strlen($this->_buffer);
    $this->_out('2 0 obj');
    $this->_out('<</ProcSet [/PDF /Text /ImageB /ImageC /ImageI]');
    $this->_out('/Font <<');
    foreach ($this->_fonts as $font) {
        $this->_out('/F' . $font['i'] . ' ' . $font['n'] . ' 0 R');
    }
    $this->_out('>>');
    if (count($this->_images)) {     // Loop through any images
        $this->_out('/XObject <<');  // and output the objects.
        foreach ($this->_images as $image) {
            $this->_out('/I' . $image['i'] . ' ' . $image['n'] . ' 0 R');
        }
        $this->_out('>>');
    }
    $this->_out('>>');
    $this->_out('endobj');
}
[/code]
The above function calls the _putImages() to output the actual image data:

[code] 
function _putImages()
{
    /* Output any images. */
    $filter = ($this->_compress) ? '/Filter /FlateDe ' : '';
    foreach ($this->_images as $file => $info) {
        $this->_newobj();
        $this->_images[$file]['n'] = $this->_n;
        $this->_out('<</Type /XObject');
        $this->_out('/Subtype /Image');
        $this->_out('/Width ' . $info['w']);    // Image width.
        $this->_out('/Height ' . $info['h']);   // Image height.
        $this->_out('/ColorSpace /' . $info['cs']); //Colorspace
        if ($info['cs'] == 'DeviceCMYK') {
            $this->_out('/De [1 0 1 0 1 0 1 0]');
        }
        $this->_out('/BitsPerComponent ' . $info['bpc']); // Bits
        $this->_out('/Filter /' . $info['f']);  // Filter used.
        $this->_out('/Length ' . strlen($info['data']) . '>>');
        $this->_putStream($info['data']);       // Image data.
        $this->_out('endobj');
    }
}
[/code]
The Script
You can download the entire class for use with Part 2 of this tutorial.

{mospagebreak title=Example Use}
The new example script below includes the added functions. It sets different colors, draws some lines, a rectangle and a circle, and uploads an image. Note how we can use line drawing to emulate underline, which is not natively available in the PDF syntax.
[code]
<?php
require 'PDF.php';                    // Require the class.
$pdf = &PDF::factory('p', 'a4');      // Set up the pdf object.
$pdf->open();                         // Start the document.
$pdf->setCompression(true);           // Activate compression.
$pdf->addPage();                      // Start a page.
$pdf->setFont('Courier', '', 8);      // Set font to courier 8 pt.
$pdf->text(100, 100, 'First page');   // Text at x=100 and y=100.
$pdf->setFontSize(20);                // Set font size to 20 pt.
$pdf->setFillColor('rgb', 1, 0, 0);   // Set text color to red.
$pdf->text(100, 200, 'HELLO WORLD!'); // Text at x=100 and y=200.

$pdf->setDrawColor('rgb', 0, 0, 1);   // Set draw color to blue.
$pdf->line(100, 202, 240, 202);       // Draw a line.
$pdf->setFillColor('rgb', 1, 1, 0);   // Set fill/text to yellow.
$pdf->rect(200, 300, 100, 100, 'fd'); // Draw a filled rectangle.
$pdf->addPage();                      // Add a new page.

$pdf->setFont('Arial', 'BI', 12);     // Set font to arial bold
                                      // italic 12 pt.
$pdf->text(100, 100, 'Second page');  // Text at x=100 and y=100.
$pdf->image('sample.jpg', 50, 200);   // Image at x=50 and y=200.
$pdf->setLineWidth(4);                // Set line width to 4 pt.
$pdf->circle(200, 300, 150, 'd');     // Draw a non-filled
                                      // circle.
$pdf->output('foo.pdf');              // Output the file named foo.pdf
? > [/code]


 

[gp-comments width="770" linklove="off" ]
antalya escort bayan antalya escort bayan