Help language development. Donate to The Perl Foundation

PDF::Document cpan:TBROWDER last updated on 2021-02-12

=begin pod


=head1 NAME

B<PDF::Document> - Provides high-level classes and routines to create
      original documents in Portable Document Format (PDF)

This module is currently functioning as a laboratory to create
routines and classes to support other PDF modules. As such,
its API is subject to change until version 1.0.0+.

In the meantime, users are encouraged to use it, report issues,
and submit feature requests.

See the C<dev> directory in the source repository for examples
of use. The example in the B<SYNOPSIS> is program C<./dev/make-example-doc.raku>.


=begin code :lang<raku>
#!/usr/bin/env raku
use PDF::Document;

# We change only three of the many defaults for this
# example: (1) output file name, (2) force option to
# allow overwriting that file if it exists, and (3)
# turn page numbering on:
my \d = :pdf-name<example-letter>, :force, :page-numbering, :$debug;

# use the 'with' block to ease typing by one character
# per command
with d {
# but you'll crash if you forget to close the block!
#=========== THE LETTER =================
# starts with a new page, current position top baseline, left margin

# put the date at the top-right corner
.print: "2021-03-04", :tr, :align<right>, :valign<top>;
.nl; # adds the newline, resets x to left margin

.say: "Dear Mom,"; # SHOULD automatically add a newline
.nl: 1; # moves y down one line, resets x=0 (left margin)
.say: "I am fine.";
.nl: 1;
.say: "How are you?";

# simple graphics: circle, etc.
.nl: 30;
.say: "circle: radius = 36 pts, linewidth = 4 points";
.save; # save the current position and graphics state
.setlinewidth: 4; # points
.circle: :x<5in>, :y<3in>, :radius(36); # default points (or in, cm)
.restore; # don't forget to go back to normal!

.np; # new page, current position top baseline, left margin
.say: q:to/PARA/;
Pretend this is a VERY long para
that extends at least more than one line length in the
current font so we can observe the effect of  paragraph
wrapping. Isn't this swell!

.nl: 3;

.say: "Thats all, folks, but see following pages for other bells and whistles!";
.nl: 2;
.say: "Love,";
.nl: 2;
.say: "Isaiah";

.np; # for some graphics examples

.say: "ellipse: a = 1 in, b = 0.5 in", :y<8in>;
.ellipse: :x<5in>, :y<8in>, :a<1in>, :b<.5in>;

.say: "ellipse: a = 0.3 in, b = 2 cm", :y<6in>;
.ellipse: :x<5in>, :y<6in>, :a<.3in>, :b<2cm>;

.say: "circle: radius = 24 mm", :y<4in>;
.circle: :x<5in>, :y<4in>, :radius<24mm>;

.say: "rectangle: width = 2 in, height = 2 cm", :y<2in>;
.rectangle: :llx<5in>, :lly<2in>, :width<2in>, :height<2cm>;

.np; # for some more graphics examples

.say: "polyline:", :y<7.5in>;
my @pts = 1*i2p, 7*i2p, 4*i2p, 6.5*i2p, 3*i2p, 5*i2p;
.polyline: @pts;

.say: "blue polygon:", :y<4.5in>;
@pts = 1*i2p, 4*i2p, 4*i2p, 3.5*i2p, 3*i2p, 2*i2p;
.polygon: @pts, :fill, :color[0,0,1]; # rgb, 0-1 values

.end-doc; # renders the pdf and saves the output
          # also numbers the pages if you requested it
#=========== END THE LETTER =================
} # don't forget to close the 'given...' block

=end code


Module C<PDF::Document> leverages the power of lower-level modules
C<PDF::Lite> and C<Font::AFM> and encapsulates some of its classes,
routines, and variables into higher-level contructs to ease PDF
document creation.

=head2 PostScript document generation process

The module is designed around the document generation process used by
those who use PostScript (PS) code to create PS documents which are
then transformed into PDF by the GNU program C<ps2pdf>. That process
is described in great detail in the classic PS books by Adobe (see
REFERENCES) and is divided into the following logical sequences:

=item Define the prologue which usually includes:
    =item2 Finding the font faces to be used
    =item2 Font selection (creating the actual font by scaling a face to the desired size)
    =item2 Procedure definition
=item Define and render each page
=item End the document

=head3 PostScript font selection

The PS font selection process needs a little more detail to help explain
how it is done in this module. First some terminology. From Ref. 1 we get
some pertinent function names and definitions:

