gvloadimage_gs.c 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. /*************************************************************************
  2. * Copyright (c) 2011 AT&T Intellectual Property
  3. * All rights reserved. This program and the accompanying materials
  4. * are made available under the terms of the Eclipse Public License v1.0
  5. * which accompanies this distribution, and is available at
  6. * https://www.eclipse.org/legal/epl-v10.html
  7. *
  8. * Contributors: Details at https://graphviz.org
  9. *************************************************************************/
  10. #include "config.h"
  11. #include <assert.h>
  12. #include <stdbool.h>
  13. #include <stdlib.h>
  14. #include <stdint.h>
  15. #include <sys/stat.h>
  16. #include <gvc/gvplugin_loadimage.h>
  17. #ifdef HAVE_GS
  18. #ifdef HAVE_PANGOCAIRO
  19. #include <ghostscript/iapi.h>
  20. #include <ghostscript/ierrors.h>
  21. #include <cairo/cairo.h>
  22. /**
  23. * Ensure compatibility with Ghostscipt versions newer than 9.18
  24. * while maintaining compatibility with the older versions.
  25. **/
  26. #ifndef e_VMerror
  27. #define e_VMerror gs_error_VMerror
  28. #endif
  29. #ifndef e_unregistered
  30. #define e_unregistered gs_error_unregistered
  31. #endif
  32. #ifndef e_invalidid
  33. #define e_invalidid gs_error_invalidid
  34. #endif
  35. typedef enum {
  36. FORMAT_PS_CAIRO, FORMAT_EPS_CAIRO,
  37. } format_type;
  38. typedef struct gs_s {
  39. cairo_t* cr;
  40. cairo_surface_t* surface;
  41. cairo_pattern_t* pattern;
  42. } gs_t;
  43. static void gvloadimage_gs_free(usershape_t *us)
  44. {
  45. gs_t *gs = us->data;
  46. if (gs->pattern) cairo_pattern_destroy(gs->pattern);
  47. if (gs->surface) cairo_surface_destroy(gs->surface);
  48. free(gs);
  49. }
  50. static int gs_writer(void *caller_handle, const char *str, int len)
  51. {
  52. GVJ_t *job = caller_handle;
  53. if (job->common->verbose) {
  54. assert(len >= 0);
  55. return (int)fwrite(str, 1, (size_t)len, stderr);
  56. }
  57. return len;
  58. }
  59. static void gs_error(GVJ_t * job, const char *name, const char *funstr, int err)
  60. {
  61. const char *errsrc;
  62. assert (err < 0);
  63. if (err >= e_VMerror)
  64. errsrc = "PostScript Level 1";
  65. else if (err >= e_unregistered)
  66. errsrc = "PostScript Level 2";
  67. else if (err >= e_invalidid)
  68. errsrc = "DPS error";
  69. else
  70. errsrc = "Ghostscript internal error";
  71. job->common->errorfn("%s: %s() returned: %d (%s)\n",
  72. name, funstr, err, errsrc);
  73. }
  74. static int gvloadimage_process_file(GVJ_t *job, usershape_t *us, void *instance)
  75. {
  76. int rc = 0, exit_code;
  77. if (! gvusershape_file_access(us)) {
  78. job->common->errorfn("Failure to read shape file\n");
  79. return -1;
  80. }
  81. rc = gsapi_run_file(instance, us->name, -1, &exit_code);
  82. if (rc) {
  83. gs_error(job, us->name, "gsapi_run_file", rc);
  84. }
  85. gvusershape_file_release(us);
  86. return rc;
  87. }
  88. static int gvloadimage_process_surface(GVJ_t *job, usershape_t *us, gs_t *gs, void *instance)
  89. {
  90. cairo_t *cr; /* temp cr for gs */
  91. int rc, rc2;
  92. char width_height[20], dpi[10], cairo_context[30];
  93. char *gs_args[] = {
  94. "dot", /* actual value of argv[0] doesn't matter */
  95. "-dQUIET",
  96. "-dNOPAUSE",
  97. "-sDEVICE=cairo",
  98. cairo_context,
  99. width_height,
  100. dpi,
  101. };
  102. #define GS_ARGC sizeof(gs_args)/sizeof(gs_args[0])
  103. gs->surface = cairo_surface_create_similar(
  104. cairo_get_target(gs->cr),
  105. CAIRO_CONTENT_COLOR_ALPHA,
  106. (int)(us->x + us->w),
  107. (int)(us->y + us->h));
  108. cr = cairo_create(gs->surface); /* temp context for gs */
  109. snprintf(width_height, sizeof(width_height), "-g%0.fx%0.f", us->x + us->w,
  110. us->y + us->h);
  111. snprintf(dpi, sizeof(dpi), "-r%d", us->dpi);
  112. snprintf(cairo_context, sizeof(cairo_context), "-sCairoContext=%p", cr);
  113. rc = gsapi_init_with_args(instance, GS_ARGC, gs_args);
  114. cairo_destroy(cr); /* finished with temp context */
  115. if (rc)
  116. gs_error(job, us->name, "gsapi_init_with_args", rc);
  117. else
  118. rc = gvloadimage_process_file(job, us, instance);
  119. if (rc) {
  120. cairo_surface_destroy(gs->surface);
  121. gs->surface = NULL;
  122. }
  123. rc2 = gsapi_exit(instance);
  124. if (rc2) {
  125. gs_error(job, us->name, "gsapi_exit", rc2);
  126. return rc2;
  127. }
  128. if (!rc)
  129. gs->pattern = cairo_pattern_create_for_surface (gs->surface);
  130. return rc;
  131. }
  132. static cairo_pattern_t* gvloadimage_gs_load(GVJ_t * job, usershape_t *us)
  133. {
  134. gs_t *gs = NULL;
  135. gsapi_revision_t gsapi_revision_info;
  136. void *instance;
  137. int rc;
  138. assert(job);
  139. assert(us);
  140. assert(us->name);
  141. if (us->data) {
  142. if (us->datafree == gvloadimage_gs_free
  143. && ((gs_t*)(us->data))->cr == job->context)
  144. gs = us->data; /* use cached data */
  145. else {
  146. us->datafree(us); /* free incompatible cache data */
  147. us->data = NULL;
  148. }
  149. }
  150. if (!gs) {
  151. gs = malloc(sizeof(gs_t));
  152. if (!gs) {
  153. job->common->errorfn("malloc() failure\n");
  154. return NULL;
  155. }
  156. gs->cr = job->context;
  157. gs->surface = NULL;
  158. gs->pattern = NULL;
  159. /* cache this - even if things go bad below - avoids repeats */
  160. us->data = gs;
  161. us->datafree = gvloadimage_gs_free;
  162. #define GSAPI_REVISION_REQUIRED 863
  163. rc = gsapi_revision(&gsapi_revision_info, sizeof(gsapi_revision_t));
  164. if (rc && rc < (int)sizeof(gsapi_revision_t)) {
  165. job->common->errorfn("gs revision - struct too short %d\n", rc);
  166. return NULL;
  167. }
  168. if (gsapi_revision_info.revision < GSAPI_REVISION_REQUIRED) {
  169. job->common->errorfn("gs revision - too old %d\n",
  170. gsapi_revision_info.revision);
  171. return NULL;
  172. }
  173. rc = gsapi_new_instance(&instance, job);
  174. if (rc)
  175. gs_error(job, us->name, "gsapi_new_instance", rc);
  176. else {
  177. rc = gsapi_set_stdio(instance, NULL, gs_writer, gs_writer);
  178. if (rc)
  179. gs_error(job, us->name, "gsapi_set_stdio", rc);
  180. else
  181. rc = gvloadimage_process_surface(job, us, gs, instance);
  182. gsapi_delete_instance(instance);
  183. }
  184. }
  185. return gs->pattern;
  186. }
  187. static void gvloadimage_gs_cairo(GVJ_t * job, usershape_t *us, boxf b, bool filled)
  188. {
  189. (void)filled;
  190. cairo_t *cr = job->context; // target context
  191. cairo_pattern_t *pattern = gvloadimage_gs_load(job, us);
  192. if (pattern) {
  193. cairo_save(cr);
  194. cairo_translate(cr, b.LL.x - us->x, -b.UR.y);
  195. cairo_scale(cr, (b.UR.x - b.LL.x) / us->w, (b.UR.y - b.LL.y) / us->h);
  196. cairo_set_source(cr, pattern);
  197. cairo_paint(cr);
  198. cairo_restore(cr);
  199. }
  200. }
  201. static gvloadimage_engine_t engine_cairo = {
  202. gvloadimage_gs_cairo
  203. };
  204. #endif
  205. #endif
  206. gvplugin_installed_t gvloadimage_gs_types[] = {
  207. #ifdef HAVE_GS
  208. #ifdef HAVE_PANGOCAIRO
  209. {FORMAT_PS_CAIRO, "ps:cairo", 1, &engine_cairo, NULL},
  210. {FORMAT_EPS_CAIRO, "eps:cairo", 1, &engine_cairo, NULL},
  211. #endif
  212. #endif
  213. {0, NULL, 0, NULL, NULL}
  214. };