NAME

Using PDF::API2

VERSION

0.10 - 2004-08-20 - Original version posted at http://www.printaform.com.au/clients/pdfapi2/
0.20 - 2007-02-14 - Code fixed, documentation updated, stylesheet changed. Downloads fixed.

TUTORIAL

A Tutorial by Example

PDF is a powerful document distribution mechanism and it's mastery is a great addition to your tool belt. However the language is complex and you can still drive your car even if you don't understand the complexities of internal combustion engines.

This document is designed to teach you to drive PDF::API2, the most comprehensive toolkit for creating PDF files from perl. It wont teach you PDF. If you want to learn PDF, Adobe have the full specification available for download on their website.

In order to drive PDF::API2 I'll take you through the steps. This document only covers the basic steps required to create a good looking PDF. There are many other functions that can be included and many other options.

This document was originally written against version 0.03.77 of PDF::API2. If you have an older version, I recommend that you upgrade. It may still work, but I'm not guaranteeing it. Since the release of 0.04, there have not been any significant changes that break anything in this tutorial.

Who am I?

I'm a perl hacker just like you. However I've worked for over 12 years in the design industry and am consulted on all technical issues related to pre-press. I troubleshoot problems related to data and software, through to color management, RIP and system management. When I first wrote this document I was working on a project that uses PDF::API2 to allow users to order stationery online and see accurate onscreen proofs. Since then I've moved on and am now using PDF::API2 to create complex reports for a mail filtering company.

How you can use PDF

PDF can embed all it's data into a single file, from colors and text to the font and picture files you use. It all gets wrapped up into a single file. PDF can be used for many reasons from distributing on-screen software documentation to delivering high-resolution files to your printing company. Because of this, PDF can also embed color profiles for highly accurate high-end output.

As PDF has so many uses the features are fairly complex, however you can start slowly. This tutorial makes use of a function I have written to take the pain out of creating text blocks. Maybe one day I'll get around to writing the promised tutorial takes that routine apart. However, like PDF itself, you do not need to understand what is going on to use it. Feel free to download and use the text_block subroutine and use it where you want under the LGPL licence.

Some important PDF concepts

Media, bleed, crop and art boxes

diagram showing the media, bleed, crop and art boxes Often when you look at a printed document you'll notice that the image (or a slab of color) goes right to the edge. I'll let you in on a big secret: it goes over the edge! Different printing companies will have different distances that the image needs to go over the edge, but it always does. That's called a bleed.

The next thing to know is that we dont print on the final paper size. If we did that then the bleed would be printing on rollers inside the press, and that's not a good thing. So when we print a letterhead on A4 (or letter) paper, then we really print it on oversize A4, then the bleed still fits on the paper. The paper is called the media (because you can print on plastic, or t-shirts or anything else!)

After we finish printing we cut the printed job down to the size you require. That's called the crop size. No information should be placed outside this area except that required by the printer. In the example to the right you'll notice that there are black looking lines that extend the crop box. They show the guillotiner where to cut the job down to the size you require.

The only thing left now is the art size. That's the size that the important parts of your artwork fit into. Normally the art size is the same distance inside your crop size as your bleed is outside the crop box. It's like that because while printing these days is fairly accurate, it's not perfect. If you have important text going all the way to the edge of your document and the press is out by as much as a millimeter then you'll lose some information.

Core, postscript, truetype and constructed fonts

As I said earlier, PDF embeds all your data into a single file, including the fonts. There are many font formats around, but the only ones we need (or can use) with PDF::API2 are Core, PostScript and TrueType fonts. The definition of these is well beyond the scope of this document except to say that we're only using core fonts for the moment because we don't need any external files.

Measurement systems and origins

PDF uses 'points' as its default measurement unit, however the example code shows how to use units that you're more comfortable with. (I use millimeters in the example).

The origin point is the lower left corner -- just like cartesian coordinates. Normally we think of the upper left as being (0,0) and head toward the bottom right, but when working with PDF you need to get used to the lower left origin. (It would be quite easy to write a function to convert your measurements by feeding it your page size, but that's left as an excercise for the reader)

Installing the software

Installing PDF::API2 is beyond the scope of this document, however like all perl modules on CPAN, you can use the following command from the prompt on any linux (or other unix variant) system.

sudo perl -MCPAN -e "install PDF::API2"

If you have any errors, ask your sysadmin or your local guru. If you can't do any of those try the friendly folk at PerlMonks.

Creating a document

An example

In this tutorial, we'll construct the file shown above (you can provide your own picture there, the one I've used is the current Australian Prime Minister). However, because we'll define a crop box, when we look at our document in Acrobat, kpdf, xpdf or any of the other thousands of PDF viewers, it will look more like this:

