[Raku PDF Project]
/ PDF::Lite
PDF::Lite
PDF::Lite
is a minimal class for creating or editing PDF documents, including:
- Basic Text
- Simple forms and images (GIF, JPEG & PNG)
- Graphics and Drawing
- Content reuse (Pages and form objects)
use v6;
use PDF::Lite;
my PDF::Lite $pdf .= new;
$pdf.media-box = 'Letter';
my PDF::Lite::Page $page = $pdf.add-page;
constant X-Margin = 10;
constant Padding = 10;
$page.graphics: {
enum <x0 y0 x1 y1>;
my $font = $pdf.core-font( :family<Helvetica>, :weight<bold>, :style<italic> );
my @position = [10, 10];
my @box = .say: "Hello World", :@position, :$font;
my PDF::Lite::XObject $img = .load-image: "t/images/lightbulb.gif";
.do: $img, :position[@box[x1] + Padding, 10];
}
given $pdf.Info //= {} {
.CreationDate = DateTime.now;
}
$pdf.save-as: "examples/hello-world.pdf";
Text
.say
and .print
are simple convenience methods for displaying simple blocks of text with encoding, optional line-wrapping, alignment and kerning.
These methods return a rectangle given the rendered text region;
use PDF::Lite;
enum <x0 y0 x1 y1>;
my PDF::Lite $pdf .= new;
$pdf.media-box = [0, 0, 500, 150];
my PDF::Lite::Page $page = $pdf.add-page;
my $font = $pdf.core-font( :family<Helvetica> );
my $header-font = $pdf.core-font( :family<Helvetica>, :weight<bold> );
$page.text: -> $txt {
$txt.font = $header-font, 16;
$txt.text-position = 250, 112;
$txt.say: 'Some Sample PDF::Lite Text', :align<center>, :valign<bottom>;
my $width := 200;
my $text = q:to"--END--";
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt
ut labore et dolore magna aliqua.
--END--
$txt.font = $font, 12;
# output text with left, top corner at (20, 100)
my @box = $txt.say: $text, :$width, :position[:left(20), :top(100)];
note "text height: {@box[y1] - @box[y0]}";
# output kerned paragraph, flow from right to left, right, top edge at (450, 100)
$txt.say( $text, :$width, :height(150), :align<right>, :kern, :position[450, 100] );
# add another line of text, flowing on to the next line
$txt.font = $pdf.core-font( :family<Helvetica>, :weight<bold> ), 12;
$txt.say( "But wait, there's more!!", :align<right>, :kern );
}
$pdf.save-as: "examples/sample-text.pdf";
Images (.load-image
and .do
methods):
The .load-image
method can be used to open an image.
The .do
method can them be used to render it.
use PDF::Lite;
my PDF::Lite $pdf .= new;
$pdf.media-box = [0, 0, 450, 250];
my PDF::Lite::Page $page = $pdf.add-page;
$page.graphics: -> $gfx {
my PDF::Lite::XObject $img = $gfx.load-image("t/images/snoopy-happy-dance.jpg");
$gfx.do($img, 50, 40, :width(150) );
# displays the image again, semi-transparently with translation, rotation and scaling
$gfx.transform( :translate[180, 100]);
$gfx.transform( :rotate(-.5), :scale(.75) );
$gfx.FillAlpha = 0.5;
$gfx.do($img, :width(150) );
}
$pdf.save-as: "examples/sample-image.pdf";
Note: at this stage, only the JPEG
, GIF
and PNG
image formats are supported.
Text effects
To display card suits symbols, using the ZapfDingbats core-font, with diamonds and hearts colored red:
use PDF::Lite;
use PDF::Content::Color :rgb;
my PDF::Lite $pdf .= new;
$pdf.media-box = [0, 0, 400, 120];
my PDF::Lite::Page $page = $pdf.add-page;
$page.graphics: {
$page.text: {
.text-position = [20, 70];
.font = [ $pdf.core-font('ZapfDingbats'), 24];
.WordSpacing = 16;
.print("♠ ♣\c[NO-BREAK SPACE]");
.FillColor = rgb(1, .3, .3); # reddish
.say("♦ ♥");
}
# Display outline, slanted text
my $header-font = $pdf.core-font( :family<Helvetica>, :weight<bold> );
$page.text: {
use PDF::Content::Ops :TextMode;
.font = ( $header-font, 18);
.TextRender = TextMode::FillOutlineText;
.LineWidth = .5;
.text-transform( :skew[0, -6], :translate[10, 30] );
.FillColor = rgb(.6, .7, .9);
.print('Outline Slanted Text @(10,30)');
}
}
$pdf.save-as: "examples/text-effects.pdf";
Fonts
This module has build-in support for the PDF core fonts: Courier, Times, Helvetica, ZapfDingbats and Symbol.
The companion module PDF::Font::Loader can be used to access a wider range of fonts:
use PDF::Lite;
use PDF::Font::Loader :load-font;
my PDF::Lite $pdf .= new;
$pdf.media-box = [0, 0, 400, 120];
my PDF::Lite::Page $page = $pdf.add-page;
my $noto = load-font( :file<t/fonts/NotoSans-Regular.ttf> );
# or find a system font by family and attributes (also requires fontconfig)
# $noto = load-font: :family<NotoSans>, :weight<book>;
$page.text: {
.text-position = [10,100];
.font = $noto;
.say: "Noto Sans Regular";
}
$pdf.save-as: "examples/fonts.pdf";
Forms are a reusable graphics component. They can be used whereever
images can be used.
A pattern can be used to fill an area with a repeating graphic.
use PDF::Lite;
use PDF::Content::Color :rgb;
my PDF::Lite $pdf .= new;
$pdf.media-box = [0, 0, 400, 120];
my PDF::Lite::Page $page = $pdf.add-page;
$page.graphics: {
my $font = $pdf.core-font( :family<Helvetica> );
my PDF::Lite::XObject $form = .xobject-form(:BBox[0, 0, 95, 25]);
$form.graphics: {
# Set a background color
.FillColor = rgb(.8, .9, .9);
.Rectangle: |$form<BBox>;
.paint: :fill;
.font = $font;
.FillColor = rgb(1, .3, .3); # reddish
.say("Simple Form", :position[2, 5]);
}
# display a simple form a couple of times
.do($form, 10, 10);
.transform: :translate(10,40), :rotate(.1), :scale(.75);
.do($form, 10, 10);
}
$page.graphics: {
my PDF::Lite::Tiling-Pattern $pattern = .tiling-pattern(:BBox[0, 0, 25, 25], );
$pattern.graphics: {
# Set a background color
.FillColor = rgb(.8, .8, .9);
.Rectangle: |$pattern<BBox>;
.paint: :fill;
# Display an image
my PDF::Lite::XObject $img = .load-image("t/images/lightbulb.gif");
.do($img, 6, 2 );
}
# fill a rectangle using this pattern
.FillColor = .use-pattern($pattern);
.Rectangle(125, 10, 200, 100);
.paint: :stroke, :fill;
}
$pdf.save-as: "examples/forms-and-patterns.pdf";
Resources and Reuse
The to-xobject
method can be used to convert a page to an XObject Form to lay-up one or more input pages on an output page.
use PDF::Lite;
my $pdf-with-images = PDF::Lite.open: "t/images.pdf";
my $pdf-with-text = PDF::Lite.open: "examples/sample-text.pdf";
my PDF::Lite $new-doc .= new;
$new-doc.media-box = [0, 0, 500, 400];
# add a page; layup imported pages and images
my PDF::Lite::Page $page = $new-doc.add-page;
my PDF::Lite::XObject $xobj-image = $pdf-with-images.page(1).images[6];
my PDF::Lite::XObject $xobj-with-text = $pdf-with-text.page(1).to-xobject;
my PDF::Lite::XObject $xobj-with-images = $pdf-with-images.page(1).to-xobject;
$page.graphics: {
# scale up an image; use it as a semi-transparent background
.FillAlpha = 0.5;
.do($xobj-image, 0, 0, :width(500), :height(400) );
};
$page.graphics: {
# overlay pages; scale these down
.do($xobj-with-text, 20, 100, :width(300) );
.do($xobj-with-images, 300, 100, :width(200) );
}
# copy whole pages from a document
for 1 .. $pdf-with-text.page-count -> $page-no {
$new-doc.add-page: $pdf-with-text.page($page-no);
}
$new-doc.save-as: "examples/reuse.pdf";
To list all images and forms for each page
use PDF::Lite;
my $pdf = PDF::Lite.open: "t/images.pdf";
for 1 ... $pdf.page-count -> $page-no {
say "page: $page-no";
my PDF::Lite::Page $page = $pdf.page: $page-no;
# get all X-Objects (images and forms) on the page
my PDF::Lite::XObject %object = $page.resources('XObject');
# also report on images embedded in the page content
my $k = "(inline-0)";
%object{++$k} = $_
for $page.gfx.inline-images;
for %object.keys -> $key {
my $xobject = %object{$key};
my $subtype = $xobject<Subtype>;
my $size = $xobject.encoded.codes;
say "\t$key: $subtype {$xobject.width}x{$xobject.height} $size bytes"
}
}
Resource types are: ExtGState
(graphics state), ColorSpace
, Pattern
, Shading
, XObject
(forms and images) and Properties
.
Resources of type Pattern
and XObject/Image
may have further associated resources.
Whole pages or individual resources may be copied from one PDF to another.
Graphics Operations
A full range of general graphics is available for drawing and displaying text.
use PDF::Lite;
my PDF::Lite $pdf .= new;
my $page = $pdf.add-page;
# Draw a simple Bézier curve:
# ------------------------
# Alternative 1: Using operator functions (see PDF::Content)
sub draw-curve1($gfx) {
$gfx.Save;
$gfx.MoveTo(175, 720);
$gfx.LineTo(175, 700);
$gfx.CurveToInitial( 300, 800, 400, 720 );
$gfx.ClosePath;
$gfx.Stroke;
$gfx.Restore;
}
draw-curve1($page.gfx);
# ------------------------
# Alternative 2: draw from content instructions string:
sub draw-curve2($gfx) {
$gfx.ops: q:to"--END--"
q % save
175 720 m % move-to
175 700 l % line-to
300 800 400 720 v % curve-to
h % close
S % stroke
Q % restore
--END--
}
draw-curve2($pdf.add-page.gfx);
# ------------------------
# Alternative 3: draw from raw data
sub draw-curve3($gfx) {
$gfx.ops: [
'q', # save,
:m[175, 720], # move-to
:l[175, 700], # line-to
:v[300, 800,
400, 720], # curve-to
:h[], # close (or equivalently, 'h')
'S', # stroke (or equivalently, :S[])
'Q', # restore
];
}
draw-curve3($pdf.add-page.gfx);
Please see PDF::API6 Appendix I - Graphics for a description of available operators and graphics.
Graphics can also be read from an existing PDF file:
use PDF::Lite;
my $pdf = PDF::Lite.open: "examples/hello-world.pdf";
say $pdf.page(1).gfx.ops;
Graphics and Rendering
A number of variables are maintained that describe the graphics state. In many cases these may be set directly:
use PDF::Lite;
my PDF::Lite $pdf .= new;
my PDF::Lite::Page $page = $pdf.add-page;
$page.graphics: {
.text: { # start a text block
.CharSpacing = 1.0; # show text with wide spacing
# Set the font to twelve point helvetica
my $face = $pdf.core-font( :family<Helvetica>, :weight<bold>, :style<italic> );
.font = [ $face, 10 ];
.TextLeading = 12; # new-line advances 12 points
.text-position = 10, 20;
.say("Sample Text", :position[10, 20]);
# '$gfx.say' has updated the text position to the next line
say .text-position;
} # restore previous text state
say .CharSpacing; # restored to 0
}
A renderer callback can be specified when reading content. This will be called for each graphics operation and has access to the graphics state, via
the $*gfx
dynamic variable.
use PDF::Lite;
use PDF::Content::Ops :OpCode;
my PDF::Lite $pdf .= open: "examples/hello-world.pdf";
my &callback = -> $op, *@args {
given $op {
when SetTextMatrix {
say "text matrix set to: {$*gfx.TextMatrix}";
}
}
}
my $gfx = $pdf.page(1).render(:&callback);
# text matrix set to: 1 0 0 1 10 10
See also
PDF::Font::Loader for using Postscript, TrueType and OpenType fonts.
HTML::Canvas::To::PDF HTML Canvas renderer
This module (PDF::Lite) is based on PDF and has all of it methods available. This includes:
open
to read an existing PDF or JSON filesave-as
to save to PDF or JSONupdate
to perform an in-place incremental update of the PDFInfo
to access document meta-data
PDF::API6 Graphics Documentation for a fuller description of methods, operators and graphics variables, which are also applicable to this module. In particular: