libgxps
| Language | C (GObject) |
|---|---|
| License | LGPL-2.1-or-later |
| Repository | gitlab.gnome.org/GNOME/libgxps |
| Documentation | libgxps project page, API comments in the headers |
| Depends on | GLib/GIO, cairo, libarchive, FreeType, libpng, libjpeg, libtiff, lcms2 (optional) |
| Read / write | yes / no (renders onto any cairo surface) |
libgxps opens XPS and OpenXPS packages, parses the page markup and draws it with cairo. Because cairo can target images, PDF, PostScript and SVG, rendering a page onto the right surface is also how you convert XPS. Evince uses it, and it ships with command-line tools: xpstopng, xpstojpeg, xpstopdf, xpstops and xpstosvg.
The API has three objects: GXPSFile (the package), GXPSDocument (a FixedDocument; most files have one) and GXPSPage. Page sizes are in XPS units of 1/96 inch. libgxps doesn’t extract text and doesn’t write XPS. It’s maintained with occasional fixes; the last release is 0.3.2 (2021).
Installation#
# Debian / Ubuntu
sudo apt install libgxps-dev
# Fedora
sudo dnf install libgxps-devel
# macOS
brew install libgxps
cc xps2png.c $(pkg-config --cflags --libs libgxps) -o xps2png
Basic usage: render the first page to PNG#
#include <libgxps/gxps.h>
#include <cairo.h>
int main(int argc, char **argv) {
GError *error = NULL;
GFile *file = g_file_new_for_path(argc > 1 ? argv[1] : "in.xps");
GXPSFile *xps = gxps_file_new(file, &error);
g_object_unref(file);
if (!xps) {
g_printerr("cannot open: %s\n", error->message);
g_error_free(error);
return 1;
}
GXPSDocument *doc = gxps_file_get_document(xps, 0, &error);
if (!doc) {
g_printerr("no document: %s\n", error->message);
g_error_free(error);
g_object_unref(xps);
return 1;
}
g_print("documents: %u, pages: %u\n",
gxps_file_get_n_documents(xps), gxps_document_get_n_pages(doc));
GXPSPage *page = gxps_document_get_page(doc, 0, &error);
if (!page) {
g_printerr("cannot load page: %s\n", error->message);
return 1;
}
gdouble w, h;
gxps_page_get_size(page, &w, &h); // 1/96 inch
double scale = 150.0 / 96.0; // render at 150 dpi
cairo_surface_t *surface = cairo_image_surface_create(
CAIRO_FORMAT_RGB24, (int)(w * scale), (int)(h * scale));
cairo_t *cr = cairo_create(surface);
cairo_set_source_rgb(cr, 1, 1, 1); // white background
cairo_paint(cr);
cairo_scale(cr, scale, scale);
if (!gxps_page_render(page, cr, &error)) {
g_printerr("render error: %s\n", error->message);
g_clear_error(&error);
}
cairo_destroy(cr);
cairo_surface_write_to_png(surface, "page1.png");
cairo_surface_destroy(surface);
g_object_unref(page);
g_object_unref(doc);
g_object_unref(xps);
return 0;
}
How to convert XPS to PDF#
Render every page onto a cairo PDF surface. PDF uses points (1/72 inch), so scale by 72/96:
#include <libgxps/gxps.h>
#include <cairo.h>
#include <cairo-pdf.h>
int xps_to_pdf(GXPSDocument *doc, const char *out) {
cairo_surface_t *pdf = cairo_pdf_surface_create(out, 612, 792);
cairo_t *cr = cairo_create(pdf);
guint n = gxps_document_get_n_pages(doc);
for (guint i = 0; i < n; i++) {
GError *error = NULL;
GXPSPage *page = gxps_document_get_page(doc, i, &error);
if (!page) {
g_printerr("page %u: %s\n", i + 1, error->message);
g_error_free(error);
continue;
}
gdouble w, h;
gxps_page_get_size(page, &w, &h);
cairo_pdf_surface_set_size(pdf, w * 72.0 / 96.0, h * 72.0 / 96.0);
cairo_save(cr);
cairo_scale(cr, 72.0 / 96.0, 72.0 / 96.0);
if (!gxps_page_render(page, cr, &error)) {
g_printerr("page %u: %s\n", i + 1, error->message);
g_error_free(error);
}
cairo_restore(cr);
cairo_show_page(cr);
g_object_unref(page);
}
cairo_destroy(cr);
cairo_surface_finish(pdf);
int ok = cairo_surface_status(pdf) == CAIRO_STATUS_SUCCESS;
cairo_surface_destroy(pdf);
return ok ? 0 : 1;
}
The same command-line tool exists: xpstopdf in.xps out.pdf.
How to read the title and author of an XPS file#
Core properties are optional; the function returns NULL when a file has none:
GError *error = NULL;
GXPSCoreProperties *props = gxps_file_get_core_properties(xps, &error);
if (props) {
const gchar *title = gxps_core_properties_get_title(props);
const gchar *creator = gxps_core_properties_get_creator(props);
g_print("title: %s\ncreator: %s\n", title ? title : "-", creator ? creator : "-");
g_object_unref(props);
} else if (error) {
g_printerr("%s\n", error->message);
g_error_free(error);
}
How to get page sizes without rendering#
guint n = gxps_document_get_n_pages(doc);
for (guint i = 0; i < n; i++) {
gdouble w, h;
if (gxps_document_get_page_size(doc, i, &w, &h))
g_print("page %u: %.0f x %.0f (%.1f x %.1f in)\n", i + 1, w, h, w / 96, h / 96);
}
gxps_document_get_page_size uses the sizes from the FixedDocument and returns FALSE if they’re missing; then load the page and call gxps_page_get_size.
Tips#
gxps_document_get_structurereturns the outline (bookmarks) when the document has aDocStructurepart.gxps_page_get_linksreturns the hyperlinks on a page, with their areas.- For text extraction use MuPDF, or read the
UnicodeStringattributes ofGlyphselements from the page XML yourself.