The document this script will produce

You can download the source code here: pdf-api2.txt (10kb) and a copy of the final PDF here: pdf-api2.pdf (59kb)

Load and import the module

For the most part, you'll only ever have to use this one module. It will, as needed, call all the other modules that form part of the distribution. (Of course, we use strict and warnings because we're all well behaved progammers .. right?)

   1: #!/usr/bin/perl
   2:
   3: use strict;
   4: use warnings;
   5:
   6: use PDF::API2;

Define a few constants

These make measurements easier when using PDF::API2. PDF primarily uses points to measure sizes and distances, so if we define these we can use them later to use other units. For example 5/mm returns 5 millimeters in points. The points (pt) is given just so we can clearly state when we're talking in points.

Note that these are not necessary, but make it easier to create PDF files in perl. For the technically minded: There are 72 postscript points in an inch and there are 25.4 millimeters in an inch.

   8: use constant mm => 25.4 / 72;
   9: use constant in => 1 / 72;
  10: use constant pt => 1;
  12: my ( $paragraph1, $paragraph2, $picture ) = get_data();

Define the PDF

This is the first real part of creating our PDF. All that happens here is that the PDF is defined. In this example we tell it where we'd like to save it - but that isn't necessary and can be done at the end.

  14: my $pdf = PDF::API2->new( -file => "$0.pdf" );

Create a page

Each time you call $pdf->page($page) you will get a new page. The ->page method works as a PDF::API2::Page constructor, however it attaches the page to the end of the current PDF. If you specify a page number, the page is inserted after that page otherwise the page is appended to the end of the document.

  16: my $page = $pdf->page;

Set some options

In PDF, each page has its own size definition. We learned above that there are four parts that describe the size of the page. In our example we'll only worry about the mediabox (the size of our paper) and the cropbox (the size we'll cut the paper down to at the end). Unless otherwise defined, each box inherits it's parent container's dimensions, thus the only required box is the mediabox.

The mediabox takes just a width and a height. The bleedbox, cropbox and artbox all take left, bottom, right and top (in that order).

  17: $page->mediabox (105/mm, 148/mm);
  18: #$page->bleedbox(  5/mm,   5/mm,  100/mm,   143/mm);
  19: $page->cropbox  (7.5/mm, 7.5/mm, 97.5/mm, 140.5/mm);
  20: #$page->artbox  ( 10/mm,  10/mm,   95/mm,   138/mm);

Define some fonts

Fonts are added to the PDF itself and every page can then use them. Defining them here at the top, rather than each time we need them means that we don't end up with duplicates of the same font embedded into the PDF. Like the pages these construct actual font objects on which we can call various functions.

In our example, we'll only use Core Fonts. These are fonts that Adobe have licenced for developers to include in their files for free. PDF::API2 also includes some extra core fonts that Microsoft have licenced similarly.

How you store the objects is up to you. I prefer to use a nested hash as I find it easier to find the font later and I feel that they're all stored close together. There'd be nothing stopping you from storing Helvetica Bold in $foo or $helveticabold.

You'll note that some of these lines are commented out so that they're not defined. Thats because every font you define will be embedded into the PDF, even if you don't use it.

  22: my %font = (
  23:     Helvetica => {
  24:         Bold   => $pdf->corefont( 'Helvetica-Bold',    -encoding => 'latin1' ),
  25:         Roman  => $pdf->corefont( 'Helvetica',         -encoding => 'latin1' ),
  26:         Italic => $pdf->corefont( 'Helvetica-Oblique', -encoding => 'latin1' ),
  27:     },
  28:     Times => {
  29:         Bold   => $pdf->corefont( 'Times-Bold',   -encoding => 'latin1' ),
  30:         Roman  => $pdf->corefont( 'Times',        -encoding => 'latin1' ),
  31:         Italic => $pdf->corefont( 'Times-Italic', -encoding => 'latin1' ),
  32:     },
  33: );

Doing something useful

Until now nothing happens. If you save and close the PDF (with $pdf->save; $pdf->close;) you'll get a blank PDF in your viewer.

Now we'll do something useful. PDFs are constructed bottom-up. What we add later gets put on top of what was already there. Looking at the example PDF, there's a two major areas: The title area and the text area. It doesn't matter which we start with because nothing crosses between them. Lets start then at the top of the document. We'll define the blue rectangle first as it sits behind everything else.

