Ghostscript
| Language | C (usable from C++) |
|---|---|
| License | AGPL-3.0 or commercial license from Artifex |
| Repository | github.com/ArtifexSoftware/ghostpdl |
| Documentation | Ghostscript API, EPS options, eps2write |
| Depends on | nothing required (bundles its own zlib, libpng, libjpeg, FreeType, lcms2) |
| Read / write | yes / yes (renders EPS with or without a binary preview header; writes EPS with eps2write) |
Ghostscript is the PostScript interpreter used by nearly every program that renders EPS. Through its C API (libgs) you run it in-process with the same arguments as the gs command. It recognizes EPS files, including files with a binary TIFF/WMF preview header, and the EPS switches control how the graphic is placed:
-dEPSCrop: make the output page the size of the%%BoundingBox. Use this for EPS to PNG or PDF.-dEPSFitPage: scale the graphic to fit the output page.-dNOEPS: treat the file as plain PostScript (for files with broken DSC comments).
License: AGPL-3.0. Linking Ghostscript into a distributed program or a network service requires AGPL compliance or a commercial license from Artifex.
Installation#
sudo apt install libgs-dev # Debian / Ubuntu
sudo dnf install libgs-devel # Fedora
brew install ghostscript # macOS
On Windows use gsdll64.dll from the Ghostscript installer. Link with -lgs.
Basic usage: render EPS to PNG#
#include <ghostscript/iapi.h>
#include <ghostscript/ierrors.h>
#include <stdio.h>
static int run_gs(int argc, char **argv) {
void *gs = NULL;
int code = gsapi_new_instance(&gs, NULL);
if (code < 0) return code;
gsapi_set_arg_encoding(gs, GS_ARG_ENCODING_UTF8);
code = gsapi_init_with_args(gs, argc, argv);
int code1 = gsapi_exit(gs);
if (code == 0 || code == gs_error_Quit) code = code1;
gsapi_delete_instance(gs);
return code == gs_error_Quit ? 0 : code;
}
int main(void) {
char *args[] = {
"gs", "-dNOPAUSE", "-dBATCH", "-dSAFER", "-dQUIET",
"-dEPSCrop", // page = bounding box
"-sDEVICE=pngalpha", // RGBA with transparent background
"-r300", // 300 dpi
"-dTextAlphaBits=4", "-dGraphicsAlphaBits=4",
"-sOutputFile=out.png",
"in.eps",
};
int code = run_gs(sizeof(args) / sizeof(args[0]), args);
if (code != 0) {
fprintf(stderr, "Ghostscript failed: %d\n", code);
return 1;
}
return 0;
}
How to convert EPS to PDF#
The same run_gs with the pdfwrite device keeps the vector graphics:
char *args[] = {
"gs", "-dNOPAUSE", "-dBATCH", "-dSAFER", "-dQUIET",
"-dEPSCrop", "-sDEVICE=pdfwrite", "-sOutputFile=out.pdf", "in.eps",
};
How to fix a wrong bounding box#
The bbox device prints the box of what is actually drawn to stderr. Capture it with gsapi_set_stdio and parse the %%HiResBoundingBox line:
#include <ghostscript/iapi.h>
#include <ghostscript/ierrors.h>
#include <stdio.h>
#include <string.h>
static char captured[4096];
static size_t captured_len;
static int gs_in(void *h, char *buf, int len) { return 0; }
static int gs_out(void *h, const char *str, int len) { return len; }
static int gs_err(void *h, const char *str, int len) {
if (captured_len + len < sizeof(captured)) {
memcpy(captured + captured_len, str, len);
captured_len += len;
captured[captured_len] = 0;
}
return len;
}
int real_bbox(const char *path, double box[4]) {
char file[1024];
snprintf(file, sizeof(file), "%s", path);
char *args[] = { "gs", "-dNOPAUSE", "-dBATCH", "-dSAFER", "-dQUIET",
"-sDEVICE=bbox", file };
void *gs = NULL;
if (gsapi_new_instance(&gs, NULL) < 0) return -1;
gsapi_set_stdio(gs, gs_in, gs_out, gs_err);
captured_len = 0;
int code = gsapi_init_with_args(gs, 7, args);
gsapi_exit(gs);
gsapi_delete_instance(gs);
if (code < 0 && code != gs_error_Quit) return -1;
const char *p = strstr(captured, "%%HiResBoundingBox:");
if (!p) return -1;
return sscanf(p, "%%%%HiResBoundingBox: %lf %lf %lf %lf",
&box[0], &box[1], &box[2], &box[3]) == 4 ? 0 : -1;
}
Write the result back into the file’s %%BoundingBox line (rounded outwards to integers), or let eps2write rewrite the file.
How to convert a PDF page to EPS#
eps2write writes clean Level 2 EPS with a correct bounding box:
char *args[] = {
"gs", "-dNOPAUSE", "-dBATCH", "-dSAFER", "-dQUIET",
"-sDEVICE=eps2write",
"-dFirstPage=1", "-dLastPage=1", // EPS holds one page
"-sOutputFile=out.eps",
"in.pdf",
};
How to extract the PostScript from an EPS with a binary preview#
Ghostscript handles the header itself, but other tools need plain PostScript. The header is 30 bytes, little-endian:
#include <stdio.h>
#include <stdint.h>
static uint32_t le32(const unsigned char *p) {
return p[0] | (p[1] << 8) | (p[2] << 16) | ((uint32_t)p[3] << 24);
}
int strip_preview(const char *in, const char *out) {
FILE *f = fopen(in, "rb");
if (!f) return -1;
unsigned char h[30];
if (fread(h, 1, 30, f) != 30 || le32(h) != 0xC6D3D0C5) { // bytes C5 D0 D3 C6
fclose(f);
return -1; // not a binary EPS
}
uint32_t ps_off = le32(h + 4), ps_len = le32(h + 8);
FILE *o = fopen(out, "wb");
fseek(f, ps_off, SEEK_SET);
char buf[8192];
while (ps_len > 0) {
size_t n = fread(buf, 1, ps_len < sizeof(buf) ? ps_len : sizeof(buf), f);
if (n == 0) break;
fwrite(buf, 1, n, o);
ps_len -= (uint32_t)n;
}
fclose(o);
fclose(f);
return 0;
}
Tips#
- Without
-dEPSCropthe graphic lands on a full Letter/A4 page at its original position. -dEPSFitPage -sPAPERSIZE=a4scales a small EPS up to fill an A4 page.- Keep
-dSAFER(the default since 9.50): an EPS file is a program.