Skip to content

Instantly share code, notes, and snippets.

@hmenke
Last active February 18, 2023 13:33
Show Gist options
  • Save hmenke/9facc3fe5ede9ed46c1838a919f7376f to your computer and use it in GitHub Desktop.
Save hmenke/9facc3fe5ede9ed46c1838a919f7376f to your computer and use it in GitHub Desktop.
Render an SVG image as PDF using librsvg2 and cairo.
local ffi = require"ffi"
ffi.cdef[[
// Cairo types
typedef struct _cairo_surface cairo_surface_t;
typedef int cairo_status_t;
typedef struct _cairo cairo_t;
// Poppler types
typedef struct _PopplerPage PopplerPage;
typedef struct _PopplerDocument PopplerDocument;
// Glib types
typedef struct {
int domain;
int code;
char *message;
} GError;
// Cairo functions
cairo_surface_t * cairo_svg_surface_create(const char *filename,
double width_in_points,
double height_in_points);
cairo_status_t cairo_surface_status(cairo_surface_t *surface);
cairo_t * cairo_create(cairo_surface_t *);
cairo_status_t cairo_status(cairo_t *cr);
void cairo_show_page(cairo_t *cr);
void cairo_destroy(cairo_t *);
void cairo_surface_destroy(cairo_surface_t *);
// Poppler functions
PopplerDocument * poppler_document_new_from_file(const char *uri,
const char *password,
GError **error);
int poppler_document_get_n_pages(PopplerDocument *document);
PopplerPage * poppler_document_get_page(PopplerDocument *document,
int index);
void poppler_page_get_size(PopplerPage *page,
double *width,
double *height);
void poppler_page_render_for_printing(PopplerPage *page,
cairo_t *cairo);
// Glib functions
char * g_get_current_dir(void);
char * g_build_filename(const char *first_element, ...);
char * g_filename_to_uri(const char *filename,
const char *hostname,
GError **error);
void g_free(void *);
void g_object_unref(void *);
]]
local POPPLER = ffi.load("poppler-glib") -- libpoppler-glib-dev
local CAIRO = ffi.load("cairo") -- libcairo2-dev
local GLIB = ffi.load("gobject-2.0") -- libglib2.0-dev
local CAIRO_STATUS_SUCCESS = 0
local errmessage = error
local function page_to_svg(pdfname, svgname, idx)
-- Allocate an error object
local err = ffi.new("GError*[1]", ffi.NULL)
-- Convert relative path to absolute path
local currentdir = GLIB.g_get_current_dir()
local absolutefilename = GLIB.g_build_filename(currentdir, pdfname, ffi.NULL)
GLIB.g_free(currentdir)
-- Convert path to URI
local filename_uri = GLIB.g_filename_to_uri(absolutefilename, ffi.NULL, err)
GLIB.g_free(absolutefilename)
if filename_uri == ffi.NULL then
errmessage(ffi.string(err[0].message))
end
-- Open PDF file
local pdffile = POPPLER.poppler_document_new_from_file(filename_uri, ffi.NULL, err)
GLIB.g_free(filename_uri)
if pdffile == ffi.NULL then
errmessage(ffi.string(err[0].message))
end
-- Test page count and get page
local pagecount = POPPLER.poppler_document_get_n_pages(pdffile)
if not (idx < pagecount) then
errmessage("Page out of range (index " .. idx .. " >= " .. pagecount .. " pages)")
end
local page = POPPLER.poppler_document_get_page(pdffile, idx)
-- Get page dimensions
local width = ffi.new("double[1]")
local height = ffi.new("double[1]")
POPPLER.poppler_page_get_size(page, width, height)
-- Open Cairo surface
local surface = CAIRO.cairo_svg_surface_create(svgname, width[0], height[0]);
local status = CAIRO.cairo_surface_status(surface)
if status ~= CAIRO_STATUS_SUCCESS then
errmessage("Cairo surface error (code " .. status .. ")")
end
-- Open Cairo context
local cr = CAIRO.cairo_create(surface)
local status = CAIRO.cairo_status(cr)
if status ~= CAIRO_STATUS_SUCCESS then
errmessage("Cairo error (code " .. status .. ")")
end
-- Render PDF in Cairo context
POPPLER.poppler_page_render_for_printing(page, cr)
CAIRO.cairo_show_page(cr)
-- Clean up
if (cr ~= ffi.NULL) then CAIRO.cairo_destroy(cr) end
if (surface ~= ffi.NULL) then CAIRO.cairo_surface_destroy(surface) end
if (page ~= ffi.NULL) then GLIB.g_object_unref(page) end
if (pdffile ~= ffi.NULL) then GLIB.g_object_unref(pdffile) end
if (err[0] ~= ffi.NULL) then GLIB.g_object_unref(err[0]) end
end
page_to_svg("test.pdf", "test.svg", 0)
/*
Render an SVG image as PDF using librsvg2 and cairo.
Use with care!
Also librsvg and cairo leak all over the place.
clang -Wall -Wextra -Wpedantic \
-I/usr/include/librsvg-2.0/librsvg/ \
-I/usr/include/glib-2.0/ \
-I/usr/lib/x86_64-linux-gnu/glib-2.0/include/ \
-I/usr/include/gdk-pixbuf-2.0/ \
-I/usr/include/cairo/ \
test.c -lcairo -lrsvg-2 -lgobject-2.0
*/
#include <stdio.h>
#include <rsvg.h>
#include <cairo-pdf.h>
static cairo_status_t
rsvg_cairo_write_func(void *closure, unsigned char const *data, unsigned int length) {
if (fwrite(data, 1, length, (FILE*)closure) == length) {
return CAIRO_STATUS_SUCCESS;
}
return CAIRO_STATUS_WRITE_ERROR;
}
int main()
{
// Open file
GError *error = NULL;
RsvgHandle *rsvg = rsvg_handle_new_from_file("test.svg", &error);
if (rsvg == NULL) {
fprintf(stderr, "%s\n", error->message);
return 1;
}
// Get size of image
RsvgDimensionData dimensions;
rsvg_handle_get_dimensions(rsvg, &dimensions);
// Open cairo PDF canvas of same size
FILE *ofile = fopen("test.pdf", "wb");
cairo_surface_t *surface = cairo_pdf_surface_create_for_stream(
rsvg_cairo_write_func, ofile, dimensions.width, dimensions.height);
cairo_status_t status = cairo_surface_status(surface);
if (status != CAIRO_STATUS_SUCCESS) {
fprintf(stderr, "Cairo surface error (code %d)\n", status);
return 1;
}
// Render SVG on the canvas
cairo_t *cr = cairo_create(surface);
status = cairo_status(cr);
if (status != CAIRO_STATUS_SUCCESS) {
fprintf(stderr, "Cairo surface error (code %d)\n", status);
return 1;
}
int success = rsvg_handle_render_cairo(rsvg, cr);
if (success == FALSE) {
fprintf(stderr, "Rsvg rendering failed\n");
return 1;
}
// Clean up
if (cr) { cairo_destroy(cr); }
if (surface) { cairo_surface_destroy(surface); }
if (ofile) { fclose(ofile); }
if (rsvg) { g_object_unref(rsvg); }
if (error) { g_object_unref(error); }
}
local ffi = require("ffi")
ffi.cdef[[
// Types
typedef struct FILE_tag FILE;
typedef struct RsvgHandle_tag RsvgHandle;
typedef struct cairo_surface_tag cairo_surface_t;
typedef enum cairo_status_tag cairo_status_t;
typedef struct cairo_tag cairo_t;
typedef int (*cairo_write_func_t)(void *, const unsigned char *, unsigned int);
typedef struct {
int domain;
int code;
char *message;
} GError;
typedef struct {
int width;
int height;
double em;
double ex;
} RsvgDimensionData;
// Conversion functions
RsvgHandle * rsvg_handle_new_from_file(const char *, GError **);
void rsvg_handle_get_dimensions(RsvgHandle *, RsvgDimensionData *);
cairo_surface_t * cairo_pdf_surface_create_for_stream(cairo_write_func_t, FILE *, double, double);
cairo_status_t cairo_surface_status(cairo_surface_t *surface);
cairo_t * cairo_create(cairo_surface_t *);
cairo_status_t cairo_status(cairo_t *cr);
int rsvg_handle_render_cairo(RsvgHandle *, cairo_t *);
// Cleanup functions
void cairo_destroy(cairo_t *);
void cairo_surface_destroy(cairo_surface_t *);
void g_object_unref(void *);
]]
local lcairo = ffi.load("cairo")
local lrsvg = ffi.load("rsvg-2")
local CAIRO_STATUS_SUCCESS = 0
local CAIRO_STATUS_WRITE_ERROR = 11
local ofile = io.open("test.pdf", "wb")
function rsvg_cairo_write_func(_, data, length)
if (ofile:write(ffi.string(data, length))) then
return CAIRO_STATUS_SUCCESS
end
return CAIRO_STATUS_WRITE_ERROR
end
-- Open input file
local err = ffi.new("GError*[1]", ffi.NULL)
local rsvg = lrsvg.rsvg_handle_new_from_file("test.svg", err)
if rsvg == ffi.NULL then
error(ffi.string(err[0].message))
end
-- Get size of image
local dimensions = ffi.new("RsvgDimensionData[1]")
lrsvg.rsvg_handle_get_dimensions(rsvg, dimensions)
-- Open cairo PDF canvas of same size
local surface = lcairo.cairo_pdf_surface_create_for_stream(
rsvg_cairo_write_func, nil, dimensions[0].width, dimensions[0].height)
local status = lcairo.cairo_surface_status(surface)
if status ~= CAIRO_STATUS_SUCCESS then
error("Cairo surface error (code " .. status .. ")")
end
-- Render SVG on the canvas
local cr = lcairo.cairo_create(surface)
local status = lcairo.cairo_status(cr)
if status ~= CAIRO_STATUS_SUCCESS then
error("Cairo error (code " .. status .. ")")
end
local success = lrsvg.rsvg_handle_render_cairo(rsvg, cr)
if success == 0 then
error("Rsvg rendering failed")
end
-- Clean up
if (cr ~= ffi.NULL) then lcairo.cairo_destroy(cr) end
if (surface ~= ffi.NULL) then lcairo.cairo_surface_destroy(surface) end
if (rsvg ~= ffi.NULL) then lrsvg.g_object_unref(rsvg) end
if (err[0] ~= ffi.NULL) then lrsvg.g_object_unref(err[0]) end
ofile:close()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment