From 4ad534428f8f346357d42a76ec6c2e54cf367b81 Mon Sep 17 00:00:00 2001 From: John Ellson Date: Sat, 29 Jun 2013 05:20:59 -0400 Subject: [PATCH] framework for a "poppler" plugin for pdf image loading --- configure.ac | 24 +++ graphviz.spec.in | 8 + plugin/Makefile.am | 2 +- plugin/poppler/Makefile.am | 47 +++++ plugin/poppler/Makefile.test | 11 ++ plugin/poppler/gvloadimage_poppler.c | 251 +++++++++++++++++++++++++++ plugin/poppler/gvplugin_poppler.c | 23 +++ plugin/poppler/pdftoimage.c | 102 +++++++++++ plugin/poppler/pdftops.c | 81 +++++++++ 9 files changed, 548 insertions(+), 1 deletion(-) create mode 100644 plugin/poppler/Makefile.am create mode 100644 plugin/poppler/Makefile.test create mode 100644 plugin/poppler/gvloadimage_poppler.c create mode 100644 plugin/poppler/gvplugin_poppler.c create mode 100644 plugin/poppler/pdftoimage.c create mode 100644 plugin/poppler/pdftops.c diff --git a/configure.ac b/configure.ac index a7e0b303e..db459b273 100644 --- a/configure.ac +++ b/configure.ac @@ -2116,6 +2116,28 @@ else fi AM_CONDITIONAL(WITH_RSVG, [test "x$use_rsvg" == "xYes"]) +dnl ----------------------------------- +dnl INCLUDES and LIBS for POPPLER + +AC_ARG_WITH(poppler, + [AS_HELP_STRING([--with-poppler=yes],[poppler-glib library])], + [], [with_poppler=yes]) + +if test "x$with_poppler" != "xyes"; then + use_poppler="No (disabled)" +else + PKG_CHECK_MODULES(POPPLER, [poppler-glib],[ + use_poppler="Yes" + AC_DEFINE_UNQUOTED(HAVE_POPPLER,1, + [Define if you have the poppler-glib library]) + AC_SUBST([POPPLER_CFLAGS]) + AC_SUBST([POPPLER_LIBS]) + ],[ + use_poppler="No (poppler-glib library not available)" + ]) +fi +AM_CONDITIONAL(WITH_POPPLER, [test "x$use_poppler" == "xYes"]) + dnl ----------------------------------- dnl INCLUDES and LIBS for GHOSTSCRIPT @@ -3128,6 +3150,7 @@ AC_CONFIG_FILES(Makefile plugin/lasi/Makefile plugin/ming/Makefile plugin/pango/Makefile + plugin/poppler/Makefile plugin/quartz/Makefile plugin/rsvg/Makefile plugin/visio/Makefile @@ -3265,6 +3288,7 @@ echo " gtk: $use_gtk" echo " lasi: $use_lasi" echo " ming: $use_ming" echo " pangocairo: $use_pangocairo" +echo " poppler: $use_poppler" echo " quartz: $use_quartz" echo " rsvg: $use_rsvg" echo " visio: $use_visio" diff --git a/graphviz.spec.in b/graphviz.spec.in index 40eb6b894..5873e8f74 100644 --- a/graphviz.spec.in +++ b/graphviz.spec.in @@ -92,6 +92,8 @@ BuildRequires: gd gd-devel %define GDK_PIXBUF 1 %define GHOSTSCRIPT 1 BuildRequires: ghostscript-devel +%define POPPLER 1 +BuildRequires: poppler-glib-devel %define _QT 1 BuildRequires: qt-devel %endif @@ -149,6 +151,8 @@ BuildRequires: libglade2-devel gtkglarea2-devel gtkglext-devel glade3-libgladeui # BuildRequires: glitz-devel %define GHOSTSCRIPT 1 BuildRequires: ghostscript-devel +%define POPPLER 1 +BuildRequires: poppler-glib-devel #define MING 1 #BuildRequires: ming ming-devel %define R_LANG 1 @@ -324,6 +328,9 @@ LD_LIBRARY_PATH=$RPM_INSTALL_PREFIX0/%{_lib} $RPM_INSTALL_PREFIX0/bin/dot -c %if 0%{?GHOSTSCRIPT} %{_libdir}/graphviz/libgvplugin_gs.* %endif +%if 0%{?POPPLER} +%{_libdir}/graphviz/libgvplugin_poppler.* +%endif %if 0%{?RSVG} %{_libdir}/graphviz/libgvplugin_rsvg.* %endif @@ -763,6 +770,7 @@ CFLAGS="$RPM_OPT_FLAGS" \ --with%{!?MING:out}-ming \ --with%{!?_QT:out}-qt \ --with%{!?PANGOCAIRO:out}-pangocairo \ + --with%{!?POPPLER:out}-poppler \ --with%{!?RSVG:out}-rsvg \ --with%{!?ORTHO:out}-ortho \ --with%{!?SFDP:out}-sfdp \ diff --git a/plugin/Makefile.am b/plugin/Makefile.am index 2369be0e7..111b3747b 100644 --- a/plugin/Makefile.am +++ b/plugin/Makefile.am @@ -1,6 +1,6 @@ # $Id$ $Revision$ ## Process this file with automake to produce Makefile.in -SUBDIRS = core devil gd gdk_pixbuf gdiplus glitz gs gtk lasi ming pango quartz rsvg visio xlib dot_layout neato_layout webp +SUBDIRS = core devil gd gdk_pixbuf gdiplus glitz gs gtk lasi ming pango quartz rsvg visio xlib dot_layout neato_layout webp poppler EXTRA_DIST = Makefile.old diff --git a/plugin/poppler/Makefile.am b/plugin/poppler/Makefile.am new file mode 100644 index 000000000..afa8ec108 --- /dev/null +++ b/plugin/poppler/Makefile.am @@ -0,0 +1,47 @@ +# $Id$ $Revision$ +## Process this file with automake to produce Makefile.in + +if WITH_CGRAPH +GRAPH = cgraph +else +GRAPH = graph +endif + +AM_CPPFLAGS = \ + -I$(top_srcdir) \ + -I$(top_srcdir)/lib/common \ + -I$(top_srcdir)/lib/pathplan \ + -I$(top_srcdir)/lib/gvc \ + -I$(top_srcdir)/lib/$(GRAPH) \ + -I$(top_srcdir)/lib/cdt \ + $(POPPLER_CFLAGS) $(PANGOCAIRO_CFLAGS) + +if WITH_POPPLER +if WITH_PANGOCAIRO +noinst_LTLIBRARIES = libgvplugin_poppler_C.la +if WITH_WIN32 +lib_LTLIBRARIES = libgvplugin_poppler.la +else +pkglib_LTLIBRARIES = libgvplugin_poppler.la +endif +endif +endif + +libgvplugin_poppler_C_la_SOURCES = \ + gvplugin_poppler.c \ + gvloadimage_poppler.c + +libgvplugin_poppler_la_LDFLAGS = -version-info $(GVPLUGIN_VERSION_INFO) +libgvplugin_poppler_la_SOURCES = $(libgvplugin_poppler_C_la_SOURCES) +libgvplugin_poppler_la_LIBADD = \ + $(top_builddir)/lib/gvc/libgvc.la $(PANGOCAIRO_LIBS) $(POPPLER_LIBS) + +if WITH_WIN32 +libgvplugin_poppler_la_LDFLAGS += -no-undefined +endif + +if WITH_DARWIN9 +libgvplugin_poppler_la_LDFLAGS += -Wl,-exported_symbol,_gvplugin_poppler_LTX_library +endif + +EXTRA_DIST = diff --git a/plugin/poppler/Makefile.test b/plugin/poppler/Makefile.test new file mode 100644 index 000000000..c652719c7 --- /dev/null +++ b/plugin/poppler/Makefile.test @@ -0,0 +1,11 @@ +CFLAGS=`pkg-config --cflags cairo poppler-glib` +LDFLAGS=`pkg-config --libs cairo poppler-glib` + +all: pdftops pdftoimage + +pdftops: pdftops.c + +pdftoimage: pdftoimage.c + +clean: + rm -f pdftops pdftoimage diff --git a/plugin/poppler/gvloadimage_poppler.c b/plugin/poppler/gvloadimage_poppler.c new file mode 100644 index 000000000..601480fd8 --- /dev/null +++ b/plugin/poppler/gvloadimage_poppler.c @@ -0,0 +1,251 @@ +/* $Id$ $Revision$ */ +/* vim:set shiftwidth=4 ts=8: */ + +/************************************************************************* + * Copyright (c) 2011 AT&T Intellectual Property + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: See CVS logs. Details at http://www.graphviz.org/ + *************************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#ifdef HAVE_STDINT_H +#include +#endif +#ifdef HAVE_INTTYPES_H +#include +#endif +#include + +#include "gvplugin_loadimage.h" + +// NOT YET +#undef HAVE_POPPLER + + +#ifdef HAVE_POPPLER +#ifdef HAVE_PANGOCAIRO +#include +#include +#include + +#ifdef WIN32 +#define NUL_FILE "nul" +#else +#define NUL_FILE "/dev/null" +#endif + +typedef enum { + FORMAT_PDF_CAIRO, +} format_type; + +typedef struct poppler_s { + cairo_t* cr; + cairo_surface_t* surface; + cairo_pattern_t* pattern; +} poppler_t; + +static void gvloadimage_poppler_free(usershape_t *us) +{ + poppler_t *poppler = (poppler_t*)us->data; + + if (poppler->pattern) cairo_pattern_destroy(poppler->pattern); + if (poppler->surface) cairo_surface_destroy(poppler->surface); + free(poppler); +} + +static int poppler_writer(void *caller_handle, const char *str, int len) +{ + GVJ_t *job = (GVJ_t*)caller_handle; + + if (job->common->verbose) + return fwrite(str, 1, len, stderr); + return len; +} + +static void poppler_error(GVJ_t * job, const char *name, const char *funstr, int err) +{ + const char *errsrc; + + assert (err < 0); + + if (err >= e_VMerror) + errsrc = "PostScript Level 1"; + else if (err >= e_unregistered) + errsrc = "PostScript Level 2"; + else if (err >= e_invalidid) + errsrc = "DPS error"; + else + errsrc = "Ghostscript internal error"; + + job->common->errorfn("%s: %s() returned: %d \"%s\" (%s)\n", + name, funstr, err, poppler_error_names[-err - 1], errsrc); +} + +static int gvloadimage_process_file(GVJ_t *job, usershape_t *us, void *instance) +{ + int rc = 0, exit_code; + + if (! gvusershape_file_access(us)) { + job->common->errorfn("Failure to read shape file\n"); + return -1; + } + rc = popplerapi_run_file(instance, us->name, -1, &exit_code); + if (rc) { + poppler_error(job, us->name, "popplerapi_run_file", rc); + } + gvusershape_file_release(us); + return rc; +} + +static int gvloadimage_process_surface(GVJ_t *job, usershape_t *us, poppler_t *poppler, void *instance) +{ + cairo_t *cr; /* temp cr for poppler */ + int rc, rc2; + char width_height[20], dpi[10], cairo_context[30]; + char *poppler_args[] = { + "dot", /* actual value of argv[0] doesn't matter */ + "-dQUIET", + "-dNOPAUSE", + "-sDEVICE=cairo", + cairo_context, + width_height, + dpi, + }; +#define POPPLER_ARGC sizeof(poppler_args)/sizeof(poppler_args[0]) + + poppler->surface = cairo_surface_create_similar( + cairo_get_target(poppler->cr), + CAIRO_CONTENT_COLOR_ALPHA, + us->x + us->w, + us->y + us->h); + + cr = cairo_create(poppler->surface); /* temp context for poppler */ + + sprintf(width_height, "-g%dx%d", us->x + us->w, us->y + us->h); + sprintf(dpi, "-r%d", us->dpi); + sprintf(cairo_context, "-sCairoContext=%p", cr); + + rc = popplerapi_init_with_args(instance, POPPLER_ARGC, poppler_args); + + cairo_destroy(cr); /* finished with temp context */ + + if (rc) + poppler_error(job, us->name, "popplerapi_init_with_args", rc); + else + rc = gvloadimage_process_file(job, us, instance); + + if (rc) { + cairo_surface_destroy(poppler->surface); + poppler->surface = NULL; + } + + rc2 = popplerapi_exit(instance); + if (rc2) { + poppler_error(job, us->name, "popplerapi_exit", rc2); + return rc2; + } + + if (!rc) + poppler->pattern = cairo_pattern_create_for_surface (poppler->surface); + + return rc; +} + +static cairo_pattern_t* gvloadimage_poppler_load(GVJ_t * job, usershape_t *us) +{ + poppler_t *poppler = NULL; + popplerapi_revision_t popplerapi_revision_info; + void *instance; + int rc; + + assert(job); + assert(us); + assert(us->name); + + if (us->data) { + if (us->datafree == gvloadimage_poppler_free + && ((poppler_t*)(us->data))->cr == (cairo_t *)job->context) + poppler = (poppler_t*)(us->data); /* use cached data */ + else { + us->datafree(us); /* free incompatible cache data */ + us->data = NULL; + } + } + if (!poppler) { + poppler = (poppler_t *)malloc(sizeof(poppler_t)); + if (!poppler) { + job->common->errorfn("malloc() failure\n"); + return NULL; + } + poppler->cr = (cairo_t *)job->context; + poppler->surface = NULL; + poppler->pattern = NULL; + + /* cache this - even if things go bad below - avoids repeats */ + us->data = (void*)poppler; + us->datafree = gvloadimage_poppler_free; + +#define POPPLERAPI_REVISION_REQUIRED 863 + rc = popplerapi_revision(&popplerapi_revision_info, sizeof(popplerapi_revision_t)); + if (rc && rc < sizeof(popplerapi_revision_t)) { + job->common->errorfn("poppler revision - struct too short %d\n", rc); + return NULL; + } + if (popplerapi_revision_info.revision < POPPLERAPI_REVISION_REQUIRED) { + job->common->errorfn("poppler revision - too old %d\n", + popplerapi_revision_info.revision); + return NULL; + } + + rc = popplerapi_new_instance(&instance, (void*)job); + if (rc) + poppler_error(job, us->name, "popplerapi_new_instance", rc); + else { + rc = popplerapi_set_stdio(instance, NULL, poppler_writer, poppler_writer); + if (rc) + poppler_error(job, us->name, "popplerapi_set_stdio", rc); + else + rc = gvloadimage_process_surface(job, us, poppler, instance); + popplerapi_delete_instance(instance); + } + } + return poppler->pattern; +} + +static void gvloadimage_poppler_cairo(GVJ_t * job, usershape_t *us, boxf b, boolean filled) +{ + cairo_t *cr = (cairo_t *) job->context; /* target context */ + cairo_pattern_t *pattern = gvloadimage_poppler_load(job, us); + + if (pattern) { + cairo_save(cr); + cairo_translate(cr, b.LL.x - us->x, -b.UR.y); + cairo_scale(cr, (b.UR.x - b.LL.x) / us->w, (b.UR.y - b.LL.y) / us->h); + cairo_set_source(cr, pattern); + cairo_paint(cr); + cairo_restore(cr); + } +} + +static gvloadimage_engine_t engine_cairo = { + gvloadimage_poppler_cairo +}; +#endif +#endif + +gvplugin_installed_t gvloadimage_poppler_types[] = { +#ifdef HAVE_POPPLER +#ifdef HAVE_PANGOCAIRO + {FORMAT_PDF_CAIRO, "pdf:cairo", 1, &engine_cairo, NULL}, +#endif +#endif + {0, NULL, 0, NULL, NULL} +}; diff --git a/plugin/poppler/gvplugin_poppler.c b/plugin/poppler/gvplugin_poppler.c new file mode 100644 index 000000000..b6f305490 --- /dev/null +++ b/plugin/poppler/gvplugin_poppler.c @@ -0,0 +1,23 @@ +/* $Id$ $Revision$ */ +/* vim:set shiftwidth=4 ts=8: */ + +/************************************************************************* + * Copyright (c) 2011 AT&T Intellectual Property + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: See CVS logs. Details at http://www.graphviz.org/ + *************************************************************************/ + +#include "gvplugin.h" + +extern gvplugin_installed_t gvloadimage_poppler_types[]; + +static gvplugin_api_t apis[] = { + {API_loadimage, gvloadimage_poppler_types}, + {(api_t)0, 0}, +}; + +gvplugin_library_t gvplugin_poppler_LTX_library = { "poppler", apis }; diff --git a/plugin/poppler/pdftoimage.c b/plugin/poppler/pdftoimage.c new file mode 100644 index 000000000..a39e24cb4 --- /dev/null +++ b/plugin/poppler/pdftoimage.c @@ -0,0 +1,102 @@ +#include +#include +#include +#include + +#define IMAGE_DPI 150 + +int main(int argc, char *argv[]) +{ + PopplerDocument *document; + PopplerPage *page; + double width, height; + GError *error; + const char *pdf_file; + const char *png_file; + gchar *absolute, *uri; + int page_num, num_pages; + cairo_surface_t *surface; + cairo_t *cr; + cairo_status_t status; + + if (argc != 4) { + printf ("Usage: pdftoimage input_file.pdf output_file.png page\n"); + return 0; + } + + pdf_file = argv[1]; + png_file = argv[2]; + page_num = atoi(argv[3]); + g_type_init (); + error = NULL; + + if (g_path_is_absolute(pdf_file)) { + absolute = g_strdup (pdf_file); + } else { + gchar *dir = g_get_current_dir (); + absolute = g_build_filename (dir, pdf_file, (gchar *) 0); + free (dir); + } + + uri = g_filename_to_uri (absolute, NULL, &error); + free (absolute); + if (uri == NULL) { + printf("%s\n", error->message); + return 1; + } + + document = poppler_document_new_from_file (uri, NULL, &error); + if (document == NULL) { + printf("%s\n", error->message); + return 1; + } + + num_pages = poppler_document_get_n_pages (document); + if (page_num < 1 || page_num > num_pages) { + printf("page must be between 1 and %d\n", num_pages); + return 1; + } + + page = poppler_document_get_page (document, page_num - 1); + if (page == NULL) { + printf("poppler fail: page not found\n"); + return 1; + } + + poppler_page_get_size (page, &width, &height); + + /* For correct rendering of PDF, the PDF is first rendered to a + * * transparent image (all alpha = 0). */ + surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, + IMAGE_DPI*width/72.0, + IMAGE_DPI*height/72.0); + cr = cairo_create (surface); + cairo_scale (cr, IMAGE_DPI/72.0, IMAGE_DPI/72.0); + cairo_save (cr); + poppler_page_render (page, cr); + cairo_restore (cr); + g_object_unref (page); + + /* Then the image is painted on top of a white "page". Instead of + * * creating a second image, painting it white, then painting the + * * PDF image over it we can use the CAIRO_OPERATOR_DEST_OVER + * * operator to achieve the same effect with the one image. */ + cairo_set_operator (cr, CAIRO_OPERATOR_DEST_OVER); + cairo_set_source_rgb (cr, 1, 1, 1); + cairo_paint (cr); + + status = cairo_status(cr); + if (status) + printf("%s\n", cairo_status_to_string (status)); + + cairo_destroy (cr); + status = cairo_surface_write_to_png (surface, png_file); + if (status) + printf("%s\n", cairo_status_to_string (status)); + + cairo_surface_destroy (surface); + + g_object_unref (document); + + return 0; +} diff --git a/plugin/poppler/pdftops.c b/plugin/poppler/pdftops.c new file mode 100644 index 000000000..035e0b8d8 --- /dev/null +++ b/plugin/poppler/pdftops.c @@ -0,0 +1,81 @@ +#include +#include +#include + +int main(int argc, char *argv[]) +{ + PopplerDocument *document; + PopplerPage *page; + double width, height; + GError *error; + const char *filename; + gchar *absolute, *uri; + int num_pages, i; + cairo_surface_t *surface; + cairo_t *cr; + cairo_status_t status; + + if (argc != 2) { + printf ("Usage: pdf2cairo input_file.pdf\n"); + return 0; + } + + filename = argv[1]; + g_type_init (); + error = NULL; + + if (g_path_is_absolute(filename)) { + absolute = g_strdup (filename); + } else { + gchar *dir = g_get_current_dir (); + absolute = g_build_filename (dir, filename, (gchar *) 0); + free (dir); + } + + uri = g_filename_to_uri (absolute, NULL, &error); + free (absolute); + if (uri == NULL) { + printf("poppler fail: %s\n", error->message); + return 1; + } + + document = poppler_document_new_from_file (uri, NULL, &error); + if (document == NULL) { + printf("poppler fail: %s\n", error->message); + return 1; + } + + num_pages = poppler_document_get_n_pages (document); + + /* Page size does not matter here as the size is changed before + * * each page */ + surface = cairo_ps_surface_create ("output.ps", 595, 842); + cr = cairo_create (surface); + for (i = 0; i < num_pages; i++) { + page = poppler_document_get_page (document, i); + if (page == NULL) { + printf("poppler fail: page not found\n"); + return 1; + } + poppler_page_get_size (page, &width, &height); + cairo_ps_surface_set_size (surface, width, height); + cairo_save (cr); + poppler_page_render_for_printing (page, cr); + cairo_restore (cr); + cairo_surface_show_page (surface); + g_object_unref (page); + } + status = cairo_status(cr); + if (status) + printf("%s\n", cairo_status_to_string (status)); + cairo_destroy (cr); + cairo_surface_finish (surface); + status = cairo_surface_status(surface); + if (status) + printf("%s\n", cairo_status_to_string (status)); + cairo_surface_destroy (surface); + + g_object_unref (document); + + return 0; +} -- 2.40.0