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 | |
36 | typedef enum { FORMAT_PS, FORMAT_PS2, FORMAT_EPS } format_type; |
37 | |
38 | static int isLatin1; |
39 | static char setupLatin1; |
40 | |
41 | static 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 | |
52 | static 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 | |
66 | static 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 | |
115 | static 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 | |
120 | static 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 | |
155 | static 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 | |
169 | static 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 | |
178 | static void psgen_end_cluster(GVJ_t * job) |
179 | { |
180 | gvputs(job, "grestore\n" ); |
181 | } |
182 | |
183 | static void psgen_begin_node(GVJ_t * job) |
184 | { |
185 | gvputs(job, "gsave\n" ); |
186 | } |
187 | |
188 | static void psgen_end_node(GVJ_t * job) |
189 | { |
190 | gvputs(job, "grestore\n" ); |
191 | } |
192 | |
193 | static void |
194 | psgen_begin_edge(GVJ_t * job) |
195 | { |
196 | gvputs(job, "gsave\n" ); |
197 | } |
198 | |
199 | static void psgen_end_edge(GVJ_t * job) |
200 | { |
201 | gvputs(job, "grestore\n" ); |
202 | } |
203 | |
204 | static 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 | |
220 | static void |
221 | ps_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 | |
247 | static 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 | |
272 | static 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 | |
302 | static 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 | |
324 | static void |
325 | psgen_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 | |
355 | static 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 | |
384 | static 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 | |
402 | static void (GVJ_t * job, char *str) |
403 | { |
404 | gvputs(job, "% " ); |
405 | gvputs(job, str); |
406 | gvputs(job, "\n" ); |
407 | } |
408 | |
409 | static 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 | |
430 | static 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 | |
463 | static 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 | |
474 | static 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 | |
482 | static 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 | |
489 | gvplugin_installed_t gvrender_ps_types[] = { |
490 | {FORMAT_PS, "ps" , 1, &psgen_engine, &render_features_ps}, |
491 | {0, NULL, 0, NULL, NULL} |
492 | }; |
493 | |
494 | gvplugin_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 | |