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 | |
30 | typedef 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 | |
41 | static double dashed[] = {6.}; |
42 | static int dashed_len = ARRAY_SIZE(dashed); |
43 | |
44 | static double dotted[] = {2., 6.}; |
45 | static 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 | |
59 | static void cairogen_polyline(GVJ_t * job, pointf * A, int n); |
60 | |
61 | static 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 | |
67 | static 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 | |
74 | static cairo_status_t |
75 | writer (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 | |
82 | static void cairogen_begin_job(GVJ_t * job) |
83 | { |
84 | if (job->external_context && job->context) |
85 | cairo_save((cairo_t *) job->context); |
86 | } |
87 | |
88 | static 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 | |
103 | static 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 | |
179 | static 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 | |
221 | static 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 | |
252 | static 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 | |
289 | static 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 | |
304 | static 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 | |
343 | static 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 |
358 | if (rx < RMIN) rx = RMIN; |
359 | if (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 | |
379 | static void |
380 | cairogen_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 | |
403 | static void |
404 | cairogen_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 | |
428 | static void |
429 | cairogen_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 | |
444 | static 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 | |
477 | static 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 | |
486 | static 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 | |
494 | static 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 | |
502 | static 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 | |
510 | static 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 | |
521 | static 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 | |
529 | gvplugin_installed_t gvrender_pango_types[] = { |
530 | {FORMAT_CAIRO, "cairo" , 10, &cairogen_engine, &render_features_cairo}, |
531 | {0, NULL, 0, NULL, NULL} |
532 | }; |
533 | |
534 | gvplugin_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 | |