Using PDFlib with Caché

This document describes how to use the PDF generation library PDFlib (see www.pdflib.com) from Caché (version 5, non-unicode) ObjectScript, both on Windows and Linux. Interface classes are provided, and, to use the library version, C source code for a stub shared object in the special format that Caché requires. Precompiled versions of the stub library for Windows XP and Linux are also supplied.

PDFlib

PDFlib is a commercial product for programmatically creating PDF files. A stripped-down version, PDFlib Lite, is available (in source code form) which can be used free of charge for open-source projects and private use; see its license for more details. A trial version is also available; it has all features of the full product, but displays a demo stamp across all generated pages.

PDFlib comes with a host of language bindings. Two of those can be used by Caché: the COM binding (available on Windows, and only with the full version) and the C library binding. The latter has to be accessed using a DLL compiled especially for Caché, which passes the Caché $ZF calls to the PDFlib library.

Note that I am in no way affiliated with PDFlib GmbH, the product's vendor, nor does this page imply a comment on the quality of the product. I have not created any significant PDF documents with it myself; the library's performance appears to be really good, though.

The COM binding

On Windows, the full product comes with a COM interface which can be accessed using the %Activate classes. After installation, just run the Activate Wizard in Studio, select the PDFlib_com object, and optionally change the package name. When finished the wizard will have created two classes, named (by default) Activate.PDFlibucom.PDF and Activate.PDFlibucom.IPDF. The first is the class to use, the second is merely an interface class used by the first.

While the generated classes work, and offer access to most features of the COM interface, they have a disadvantage that becomes apparent when looking at the method names. PDFlib uses lowercase names for all its methods, and uses underscores to improve readability; e.g., begin_page_ext. As the underscore can't be used in names in Caché, the Activate Wizard replaces them with a lowercase "u"; so, the name of the previous example becomes beginupageuext. Rather difficult to read and easy to mistype.

The generated class also exposes all methods indiscrimitately, even those that are marked deprecated in the PDFlib documentation. For new projects, this increases the size of the class needlessly.

To solve these issues, I have created an alternative interface class for PDFlib V6.0.0p1. It is based on (i.e., generated from) the Activate- generated classes, but doesn't use them. The generator code parses the C header file pdflib.h (which can be found in the Lite distribution) to determine the original method names, and (from the comments in that file) which methods are deprecated. Basic differences are:

Other than the name, the method signature and code are exactly the same as those in the Activate-generated classes. This class can be downloaded here. It can be used freely; for license details see below.

Note that I have not extensively used PDFlib, so I don't know if all methods actually work. I did notice, though, that on my system the GetBuffer method (used to retrieve the in-memory PDF when not creating it to a file) returns a string consisting solely of question marks. I suspect this is because that method returns an UTF-16 string which Caché apparently can't convert back, but I am not certain of this.

The C library binding

PDFlib can also be used in the form of a function library, using the C programming language's calling conventions. Below is described how to interface to this library, both on Linux and Windows.

The stub library