=item B<findfont>
    - I<key> B<findfont> I<font>
    - "obtains a font dictionary defined by the I<key> and pushes it on the operand stack...", p. 418
=item B<scalefont>
    - I<font scale> B<scalefont> I<font'>
    - "applies the scale factor I<scale> to I<font>, producing a new I<font'> whose characters are scaled by I<scale>
      (in both I<x> and I<y>) when they are shown.", p. 488
=item B<setfont>
    - I<font> B<setfont> I<->
    - "establishes the font dictionary parameter in the graphics state.", p. 503
=item B<selectfont B<[Level 2]>>
    - I<key scale> B<scalefont>
    - "obtains a font whose name is I<key>, transforms is according to I<scale>, and establishes it as the current font dictionary
      in the graphics state.", p. 490

The PS Level 1 method for defining a usable font is shown in this example:
/Times-Roman findfont 10 scalefont setfont

The PS Level 2 method for defining a usable font is shown in this example:
/Times-Roman 10 selectfont

(Note the the Level 2 method is "almost always more efficient.", Ref. 1, p. 490)

In either case, we usually save desired combinations of font prototypes and scale by
defining them by another name for easy recall. For example:
/h12 /Helvetica 10 selectfont def
/hb12 /Hevetica-Bold 12 selectfont def

Now we can use them like this:
hb12 (Cowboy slang: ) show
h10 (Howdy, podnuh!) show

Which would generate something like this in the final document: "B<Cowboy slang:> Howdy, podnuh!"

=head2 PDF document generation process

That sequence is also followed in the PDF document creation process:

=item Define the C<PDF> class instance (a heavy-weight instantiation, only one per document)
    =item2 C<my $pdf = PDF::Lite;>
=item Find the fonts to be used (also a heavy-weight instantiation)
    =item2 C<my $courier = find-font :name<Courier>, :$pdf;>
    =item2 C<my $helvetica> = find-font :name<h>, :$pdf; # use its alias>
=item Select the fonts to be used by adding size to a copy of an existing font family
(a light-weight instantiation)
    =item2 C<my $c10 = select-font :fontfamily($courier), :size(10);>
    =item2 C<my $h12h= select-font :fontfamily($helvetica>, :size(12.5);>
=item Define each page
    =item2 C<my $page = $pdf.add-page;>
    =item2 C<#...add text and graphics...>
    =item2 C<#...add a new page...>
    =item2 C<my $page = $pdf.add-page;>
    =item2 C<#...add text and graphics...>
=item Create the document and exit
    =item2 C<$<MyDoc.pdf>;>

=head3 PDF font selection

As opposed to PS, the font selection process using C<PDF::Lite> is a
bit different since, with the given low-level routines, we keep the
font "prototype" separate from the desired font size when we use the
font in a text block. For example, here is a text block being rendered
on a PDF page instance:

=begin code :lang<raku>
$page.text: {
    .text-position = $x, $y;
    .font = $setfont.font, $setfont.size;
    .say("Howdy, podnuh!");
=end code

=head2 Summary

As you can see the document steps are equivalent, but the steps in PDF page
creation are much easier because common low-level code required in PS
creation is available under the covers in C<PDF::Lite> and accessed
more easily by this module.


Currently the the module provides routines and constants as used in the example
program shown in the B<SYNOPSIS>. Much more work is planned including:

=item1 font underlining
=item1 font strikethrough
=item1 more graphics objects (e.g., Moon phases)


This module is being used during the development of the author's other PDF modules:

=item C<PDF::Writer>*
=item C<PDF::Labelmaker>**
=item C<PDF::Calendar>
=item C<PDF::ReWriter>
=item C<PDF::Forms>

This module will be updated with more items as the user modules are
updated and published.

NOTE: The asterisk (C<*>) indicates the module has been published,
albeit of minimal use. Two asterisks means the published module is not
even minimally useful, but it is exposed to issues or feature requests
from interested parties.


=item 1. I<PostScript Language Reference Manual>, 2nd Edition, Adobe Systems Inc., 1990
=item 2. I<PostScript Language Tutorial and Cookbook>, Adobe Systems Inc., 1986

=head1 AUTHOR

Tom Browder <[email protected]>


Copyright E<0x00a9> 2021 Tom Browder

This library is free software; you can redistribute it or modify it
under the Artistic License 2.0.

=end pod