PDF Object Source Code

Source Code for C++ Builder

This source code has been available for download as a 'Thank-you' for the help I have received on the Embarcadero Developers Forum. The ladies & gentlemen there have been very helpful in pointing to solutions for some of my programming problems, so, by way of thanks I offer up this source code for any C++ Builder user to incorporate in their programs.

So, What is it?

It is the source code (4 off C++ Builder VCL units) to create a basic PDF file, Version 1.3 format, and use it in the program in pretty much the same way as the C++ Builder TPrinter() object. The PDFObject is a descendant of the VCL TObject, and as a result this source code is limited to being used in C++ Builder.

Now, I am not the world's most proficient programmer, so there are limitations. The PDFObject cannot embed fonts (instead it is limited to PDF standard fonts of Times, Helvetica & Courier), and there is no internal compression in the file (fast, large files). However, virtually all the capabilities of the TPrinter()->Canvas have been replicated, with a few extras like internal and external hyperlinks. If you are a brilliant programmer (unlike me) and can solve the limitations, please modify the code & share your work.

The FULL source code can be downloaded below - there are no libraries to use. A readme file and 8 source code files are included in the zip file. A 24 page cross stitch chart created with the unit is also included to demonstrate many of its capabilities. The cross stitch chart is pretty demanding of the PDFObject, using lines, text as well as re-using glyphs in multiple places and having a hyperlinked contents page. It also shows that the PDf file can have different page sizes/orientations within the document - most pages are A4 portrait, but there is one A2 full size mock-up page at the end.

Download the PDF Object Source Code. (2.839 Mb)

The Source Code works as it is with C++ Builder in RAD Studio 10.1 Berlin. It was originally developed in C++ Builder 4, only #include's had to be changed, so it should be possible to make it work in any intermediate version of C++ Builder. Full description and use instructions (600 lines of text), plus example use code, are included in 'pdfunit.cpp'. Note: This is source code and not a stand alone program.

I would like to warn you now, I am a Mechanical Engineer, not a professional programmer, and consequently my solutions owe more to the brute ignorance & force school of programming than any form of computer science. My solutions work, but it's highly unlikely that they are the most efficient solution.

Notes within the PDFUnit:

Use this at your own risk. I am not liable for any use/misuse/failure.
Copyright is retained by Richard Williams for the parts he has written.
You may use and adapt this code in your own programs as you see fit. You
retain copyright for the modifications. It may be used for commercial or
home purposes.
Please share the modifications.
Please give credit where it's due.
Please note the original author is a mechanical engineer & not a programmer,
so relies on the BRUTE IGNORANCE & FORCE method of programming to get to the
end. There are almost certainly quicker ways of doing the same job.


Main Reference document used when creating this unit:
'PDF Reference Second edition
Adobe Portable Document Format
Version 1.3
Adobe Systems Incorporated'
May be found on the Adobe Web site.
Seems to have been written by someone who knows the standard inside out,
but hasn't the faintest idea what the standard will be used for. As a
result, it is close to useless.

There is example code showing how to use this unit lower down in these
notes, which should work if copied as is.

This unit is intended to act as a simple PDF creator, creating simple pages,
no thumbnails etc. All page positioning references are in mm, related to the
top left of the page - as per the C++ Builder Canvas but using mm rather
than pixels. The pdf file measures everything from the bottom left of the
page, in points (72 per inch) - as per mathematical/engineering practice.
The conversion from one form to the other is done automatically by the
PDFObject based on the PageHeight defined at the time the command is made.
All mm dimensions are double. Why? Well, I have come to grief in the past
with running out of significant digits in an application when only using
float numbers, so now only use doubles.

It is intended to behave in a manner similar to that of the standard
C++ Builder 'Printer()' object. On creation of the object, defaults are
specified, which may be altered prior to the BeginDocument() command.
On opening the pdf file, the binary file is created, opened and the PDF 1.3
header is created and 2 lines written.

