Skip to content

Paths, Vector Graphics and PHP images in FPDF

Looking at FPDF and at my PDF tutorial, it is clear that there are a few things that PDF's can do that aren't part of the API of FPDF. However, FPDF is easily extensible to include everything I might find useful, so I put together a package of those routines.

See the code.

See the sample output.

See the code that produced the sample output.

Path Drawing

Create a "path" by moving a virtual stylus around the page, then draw it or fill it in. The main advantage of using the path drawing routines rather than $pdf->Line is that PDF creates nice line joins at the angles, rather than just overlaying the lines: Triange formed with path commands as opposed to Triangle formed from individual lines.

MoveTo ($x, $y)
Moves the stylus to ($x, $y) (in user units) without drawing the path from the previous point. $x and $y must be numbers. Paths must start with a MoveTo to set the original stylus location or the result is undefined.
LineTo ($x, $y)
Draw a line from the current stylus location to ($x, $y), which becomes the new stylus location. Note that this only creates the line in the path; it does not actually draw the line on the page.
CurveTo ($x1, $y1, $x2, $y2, $x3, $y3)
Draw a cubic Bézier curve from the current location to ($x3, $y3), using ($x1, $y1) and ($x2, $y2) as control points. See Mike "Pomax" Kamermans's site for more information on drawing with Bézier curves.
ClosePath()
Draw a line from the current location to the last MoveTo point (if not the same) and mark the path as closed so the first and last lines join nicely.
DrawPath($style='D')
Actually draw the path on the page. $style can be:
  • 'D' (or an empty string) to draw the path with the current drawing color.
  • 'F' to fill the path with the current fill color.
  • 'DF' or 'FD' to do both.

Drawing

Image($file, $x=null, $y=null, $w=0, $h=0, $type='', $link='')
Enhanced Image to allow the use of PHP GD images directly, including the alpha channel (using the code from Valentin Schmidt). Uses temporary PNG files, so requires write-access to the current directory.
For example,
$im = imagecreate(110, 20);
$background_color = imagecolorallocate($im, 0, 0, 0);
$text_color = imagecolorallocate($im, 233, 14, 91);
imagestring($im, 1, 5, 5,  "A Simple Text String", $text_color);
$pdf->Image($im);
will insert the image Image of text onto the page.
Also enhanced to allow the use of vector-based images; see OpenImage and CloseImage below.
Text($x, $y, $txt, $theta=0)
Enhanced Text to allow rotation of the baseline by $theta radians (counter clockwise).
Rect($x, $y, $w, $h, $style='', $theta=0)
Enhanced Rect to allow rotation of the rectangle by $theta radians (counter clockwise) around the upper left corner.
PlaceImage($file, $x, $y, $w=0, $h=0, $theta=0, $type='')
Similar to Image but puts the image with ($x, $y) at the center rather than the upper left of the image, and rotates the image by $theta radians (counter clockwise) around the center. Unlike Image, does not allow $x=NULL to place the image in the text flow, and does not create links.
Arc($x, $y, $style='', $a, $b=NULL, $theta=0, $lambda1=0, $lambda2=0)
Draw an arc or wedge of an ellipse. ($x, $y) is the center of the ellipse, $a is the semimajor axis (half the width) and $b is the semiminor axis (half the height). If $b===NULL, then $b is set to $a (a circle with radius $a). The ellipse is rotated $theta radians counter clockwise from the horizontal. The arc goes from $lambda1 radians from the major axis (not the horizontal) to $lambda2 radians; if they are equal then the whole ellipse is drawn. $style is the same as for Rect: 'D' to draw the arc, 'F' to fill in the wedge, 'DF' or 'FD' to do both.
The calculations to estimate circles with Bézier curves comes from L. Maisonobe.
Polygon($x, $y, $n, $style='', $a, $b=NULL, $skip=1, $theta=0, $lambda=0)
Draw a regular polygon with $n sides, inscribed in the ellipse that would be drawn with Arc($x, $y, '', $a, $b, $theta). The first vertex is placed at $lambda to the major axis. $skip determines the shape of the polygon: if $skip==1, draw a regular, convex polygon. If $skip>1, draw a star by drawing the lines from each vertex i to i+$skip. The higher $skip is, the tighter the star (up to $n/2).
So $pdf->Polygon(1, 1, 5, 'D', 10, 10, 1) draws a pentagon with a radius of 10, and $pdf->Polygon(1, 1, 5, 'D', 10, 10, 2) draws a five-pointed star with a radius of 10.
Cloverleaf($x, $y, $n, $style='', $a, $b=NULL, $skip=1, $theta=0, $lambda=0)
Same as Polygon, but draws curves with loops at the vertices.

