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 "gvplugin_render.h"
20#include "gvplugin_device.h"
21#include "gvio.h"
22#include "agxbuf.h"
23#include "utils.h"
24#include "ps.h"
25
26/* for CHAR_LATIN1 */
27#include "const.h"
28
29/*
30 * J$: added `pdfmark' URL embedding. PostScript rendered from
31 * dot files with URL attributes will get active PDF links
32 * from Adobe's Distiller.
33 */
34#define PDFMAX 14400 /* Maximum size of PDF page */
35
36typedef enum { FORMAT_PS, FORMAT_PS2, FORMAT_EPS } format_type;
37
38static int isLatin1;
39static char setupLatin1;
40
41static void psgen_begin_job(GVJ_t * job)
42{
43 gvputs(job, "%!PS-Adobe-3.0");
44 if (job->render.id == FORMAT_EPS)
45 gvputs(job, " EPSF-3.0\n");
46 else
47 gvputs(job, "\n");
48 gvprintf(job, "%%%%Creator: %s version %s (%s)\n",
49 job->common->info[0], job->common->info[1], job->common->info[2]);
50}
51
52static void psgen_end_job(GVJ_t * job)
53{
54 gvputs(job, "%%Trailer\n");
55 if (job->render.id != FORMAT_EPS)
56 gvprintf(job, "%%%%Pages: %d\n", job->common->viewNum);
57 if (job->common->show_boxes == NULL)
58 if (job->render.id != FORMAT_EPS)
59 gvprintf(job, "%%%%BoundingBox: %d %d %d %d\n",
60 job->boundingBox.LL.x, job->boundingBox.LL.y,
61 job->boundingBox.UR.x, job->boundingBox.UR.y);
62 gvputs(job, "end\nrestore\n");
63 gvputs(job, "%%EOF\n");
64}
65
66static void psgen_begin_graph(GVJ_t * job)
67{
68 obj_state_t *obj = job->obj;
69
70 setupLatin1 = FALSE;
71
72 if (job->common->viewNum == 0) {
73 gvprintf(job, "%%%%Title: %s\n", agnameof(obj->u.g));
74 if (job->render.id != FORMAT_EPS)
75 gvputs(job, "%%Pages: (atend)\n");
76 else
77 gvputs(job, "%%Pages: 1\n");
78 if (job->common->show_boxes == NULL) {
79 if (job->render.id != FORMAT_EPS)
80 gvputs(job, "%%BoundingBox: (atend)\n");
81 else
82 gvprintf(job, "%%%%BoundingBox: %d %d %d %d\n",
83 job->pageBoundingBox.LL.x, job->pageBoundingBox.LL.y,
84 job->pageBoundingBox.UR.x, job->pageBoundingBox.UR.y);
85 }
86 gvputs(job, "%%EndComments\nsave\n");
87 /* include shape library */
88 cat_libfile(job, job->common->lib, ps_txt);
89 /* include epsf */
90 epsf_define(job);
91 if (job->common->show_boxes) {
92 const char* args[2];
93 args[0] = job->common->show_boxes[0];
94 args[1] = NULL;
95 cat_libfile(job, NULL, args);
96 }
97 }
98 isLatin1 = (GD_charset(obj->u.g) == CHAR_LATIN1 ? CHAR_LATIN1 : -1);
99 /* We always setup Latin1. The charset info is always output,
100 * and installing it is cheap. With it installed, we can then
101 * rely on ps_string to convert UTF-8 characters whose encoding
102 * is in the range of Latin-1 into the Latin-1 equivalent and
103 * get the expected PostScript output.
104 */
105 if (!setupLatin1) {
106 gvputs(job, "setupLatin1\n"); /* as defined in ps header */
107 setupLatin1 = TRUE;
108 }
109 /* Set base URL for relative links (for Distiller >= 3.0) */
110 if (obj->url)
111 gvprintf(job, "[ {Catalog} << /URI << /Base %s >> >>\n"
112 "/PUT pdfmark\n", ps_string(obj->url,isLatin1));
113}
114
115static void psgen_begin_layer(GVJ_t * job, char *layername, int layerNum, int numLayers)
116{
117 gvprintf(job, "%d %d setlayer\n", layerNum, numLayers);
118}
119
120static void psgen_begin_page(GVJ_t * job)
121{
122 box pbr = job->pageBoundingBox;
123
124 gvprintf(job, "%%%%Page: %d %d\n",
125 job->common->viewNum + 1, job->common->viewNum + 1);
126 if (job->common->show_boxes == NULL)
127 gvprintf(job, "%%%%PageBoundingBox: %d %d %d %d\n",
128 pbr.LL.x, pbr.LL.y, pbr.UR.x, pbr.UR.y);
129 gvprintf(job, "%%%%PageOrientation: %s\n",
130 (job->rotation ? "Landscape" : "Portrait"));
131 if (job->render.id == FORMAT_PS2)
132 gvprintf(job, "<< /PageSize [%d %d] >> setpagedevice\n",
133 pbr.UR.x, pbr.UR.y);
134 gvprintf(job, "%d %d %d beginpage\n",
135 job->pagesArrayElem.x, job->pagesArrayElem.y, job->numPages);
136 if (job->common->show_boxes == NULL)
137 gvprintf(job, "gsave\n%d %d %d %d boxprim clip newpath\n",
138 pbr.LL.x, pbr.LL.y, pbr.UR.x-pbr.LL.x, pbr.UR.y-pbr.LL.y);
139 gvprintf(job, "%g %g set_scale %d rotate %g %g translate\n",
140 job->scale.x, job->scale.y,
141 job->rotation,
142 job->translation.x, job->translation.y);
143
144 /* Define the size of the PS canvas */
145 if (job->render.id == FORMAT_PS2) {
146 if (pbr.UR.x >= PDFMAX || pbr.UR.y >= PDFMAX)
147 job->common->errorfn("canvas size (%d,%d) exceeds PDF limit (%d)\n"
148 "\t(suggest setting a bounding box size, see dot(1))\n",
149 pbr.UR.x, pbr.UR.y, PDFMAX);
150 gvprintf(job, "[ /CropBox [%d %d %d %d] /PAGES pdfmark\n",
151 pbr.LL.x, pbr.LL.y, pbr.UR.x, pbr.UR.y);
152 }
153}
154
155static void psgen_end_page(GVJ_t * job)
156{
157 if (job->common->show_boxes) {
158 gvputs(job, "0 0 0 edgecolor\n");
159 cat_libfile(job, NULL, job->common->show_boxes + 1);
160 }
161 /* the showpage is really a no-op, but at least one PS processor
162 * out there needs to see this literal token. endpage does the real work.
163 */
164 gvputs(job, "endpage\nshowpage\ngrestore\n");
165 gvputs(job, "%%PageTrailer\n");
166 gvprintf(job, "%%%%EndPage: %d\n", job->common->viewNum);
167}
168
169static void psgen_begin_cluster(GVJ_t * job)
170{
171 obj_state_t *obj = job->obj;
172
173 gvprintf(job, "%% %s\n", agnameof(obj->u.g));
174
175 gvputs(job, "gsave\n");
176}
177
178static void psgen_end_cluster(GVJ_t * job)
179{
180 gvputs(job, "grestore\n");
181}
182
183static void psgen_begin_node(GVJ_t * job)
184{
185 gvputs(job, "gsave\n");
186}
187
188static void psgen_end_node(GVJ_t * job)
189{
190 gvputs(job, "grestore\n");
191}
192
193static void
194psgen_begin_edge(GVJ_t * job)
195{
196 gvputs(job, "gsave\n");
197}
198
199static void psgen_end_edge(GVJ_t * job)
200{
201 gvputs(job, "grestore\n");
202}
203
204static void psgen_begin_anchor(GVJ_t *job, char *url, char *tooltip, char *target, char *id)
205{
206 obj_state_t *obj = job->obj;
207
208 if (url && obj->url_map_p) {
209 gvputs(job, "[ /Rect [ ");
210 gvprintpointflist(job, obj->url_map_p, 2);
211 gvputs(job, " ]\n");
212 gvprintf(job, " /Border [ 0 0 0 ]\n"
213 " /Action << /Subtype /URI /URI %s >>\n"
214 " /Subtype /Link\n"
215 "/ANN pdfmark\n",
216 ps_string(url, isLatin1));
217 }
218}
219
220static void
221ps_set_pen_style(GVJ_t *job)
222{
223 double penwidth = job->obj->penwidth;
224 char *p, *line, **s = job->obj->rawstyle;
225
226 gvprintdouble(job, penwidth);
227 gvputs(job," setlinewidth\n");
228
229 while (s && (p = line = *s++)) {
230 if (strcmp(line, "setlinewidth") == 0)
231 continue;
232 while (*p)
233 p++;
234 p++;
235 while (*p) {
236 gvprintf(job,"%s ", p);
237 while (*p)
238 p++;
239 p++;
240 }
241 if (strcmp(line, "invis") == 0)
242 job->obj->penwidth = 0;
243 gvprintf(job, "%s\n", line);
244 }
245}
246
247static void ps_set_color(GVJ_t *job, gvcolor_t *color)
248{
249 char *objtype;
250
251 if (color) {
252 switch (job->obj->type) {
253 case ROOTGRAPH_OBJTYPE:
254 case CLUSTER_OBJTYPE:
255 objtype = "graph";
256 break;
257 case NODE_OBJTYPE:
258 objtype = "node";
259 break;
260 case EDGE_OBJTYPE:
261 objtype = "edge";
262 break;
263 default:
264 objtype = "sethsb";
265 break;
266 }
267 gvprintf(job, "%.5g %.5g %.5g %scolor\n",
268 color->u.HSVA[0], color->u.HSVA[1], color->u.HSVA[2], objtype);
269 }
270}
271
272static void psgen_textspan(GVJ_t * job, pointf p, textspan_t * span)
273{
274 char *str;
275
276 if (job->obj->pencolor.u.HSVA[3] < .5)
277 return; /* skip transparent text */
278
279 ps_set_color(job, &(job->obj->pencolor));
280 gvprintdouble(job, span->font->size);
281 gvprintf(job, " /%s set_font\n", span->font->name);
282 str = ps_string(span->str,isLatin1);
283 switch (span->just) {
284 case 'r':
285 p.x -= span->size.x;
286 break;
287 case 'l':
288 p.x -= 0.0;
289 break;
290 case 'n':
291 default:
292 p.x -= span->size.x / 2.0;
293 break;
294 }
295 p.y += span->yoffset_centerline;
296 gvprintpointf(job, p);
297 gvputs(job, " moveto ");
298 gvprintdouble(job, span->size.x);
299 gvprintf(job, " %s alignedtext\n", str);
300}
301
302static void psgen_ellipse(GVJ_t * job, pointf * A, int filled)
303{
304 /* A[] contains 2 points: the center and corner. */
305 pointf AA[2];
306
307 AA[0] = A[0];
308 AA[1].x = A[1].x - A[0].x;
309 AA[1].y = A[1].y - A[0].y;
310
311 if (filled && job->obj->fillcolor.u.HSVA[3] > .5) {
312 ps_set_color(job, &(job->obj->fillcolor));
313 gvprintpointflist(job, AA, 2);
314 gvputs(job, " ellipse_path fill\n");
315 }
316 if (job->obj->pencolor.u.HSVA[3] > .5) {
317 ps_set_pen_style(job);
318 ps_set_color(job, &(job->obj->pencolor));
319 gvprintpointflist(job, AA, 2);
320 gvputs(job, " ellipse_path stroke\n");
321 }
322}
323
324static void
325psgen_bezier(GVJ_t * job, pointf * A, int n, int arrow_at_start,
326 int arrow_at_end, int filled)
327{
328 int j;
329
330 if (filled && job->obj->fillcolor.u.HSVA[3] > .5) {
331 ps_set_color(job, &(job->obj->fillcolor));
332 gvputs(job, "newpath ");
333 gvprintpointf(job, A[0]);
334 gvputs(job, " moveto\n");
335 for (j = 1; j < n; j += 3) {
336 gvprintpointflist(job, &A[j], 3);
337 gvputs(job, " curveto\n");
338 }
339 gvputs(job, "closepath fill\n");
340 }
341 if (job->obj->pencolor.u.HSVA[3] > .5) {
342 ps_set_pen_style(job);
343 ps_set_color(job, &(job->obj->pencolor));
344 gvputs(job, "newpath ");
345 gvprintpointf(job, A[0]);
346 gvputs(job, " moveto\n");
347 for (j = 1; j < n; j += 3) {
348 gvprintpointflist(job, &A[j], 3);
349 gvputs(job, " curveto\n");
350 }
351 gvputs(job, "stroke\n");
352 }
353}
354
355static void psgen_polygon(GVJ_t * job, pointf * A, int n, int filled)
356{
357 int j;
358
359 if (filled && job->obj->fillcolor.u.HSVA[3] > .5) {
360 ps_set_color(job, &(job->obj->fillcolor));
361 gvputs(job, "newpath ");
362 gvprintpointf(job, A[0]);
363 gvputs(job, " moveto\n");
364 for (j = 1; j < n; j++) {
365 gvprintpointf(job, A[j]);
366 gvputs(job, " lineto\n");
367 }
368 gvputs(job, "closepath fill\n");
369 }
370 if (job->obj->pencolor.u.HSVA[3] > .5) {
371 ps_set_pen_style(job);
372 ps_set_color(job, &(job->obj->pencolor));
373 gvputs(job, "newpath ");
374 gvprintpointf(job, A[0]);
375 gvputs(job, " moveto\n");
376 for (j = 1; j < n; j++) {
377 gvprintpointf(job, A[j]);
378 gvputs(job, " lineto\n");
379 }
380 gvputs(job, "closepath stroke\n");
381 }
382}
383
384static void psgen_polyline(GVJ_t * job, pointf * A, int n)
385{
386 int j;
387
388 if (job->obj->pencolor.u.HSVA[3] > .5) {
389 ps_set_pen_style(job);
390 ps_set_color(job, &(job->obj->pencolor));
391 gvputs(job, "newpath ");
392 gvprintpointf(job, A[0]);
393 gvputs(job, " moveto\n");
394 for (j = 1; j < n; j++) {
395 gvprintpointf(job, A[j]);
396 gvputs(job, " lineto\n");
397 }
398 gvputs(job, "stroke\n");
399 }
400}
401
402static void psgen_comment(GVJ_t * job, char *str)
403{
404 gvputs(job, "% ");
405 gvputs(job, str);
406 gvputs(job, "\n");
407}
408
409static void psgen_library_shape(GVJ_t * job, char *name, pointf * A, int n, int filled)
410{
411 if (filled && job->obj->fillcolor.u.HSVA[3] > .5) {
412 ps_set_color(job, &(job->obj->fillcolor));
413 gvputs(job, "[ ");
414 gvprintpointflist(job, A, n);
415 gvputs(job, " ");
416 gvprintpointf(job, A[0]);
417 gvprintf(job, " ] %d true %s\n", n, name);
418 }
419 if (job->obj->pencolor.u.HSVA[3] > .5) {
420 ps_set_pen_style(job);
421 ps_set_color(job, &(job->obj->pencolor));
422 gvputs(job, "[ ");
423 gvprintpointflist(job, A, n);
424 gvputs(job, " ");
425 gvprintpointf(job, A[0]);
426 gvprintf(job, " ] %d false %s\n", n, name);
427 }
428}
429
430static gvrender_engine_t psgen_engine = {
431 psgen_begin_job,
432 psgen_end_job,
433 psgen_begin_graph,
434 0, /* psgen_end_graph */
435 psgen_begin_layer,
436 0, /* psgen_end_layer */
437 psgen_begin_page,
438 psgen_end_page,
439 psgen_begin_cluster,
440 psgen_end_cluster,
441 0, /* psgen_begin_nodes */
442 0, /* psgen_end_nodes */
443 0, /* psgen_begin_edges */
444 0, /* psgen_end_edges */
445 psgen_begin_node,
446 psgen_end_node,
447 psgen_begin_edge,
448 psgen_end_edge,
449 psgen_begin_anchor,
450 0, /* psgen_end_anchor */
451 0, /* psgen_begin_label */
452 0, /* psgen_end_label */
453 psgen_textspan,
454 0, /* psgen_resolve_color */
455 psgen_ellipse,
456 psgen_polygon,
457 psgen_bezier,
458 psgen_polyline,
459 psgen_comment,
460 psgen_library_shape,
461};
462
463static gvrender_features_t render_features_ps = {
464 GVRENDER_DOES_TRANSFORM
465 | GVRENDER_DOES_MAPS
466 | GVRENDER_NO_WHITE_BG
467 | GVRENDER_DOES_MAP_RECTANGLE,
468 4., /* default pad - graph units */
469 NULL, /* knowncolors */
470 0, /* sizeof knowncolors */
471 HSVA_DOUBLE, /* color_type */
472};
473
474static gvdevice_features_t device_features_ps = {
475 GVDEVICE_DOES_PAGES
476 | GVDEVICE_DOES_LAYERS, /* flags */
477 {36.,36.}, /* default margin - points */
478 {612.,792.}, /* default page width, height - points */
479 {72.,72.}, /* default dpi */
480};
481
482static gvdevice_features_t device_features_eps = {
483 0, /* flags */
484 {36.,36.}, /* default margin - points */
485 {612.,792.}, /* default page width, height - points */
486 {72.,72.}, /* default dpi */
487};
488
489gvplugin_installed_t gvrender_ps_types[] = {
490 {FORMAT_PS, "ps", 1, &psgen_engine, &render_features_ps},
491 {0, NULL, 0, NULL, NULL}
492};
493
494gvplugin_installed_t gvdevice_ps_types[] = {
495 {FORMAT_PS, "ps:ps", 1, NULL, &device_features_ps},
496 {FORMAT_PS2, "ps2:ps", 1, NULL, &device_features_ps},
497 {FORMAT_EPS, "eps:ps", 1, NULL, &device_features_eps},
498 {0, NULL, 0, NULL, NULL}
499};
500