Graphviz 13.0.0~dev.20241220.2304
Loading...
Searching...
No Matches
gvdevice.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 v1.0
5 * which accompanies this distribution, and is available at
6 * https://www.eclipse.org/legal/epl-v10.html
7 *
8 * Contributors: Details at https://graphviz.org
9 *************************************************************************/
10
11/*
12 * This library forms the socket for run-time loadable device plugins.
13 */
14
15#include "config.h"
16
17#include <ctype.h>
18#include <limits.h>
19#include <stdarg.h>
20#include <stdbool.h>
21#include <stdio.h>
22#include <stdlib.h>
23#include <string.h>
24#include <inttypes.h>
25#include <errno.h>
26#include <unistd.h>
27#include <util/gv_fopen.h>
28
29#ifdef _WIN32
30#include <fcntl.h>
31#include <io.h>
32#endif
33
34#ifdef HAVE_LIBZ
35#include <zlib.h>
36
37#ifndef OS_CODE
38# define OS_CODE 0x03 /* assume Unix */
39#endif
40static const unsigned char z_file_header[] =
41 {0x1f, 0x8b, /*magic*/ Z_DEFLATED, 0 /*flags*/, 0,0,0,0 /*time*/, 0 /*xflags*/, OS_CODE};
42
43static z_stream z_strm;
44static unsigned char *df;
45static unsigned int dfallocated;
46static uint64_t crc;
47#endif /* HAVE_LIBZ */
48
49#include <assert.h>
50#include <common/const.h>
51#include <gvc/gvplugin_device.h>
52#include <gvc/gvcjob.h>
53#include <gvc/gvcint.h>
54#include <gvc/gvcproc.h>
55#include <common/utils.h>
56#include <gvc/gvio.h>
57#include <util/agxbuf.h>
58#include <util/exit.h>
59#include <util/startswith.h>
60
61static size_t gvwrite_no_z(GVJ_t * job, const void *s, size_t len) {
62 if (job->gvc->write_fn) /* externally provided write discipline */
63 return job->gvc->write_fn(job, s, len);
64 if (job->output_data) {
65 if (len > job->output_data_allocated - (job->output_data_position + 1)) {
66 /* ensure enough allocation for string = null terminator */
68 job->output_data = realloc(job->output_data, job->output_data_allocated);
69 if (!job->output_data) {
70 job->common->errorfn("memory allocation failure\n");
72 }
73 }
74 memcpy(job->output_data + job->output_data_position, s, len);
76 job->output_data[job->output_data_position] = '\0'; /* keep null terminated */
77 return len;
78 }
79 assert(job->output_file != NULL);
80 return fwrite(s, sizeof(char), len, job->output_file);
81}
82
83static void auto_output_filename(GVJ_t *job)
84{
85 static agxbuf buf;
86 char *fn;
87
88 if (!(fn = job->input_filename))
89 fn = "noname.gv";
90 agxbput(&buf, fn);
91 if (job->graph_index)
92 agxbprint(&buf, ".%d", job->graph_index + 1);
93 agxbputc(&buf, '.');
94
95 {
96 const char *src = job->output_langname;
97 const char *src_end = src + strlen(src);
98 for (const char *q = src_end; ; --q) {
99 if (*q == ':') {
100 agxbprint(&buf, "%.*s.", (int)(src_end - q - 1), q + 1);
101 src_end = q;
102 }
103 if (q == src) {
104 agxbprint(&buf, "%.*s", (int)(src_end - src), src);
105 break;
106 }
107 }
108 }
109
110 job->output_filename = agxbuse(&buf);
111}
112
113/* gvdevice_initialize:
114 * Return 0 on success, non-zero on failure
115 */
117{
118 gvdevice_engine_t *gvde = job->device.engine;
119 GVC_t *gvc = job->gvc;
120
121 if (gvde && gvde->initialize) {
122 gvde->initialize(job);
123 }
124 else if (job->output_data) {
125 }
126 /* if the device has no initialization then it uses file output */
127 else if (!job->output_file) { /* if not yet opened */
130 if (job->output_filename) {
131 job->output_file = gv_fopen(job->output_filename, "w");
132 if (job->output_file == NULL) {
133 job->common->errorfn("Could not open \"%s\" for writing : %s\n",
134 job->output_filename, strerror(errno));
135 /* perror(job->output_filename); */
136 return 1;
137 }
138 }
139 else
140 job->output_file = stdout;
141
142#ifdef HAVE_SETMODE
143#ifdef O_BINARY
144 if (job->flags & GVDEVICE_BINARY_FORMAT)
145#ifdef _WIN32
146 _setmode(fileno(job->output_file), O_BINARY);
147#else
148 setmode(fileno(job->output_file), O_BINARY);
149#endif
150#endif
151#endif
152 }
153
155#ifdef HAVE_LIBZ
156 z_stream *z = &z_strm;
157
158 z->zalloc = 0;
159 z->zfree = 0;
160 z->opaque = 0;
161 z->next_in = NULL;
162 z->next_out = NULL;
163 z->avail_in = 0;
164
165 crc = crc32(0L, Z_NULL, 0);
166
167 if (deflateInit2(z, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -MAX_WBITS, MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY) != Z_OK) {
168 job->common->errorfn("Error initializing for deflation\n");
169 return 1;
170 }
171 gvwrite_no_z(job, z_file_header, sizeof(z_file_header));
172#else
173 job->common->errorfn("No libz support.\n");
174 return 1;
175#endif
176 }
177 return 0;
178}
179
180size_t gvwrite (GVJ_t * job, const char *s, size_t len)
181{
182 size_t ret, olen;
183
184 if (!len || !s)
185 return 0;
186
188#ifdef HAVE_LIBZ
189 z_streamp z = &z_strm;
190
191 size_t dflen = deflateBound(z, len);
192 if (dfallocated < dflen) {
193 dfallocated = dflen > UINT_MAX - 1 ? UINT_MAX : (unsigned)dflen + 1;
194 df = realloc(df, dfallocated);
195 if (! df) {
196 job->common->errorfn("memory allocation failure\n");
197 graphviz_exit(1);
198 }
199 }
200
201#if ZLIB_VERNUM >= 0x1290
202 crc = crc32_z(crc, (const unsigned char*)s, len);
203#else
204 crc = crc32(crc, (const unsigned char*)s, len);
205#endif
206
207 for (size_t offset = 0; offset < len; ) {
208// Suppress Clang/GCC -Wcast-qual warnings. `next_in` is morally const.
209#ifdef __GNUC__
210#pragma GCC diagnostic push
211#pragma GCC diagnostic ignored "-Wcast-qual"
212#endif
213 z->next_in = (unsigned char *)s + offset;
214#ifdef __GNUC__
215#pragma GCC diagnostic pop
216#endif
217 const unsigned chunk = len - offset > UINT_MAX
218 ? UINT_MAX : (unsigned)(len - offset);
219 z->avail_in = chunk;
220 z->next_out = df;
221 z->avail_out = dfallocated;
222 int r = deflate(z, Z_NO_FLUSH);
223 if (r != Z_OK) {
224 job->common->errorfn("deflation problem %d\n", r);
225 graphviz_exit(1);
226 }
227
228 if ((olen = (size_t)(z->next_out - df))) {
229 ret = gvwrite_no_z(job, df, olen);
230 if (ret != olen) {
231 job->common->errorfn("gvwrite_no_z problem %d\n", ret);
232 graphviz_exit(1);
233 }
234 }
235 offset += chunk - z->avail_in;
236 }
237
238#else
239 (void)olen;
240 job->common->errorfn("No libz support.\n");
241 graphviz_exit(1);
242#endif
243 }
244 else { /* uncompressed write */
245 ret = gvwrite_no_z (job, s, len);
246 if (ret != len) {
247 job->common->errorfn("gvwrite_no_z problem %d\n", len);
248 graphviz_exit(1);
249 }
250 }
251 return len;
252}
253
254int gvferror (FILE* stream)
255{
256 GVJ_t *job = (GVJ_t*)stream;
257
258 if (!job->gvc->write_fn && !job->output_data)
259 return ferror(job->output_file);
260
261 return 0;
262}
263
264int gvputs(GVJ_t * job, const char *s)
265{
266 size_t len = strlen(s);
267
268 if (gvwrite (job, s, len) != len) {
269 return EOF;
270 }
271 return 1;
272}
273
274int gvputs_xml(GVJ_t *job, const char *s) {
275 const xml_flags_t flags = {.dash = 1, .nbsp = 1};
276 return xml_escape(s, flags, (int (*)(void *, const char *))gvputs, job);
277}
278
279void gvputs_nonascii(GVJ_t *job, const char *s) {
280 for (; *s != '\0'; ++s) {
281 if (*s == '\\') {
282 gvputs(job, "\\\\");
283 } else if (isascii((int)*s)) {
284 gvputc(job, *s);
285 } else {
286 gvprintf(job, "%03o", (unsigned)*s);
287 }
288 }
289}
290
291int gvputc(GVJ_t * job, int c)
292{
293 const char cc = (char)c;
294
295 if (gvwrite (job, &cc, 1) != 1) {
296 return EOF;
297 }
298 return c;
299}
300
301int gvflush (GVJ_t * job)
302{
303 if (job->output_file
304 && ! job->external_context
305 && ! job->gvc->write_fn) {
306 return fflush(job->output_file);
307 }
308 else
309 return 0;
310}
311
312static void gvdevice_close(GVJ_t * job)
313{
314 if (job->output_filename
315 && job->output_file != stdout
316 && ! job->external_context) {
317 if (job->output_file) {
318 fclose(job->output_file);
319 job->output_file = NULL;
320 }
321 job->output_filename = NULL;
322 }
323}
324
326{
327 gvdevice_engine_t *gvde = job->device.engine;
328
329 if (gvde && gvde->format)
330 gvde->format(job);
331 gvflush (job);
332}
333
335{
336 gvdevice_engine_t *gvde = job->device.engine;
337 bool finalized_p = false;
338
340#ifdef HAVE_LIBZ
341 z_streamp z = &z_strm;
342 unsigned char out[8] = "";
343 int ret;
344 int cnt = 0;
345
346 z->next_in = out;
347 z->avail_in = 0;
348 z->next_out = df;
349 z->avail_out = dfallocated;
350 while ((ret = deflate (z, Z_FINISH)) == Z_OK && (cnt++ <= 100)) {
351 gvwrite_no_z(job, df, (size_t)(z->next_out - df));
352 z->next_out = df;
353 z->avail_out = dfallocated;
354 }
355 if (ret != Z_STREAM_END) {
356 job->common->errorfn("deflation finish problem %d cnt=%d\n", ret, cnt);
357 graphviz_exit(1);
358 }
359 gvwrite_no_z(job, df, (size_t)(z->next_out - df));
360
361 ret = deflateEnd(z);
362 if (ret != Z_OK) {
363 job->common->errorfn("deflation end problem %d\n", ret);
364 graphviz_exit(1);
365 }
366 out[0] = (unsigned char)crc;
367 out[1] = (unsigned char)(crc >> 8);
368 out[2] = (unsigned char)(crc >> 16);
369 out[3] = (unsigned char)(crc >> 24);
370 out[4] = (unsigned char)z->total_in;
371 out[5] = (unsigned char)(z->total_in >> 8);
372 out[6] = (unsigned char)(z->total_in >> 16);
373 out[7] = (unsigned char)(z->total_in >> 24);
374 gvwrite_no_z(job, out, sizeof(out));
375#else
376 job->common->errorfn("No libz support\n");
377 graphviz_exit(1);
378#endif
379 }
380
381 if (gvde) {
382 if (gvde->finalize) {
383 gvde->finalize(job);
384 finalized_p = true;
385 }
386 }
387
388 if (! finalized_p) {
389 /* if the device has no finalization then it uses file output */
390 gvflush (job);
391 gvdevice_close(job);
392 }
393}
394
395void gvprintf(GVJ_t * job, const char *format, ...)
396{
397 agxbuf buf = {0};
398 va_list argp;
399
400 va_start(argp, format);
401 int len = vagxbprint(&buf, format, argp);
402 if (len < 0) {
403 va_end(argp);
404 agerrorf("gvprintf: %s\n", strerror(errno));
405 return;
406 }
407 va_end(argp);
408
409 gvwrite(job, agxbuse(&buf), (size_t)len);
410 agxbfree(&buf);
411}
412
413
414/* Test with:
415 * cc -DGVPRINTNUM_TEST gvprintnum.c -o gvprintnum
416 */
417
418/* use macro so maxnegnum is stated just once for both double and string versions */
419#define val_str(n, x) static double n = x; static char n##str[] = #x;
420val_str(maxnegnum, -999999999999999.99)
421
422static void gvprintnum(agxbuf *xb, double number) {
423 /*
424 number limited to a working range: maxnegnum >= n >= -maxnegnum
425 suppressing trailing "0" and "."
426 */
427
428 if (number < maxnegnum) { /* -ve limit */
429 agxbput(xb, maxnegnumstr);
430 return;
431 }
432 if (number > -maxnegnum) { /* +ve limit */
433 agxbput(xb, maxnegnumstr + 1); // +1 to skip the '-' sign
434 return;
435 }
436
437 agxbprint(xb, "%.03f", number);
439
440 // strip off unnecessary leading '0'
441 {
442 char *staging = agxbdisown(xb);
443 if (startswith(staging, "0.")) {
444 memmove(staging, &staging[1], strlen(staging));
445 } else if (startswith(staging, "-0.")) {
446 memmove(&staging[1], &staging[2], strlen(&staging[1]));
447 }
448 agxbput(xb, staging);
449 free(staging);
450 }
451}
452
453
454#ifdef GVPRINTNUM_TEST
455int main (int argc, char *argv[])
456{
457 agxbuf xb = {0};
458 char *buf;
459 size_t len;
460
461 double test[] = {
462 -maxnegnum*1.1, -maxnegnum*.9,
463 1e8, 10.008, 10, 1, .1, .01,
464 .006, .005, .004, .001, 1e-8,
465 0, -0,
466 -1e-8, -.001, -.004, -.005, -.006,
467 -.01, -.1, -1, -10, -10.008, -1e8,
468 maxnegnum*.9, maxnegnum*1.1
469 };
470 int i = sizeof(test) / sizeof(test[0]);
471
472 while (i--) {
473 gvprintnum(&xb, test[i]);
474 buf = agxbuse(&xb);;
475 fprintf (stdout, "%g = %s %d\n", test[i], buf, len);
476 }
477
478 agxbfree(&xb);
479 graphviz_exit(0);
480}
481#endif
482
483/* gv_trim_zeros
484* Identify Trailing zeros and decimal point, if possible.
485* Assumes the input is the result of %.02f printing.
486*/
487static size_t gv_trim_zeros(const char *buf) {
488 char *dotp = strchr(buf, '.');
489 if (dotp == NULL) {
490 return strlen(buf);
491 }
492
493 // check this really is the result of %.02f printing
494 assert(isdigit((int)dotp[1]) && isdigit((int)dotp[2]) && dotp[3] == '\0');
495
496 if (dotp[2] == '0') {
497 if (dotp[1] == '0') {
498 return (size_t)(dotp - buf);
499 } else {
500 return (size_t)(dotp - buf) + 2;
501 }
502 }
503
504 return strlen(buf);
505}
506
507void gvprintdouble(GVJ_t * job, double num)
508{
509 // Prevents values like -0
510 if (num > -0.005 && num < 0.005)
511 {
512 gvwrite(job, "0", 1);
513 return;
514 }
515
516 char buf[50];
517
518 snprintf(buf, 50, "%.02f", num);
519 size_t len = gv_trim_zeros(buf);
520
521 gvwrite(job, buf, len);
522}
523
525{
526 agxbuf xb = {0};
527
528 gvprintnum(&xb, p.x);
529 const char *buf = agxbuse(&xb);
530 gvwrite(job, buf, strlen(buf));
531 gvwrite(job, " ", 1);
532 gvprintnum(&xb, p.y);
533 buf = agxbuse(&xb);
534 gvwrite(job, buf, strlen(buf));
535 agxbfree(&xb);
536}
537
538void gvprintpointflist(GVJ_t *job, pointf *p, size_t n) {
539 const char *separator = "";
540 for (size_t i = 0; i < n; ++i) {
541 gvputs(job, separator);
542 gvprintpointf(job, p[i]);
543 separator = " ";
544 }
545}
static void out(agerrlevel_t level, const char *fmt, va_list args)
Report messages using a user-supplied or default write function.
Definition agerror.c:84
static void agxbfree(agxbuf *xb)
free any malloced resources
Definition agxbuf.h:78
static int agxbprint(agxbuf *xb, const char *fmt,...)
Printf-style output to an agxbuf.
Definition agxbuf.h:234
static int vagxbprint(agxbuf *xb, const char *fmt, va_list ap)
vprintf-style output to an agxbuf
Definition agxbuf.h:162
static WUR char * agxbuse(agxbuf *xb)
Definition agxbuf.h:307
static int agxbputc(agxbuf *xb, char c)
add character to buffer
Definition agxbuf.h:277
static char * agxbdisown(agxbuf *xb)
Definition agxbuf.h:327
static NORETURN void graphviz_exit(int status)
Definition exit.h:23
static int flags
Definition gc.c:61
static double len(glCompPoint p)
Definition glutils.c:150
static attrs_t * L
Definition gmlparse.c:93
node NULL
Definition grammar.y:163
static int cnt(Dict_t *d, Dtlink_t **set)
Definition graph.c:210
void agerrorf(const char *fmt,...)
Definition agerror.c:165
static GVC_t * gvc
Definition gv.cpp:23
FILE * gv_fopen(const char *filename, const char *mode)
Definition gv_fopen.c:32
wrapper around fopen for internal library usage
swig_ptr_object_handlers offset
Definition gv_php.cpp:5907
#define GVDEVICE_COMPRESSED_FORMAT
Definition gvcjob.h:92
#define GVDEVICE_BINARY_FORMAT
Definition gvcjob.h:91
double number
Definition gvdevice.c:422
agxbput(xb, staging)
size_t gvwrite(GVJ_t *job, const char *s, size_t len)
Definition gvdevice.c:180
int gvputc(GVJ_t *job, int c)
Definition gvdevice.c:291
static void auto_output_filename(GVJ_t *job)
Definition gvdevice.c:83
void gvdevice_finalize(GVJ_t *job)
Definition gvdevice.c:334
static void gvdevice_close(GVJ_t *job)
Definition gvdevice.c:312
void gvputs_nonascii(GVJ_t *job, const char *s)
Definition gvdevice.c:279
static size_t gvwrite_no_z(GVJ_t *job, const void *s, size_t len)
Definition gvdevice.c:61
int gvputs_xml(GVJ_t *job, const char *s)
Definition gvdevice.c:274
agxbuf_trim_zeros(xb)
int gvflush(GVJ_t *job)
Definition gvdevice.c:301
int gvferror(FILE *stream)
Definition gvdevice.c:254
int gvdevice_initialize(GVJ_t *job)
Definition gvdevice.c:116
int gvputs(GVJ_t *job, const char *s)
Definition gvdevice.c:264
#define val_str(n, x)
Definition gvdevice.c:419
void gvdevice_format(GVJ_t *job)
Definition gvdevice.c:325
void gvprintpointflist(GVJ_t *job, pointf *p, size_t n)
Definition gvdevice.c:538
void gvprintpointf(GVJ_t *job, pointf p)
Definition gvdevice.c:524
static size_t gv_trim_zeros(const char *buf)
Definition gvdevice.c:487
free(staging)
void gvprintf(GVJ_t *job, const char *format,...)
Definition gvdevice.c:395
void gvprintdouble(GVJ_t *job, double num)
Definition gvdevice.c:507
GVIO_API const char * format
Definition gvio.h:51
static int z
static bool startswith(const char *s, const char *prefix)
does the string s begin with the string prefix?
Definition startswith.h:11
bool auto_outfile_names
Definition gvcommon.h:23
void(* errorfn)(const char *fmt,...)
Definition gvcommon.h:24
Definition gvcint.h:80
GVCOMMON_t common
Definition gvcint.h:81
size_t(* write_fn)(GVJ_t *job, const char *s, size_t len)
Definition gvcint.h:103
size_t output_data_allocated
Definition gvcjob.h:279
size_t output_data_position
Definition gvcjob.h:280
int flags
Definition gvcjob.h:299
const char * output_filename
Definition gvcjob.h:276
gvplugin_active_device_t device
Definition gvcjob.h:286
char * output_data
Definition gvcjob.h:278
bool external_context
Definition gvcjob.h:296
GVCOMMON_t * common
Definition gvcjob.h:267
FILE * output_file
Definition gvcjob.h:277
GVC_t * gvc
Definition gvcjob.h:263
char * input_filename
Definition gvcjob.h:271
const char * output_langname
Definition gvcjob.h:282
int graph_index
Definition gvcjob.h:272
void(* format)(GVJ_t *firstjob)
void(* finalize)(GVJ_t *firstjob)
void(* initialize)(GVJ_t *firstjob)
gvdevice_engine_t * engine
Definition gvcjob.h:128
double x
Definition geom.h:29
double y
Definition geom.h:29
unsigned dash
Definition utils.h:42
int main()
Definition grammar.c:93
int xml_escape(const char *s, xml_flags_t flags, int(*cb)(void *state, const char *s), void *state)
Definition xml.c:179