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#include <stdarg.h>
19#include <ctype.h>
20
21#include "gvplugin_render.h"
22#include "gvplugin_device.h"
23#include "gvio.h"
24#include "agxbuf.h"
25#include "utils.h"
26#include "color.h"
27#include "colorprocs.h"
28
29#include "const.h"
30
31/* Number of points to split splines into */
32#define BEZIERSUBDIVISION 6
33
34#define PIC_COORDS_PER_LINE (16) /* to avoid stdio BUF overflow */
35
36typedef enum { FORMAT_PIC, } format_type;
37
38static int onetime = TRUE;
39static double Fontscale;
40
41/* There are a couple of ways to generate output:
42 1. generate for whatever size is given by the bounding box
43 - the drawing at its "natural" size might not fit on a physical page
44 ~ dot size specification can be used to scale the drawing
45 ~ and it's not difficult for user to scale the pic output to fit (multiply 4 (3 distinct) numbers on 3 lines by a scale factor)
46 - some troff implementations may clip large graphs
47 ~ handle by scaling to manageable size
48 - give explicit width and height as parameters to .PS
49 - pic scale variable is reset to 1.0
50 - fonts are printed as size specified by caller, modified by user scaling
51 2. scale to fit on a physical page
52 - requires an assumption of page size (GNU pic assumes 8.5x11.0 inches)
53 ~ any assumption is bound to be wrong more often than right
54 - requires separate scaling of font point sizes since pic's scale variable doesn't affect text
55 ~ possible, as above
56 - likewise for line thickness
57 - GNU pic does this (except for fonts) if .PS is used without explicit width or height; DWB pic does not
58 ~ pic variants likely to cause trouble
59 The first approach is used here.
60*/
61
62static const char *EscComment = ".\\\" "; /* troff comment */
63static const char picgen_msghdr[] = "dot pic plugin: ";
64
65static void unsupported(char *s)
66{
67 agerr(AGWARN, "%s%s unsupported\n", picgen_msghdr, s);
68}
69
70/* troff font mapping */
71typedef struct {
72 char trname[3], *psname;
73} fontinfo;
74
75static fontinfo fonttab[] = {
76 {"AB", "AvantGarde-Demi"},
77 {"AI", "AvantGarde-BookOblique"},
78 {"AR", "AvantGarde-Book"},
79 {"AX", "AvantGarde-DemiOblique"},
80 {"B ", "Times-Bold"},
81 {"BI", "Times-BoldItalic"},
82 {"CB", "Courier-Bold"},
83 {"CO", "Courier"},
84 {"CX", "Courier-BoldOblique"},
85 {"H ", "Helvetica"},
86 {"HB", "Helvetica-Bold"},
87 {"HI", "Helvetica-Oblique"},
88 {"HX", "Helvetica-BoldOblique"},
89 {"Hb", "Helvetica-Narrow-Bold"},
90 {"Hi", "Helvetica-Narrow-Oblique"},
91 {"Hr", "Helvetica-Narrow"},
92 {"Hx", "Helvetica-Narrow-BoldOblique"},
93 {"I ", "Times-Italic"},
94 {"KB", "Bookman-Demi"},
95 {"KI", "Bookman-LightItalic"},
96 {"KR", "Bookman-Light"},
97 {"KX", "Bookman-DemiItalic"},
98 {"NB", "NewCenturySchlbk-Bold"},
99 {"NI", "NewCenturySchlbk-Italic"},
100 {"NR", "NewCenturySchlbk-Roman"},
101 {"NX", "NewCenturySchlbk-BoldItalic"},
102 {"PA", "Palatino-Roman"},
103 {"PB", "Palatino-Bold"},
104 {"PI", "Palatino-Italic"},
105 {"PX", "Palatino-BoldItalic"},
106 {"R ", "Times-Roman"},
107 {"S ", "Symbol"},
108 {"ZD", "ZapfDingbats"},
109 {"\000\000", (char *) 0}
110};
111
112static char *picfontname(char *psname)
113{
114 char *rv;
115 fontinfo *p;
116
117 for (p = fonttab; p->psname; p++)
118 if (strcmp(p->psname, psname) == 0)
119 break;
120 if (p->psname)
121 rv = p->trname;
122 else {
123 agerr(AGERR, "%s%s is not a troff font\n", picgen_msghdr, psname);
124 /* try base font names, e.g. Helvetica-Outline-Oblique -> Helvetica-Outline -> Helvetica */
125 if ((rv = strrchr(psname, '-'))) {
126 *rv = '\0'; /* psname is not specified as const ... */
127 rv = picfontname(psname);
128 } else
129 rv = "R";
130 }
131 return rv;
132}
133
134static void picptarray(GVJ_t *job, pointf * A, int n, int close)
135{
136 int i;
137 point p;
138
139 for (i = 0; i < n; i++) {
140 PF2P(A[i],p);
141 gvprintf(job, " %d %d", p.x, p.y);
142 }
143 if (close) {
144 PF2P(A[0],p);
145 gvprintf(job, " %d %d", p.x, p.y);
146 }
147 gvputs(job, "\n");
148}
149
150static char *pic_string(char *s)
151{
152 static char *buf = NULL;
153 static int bufsize = 0;
154 int pos = 0;
155 char *p;
156 unsigned char c;
157
158 if (!buf) {
159 bufsize = 64;
160 buf = malloc(bufsize * sizeof(char));
161 }
162
163 p = buf;
164 while ((c = *s++)) {
165 if (pos > (bufsize - 8)) {
166 bufsize *= 2;
167 buf = realloc(buf, bufsize * sizeof(char));
168 p = buf + pos;
169 }
170 if (isascii(c)) {
171 if (c == '\\') {
172 *p++ = '\\';
173 pos++;
174 }
175 *p++ = c;
176 pos++;
177 } else {
178 *p++ = '\\';
179 sprintf(p, "%03o", c);
180 p += 3;
181 pos += 4;
182 }
183 }
184 *p = '\0';
185 return buf;
186}
187
188static void pic_line_style(obj_state_t *obj, int *line_style, double *style_val)
189{
190 switch (obj->pen) {
191 case PEN_DASHED:
192 *line_style = 1;
193 *style_val = 10.;
194 break;
195 case PEN_DOTTED:
196 *line_style = 2;
197 *style_val = 10.;
198 break;
199 case PEN_SOLID:
200 default:
201 *line_style = 0;
202 *style_val = 0.;
203 break;
204 }
205}
206
207static void pic_comment(GVJ_t *job, char *str)
208{
209 gvprintf(job, "%s %s\n", EscComment, str);
210}
211
212static void pic_begin_graph(GVJ_t * job)
213{
214 obj_state_t *obj = job->obj;
215
216 gvprintf(job, "%s Creator: %s version %s (%s)\n",
217 EscComment, job->common->info[0], job->common->info[1], job->common->info[2]);
218 gvprintf(job, "%s Title: %s\n", EscComment, agnameof(obj->u.g));
219 gvprintf(job,
220 "%s save point size and font\n.nr .S \\n(.s\n.nr DF \\n(.f\n",
221 EscComment);
222}
223
224static void pic_end_graph(GVJ_t * job)
225{
226 gvprintf(job,
227 "%s restore point size and font\n.ps \\n(.S\n.ft \\n(DF\n",
228 EscComment);
229}
230
231static void pic_begin_page(GVJ_t * job)
232{
233 box pbr = job->pageBoundingBox;
234 double height, width;
235
236 if (onetime && job->rotation && (job->rotation != 90)) {
237 unsupported("rotation");
238 onetime = FALSE;
239 }
240 height = PS2INCH((double) (pbr.UR.y) - (double) (pbr.LL.y));
241 width = PS2INCH((double) (pbr.UR.x) - (double) (pbr.LL.x));
242 if (job->rotation == 90) {
243 double temp = width;
244 width = height;
245 height = temp;
246 }
247 gvprintf(job, ".PS %.5f %.5f\n", width, height);
248 gvprintf(job,
249 "%s to change drawing size, multiply the width and height on the .PS line above and the number on the two lines below (rounded to the nearest integer) by a scale factor\n",
250 EscComment);
251 if (width > 0.0) {
252 Fontscale = log10(width);
253 Fontscale += 3.0 - (int) Fontscale; /* between 3.0 and 4.0 */
254 } else
255 Fontscale = 3.0;
256 Fontscale = pow(10.0, Fontscale); /* a power of 10 times width, between 1000 and 10000 */
257 gvprintf(job, ".nr SF %.0f\nscalethickness = %.0f\n", Fontscale,
258 Fontscale);
259 gvprintf(job,
260 "%s don't change anything below this line in this drawing\n",
261 EscComment);
262 gvprintf(job,
263 "%s non-fatal run-time pic version determination, version 2\n",
264 EscComment);
265 gvprintf(job,
266 "boxrad=2.0 %s will be reset to 0.0 by gpic only\n",
267 EscComment);
268 gvprintf(job, "scale=1.0 %s required for comparisons\n",
269 EscComment);
270 gvprintf(job,
271 "%s boxrad is now 0.0 in gpic, else it remains 2.0\n",
272 EscComment);
273 gvprintf(job,
274 "%s dashwid is 0.1 in 10th Edition, 0.05 in DWB 2 and in gpic\n",
275 EscComment);
276 gvprintf(job,
277 "%s fillval is 0.3 in 10th Edition (fill 0 means black), 0.5 in gpic (fill 0 means white), undefined in DWB 2\n",
278 EscComment);
279 gvprintf(job,
280 "%s fill has no meaning in DWB 2, gpic can use fill or filled, 10th Edition uses fill only\n",
281 EscComment);
282 gvprintf(job,
283 "%s DWB 2 doesn't use fill and doesn't define fillval\n",
284 EscComment);
285 gvprintf(job,
286 "%s reset works in gpic and 10th edition, but isn't defined in DWB 2\n",
287 EscComment);
288 gvprintf(job, "%s DWB 2 compatibility definitions\n",
289 EscComment);
290 gvprintf(job,
291 "if boxrad > 1.0 && dashwid < 0.075 then X\n\tfillval = 1;\n\tdefine fill Y Y;\n\tdefine solid Y Y;\n\tdefine reset Y scale=1.0 Y;\nX\n");
292 gvprintf(job, "reset %s set to known state\n", EscComment);
293 gvprintf(job, "%s GNU pic vs. 10th Edition d\\(e'tente\n",
294 EscComment);
295 gvprintf(job,
296 "if fillval > 0.4 then X\n\tdefine setfillval Y fillval = 1 - Y;\n\tdefine bold Y thickness 2 Y;\n");
297 gvprintf(job,
298 "\t%s if you use gpic and it barfs on encountering \"solid\",\n",
299 EscComment);
300 gvprintf(job,
301 "\t%s\tinstall a more recent version of gpic or switch to DWB or 10th Edition pic;\n",
302 EscComment);
303 gvprintf(job,
304 "\t%s\tsorry, the groff folks changed gpic; send any complaint to them;\n",
305 EscComment);
306 gvprintf(job,
307 "X else Z\n\tdefine setfillval Y fillval = Y;\n\tdefine bold Y Y;\n\tdefine filled Y fill Y;\nZ\n");
308 gvprintf(job,
309 "%s arrowhead has no meaning in DWB 2, arrowhead = 7 makes filled arrowheads in gpic and in 10th Edition\n",
310 EscComment);
311 gvprintf(job,
312 "%s arrowhead is undefined in DWB 2, initially 1 in gpic, 2 in 10th Edition\n",
313 EscComment);
314 gvprintf(job, "arrowhead = 7 %s not used by graphviz\n",
315 EscComment);
316 gvprintf(job,
317 "%s GNU pic supports a boxrad variable to draw boxes with rounded corners; DWB and 10th Ed. do not\n",
318 EscComment);
319 gvprintf(job, "boxrad = 0 %s no rounded corners in graphviz\n",
320 EscComment);
321 gvprintf(job,
322 "%s GNU pic supports a linethick variable to set line thickness; DWB and 10th Ed. do not\n",
323 EscComment);
324 gvprintf(job, "linethick = 0; oldlinethick = linethick\n");
325 gvprintf(job,
326 "%s .PS w/o args causes GNU pic to scale drawing to fit 8.5x11 paper; DWB does not\n",
327 EscComment);
328 gvprintf(job,
329 "%s maxpsht and maxpswid have no meaning in DWB 2.0, set page boundaries in gpic and in 10th Edition\n",
330 EscComment);
331 gvprintf(job,
332 "%s maxpsht and maxpswid are predefined to 11.0 and 8.5 in gpic\n",
333 EscComment);
334 gvprintf(job, "maxpsht = %f\nmaxpswid = %f\n", height, width);
335 gvprintf(job, "Dot: [\n");
336 gvprintf(job,
337 "define attrs0 %% %%; define unfilled %% %%; define rounded %% %%; define diagonals %% %%\n");
338}
339
340static void pic_end_page(GVJ_t * job)
341{
342 gvprintf(job,
343 "]\n.PE\n");
344}
345
346static void pic_textspan(GVJ_t * job, pointf p, textspan_t * span)
347{
348 static char *lastname;
349 static int lastsize;
350 int sz;
351
352 switch (span->just) {
353 case 'l':
354 break;
355 case 'r':
356 p.x -= span->size.x;
357 break;
358 default:
359 case 'n':
360 p.x -= span->size.x / 2;
361 break;
362 }
363 /* Why on earth would we do this. But it works. SCN 2/26/2002 */
364 p.y += span->font->size / (3.0 * POINTS_PER_INCH);
365 p.x += span->size.x / (2.0 * POINTS_PER_INCH);
366
367 if (span->font->name && (!(lastname) || strcmp(lastname, span->font->name))) {
368 gvprintf(job, ".ft %s\n", picfontname(span->font->name));
369 lastname = span->font->name;
370 }
371 if ((sz = (int)span->font->size) < 1)
372 sz = 1;
373 if (sz != lastsize) {
374 gvprintf(job, ".ps %d*\\n(SFu/%.0fu\n", sz, Fontscale);
375 lastsize = sz;
376 }
377 gvprintf(job, "\"%s\" at (%.5f,%.5f);\n",
378 pic_string(span->str), p.x, p.y);
379}
380
381static void pic_ellipse(GVJ_t * job, pointf * A, int filled)
382{
383 /* A[] contains 2 points: the center and corner. */
384
385 gvprintf(job,
386 "ellipse attrs%d %swid %.5f ht %.5f at (%.5f,%.5f);\n", 1,
387 filled ? "fill " : "",
388 PS2INCH(2*(A[1].x - A[0].x)),
389 PS2INCH(2*(A[1].y - A[0].y)),
390 PS2INCH(A[0].x),
391 PS2INCH(A[0].y));
392}
393
394static void pic_bezier(GVJ_t * job, pointf * A, int n, int arrow_at_start,
395// start_y, end_x, end_y);
396 int arrow_at_end, int filled)
397{
398 obj_state_t *obj = job->obj;
399
400// int object_code = 3; /* always 3 for spline */
401 int sub_type;
402 int line_style; /* solid, dotted, dashed */
403// int thickness = obj->penwidth;
404// int pen_color = obj->pencolor.u.index;
405 int fill_color = obj->fillcolor.u.index;
406// int pen_style = 0; /* not used */
407 int area_fill;
408 double style_val;
409// int cap_style = 0;
410// int forward_arrow = 0;
411// int backward_arrow = 0;
412 int npoints = n;
413 int i;
414
415 pointf pf, V[4];
416 point p;
417 int j, step;
418 int count = 0;
419 int size;
420
421 char *buffer;
422 char *buf;
423 buffer =
424 malloc((npoints + 1) * (BEZIERSUBDIVISION +
425 1) * 20 * sizeof(char));
426 buf = buffer;
427
428 pic_line_style(obj, &line_style, &style_val);
429
430 if (filled) {
431 sub_type = 5; /* closed X-spline */
432 area_fill = 20; /* fully saturated color */
433 fill_color = job->obj->fillcolor.u.index;
434 }
435 else {
436 sub_type = 4; /* opened X-spline */
437 area_fill = -1;
438 fill_color = 0;
439 }
440 V[3].x = A[0].x;
441 V[3].y = A[0].y;
442 /* Write first point in line */
443 count++;
444 PF2P(A[0], p);
445 size = sprintf(buf, " %d %d", p.x, p.y);
446 buf += size;
447 /* write subsequent points */
448 for (i = 0; i + 3 < n; i += 3) {
449 V[0] = V[3];
450 for (j = 1; j <= 3; j++) {
451 V[j].x = A[i + j].x;
452 V[j].y = A[i + j].y;
453 }
454 for (step = 1; step <= BEZIERSUBDIVISION; step++) {
455 count++;
456 pf = Bezier (V, 3, (double) step / BEZIERSUBDIVISION, NULL, NULL);
457 PF2P(pf, p);
458 size = sprintf(buf, " %d %d", p.x, p.y);
459 buf += size;
460 }
461 }
462
463// gvprintf(job, "%d %d %d %d %d %d %d %d %d %.1f %d %d %d %d\n",
464// object_code,
465// sub_type,
466// line_style,
467// thickness,
468// pen_color,
469// fill_color,
470// depth,
471// pen_style,
472// area_fill,
473// style_val, cap_style, forward_arrow, backward_arrow, count);
474
475 gvprintf(job, " %s\n", buffer); /* print points */
476 free(buffer);
477 for (i = 0; i < count; i++) {
478 gvprintf(job, " %d", i % (count + 1) ? 1 : 0); /* -1 on all */
479 }
480 gvputs(job, "\n");
481}
482
483static void pic_polygon(GVJ_t * job, pointf * A, int n, int filled)
484{
485 obj_state_t *obj = job->obj;
486
487// int object_code = 2; /* always 2 for polyline */
488// int sub_type = 3; /* always 3 for polygon */
489 int line_style; /* solid, dotted, dashed */
490// int thickness = obj->penwidth;
491// int pen_color = obj->pencolor.u.index;
492// int fill_color = obj->fillcolor.u.index;
493// int pen_style = 0; /* not used */
494// int area_fill = filled ? 20 : -1;
495 double style_val;
496// int join_style = 0;
497// int cap_style = 0;
498// int radius = 0;
499// int forward_arrow = 0;
500// int backward_arrow = 0;
501// int npoints = n + 1;
502
503 pic_line_style(obj, &line_style, &style_val);
504
505// gvprintf(job,
506// "%d %d %d %d %d %d %d %d %d %.1f %d %d %d %d %d %d\n",
507// object_code, sub_type, line_style, thickness, pen_color,
508// fill_color, depth, pen_style, area_fill, style_val, join_style,
509// cap_style, radius, forward_arrow, backward_arrow, npoints);
510 picptarray(job, A, n, 1); /* closed shape */
511}
512
513static void pic_polyline(GVJ_t * job, pointf * A, int n)
514{
515 obj_state_t *obj = job->obj;
516
517// int object_code = 2; /* always 2 for polyline */
518// int sub_type = 1; /* always 1 for polyline */
519 int line_style; /* solid, dotted, dashed */
520// int thickness = obj->penwidth;
521// int pen_color = obj->pencolor.u.index;
522// int fill_color = 0;
523// int pen_style = 0; /* not used */
524// int area_fill = 0;
525 double style_val;
526// int join_style = 0;
527// int cap_style = 0;
528// int radius = 0;
529// int forward_arrow = 0;
530// int backward_arrow = 0;
531// int npoints = n;
532
533 pic_line_style(obj, &line_style, &style_val);
534
535// gvprintf(job,
536// "%d %d %d %d %d %d %d %d %d %.1f %d %d %d %d %d %d\n",
537// object_code, sub_type, line_style, thickness, pen_color,
538// fill_color, depth, pen_style, area_fill, style_val, join_style,
539// cap_style, radius, forward_arrow, backward_arrow, npoints);
540 picptarray(job, A, n, 0); /* open shape */
541}
542
543gvrender_engine_t pic_engine = {
544 0, /* pic_begin_job */
545 0, /* pic_end_job */
546 pic_begin_graph,
547 pic_end_graph,
548 0, /* pic_begin_layer */
549 0, /* pic_end_layer */
550 pic_begin_page,
551 pic_end_page,
552 0, /* pic_begin_cluster */
553 0, /* pic_end_cluster */
554 0, /* pic_begin_nodes */
555 0, /* pic_end_nodes */
556 0, /* pic_begin_edges */
557 0, /* pic_end_edges */
558 0, /* pic_begin_node */
559 0, /* pic_end_node */
560 0, /* pic_begin_edge */
561 0, /* pic_end_edge */
562 0, /* pic_begin_anchor */
563 0, /* pic_end_anchor */
564 0, /* pic_begin_label */
565 0, /* pic_end_label */
566 pic_textspan,
567 0, /* pic_resolve_color */
568 pic_ellipse,
569 pic_polygon,
570 pic_bezier,
571 pic_polyline,
572 pic_comment,
573 0, /* pic_library_shape */
574};
575
576
577static gvrender_features_t render_features_pic = {
578 0, /* flags */
579 4., /* default pad - graph units */
580 NULL, /* knowncolors */
581 0, /* sizeof knowncolors */
582 HSVA_DOUBLE, /* color_type */
583};
584
585static gvdevice_features_t device_features_pic = {
586 0, /* flags */
587 {0.,0.}, /* default margin - points */
588 {0.,0.}, /* default page width, height - points */
589 {72.,72.}, /* default dpi */
590};
591
592gvplugin_installed_t gvrender_pic_types[] = {
593 {FORMAT_PIC, "pic", -1, &pic_engine, &render_features_pic},
594 {0, NULL, 0, NULL, NULL}
595};
596
597gvplugin_installed_t gvdevice_pic_types[] = {
598 {FORMAT_PIC, "pic:pic", -1, NULL, &device_features_pic},
599 {0, NULL, 0, NULL, NULL}
600};
601