Graphviz 16.1.0~dev.20260902.0144
Loading...
Searching...
No Matches
gvrender_core_svg.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/* Comments on the SVG coordinate system (SN 8 Dec 2006):
12 The initial <svg> element defines the SVG coordinate system so
13 that the graphviz canvas (in units of points) fits the intended
14 absolute size in inches. After this, the situation should be
15 that "px" = "pt" in SVG, so we can dispense with stating units.
16 Also, the input units (such as fontsize) should be preserved
17 without scaling in the output SVG (as long as the graph size
18 was not constrained.)
19 */
20
21#include "config.h"
22#include <math.h>
23#include <stdarg.h>
24#include <stdbool.h>
25#include <stdint.h>
26#include <stdlib.h>
27#include <string.h>
28#include <ctype.h>
29
30#include <common/macros.h>
31#include <common/const.h>
32
33#include <gvc/gvplugin_render.h>
34#include <common/utils.h>
35#include <gvc/gvplugin_device.h>
36#include <gvc/gvio.h>
37#include <gvc/gvcint.h>
38#include <util/agxbuf.h>
39#include <util/strcasecmp.h>
40#include <util/unreachable.h>
41#include <util/unused.h>
42#include <util/xml.h>
43
44#define LOCALNAMEPREFIX '%'
45
46#ifndef EDGEALIGN
47 #define EDGEALIGN 0
48#endif
49
51
52/* SVG dash array */
53static const char sdasharray[] = "5,2";
54/* SVG dot array */
55static const char sdotarray[] = "1,5";
56
57static const char transparent[] = "transparent";
58static const char none[] = "none";
59static const char black[] = "black";
60
61static bool emit_standalone_headers(const GVJ_t *job) {
62 return job->render.id != FORMAT_SVG_INLINE;
63}
64
65static void svg_bzptarray(GVJ_t *job, pointf *A, size_t n) {
66 char c;
67
68 c = 'M'; /* first point */
69#if EDGEALIGN
70 if (A[0].x <= A[n-1].x) {
71#endif
72 for (size_t i = 0; i < n; i++) {
73 gvwrite(job, &c, 1);
74 gvprintdouble(job, A[i].x);
75 gvputc(job, ',');
76 gvprintdouble(job, -A[i].y);
77 if (i == 0)
78 c = 'C'; /* second point */
79 else
80 c = ' '; /* remaining points */
81 }
82#if EDGEALIGN
83 } else {
84 for (size_t i = n - 1; i != SIZE_MAX; i--) {
85 gvwrite(job, &c, 1);
86 gvprintdouble(job, A[i].x);
87 gvputc(job, ',');
88 gvprintdouble(job, -A[i].y);
89 if (i == 0)
90 c = 'C'; /* second point */
91 else
92 c = ' '; /* remaining points */
93 }
94 }
95#endif
96}
97
98static void svg_print_id_class(GVJ_t * job, char* id, char* idx, char* kind, void* obj)
99{
100 char* str;
101
102 gvputs(job, "<g id=\"");
103 gvputs_xml(job, id);
104 if (idx) {
105 gvputc(job, '_');
106 gvputs_xml(job, idx);
107 }
108 gvprintf(job, "\" class=\"%s", kind);
109 if ((str = agget(obj, "class")) && *str) {
110 gvputc(job, ' ');
111 gvputs_xml(job, str);
112 }
113 gvputc(job, '"');
114}
115
116/* svg_print_paint assumes the caller will set the opacity if the alpha channel
117 * is greater than 0 and less than 255
118 */
120{
121 switch (color.type) {
122 case COLOR_STRING:
123 if (!strcmp(color.u.string, transparent))
124 gvputs(job, none);
125 else
126 gvputs(job, color.u.string);
127 break;
128 case RGBA_BYTE:
129 if (color.u.rgba[3] == 0) /* transparent */
130 gvputs(job, none);
131 else
132 gvprintf(job, "#%02x%02x%02x",
133 color.u.rgba[0], color.u.rgba[1], color.u.rgba[2]);
134 break;
135 default:
136 UNREACHABLE(); // internal error
137 }
138}
139
140/* svg_print_gradient_color assumes the caller will set the opacity if the
141 * alpha channel is less than 255.
142 *
143 * "transparent" in SVG 2 gradients is considered to be black with 0 opacity,
144 * so for compatibility with SVG 1.1 output we use black when the color string
145 * is transparent and assume the caller will also check and set opacity 0.
146 */
148{
149 switch (color.type) {
150 case COLOR_STRING:
151 if (!strcmp(color.u.string, transparent))
152 gvputs(job, black);
153 else
154 gvputs(job, color.u.string);
155 break;
156 case RGBA_BYTE:
157 gvprintf(job, "#%02x%02x%02x",
158 color.u.rgba[0], color.u.rgba[1], color.u.rgba[2]);
159 break;
160 default:
161 UNREACHABLE(); // internal error
162 }
163}
164
165static void svg_grstyle(GVJ_t * job, int filled, int gid)
166{
167 obj_state_t *obj = job->obj;
168
169 gvputs(job, " fill=\"");
170 if (filled == GRADIENT) {
171 gvputs(job, "url(#");
172 if (obj->id != NULL) {
173 gvputs_xml(job, obj->id);
174 gvputc(job, '_');
175 }
176 gvprintf(job, "l_%d)", gid);
177 } else if (filled == RGRADIENT) {
178 gvputs(job, "url(#");
179 if (obj->id != NULL) {
180 gvputs_xml(job, obj->id);
181 gvputc(job, '_');
182 }
183 gvprintf(job, "r_%d)", gid);
184 } else if (filled) {
185 svg_print_paint(job, obj->fillcolor);
186 if (obj->fillcolor.type == RGBA_BYTE
187 && obj->fillcolor.u.rgba[3] > 0
188 && obj->fillcolor.u.rgba[3] < 255)
189 gvprintf(job, "\" fill-opacity=\"%f",
190 (float)obj->fillcolor.u.rgba[3] / 255.0);
191 } else {
192 gvputs(job, "none");
193 }
194 gvputs(job, "\" stroke=\"");
195 svg_print_paint(job, obj->pencolor);
196 // will `gvprintdouble` output something different from `PENWIDTH_NORMAL`?
197 const double GVPRINT_DOUBLE_THRESHOLD = 0.005;
198 if (!(fabs(obj->penwidth - PENWIDTH_NORMAL) < GVPRINT_DOUBLE_THRESHOLD)) {
199 gvputs(job, "\" stroke-width=\"");
200 gvprintdouble(job, obj->penwidth);
201 }
202 if (obj->pen == PEN_DASHED) {
203 gvprintf(job, "\" stroke-dasharray=\"%s", sdasharray);
204 } else if (obj->pen == PEN_DOTTED) {
205 gvprintf(job, "\" stroke-dasharray=\"%s", sdotarray);
206 }
207 if (obj->pencolor.type == RGBA_BYTE && obj->pencolor.u.rgba[3] > 0
208 && obj->pencolor.u.rgba[3] < 255)
209 gvprintf(job, "\" stroke-opacity=\"%f",
210 (float)obj->pencolor.u.rgba[3] / 255.0);
211
212 gvputc(job, '"');
213}
214
215static void svg_comment(GVJ_t * job, char *str)
216{
217 gvputs(job, "<!-- ");
218 gvputs_xml(job, str);
219 gvputs(job, " -->\n");
220}
221
222static void svg_begin_job(GVJ_t * job)
223{
224 char *s;
225 if (emit_standalone_headers(job)) {
226 gvputs(job,
227 "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n");
228 if ((s = agget(job->gvc->g, "stylesheet")) && s[0]) {
229 gvputs(job, "<?xml-stylesheet href=\"");
230 gvputs(job, s);
231 gvputs(job, "\" type=\"text/css\"?>\n");
232 }
233 gvputs(job, "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n"
234 " \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n");
235 }
236 gvputs(job,"<!-- Generated by ");
237 gvputs_xml(job, job->common->info[0]);
238 gvputs(job, " version ");
239 gvputs_xml(job, job->common->info[1]);
240 gvputs(job, " (");
241 gvputs_xml(job, job->common->info[2]);
242 gvputs(job, ")\n"
243 " -->\n");
244}
245
246static void svg_begin_graph(GVJ_t * job)
247{
248 obj_state_t *obj = job->obj;
249 char *svgid = 0;
250
251 gvputs(job, "<!--");
252 if (agnameof(obj->u.g)[0] && agnameof(obj->u.g)[0] != LOCALNAMEPREFIX) {
253 gvputs(job, " Title: ");
254 gvputs_xml(job, agnameof(obj->u.g));
255 }
256 gvprintf(job, " Pages: %d -->\n",
257 job->pagesArraySize.x * job->pagesArraySize.y);
258
259 gvprintf(job, "<svg width=\"%dpt\" height=\"%dpt\"\n",
260 job->width, job->height);
261 gvprintf(job, " viewBox=\"%d.00 %d.00 %d.00 %d.00\"",
262 job->pageBoundingBox.LL.x,
263 job->pageBoundingBox.LL.y,
264 job->pageBoundingBox.UR.x,
265 job->pageBoundingBox.UR.y);
266 // https://svgwg.org/svg2-draft/struct.html#Namespace says:
267 // > There's no need to have an ‘xmlns’ attribute declaring that the
268 // > element is in the SVG namespace when using the HTML parser. The HTML
269 // > parser will automatically create the SVG elements in the proper
270 // > namespace.
271 if (emit_standalone_headers(job)) {
272 /* namespace of svg */
273 gvputs(job, " xmlns=\"http://www.w3.org/2000/svg\""
274 /* namespace of xlink */
275 " xmlns:xlink=\"http://www.w3.org/1999/xlink\"");
276 }
277 if ((svgid = agget(obj->u.g,"svgid")) && svgid[0]) {
278 gvputs(job, " id=\"");
279 gvputs_xml(job, svgid);
280 gvputc(job, '"');
281 }
282 gvputs(job, ">\n");
283}
284
285static void svg_end_graph(GVJ_t * job)
286{
287 gvputs(job, "</svg>\n");
288}
289
290static void svg_begin_layer(GVJ_t * job, char *layername, int layerNum,
291 int numLayers)
292{
293 (void)layerNum;
294 (void)numLayers;
295
296 obj_state_t *obj = job->obj;
297
298 svg_print_id_class(job, layername, NULL, "layer", obj->u.g);
299 gvputs(job, ">\n");
300}
301
302static void svg_end_layer(GVJ_t * job)
303{
304 gvputs(job, "</g>\n");
305}
306
307/* svg_begin_page:
308 * Currently, svg output does not support pages.
309 * FIX: If implemented, we must guarantee the id is unique.
310 */
311static void svg_begin_page(GVJ_t * job)
312{
313 obj_state_t *obj = job->obj;
314
315 /* its really just a page of the graph, but its still a graph,
316 * and it is the entire graph if we're not currently paging */
317 svg_print_id_class(job, obj->id, NULL, "graph", obj->u.g);
318 gvputs(job, " transform=\"scale(");
319 // cannot be gvprintdouble because 2 digits precision insufficient
320 gvprintf(job, "%g %g", job->scale.x, job->scale.y);
321 gvprintf(job, ") rotate(%d) translate(", -job->rotation);
322 gvprintdouble(job, job->translation.x);
323 gvputc(job, ' ');
324 gvprintdouble(job, -job->translation.y);
325 gvputs(job, ")\">\n");
326 /* default style */
327 if (agnameof(obj->u.g)[0] && agnameof(obj->u.g)[0] != LOCALNAMEPREFIX) {
328 gvputs(job, "<title>");
329 gvputs_xml(job, agnameof(obj->u.g));
330 gvputs(job, "</title>\n");
331 }
332}
333
334static void svg_end_page(GVJ_t * job)
335{
336 gvputs(job, "</g>\n");
337}
338
339static void svg_begin_cluster(GVJ_t * job)
340{
341 obj_state_t *obj = job->obj;
342
343 svg_print_id_class(job, obj->id, NULL, "cluster", obj->u.sg);
344 gvputs(job, ">\n"
345 "<title>");
346 gvputs_xml(job, agnameof(obj->u.g));
347 gvputs(job, "</title>\n");
348}
349
350static void svg_end_cluster(GVJ_t * job)
351{
352 gvputs(job, "</g>\n");
353}
354
355static void svg_begin_node(GVJ_t * job)
356{
357 obj_state_t *obj = job->obj;
358 char* idx;
359
360 if (job->layerNum > 1)
361 idx = job->gvc->layerIDs[job->layerNum];
362 else
363 idx = NULL;
364 svg_print_id_class(job, obj->id, idx, "node", obj->u.n);
365 gvputs(job, ">\n"
366 "<title>");
367 gvputs_xml(job, agnameof(obj->u.n));
368 gvputs(job, "</title>\n");
369}
370
371static void svg_end_node(GVJ_t * job)
372{
373 gvputs(job, "</g>\n");
374}
375
376static void svg_begin_edge(GVJ_t * job)
377{
378 obj_state_t *obj = job->obj;
379 char *ename;
380
381 svg_print_id_class(job, obj->id, NULL, "edge", obj->u.e);
382 gvputs(job, ">\n"
383
384 "<title>");
385 ename = strdup_and_subst_obj("\\E", obj->u.e);
386 gvputs_xml(job, ename);
387 free(ename);
388 gvputs(job, "</title>\n");
389}
390
391static void svg_end_edge(GVJ_t * job)
392{
393 gvputs(job, "</g>\n");
394}
395
397static int gvputs_wrapper(void *state, const char *s) {
398 return gvputs(state, s);
399}
400
401static void
402svg_begin_anchor(GVJ_t * job, char *href, char *tooltip, char *target,
403 char *id)
404{
405 gvputs(job, "<g");
406 if (id) {
407 gvputs(job, " id=\"a_");
408 gvputs_xml(job, id);
409 gvputc(job, '"');
410 }
411 gvputs(job, ">"
412
413 "<a");
414 if (href && href[0]) {
415 gvputs(job, " xlink:href=\"");
416 const xml_flags_t flags = {0};
418 gvputc(job, '"');
419 }
420 if (tooltip && tooltip[0]) {
421 gvputs(job, " xlink:title=\"");
422 const xml_flags_t flags = {.raw = 1, .dash = 1, .nbsp = 1};
423 gv_xml_escape(tooltip, flags, gvputs_wrapper, job);
424 gvputc(job, '"');
425 }
426 if (target && target[0]) {
427 gvputs(job, " target=\"");
428 gvputs_xml(job, target);
429 gvputc(job, '"');
430 }
431 gvputs(job, ">\n");
432}
433
434static void svg_end_anchor(GVJ_t * job)
435{
436 gvputs(job, "</a>\n"
437 "</g>\n");
438}
439
440static void svg_textspan(GVJ_t * job, pointf p, textspan_t * span)
441{
442 obj_state_t *obj = job->obj;
443 PostscriptAlias *pA;
444 char *family = NULL, *weight = NULL, *stretch = NULL, *style = NULL;
445 unsigned int flags;
446
447 gvputs(job, "<text xml:space=\"preserve\"");
448 switch (span->just) {
449 case 'l':
450 gvputs(job, " text-anchor=\"start\"");
451 break;
452 case 'r':
453 gvputs(job, " text-anchor=\"end\"");
454 break;
455 default:
456 case 'n':
457 gvputs(job, " text-anchor=\"middle\"");
458 break;
459 }
460 p.y += span->yoffset_centerline;
461 if (!obj->labeledgealigned) {
462 gvputs(job, " x=\"");
463 gvprintdouble(job, p.x);
464 gvputs(job, "\" y=\"");
465 gvprintdouble(job, -p.y);
466 gvputs(job, "\"");
467 }
468 pA = span->font->postscript_alias;
469 if (pA) {
470 switch (GD_fontnames(job->gvc->g)) {
471 case PSFONTS:
472 family = pA->name;
473 weight = pA->weight;
474 style = pA->style;
475 break;
476 case SVGFONTS:
477 family = pA->svg_font_family;
478 weight = pA->svg_font_weight;
479 style = pA->svg_font_style;
480 break;
481 default:
482 case NATIVEFONTS:
483 family = pA->family;
484 weight = pA->weight;
485 style = pA->style;
486 break;
487 }
488 stretch = pA->stretch;
489
490 gvprintf(job, " font-family=\"%s", family);
491 if (pA->svg_font_family && pA->svg_font_family != family)
492 gvprintf(job, ",%s", pA->svg_font_family);
493 gvputc(job, '"');
494 if (weight)
495 gvprintf(job, " font-weight=\"%s\"", weight);
496 if (stretch)
497 gvprintf(job, " font-stretch=\"%s\"", stretch);
498 if (style)
499 gvprintf(job, " font-style=\"%s\"", style);
500 } else
501 gvprintf(job, " font-family=\"%s\"", span->font->name);
502 if ((flags = span->font->flags)) {
503 if ((flags & HTML_BF) && !weight)
504 gvputs(job, " font-weight=\"bold\"");
505 if ((flags & HTML_IF) && !style)
506 gvputs(job, " font-style=\"italic\"");
507 if (flags & (HTML_UL|HTML_S|HTML_OL)) {
508 int comma = 0;
509 gvputs(job, " text-decoration=\"");
510 if ((flags & HTML_UL)) {
511 gvputs(job, "underline");
512 comma = 1;
513 }
514 if (flags & HTML_OL) {
515 gvprintf(job, "%soverline", (comma?",":""));
516 comma = 1;
517 }
518 if (flags & HTML_S)
519 gvprintf(job, "%sline-through", (comma?",":""));
520 gvputc(job, '"');
521 }
522 if (flags & HTML_SUP)
523 gvputs(job, " baseline-shift=\"super\"");
524 if (flags & HTML_SUB)
525 gvputs(job, " baseline-shift=\"sub\"");
526 }
527
528 gvprintf(job, " font-size=\"%.2f\"", span->font->size);
529 switch (obj->pencolor.type) {
530 case COLOR_STRING:
531 if (strcasecmp(obj->pencolor.u.string, "black"))
532 gvprintf(job, " fill=\"%s\"", obj->pencolor.u.string);
533 break;
534 case RGBA_BYTE:
535 gvprintf(job, " fill=\"#%02x%02x%02x\"",
536 obj->pencolor.u.rgba[0], obj->pencolor.u.rgba[1],
537 obj->pencolor.u.rgba[2]);
538 if (obj->pencolor.u.rgba[3] < 255)
539 gvprintf(job, " fill-opacity=\"%f\"", (float)obj->pencolor.u.rgba[3] / 255.0);
540 break;
541 default:
542 UNREACHABLE(); // internal error
543 }
544 gvputc(job, '>');
545 if (obj->labeledgealigned) {
546 gvputs(job, "<textPath xlink:href=\"#");
547 gvputs_xml(job, obj->id);
548 gvputs(job, "_p\" startOffset=\"50%\"><tspan x=\"0\" dy=\"");
549 gvprintdouble(job, -p.y);
550 gvputs(job, "\">");
551 }
552 const xml_flags_t xml_flags = {.raw = 1, .dash = 1, .nbsp = 1};
553 gv_xml_escape(span->str, xml_flags, gvputs_wrapper, job);
554 if (obj->labeledgealigned)
555 gvputs(job, "</tspan></textPath>");
556 gvputs(job, "</text>\n");
557}
558
559static void svg_print_stop(GVJ_t * job, double offset, gvcolor_t color)
560{
561 if (fabs(offset - 0.0) < 0.0005)
562 gvputs(job, "<stop offset=\"0\" style=\"stop-color:");
563 else if (fabs(offset - 1.0) < 0.0005)
564 gvputs(job, "<stop offset=\"1\" style=\"stop-color:");
565 else
566 gvprintf(job, "<stop offset=\"%.03f\" style=\"stop-color:", offset);
568 gvputs(job, ";stop-opacity:");
569 if (color.type == RGBA_BYTE && color.u.rgba[3] < 255)
570 gvprintf(job, "%f", (float)color.u.rgba[3] / 255.0);
571 else if (color.type == COLOR_STRING && !strcmp(color.u.string, transparent))
572 gvputs(job, "0");
573 else
574 gvputs(job, "1.");
575 gvputs(job, ";\"/>\n");
576}
577
578/* svg_gradstyle
579 * Outputs the SVG statements that define the gradient pattern
580 */
581static int svg_gradstyle(GVJ_t *job, pointf *A, size_t n) {
582 pointf G[2];
583 static int gradId;
584 int id = gradId++;
585
586 obj_state_t *obj = job->obj;
587 double angle = obj->gradient_angle * M_PI / 180; //angle of gradient line
588 G[0].x = G[0].y = G[1].x = G[1].y = 0.;
589 get_gradient_points(A, G, n, angle, 0); // get points on gradient line
590
591 gvputs(job, "<defs>\n<linearGradient id=\"");
592 if (obj->id != NULL) {
593 gvputs_xml(job, obj->id);
594 gvputc(job, '_');
595 }
596 gvprintf(job, "l_%d\" gradientUnits=\"userSpaceOnUse\" ", id);
597 gvputs(job, "x1=\"");
598 gvprintdouble(job, G[0].x);
599 gvputs(job, "\" y1=\"");
600 gvprintdouble(job, G[0].y);
601 gvputs(job, "\" x2=\"");
602 gvprintdouble(job, G[1].x);
603 gvputs(job, "\" y2=\"");
604 gvprintdouble(job, G[1].y);
605 gvputs(job, "\" >\n");
606
607 svg_print_stop(job, obj->gradient_frac > 0 ? obj->gradient_frac - 0.001 : 0.0, obj->fillcolor);
608 svg_print_stop(job, obj->gradient_frac > 0 ? obj->gradient_frac : 1.0, obj->stopcolor);
609
610 gvputs(job, "</linearGradient>\n</defs>\n");
611 return id;
612}
613
614/* svg_rgradstyle
615 * Outputs the SVG statements that define the radial gradient pattern
616 */
617static int svg_rgradstyle(GVJ_t * job)
618{
619 double ifx, ify;
620 static int rgradId;
621 int id = rgradId++;
622
623 obj_state_t *obj = job->obj;
624 if (obj->gradient_angle == 0) {
625 ifx = ify = 50;
626 } else {
627 double angle = obj->gradient_angle * M_PI / 180; //angle of gradient line
628 ifx = round(50 * (1 + cos(angle)));
629 ify = round(50 * (1 - sin(angle)));
630 }
631 gvputs(job, "<defs>\n<radialGradient id=\"");
632 if (obj->id != NULL) {
633 gvputs_xml(job, obj->id);
634 gvputc(job, '_');
635 }
636 gvprintf(job, "r_%d\" cx=\"50%%\" cy=\"50%%\" r=\"75%%\" "
637 "fx=\"%.0f%%\" fy=\"%.0f%%\">\n",
638 id, ifx, ify);
639
640 svg_print_stop(job, 0.0, obj->fillcolor);
641 svg_print_stop(job, 1.0, obj->stopcolor);
642
643 gvputs(job, "</radialGradient>\n</defs>\n");
644 return id;
645}
646
647
648static void svg_ellipse(GVJ_t * job, pointf * A, int filled)
649{
650 int gid = 0;
651
652 /* A[] contains 2 points: the center and corner. */
653 if (filled == GRADIENT) {
654 gid = svg_gradstyle(job, A, 2);
655 } else if (filled == RGRADIENT) {
656 gid = svg_rgradstyle(job);
657 }
658 gvputs(job, "<ellipse");
659 svg_grstyle(job, filled, gid);
660 gvputs(job, " cx=\"");
661 gvprintdouble(job, A[0].x);
662 gvputs(job, "\" cy=\"");
663 gvprintdouble(job, -A[0].y);
664 gvputs(job, "\" rx=\"");
665 gvprintdouble(job, A[1].x - A[0].x);
666 gvputs(job, "\" ry=\"");
667 gvprintdouble(job, A[1].y - A[0].y);
668 gvputs(job, "\"/>\n");
669}
670
671static void svg_bezier(GVJ_t *job, pointf *A, size_t n, int filled) {
672 int gid = 0;
673 obj_state_t *obj = job->obj;
674
675 if (filled == GRADIENT) {
676 gid = svg_gradstyle(job, A, n);
677 } else if (filled == RGRADIENT) {
678 gid = svg_rgradstyle(job);
679 }
680 gvputs(job, "<path");
681 if (obj->labeledgealigned) {
682 gvputs(job, " id=\"");
683 gvputs_xml(job, obj->id);
684 gvputs(job, "_p\" ");
685 }
686 svg_grstyle(job, filled, gid);
687 gvputs(job, " d=\"");
688 svg_bzptarray(job, A, n);
689 gvputs(job, "\"/>\n");
690}
691
692static void svg_polygon(GVJ_t *job, pointf *A, size_t n, int filled) {
693 int gid = 0;
694 if (filled == GRADIENT) {
695 gid = svg_gradstyle(job, A, n);
696 } else if (filled == RGRADIENT) {
697 gid = svg_rgradstyle(job);
698 }
699 gvputs(job, "<polygon");
700 svg_grstyle(job, filled, gid);
701 gvputs(job, " points=\"");
702 for (size_t i = 0; i < n; i++) {
703 gvprintdouble(job, A[i].x);
704 gvputc(job, ',');
705 gvprintdouble(job, -A[i].y);
706 gvputc(job, ' ');
707 }
708 /* repeat the first point because Adobe SVG is broken */
709 gvprintdouble(job, A[0].x);
710 gvputc(job, ',');
711 gvprintdouble(job, -A[0].y);
712 gvputs(job, "\"/>\n");
713}
714
715static void svg_polyline(GVJ_t *job, pointf *A, size_t n) {
716 gvputs(job, "<polyline");
717 svg_grstyle(job, 0, 0);
718 gvputs(job, " points=\"");
719 for (size_t i = 0; i < n; i++) {
720 gvprintdouble(job, A[i].x);
721 gvputc(job, ',');
722 gvprintdouble(job, -A[i].y);
723 if (i + 1 != n) {
724 gvputc(job, ' ');
725 }
726 }
727 gvputs(job, "\"/>\n");
728}
729
730/* color names from http://www.w3.org/TR/SVG/types.html */
731/* NB. List must be LANG_C sorted */
732static char *svg_knowncolors[] = {
733 "aliceblue", "antiquewhite", "aqua", "aquamarine", "azure",
734 "beige", "bisque", "black", "blanchedalmond", "blue",
735 "blueviolet", "brown", "burlywood",
736 "cadetblue", "chartreuse", "chocolate", "coral",
737 "cornflowerblue", "cornsilk", "crimson", "cyan",
738 "darkblue", "darkcyan", "darkgoldenrod", "darkgray",
739 "darkgreen", "darkgrey", "darkkhaki", "darkmagenta",
740 "darkolivegreen", "darkorange", "darkorchid", "darkred",
741 "darksalmon", "darkseagreen", "darkslateblue", "darkslategray",
742 "darkslategrey", "darkturquoise", "darkviolet", "deeppink",
743 "deepskyblue", "dimgray", "dimgrey", "dodgerblue",
744 "firebrick", "floralwhite", "forestgreen", "fuchsia",
745 "gainsboro", "ghostwhite", "gold", "goldenrod", "gray",
746 "green", "greenyellow", "grey",
747 "honeydew", "hotpink", "indianred",
748 "indigo", "ivory", "khaki",
749 "lavender", "lavenderblush", "lawngreen", "lemonchiffon",
750 "lightblue", "lightcoral", "lightcyan", "lightgoldenrodyellow",
751 "lightgray", "lightgreen", "lightgrey", "lightpink",
752 "lightsalmon", "lightseagreen", "lightskyblue",
753 "lightslategray", "lightslategrey", "lightsteelblue",
754 "lightyellow", "lime", "limegreen", "linen",
755 "magenta", "maroon", "mediumaquamarine", "mediumblue",
756 "mediumorchid", "mediumpurple", "mediumseagreen",
757 "mediumslateblue", "mediumspringgreen", "mediumturquoise",
758 "mediumvioletred", "midnightblue", "mintcream",
759 "mistyrose", "moccasin",
760 "navajowhite", "navy", "oldlace",
761 "olive", "olivedrab", "orange", "orangered", "orchid",
762 "palegoldenrod", "palegreen", "paleturquoise",
763 "palevioletred", "papayawhip", "peachpuff", "peru", "pink",
764 "plum", "powderblue", "purple",
765 "red", "rosybrown", "royalblue",
766 "saddlebrown", "salmon", "sandybrown", "seagreen", "seashell",
767 "sienna", "silver", "skyblue", "slateblue", "slategray",
768 "slategrey", "snow", "springgreen", "steelblue",
769 "tan", "teal", "thistle", "tomato", "transparent", "turquoise",
770 "violet",
771 "wheat", "white", "whitesmoke",
772 "yellow", "yellowgreen"
773};
774
777 0, /* svg_end_job */
786 0, /* svg_begin_nodes */
787 0, /* svg_end_nodes */
788 0, /* svg_begin_edges */
789 0, /* svg_end_edges */
796 0, /* svg_begin_anchor */
797 0, /* svg_end_anchor */
799 0, /* svg_resolve_color */
805 0, /* svg_library_shape */
806};
807
810 4., /* default pad - graph units */
811 svg_knowncolors, /* knowncolors */
812 sizeof(svg_knowncolors) / sizeof(char *), /* sizeof knowncolors */
813 RGBA_BYTE, /* color_type */
814};
815
818 {0., 0.}, /* default margin - points */
819 {0., 0.}, /* default page width, height - points */
820 {72., 72.}, /* default dpi */
821};
822
825 {0., 0.}, /* default margin - points */
826 {0., 0.}, /* default page width, height - points */
827 {72., 72.}, /* default dpi */
828};
829
835
837 {FORMAT_SVG, "svg:svg", 1, NULL, &device_features_svg},
838#ifdef HAVE_LIBZ
839 {FORMAT_SVGZ, "svgz:svg", 1, NULL, &device_features_svgz},
840#endif
841 {FORMAT_SVG_INLINE, "svg_inline:svg", 1, NULL, &device_features_svg},
842 {0, NULL, 0, NULL, NULL}
843};
Dynamically expanding string buffers.
#define M_PI
Definition arith.h:41
@ RGBA_BYTE
Definition color.h:26
@ COLOR_STRING
Definition color.h:27
void get_gradient_points(pointf *A, pointf *G, size_t n, double angle, int flags)
Definition utils.c:1446
#define GRADIENT
Definition const.h:223
#define RGRADIENT
Definition const.h:224
#define A(n, t)
Definition expr.h:76
static int flags
Definition gc.c:63
#define G
Definition gdefs.h:7
void free(void *)
#define SIZE_MAX
Definition gmlscan.c:347
node NULL
Definition grammar.y:181
char * agget(void *obj, char *name)
Definition attr.c:447
#define GD_fontnames(g)
Definition types.h:402
char * agnameof(void *)
returns a string descriptor for the object.
Definition id.c:145
static uint64_t id
Definition gv2gml.c:42
@ PEN_DOTTED
Definition gvcjob.h:35
@ PEN_DASHED
Definition gvcjob.h:35
#define GVDEVICE_COMPRESSED_FORMAT
Definition gvcjob.h:92
#define GVRENDER_DOES_TOOLTIPS
Definition gvcjob.h:103
#define GVRENDER_DOES_LABELS
Definition gvcjob.h:96
#define GVDEVICE_DOES_LAYERS
Definition gvcjob.h:88
#define GVDEVICE_DOES_TRUECOLOR
Definition gvcjob.h:90
#define GVDEVICE_BINARY_FORMAT
Definition gvcjob.h:91
#define GVRENDER_DOES_MAPS
Definition gvcjob.h:97
#define GVRENDER_DOES_TARGETS
Definition gvcjob.h:104
#define PENWIDTH_NORMAL
Definition gvcjob.h:40
#define GVRENDER_DOES_TRANSFORM
Definition gvcjob.h:95
#define GVRENDER_Y_GOES_DOWN
Definition gvcjob.h:94
static void color(Agraph_t *g)
Definition gvcolor.c:118
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
int gvputs_xml(GVJ_t *job, const char *s)
Definition gvdevice.c:281
int gvputs(GVJ_t *job, const char *s)
Definition gvdevice.c:266
void gvprintf(GVJ_t *job, const char *format,...)
Definition gvdevice.c:402
void gvprintdouble(GVJ_t *job, double num)
Definition gvdevice.c:513
static UNUSED gvdevice_features_t device_features_svgz
static const char transparent[]
static void svg_textspan(GVJ_t *job, pointf p, textspan_t *span)
static int svg_gradstyle(GVJ_t *job, pointf *A, size_t n)
static void svg_end_page(GVJ_t *job)
static void svg_begin_node(GVJ_t *job)
static void svg_print_paint(GVJ_t *job, gvcolor_t color)
static void svg_grstyle(GVJ_t *job, int filled, int gid)
static bool emit_standalone_headers(const GVJ_t *job)
static gvrender_features_t render_features_svg
static const char sdasharray[]
static void svg_print_gradient_color(GVJ_t *job, gvcolor_t color)
static gvdevice_features_t device_features_svg
static void svg_end_cluster(GVJ_t *job)
static void svg_end_node(GVJ_t *job)
static void svg_begin_cluster(GVJ_t *job)
static void svg_polyline(GVJ_t *job, pointf *A, size_t n)
static void svg_bezier(GVJ_t *job, pointf *A, size_t n, int filled)
static void svg_comment(GVJ_t *job, char *str)
static int gvputs_wrapper(void *state, const char *s)
wrap gvputs to offer a void * first parameter
static void svg_end_edge(GVJ_t *job)
static gvrender_engine_t svg_engine
gvplugin_installed_t gvdevice_svg_types[]
static char * svg_knowncolors[]
static void svg_begin_page(GVJ_t *job)
static void svg_bzptarray(GVJ_t *job, pointf *A, size_t n)
static const char sdotarray[]
static void svg_end_anchor(GVJ_t *job)
static void svg_polygon(GVJ_t *job, pointf *A, size_t n, int filled)
#define LOCALNAMEPREFIX
@ FORMAT_SVG
@ FORMAT_SVG_INLINE
@ FORMAT_SVGZ
static void svg_end_layer(GVJ_t *job)
static void svg_print_id_class(GVJ_t *job, char *id, char *idx, char *kind, void *obj)
static void svg_begin_layer(GVJ_t *job, char *layername, int layerNum, int numLayers)
static void svg_begin_anchor(GVJ_t *job, char *href, char *tooltip, char *target, char *id)
static void svg_begin_edge(GVJ_t *job)
static void svg_begin_graph(GVJ_t *job)
static void svg_ellipse(GVJ_t *job, pointf *A, int filled)
static void svg_begin_job(GVJ_t *job)
gvplugin_installed_t gvrender_svg_types[]
static const char black[]
static void svg_end_graph(GVJ_t *job)
static int svg_rgradstyle(GVJ_t *job)
static void svg_print_stop(GVJ_t *job, double offset, gvcolor_t color)
static const char none[]
textitem scanner parser str
Definition htmlparse.y:218
char * strdup_and_subst_obj(char *str, void *obj)
Processes graph object escape sequences; also collapses \ to .
Definition labels.c:387
platform abstraction for case-insensitive string functions
char ** info
Definition gvcommon.h:20
graph_t * g
Definition gvcint.h:118
char ** layerIDs
Definition gvcint.h:140
int rotation
Definition gvcjob.h:319
point pagesArraySize
Definition gvcjob.h:304
obj_state_t * obj
Definition gvcjob.h:269
gvplugin_active_render_t render
Definition gvcjob.h:285
GVCOMMON_t * common
Definition gvcjob.h:267
box pageBoundingBox
Definition gvcjob.h:329
pointf scale
Definition gvcjob.h:332
GVC_t * gvc
Definition gvcjob.h:263
int layerNum
Definition gvcjob.h:302
unsigned int width
Definition gvcjob.h:327
pointf translation
Definition gvcjob.h:333
unsigned int height
Definition gvcjob.h:328
char * svg_font_style
Definition textspan.h:48
char * svg_font_weight
Definition textspan.h:47
char * svg_font_family
Definition textspan.h:46
point LL
Definition geom.h:39
point UR
Definition geom.h:39
char * string
Definition color.h:36
union color_s::@40 u
unsigned char rgba[4]
Definition color.h:34
color_type_t type
Definition color.h:39
ingroup plugin_api
Definition gvplugin.h:35
graph_t * g
Definition gvcjob.h:186
edge_t * e
Definition gvcjob.h:189
gvcolor_t fillcolor
Definition gvcjob.h:194
double gradient_frac
Definition gvcjob.h:196
gvcolor_t stopcolor
Definition gvcjob.h:194
pen_type pen
Definition gvcjob.h:197
node_t * n
Definition gvcjob.h:188
union obj_state_s::@65 u
graph_t * sg
Definition gvcjob.h:187
gvcolor_t pencolor
Definition gvcjob.h:194
char * id
Definition gvcjob.h:211
int gradient_angle
Definition gvcjob.h:195
unsigned labeledgealigned
Definition gvcjob.h:235
double penwidth
Definition gvcjob.h:199
int y
Definition geom.h:27
int x
Definition geom.h:27
double x
Definition geom.h:29
double y
Definition geom.h:29
char * name
Definition textspan.h:56
PostscriptAlias * postscript_alias
Definition textspan.h:58
unsigned int flags
Definition textspan.h:61
double size
Definition textspan.h:59
char * str
Definition textspan.h:68
char just
'l' 'n' 'r'
Definition textspan.h:74
textfont_t * font
Definition textspan.h:69
double yoffset_centerline
Definition textspan.h:72
options to tweak the behavior of XML escaping
Definition xml.h:13
unsigned raw
assume no embedded escapes, and escape "\n" and "\r"
Definition xml.h:15
#define HTML_OL
Definition textspan.h:37
#define HTML_IF
Definition textspan.h:32
#define HTML_UL
Definition textspan.h:33
#define HTML_BF
Definition textspan.h:31
#define HTML_SUP
Definition textspan.h:34
#define HTML_S
Definition textspan.h:36
#define HTML_SUB
Definition textspan.h:35
@ NATIVEFONTS
Definition types.h:274
@ PSFONTS
Definition types.h:274
@ SVGFONTS
Definition types.h:274
Definition grammar.c:90
#define UNREACHABLE()
Definition unreachable.h:30
abstraction for squashing compiler warnings for unused symbols
#define UNUSED
Definition unused.h:25
int gv_xml_escape(const char *s, xml_flags_t flags, int(*cb)(void *state, const char *s), void *state)
Definition xml.c:185
XML escaping functionality.