Adding content

Drawing a blue rectangle

To do this we need it to grab a graphics handler from our page. We could just call it $gfx and place all graphic elements in the one handler, but that makes the PDF harder to edit (with third-party tools) later on. If you have no need to edit the file later on, feel free to just use a single handler. In this example, we'll use a different handler for each object. NOTE: It is considered best-practice to only use one object and use the save() and restore() methods on it. Maybe I'll do that in the next version

  35: my $blue_box = $page->gfx;

Next we'll set the fill color of whatever we're going to draw. Once again, this is just a definition and the color will not be applied unless we want to. Like the fonts, if you define a color then don't use it, it will still be embedded into the PDF.

There are several color schemes available in PDF::API2, in this case we're just using the business color names. A later tutorial might look at colors and at colorspaces used in high-end output.

  36: $blue_box->fillcolor('darkblue');

Now we draw the rectangle. The parameters are left, bottom, width and height in relation to the media (not the cropbox). So here we're drawing a rectangle that is 5mm off the left edge and 125mm from the bottom. The rectangle is 95mm wide and 18mm high.

  37: $blue_box->rect( 5/mm, 125/mm, 95/mm, 18/mm );

As I said before, the color is defined, but not used until we apply it. So here we tell the rectangle we've drawn to fill with the current fillcolor. After doing this we could change our fillcolor without it affecting the rectangle as we've already applied the color.

  38: $blue_box->fill;

Drawing a red line

The next thing we'll draw is a red line. Did you ever program in logo as a kid? This works similarly.

First we grab another graphics object from the page. (As I said previously, we could use the same one all the way through and it would have no effect on the output, only on editing it later.)

  40: my $red_line = $page->gfx;

This time we need to set a strokecolor rather than a fillcolor. Same deal applies: This doesn't apply the color, only defines it.

  41: $red_line->strokecolor('red');

Just like in logo, we can have our pen up or our pen down. The ->move( $x, $y ) function takes the pen off the page and moves to the specified coordinates. Remember that the origin in PDF is the lower left rather than the upper left.

  42: $red_line->move( 5/mm, 125/mm );

Now we put our pen down and draw a line to the next coordinate.

  43: $red_line->line( 100/mm, 125/mm );

And finally we apply the stroke color we defined before.

  44: $red_line->stroke;

A useful document

At this point we could save and close our PDF and view the result. You'll see a blue rectangle reaching from one side to the other side of the document and a red line along it's bottom edge.

You may be wondering why it reaches from edge to edge when we told it to start at 5mm? Remember the crop box? It's cropped the view of the PDF to show us just the final output area. If your software has the option, tell it to view the mediabox. If not, comment out the cropbox in line 37 and run your script again. You'll see that the box does start 5mm from the edges of the mediabox!

Adding some text

We defined some fonts before, so lets use them. Our title area contains the words 'USING PDF::API2' on the right hand side. In fact the right side of the text meets our artbox so we can align it to that.

First we grab a text a text handler:

  46: my $headline_text = $page->text;

... and apply a font to it. The first parameter of the ->font() method is the font object we created in the hash at the start. The second parameter is the size of the font -- in this case 18pt.

  47: $headline_text->font( $font{'Helvetica'}{'Bold'}, 18/pt );

By now you'll understand this line:

  48: $headline_text->fillcolor('white');

To place text, we need to move the cursor to the point at which we want to place it. In this case, it's at the right side of the artbox (95mm) and 131mm from the bottom of the mediabox. The function we need is ->translate()

  49: $headline_text->translate( 95/mm, 131/mm );

There are several methods for putting text onto a PDF and most are aimed at placing a single line of text. For creating paragraphs or blocks of text, there are several others. For now, we just want to place one line of right-justified text:

  50: $headline_text->text_right('USING PDF::API2');

Adding circles

To make our background a bit more interesting than a plain white sheet of paper, we'll add some overlapping circles. It was a bit of a design fad when I wrote this document, so we'll hop on board!

As usual, we grab a graphics handler from the page:

  52: my $background = $page->gfx;

... set our stroke color

  53: $background->strokecolor('lightgrey');

... then draw the circles

The ->circle() function takes three parameters: The first two are the X and Y coordinates of the centre (in relation to the bottom left), the third is the radius.

  54: $background->circle( 20/mm, 45/mm, 45/mm );
  55: $background->circle( 18/mm, 48/mm, 43/mm );
  56: $background->circle( 19/mm, 40/mm, 46/mm );

