1/* $Id$ $Revision$ */
2/* vim:set shiftwidth=4 ts=8: */
3
4/*************************************************************************
5 * Copyright (c) 2015 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#ifdef _WIN32
17#include <io.h>
18#include "compat.h"
19#endif
20
21#include <stdarg.h>
22#include <stdlib.h>
23#include <string.h>
24#include <ctype.h>
25
26#include "macros.h"
27#include "const.h"
28#include "xdot.h"
29
30#include "gvplugin_render.h"
31#include "gvplugin_device.h"
32#include "agxbuf.h"
33#include "utils.h"
34#include "gvc.h"
35#include "gvio.h"
36#include "gvcint.h"
37
38typedef enum {
39 FORMAT_JSON,
40 FORMAT_JSON0,
41 FORMAT_DOT_JSON,
42 FORMAT_XDOT_JSON,
43} format_type;
44
45typedef struct {
46 int Level;
47 boolean isLatin;
48 boolean doXDot;
49 boolean Attrs_not_written_flag;
50} state_t;
51
52typedef struct {
53 Agrec_t h;
54 int id;
55} gvid_t;
56
57#define ID "id"
58#define ND_gid(n) (((gvid_t*)aggetrec(n, ID, FALSE))->id)
59#define ED_gid(n) (((gvid_t*)aggetrec(n, ID, FALSE))->id)
60#define GD_gid(n) (((gvid_t*)aggetrec(n, ID, FALSE))->id)
61
62#define IS_CLUSTER(s) (!strncmp(agnameof(s), "cluster", 7))
63
64static void json_begin_graph(GVJ_t *job)
65{
66 if (job->render.id == FORMAT_JSON) {
67 GVC_t* gvc = gvCloneGVC (job->gvc);
68 graph_t *g = job->obj->u.g;
69 gvRender (gvc, g, "xdot", NULL);
70 gvFreeCloneGVC (gvc);
71 }
72 else if (job->render.id == FORMAT_JSON0) {
73 attach_attrs(job->gvc->g);
74 }
75}
76
77#define LOCALNAMEPREFIX '%'
78
79/* stoj:
80 * Convert dot string to a valid json string embedded in double quotes.
81 */
82static char* stoj (char* ins, state_t* sp)
83{
84 char* s;
85 char* input;
86 static agxbuf xb;
87 unsigned char c;
88
89 if (sp->isLatin)
90 input = latin1ToUTF8 (ins);
91 else
92 input = ins;
93
94 if (xb.buf == NULL)
95 agxbinit(&xb, BUFSIZ, NULL);
96 for (s = input; (c = *s); s++) {
97 switch (c) {
98 case '"' :
99 agxbput(&xb, "\\\"");
100 break;
101 case '\\' :
102 agxbput(&xb, "\\\\");
103 break;
104 case '/' :
105 agxbput(&xb, "\\/");
106 break;
107 case '\b' :
108 agxbput(&xb, "\\b");
109 break;
110 case '\f' :
111 agxbput(&xb, "\\f");
112 break;
113 case '\n' :
114 agxbput(&xb, "\\n");
115 break;
116 case '\r' :
117 agxbput(&xb, "\\r");
118 break;
119 case '\t' :
120 agxbput(&xb, "\\t");
121 break;
122 default :
123 agxbputc(&xb, c);
124 break;
125 }
126 }
127 s = agxbuse(&xb);
128
129 if (sp->isLatin)
130 free (input);
131 return s;
132}
133
134static void indent(GVJ_t * job, int level)
135{
136 int i;
137 for (i = level; i > 0; i--)
138 gvputs(job, " ");
139}
140
141static void set_attrwf(Agraph_t * g, int toplevel, int value)
142{
143 Agraph_t *subg;
144 Agnode_t *n;
145 Agedge_t *e;
146
147 AGATTRWF(g) = value;
148 for (subg = agfstsubg(g); subg; subg = agnxtsubg(subg)) {
149 set_attrwf(subg, FALSE, value);
150 }
151 if (toplevel) {
152 for (n = agfstnode(g); n; n = agnxtnode(g, n)) {
153 AGATTRWF(n) = value;
154 for (e = agfstout(g, n); e; e = agnxtout(g, e))
155 AGATTRWF(e) = value;
156 }
157 }
158}
159
160static void write_polyline (GVJ_t * job, xdot_polyline* polyline)
161{
162 int i;
163 int cnt = polyline->cnt;
164 xdot_point* pts = polyline->pts;
165
166 gvprintf(job, "\"points\": [");
167 for (i = 0; i < cnt; i++) {
168 if (i > 0) gvprintf(job, ",");
169 gvprintf(job, "[%.03f,%.03f]", pts[i].x, pts[i].y);
170 }
171 gvprintf(job, "]\n");
172}
173
174static void write_stops (GVJ_t * job, int n_stops, xdot_color_stop* stp, state_t* sp)
175{
176 int i;
177
178 gvprintf(job, "\"stops\": [");
179 for (i = 0; i < n_stops; i++) {
180 if (i > 0) gvprintf(job, ",");
181 gvprintf(job, "{\"frac\": %.03f, \"color\": \"%s\"}",
182 stp[i].frac, stoj(stp[i].color, sp));
183 }
184 gvprintf(job, "]\n");
185}
186
187static void write_radial_grad (GVJ_t * job, xdot_radial_grad* rg, state_t* sp)
188{
189 indent(job, sp->Level);
190 gvprintf(job, "\"p0\": [%.03f,%.03f,%.03f],\n", rg->x0, rg->y0, rg->r0);
191 indent(job, sp->Level);
192 gvprintf(job, "\"p1\": [%.03f,%.03f,%.03f],\n", rg->x1, rg->y1, rg->r1);
193 indent(job, sp->Level);
194 write_stops (job, rg->n_stops, rg->stops, sp);
195}
196
197static void write_linear_grad (GVJ_t * job, xdot_linear_grad* lg, state_t* sp)
198{
199 indent(job, sp->Level);
200 gvprintf(job, "\"p0\": [%.03f,%.03f],\n", lg->x0, lg->y0);
201 indent(job, sp->Level);
202 gvprintf(job, "\"p1\": [%.03f,%.03f],\n", lg->x1, lg->y1);
203 indent(job, sp->Level);
204 write_stops (job, lg->n_stops, lg->stops, sp);
205}
206
207static void write_xdot (xdot_op * op, GVJ_t * job, state_t* sp)
208{
209 indent(job, sp->Level++);
210 gvputs(job, "{\n");
211 indent(job, sp->Level);
212
213 switch (op->kind) {
214 case xd_filled_ellipse :
215 case xd_unfilled_ellipse :
216 gvprintf(job, "\"op\": \"%c\",\n",
217 (op->kind == xd_filled_ellipse ? 'E' : 'e'));
218 indent(job, sp->Level);
219 gvprintf(job, "\"rect\": [%.03f,%.03f,%.03f,%.03f]\n",
220 op->u.ellipse.x, op->u.ellipse.y, op->u.ellipse.w, op->u.ellipse.h);
221 break;
222 case xd_filled_polygon :
223 case xd_unfilled_polygon :
224 gvprintf(job, "\"op\": \"%c\",\n",
225 (op->kind == xd_filled_polygon ? 'P' : 'p'));
226 indent(job, sp->Level);
227 write_polyline (job, &op->u.polygon);
228 break;
229 case xd_filled_bezier :
230 case xd_unfilled_bezier :
231 gvprintf(job, "\"op\": \"%c\",\n",
232 (op->kind == xd_filled_bezier ? 'B' : 'b'));
233 indent(job, sp->Level);
234 write_polyline (job, &op->u.bezier);
235 break;
236 case xd_polyline :
237 gvprintf(job, "\"op\": \"L\",\n");
238 indent(job, sp->Level);
239 write_polyline (job, &op->u.polyline);
240 break;
241 case xd_text :
242 gvprintf(job, "\"op\": \"T\",\n");
243 indent(job, sp->Level);
244 gvprintf(job, "\"pt\": [%.03f,%.03f],\n", op->u.text.x, op->u.text.y);
245 indent(job, sp->Level);
246 gvprintf(job, "\"align\": \"%c\",\n",
247 (op->u.text.align == xd_left? 'l' :
248 (op->u.text.align == xd_center ? 'c' : 'r')));
249 indent(job, sp->Level);
250 gvprintf(job, "\"width\": %.03f,\n", op->u.text.width);
251 indent(job, sp->Level);
252 gvprintf(job, "\"text\": \"%s\"\n", stoj(op->u.text.text, sp));
253 break;
254 case xd_fill_color :
255 case xd_pen_color :
256 gvprintf(job, "\"op\": \"%c\",\n",
257 (op->kind == xd_fill_color ? 'C' : 'c'));
258 indent(job, sp->Level);
259 gvprintf(job, "\"grad\": \"none\",\n");
260 indent(job, sp->Level);
261 gvprintf(job, "\"color\": \"%s\"\n", stoj(op->u.color, sp));
262 break;
263 case xd_grad_pen_color :
264 case xd_grad_fill_color :
265 gvprintf(job, "\"op\": \"%c\",\n",
266 (op->kind == xd_grad_fill_color ? 'C' : 'c'));
267 indent(job, sp->Level);
268 if (op->u.grad_color.type == xd_none) {
269 gvprintf(job, "\"grad\": \"none\",\n");
270 indent(job, sp->Level);
271 gvprintf(job, "\"color\": \"%s\"\n",
272 stoj(op->u.grad_color.u.clr, sp));
273 }
274 else {
275 if (op->u.grad_color.type == xd_linear) {
276 gvprintf(job, "\"grad\": \"linear\",\n");
277 indent(job, sp->Level);
278 write_linear_grad (job, &op->u.grad_color.u.ling, sp);
279 }
280 else {
281 gvprintf(job, "\"grad\": \"radial\",\n");
282 indent(job, sp->Level);
283 write_radial_grad (job, &op->u.grad_color.u.ring, sp);
284 }
285 }
286 break;
287 case xd_font :
288 gvprintf(job, "\"op\": \"F\",\n");
289 indent(job, sp->Level);
290 gvprintf(job, "\"size\": %.03f,\n", op->u.font.size);
291 indent(job, sp->Level);
292 gvprintf(job, "\"face\": \"%s\"\n", stoj(op->u.font.name, sp));
293 break;
294 case xd_style :
295 gvprintf(job, "\"op\": \"S\",\n");
296 indent(job, sp->Level);
297 gvprintf(job, "\"style\": \"%s\"\n", stoj(op->u.style, sp));
298 break;
299 case xd_image :
300 break;
301 case xd_fontchar :
302 gvprintf(job, "\"op\": \"t\",\n");
303 indent(job, sp->Level);
304 gvprintf(job, "\"fontchar\": %d\n", op->u.fontchar);
305 break;
306 }
307 sp->Level--;
308 indent(job, sp->Level);
309 gvputs(job, "}");
310}
311
312static void write_xdots (char * val, GVJ_t * job, state_t* sp)
313{
314 xdot* cmds;
315 int i;
316 int not_first = 0;
317
318 if (!val || (*val == '\0')) return;
319
320 cmds = parseXDot(val);
321 if (!cmds) {
322 agerr(AGWARN, "Could not parse xdot \"%s\"\n", val);
323 return;
324 }
325
326 gvputs(job, "\n");
327 indent(job, sp->Level++);
328 gvputs(job, "[\n");
329 for (i = 0; i < cmds->cnt; i++) {
330 if (not_first)
331 gvputs(job, ",\n");
332 else
333 not_first = 1;
334 write_xdot (cmds->ops+i, job, sp);
335 }
336 sp->Level--;
337 gvputs(job, "\n");
338 indent(job, sp->Level);
339 gvputs(job, "]");
340 freeXDot(cmds);
341}
342
343static int isXDot (char* name)
344{
345 return ((*name++ == '_') &&
346 (streq(name,"draw_") || streq(name,"ldraw_") ||
347 streq(name,"hdraw_") || streq(name,"tdraw_") ||
348 streq(name,"hldraw_") || streq(name,"tldraw_")));
349}
350
351static void write_attrs(Agobj_t * obj, GVJ_t * job, state_t* sp)
352{
353 Agraph_t* g = agroot(obj);
354 int type = AGTYPE(obj);
355 char* attrval;
356 Agsym_t* sym = agnxtattr(g, type, NULL);
357 if (!sym) return;
358
359 for (; sym; sym = agnxtattr(g, type, sym)) {
360 if (!(attrval = agxget(obj, sym))) continue;
361 if ((*attrval == '\0') && !streq(sym->name, "label")) continue;
362 gvputs(job, ",\n");
363 indent(job, sp->Level);
364 gvprintf(job, "\"%s\": ", stoj(sym->name, sp));
365 if (sp->doXDot && isXDot(sym->name))
366 write_xdots(agxget(obj, sym), job, sp);
367 else
368 gvprintf(job, "\"%s\"", stoj(agxget(obj, sym), sp));
369 }
370}
371
372static void write_hdr(Agraph_t * g, GVJ_t * job, int top, state_t* sp)
373{
374 char *name;
375
376 name = agnameof(g);
377 indent(job, sp->Level);
378 gvprintf(job, "\"name\": \"%s\"", stoj (name, sp));
379
380 if (top) {
381 gvputs(job, ",\n");
382 indent(job, sp->Level);
383 gvprintf(job, "\"directed\": %s,\n", (agisdirected(g)?"true":"false"));
384 indent(job, sp->Level);
385 gvprintf(job, "\"strict\": %s", (agisstrict(g)?"true":"false"));
386 }
387}
388
389static void write_graph(Agraph_t * g, GVJ_t * job, int top, state_t* sp);
390
391static void write_subg(Agraph_t * g, GVJ_t * job, state_t* sp)
392{
393 Agraph_t* sg;
394
395 write_graph (g, job, FALSE, sp);
396 for (sg = agfstsubg(g); sg; sg = agnxtsubg(sg)) {
397 gvputs(job, ",\n");
398 write_subg(sg, job, sp);
399 }
400}
401
402/*
403static int write_subgs(Agraph_t * g, GVJ_t * job, int top, state_t* sp)
404{
405 Agraph_t* sg;
406 int not_first = 0;
407
408 sg = agfstsubg(g);
409 if (!sg) return 0;
410
411 gvputs(job, ",\n");
412 indent(job, sp->Level++);
413 gvputs(job, "\"subgraphs\": [\n");
414 for (; sg; sg = agnxtsubg(sg)) {
415 if (not_first)
416 gvputs(job, ",\n");
417 else
418 not_first = 1;
419 write_subg (sg, job, top, sp);
420 }
421 sp->Level--;
422 gvputs(job, "\n");
423 indent(job, sp->Level);
424 gvputs(job, "]");
425 return 1;
426}
427*/
428
429static int write_subgs(Agraph_t * g, GVJ_t * job, int top, state_t* sp)
430{
431 Agraph_t* sg;
432 int not_first = 0;
433
434 sg = agfstsubg(g);
435 if (!sg) return 0;
436
437 gvputs(job, ",\n");
438 indent(job, sp->Level++);
439 if (top)
440 gvputs(job, "\"objects\": [\n");
441 else {
442 gvputs(job, "\"subgraphs\": [\n");
443 indent(job, sp->Level);
444 }
445 for (; sg; sg = agnxtsubg(sg)) {
446 if (not_first)
447 gvputs(job, ",\n");
448 else
449 not_first = 1;
450 if (top)
451 write_subg (sg, job, sp);
452 else
453 gvprintf(job, "%d", GD_gid(sg));
454 }
455 if (!top) {
456 sp->Level--;
457 gvputs(job, "\n");
458 indent(job, sp->Level);
459 gvputs(job, "]");
460 }
461
462 return 1;
463}
464
465static void write_edge(Agedge_t * e, GVJ_t * job, int top, state_t* sp)
466{
467 if (top) {
468 indent(job, sp->Level++);
469 gvputs(job, "{\n");
470 indent(job, sp->Level);
471 gvprintf(job, "\"_gvid\": %d,\n", ED_gid(e));
472 indent(job, sp->Level);
473 gvprintf(job, "\"tail\": %d,\n", ND_gid(agtail(e)));
474 indent(job, sp->Level);
475 gvprintf(job, "\"head\": %d", ND_gid(aghead(e)));
476 write_attrs((Agobj_t*)e, job, sp);
477 gvputs(job, "\n");
478 sp->Level--;
479 indent(job, sp->Level);
480 gvputs(job, "}");
481 }
482 else {
483 gvprintf(job, "%d", ED_gid(e));
484 }
485}
486
487static int write_edges(Agraph_t * g, GVJ_t * job, int top, state_t* sp)
488{
489 Agnode_t* np;
490 Agedge_t* ep;
491 int not_first = 0;
492
493 np = agfstnode(g);
494 if (!np) return 0;
495 ep = NULL;
496 /* find a first edge */
497 for (; np; np = agnxtnode(g,np)) {
498 for (ep = agfstout(g, np); ep; ep = agnxtout(g,ep)) {
499 if (ep) break;
500 }
501 if (ep) break;
502 }
503 if (!ep) return 0;
504
505 gvputs(job, ",\n");
506 indent(job, sp->Level++);
507 gvputs(job, "\"edges\": [\n");
508 if (!top)
509 indent(job, sp->Level);
510 for (; np; np = agnxtnode(g,np)) {
511 for (ep = agfstout(g, np); ep; ep = agnxtout(g,ep)) {
512 if (not_first)
513 if (top)
514 gvputs(job, ",\n");
515 else
516 gvputs(job, ",");
517 else
518 not_first = 1;
519 write_edge(ep, job, top, sp);
520 }
521 }
522 sp->Level--;
523 gvputs(job, "\n");
524 indent(job, sp->Level);
525 gvputs(job, "]");
526 return 1;
527}
528
529static void write_node(Agnode_t * n, GVJ_t * job, int top, state_t* sp)
530{
531 if (top) {
532 indent(job, sp->Level++);
533 gvputs(job, "{\n");
534 indent(job, sp->Level);
535 gvprintf(job, "\"_gvid\": %d,\n", ND_gid(n));
536 indent(job, sp->Level);
537 gvprintf(job, "\"name\": \"%s\"", stoj (agnameof(n), sp));
538 write_attrs((Agobj_t*)n, job, sp);
539 gvputs(job, "\n");
540 sp->Level--;
541 indent(job, sp->Level);
542 gvputs(job, "}");
543 }
544 else {
545 gvprintf(job, "%d", ND_gid(n));
546 }
547}
548
549static int write_nodes(Agraph_t * g, GVJ_t * job, int top, int has_subgs, state_t* sp)
550{
551 Agnode_t* n;
552 int not_first = 0;
553
554 n = agfstnode(g);
555 if (!n) {
556 if (has_subgs && top) {
557 sp->Level--;
558 gvputs(job, "\n");
559 indent(job, sp->Level);
560 gvputs(job, "]");
561 }
562 return 0;
563 }
564 gvputs(job, ",\n");
565 if (top) {
566 if (!has_subgs) {
567 indent(job, sp->Level++);
568 gvputs(job, "\"objects\": [\n");
569 }
570 }
571 else {
572 indent(job, sp->Level++);
573 gvputs(job, "\"nodes\": [\n");
574 indent(job, sp->Level);
575 }
576 for (; n; n = agnxtnode(g, n)) {
577 if (IS_CLUST_NODE(n)) continue;
578 if (not_first)
579 if (top)
580 gvputs(job, ",\n");
581 else
582 gvputs(job, ",");
583 else
584 not_first = 1;
585 write_node (n, job, top, sp);
586 }
587 sp->Level--;
588 gvputs(job, "\n");
589 indent(job, sp->Level);
590 gvputs(job, "]");
591 return 1;
592}
593
594typedef struct {
595 Dtlink_t link;
596 char* id;
597 int v;
598} intm;
599
600static void freef(Dt_t * dt, intm * obj, Dtdisc_t * disc)
601{
602 free(obj->id);
603 free(obj);
604}
605
606static Dtdisc_t intDisc = {
607 offsetof(intm, id),
608 -1,
609 offsetof(intm, link),
610 (Dtmake_f) NULL,
611 (Dtfree_f) freef,
612 (Dtcompar_f) NULL,
613 0,
614 0,
615 0
616};
617
618#define NEW(t) (t*)calloc(1,sizeof(t))
619
620static int lookup (Dt_t* map, char* name)
621{
622 intm* ip = (intm*)dtmatch(map, name);
623 if (ip) return ip->v;
624 else return -1;
625}
626
627static void insert (Dt_t* map, char* name, int v)
628{
629 intm* ip = (intm*)dtmatch(map, name);
630
631 if (ip) {
632 if (ip->v != v)
633 agerr(AGWARN, "Duplicate cluster name \"%s\"\n", name);
634 return;
635 }
636 ip = NEW(intm);
637 ip->id = strdup(name);
638 ip->v = v;
639 dtinsert (map, ip);
640}
641
642static int label_subgs(Agraph_t* g, int lbl, Dt_t* map)
643{
644 Agraph_t* sg;
645
646 if (g != agroot(g)) {
647 GD_gid(g) = lbl++;
648 if (IS_CLUSTER(g))
649 insert (map, agnameof(g), GD_gid(g));
650 }
651 for (sg = agfstsubg(g); sg; sg = agnxtsubg(sg)) {
652 lbl = label_subgs(sg, lbl, map);
653 }
654 return lbl;
655}
656
657
658static void write_graph(Agraph_t * g, GVJ_t * job, int top, state_t* sp)
659{
660 Agnode_t* np;
661 Agedge_t* ep;
662 int ncnt = 0;
663 int ecnt = 0;
664 int sgcnt = 0;
665 int has_subgs;
666 Dt_t* map;
667
668 if (top) {
669 map = dtopen (&intDisc, Dtoset);
670 aginit(g, AGNODE, ID, sizeof(gvid_t), FALSE);
671 aginit(g, AGEDGE, ID, sizeof(gvid_t), FALSE);
672 aginit(g, AGRAPH, ID, -((int)sizeof(gvid_t)), FALSE);
673 sgcnt = label_subgs(g, sgcnt, map);
674 for (np = agfstnode(g); np; np = agnxtnode(g,np)) {
675 if (IS_CLUST_NODE(np)) {
676 ND_gid(np) = lookup(map, agnameof(np));
677 }
678 else {
679 ND_gid(np) = sgcnt + ncnt++;
680 }
681 for (ep = agfstout(g, np); ep; ep = agnxtout(g,ep)) {
682 ED_gid(ep) = ecnt++;
683 }
684 }
685 dtclose(map);
686 }
687
688 indent(job, sp->Level++);
689 gvputs(job, "{\n");
690 write_hdr(g, job, top, sp);
691 write_attrs((Agobj_t*)g, job, sp);
692 if (top) {
693 gvputs(job, ",\n");
694 indent(job, sp->Level);
695 gvprintf(job, "\"_subgraph_cnt\": %d", sgcnt);
696 } else {
697 gvputs(job, ",\n");
698 indent(job, sp->Level);
699 gvprintf(job, "\"_gvid\": %d", GD_gid(g));
700 }
701 has_subgs = write_subgs(g, job, top, sp);
702 write_nodes (g, job, top, has_subgs, sp);
703 write_edges (g, job, top, sp);
704 gvputs(job, "\n");
705 sp->Level--;
706 indent(job, sp->Level);
707 if (top)
708 gvputs(job, "}\n");
709 else
710 gvputs(job, "}");
711}
712
713typedef int (*putstrfn) (void *chan, const char *str);
714typedef int (*flushfn) (void *chan);
715
716static void json_end_graph(GVJ_t *job)
717{
718 graph_t *g = job->obj->u.g;
719 state_t sp;
720 Agiodisc_t* io_save;
721 static Agiodisc_t io;
722
723 if (io.afread == NULL) {
724 io.afread = AgIoDisc.afread;
725 io.putstr = (putstrfn)gvputs;
726 io.flush = (flushfn)gvflush;
727 }
728
729 io_save = g->clos->disc.io;
730 g->clos->disc.io = &io;
731
732 set_attrwf(g, TRUE, FALSE);
733 sp.Level = 0;
734 sp.isLatin = (GD_charset(g) == CHAR_LATIN1);
735 sp.doXDot = ((job->render.id == FORMAT_JSON) || (job->render.id == FORMAT_XDOT_JSON));
736 sp.Attrs_not_written_flag = 0;
737 write_graph(g, job, TRUE, &sp);
738 /* agwrite(g, (FILE*)job); */
739}
740
741gvrender_engine_t json_engine = {
742 0, /* json_begin_job */
743 0, /* json_end_job */
744 json_begin_graph,
745 json_end_graph,
746 0, /* json_begin_layer */
747 0, /* json_end_layer */
748 0, /* json_begin_page */
749 0, /* json_end_page */
750 0, /* json_begin_cluster */
751 0, /* json_end_cluster */
752 0, /* json_begin_nodes */
753 0, /* json_end_nodes */
754 0, /* json_begin_edges */
755 0, /* json_end_edges */
756 0, /* json_begin_node */
757 0, /* json_end_node */
758 0, /* json_begin_edge */
759 0, /* json_end_edge */
760 0, /* json_begin_anchor */
761 0, /* json_end_anchor */
762 0, /* json_begin_label */
763 0, /* json_end_label */
764 0, /* json_textspan */
765 0, /* json_resolve_color */
766 0, /* json_ellipse */
767 0, /* json_polygon */
768 0, /* json_bezier */
769 0, /* json_polyline */
770 0, /* json_comment */
771 0, /* json_library_shape */
772};
773
774gvrender_features_t render_features_json1 = {
775 GVRENDER_DOES_TRANSFORM, /* not really - uses raw graph coords */ /* flags */
776 0., /* default pad - graph units */
777 NULL, /* knowncolors */
778 0, /* sizeof knowncolors */
779 COLOR_STRING, /* color_type */
780};
781
782gvrender_features_t render_features_json = {
783 GVRENDER_DOES_TRANSFORM /* not really - uses raw graph coords */
784 | GVRENDER_DOES_MAPS
785 | GVRENDER_DOES_TARGETS
786 | GVRENDER_DOES_TOOLTIPS, /* flags */
787 0., /* default pad - graph units */
788 NULL, /* knowncolors */
789 0, /* sizeof knowncolors */
790 COLOR_STRING, /* color_type */
791};
792
793gvdevice_features_t device_features_json_nop = {
794 LAYOUT_NOT_REQUIRED, /* flags */
795 {0.,0.}, /* default margin - points */
796 {0.,0.}, /* default page width, height - points */
797 {72.,72.}, /* default dpi */
798};
799
800gvdevice_features_t device_features_json = {
801 0, /* flags */
802 {0.,0.}, /* default margin - points */
803 {0.,0.}, /* default page width, height - points */
804 {72.,72.}, /* default dpi */
805};
806
807gvplugin_installed_t gvrender_json_types[] = {
808 {FORMAT_JSON, "json", 1, &json_engine, &render_features_json},
809 {FORMAT_JSON0, "json0", 1, &json_engine, &render_features_json},
810 {FORMAT_DOT_JSON, "dot_json", 1, &json_engine, &render_features_json},
811 {FORMAT_XDOT_JSON, "xdot_json", 1, &json_engine, &render_features_json},
812 {0, NULL, 0, NULL, NULL}
813};
814
815gvplugin_installed_t gvdevice_json_types[] = {
816 {FORMAT_JSON, "json:json", 1, NULL, &device_features_json},
817 {FORMAT_JSON0, "json0:json", 1, NULL, &device_features_json},
818 {FORMAT_DOT_JSON, "dot_json:json", 1, NULL, &device_features_json_nop},
819 {FORMAT_XDOT_JSON, "xdot_json:json", 1, NULL, &device_features_json_nop},
820 {0, NULL, 0, NULL, NULL}
821};
822