libspectre
| Language | C |
|---|---|
| License | GPL-2.0-or-later (uses Ghostscript, which is AGPL-3.0) |
| Repository | gitlab.freedesktop.org/libspectre/libspectre |
| Documentation | comments in the headers (spectre-document.h, spectre-page.h) |
| Depends on | Ghostscript (libgs) |
| Read / write | yes / no (renders pages; saves a copy as PS or PDF) |
libspectre wraps Ghostscript in a document and page API made for viewers. It parses the DSC comments to find pages, title, creator and bounding boxes without running the whole file, then asks Ghostscript to render a single page into a 32-bit RGB buffer. Evince, Okular and other viewers use it for PostScript and EPS.
It’s small and stable (bug-fix releases only; 0.2.12 is from 2023). It depends on DSC comments: a PostScript file without %%Page: comments shows up as a single page. For conversions and anything beyond viewing, call Ghostscript directly.
Installation#
# Debian / Ubuntu
sudo apt install libspectre-dev
# Fedora
sudo dnf install libspectre-devel
# macOS
brew install libspectre
Compile with pkg-config:
cc app.c $(pkg-config --cflags --libs libspectre) -o app
Basic usage: open a file and read pages and metadata#
#include <libspectre/spectre.h>
#include <stdio.h>
static const char *str(const char *s) { return s ? s : "(none)"; }
int main(int argc, char **argv) {
const char *path = argc > 1 ? argv[1] : "in.ps";
SpectreDocument *doc = spectre_document_new();
spectre_document_load(doc, path);
SpectreStatus status = spectre_document_status(doc);
if (status != SPECTRE_STATUS_SUCCESS) {
fprintf(stderr, "cannot load %s: %s\n", path, spectre_status_to_string(status));
spectre_document_free(doc);
return 1;
}
printf("pages: %u\n", spectre_document_get_n_pages(doc));
printf("title: %s\n", str(spectre_document_get_title(doc)));
printf("creator: %s\n", str(spectre_document_get_creator(doc)));
printf("created: %s\n", str(spectre_document_get_creation_date(doc)));
printf("format: %s\n", str(spectre_document_get_format(doc)));
printf("EPS: %s\n", spectre_document_is_eps(doc) ? "yes" : "no");
printf("level: %u\n", spectre_document_get_language_level(doc));
spectre_document_free(doc);
return 0;
}
How to render a PostScript page to an image#
spectre_page_render returns a buffer of 4 bytes per pixel in the same layout as cairo’s CAIRO_FORMAT_RGB24 (on little-endian machines: blue, green, red, unused). Rows can be padded: use row_length, not width * 4. This writes a PPM file:
#include <libspectre/spectre.h>
#include <stdio.h>
#include <stdlib.h>
int render_page(SpectreDocument *doc, unsigned index, double scale, const char *out) {
SpectrePage *page = spectre_document_get_page(doc, index); // 0-based
if (!page) return 1;
int w, h;
spectre_page_get_size(page, &w, &h); // in points (1/72 inch)
SpectreRenderContext *rc = spectre_render_context_new();
spectre_render_context_set_scale(rc, scale, scale); // 2.0 = 144 dpi
spectre_render_context_set_antialias_bits(rc, 4, 4); // graphics, text
unsigned char *data = NULL;
int row_length = 0;
spectre_page_render(page, rc, &data, &row_length);
SpectreStatus status = spectre_page_status(page);
spectre_render_context_free(rc);
spectre_page_free(page);
if (status != SPECTRE_STATUS_SUCCESS || !data) {
free(data);
return 1;
}
int pw = (int)(w * scale), ph = (int)(h * scale);
FILE *f = fopen(out, "wb");
fprintf(f, "P6\n%d %d\n255\n", pw, ph);
for (int y = 0; y < ph; y++) {
unsigned char *row = data + y * row_length;
for (int x = 0; x < pw; x++) {
unsigned char rgb[3] = { row[x * 4 + 2], row[x * 4 + 1], row[x * 4] };
fwrite(rgb, 1, 3, f);
}
}
fclose(f);
free(data);
return 0;
}
How to get the size of each page#
unsigned n = spectre_document_get_n_pages(doc);
for (unsigned i = 0; i < n; i++) {
SpectrePage *page = spectre_document_get_page(doc, i);
int w, h;
spectre_page_get_size(page, &w, &h);
printf("page %s: %d x %d pt\n", spectre_page_get_label(page), w, h);
spectre_page_free(page);
}
How to convert PostScript to PDF#
spectre_document_save_to_pdf(doc, "out.pdf");
if (spectre_document_status(doc) != SPECTRE_STATUS_SUCCESS)
fprintf(stderr, "export failed\n");
Tips#
- For EPS files,
spectre_document_renderandspectre_document_get_page_sizerender or measure the only page without aSpectrePage. spectre_render_context_set_use_platform_fonts(rc, 0)makes Ghostscript use only its own fonts, so output doesn’t depend on installed fonts.- Licensing: libspectre is GPL and loads AGPL Ghostscript, so a program that links it has to be GPL-compatible.