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 <stddef.h> |
18 | #include <stdint.h> |
19 | #include <string.h> |
20 | #include <fcntl.h> |
21 | |
22 | #include "gvplugin_render.h" |
23 | #include "gvplugin_device.h" |
24 | #include "gvcint.h" /* for gvc->g for agget */ |
25 | #include "gd.h" |
26 | |
27 | #ifndef INT32_MAX |
28 | #define INT32_MAX (2147483647) |
29 | #endif |
30 | #ifndef UINT32_MAX |
31 | #define UINT32_MAX (4294967295U) |
32 | #endif |
33 | |
34 | |
35 | typedef enum { |
36 | FORMAT_GIF, |
37 | FORMAT_JPEG, |
38 | FORMAT_PNG, |
39 | FORMAT_WBMP, |
40 | FORMAT_GD, |
41 | FORMAT_GD2, |
42 | FORMAT_XBM, |
43 | } format_type; |
44 | |
45 | extern boolean mapbool(char *); |
46 | extern pointf Bezier(pointf * V, int degree, double t, pointf * Left, pointf * Right); |
47 | |
48 | #define BEZIERSUBDIVISION 10 |
49 | |
50 | static void gdgen_resolve_color(GVJ_t * job, gvcolor_t * color) |
51 | { |
52 | gdImagePtr im = (gdImagePtr) job->context; |
53 | int alpha; |
54 | |
55 | if (!im) |
56 | return; |
57 | |
58 | /* convert alpha (normally an "opacity" value) to gd's "transparency" */ |
59 | alpha = (255 - color->u.rgba[3]) * gdAlphaMax / 255; |
60 | |
61 | if(alpha == gdAlphaMax) |
62 | color->u.index = gdImageGetTransparent(im); |
63 | else |
64 | color->u.index = gdImageColorResolveAlpha(im, |
65 | color->u.rgba[0], |
66 | color->u.rgba[1], |
67 | color->u.rgba[2], |
68 | alpha); |
69 | color->type = COLOR_INDEX; |
70 | } |
71 | |
72 | static int white, black, transparent, basecolor; |
73 | |
74 | #define GD_XYMAX INT32_MAX |
75 | |
76 | static void gdgen_begin_page(GVJ_t * job) |
77 | { |
78 | char *bgcolor_str = NULL, *truecolor_str = NULL; |
79 | boolean truecolor_p = FALSE; /* try to use cheaper paletted mode */ |
80 | boolean bg_transparent_p = FALSE; |
81 | gdImagePtr im = NULL; |
82 | |
83 | truecolor_str = agget((graph_t*)(job->gvc->g), "truecolor" ); /* allow user to force truecolor */ |
84 | bgcolor_str = agget((graph_t*)(job->gvc->g), "bgcolor" ); |
85 | |
86 | if (truecolor_str && truecolor_str[0]) |
87 | truecolor_p = mapbool(truecolor_str); |
88 | |
89 | if (bgcolor_str && strcmp(bgcolor_str, "transparent" ) == 0) { |
90 | bg_transparent_p = TRUE; |
91 | if (job->render.features->flags & GVDEVICE_DOES_TRUECOLOR) |
92 | truecolor_p = TRUE; /* force truecolor */ |
93 | } |
94 | |
95 | if (GD_has_images(job->gvc->g)) |
96 | truecolor_p = TRUE; /* force truecolor */ |
97 | |
98 | if (job->external_context) { |
99 | if (job->common->verbose) |
100 | fprintf(stderr, "%s: using existing GD image\n" , job->common->cmdname); |
101 | im = (gdImagePtr) (job->context); |
102 | } else { |
103 | if (job->width * job->height >= GD_XYMAX) { |
104 | double scale = sqrt(GD_XYMAX / (job->width * job->height)); |
105 | job->width *= scale; |
106 | job->height *= scale; |
107 | job->zoom *= scale; |
108 | fprintf(stderr, |
109 | "%s: graph is too large for gd-renderer bitmaps. Scaling by %g to fit\n" , |
110 | job->common->cmdname, scale); |
111 | } |
112 | if (truecolor_p) { |
113 | if (job->common->verbose) |
114 | fprintf(stderr, |
115 | "%s: allocating a %dK TrueColor GD image (%d x %d pixels)\n" , |
116 | job->common->cmdname, |
117 | ROUND(job->width * job->height * 4 / 1024.), |
118 | job->width, job->height); |
119 | im = gdImageCreateTrueColor(job->width, job->height); |
120 | } else { |
121 | if (job->common->verbose) |
122 | fprintf(stderr, |
123 | "%s: allocating a %dK PaletteColor GD image (%d x %d pixels)\n" , |
124 | job->common->cmdname, |
125 | ROUND(job->width * job->height / 1024.), |
126 | job->width, job->height); |
127 | im = gdImageCreate(job->width, job->height); |
128 | } |
129 | job->context = (void *) im; |
130 | } |
131 | |
132 | if (!im) { |
133 | job->common->errorfn("gdImageCreate returned NULL. Malloc problem?\n" ); |
134 | return; |
135 | } |
136 | |
137 | /* first color is the default background color */ |
138 | /* - used for margins - if any */ |
139 | transparent = gdImageColorResolveAlpha(im, |
140 | gdRedMax - 1, gdGreenMax, |
141 | gdBlueMax, gdAlphaTransparent); |
142 | gdImageColorTransparent(im, transparent); |
143 | |
144 | white = gdImageColorResolveAlpha(im, |
145 | gdRedMax, gdGreenMax, gdBlueMax, |
146 | gdAlphaOpaque); |
147 | |
148 | black = gdImageColorResolveAlpha(im, 0, 0, 0, gdAlphaOpaque); |
149 | |
150 | /* Blending must be off to lay a transparent basecolor. |
151 | Nothing to blend with anyway. */ |
152 | gdImageAlphaBlending(im, FALSE); |
153 | gdImageFill(im, im->sx / 2, im->sy / 2, transparent); |
154 | /* Blend everything else together, |
155 | especially fonts over non-transparent backgrounds */ |
156 | gdImageAlphaBlending(im, TRUE); |
157 | } |
158 | |
159 | extern int gvdevice_gd_putBuf (gdIOCtx *context, const void *buffer, int len); |
160 | extern void gvdevice_gd_putC (gdIOCtx *context, int C); |
161 | |
162 | static void gdgen_end_page(GVJ_t * job) |
163 | { |
164 | gdImagePtr im = (gdImagePtr) job->context; |
165 | |
166 | gdIOCtx ctx; |
167 | |
168 | ctx.putBuf = gvdevice_gd_putBuf; |
169 | ctx.putC = gvdevice_gd_putC; |
170 | ctx.tell = (void*)job; /* hide *job here */ |
171 | |
172 | if (!im) |
173 | return; |
174 | if (job->external_context) { |
175 | /* leave image in memory to be handled by Gdtclft output routines */ |
176 | #ifdef MYTRACE |
177 | fprintf(stderr, "gdgen_end_graph (to memory)\n" ); |
178 | #endif |
179 | } else { |
180 | /* Only save the alpha channel in outputs that support it if |
181 | the base color was transparent. Otherwise everything |
182 | was blended so there is no useful alpha info */ |
183 | gdImageSaveAlpha(im, (basecolor == transparent)); |
184 | switch (job->render.id) { |
185 | case FORMAT_GIF: |
186 | #ifdef HAVE_GD_GIF |
187 | gdImageTrueColorToPalette(im, 0, 256); |
188 | gdImageGifCtx(im, &ctx); |
189 | #endif |
190 | break; |
191 | case FORMAT_JPEG: |
192 | #ifdef HAVE_GD_JPEG |
193 | /* |
194 | * Write IM to OUTFILE as a JFIF-formatted JPEG image, using |
195 | * quality JPEG_QUALITY. If JPEG_QUALITY is in the range |
196 | * 0-100, increasing values represent higher quality but also |
197 | * larger image size. If JPEG_QUALITY is negative, the |
198 | * IJG JPEG library's default quality is used (which should |
199 | * be near optimal for many applications). See the IJG JPEG |
200 | * library documentation for more details. */ |
201 | #define JPEG_QUALITY -1 |
202 | gdImageJpegCtx(im, &ctx, JPEG_QUALITY); |
203 | #endif |
204 | |
205 | break; |
206 | case FORMAT_PNG: |
207 | #ifdef HAVE_GD_PNG |
208 | gdImagePngCtx(im, &ctx); |
209 | #endif |
210 | break; |
211 | |
212 | #ifdef HAVE_GD_GIF |
213 | case FORMAT_WBMP: |
214 | { |
215 | /* Use black for the foreground color for the B&W wbmp image. */ |
216 | int black = gdImageColorResolveAlpha(im, 0, 0, 0, gdAlphaOpaque); |
217 | gdImageWBMPCtx(im, black, &ctx); |
218 | } |
219 | break; |
220 | #endif |
221 | |
222 | case FORMAT_GD: |
223 | gdImageGd(im, job->output_file); |
224 | break; |
225 | |
226 | #ifdef HAVE_LIBZ |
227 | case FORMAT_GD2: |
228 | #define GD2_CHUNKSIZE 128 |
229 | #define GD2_RAW 1 |
230 | #define GD2_COMPRESSED 2 |
231 | gdImageGd2(im, job->output_file, GD2_CHUNKSIZE, GD2_COMPRESSED); |
232 | break; |
233 | #endif |
234 | |
235 | case FORMAT_XBM: |
236 | #if 0 |
237 | /* libgd support only reading .xpm files */ |
238 | #ifdef HAVE_GD_XPM |
239 | gdImageXbm(im, job->output_file); |
240 | #endif |
241 | #endif |
242 | break; |
243 | } |
244 | gdImageDestroy(im); |
245 | #ifdef MYTRACE |
246 | fprintf(stderr, "gdgen_end_graph (to file)\n" ); |
247 | #endif |
248 | job->context = NULL; |
249 | } |
250 | } |
251 | |
252 | static void gdgen_missingfont(char *err, char *fontreq) |
253 | { |
254 | static char *lastmissing = 0; |
255 | static int n_errors = 0; |
256 | |
257 | if (n_errors >= 20) |
258 | return; |
259 | if ((lastmissing == 0) || (strcmp(lastmissing, fontreq))) { |
260 | #ifdef HAVE_GD_FONTCONFIG |
261 | #if 0 |
262 | /* FIXME - error function */ |
263 | agerr(AGERR, "%s : %s\n" , err, fontreq); |
264 | #endif |
265 | #else |
266 | char *p = getenv("GDFONTPATH" ); |
267 | if (!p) |
268 | p = DEFAULT_FONTPATH; |
269 | #if 0 |
270 | /* FIXME - error function */ |
271 | agerr(AGERR, "%s : %s in %s\n" , err, fontreq, p); |
272 | #endif |
273 | #endif |
274 | if (lastmissing) |
275 | free(lastmissing); |
276 | lastmissing = strdup(fontreq); |
277 | n_errors++; |
278 | #if 0 |
279 | /* FIXME - error function */ |
280 | if (n_errors >= 20) |
281 | agerr(AGWARN, "(font errors suppressed)\n" ); |
282 | #endif |
283 | } |
284 | } |
285 | |
286 | /* fontsize at which text is omitted entirely */ |
287 | #define FONTSIZE_MUCH_TOO_SMALL 0.15 |
288 | /* fontsize at which text is rendered by a simple line */ |
289 | #define FONTSIZE_TOO_SMALL 1.5 |
290 | |
291 | #ifdef _WIN32 |
292 | # define GD_IMPORT __declspec(dllimport) |
293 | #else |
294 | # define GD_IMPORT extern |
295 | #endif |
296 | GD_IMPORT gdFontPtr gdFontTiny, gdFontSmall, gdFontMediumBold, gdFontLarge, gdFontGiant; |
297 | |
298 | void gdgen_text(gdImagePtr im, pointf spf, pointf epf, int fontcolor, double fontsize, int fontdpi, double fontangle, char *fontname, char *str) |
299 | { |
300 | gdFTStringExtra strex; |
301 | point sp, ep; /* start point, end point, in pixels */ |
302 | |
303 | PF2P(spf, sp); |
304 | PF2P(epf, ep); |
305 | |
306 | strex.flags = gdFTEX_RESOLUTION; |
307 | strex.hdpi = strex.vdpi = fontdpi; |
308 | |
309 | if (strstr(fontname, "/" )) |
310 | strex.flags |= gdFTEX_FONTPATHNAME; |
311 | else |
312 | strex.flags |= gdFTEX_FONTCONFIG; |
313 | |
314 | if (fontsize <= FONTSIZE_MUCH_TOO_SMALL) { |
315 | /* ignore entirely */ |
316 | } else if (fontsize <= FONTSIZE_TOO_SMALL) { |
317 | /* draw line in place of text */ |
318 | gdImageLine(im, sp.x, sp.y, ep.x, ep.y, fontcolor); |
319 | } else { |
320 | #ifdef HAVE_GD_FREETYPE |
321 | char *err; |
322 | int brect[8]; |
323 | #ifdef HAVE_GD_FONTCONFIG |
324 | char* fontlist = fontname; |
325 | #else |
326 | extern char *gd_alternate_fontlist(char *font); |
327 | char* fontlist = gd_alternate_fontlist(fontname); |
328 | #endif |
329 | err = gdImageStringFTEx(im, brect, fontcolor, |
330 | fontlist, fontsize, fontangle, sp.x, sp.y, str, &strex); |
331 | |
332 | if (err) { |
333 | /* revert to builtin fonts */ |
334 | gdgen_missingfont(err, fontname); |
335 | #endif |
336 | sp.y += 2; |
337 | if (fontsize <= 8.5) { |
338 | gdImageString(im, gdFontTiny, sp.x, sp.y - 9, (unsigned char*)str, fontcolor); |
339 | } else if (fontsize <= 9.5) { |
340 | gdImageString(im, gdFontSmall, sp.x, sp.y - 12, (unsigned char*)str, fontcolor); |
341 | } else if (fontsize <= 10.5) { |
342 | gdImageString(im, gdFontMediumBold, sp.x, sp.y - 13, (unsigned char*)str, fontcolor); |
343 | } else if (fontsize <= 11.5) { |
344 | gdImageString(im, gdFontLarge, sp.x, sp.y - 14, (unsigned char*)str, fontcolor); |
345 | } else { |
346 | gdImageString(im, gdFontGiant, sp.x, sp.y - 15, (unsigned char*)str, fontcolor); |
347 | } |
348 | #ifdef HAVE_GD_FREETYPE |
349 | } |
350 | #endif |
351 | } |
352 | } |
353 | |
354 | extern char* gd_psfontResolve (PostscriptAlias* pa); |
355 | |
356 | static void gdgen_textspan(GVJ_t * job, pointf p, textspan_t * span) |
357 | { |
358 | gdImagePtr im = (gdImagePtr) job->context; |
359 | pointf spf, epf; |
360 | double spanwidth = span->size.x * job->zoom * job->dpi.x / POINTS_PER_INCH; |
361 | char* fontname; |
362 | #ifdef HAVE_GD_FONTCONFIG |
363 | PostscriptAlias *pA; |
364 | #endif |
365 | |
366 | if (!im) |
367 | return; |
368 | |
369 | switch (span->just) { |
370 | case 'l': |
371 | spf.x = 0.0; |
372 | break; |
373 | case 'r': |
374 | spf.x = -spanwidth; |
375 | break; |
376 | default: |
377 | case 'n': |
378 | spf.x = -spanwidth / 2; |
379 | break; |
380 | } |
381 | epf.x = spf.x + spanwidth; |
382 | |
383 | if (job->rotation) { |
384 | spf.y = -spf.x + p.y; |
385 | epf.y = epf.x + p.y; |
386 | epf.x = spf.x = p.x; |
387 | } |
388 | else { |
389 | spf.x += p.x; |
390 | epf.x += p.x; |
391 | epf.y = spf.y = p.y - span->yoffset_centerline * job->zoom * job->dpi.x / POINTS_PER_INCH; |
392 | } |
393 | |
394 | #ifdef HAVE_GD_FONTCONFIG |
395 | pA = span->font->postscript_alias; |
396 | if (pA) |
397 | fontname = gd_psfontResolve (pA); |
398 | else |
399 | #endif |
400 | fontname = span->font->name; |
401 | |
402 | gdgen_text(im, spf, epf, |
403 | job->obj->pencolor.u.index, |
404 | span->font->size * job->zoom, |
405 | job->dpi.x, |
406 | job->rotation ? (M_PI / 2) : 0, |
407 | fontname, |
408 | span->str); |
409 | } |
410 | |
411 | static int gdgen_set_penstyle(GVJ_t * job, gdImagePtr im, gdImagePtr* brush) |
412 | { |
413 | obj_state_t *obj = job->obj; |
414 | int i, pen, width, dashstyle[40]; |
415 | |
416 | if (obj->pen == PEN_DASHED) { |
417 | for (i = 0; i < 10; i++) |
418 | dashstyle[i] = obj->pencolor.u.index; |
419 | for (; i < 20; i++) |
420 | dashstyle[i] = gdTransparent; |
421 | gdImageSetStyle(im, dashstyle, 20); |
422 | pen = gdStyled; |
423 | } else if (obj->pen == PEN_DOTTED) { |
424 | for (i = 0; i < 2; i++) |
425 | dashstyle[i] = obj->pencolor.u.index; |
426 | for (; i < 14; i++) |
427 | dashstyle[i] = gdTransparent; |
428 | gdImageSetStyle(im, dashstyle, 12); |
429 | pen = gdStyled; |
430 | } else { |
431 | pen = obj->pencolor.u.index; |
432 | } |
433 | |
434 | width = obj->penwidth * job->zoom; |
435 | if (width < PENWIDTH_NORMAL) |
436 | width = PENWIDTH_NORMAL; /* gd can't do thin lines */ |
437 | gdImageSetThickness(im, width); |
438 | /* use brush instead of Thickness to improve end butts */ |
439 | if (width != PENWIDTH_NORMAL) { |
440 | if (im->trueColor) { |
441 | *brush = gdImageCreateTrueColor(width,width); |
442 | } |
443 | else { |
444 | *brush = gdImageCreate(width, width); |
445 | gdImagePaletteCopy(*brush, im); |
446 | } |
447 | gdImageFilledRectangle(*brush, 0, 0, width - 1, width - 1, |
448 | obj->pencolor.u.index); |
449 | gdImageSetBrush(im, *brush); |
450 | if (pen == gdStyled) |
451 | pen = gdStyledBrushed; |
452 | else |
453 | pen = gdBrushed; |
454 | } |
455 | |
456 | return pen; |
457 | } |
458 | |
459 | static void |
460 | gdgen_bezier(GVJ_t * job, pointf * A, int n, int arrow_at_start, |
461 | int arrow_at_end, int filled) |
462 | { |
463 | obj_state_t *obj = job->obj; |
464 | gdImagePtr im = (gdImagePtr) job->context; |
465 | pointf p0, p1, V[4]; |
466 | int i, j, step, pen; |
467 | boolean pen_ok, fill_ok; |
468 | gdImagePtr brush = NULL; |
469 | gdPoint F[4]; |
470 | |
471 | if (!im) |
472 | return; |
473 | |
474 | pen = gdgen_set_penstyle(job, im, &brush); |
475 | pen_ok = (pen != gdImageGetTransparent(im)); |
476 | fill_ok = (filled && obj->fillcolor.u.index != gdImageGetTransparent(im)); |
477 | |
478 | if (pen_ok || fill_ok) { |
479 | V[3] = A[0]; |
480 | PF2P(A[0], F[0]); |
481 | PF2P(A[n-1], F[3]); |
482 | for (i = 0; i + 3 < n; i += 3) { |
483 | V[0] = V[3]; |
484 | for (j = 1; j <= 3; j++) |
485 | V[j] = A[i + j]; |
486 | p0 = V[0]; |
487 | for (step = 1; step <= BEZIERSUBDIVISION; step++) { |
488 | p1 = Bezier(V, 3, (double) step / BEZIERSUBDIVISION, NULL, NULL); |
489 | PF2P(p0, F[1]); |
490 | PF2P(p1, F[2]); |
491 | if (pen_ok) |
492 | gdImageLine(im, F[1].x, F[1].y, F[2].x, F[2].y, pen); |
493 | if (fill_ok) |
494 | gdImageFilledPolygon(im, F, 4, obj->fillcolor.u.index); |
495 | p0 = p1; |
496 | } |
497 | } |
498 | } |
499 | if (brush) |
500 | gdImageDestroy(brush); |
501 | } |
502 | |
503 | static gdPoint *points; |
504 | static int points_allocated; |
505 | |
506 | static void gdgen_polygon(GVJ_t * job, pointf * A, int n, int filled) |
507 | { |
508 | obj_state_t *obj = job->obj; |
509 | gdImagePtr im = (gdImagePtr) job->context; |
510 | gdImagePtr brush = NULL; |
511 | int i; |
512 | int pen; |
513 | boolean pen_ok, fill_ok; |
514 | |
515 | if (!im) |
516 | return; |
517 | |
518 | pen = gdgen_set_penstyle(job, im, &brush); |
519 | pen_ok = (pen != gdImageGetTransparent(im)); |
520 | fill_ok = (filled && obj->fillcolor.u.index != gdImageGetTransparent(im)); |
521 | |
522 | if (pen_ok || fill_ok) { |
523 | if (n > points_allocated) { |
524 | points = realloc(points, n * sizeof(gdPoint)); |
525 | points_allocated = n; |
526 | } |
527 | for (i = 0; i < n; i++) { |
528 | points[i].x = ROUND(A[i].x); |
529 | points[i].y = ROUND(A[i].y); |
530 | } |
531 | if (fill_ok) |
532 | gdImageFilledPolygon(im, points, n, obj->fillcolor.u.index); |
533 | |
534 | if (pen_ok) |
535 | gdImagePolygon(im, points, n, pen); |
536 | } |
537 | if (brush) |
538 | gdImageDestroy(brush); |
539 | } |
540 | |
541 | static void gdgen_ellipse(GVJ_t * job, pointf * A, int filled) |
542 | { |
543 | obj_state_t *obj = job->obj; |
544 | gdImagePtr im = (gdImagePtr) job->context; |
545 | double dx, dy; |
546 | int pen; |
547 | boolean pen_ok, fill_ok; |
548 | gdImagePtr brush = NULL; |
549 | |
550 | if (!im) |
551 | return; |
552 | |
553 | pen = gdgen_set_penstyle(job, im, &brush); |
554 | pen_ok = (pen != gdImageGetTransparent(im)); |
555 | fill_ok = (filled && obj->fillcolor.u.index != gdImageGetTransparent(im)); |
556 | |
557 | dx = 2 * (A[1].x - A[0].x); |
558 | dy = 2 * (A[1].y - A[0].y); |
559 | |
560 | if (fill_ok) |
561 | gdImageFilledEllipse(im, ROUND(A[0].x), ROUND(A[0].y), |
562 | ROUND(dx), ROUND(dy), |
563 | obj->fillcolor.u.index); |
564 | if (pen_ok) |
565 | gdImageArc(im, ROUND(A[0].x), ROUND(A[0].y), ROUND(dx), ROUND(dy), |
566 | 0, 360, pen); |
567 | if (brush) |
568 | gdImageDestroy(brush); |
569 | } |
570 | |
571 | static void gdgen_polyline(GVJ_t * job, pointf * A, int n) |
572 | { |
573 | gdImagePtr im = (gdImagePtr) job->context; |
574 | pointf p, p1; |
575 | int i; |
576 | int pen; |
577 | boolean pen_ok; |
578 | gdImagePtr brush = NULL; |
579 | |
580 | if (!im) |
581 | return; |
582 | |
583 | pen = gdgen_set_penstyle(job, im, &brush); |
584 | pen_ok = (pen != gdImageGetTransparent(im)); |
585 | |
586 | if (pen_ok) { |
587 | p = A[0]; |
588 | for (i = 1; i < n; i++) { |
589 | p1 = A[i]; |
590 | gdImageLine(im, ROUND(p.x), ROUND(p.y), |
591 | ROUND(p1.x), ROUND(p1.y), pen); |
592 | p = p1; |
593 | } |
594 | } |
595 | if (brush) |
596 | gdImageDestroy(brush); |
597 | } |
598 | |
599 | static gvrender_engine_t gdgen_engine = { |
600 | 0, /* gdgen_begin_job */ |
601 | 0, /* gdgen_end_job */ |
602 | 0, /* gdgen_begin_graph */ |
603 | 0, /* gdgen_end_graph */ |
604 | 0, /* gdgen_begin_layer */ |
605 | 0, /* gdgen_end_layer */ |
606 | gdgen_begin_page, |
607 | gdgen_end_page, |
608 | 0, /* gdgen_begin_cluster */ |
609 | 0, /* gdgen_end_cluster */ |
610 | 0, /* gdgen_begin_nodes */ |
611 | 0, /* gdgen_end_nodes */ |
612 | 0, /* gdgen_begin_edges */ |
613 | 0, /* gdgen_end_edges */ |
614 | 0, /* gdgen_begin_node */ |
615 | 0, /* gdgen_end_node */ |
616 | 0, /* gdgen_begin_edge */ |
617 | 0, /* gdgen_end_edge */ |
618 | 0, /* gdgen_begin_anchor */ |
619 | 0, /* gdgen_end_anchor */ |
620 | 0, /* gdgen_begin_label */ |
621 | 0, /* gdgen_end_label */ |
622 | gdgen_textspan, |
623 | gdgen_resolve_color, |
624 | gdgen_ellipse, |
625 | gdgen_polygon, |
626 | gdgen_bezier, |
627 | gdgen_polyline, |
628 | 0, /* gdgen_comment */ |
629 | 0, /* gdgen_library_shape */ |
630 | }; |
631 | |
632 | static gvrender_features_t render_features_gd = { |
633 | GVRENDER_Y_GOES_DOWN, /* flags */ |
634 | 4., /* default pad - graph units */ |
635 | NULL, /* knowncolors */ |
636 | 0, /* sizeof knowncolors */ |
637 | RGBA_BYTE, /* color_type */ |
638 | }; |
639 | |
640 | static gvdevice_features_t device_features_gd = { |
641 | GVDEVICE_BINARY_FORMAT, /* flags */ |
642 | {0.,0.}, /* default margin - points */ |
643 | {0.,0.}, /* default page width, height - points */ |
644 | {96.,96.}, /* default dpi */ |
645 | }; |
646 | |
647 | static gvdevice_features_t device_features_gd_tc = { |
648 | GVDEVICE_BINARY_FORMAT |
649 | | GVDEVICE_DOES_TRUECOLOR,/* flags */ |
650 | {0.,0.}, /* default margin - points */ |
651 | {0.,0.}, /* default page width, height - points */ |
652 | {96.,96.}, /* default dpi */ |
653 | }; |
654 | |
655 | static gvdevice_features_t device_features_gd_tc_no_writer = { |
656 | GVDEVICE_BINARY_FORMAT |
657 | | GVDEVICE_DOES_TRUECOLOR |
658 | | GVDEVICE_NO_WRITER, /* flags */ |
659 | {0.,0.}, /* default margin - points */ |
660 | {0.,0.}, /* default page width, height - points */ |
661 | {96.,96.}, /* default dpi */ |
662 | }; |
663 | |
664 | gvplugin_installed_t gvrender_gd_types[] = { |
665 | {FORMAT_GD, "gd" , 1, &gdgen_engine, &render_features_gd}, |
666 | {0, NULL, 0, NULL, NULL} |
667 | }; |
668 | |
669 | gvplugin_installed_t gvdevice_gd_types2[] = { |
670 | #ifdef HAVE_GD_GIF |
671 | {FORMAT_GIF, "gif:gd" , 1, NULL, &device_features_gd_tc}, /* pretend gif is truecolor because it supports transparency */ |
672 | {FORMAT_WBMP, "wbmp:gd" , 1, NULL, &device_features_gd}, |
673 | #endif |
674 | |
675 | #ifdef HAVE_GD_JPEG |
676 | {FORMAT_JPEG, "jpe:gd" , 1, NULL, &device_features_gd}, |
677 | {FORMAT_JPEG, "jpeg:gd" , 1, NULL, &device_features_gd}, |
678 | {FORMAT_JPEG, "jpg:gd" , 1, NULL, &device_features_gd}, |
679 | #endif |
680 | |
681 | #ifdef HAVE_GD_PNG |
682 | {FORMAT_PNG, "png:gd" , 1, NULL, &device_features_gd_tc}, |
683 | #endif |
684 | |
685 | {FORMAT_GD, "gd:gd" , 1, NULL, &device_features_gd_tc_no_writer}, |
686 | |
687 | #ifdef HAVE_LIBZ |
688 | {FORMAT_GD2, "gd2:gd" , 1, NULL, &device_features_gd_tc_no_writer}, |
689 | #endif |
690 | |
691 | #if 0 |
692 | /* libgd has no support for xbm as output */ |
693 | #ifdef HAVE_GD_XPM |
694 | {FORMAT_XBM, "xbm:gd" , 1, NULL, &device_features_gd}, |
695 | #endif |
696 | #endif |
697 | {0, NULL, 0, NULL, NULL} |
698 | }; |
699 | |