1/* $Id$ $Revision$ */
2/* vim:set shiftwidth=4 ts=8: */
3
4/*************************************************************************
5 * Copyright (c) 2011 AT&T Intellectual Property
6 * All rights reserved. This program and the accompanying materials
7 * are made available under the terms of the Eclipse Public License v1.0
8 * which accompanies this distribution, and is available at
9 * http://www.eclipse.org/legal/epl-v10.html
10 *
11 * Contributors: See CVS logs. Details at http://www.graphviz.org/
12 *************************************************************************/
13
14#include "config.h"
15
16#include <stdlib.h>
17#include <string.h>
18
19#include "const.h"
20#include "gvplugin_render.h"
21#include "agxbuf.h"
22#include "utils.h"
23#include "gvplugin_device.h"
24#include "gvio.h"
25
26#include "gvplugin_pango.h"
27
28#include <pango/pangocairo.h>
29
30typedef enum {
31 FORMAT_CAIRO,
32 FORMAT_PNG,
33 FORMAT_PS,
34 FORMAT_PDF,
35 FORMAT_SVG,
36 FORMAT_EPS,
37 } format_type;
38
39#define ARRAY_SIZE(A) (sizeof(A)/sizeof(A[0]))
40
41static double dashed[] = {6.};
42static int dashed_len = ARRAY_SIZE(dashed);
43
44static double dotted[] = {2., 6.};
45static int dotted_len = ARRAY_SIZE(dotted);
46
47#ifdef CAIRO_HAS_PS_SURFACE
48#include <cairo-ps.h>
49#endif
50
51#ifdef CAIRO_HAS_PDF_SURFACE
52#include <cairo-pdf.h>
53#endif
54
55#ifdef CAIRO_HAS_SVG_SURFACE
56#include <cairo-svg.h>
57#endif
58
59static void cairogen_polyline(GVJ_t * job, pointf * A, int n);
60
61static void cairogen_set_color(cairo_t * cr, gvcolor_t * color)
62{
63 cairo_set_source_rgba(cr, color->u.RGBA[0], color->u.RGBA[1],
64 color->u.RGBA[2], color->u.RGBA[3]);
65}
66
67static void cairogen_add_color_stop_rgba(cairo_pattern_t *pat, double stop , gvcolor_t * color)
68{
69 cairo_pattern_add_color_stop_rgba (pat, stop,color->u.RGBA[0], color->u.RGBA[1],
70 color->u.RGBA[2], color->u.RGBA[3]);
71}
72
73
74static cairo_status_t
75writer (void *closure, const unsigned char *data, unsigned int length)
76{
77 if (length == gvwrite((GVJ_t *)closure, (const char*)data, length))
78 return CAIRO_STATUS_SUCCESS;
79 return CAIRO_STATUS_WRITE_ERROR;
80}
81
82static void cairogen_begin_job(GVJ_t * job)
83{
84 if (job->external_context && job->context)
85 cairo_save((cairo_t *) job->context);
86}
87
88static void cairogen_end_job(GVJ_t * job)
89{
90 cairo_t *cr = (cairo_t *) job->context;
91
92 if (job->external_context)
93 cairo_restore(cr);
94 else {
95 cairo_destroy(cr);
96 job->context = NULL;
97 }
98}
99
100#define CAIRO_XMAX 32767
101#define CAIRO_YMAX 32767
102
103static void cairogen_begin_page(GVJ_t * job)
104{
105 cairo_t *cr = (cairo_t *) job->context;
106 cairo_surface_t *surface;
107 cairo_status_t status;
108
109 if (cr == NULL) {
110 switch (job->render.id) {
111 case FORMAT_PS:
112 case FORMAT_EPS:
113#ifdef CAIRO_HAS_PS_SURFACE
114 surface = cairo_ps_surface_create_for_stream (writer,
115 job, job->width, job->height);
116 if (job->render.id == FORMAT_EPS)
117 cairo_ps_surface_set_eps (surface, TRUE);
118#endif
119 break;
120 case FORMAT_PDF:
121#ifdef CAIRO_HAS_PDF_SURFACE
122 surface = cairo_pdf_surface_create_for_stream (writer,
123 job, job->width, job->height);
124#endif
125 break;
126 case FORMAT_SVG:
127#ifdef CAIRO_HAS_SVG_SURFACE
128 surface = cairo_svg_surface_create_for_stream (writer,
129 job, job->width, job->height);
130#endif
131 break;
132 case FORMAT_CAIRO:
133 case FORMAT_PNG:
134 default:
135 if (job->width >= CAIRO_XMAX || job->height >= CAIRO_YMAX) {
136 double scale = MIN((double)CAIRO_XMAX / job->width,
137 (double)CAIRO_YMAX / job->height);
138 job->width *= scale;
139 job->height *= scale;
140 job->scale.x *= scale;
141 job->scale.y *= scale;
142 fprintf(stderr,
143 "%s: graph is too large for cairo-renderer bitmaps. Scaling by %g to fit\n",
144 job->common->cmdname, scale);
145 }
146 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
147 job->width, job->height);
148 if (job->common->verbose)
149 fprintf(stderr,
150 "%s: allocating a %dK cairo image surface (%d x %d pixels)\n",
151 job->common->cmdname,
152 ROUND(job->width * job->height * 4 / 1024.),
153 job->width, job->height);
154 break;
155 }
156 status = cairo_surface_status(surface);
157 if (status != CAIRO_STATUS_SUCCESS) {
158 fprintf(stderr, "%s: failure to create cairo surface: %s\n",
159 job->common->cmdname,
160 cairo_status_to_string(status));
161 cairo_surface_destroy (surface);
162 return;
163 }
164 cr = cairo_create(surface);
165 cairo_surface_destroy (surface);
166 job->context = (void *) cr;
167 }
168
169 cairo_scale(cr, job->scale.x, job->scale.y);
170 cairo_rotate(cr, -job->rotation * M_PI / 180.);
171 cairo_translate(cr, job->translation.x, -job->translation.y);
172
173 cairo_rectangle(cr, job->clip.LL.x, - job->clip.LL.y,
174 job->clip.UR.x - job->clip.LL.x, - (job->clip.UR.y - job->clip.LL.y));
175 cairo_clip(cr);
176 /* cairo_set_line_join(cr, CAIRO_LINE_JOIN_ROUND); */
177}
178
179static void cairogen_end_page(GVJ_t * job)
180{
181 cairo_t *cr = (cairo_t *) job->context;
182 cairo_surface_t *surface;
183 cairo_status_t status;
184
185 switch (job->render.id) {
186
187#ifdef CAIRO_HAS_PNG_FUNCTIONS
188 case FORMAT_PNG:
189 surface = cairo_get_target(cr);
190 cairo_surface_write_to_png_stream(surface, writer, job);
191 break;
192#endif
193
194 case FORMAT_PS:
195 case FORMAT_PDF:
196 case FORMAT_SVG:
197 cairo_show_page(cr);
198 surface = cairo_surface_reference(cairo_get_target(cr));
199 cairo_surface_finish(surface);
200 status = cairo_surface_status(surface);
201 cairo_surface_destroy(surface);
202 if (status != CAIRO_STATUS_SUCCESS)
203 fprintf(stderr, "cairo: %s\n", cairo_status_to_string(status));
204 break;
205
206 case FORMAT_CAIRO:
207 default:
208 surface = cairo_get_target(cr);
209 if (cairo_image_surface_get_width(surface) == 0 || cairo_image_surface_get_height(surface) == 0) {
210 /* apparently cairo never allocates a surface if nothing was ever written to it */
211/* but suppress this error message since a zero area surface seems to happen during normal operations, particular in -Tx11
212 fprintf(stderr, "ERROR: cairo surface has zero area, this may indicate some problem during rendering shapes.\n");
213 - jce */
214 }
215 job->imagedata = (char *)(cairo_image_surface_get_data(surface));
216 break;
217 /* formatting will be done by gvdevice_format() */
218 }
219}
220
221static void cairogen_begin_anchor(GVJ_t *job, char *url, char *tooltip, char *target, char *id)
222{
223 obj_state_t *obj = job->obj;
224 cairo_t *cr = (cairo_t *) job->context;
225 double p0x, p0y, p1x, p1y;
226 char *buf;
227 size_t buf_len;
228
229 if (url && obj->url_map_p) {
230 p0x = obj->url_map_p[0].x;
231 p0y = -obj->url_map_p[0].y;
232 cairo_user_to_device (cr, &p0x, &p0y);
233 p1x = obj->url_map_p[1].x;
234 p1y = -obj->url_map_p[1].y;
235 cairo_user_to_device (cr, &p1x, &p1y);
236 buf_len = strlen(url) + 200;
237 buf = malloc(buf_len);
238 snprintf(buf, buf_len, "rect=[%f %f %f %f] uri='%s'",
239 p0x,
240 p0y,
241 p1x - p0x,
242 p1y - p0y,
243 url);
244#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 16, 0)
245 cairo_tag_begin (cr, CAIRO_TAG_LINK, buf);
246 cairo_tag_end (cr, CAIRO_TAG_LINK);
247#endif
248 free(buf);
249 }
250}
251
252static void cairogen_textspan(GVJ_t * job, pointf p, textspan_t * span)
253{
254 obj_state_t *obj = job->obj;
255 cairo_t *cr = (cairo_t *) job->context;
256 pointf A[2];
257
258 cairo_set_dash (cr, dashed, 0, 0.0); /* clear any dashing */
259 cairogen_set_color(cr, &(obj->pencolor));
260
261 switch (span->just) {
262 case 'r':
263 p.x -= span->size.x;
264 break;
265 case 'l':
266 p.x -= 0.0;
267 break;
268 case 'n':
269 default:
270 p.x -= span->size.x / 2.0;
271 break;
272 }
273 p.y += span->yoffset_centerline + span->yoffset_layout;
274
275 cairo_move_to (cr, p.x, -p.y);
276 cairo_save(cr);
277 cairo_scale(cr, POINTS_PER_INCH / FONT_DPI, POINTS_PER_INCH / FONT_DPI);
278 pango_cairo_show_layout(cr, (PangoLayout*)(span->layout));
279 cairo_restore(cr);
280
281 if ((span->font) && (span->font->flags & HTML_OL)) {
282 A[0].x = p.x;
283 A[1].x = p.x + span->size.x;
284 A[1].y = A[0].y = p.y;
285 cairogen_polyline(job, A, 2);
286 }
287}
288
289static void cairogen_set_penstyle(GVJ_t *job, cairo_t *cr)
290{
291 obj_state_t *obj = job->obj;
292
293 if (obj->pen == PEN_DASHED) {
294 cairo_set_dash (cr, dashed, dashed_len, 0.0);
295 } else if (obj->pen == PEN_DOTTED) {
296 cairo_set_dash (cr, dotted, dotted_len, 0.0);
297 } else {
298 cairo_set_dash (cr, dashed, 0, 0.0);
299 }
300 cairo_set_line_width (cr, obj->penwidth);
301
302}
303
304static void cairo_gradient_fill (cairo_t* cr, obj_state_t* obj, int filled, pointf* A, int n)
305{
306 cairo_pattern_t* pat;
307 float angle = obj->gradient_angle * M_PI / 180;
308 float r1,r2;
309 pointf G[2],c1;
310
311 if (filled == GRADIENT) {
312 get_gradient_points(A, G, n, angle, 0);
313 pat = cairo_pattern_create_linear (G[0].x,G[0].y,G[1].x,G[1].y);
314 }
315 else {
316 get_gradient_points(A, G, n, 0, 1);
317 //r1 is inner radius, r2 is outer radius
318 r1 = G[1].x; /* Set a r2/4 in get_gradient_points */
319 r2 = G[1].y;
320 if (angle == 0) {
321 c1.x = G[0].x;
322 c1.y = G[0].y;
323 }
324 else {
325 c1.x = G[0].x + r1 * cos(angle);
326 c1.y = G[0].y - r1 * sin(angle);
327 }
328 pat = cairo_pattern_create_radial(c1.x,c1.y,r1,G[0].x,G[0].y,r2);
329 }
330 if (obj->gradient_frac > 0) {
331 cairogen_add_color_stop_rgba(pat,obj->gradient_frac - 0.001,&(obj->fillcolor));
332 cairogen_add_color_stop_rgba(pat,obj->gradient_frac,&(obj->stopcolor));
333 }
334 else {
335 cairogen_add_color_stop_rgba(pat,0,&(obj->fillcolor));
336 cairogen_add_color_stop_rgba(pat,1,&(obj->stopcolor));
337 }
338 cairo_set_source (cr, pat);
339 cairo_fill_preserve (cr);
340 cairo_pattern_destroy (pat);
341}
342
343static void cairogen_ellipse(GVJ_t * job, pointf * A, int filled)
344{
345 obj_state_t *obj = job->obj;
346 cairo_t *cr = (cairo_t *) job->context;
347 cairo_matrix_t matrix;
348 double rx, ry;
349
350 cairogen_set_penstyle(job, cr);
351
352 cairo_get_matrix(cr, &matrix);
353
354 rx = A[1].x - A[0].x;
355 ry = A[1].y - A[0].y;
356
357#define RMIN 0.01
358if (rx < RMIN) rx = RMIN;
359if (ry < RMIN) ry = RMIN;
360
361 cairo_translate(cr, A[0].x, -A[0].y);
362 cairo_scale(cr, rx, ry);
363 cairo_move_to(cr, 1., 0.);
364 cairo_arc(cr, 0., 0., 1., 0., 2 * M_PI);
365
366 cairo_set_matrix(cr, &matrix);
367
368 if (filled == GRADIENT || filled == (RGRADIENT)) {
369 cairo_gradient_fill (cr, obj, filled, A, 2);
370 }
371 else if (filled) {
372 cairogen_set_color(cr, &(obj->fillcolor));
373 cairo_fill_preserve(cr);
374 }
375 cairogen_set_color(cr, &(obj->pencolor));
376 cairo_stroke(cr);
377}
378
379static void
380cairogen_polygon(GVJ_t * job, pointf * A, int n, int filled)
381{
382 obj_state_t *obj = job->obj;
383 cairo_t *cr = (cairo_t *) job->context;
384 int i;
385
386 cairogen_set_penstyle(job, cr);
387
388 cairo_move_to(cr, A[0].x, -A[0].y);
389 for (i = 1; i < n; i++)
390 cairo_line_to(cr, A[i].x, -A[i].y);
391 cairo_close_path(cr);
392 if (filled == GRADIENT || filled == (RGRADIENT)) {
393 cairo_gradient_fill (cr, obj, filled, A, n);
394 }
395 else if (filled) {
396 cairogen_set_color(cr, &(obj->fillcolor));
397 cairo_fill_preserve(cr);
398 }
399 cairogen_set_color(cr, &(obj->pencolor));
400 cairo_stroke(cr);
401}
402
403static void
404cairogen_bezier(GVJ_t * job, pointf * A, int n, int arrow_at_start,
405 int arrow_at_end, int filled)
406{
407 obj_state_t *obj = job->obj;
408 cairo_t *cr = (cairo_t *) job->context;
409 int i;
410
411 cairogen_set_penstyle(job, cr);
412
413 cairo_move_to(cr, A[0].x, -A[0].y);
414 for (i = 1; i < n; i += 3)
415 cairo_curve_to(cr, A[i].x, -A[i].y, A[i + 1].x, -A[i + 1].y,
416 A[i + 2].x, -A[i + 2].y);
417 if (filled == GRADIENT || filled == (RGRADIENT)) {
418 cairo_gradient_fill (cr, obj, filled, A, n);
419 }
420 else if (filled) {
421 cairogen_set_color(cr, &(obj->fillcolor));
422 cairo_fill_preserve(cr);
423 }
424 cairogen_set_color(cr, &(obj->pencolor));
425 cairo_stroke(cr);
426}
427
428static void
429cairogen_polyline(GVJ_t * job, pointf * A, int n)
430{
431 obj_state_t *obj = job->obj;
432 cairo_t *cr = (cairo_t *) job->context;
433 int i;
434
435 cairogen_set_penstyle(job, cr);
436
437 cairo_move_to(cr, A[0].x, -A[0].y);
438 for (i = 1; i < n; i++)
439 cairo_line_to(cr, A[i].x, -A[i].y);
440 cairogen_set_color(cr, &(obj->pencolor));
441 cairo_stroke(cr);
442}
443
444static gvrender_engine_t cairogen_engine = {
445 cairogen_begin_job,
446 cairogen_end_job,
447 0, /* cairogen_begin_graph */
448 0, /* cairogen_end_graph */
449 0, /* cairogen_begin_layer */
450 0, /* cairogen_end_layer */
451 cairogen_begin_page,
452 cairogen_end_page,
453 0, /* cairogen_begin_cluster */
454 0, /* cairogen_end_cluster */
455 0, /* cairogen_begin_nodes */
456 0, /* cairogen_end_nodes */
457 0, /* cairogen_begin_edges */
458 0, /* cairogen_end_edges */
459 0, /* cairogen_begin_node */
460 0, /* cairogen_end_node */
461 0, /* cairogen_begin_edge */
462 0, /* cairogen_end_edge */
463 cairogen_begin_anchor, /* cairogen_begin_anchor */
464 0, /* cairogen_end_anchor */
465 0, /* cairogen_begin_label */
466 0, /* cairogen_end_label */
467 cairogen_textspan,
468 0, /* cairogen_resolve_color */
469 cairogen_ellipse,
470 cairogen_polygon,
471 cairogen_bezier,
472 cairogen_polyline,
473 0, /* cairogen_comment */
474 0, /* cairogen_library_shape */
475};
476
477static gvrender_features_t render_features_cairo = {
478 GVRENDER_Y_GOES_DOWN
479 | GVRENDER_DOES_TRANSFORM, /* flags */
480 4., /* default pad - graph units */
481 0, /* knowncolors */
482 0, /* sizeof knowncolors */
483 RGBA_DOUBLE, /* color_type */
484};
485
486static gvdevice_features_t device_features_png = {
487 GVDEVICE_BINARY_FORMAT
488 | GVDEVICE_DOES_TRUECOLOR,/* flags */
489 {0.,0.}, /* default margin - points */
490 {0.,0.}, /* default page width, height - points */
491 {96.,96.}, /* typical monitor dpi */
492};
493
494static gvdevice_features_t device_features_ps = {
495 GVRENDER_NO_WHITE_BG
496 | GVDEVICE_DOES_TRUECOLOR, /* flags */
497 {36.,36.}, /* default margin - points */
498 {0.,0.}, /* default page width, height - points */
499 {72.,72.}, /* postscript 72 dpi */
500};
501
502static gvdevice_features_t device_features_eps = {
503 GVRENDER_NO_WHITE_BG
504 | GVDEVICE_DOES_TRUECOLOR, /* flags */
505 {36.,36.}, /* default margin - points */
506 {0.,0.}, /* default page width, height - points */
507 {72.,72.}, /* postscript 72 dpi */
508};
509
510static gvdevice_features_t device_features_pdf = {
511 GVDEVICE_BINARY_FORMAT
512 | GVRENDER_NO_WHITE_BG
513 | GVRENDER_DOES_MAPS
514 | GVRENDER_DOES_MAP_RECTANGLE
515 | GVDEVICE_DOES_TRUECOLOR,/* flags */
516 {36.,36.}, /* default margin - points */
517 {0.,0.}, /* default page width, height - points */
518 {72.,72.}, /* postscript 72 dpi */
519};
520
521static gvdevice_features_t device_features_svg = {
522 GVRENDER_NO_WHITE_BG
523 | GVDEVICE_DOES_TRUECOLOR, /* flags */
524 {0.,0.}, /* default margin - points */
525 {0.,0.}, /* default page width, height - points */
526 {72.,72.}, /* svg 72 dpi */
527};
528
529gvplugin_installed_t gvrender_pango_types[] = {
530 {FORMAT_CAIRO, "cairo", 10, &cairogen_engine, &render_features_cairo},
531 {0, NULL, 0, NULL, NULL}
532};
533
534gvplugin_installed_t gvdevice_pango_types[] = {
535#ifdef CAIRO_HAS_PNG_FUNCTIONS
536 {FORMAT_PNG, "png:cairo", 10, NULL, &device_features_png},
537#endif
538#ifdef CAIRO_HAS_PS_SURFACE
539 {FORMAT_PS, "ps:cairo", -10, NULL, &device_features_ps},
540 {FORMAT_EPS, "eps:cairo", -10, NULL, &device_features_eps},
541#endif
542#ifdef CAIRO_HAS_PDF_SURFACE
543 {FORMAT_PDF, "pdf:cairo", 10, NULL, &device_features_pdf},
544#endif
545#ifdef CAIRO_HAS_SVG_SURFACE
546 {FORMAT_SVG, "svg:cairo", -10, NULL, &device_features_svg},
547#endif
548 {0, NULL, 0, NULL, NULL}
549};
550