Transparent Drawing

In addition to allowing alpha-channel transparency in images with the enhanced Image, you can use transparent colors for drawing, filling and text. The code is based on that of Martin Hall-May.

SetDrawColor($r, $g=null, $b=null, $alpha=0)
Enhanced SetDrawColor to allow alpha channel transparency. $alpha==0 is opaque and $alpha==127 is fully transparent. Note that this is backward from CSS opacity, which uses 0 for transparent and 1 for opaque.
SetFillColor($r, $g=null, $b=null, $alpha=0)
Enhanced SetFillColor to allow alpha channel transparency. $alpha==0 is opaque and $alpha==127 is fully transparent.
SetTextColor($r, $g=null, $b=null, $alpha=0)
Enhanced SetTextColor to allow alpha channel transparency. $alpha==0 is opaque and $alpha==127 is fully transparent.

Vector-based images

In addition to bitmapped images like JPEG or PNG images (or GD images), PDF supports vector graphics similar to SVG (a class to allow using SVG directly would be useful; I may get the time). Create a new image with OpenImage, then any of the drawing commands will draw into the image being created rather than onto the page. Use Image or PlaceImage to put the image onto the page.

For example,

$pdf = new GraphicsPDF('P', 'in', 'letter'); // use inches for user units
$pdf->OpenImage('star', 2, 2); // create a 2 x 2 image
$pdf->SetFillColor (0, 0, 255);
$pdf->Rect(0, 0, 2, 2, 'F'); // fill it with blue
$pdf->SetFillColor (255);
$pdf->Polygon(1, 1, 5, 'F', 1, 1, 2, 0, deg2rad(90)); // put a white star in the center
$pdf->CloseImage();
$pdf->AddPage();
$pdf->Image('star', 1, 1); // put the image in various sizes
$pdf->Image('star', 3, 1, 0.25);
$pdf->Image('star', 5, 1, 3); 
$pdf->Image('star', 5, 7, 4);
$pdf->Output();

produces this.

OpenImage($name, $w, $h)
Start creating a vector image named $name with width $w and height $h. All drawing code will go into the image, not the page (however, state-changing calls like SetDrawColor and SetLineWidth will carry over to the page).
CloseImage()
Stop recording the current image and return to drawing directly on the page.

Curve Calculations

Arc, Polygon and Cloverleaf involve a fair amount of heavy math, so I exposed methods to return simply the boundary points for the shapes rather than drawing them, to be used for building paths and the like.

Each of these returns an array of arrays. Each subarray has 4, 8 or 0 elements. 0 elements indicates that one shape has ended and another needs to start; think of a six-pointed star consisting of two triangles. 4 elements indicates a line: [x0, y0, x1, y1] is a line from (x0, y0) to (x1, y1). 8 elements indicates a curve: [x0, y0, x1, y1, x2, y2, x3, y3] is a curve from (x0, y0) to (x3, y3) with control points (x1, y1) and (x2, y2).

EllipseCurves($x, $y, $a, $b=NULL, $theta=0, $lambda1=0, $lambda2=0)
Generate the curves for the ellipse that would be drawn by Arc($x, $y, 'D', $a, $b, $theta, $lambda1, $lambda2).
PolygonLines($x, $y, $n, $a, $b=NULL, $skip=1, $theta=0, $lambda=0)
Generate the lines that would be drawn by Polygon($x, $y, 'D', $n, $a, $b, $skip, $theta, $lambda).
CloverleafCurves($x, $y, $n, $a, $b=NULL, $skip=1, $theta=0, $lambda=0)
Generate the curves that would be drawn by Cloverleaf($x, $y, 'D', $n, $a, $b, $skip, $theta, $lambda).

Hope this is useful to someone.