Each page should be started with a 'NewPage()' command.
If no page is open and any visible object is positioned on the page, then a
'NewPage()' is automatically triggered. This will only happen for the first
The current page is flushed from the buffer by the next 'NewPage()' or
'EndDocument()' command. All items are flushed from the buffer on to the
page in the order they were added to the buffer.
If items overlap, items added later will be on top of earlier items.
The page size for the current page must be defined before the next NewPage()
or the EndDocument() command. Ideally it should be defined immediately after
the NewPage() for page content to be in the correct position up and down the
page. Different page sizes within the document are permitted, as are
different page orientations.
If the Page size is not defined before the next NewPage(), the page inherits
the same size as the previous page.
It does not matter if there are multiple page size definitions for the page
before the next NewPage() or EndDocument(). The last one will always apply,
but any graphics/text placed on the page are positioned relative to the top
of the page in force at the time of creation.
If the page size is not defined in the document at all, it inherits the A4
Portrait which the PDFObject defaults to on instantiation.
The Author, title etc info needs to be set before the EndDocument() command,
but can be set before the BeginDocument() command, as can all properties.

When the EndDocument() command is used, the last objects for the PDF file
are written to disc and the file closed. If the PDFObject is destroyed
before the EndDocument(), the EndDocument() command is called in the

On destruction, if the LaunchOnExit property is true, the created PDF file
is opened with an API ShellExecute() command. This is default behaviour.

The PDF specification is structured in the form of a header, a footer and
everything in between is in the form of an object. All characters in the PDF
file are single byte characters, NO WIDE CHARS. This is why AnsiStrings are
used. Anything prefaced by a '%' character is a comment in the PDF file, and
is equivalent to a '//' in C++. Each line ends in a Carriage Return
character & a Line Feed character.

For the PDF 1.3 spec it is always as below. The last three bytes on the
second line may be different, but all characters should be above ASCII 127.

At the end of the document there will always be 5 standard objects with
defined object numbers. These are:
1) Object 1: Catalogue
2) Object 2: Outlines At the moment no plans to expand this
3) Object 3: Pages In the PDF spec the Pages object can allow page groups.
At the moment the PDFObject does not group pages.
4) Object 4: Producer Info
5) Special Object 0: Object X ref list.
Gives the generation and byte offset of every object from the file start.
Then there is the trailer, startxref, and EOF indicator

The content of the PDF file is in the form of an object. Each object starts
with a line as below:
[objectnumber] [object generation number] obj
The object ends with:
In between there will be various arrays (surrounded by square brackets [ ]) or
data dictionaries (surrounded by << and >> ) as defined by the PDF
The PDFObject creates a new PDF file, so all objects are created with
object generation number 0 hard coded in.
The objects do not need to be in numerical order within the file.

eg the 60x60 pixel bitmap XObject below:
5 0 obj
/Type /XObject %this object is an XObject
/Subtype /Image %the XObject is an image
/Width 60 %that is 60 pixels wide
/Height 60 %and 60 pixels high
/ColorSpace /DeviceRGB %stream is specified as rgb triplets
/BitsPerComponent 8 %and there are 8 bits per colour component
/Length 10800 %There are 10800 bytes total (60*60*3)
{10800 bytes of data}endstream