Finally, we apply our strokecolor.

  57: $background->stroke;

Adding body text

These are the usual parts, if you don't understand them go back and read the first part of the tutorial on adding content:

  59: my $left_column_text = $page->text;
  60: $left_column_text->font( $font{'Times'}{'Roman'}, 6/pt );
  61: $left_column_text->fillcolor('black');

PDF::API2 contains several functions for adding lines of text, and also contains a paragraph function for adding paragraphs of text. We want to place multiple paragraphs so we need to use a function I wrote: text_block. If you're interested in the workings of that module, it comes with it's own tutorial.

The basic syntax of the function is:

($width_of_last_line, $ypos_of_last_line, $left_over_text) = text_block(

    $text_handler_from_page,
    $text_to_place,
    -x        => $left_edge_of_block,

    -y        => $baseline_of_first_line,
    -w        => $width_of_block,

    -h        => $height_of_block,
   [-lead     => $font_size * 1.2 | $distance_between_lines,]
   [-parspace => 0 | $extra_distance_between_paragraphs,]
   [-align    => "left|right|center|justify|fulljustify",]
   [-hang     => $optional_hanging_indent,]

);
  62: my ( $endw, $ypos, $paragraph ) = text_block(
  63:     $left_column_text,
  64:     $paragraph1,
  65:     -x        => 10/mm,
  66:     -y        => 119/mm,
  67:     -w        => 41.5/mm,
  68:     -h        => 110/mm - 7/pt,
  69:     -lead     => 7/pt,
  70:     -parspace => 0/pt,
  71:     -align    => 'justify',
  72: );