{ 14 } Comments

  1. RL | July 18, 2012 at 2:45 pm | Permalink

    Thanks, i Was looking for a way to draw a filled triangle, which isn’t native to FPDF

  2. Danny | July 18, 2012 at 3:22 pm | Permalink

    @RL:
    I’m glad this turned out to be useful to someone.
    –Danny

  3. Tony | February 7, 2013 at 4:34 pm | Permalink

    Thank you, I needed a watermark with transparency and your library did the trick like a charm.

  4. Danny | February 7, 2013 at 5:03 pm | Permalink

    @Tony:
    Nice to know I could help someone!
    –Danny

  5. Edwin Mitchell-Finch | June 26, 2013 at 7:50 am | Permalink

    I’ve been trying to replicate the transparent image overlays from your example – map, dice, gecko and can’t get the transparency to work:

    http://mybrain.co.uk/mind/algorithm/v00002/test4.php

    I used your code, and as you can see, I even used your images, but still no go…
    Any ideas where the problem could be?

    Ed

  6. Danny | June 26, 2013 at 8:31 am | Permalink

    @Edwin:
    The code creates a temporary alpha-channel (transparency information) image file (see the method _parseOneImage) using $file = tempnam('.','img'); imagepng($im, $file); .

    If tempnam is creating a file that you don’t have permission to write, the transparency will fail. Remove the @unlink($file); line from _parseOneImage and see if you are creating a bunch of temporary files in your current directory.

    That’s the only thing I can think of.
    Hope it helps,
    Danny

  7. Edwin Mitchell-Finch | June 26, 2013 at 8:57 am | Permalink

    Thanks for the quick response – yes it is creating 4 temporary files when I run the script, but they are just files, they have no file extensions – is that correct?

  8. Danny | June 26, 2013 at 9:32 am | Permalink

    @Edwin:
    Yes, the files have no extension (that’s just how tempnam works).
    Are they valid image files? Open them as png’s (you may need to rename them with the .png extension). Each image with an alpha channel should have two images, an original with the black background and a grayscale mask for the transparency.
    Beyond that, I’m not sure. The rest of the sample code works?
    –Danny

  9. Edwin Mitchell-Finch | June 26, 2013 at 9:49 am | Permalink

    The files are exactly as you described. Could it be a version thing?
    What version PHP is your server running for instance?

  10. Danny | June 26, 2013 at 11:00 am | Permalink

    @Edwin:
    I’m using PHP 5.3, with tPDF 1.03 (dated 2010-07-25).
    The alpha channel code is based on http://www.fpdf.org/en/script/script83.php
    Can you get that to work?

  11. Edwin Mitchell-Finch | June 27, 2013 at 2:31 am | Permalink

    Sent you an email but got a mail delivery error on your address.

    Thanks for your help – it worked with tFPDF version 1.03 (based on FPDF 1.6) but it doesn’t work with tFPDF version 1.24 (based on FPDF 1.7).

    One last issue is that the graphics are all enlarged by around 30% is there a way of ensuring they appear at their original size?

  12. Edwin Mitchell-Finch | June 27, 2013 at 4:09 am | Permalink

    The GraphicsPDF you kindly sent me didn’t have any text to disply, when I add a font using the usual $pdf->SetFont(‘Helvetica’, ”, 12); I get an error message:
    tFPDF error: Could not include font metric file.

    Could this be a mismatch between your version of tFPDF and the version of ttfonts.php I am using?

    Would you mind sending me your complete tFPDF package including the fonts directory?

    Thanks

  13. Danny | June 27, 2013 at 9:18 am | Permalink

    @Edwin:
    I’m glad we could figure most of this out, but with the ongoing issues you are having, you may want to look at tcpdf, which I discuss in this post. It handles transparency in PNGs and does fonts (for me at least) better than tFDF.
    I developed these routines more as a way to learn what makes PDF’s tick; they are not being actively maintained. tcpdf is. It is what I use in production.

  14. Rob Toutant | November 25, 2020 at 11:03 pm | Permalink

    Very nice code …
    I am not sure if you will be able to help – but i just discovered your code and thought it might be useful in creating text from opentype.js …

    i got the path from opentype.js as follows

    {“commands”:[{“type”:”M”,”x”:21.12,”y”:-5.5680000000000005},{“type”:”C”,”x1″:19.456,”y1″:-4.5440000000000005,”x2″:18.112000000000002,”y2″:-4.096,”x”:16.64,”y”:-4.096},{“type”:”C”,”x1″:13.696,”y1″:-4.096,”x2″:12.608,”y2″:-5.696,”x”:12.608,”y”:-9.088000000000001},{“type”:”L”,”x”:12.608,”y”:-29.248},{“type”:”L”,”x”:20.416,”y”:-29.248},{“type”:”L”,”x”:21.12,”y”:-33.728},{“type”:”L”,”x”:12.608,”y”:-33.728},{“type”:”L”,”x”:12.608,”y”:-42.048},{“type”:”L”,”x”:6.72,”y”:-41.344},{“type”:”L”,”x”:6.72,”y”:-33.728},{“type”:”L”,”x”:1.344,”y”:-33.728},{“type”:”L”,”x”:1.344,”y”:-29.248},{“type”:”L”,”x”:6.72,”y”:-29.248},{“type”:”L”,”x”:6.72,”y”:-9.024000000000001},{“type”:”C”,”x1″:6.72,”y1″:-3.2,”x2″:9.536,”y2″:0.768,”x”:15.808,”y”:0.768},{“type”:”C”,”x1″:18.624,”y1″:0.768,”x2″:21.312,”y2″:0,”x”:23.36,”y”:-1.536},{“type”:”Z”},{“type”:”M”,”x”:55.168,”y”:-14.848},{“type”:”C”,”x1″:55.232,”y1″:-15.616,”x2″:55.296,”y2″:-16.704,”x”:55.296,”y”:-17.856},{“type”:”C”,”x1″:55.296,”y1″:-28.16,”x2″:50.495999999999995,”y2″:-34.496,”x”:41.664,”y”:-34.496},{“type”:”C”,”x1″:32.704,”y1″:-34.496,”x2″:27.392,”y2″:-27.008,”x”:27.392,”y”:-16.832},{“type”:”C”,”x1″:27.392,”y1″:-6.336,”x2″:32.576,”y2″:0.768,”x”:42.623999999999995,”y”:0.768},{“type”:”C”,”x1″:46.976,”y1″:0.768,”x2″:50.751999999999995,”y2″:-0.768,”x”:53.888000000000005,”y”:-3.2640000000000002},{“type”:”L”,”x”:51.2,”y”:-7.168},{“type”:”C”,”x1″:48.32,”y1″:-4.8,”x2″:45.824,”y2″:-4.032,”x”:42.751999999999995,”y”:-4.032},{“type”:”C”,”x1″:37.632,”y1″:-4.032,”x2″:34.176,”y2″:-7.36,”x”:33.664,”y”:-14.848},{“type”:”Z”},{“type”:”M”,”x”:41.664,”y”:-29.824},{“type”:”C”,”x1″:46.912,”y1″:-29.824,”x2″:49.344,”y2″:-26.176000000000002,”x”:49.472,”y”:-19.2},{“type”:”L”,”x”:33.664,”y”:-19.2},{“type”:”C”,”x1″:34.112,”y1″:-26.432000000000002,”x2″:37.056,”y2″:-29.824,”x”:41.664,”y”:-29.824},{“type”:”Z”},{“type”:”M”,”x”:72.96000000000001,”y”:-3.968},{“type”:”C”,”x1″:69.376,”y1″:-3.968,”x2″:66.24000000000001,”y2″:-5.184,”x”:63.616,”y”:-7.36},{“type”:”L”,”x”:60.352000000000004,”y”:-3.648},{“type”:”C”,”x1″:63.29600000000001,”y1″:-1.088,”x2″:67.392,”y2″:0.768,”x”:73.08800000000001,”y”:0.768},{“type”:”C”,”x1″:79.808,”y1″:0.768,”x2″:86.4,”y2″:-2.3040000000000003,”x”:86.4,”y”:-9.536},{“type”:”C”,”x1″:86.4,”y1″:-16.064,”x2″:81.664,”y2″:-18.240000000000002,”x”:75.712,”y”:-19.904},{“type”:”C”,”x1″:70.016,”y1″:-21.44,”x2″:67.904,”y2″:-22.528,”x”:67.904,”y”:-25.408},{“type”:”C”,”x1″:67.904,”y1″:-28.096,”x2″:70.528,”y2″:-29.824,”x”:74.176,”y”:-29.824},{“type”:”C”,”x1″:77.76,”y1″:-29.824,”x2″:80.256,”y2″:-28.8,”x”:82.688,”y”:-27.136},{“type”:”L”,”x”:85.31200000000001,”y”:-31.104},{“type”:”C”,”x1″:82.56,”y1″:-33.088,”x2″:79.04,”y2″:-34.496,”x”:74.432,”y”:-34.496},{“type”:”C”,”x1″:67.328,”y1″:-34.496,”x2″:61.824000000000005,”y2″:-30.72,”x”:61.824000000000005,”y”:-25.024},{“type”:”C”,”x1″:61.824000000000005,”y1″:-19.52,”x2″:65.72800000000001,”y2″:-16.896,”x”:71.808,”y”:-15.36},{“type”:”C”,”x1″:78.464,”y1″:-13.696,”x2″:80.06400000000001,”y2″:-12.416,”x”:80.06400000000001,”y”:-9.088000000000001},{“type”:”C”,”x1″:80.06400000000001,”y1″:-5.952,”x2″:77.248,”y2″:-3.968,”x”:72.96000000000001,”y”:-3.968},{“type”:”Z”},{“type”:”M”,”x”:109.76,”y”:-5.5680000000000005},{“type”:”C”,”x1″:108.096,”y1″:-4.5440000000000005,”x2″:106.75200000000001,”y2″:-4.096,”x”:105.28,”y”:-4.096},{“type”:”C”,”x1″:102.336,”y1″:-4.096,”x2″:101.248,”y2″:-5.696,”x”:101.248,”y”:-9.088000000000001},{“type”:”L”,”x”:101.248,”y”:-29.248},{“type”:”L”,”x”:109.056,”y”:-29.248},{“type”:”L”,”x”:109.76,”y”:-33.728},{“type”:”L”,”x”:101.248,”y”:-33.728},{“type”:”L”,”x”:101.248,”y”:-42.048},{“type”:”L”,”x”:95.36,”y”:-41.344},{“type”:”L”,”x”:95.36,”y”:-33.728},{“type”:”L”,”x”:89.984,”y”:-33.728},{“type”:”L”,”x”:89.984,”y”:-29.248},{“type”:”L”,”x”:95.36,”y”:-29.248},{“type”:”L”,”x”:95.36,”y”:-9.024000000000001},{“type”:”C”,”x1″:95.36,”y1″:-3.2,”x2″:98.176,”y2″:0.768,”x”:104.44800000000001,”y”:0.768},{“type”:”C”,”x1″:107.264,”y1″:0.768,”x2″:109.952,”y2″:0,”x”:112,”y”:-1.536},{“type”:”Z”}],”fill”:”black”,”stroke”:null,”strokeWidth”:1}

    implemented a simple addition to your code for testing

    $pdf->SetDrawColor (120, 120, 255);
    $pdf->SetFillColor (175, 238, 238);
    $pdf->SetLineWidth(0.01);

    for($i=0;$icommands);$i++){

    $o = $path->commands[$i];

    // error_log(json_encode($o).”\n”);

    if($o->type==’M’){

    $pdf->MoveTo( floatval( $o->x ), floatval( $o->y ));
    }
    else if($o->type==’C’){

    $pdf->CurveTo(
    floatval( $o->x ),floatval( $o->y ) ,
    floatval( $o->x1 ), floatval( $o->y1 ),
    floatval( $o->x2 ), floatval( $o->y2 ) );

    }
    else if($o->type==’L’){

    $pdf->LineTo(floatval( $o->x ), floatval( $o->y ));
    }
    else if($o->type==’Z’){
    $pdf->ClosePath();

    }
    else{
    error_log(“unknown type “.$o->type);
    }

    }

    $pdf->DrawPath(“FD”);

    but nothing appears …
    any help would be greatly appreciated

Post a Comment

Your email is never published nor shared. Required fields are marked *