Last active
February 18, 2023 13:33
-
-
Save hmenke/9facc3fe5ede9ed46c1838a919f7376f to your computer and use it in GitHub Desktop.
Render an SVG image as PDF using librsvg2 and cairo.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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); } | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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