Each Page object is similar to that below:
7 0 obj << /Type /Page %indicates this is a page object
/MediaBox [0 0 595.27 841.88 ] %specifies page bottom left and top right corners in points from bottom left
/CropBox [34.02 56.69 566.92 799.36 ] %specifies margin in points from bottom left
/Rotate 0 %view page rotated by 0 degrees
/PZ 1.0 %preferred zoom scale. Doesn't seem to be followed
/Parent 3 0 R %page parent is object 3, generation 0, the 'R' indicates it's an object reference rather than an object definition
/Annots [
11 0 R %Hyperlink annotation used on this page is object 11
/Resources << %Various resource objects are called up by this page
/ProcSet [ %Prepare for the following to be defined on this page
/PDF %This is a pdf page
/ImageC %that uses at least one colour image
/Text %and some text
/Font <<
/Font1 8 0 R %page uses Font1, and that is defined in object 8
/Font2 9 0 R %page also uses Font2, and that is defined in object 9
/XObject <<
/Image1 5 0 R %page uses bitmaps stored as XObjects. Image1 is object 5
/Image2 6 0 R %and Image2 is object 6
/Contents 12 0 R %the page contents are stored in object 12

Each page contents object is physically located 2 objects before the page
object in the pdf file, and will be similar to that below:
12 0 obj
<< /Length 13 0 R >> %the length of this content stream will be specified in object 13
0.00 0.00 0.00 RG %beginning of page, define pen colour black (rgb components 0.00 to 1.00)
1.00 1.00 1.00 rg %define background colour white
1 J %Line Cap Style: Round. Hard coded to be like the TCanvas object.
0 j %Line Join Style: Mitred. Hard coded to be like the TCanvas object.
0.71 w %pen width in points, 0.25mm in this case.
1.00 0.00 0.50 RG %pen colour for this line, 100% Red, 0% Green, 50% Blue
340.152 799.357 m %move to X340.152, Y799.357 (X120.00mm, Y282.00mm) from bottom left corner of page
56.692 289.129 l %line to X56.692, Y289.129 (X20.00mm, Y102.00mm)
S %fill in line.
0.00 1.00 1.00 rg %Fill colour = 0% Red, 100% Green, 100% Blue = Cyan
285.586 657.627 m
285.586 619.664 344.916 588.888 418.103 588.888 c %4 point bezier curve using initial pen pos, then the three co-ord pairs specified on this line
491.291 588.888 550.621 619.664 550.621 657.627 c % end result is an ellipse/circle
550.621 695.591 491.291 726.366 418.103 726.366 c
344.916 726.366 285.586 695.591 285.586 657.627 c
B %flood fill shape enclosed and colour line
q %push current graphics state on stack: must be paired with a 'Q'
28.346 0 0 28.346 113.384 700.146 cm %next object will have lower left corner 28.346, 28.346 and will be 113.384 x 700.146 points wide
/Image1 Do %draw Image1 in this space
Q %pop previous saved graphics state off stack
BT %Begin text (may be more than one BT on a page, but must be matched by an ET (end text), and ideally should be divided from the next BT/ET pair by some other content type
/Font1 10 Tf %Use Font1, scale 10 point
1 0 0 1 283.460 406.686 Tm %Locate bottom left of text at 283.46 points across, 406.686 points up from bottom left corner, with the 1 0 0 1 being the unity rotation matrix to apply to the text. The PDFObject does not permit rotated text.
0.00 0.00 0.00 rg %text colour black. Note the pen & text colours are defined by the same PDF command.
(Hello) Tj %write text 'Hello'
/Font2 20 Tf %Use Font2, scale 20 point
1 0 0 1 283.460 339.994 Tm
0.00 0.00 0.00 rg
(World) Tj %wite text 'World' some way below 'Hello' but at the same horizontal position from the left edge
ET %end text
When content is written to a page in the PDFObject, it is actually added to
the TStringList PageBuffer. The page buffer is written to the PDF file just
before each NewPage() command is implemented.

The page content length object will be similar to this:
13 0 obj
1896 %content stream length in bytes

A font object will be as below:
8 0 obj
/Type /Font %object is a font
/Subtype /Type1 %of Type1
/Name /Font1 %it's internal name is 'Font1'
/BaseFont /Times-BoldItalic %and the PDF font name is 'Times-BoldItalic' which is one of the standard assumed fonts - see later.
/Encoding /WinAnsiEncoding %for special characters, the Windows/Ansi coding map is used, so the copyright sign is decimal 160

There are two types of hyperlink objects, a URL and a Page Link
16 0 obj
/Subtype /Link
/Rect [ 283.46 65.04 501.63 85.04 ] %Hotspot area, bottom left to top right in points from bottom left corner of page
/BS <> %border width 0, hard coded
/F 4
/A <> %Specifies that the action from this link is a URL link, giving the web address

Page Link
17 0 obj
/Subtype /Link
/Rect [ 283.46 36.69 346.87 56.69 ]
/BS <>
/F 4
/A <> %Link sends user to page defined by object 7 and specifies top left screen position to be page co-ordinate 10.97,595.27, shown at zoom 0 (current zoom)

Where possible when writing to a page, the names, and way the functions
operate is intended to be the same as on a TCanvas. There is one serious
exception though. All dimensions are in mm, which may have fractional
components, so are defined as doubles. The 'coordinate' struct is the double
equivalent of the TPoint structure and an array of this will be needed for
the PolyBezier/Polygon/PolyLine functions.
All dimensions in the document are rounded to 3 decimal places of point -
so at 72 points per inch, this gives a real world precision of 0.0003528mm.
This should be adequate precision even for a 2400dpi printer (comes out as
accurate to the nearest 0.84 dot).

Compression of data streams is not carried out. I simply do not understand
how to do it. The PDF file produced by this object is large, but as it's all
text with a large number of repeating elements in it, it zips up much better
than many files, creating small, easily distributed zip files. The PDF files
seem to open very fast without the compression, too.

Try to avoid using the margins in the PDF object. They are used to create
the 'CropBox' in the pdf file. This literally crops the displayed part of
the page to the cropbox and does not display or render any part of the page
outside that area. The margin is only visible when printing as a greyed area
around the printed bit. You should really manage the margins inside the
calling application, as you would with the C++ Builder Printer() object.

When specifying a bitmap object to be saved in the pdf file, it is always
saved as an XObject, rather than as an in-line object. The Draw command is
an all purpose Draw/StretchDraw command, in that the bitmap may be stretched
or distorted simply by specifying the dimensions over which it is to paint.
If you are likely to re-use the bitmap, keep a record of the objectnumber
returned by the Draw() function as this will be needed for the
UseResourceImage() method to re-use it.
The image can be saved in the pdf file before any pages are created, using
the AddResourceImage() command. But the Draw() and UseResourceImage()
commands can only be fully effective after the first NewPage() command.
A Draw() or AddResourceImage() must occur before the UseResourceImage() -
simply put, you cannot use/re-use something before defining it. If you try
to use a bitmap before definition, the command is simply ignored.

With text, the text position is, as per TCanvas practice, defined at the
upper left corner.
The PDF file defines the active point of the text as the lower left corner.

Two TextHeight functions are offered.
The first is the 'Official' height of
the font, which is the point size, so 14 point is 14/2.8346mm = 4.9390mm
Thus when positioning the text, the vertical position will be defined as
the defined position + font height down from the top left corner of the
page, then transposed to a height up from the bottom left corner. This allows
you to define line spacing, as it is simply the height of the font.

The second TextHeight gives that actual height of the text presented, and is
calculated using the same kludge as the TextWidth below.

Text Width is a kludge. A Bitmap with a Canvas is created. Then the width in
pixels of the text at 10x its font size is found. this is then divided by
the number of pixels forming the text width of Arial 100point 'Hi There' and
multiplied by the actual width of Arial 10pt 'Hi There', which is 13.517mm.

There are 14 standard fonts that are assumed to be on each machine, and will
be treated as Type 1 fonts which do not need to be embedded. These are:
At the moment fonts are not embedded, so you should use the standard fonts.
On Windows machines there are standard substitutions for the above fonts, as
below, and there is no need to be concerned with their presence:
Courier New
Times New Roman
If you do choose to use True Type Fonts, in effect, it's assumed they will
be on the machine, leaving any substitutions down to the PDF reader app. I
have spent many frustrating hours trying to interrogate the font system in
the Windows API for the data to define a font glyph width etc, and got
nowhere. As a result, TrueType fonts will always come up with a 'FontBox
Error' when used in the PDF file, and the text size/width will be wrong.

The PDFObject does not have a TFont object for the current font in use. The
reason for this is that the default PDF font names probably have no windows
equivalents. It is likely there will be 'Arial' & no 'Helvetica'. C++Builder
10 seems to have a very strong affinity between fonts on the machine and
fonts available in a TFont object. Giving a font name of 'Helvetica' when
there was no such font on the machine caused the TFont object to die - I
think it's because no Handle can be generated for a font that's not there.
So, instead, as the only info needed for a font is the Name and Size, a
TPDFFont structure was created. This has a FontStyle built in, but is for
internal use only - so don't access it directly please.

Underlining and strikeout of the font is achieved by drawing lines through
and/or under the text and not as an integral feature of the font. The lines
are drawn 3/4 the way down for strikeout, and 1/10th the font height below
the font line for underline. The line width is 1/15th the font height. This
looks right.

The following TCanvas methods and their equivalents are given below:-
TCanvas TPDFObject
AngleArc AngleArc
Arc Arc
ArcTo Arc, according to the C++ Builder documentation,
this is functionally identical to
Arc, so use Arc.
Brush Copy None
Chord Chord
Copy Rect None. Use 'Draw' instead, and draw what you want
to see.
Draw Draw
DrawFocusRect None. The use of pen styles other than psSolid or
psClear is not yet catered for.
Ellipse Ellipse
FillRect Rectangle (set Pen->Style = bsClear and
PenWidth = 0 first)
FloodFill None
FrameRect Rectangle (set Brush->Style = bsClear and
PenWidth = 0 first)
LineTo LineTo
Lock N/A
MoveTo MoveTo
Pie Pie
PolyBezier PolyBezier
PolyBezierTo PolyBezier, according to the C++ Builder
documentation, this is functionally
identical to PolyBezier, so use
Polygon Polygon
Polyline PolyLine (note the capital letter, I hated the
lack of it in the TCanvas as it was
inconsistent with AngleArc, ArcTo,
PolyBezier, RoundRect. However for
ease in copying code the
alternative without the capital is
also offered.)
Rectangle Rectangle (note, the rectangle fits exactly inside
the space defined for it. The
boundary line, however thick (so
long as a thickness less than the
rectangle dimension is chosen),
should never exceed it. This is
not identical behaviour to
TCanvas->Rectangle() which doesn't
always go exactly up to the bottom
right corner, often leaving 1 pixel
Refresh N/A
RoundRect RoundRect
StretchDraw Draw. All bitmaps are drawn stretched.
TCanvas TPDFObject
TextExtent None, use TextHeight & TextWidth
TextHeight TextHeight() Official height of font
TextHeight(AnsiString text) Actual height of
text presented.
TextOut TextOut
TextRect Use TextOut
TextWidth TextWidth
TryLock N/A
Unlock N/A
None AddResourceImage, adds a bitmap to the pdf file so
it can be used later
None UseResourceImage, uses a bitmap image on a page
provided it has already been defined
in the pdf file using Draw() or
AddResourceImage() and the object
number was retained. Bitmaps can
then be referenced thousands of
times in a pdf file and there only
need be one copy of the bitmap.
None URL See below.
None PageLink See below.
None HotSpot See below.
None Comment Adds a user readable comment to the
Page Buffer

The following Properties and their equivalents are present:
TCanvas TPDFObject
Width PageWidth
Height PageHeight
Font->Name Font.Name
Font->Color Font.Color
Font->Style FontStyle
Brush->Style Brush.Style Note only bsSolid & bsClear are
catered for.
Brush->Color Brush.Color
Pen Pen Note only psSolid & psClear pen
styles are catered for.
Pen->Width PenWidth Note: PenWidth=0.0 is equivalent to
'Minimum Pen Width' not
Pen->Style = psClear. It will be
different on every device.
Pen->Color PenColour I am English. I spell Colour with a 'u'.
None LaunchOnExit, ShellExecute() opens the PDF File in
the PDFObject->BeforeDestruction()
event provided the PDF file was
created successfully.
Defaults to 'true'

URL: Embeds a hyperlink to a particular URL, and prints some text at the
same time. The Hyperlink actually defines a clickable area

PageLink: Allows a jump to a set page, similarly to a URL hyperlink.

HotSpot: should be a linkable part of a page, over a graphic etc. It does
seem to work erratically up to once a page. Beyond that it fails. It should
work, and I can find no reason for it not to. Probably better not to use.

Comment: Will be invisible in document, just to a reader looking at the
uncompressed PDF file as a line beginning with '% '. Comments are
automatically added to denote the start many of the other functions. This
is done to aide de-bugging of an application that uses the PDFObject to
create PDF files, and echoes the method call into the page buffer.

//creates a 3 page pdf file using many of the capabilities
//this code goes into the calling unit.
//requires two image files in .bmp format called "TestBitmap1.bmp" and
//"TestBitmap2.bmp". These images may be any size, but are ideally square.
#include "PageSizeHelp.h"
#include "PDFUnit.h"
TPDFObject *PDFObject = new TPDFObject(this);
PDFObject->Title = "Test file " + Edit1->Text;
PDFObject->Author = "I am the Author";
PDFObject->Creator = "CreatePDF Test Program";
PDFObject->PageSize = psA4;
try {
if (PDFObject->BeginDocument("c:\\temp\\test.pdf")) {
Graphics::TBitmap *Bitmap = new Graphics::TBitmap();
int objno1 = PDFObject->AddResourceImage(Bitmap);
int objno2 = PDFObject->AddResourceImage(Bitmap);
delete Bitmap;
PDFObject->PenColour = clRed;
PDFObject->PenColour = clBlack;
PDFObject->PenWidth = 0.5;
PDFObject->BrushColour = clSilver;
PDFObject->BrushColour = clAqua;
PDFObject->Brush.Style = bsClear;
coordinates Points[4];
Points[0].X = 125.0;
Points[0].Y = 125.0;
Points[1].X = 130.0;
Points[1].Y = 140.0;
Points[2].X = 140.0;
Points[2].Y = 120.0;
Points[3].X = 150.0;
Points[3].Y = 150.0;
//now to stretch the bitmap
PDFObject->Font.Color = clBlack;
PDFObject->Font.Name = "Times";
PDFObject->FontStyle << fsBold << fsItalic;
PDFObject->Font.Size = 10;
PDFObject->Font.Name = "Courier";
PDFObject->Font.Size = 20;
PDFObject->Font.Name = "Arial";
PDFObject->URL("Chestnut Pens Web Site","http://www.chestnutpens.co.uk",100.0,180.0);
PDFObject->PageLink("Page 1",1,100.0,190.0);
PDFObject->PageLink("Page 3",3,100.0,200.0);
PDFObject->PageSize = psA3;
PDFObject->PageOrientation = pdLandscape;
PDFObject->LeftMargin = 0.0;
PDFObject->TopMargin = 0.0;
PDFObject->RightMargin = 0.0;
PDFObject->BottomMargin = 0.0;
PDFObject->FontStyle << fsUnderline < PDFObject->TextOut("This is Page 3 and it's A3 Landscape, and this should be underlined & struck out",10.0,10.0);
PDFObject->PenWidth = 1.5;
PDFObject->BrushColour = clSilver;
PDFObject->Brush.Style = bsSolid;
PDFObject->BrushColour = clTeal;
PDFObject->BrushColour = clSilver;
PDFObject->BrushColour = clNavy;

} else {
String message = "Sorry, the PDF file could not be created";
String title = "Error";
__finally {
delete PDFObject;


This unit used to work with BCB4 Pro, but I've had to change the '#include's
to get it to work with RAD Studio 10.1 Berlin for C++ Builder. It is a VCL
using component and care will need to be exercised when using with
Firemonkey applications. To get it to work with any other C++ Builder
version should involve no more than changing the '#include's to match your

The unit that calls the PDF unit will need the following includes on the
source or header page:
#include "PageSizeHelp.h"
#include "PDFUnit.h"

You will need to add the following units to the project:

Some things I really do not understand (well, there are millions of others,
but only these are related to the PDFUnit):
Acrobat Reader always asks if you wish to save changes made to a PDF made
with this unit. I think this is because there is no compression in the PDF
file and Acrobat Reader wants to save it with some - I'm not really sure.
I do know, however, Acrobat Reader uses a more verbose format which does
not compress so well when stuffed into a .zip file. This does not happen
with any other reader I have used.
Once in a while, when doing a debug run hooked into RAD Studio, it can hang
and freeze RAD Studio with it. I have never had a program hang when not
attached to RAD Studio, and using the .exe file that froze when hooked up to
RAD Studio, it behaves properly when run on its own without RAD Studio.
When the IDE hangs, the whole lot needs to be killed using 'Task Manager,
End Process Tree' on RAD Studio. It may be a RAD Studio issue, as I have
seen reports of this happening in debug mode for other people with their

IN CONCLUSION (positives & negatives):
1) +Easy to use
2) +Easy to understand
3) -No compression
4) -No font embedding
5) -Fill & Line Style only solid or clear.
6) -Only use Pre-defined fonts.
7) +No Delphi code to get your head around.
8) +Files produced compress really well in zip utilities.

Beyond that, if there are questions, please contact me at
Please put something sensible down in the subject line, else it'll get
deleted automatically.

Hope this unit is of use. If you improve it, find a way to embed fonts or
compress the content, please, please share your work and/or send me a copy.

Chestnut Pens Miscellaneous Pages Home
Chestnut Pens Home