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