Graphviz 14.1.5~dev.20260409.1203
Loading...
Searching...
No Matches
gvrender_core_pic.c
Go to the documentation of this file.
1/*************************************************************************
2 * Copyright (c) 2011 AT&T Intellectual Property
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License v2.0
5 * which accompanies this distribution, and is available at
6 * https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.html
7 *
8 * Contributors: Details at https://graphviz.org
9 *************************************************************************/
10
11#include "config.h"
12#include <math.h>
13#include <stdatomic.h>
14#include <stdbool.h>
15#include <stdint.h>
16#include <stdlib.h>
17#include <string.h>
18#include <stdarg.h>
19
20#include <gvc/gvplugin_render.h>
21#include <gvc/gvplugin_device.h>
22#include <gvc/gvio.h>
23#include <common/utils.h>
24#include <common/color.h>
25#include <common/colorprocs.h>
26
27#include <common/const.h>
28#include <util/agxbuf.h>
29#include <util/gv_math.h>
30#include <util/strview.h>
31
32/* Number of points to split splines into */
33#define BEZIERSUBDIVISION 6
34
36
43static double get_fontscale(const GVJ_t *job, double *height, double *width) {
44 assert(job != NULL);
45 assert(height != NULL);
46 assert(width != NULL);
47
48 const box pbr = job->pageBoundingBox;
49 *height = PS2INCH((double)pbr.UR.y - pbr.LL.y);
50 *width = PS2INCH((double)pbr.UR.x - pbr.LL.x);
51 if (job->rotation == 90) {
52 SWAP(width, height);
53 }
54 double fontscale;
55 if (*width > 0.0) {
56 fontscale = log10(*width);
57 fontscale += 3.0 - (int)fontscale; // between 3.0 and 4.0
58 } else {
59 fontscale = 3.0;
60 }
61 fontscale = pow(10.0, fontscale); // a power of 10 times width, between 1000 and 10000
62 return fontscale;
63}
64
65/* There are a couple of ways to generate output:
66 1. generate for whatever size is given by the bounding box
67 - the drawing at its "natural" size might not fit on a physical page
68 ~ dot size specification can be used to scale the drawing
69 ~ 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)
70 - some troff implementations may clip large graphs
71 ~ handle by scaling to manageable size
72 - give explicit width and height as parameters to .PS
73 - pic scale variable is reset to 1.0
74 - fonts are printed as size specified by caller, modified by user scaling
75 2. scale to fit on a physical page
76 - requires an assumption of page size (GNU pic assumes 8.5x11.0 inches)
77 ~ any assumption is bound to be wrong more often than right
78 - requires separate scaling of font point sizes since pic's scale variable doesn't affect text
79 ~ possible, as above
80 - likewise for line thickness
81 - GNU pic does this (except for fonts) if .PS is used without explicit width or height; DWB pic does not
82 ~ pic variants likely to cause trouble
83 The first approach is used here.
84*/
85
86static const char pic_comments[] = "# "; /* PIC comment */
87static const char troff_comments[] = ".\\\" "; /* troff comment */
88static const char picgen_msghdr[] = "dot pic plugin: ";
89
90static void unsupported(char *s)
91{
92 agwarningf("%s%s unsupported\n", picgen_msghdr, s);
93}
94
95/* troff font mapping */
96typedef struct {
97 char trname[3], *psname;
98} fontinfo;
99
100static const fontinfo fonttab[] = {
101 {"AB", "AvantGarde-Demi"},
102 {"AI", "AvantGarde-BookOblique"},
103 {"AR", "AvantGarde-Book"},
104 {"AX", "AvantGarde-DemiOblique"},
105 {"B ", "Times-Bold"},
106 {"BI", "Times-BoldItalic"},
107 {"CB", "Courier-Bold"},
108 {"CO", "Courier"},
109 {"CX", "Courier-BoldOblique"},
110 {"H ", "Helvetica"},
111 {"HB", "Helvetica-Bold"},
112 {"HI", "Helvetica-Oblique"},
113 {"HX", "Helvetica-BoldOblique"},
114 {"Hb", "Helvetica-Narrow-Bold"},
115 {"Hi", "Helvetica-Narrow-Oblique"},
116 {"Hr", "Helvetica-Narrow"},
117 {"Hx", "Helvetica-Narrow-BoldOblique"},
118 {"I ", "Times-Italic"},
119 {"KB", "Bookman-Demi"},
120 {"KI", "Bookman-LightItalic"},
121 {"KR", "Bookman-Light"},
122 {"KX", "Bookman-DemiItalic"},
123 {"NB", "NewCenturySchlbk-Bold"},
124 {"NI", "NewCenturySchlbk-Italic"},
125 {"NR", "NewCenturySchlbk-Roman"},
126 {"NX", "NewCenturySchlbk-BoldItalic"},
127 {"PA", "Palatino-Roman"},
128 {"PB", "Palatino-Bold"},
129 {"PI", "Palatino-Italic"},
130 {"PX", "Palatino-BoldItalic"},
131 {"R ", "Times-Roman"},
132 {"S ", "Symbol"},
133 {"ZD", "ZapfDingbats"},
134};
135static const size_t fonttab_size = sizeof(fonttab) / sizeof(fonttab[0]);
136
137#ifdef HAVE_MEMRCHR
138void *memrchr(const void *s, int c, size_t n);
139#else
140static const void *memrchr(const void *s, int c, size_t n) {
141 const char *str = s;
142 for (size_t i = n - 1; i != SIZE_MAX; --i) {
143 if (str[i] == c) {
144 return &str[i];
145 }
146 }
147 return NULL;
148}
149#endif
150
151static const char *picfontname(strview_t psname) {
152 for (size_t i = 0; i < fonttab_size; ++i)
153 if (strview_str_eq(psname, fonttab[i].psname))
154 return fonttab[i].trname;
155 agerrorf("%s%.*s is not a troff font\n", picgen_msghdr,
156 (int)psname.size, psname.data);
157 /* try base font names, e.g. Helvetica-Outline-Oblique -> Helvetica-Outline -> Helvetica */
158 const char *dash = memrchr(psname.data, '-', psname.size);
159 if (dash != NULL) {
160 strview_t prefix = {.data = psname.data,
161 .size = (size_t)(dash - psname.data)};
162 return picfontname(prefix);
163 }
164 return "R";
165}
166
167static void picptarray(GVJ_t *job, pointf *A, size_t n, int close) {
168 for (size_t i = 0; i < n; i++) {
169 if (i == 0) {
170 gvprintf(job, "move to (%.0f, %.0f)", A[i].x, A[i].y);
171 } else {
172 gvprintf(job, "; line to (%.0f, %.0f)", A[i].x, A[i].y);
173 }
174 }
175 if (close) {
176 gvprintf(job, "; line to (%.0f, %.0f)", A[0].x, A[0].y);
177 }
178 gvputs(job, "\n");
179}
180
181static void pic_comment(GVJ_t *job, char *str)
182{
183 gvprintf(job, "%s %s\n", pic_comments, str);
184}
185
186static void pic_begin_graph(GVJ_t * job)
187{
188 obj_state_t *obj = job->obj;
189
190 gvprintf(job, "%s Creator: %s version %s (%s)\n",
191 troff_comments, job->common->info[0], job->common->info[1], job->common->info[2]);
192 gvprintf(job, "%s Title: %s\n", troff_comments, agnameof(obj->u.g));
193 gvprintf(job,
194 "%s save point size and font\n.nr .S \\n(.s\n.nr DF \\n(.f\n",
196}
197
199typedef struct {
200 char *name;
201 double size;
202} font_t;
203
204static void pic_end_graph(GVJ_t * job)
205{
206 gvprintf(job,
207 "%s restore point size and font\n.ps \\n(.S\n.ft \\n(DF\n",
209
210 // discard any cached font
211 font_t *const f = job->window;
212 if (f != NULL) {
213 free(f->name);
214 }
215 free(job->window);
216 job->window = NULL;
217}
218
219static void pic_begin_page(GVJ_t * job)
220{
221 static atomic_flag onetime;
222 if (!atomic_flag_test_and_set(&onetime) && job->rotation && job->rotation != 90) {
223 unsupported("rotation");
224 }
225 double height;
226 double width;
227 const double fontscale = get_fontscale(job, &height, &width);
228 gvprintf(job, ".PS %.5f %.5f\n", width, height);
229 gvprintf(job,
230 "%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",
232 gvprintf(job, ".nr SF %.0f\nscalethickness = %.0f\n", fontscale, fontscale);
233 gvprintf(job,
234 "%s don't change anything below this line in this drawing\n",
236 gvprintf(job,
237 "%s non-fatal run-time pic version determination, version 2\n",
239 gvprintf(job,
240 "boxrad=2.0 %s will be reset to 0.0 by gpic only\n",
242 gvprintf(job, "scale=1.0 %s required for comparisons\n",
244 gvprintf(job,
245 "%s boxrad is now 0.0 in gpic, else it remains 2.0\n",
247 gvprintf(job,
248 "%s dashwid is 0.1 in 10th Edition, 0.05 in DWB 2 and in gpic\n",
250 gvprintf(job,
251 "%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",
253 gvprintf(job,
254 "%s fill has no meaning in DWB 2, gpic can use fill or filled, 10th Edition uses fill only\n",
256 gvprintf(job,
257 "%s DWB 2 doesn't use fill and doesn't define fillval\n",
259 gvprintf(job,
260 "%s reset works in gpic and 10th edition, but isn't defined in DWB 2\n",
262 gvprintf(job, "%s DWB 2 compatibility definitions\n",
264 gvprintf(job,
265 "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");
266 gvprintf(job, "reset %s set to known state\n", pic_comments);
267 gvprintf(job, "%s GNU pic vs. 10th Edition d\\(e'tente\n",
269 gvprintf(job,
270 "if fillval > 0.4 then X\n\tdefine setfillval Y fillval = 1 - Y;\n\tdefine bold Y thickness 2 Y;\n");
271 gvprintf(job,
272 "\t%s if you use gpic and it barfs on encountering \"solid\",\n",
274 gvprintf(job,
275 "\t%s\tinstall a more recent version of gpic or switch to DWB or 10th Edition pic;\n",
277 gvprintf(job,
278 "\t%s\tsorry, the groff folks changed gpic; send any complaint to them;\n",
280 gvprintf(job,
281 "X else Z\n\tdefine setfillval Y fillval = Y;\n\tdefine bold Y Y;\n\tdefine filled Y fill Y;\nZ\n");
282 gvprintf(job,
283 "%s arrowhead has no meaning in DWB 2, arrowhead = 7 makes filled arrowheads in gpic and in 10th Edition\n",
285 gvprintf(job,
286 "%s arrowhead is undefined in DWB 2, initially 1 in gpic, 2 in 10th Edition\n",
288 gvprintf(job, "arrowhead = 7 %s not used by graphviz\n",
290 gvprintf(job,
291 "%s GNU pic supports a boxrad variable to draw boxes with rounded corners; DWB and 10th Ed. do not\n",
293 gvprintf(job, "boxrad = 0 %s no rounded corners in graphviz\n",
295 gvprintf(job,
296 "%s GNU pic supports a linethick variable to set line thickness; DWB and 10th Ed. do not\n",
298 gvprintf(job, "linethick = 0; oldlinethick = linethick\n");
299 gvprintf(job,
300 "%s .PS w/o args causes GNU pic to scale drawing to fit 8.5x11 paper; DWB does not\n",
302 gvprintf(job,
303 "%s maxpsht and maxpswid have no meaning in DWB 2.0, set page boundaries in gpic and in 10th Edition\n",
305 gvprintf(job,
306 "%s maxpsht and maxpswid are predefined to 11.0 and 8.5 in gpic\n",
308 gvprintf(job, "maxpsht = %f\nmaxpswid = %f\n", height, width);
309 gvprintf(job, "Dot: [\n");
310 gvprintf(job,
311 "define attrs0 %% %%; define unfilled %% %%; define rounded %% %%; define diagonals %% %%\n");
312}
313
314static void pic_end_page(GVJ_t * job)
315{
316 gvprintf(job,
317 "]\n.PE\n");
318}
319
321static bool font_name_eq(const font_t *font, const char *name) {
322 assert(name != NULL);
323 if (font == NULL) {
324 return false;
325 }
326 if (font->name == NULL) {
327 return false;
328 }
329 return strcmp(font->name, name) == 0;
330}
331
333static bool font_size_eq(const font_t *font, double size) {
334 if (font == NULL) {
335 return false;
336 }
337 return fabs(size - font->size) <= 0.5;
338}
339
340static void pic_textspan(GVJ_t * job, pointf p, textspan_t * span)
341{
342 switch (span->just) {
343 case 'l':
344 break;
345 case 'r':
346 p.x -= span->size.x;
347 break;
348 default:
349 case 'n':
350 p.x -= span->size.x / 2;
351 break;
352 }
353 /* Why on earth would we do this. But it works. SCN 2/26/2002 */
354 p.y += span->font->size / (3.0 * POINTS_PER_INCH);
355 p.x += span->size.x / (2.0 * POINTS_PER_INCH);
356
357 if (span->font->name && !font_name_eq(job->window, span->font->name)) {
358 gvprintf(job, ".ft %s\n", picfontname(strview(span->font->name, '\0')));
359
360 // cache the font name, ignoring failures that are non-critical
361 font_t *f = job->window;
362 if (f == NULL) {
363 f = calloc(1, sizeof(*f));
364 }
365 if (f != NULL) {
366 free(f->name);
367 f->name = strdup(span->font->name);
368 }
369 job->window = f;
370 }
371 double sz = fmax(span->font->size, 1);
372 if (!font_size_eq(job->window, sz)) {
373 const double fontscale = get_fontscale(job, &(double){0}, &(double){0});
374 gvprintf(job, ".ps %.0f*\\n(SFu/%.0fu\n", sz, fontscale);
375
376 // cache the font size, ignoring failures that are non-critical
377 font_t *f = job->window;
378 if (f == NULL) {
379 f = calloc(1, sizeof(*f));
380 }
381 if (f != NULL) {
382 f->size = sz;
383 }
384 job->window = f;
385 }
386 gvputc(job, '"');
387 gvputs_nonascii(job, span->str);
388 gvprintf(job, "\" at (%.5f,%.5f);\n", p.x, p.y);
389}
390
391static void pic_ellipse(GVJ_t * job, pointf * A, int filled)
392{
393 /* A[] contains 2 points: the center and corner. */
394
395 gvprintf(job,
396 "ellipse attrs0 %swid %.5f ht %.5f at (%.5f,%.5f);\n",
397 filled ? "fill " : "",
398 PS2INCH(2*(A[1].x - A[0].x)),
399 PS2INCH(2*(A[1].y - A[0].y)),
400 PS2INCH(A[0].x),
401 PS2INCH(A[0].y));
402}
403
404static void pic_bezier(GVJ_t *job, pointf *A, size_t n, int filled) {
405 (void)filled;
406
407 pointf V[4];
408
409 V[3].x = A[0].x;
410 V[3].y = A[0].y;
411 /* Write first point in line */
412 gvprintf(job, "move to (%.0f, %.0f)", A[0].x, A[0].y);
413 /* write subsequent points */
414 for (size_t i = 0; i + 3 < n; i += 3) {
415 V[0] = V[3];
416 for (size_t j = 1; j <= 3; j++) {
417 V[j].x = A[i + j].x;
418 V[j].y = A[i + j].y;
419 }
420 for (int step = 1; step <= BEZIERSUBDIVISION; step++) {
421 pointf pf = Bezier(V, (double)step / BEZIERSUBDIVISION, NULL, NULL);
422 gvprintf(job, "; spline to (%.0f, %.0f)", pf.x, pf.y);
423 }
424 }
425
426 gvputs(job, "\n");
427}
428
429static void pic_polygon(GVJ_t *job, pointf *A, size_t n, int filled) {
430 (void)filled;
431 picptarray(job, A, n, 1); // closed shape
432}
433
434static void pic_polyline(GVJ_t *job, pointf *A, size_t n) {
435 picptarray(job, A, n, 0); // open shape
436}
437
439 0, /* pic_begin_job */
440 0, /* pic_end_job */
443 0, /* pic_begin_layer */
444 0, /* pic_end_layer */
447 0, /* pic_begin_cluster */
448 0, /* pic_end_cluster */
449 0, /* pic_begin_nodes */
450 0, /* pic_end_nodes */
451 0, /* pic_begin_edges */
452 0, /* pic_end_edges */
453 0, /* pic_begin_node */
454 0, /* pic_end_node */
455 0, /* pic_begin_edge */
456 0, /* pic_end_edge */
457 0, /* pic_begin_anchor */
458 0, /* pic_end_anchor */
459 0, /* pic_begin_label */
460 0, /* pic_end_label */
462 0, /* pic_resolve_color */
468 0, /* pic_library_shape */
469};
470
471
473 0, /* flags */
474 4., /* default pad - graph units */
475 NULL, /* knowncolors */
476 0, /* sizeof knowncolors */
477 HSVA_DOUBLE, /* color_type */
478};
479
481 0, /* flags */
482 {0.,0.}, /* default margin - points */
483 {0.,0.}, /* default page width, height - points */
484 {72.,72.}, /* default dpi */
485};
486
491
493 {FORMAT_PIC, "pic:pic", -1, NULL, &device_features_pic},
494 {0, NULL, 0, NULL, NULL}
495};
Dynamically expanding string buffers.
@ HSVA_DOUBLE
Definition color.h:26
pointf Bezier(const pointf *V, double t, pointf *Left, pointf *Right)
Definition utils.c:175
#define A(n, t)
Definition expr.h:76
#define V
Definition gdefs.h:5
#define PS2INCH(a_points)
Definition geom.h:64
#define POINTS_PER_INCH
Definition geom.h:58
void free(void *)
require define api prefix
Definition gmlparse.y:17
#define SIZE_MAX
Definition gmlscan.c:347
node NULL
Definition grammar.y:181
void agwarningf(const char *fmt,...)
Definition agerror.c:175
void agerrorf(const char *fmt,...)
Definition agerror.c:167
char * agnameof(void *)
returns a string descriptor for the object.
Definition id.c:145
Arithmetic helper functions.
#define SWAP(a, b)
Definition gv_math.h:134
int gvputc(GVJ_t *job, int c)
Definition gvdevice.c:298
void gvputs_nonascii(GVJ_t *job, const char *s)
Definition gvdevice.c:286
int gvputs(GVJ_t *job, const char *s)
Definition gvdevice.c:266
void gvprintf(GVJ_t *job, const char *format,...)
Definition gvdevice.c:402
static void pic_bezier(GVJ_t *job, pointf *A, size_t n, int filled)
static bool font_name_eq(const font_t *font, const char *name)
does a font have the given name?
static const char * picfontname(strview_t psname)
static void pic_end_graph(GVJ_t *job)
gvplugin_installed_t gvdevice_pic_types[]
@ FORMAT_PIC
static void picptarray(GVJ_t *job, pointf *A, size_t n, int close)
static void pic_polygon(GVJ_t *job, pointf *A, size_t n, int filled)
static void pic_begin_graph(GVJ_t *job)
static gvrender_engine_t pic_engine
static const char troff_comments[]
static gvrender_features_t render_features_pic
static const char picgen_msghdr[]
static const size_t fonttab_size
static void pic_textspan(GVJ_t *job, pointf p, textspan_t *span)
static void pic_ellipse(GVJ_t *job, pointf *A, int filled)
static double get_fontscale(const GVJ_t *job, double *height, double *width)
gvplugin_installed_t gvrender_pic_types[]
static const fontinfo fonttab[]
static const void * memrchr(const void *s, int c, size_t n)
static void pic_comment(GVJ_t *job, char *str)
static void pic_end_page(GVJ_t *job)
static bool font_size_eq(const font_t *font, double size)
does a font have the given size?
static void pic_begin_page(GVJ_t *job)
static const char pic_comments[]
static void pic_polyline(GVJ_t *job, pointf *A, size_t n)
#define BEZIERSUBDIVISION
static gvdevice_features_t device_features_pic
static void unsupported(char *s)
textitem scanner parser str
Definition htmlparse.y:218
$2 font
Definition htmlparse.y:294
char ** info
Definition gvcommon.h:20
int rotation
Definition gvcjob.h:319
obj_state_t * obj
Definition gvcjob.h:269
GVCOMMON_t * common
Definition gvcjob.h:267
box pageBoundingBox
Definition gvcjob.h:329
void * window
Definition gvcjob.h:353
Definition geom.h:39
point LL
Definition geom.h:39
point UR
Definition geom.h:39
font characteristics
char * name
font name
double size
font point size
ingroup plugin_api
Definition gvplugin.h:35
graph_t * g
Definition gvcjob.h:186
union obj_state_s::@65 u
int y
Definition geom.h:27
int x
Definition geom.h:27
double x
Definition geom.h:29
double y
Definition geom.h:29
a non-owning string reference
Definition strview.h:20
const char * data
start of the pointed to string
Definition strview.h:21
size_t size
extent of the string in bytes
Definition strview.h:22
char * name
Definition textspan.h:54
double size
Definition textspan.h:57
char * str
Definition textspan.h:65
char just
'l' 'n' 'r'
Definition textspan.h:71
pointf size
Definition textspan.h:70
textfont_t * font
Definition textspan.h:66
Non-owning string references.
static bool strview_str_eq(strview_t a, const char *b)
compare a string reference to a string for equality
Definition strview.h:98
static strview_t strview(const char *referent, char terminator)
create a string reference
Definition strview.h:26
Definition grammar.c:90
int(* pf)(void *, char *,...)
Definition xdot.c:398