END OF WRITTEN TUTORIAL - At the moment the following is just code and I haven't yet got around to writing it up. Chances are, you've learned enough by now to take it further yourself.


  74: $left_column_text->font( $font{'Helvetica'}{'Bold'}, 6/pt );
  75: $left_column_text->fillcolor('darkblue');
  76: ( $endw, $ypos, $paragraph ) = text_block(
  77:     $left_column_text,
  78:     'Enim eugiamc ommodolor sendre feum zzrit at. Ut prat. Ut lum quisi.',
  79:     -x => 10/mm,
  80:     -y => $ypos - 7/pt,
  81:     -w => 41.5/mm,
  82:     -h => 110/mm - ( 119/mm - $ypos ),
  83:     -lead     => 7/pt,
  84:     -parspace => 0/pt,
  85:     -align    => 'center',
  86: );
  87:
  88: $left_column_text->font( $font{'Times'}{'Roman'}, 6/pt );
  89: $left_column_text->fillcolor('black');
  90: ( $endw, $ypos, $paragraph ) = text_block(
  91:     $left_column_text,
  92:     $paragraph2,
  93:     -x => 10/mm,
  94:     -y => $ypos,
  95:     -w => 41.5/mm,
  96:     -h => 110/mm - ( 119/mm - $ypos ),
  97:     -lead     => 7/pt,
  98:     -parspace => 0/pt,
  99:     -align    => 'justify',
 100: );
 101:
 102: my $photo = $page->gfx;
 103: die("Unable to find image file: $!") unless -e $picture;
 104: my $photo_file = $pdf->image_jpeg($picture);
 105: $photo->image( $photo_file, 54/mm, 66/mm, 41/mm, 55/mm );
 106:
 107: my $right_column_text = $page->text;
 108: $right_column_text->font( $font{'Times'}{'Roman'}, 6/pt );
 109: $right_column_text->fillcolor('black');
 110: ( $endw, $ypos, $paragraph ) = text_block(
 111:     $right_column_text,
 112:     $paragraph,
 113:     -x        => 54/mm,
 114:     -y        => 62/mm,
 115:     -w        => 41.5/mm,
 116:     -h        => 54/mm,
 117:     -lead     => 7/pt,
 118:     -parspace => 0/pt,
 119:     -align    => 'justify',
 120:     -hang     => "\xB7  ",
 121: );
 122:
 123: $pdf->save;
 124: $pdf->end();
 125:
 126 sub text_block {
 127:
 128:     my $text_object = shift;
 129:     my $text        = shift;
 130:
 131:     my %arg = @_;
 132:
 133:     # Get the text in paragraphs
 134:     my @paragraphs = split( /\n/, $text );
 135:
 136:     # calculate width of all words
 137:     my $space_width = $text_object->advancewidth(' ');
 138:
 139:     my @words = split( /\s+/, $text );
 140:     my %width = ();
 141:     foreach (@words) {
 142:         next if exists $width{$_};
 143:         $width{$_} = $text_object->advancewidth($_);
 144:     }
 145:
 146:     $ypos = $arg{'-y'};
 147:     my @paragraph = split( / /, shift(@paragraphs) );
 148:
 149:     my $first_line      = 1;
 150:     my $first_paragraph = 1;
 151:
 152:     # while we can add another line
 153:
 154:     while ( $ypos >= $arg{'-y'} - $arg{'-h'} + $arg{'-lead'} ) {
 155:
 156:         unless (@paragraph) {
 157:             last unless scalar @paragraphs;
 158:
 159:             @paragraph = split( / /, shift(@paragraphs) );
 160:
 161:             $ypos -= $arg{'-parspace'} if $arg{'-parspace'};
 162:             last unless $ypos >= $arg{'-y'} - $arg{'-h'};
 163:
 164:             $first_line      = 1;
 165:             $first_paragraph = 0;
 166:         }
 167:
 168:         my $xpos = $arg{'-x'};
 169:
 170:         # while there's room on the line, add another word
 171:         my @line = ();
 172:
 173:         my $line_width = 0;
 174:         if ( $first_line && exists $arg{'-hang'} ) {
 175:
 176:             my $hang_width = $text_object->advancewidth( $arg{'-hang'} );
 177:
 178:             $text_object->translate( $xpos, $ypos );
 179:             $text_object->text( $arg{'-hang'} );
 180:
 181:             $xpos       += $hang_width;
 182:             $line_width += $hang_width;
 183:             $arg{'-indent'} += $hang_width if $first_paragraph;
 184:
 185:         }
 186:         elsif ( $first_line && exists $arg{'-flindent'} ) {
 187:
 188:             $xpos       += $arg{'-flindent'};
 189:             $line_width += $arg{'-flindent'};
 190:
 191:         }
 192:         elsif ( $first_paragraph && exists $arg{'-fpindent'} ) {
 193:
 194:             $xpos       += $arg{'-fpindent'};
 195:             $line_width += $arg{'-fpindent'};
 196:
 197:         }
 198:         elsif ( exists $arg{'-indent'} ) {
 199:
 200:             $xpos       += $arg{'-indent'};
 201:             $line_width += $arg{'-indent'};
 202:
 203:         }
 204:
 205:         while ( @paragraph
 206:             and $line_width + ( scalar(@line) * $space_width ) +
 207:             $width{ $paragraph[0] } < $arg{'-w'} )
 208:         {
 209:
 210:             $line_width += $width{ $paragraph[0] };
 211:             push( @line, shift(@paragraph) );
 212:
 213:         }
 214:
 215:         # calculate the space width
 216:         my ( $wordspace, $align );
 217:         if ( $arg{'-align'} eq 'fulljustify'
 218:             or ( $arg{'-align'} eq 'justify' and @paragraph ) )
 219:         {
 220:
 221:             if ( scalar(@line) == 1 ) {
 222:                 @line = split( //, $line[0] );
 223:
 224:             }
 225:             $wordspace = ( $arg{'-w'} - $line_width ) / ( scalar(@line) - 1 );
 226:
 227:             $align = 'justify';
 228:         }
 229:         else {
 230:             $align = ( $arg{'-align'} eq 'justify' ) ? 'left' : $arg{'-align'};
 231:
 232:             $wordspace = $space_width;
 233:         }
 234:         $line_width += $wordspace * ( scalar(@line) - 1 );
 235:
 236:         if ( $align eq 'justify' ) {
 237:             foreach my $word (@line) {
 238:
 239:                 $text_object->translate( $xpos, $ypos );
 240:                 $text_object->text($word);
 241:
 242:                 $xpos += ( $width{$word} + $wordspace ) if (@line);
 243:
 244:             }
 245:             $endw = $arg{'-w'};
 246:         }
 247:         else {
 248:
 249:             # calculate the left hand position of the line
 250:             if ( $align eq 'right' ) {
 251:                 $xpos += $arg{'-w'} - $line_width;
 252:
 253:             }
 254:             elsif ( $align eq 'center' ) {
 255:                 $xpos += ( $arg{'-w'} / 2 ) - ( $line_width / 2 );
 256:
 257:             }
 258:
 259:             # render the line
 260:             $text_object->translate( $xpos, $ypos );
 261:
 262:             $endw = $text_object->text( join( ' ', @line ) );
 263:
 264:         }
 265:         $ypos -= $arg{'-lead'};
 266:         $first_line = 0;
 267:
 268:     }
 269:     unshift( @paragraphs, join( ' ', @paragraph ) ) if scalar(@paragraph);
 270:
 271:     return ( $endw, $ypos, join( "\n", @paragraphs ) )
 272:
 273: }
 274:
 275: ### SAVE ROOM AT THE TOP ###
 276 sub get_data {
 277:     (
 278: qq|Perci ent ulluptat vel eum zzriure feuguero core consenis adignim irilluptat praessit la con henit velis dio ex enim ex ex euguercilit il enismol eseniam, suscing essequis nit iliquip erci blam dolutpatisi.
 279: Orpero do odipit ercilis ad er augait ing ex elit autatio od minisis amconsequam, quis am il do consenim esequi eui blamcorer adiat. Ut prat la facip ercip eugiamconsed tio do exero ea consequis do odolor il dolut wisim adit, susciniscing et adit num num vel ip ercilit alismolorem zzril ute dolendre ming eu feui bla feugait il illa facin eu feugiam conseniam eliquisl et luptat la feu feugait, volore euguerc incillu msandigna feuipisl iriuscilit velit wisl utem veros ad min velit laor iuscilit veliquis ad tie endignim dignisl et, qui bla feugue mod enibh esendiam, si blaor si blaore te min vel utpat nonsequ issequis dolorperosto dolobore ex erit, vel in utating etum ad dolutatet la feugiatue mod euisci blandre tat iurem eum velit prat nosting essim ver aliscil dolortie cor alisit wisl delesti sciduisci ting eu feu facidunt autat. Duipis amcommy non er sit, commy numsand ionsequam, commy num alisim euis dio eu faciduisit ate moloreet, quam zzrillaore magnit eum dolor ipsum dunt dolor sequatie dolor iustrud te molum dolore velit la faccum zzriuscil utpat irit nummod magna alis eu faccum inibh erosto ea ad magniamet vel esto dipsusto elesting eugiam, commolobore deliquat praessenim et, vel ut et nibh et adit lortisi.|,
 280:
 281: qq|It augait ate magniametum irit, venim doloreet augiamet ilit alis nonse dolore delessit volor susto od ming eugiam voloborem ip ea faciduisis alit, vent nim nulput utat endre dolum quissit atem nim dolorperci tat dunt veliquat ipis acip elenit lum dunt luptat. Ut luptat nulla feu facin hent dolobore vulput augait, quamet vent non utpat nulput nonsed doloreetum dio estis eum aut accummy nos nisi.
 282: Orpero do odipit ercilis ad er augait ing ex elit autatio od minisis amconsequam, quis am il do consenim esequi eui blamcorer adiat. Ut prat la facip ercip eugiamconsed tio do exero ea consequis do odolor il dolut wisim adit, susciniscing et adit num num vel ip ercilit alismolorem zzril ute dolendre ming eu feui bla feugait il illa facin eu feugiam.
 283: Conseniam eliquisl et luptat la feu feugait, volore euguerc incillu msandigna feuipisl iriuscilit velit wisl utem veros ad min velit laor iuscilit veliquis ad tie endignim dignisl et, qui bla feugue mod enibh esendiam, si blaor si blaore te min vel utpat nonsequ issequis dolorperosto.
 284: Dolobore ex erit, vel in utating etum ad dolutatet la feugiatue mod euisci blandre tat iurem eum velit prat nosting essim ver aliscil dolortie cor alisit wisl delesti sciduisci ting eu feu facidunt autat. Duipis amcommy non er sit, commy numsand ionsequam.|,
 285:
 286:         './Portrait.jpg'
 287:     );
 288: }
 289:

SUPPORT

Support for this module is provided via Yahoo Groups.

Questions related to this document should be addressed to the author: Rick Measham

LICENSE AND COPYRIGHT

This tutorial is © Rick Measham, 2004-2007 and is released under the GFDL and so is 'free'. Any derivative works must also be free.

This tutorial includes a function 'text_block' that is released under the LGPL v2.1. Any derivative or distribution of that function must also be under the LGPL. Note that the LGPL does not force your own project to use the LGPL. The text_block function MUST be distributed with the following copyright notice:

text_block() is © Rick Measham, 2004-2007. The latest version can be found in the tutorial located at http://rick.measham.id.au/pdf-api2/

AUTHOR

Rick Measham (aka Woosta)