| 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 | |