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