Introduction

This articles present a C++ interface for the Ghostscript DLL. Ghostscript[^] (GS) is a (very)
famous interpretor for the Postscript
language[^], it is used to render .ps files to a variety of image
formats and printers. For the majority of case, using the
gswin32.exe with command line parameters is good enough.
However, someone might want to integrate GS into it's application with using
gswin32.exe.

The solution could be using the C API interface to the DLL functions
given by GS, however it is not suited for the (lazy) MSVC user: there are no
available projects or workspace to use this API and you have to use
make (an old UNIX souvenir) to rebuild the source (a
nightmare).

To avoid all those problems, a C++ wrapper for the dll has been coded with
the following features:

Dynamic loading of gsdll32.dll, by searching the path or the
registry,

retrieving of all the C API methods in the dll

Intelligent class for the initialization of the Ghostscript engine,

make Ghostscript render to GDI+.

UNICODE compliant

Note that all the classes are documented with Doxygen.

Licensing

Before getting into work, you may take a look at this quote from the
Ghostscript licensing policy:

GNU Ghostscript may not be incorporated into commercial products which
forbid copying or for which customers cannot obtain source code for no more than
the cost of reproduction, although it may be distributed ("aggregated")
with commercial products; AFPL Ghostscript may not be incorporated into
commercial products at all, and may be distributed commercially only under
extremely limited circumstances. However, Ghostscript is also available for
commercial licensing, which in addition to the right to incorporate Ghostscript
into commercial products includes support, a limited warranty, high-quality
fonts, and other benefits. For more information about commercial licensing of
Ghostscript, please contact our commercial distribution partner, the only entity
legally authorized to distribute Ghostscript per se on any terms other than the
GNU or AFPL licenses

So if you plan to use this wrapper in a commercial applications, take a look
at the note on Commercial
use.

Wrapper architecture

There are 2 main classes used to build the interface to
gsdll32.dll:

CAPI is the interface to the gsdll32.dll. It
handles dll loading/unloading, method retreiving and interface,

CAPISettings is used to set all the format output options,
flags, display callbacks and other possible parameters of the ghostscript
engine. Once this object is ready, it is used to start an engine.

Here's a brief description of the other classes:

Class name

Description

CModule

Load, unload ghostscript library

CAPI

C API interface

CAPISettings

Engine initializer

CCallback

Base class for display callbacks

CGDIpCallback

GDI+ and ghostscript interface

All the classes are in the gsdll namespace. From now on, we will
consider the following:

usingnamespace gsdll;
// ghostscript setting
CAPISettings settings;

Make sure Ghostscript is installed on your machine before trying to use this
package (tested with AFPL Ghostscript 8.0).

As mentionned aboce, the user can provide GS with a custom output device
type. This type of device is used to make GS render to GDI+ Bitmap
object and will be discussed later.

Output file

GS also allows you to control where it sends its output. With a display
device this isn't necessary as the device handles presenting the output on
screen internally. Some specialized printer drivers operate this way as well,
but most devices are general and need to be directed to a particular file or
printer.

To send the output to a file, use the SetOutputFile method. For instance, to
direct all output into the file ABC.xyz, use

settings.SetOutputFile(_T("ABC.xyz"));

When printing on MS Windows systems, output normally goes directly to the
printer, PRN. When using GS as a file rasterizer (converting PostScript or PDF
to a raster image format) you will of course want to specify an appropriately
named file for the output.

GS also accepts the special filename '-' which indicates the output should be
writtent to stardard output (the command shell).

Be aware that filenames beginning with the character have a special meaning
in PostScript. If you need to specify a file name that actually begins with ,
you must prepend the os% filedevice explicitly. For example to output to a file
named abc, you need to specify

settings.SetOutputFile(_T("%%os%%%%abc"));

Please see GS and the PostScript Language and the PostScript Language
Reference Manual for more details on and filedevices. Note that on MS Windows
systems, the character also has a special meaning for the command processor
(shell), so you will have to double it.

Specifying a single output file works fine for printing and rasterizing
figures, but sometimes you want images of each page of a multi-page document.
You can tell GS to put each page of output in a series of similarly named files.
To do this place a template '%d' in the filename which GS will replace with the
page number.

You can also control the number of digits used in the file name:

settings.SetOutputFile(_T("ABC-%%d.png"));

produces

'ABC-1.png', ... , 'ABC-10.png', ...

settings.SetOutputFile(_T("ABC-%%03d.pgm"));

produces

'ABC-001.pgm', ... , 'ABC-010.pgm', ...

