All formats

Ghostscript

LanguageC (usable from C++)
LicenseAGPL-3.0 or commercial license from Artifex
Repositorygithub.com/ArtifexSoftware/ghostpdl
DocumentationGhostscript API, EPS options, eps2write
Depends onnothing required (bundles its own zlib, libpng, libjpeg, FreeType, lcms2)
Read / writeyes / 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:

License: AGPL-3.0. Linking Ghostscript into a distributed program or a network service requires AGPL compliance or a commercial license from Artifex.

Installation#

Shell
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#

C
#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:

C
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:

C
#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:

C
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:

C
#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#