Caché can only access (with the $ZF(...) functions) external libraries (DLLs or shared objects as they're referred to on Linux) that are especially built for it. This is because it needs a special table describing the functions present in the library, and their parameters and returntype. Additionally, not all argument types are supported directly; e.g., floating point numbers can't be passed directly from Caché to C, only through pointers. Therefore, to access PDFlib's library, a mediator or stub library is required that contains the required table, converts parameters, and calls the PDFlib functions.

The stub library source code presented here is generated from PDFlib's main header file pdflib.h, which declares all functions present in the library. Argument conversion is taken care of, and the table required for Caché is built automatically. Method names are exposed in camel case, as in the COM interface class.

The code is written in ANSI C, and should compile cleanly on any ANSI C compiler.

Compiling on Linux

To compile the stub, make sure you have PDFlib installed, and then do the following:

gcc -Wall -pedantic -O -c pdflib_stub.c
ld -shared -lpdf -rpath "." -o pdflib_stub.so pdflib_stub.o
rm pdflib_stub.o

On my (Debian Woody) system, the stub source file compiles cleanly. Assuming the above was successful, you will now have the stub library in the file pdflib_stub.so. This library can be accessed using the $ZF() functions, or the interface class described below.

The C source code for the stub library, and a compiled version, can be found here. It can be used freely; for license details see below.

Compiling on Windows

I have successfully compiled the stub library using the free Microsoft C/C++ compiler (Visual C++ Toolkit 2003), using the command below. Note that everything should be on one line.

cl /nologo /TC /W3 /O2 /LD /D "WIN32" /D "_WINDOWS" /D "PDFLIB_EXPORTS"
  pdflib_stub.c /link /DEFAULTLIB:".\pdflib.lib" /def:pdflib_stub.def
  /OUT:"pdflib_stub.dll"

I have not yet been able to compile the Lite version of PDFlib on Windows. I have therefore compiled against the trial DLL version. I have copied pdflib.dll to the Windows system32 directory, and pdflib.lib to the directory containing the stub source file. The .def file should contain:

LIBRARY           pdflib_stub
EXPORTS
    GetZFTable    @1

After compilation, the stub library can optionally be copied to the Windows system32 directory as well, but as Caché does not automatically search there, it may well live in a directory outside the system's search path.

The C source code for the stub library (with Windows line endings), and a compiled version, can be found here. It can be used freely; for license details see below.

Overriding stub library functions

Overriding individual generated methods may be necessary, if the generated code proves insufficient or additional functionality is required. As it is advisable to keep changes to generated source separate from that source, they are expected in an external file, pdflib_stub_overrides.c, which is normally empty, but which can be used to place override functions in. Functions in the generated source code (which resides in pdflib_stub.c) are embedded between #ifndef statements; defining PDFLIB_STUB_<MethodName> causes the generated method not to be compiled. So, to override e.g. the function AddNameddest, place the following in pdflib_stub_overrides.c:

#define PDFLIB_STUB_AddNameddest
int AddNameddest(PDF *p, const char *name, const char *optlist)
{
  /* Your code here */
}

Accessing the library

The compiled stub library can be used through the $ZF(...) functions. See the chapter Calling out of Caché of the Caché documentation for more information and examples. Although it is probably much more convenient to use the interface class described below, if direct access of the library is desirable, the interface class's code can serve as an example.

Note that Caché does not use the operating system's search path to locate a library, i.e. in most cases either a change directory ($ZUtil(168)) to the library's directory, or an explicit path to the library is required for the $ZF(-3) or $ZF(-4) calls to be successfull.

The interface class

A complication of using the library version of PDFlib is that the document state must be passed around as a pointer to a special structure internal to PDFlib, i.e. there is no object interface. It would be convenient if using the PDFlib library was as easy as using the COM interface under Windows is. In order to make this possible, I've created a class that does all the administrative stuff, i.e. loading the library, managing the pointer, and calling the library functions, under the hood, and provides an interface almost identical to that of the COM version.

This class has the added advantage that it provides a GetBuffer function that works even on 8-bit Caché systems. At least on my system, the COM class's GetBuffer function doesn't work. This means that any PDF generated with this class must be stored to a (temporary) file, even if it's to be sent to e.g. a browser. The library class's GetBuffer returns a %GlobalBinaryStream object with the buffer content. To work around Caché string length limitations, the buffer contents is retrieved in chunks.

The interface class attempts to load the PDFlib stub DLL in the %OnNew method. If no parameter is provided, it looks for pdflib_stub.dll on Windows, and pdflib_stub.so on other operating systems. On Windows, the code explicitly traverses the operating system's search path to locate the DLL. On Linux, the file must either be present in the current directory, or an explicit path must be specified as the %OnNew method's parameter, e.g.:

  Set PDF=##class(PDF.Lib).%New("/home/gklein/pdflib/pdflib_stub.so")

If there is an error loading the library, no object will be returned and %statuscode will contain an error code.

The interface class can be found here. It can be used freely; for license details see below.

Usage

Example

The code below creates an example PDF document similar to those described in the PDFlib manual. In this case it uses the PDF.Lib class, but the exact same code works with the PDF.COM class.

  Set PDF=##class(PDF.Lib).%New()
  If '$IsObject(PDF) Write "Can't create PDF interface class.",! Quit

  Set sc=PDF.BeginDocument("test.pdf")
  If sc'=1 Write "Can't create document: "_PDF.GetErrmsg(),! Quit

  Do PDF.SetInfo("Creator","COS")
  Do PDF.SetInfo("Author","Gertjan Klein")
  Do PDF.SetInfo("Title","Hello, world (COS)!")

  Do PDF.BeginPageExt(595,842)

  Set Font=PDF.LoadFont("Helvetica-Bold","winansi")
  Do PDF.Setfont(Font,24)
  Do PDF.SetTextPos(50,700)

  Do PDF.Show("Hello, world!")
  Do PDF.ContinueText("(created with COS)")

  Do PDF.EndPageExt()
  Do PDF.EndDocument()

  Kill PDF
  Quit

Serving PDF from CSP

For those not to familiar with the techniques involved in serving non-HTML content from a CSP application, I have created a separate document that describes how PDF can be served, both as file and in-memory generated content.

Download

The following files can be downloaded here:

PDF.COM.xml The class interface for the COM language binding described above.
PDF.Lib.xml The class interface for the C language binding described above.
pdflib_stub.zip The C source code for the stub library used to access the library version of PDFlib on Windows. The zip file also contains a precompiled stub dll, compiled with the free Microsoft C/C++ compiler (Visual C++ Toolkit 2003) on Windows XP SP2.
pdflib_stub.tgz The C source code for the stub library used to access the library version of PDFlib on Linux. The archive file also contains a precompiled stub shared object, compiled with gcc 2.95 against libc6.
PDF.Server.xml A sample Caché class for serving PDF content from a CSP application, described here.

License

All code presented on this page is copyrighted © 2004 by Gertjan Klein, and currently available under a Creative Commons License. In short, this license requires you to attribute me if you decide to use the code.