settings.SetOutputFile(_T("ABC_p%%04d.tiff"));

produces

'ABC_p0001.tiff', ... , 'ABC_p0510.tiff', ... , 'ABC_p5238.tiff'

Generally 03d is the best option for normal documents. As noted above, on MS
Windows systems, you will have to double the character.

Miscellanous options

There are several option to control the raster quality:

SetResolution, sets the output resolution in dpi,

SetTextAlphaBits, controls the text sub-sampling,

SetGraphicAlphaBits, controls the text sub-sampling.

If rendering to JPEG, you can set the output quality from 0 (bad quality,
good compression) to 100 (good quality, bad compression):

settings.SetDevice(DeviceJPEG);
settings.SetJPEGQuality(50);

Custom options

There are plenty of available flags to control the output of GS. Some are
implemented in the CAPISettings class, they will suit the basic
needs. However, for the advanced user, it is possible to set custom arguments
using AddCustomArgument:

settings.AddCustomArgument(_T("-sPAPERSIZE=a4"));

Starting the engine:

Once you have set all the parameters, just build a CAPI instance
using the CAPISettings object in the constructor:

Using the Ghostcript engine with CAPI

Once your CAPI instance has been built successfully, you have to
feed it with Postscript. You can do that in numerous ways:

Sending an entire file:

gsapi.RunFile(_T("thefiletorender.ps"));

Sending a string

gsapi.RunString(_T("1 2 add == flush\n"));

Note that you can specify
the number of characters to be read.

Sending numerous strings:

// prepare for receiving strings
gsapi.RunStringBegin();
// sending string, the method RunStringContinue can be called several times
gs.RunStringContinue(_T("more ps code"));
// stop and render
gsapi.RunStringEnd();

When rasterizing, GS produces numerous messages (notification or error
messages). These messages are redirected to 2 string streams (static members of
CAPI):

// get the message output buffer of GS
TRACE(gsapi.GetOutBuffer());
// get the message error buffer of GS
TRACE(gsapi.GetErrBuffer());

Implementing your own ouput device with CCallback

As mentioned above, it is possible to implement your own output device. This
is made by furnishing GS with a series of function callbacks. In order to help
the user, these callbacks have been encapsulated into a virtual base class:
CCallback .

In order to write your own device, you must derive a class from
CCallback and implement all the following virtual functions:

int Open(...)

New device has been opened.

int PreClose(...)

Device is about to be closed.

int Close(...)

Device has been closed.

int PreSize(...)

Device is about to be resized.

int Size(...)

Device has been resized. New pointer to raster returned argument.

int Sync(..)

flushpage

int Page(...)

Showpage If you want to pause on showpage, then don't return immediately.

I will not go more into details about the use of those functions. For the
interrested user, the creation of a custom output device is illustrated with
CGDIpCallback which implements the outputing to GDI+
Bitmap.

Crossing the bridge between GS and GDI+: CGDIpCallback

This class implements the necessary callback in order to have a output device
to GDI+.

When the callback Size is called, the address of the raster
buffer is stored and a Bitmap is created with the appropriate
dimensions.

After that, when Page is called, the raster is blitted into the
Bitmap. Each time Page is called, the bitmap is stored
in a bitmap list for later use. These bitmaps can be accessed using
GetBitmapContainer:

References

Update history

2-11-2002,

Added UNICODE support

Better DLL search in CModule. Thanks to Russel Lang from Ghostgum.com

Added licensing policy

2-11-2002, initial release.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

Share

About the Author

Jonathan de Halleux is Civil Engineer in Applied Mathematics. He finished his PhD in 2004 in the rainy country of Belgium. After 2 years in the Common Language Runtime (i.e. .net), he is now working at Microsoft Research on Pex (http://research.microsoft.com/pex).

AddCustomArgument does not work in unicode - unicode conversions go out of scope
CustomArgumentContainer type should be changed to
typedef std::list CustomArgumentContainer;
and AddCustomArgument should be changed to
void CAPISettings::AddCustomArgument( LPCTSTR szArg)
{
USES_CONVERSION;
if (szArg && _tcslen(szArg)>0)
m_lCustomArgs.push_back(T2CA(szArg));
};

Well, I had to abandon that idea due to licensing constraints. We're stuck with shelling out to Ghostscript at this point, because if we compiled with the gs dlls we'd break the GPL license (I work on a commercial product).

Unfortunately, I haven't had enough non-work time to develop something like this for use by the community.

In general, a C# wrapper to the GS C DLLs (pardon the alphbet soup), would be trivial to write, however would involve a lot of time-consuming typing. Once that wrapper was in place, you could work on writing a top level library that used that wrapper, but did things in a more "C# way".

A good start would be to take this project and just create a minimal syntactic port to C#.

What would be cool is to see someone port the GS code to Java or C# directly... But that would be quite an undertaking.

I'd be glad to assist in whatever way I can if you decide to get this project going.

I've sinced moved to a new job where we are not using Ghostscript, so this has sort of fallen off my radar.. The good news is that my new job allows me a LOT more free time to work on these kind of things (ie, non-work coding projects).

So, I might be able to start on this project now.

I would say the first step would be to port this API from C++ to C#. That's not a trivial task. A quick look over the source puts it at about 2200 lines of C++ code (.cpp and .h files).

Do you have any experience porting c++ to c#?

I have minimal experience.

Another option would be, instead of porting it, to simple compile this c++ code, and wrap up the full API in C#... That would be faster, and easier, but it means that the code that actually does the work would all be in c++, meaning, to update or change it (for example if there are GS c api changes in new versions), we would need to update it in c++, rather than C#. It also means yeat another layer of wrapping, which means yet another layer of debugging to go through.

The C++ code seems straightforward and it's well documented. I think a port would be easy enough just working from what is presented in this article.

I am also interested in this project. I would be more interested in one that replaces GS entirely to bypass the commercial license issues & so I can better master interpreting PostScript. But this would be a good start! I'll check out your project on Google.

If you continue to do the same things you always did,don't be surprised if you get the same results you always got.

I'll try to have a minimally functional version of the API wrapper up there in the next couple of days.

I does occur to me that having the API established in C# would allow someone to start re-writing the backend slowly. However, the Ghostscript API is very high-level, and still operates like a "blackbox".. Meaning, you pass the postscript source code in, and it parses it, executes it, and when it figures out what to display, it draws to whatever device you've configured, which could optionally be a custom device that you've written that it calls back toe (which is how the GDI+ integration will occur).

This is a fairly minimal API. I would like to see it expanded to expose the parser, the execution and operand stack, the core static functions, actual vector information about the drawing commands instead of just raster drawing... etc. But that would mean re-writing ghostscript completely... which is probably beyond my weekend warrior schedule.

I would love to see it be a completely free product for commerical use or otherwise. I would also love to have that kind of functionality exposed so that a decent IDE could be built against it.

So -- Here you go... For all the world to see.. My offer:

If anyone wants to pay me to re-write Ghostscript in C# as a completely free, open source product, and develop a Postscript IDE, I'll definatly entertain the idea of leaving my current job if the salary is comparable to what I'm making now and I don't have to move from Portland, OR.

I have been working on trying to get PDF to PS (or for that matter PDF to EPS) via GhostScript or anything else within the .NET context. mAny go the other way and most things that call themselves PS are image files - not old school "placed text" (as in what was produced by Illustrator v9 when you saved a imported PDF as PS). I have tried working with gs pdf2ps but can not seem to get anything to come out right (either an unkown device or it comes out as an embedded image - which does not work well with prepress).

It is late so I am hoping this makes sense. Is what I am looking for inside of what you have here?

This error is driving me up a wall. It's happening when the m_iLastStatus=m_init_with_args(m_instance, argc,argv); code returns -15 so the CAPI::IsInvalid() function returns false. There is precious little information beyond that to troubleshoot with. Anyone else encounter this error or knows what a return value of -15 indicates? Please let me know

There appears to be some kind of memory corruption. ArgV, one of the parameters in m_init_with_args is an array that is populated by char** argv=settings.GetArgv();.
This function populates an array called m_argv which it then returns. By troubleshooting this function, you can watch the elements of m_argv populate fine. What gets returned into argv however is mostly gibberish. If you hard code the elements of argv, the program proceeds normally.

I am having a problem getting this simple wrapper program to run correctly. I always get "Error initializing GS API". All I did was move stdafx.h and stdafx.cpp into the ghostwrapper_src directory, and edited stdafx.h to #include "GDIpHelper.h" instead of "../GDIpHelper.h". Here is my test.cpp code:

Congratulations for this Wrapper, it's a really good job!
But i've got a little problem;
I try to render a pdf file to a GDI+ bitmap,
everything is ok with .ps but with pdf, my bitmap is null and so causes an exception.

I am using automation to open existing microsoft office document (MS word ,MS excel,MS powerpoint) in document view application.
I want to print this document to a file instead of a real printer so that i can further read and process the file output.