1/* $Id$ $Revision$ */
2/* vim:set shiftwidth=4 ts=8: */
3
4/*************************************************************************
5 * Copyright (c) 2011 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#include <stdarg.h>
17#include <stdlib.h>
18#include <string.h>
19#include <inttypes.h>
20
21#include "macros.h"
22#include "const.h"
23
24#include "gvplugin_render.h"
25#include "gvplugin_device.h"
26#include "gvio.h"
27#include "memory.h"
28
29typedef enum { FORMAT_VML, FORMAT_VMLZ, } format_type;
30
31unsigned int graphHeight,graphWidth;
32
33#ifndef HAVE_STRCASECMP
34extern int strcasecmp(const char *s1, const char *s2);
35#endif
36
37/* this is a direct copy fromlib/common/labels.c */
38static int xml_isentity(char *s)
39{
40 s++; /* already known to be '&' */
41 if (*s == '#') {
42 s++;
43 if (*s == 'x' || *s == 'X') {
44 s++;
45 while ((*s >= '0' && *s <= '9')
46 || (*s >= 'a' && *s <= 'f')
47 || (*s >= 'A' && *s <= 'F'))
48 s++;
49 } else {
50 while (*s >= '0' && *s <= '9')
51 s++;
52 }
53 } else {
54 while ((*s >= 'a' && *s <= 'z')
55 || (*s >= 'A' && *s <= 'Z'))
56 s++;
57 }
58 if (*s == ';')
59 return 1;
60 return 0;
61}
62
63static void vml_bzptarray(GVJ_t * job, pointf * A, int n)
64{
65 int i;
66 char *c;
67
68 c = "m "; /* first point */
69 for (i = 0; i < n; i++) {
70 /* integers only in path! */
71 gvprintf(job, "%s%.0f,%.0f ", c, A[i].x, graphHeight-A[i].y);
72 if (i == 0)
73 c = "c "; /* second point */
74 else
75 c = ""; /* remaining points */
76 }
77 gvputs(job, "\"");
78}
79
80static void vml_print_color(GVJ_t * job, gvcolor_t color)
81{
82 switch (color.type) {
83 case COLOR_STRING:
84 gvputs(job, color.u.string);
85 break;
86 case RGBA_BYTE:
87 if (color.u.rgba[3] == 0) /* transparent */
88 gvputs(job, "none");
89 else
90 gvprintf(job, "#%02x%02x%02x",
91 color.u.rgba[0], color.u.rgba[1], color.u.rgba[2]);
92 break;
93 default:
94 assert(0); /* internal error */
95 }
96}
97
98static void vml_grstroke(GVJ_t * job, int filled)
99{
100 obj_state_t *obj = job->obj;
101
102 gvputs(job, "<v:stroke color=\"");
103 vml_print_color(job, obj->pencolor);
104 if (obj->penwidth != PENWIDTH_NORMAL)
105 gvprintf(job, "\" weight=\"%.0fpt", obj->penwidth);
106 if (obj->pen == PEN_DASHED) {
107 gvputs(job, "\" dashstyle=\"dash");
108 } else if (obj->pen == PEN_DOTTED) {
109 gvputs(job, "\" dashstyle=\"dot");
110 }
111 gvputs(job, "\" />");
112}
113
114
115static void vml_grfill(GVJ_t * job, int filled)
116{
117 obj_state_t *obj = job->obj;
118
119 if (filled){
120 gvputs(job, " filled=\"true\" fillcolor=\"");
121 vml_print_color(job, obj->fillcolor);
122 gvputs(job, "\" ");
123 }else{
124 gvputs(job, " filled=\"false\" ");
125 }
126}
127
128/* html_string is a modified version of xml_string */
129char *html_string(char *s)
130{
131 static char *buf = NULL;
132 static int bufsize = 0;
133 char *p, *sub, *prev = NULL;
134 int len, pos = 0;
135 int temp,cnt,remaining=0;
136 char workstr[16];
137 uint64_t charnum=0;
138 unsigned char byte;
139 unsigned char mask;
140
141
142 if (!buf) {
143 bufsize = 64;
144 buf = gmalloc(bufsize);
145 }
146 p = buf;
147 while (s && *s) {
148 if (pos > (bufsize - 8)) {
149 bufsize *= 2;
150 buf = grealloc(buf, bufsize);
151 p = buf + pos;
152 }
153 /* escape '&' only if not part of a legal entity sequence */
154 if (*s == '&' && !(xml_isentity(s))) {
155 sub = "&amp;";
156 len = 5;
157 }
158 /* '<' '>' are safe to substitute even if string is already UTF-8 coded
159 * since UTF-8 strings won't contain '<' or '>' */
160 else if (*s == '<') {
161 sub = "&lt;";
162 len = 4;
163 }
164 else if (*s == '>') {
165 sub = "&gt;";
166 len = 4;
167 }
168 else if (*s == '-') { /* can't be used in xml comment strings */
169 sub = "&#45;";
170 len = 5;
171 }
172 else if (*s == ' ' && prev && *prev == ' ') {
173 /* substitute 2nd and subsequent spaces with required_spaces */
174 sub = "&#160;"; /* inkscape doesn't recognise &nbsp; */
175 len = 6;
176 }
177 else if (*s == '"') {
178 sub = "&quot;";
179 len = 6;
180 }
181 else if (*s == '\'') {
182 sub = "&#39;";
183 len = 5;
184 }
185 else if ((unsigned char)*s > 127) {
186 byte=(unsigned char)*s;
187 cnt=0;
188 for (mask=127; mask < byte; mask=mask >>1){
189 cnt++;
190 byte=byte & mask;
191 }
192 if (cnt>1){
193 charnum=byte;
194 remaining=cnt-1;
195 }else{
196 charnum=charnum<<6;
197 charnum+=byte;
198 remaining--;
199 }
200 if (remaining>0){
201 s++;
202 continue;
203 }
204 /* we will build the html value right-to-left
205 * (least significant-to-most) */
206 workstr[15]=';';
207 sub=&workstr[14];
208 len=3; /* &# + ; */
209 do {
210 temp=charnum%10;
211 *(sub--)=(char)((int)'0'+ temp);
212 charnum/=10;
213 len++;
214 if (len>12){ /* 12 is arbitrary, but clearly in error */
215 fprintf(stderr, "Error during conversion to \"UTF-8\". Quiting.\n");
216 exit(1);
217 }
218 } while (charnum>0);
219 *(sub--)='#';
220 *(sub)='&';
221 }
222 else {
223 sub = s;
224 len = 1;
225 }
226 while (len--) {
227 *p++ = *sub++;
228 pos++;
229 }
230 prev = s;
231 s++;
232 }
233 *p = '\0';
234 return buf;
235}
236static void vml_comment(GVJ_t * job, char *str)
237{
238 gvputs(job, " <!-- ");
239 gvputs(job, html_string(str));
240 gvputs(job, " -->\n");
241}
242static void vml_begin_job(GVJ_t * job)
243{
244 gvputs(job, "<HTML>\n");
245 gvputs(job, "\n<!-- Generated by ");
246 gvputs(job, html_string(job->common->info[0]));
247 gvputs(job, " version ");
248 gvputs(job, html_string(job->common->info[1]));
249 gvputs(job, " (");
250 gvputs(job, html_string(job->common->info[2]));
251 gvputs(job, ")\n-->\n");
252}
253
254static void vml_begin_graph(GVJ_t * job)
255{
256 obj_state_t *obj = job->obj;
257 char *name;
258
259 graphHeight =(int)(job->bb.UR.y - job->bb.LL.y);
260 graphWidth =(int)(job->bb.UR.x - job->bb.LL.x);
261
262 gvputs(job, "<HEAD>");
263 gvputs(job, "<META http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\n");
264
265
266 name = agnameof(obj->u.g);
267 if (name[0]) {
268 gvputs(job, "<TITLE>");
269 gvputs(job, html_string(name));
270 gvputs(job, "</TITLE>");
271 }
272 gvprintf(job, "<!-- Pages: %d -->\n", job->pagesArraySize.x * job->pagesArraySize.y);
273
274/* the next chunk and all the "DIV" stuff is not required,
275 * but it helps with non-IE browsers */
276 gvputs(job, " <SCRIPT LANGUAGE='Javascript'>\n");
277 gvputs(job, " function browsercheck()\n");
278 gvputs(job, " {\n");
279 gvputs(job, " var ua = window.navigator.userAgent\n");
280 gvputs(job, " var msie = ua.indexOf ( 'MSIE ' )\n");
281 gvputs(job, " var ievers;\n");
282 gvputs(job, " var item;\n");
283 gvputs(job, " var VMLyes=new Array('_VML1_','_VML2_');\n");
284 gvputs(job, " var VMLno=new Array('_notVML1_','_notVML2_');\n");
285 gvputs(job, " if ( msie > 0 ){ // If Internet Explorer, return version number\n");
286 gvputs(job, " ievers= parseInt (ua.substring (msie+5, ua.indexOf ('.', msie )))\n");
287 gvputs(job, " }\n");
288 gvputs(job, " if (ievers>=5){\n");
289 gvputs(job, " for (x in VMLyes){\n");
290 gvputs(job, " item = document.getElementById(VMLyes[x]);\n");
291 gvputs(job, " if (item) {\n");
292 gvputs(job, " item.style.visibility='visible';\n");
293 gvputs(job, " }\n");
294 gvputs(job, " }\n");
295 gvputs(job, " for (x in VMLno){\n");
296 gvputs(job, " item = document.getElementById(VMLno[x]);\n");
297 gvputs(job, " if (item) {\n");
298 gvputs(job, " item.style.visibility='hidden';\n");
299 gvputs(job, " }\n");
300 gvputs(job, " }\n");
301 gvputs(job, " }else{\n");
302 gvputs(job, " for (x in VMLyes){\n");
303 gvputs(job, " item = document.getElementById(VMLyes[x]);\n");
304 gvputs(job, " if (item) {\n");
305 gvputs(job, " item.style.visibility='hidden';\n");
306 gvputs(job, " }\n");
307 gvputs(job, " }\n");
308 gvputs(job, " for (x in VMLno){\n");
309 gvputs(job, " item = document.getElementById(VMLno[x]);\n");
310 gvputs(job, " if (item) {\n");
311 gvputs(job, " item.style.visibility='visible';\n");
312 gvputs(job, " }\n");
313 gvputs(job, " }\n");
314 gvputs(job, " }\n");
315 gvputs(job, " }\n");
316 gvputs(job, " </SCRIPT>\n");
317
318 gvputs(job, "</HEAD>");
319 gvputs(job, "<BODY onload='browsercheck();'>\n");
320 /* add 10pt pad to the bottom of the graph */
321 gvputs(job, "<DIV id='_VML1_' style=\"position:relative; display:inline; visibility:hidden");
322 gvprintf(job, " width: %dpt; height: %dpt\">\n", graphWidth, 10+graphHeight);
323 gvputs(job, "<STYLE>\n");
324 gvputs(job, "v\\:* { behavior: url(#default#VML);display:inline-block}\n");
325 gvputs(job, "</STYLE>\n");
326 gvputs(job, "<xml:namespace ns=\"urn:schemas-microsoft-com:vml\" prefix=\"v\" />\n");
327
328 gvputs(job, " <v:group style=\"position:relative; ");
329 gvprintf(job, " width: %dpt; height: %dpt\"", graphWidth, graphHeight);
330 gvprintf(job, " coordorigin=\"0,0\" coordsize=\"%d,%d\" >", graphWidth, graphHeight);
331}
332
333static void vml_end_graph(GVJ_t * job)
334{
335 gvputs(job, "</v:group>\n");
336 gvputs(job, "</DIV>\n");
337 /* add 10pt pad to the bottom of the graph */
338 gvputs(job, "<DIV id='_VML2_' style=\"position:relative;visibility:hidden\">\n");
339 gvputs(job, "<!-- insert any other html content here -->\n");
340 gvputs(job, "</DIV>\n");
341 gvputs(job, "<DIV id='_notVML1_' style=\"position:relative;\">\n");
342 gvputs(job, "<!-- this should only display on NON-IE browsers -->\n");
343 gvputs(job, "<H2>Sorry, this diagram will only display correctly on Internet Explorer 5 (and up) browsers.</H2>\n");
344 gvputs(job, "</DIV>\n");
345 gvputs(job, "<DIV id='_notVML2_' style=\"position:relative;\">\n");
346 gvputs(job, "<!-- insert any other NON-IE html content here -->\n");
347 gvputs(job, "</DIV>\n");
348
349 gvputs(job, "</BODY>\n</HTML>\n");
350}
351
352static void
353vml_begin_anchor(GVJ_t * job, char *href, char *tooltip, char *target, char *id)
354{
355 gvputs(job, "<a");
356 if (href && href[0])
357 gvprintf(job, " href=\"%s\"", html_string(href));
358 if (tooltip && tooltip[0])
359 gvprintf(job, " title=\"%s\"", html_string(tooltip));
360 if (target && target[0])
361 gvprintf(job, " target=\"%s\"", html_string(target));
362 gvputs(job, ">\n");
363}
364
365static void vml_end_anchor(GVJ_t * job)
366{
367 gvputs(job, "</a>\n");
368}
369
370static void vml_textspan(GVJ_t * job, pointf p, textspan_t * span)
371{
372 pointf p1,p2;
373 obj_state_t *obj = job->obj;
374 PostscriptAlias *pA;
375
376 switch (span->just) {
377 case 'l':
378 p1.x=p.x;
379 break;
380 case 'r':
381 p1.x=p.x-span->size.x;
382 break;
383 default:
384 case 'n':
385 p1.x=p.x-(span->size.x/2);
386 break;
387 }
388 p2.x=p1.x+span->size.x;
389 if (span->size.y < span->font->size){
390 span->size.y = 1 + (1.1*span->font->size);
391 }
392
393 p1.x-=8; /* vml textbox margin fudge factor */
394 p2.x+=8; /* vml textbox margin fudge factor */
395 p2.y=graphHeight-(p.y);
396 p1.y=(p2.y-span->size.y);
397 /* text "y" was too high
398 * Graphviz uses "baseline", VML seems to use bottom of descenders - so we fudge a little
399 * (heuristics - based on eyeballs) */
400 if (span->font->size <12.){ /* see graphs/directed/arrows.gv */
401 p1.y+=1.4+span->font->size/5; /* adjust by approx. descender */
402 p2.y+=1.4+span->font->size/5; /* adjust by approx. descender */
403 }else{
404 p1.y+=2+span->font->size/5; /* adjust by approx. descender */
405 p2.y+=2+span->font->size/5; /* adjust by approx. descender */
406 }
407
408 gvprintf(job, "<v:rect style=\"position:absolute; ");
409 gvprintf(job, " left: %.2f; top: %.2f;", p1.x, p1.y);
410 gvprintf(job, " width: %.2f; height: %.2f\"", p2.x-p1.x, p2.y-p1.y);
411 gvputs(job, " stroked=\"false\" filled=\"false\">\n");
412 gvputs(job, "<v:textbox inset=\"0,0,0,0\" style=\"position:absolute; v-text-wrapping:'false';padding:'0';");
413
414 pA = span->font->postscript_alias;
415 if (pA) {
416 gvprintf(job, "font-family: '%s';", pA->family);
417 if (pA->weight)
418 gvprintf(job, "font-weight: %s;", pA->weight);
419 if (pA->stretch)
420 gvprintf(job, "font-stretch: %s;", pA->stretch);
421 if (pA->style)
422 gvprintf(job, "font-style: %s;", pA->style);
423 }
424 else {
425 gvprintf(job, "font-family: \'%s\';", span->font->name);
426 }
427 gvprintf(job, " font-size: %.2fpt;", span->font->size);
428 switch (obj->pencolor.type) {
429 case COLOR_STRING:
430 if (strcasecmp(obj->pencolor.u.string, "black"))
431 gvprintf(job, "color:%s;", obj->pencolor.u.string);
432 break;
433 case RGBA_BYTE:
434 gvprintf(job, "color:#%02x%02x%02x;",
435 obj->pencolor.u.rgba[0], obj->pencolor.u.rgba[1], obj->pencolor.u.rgba[2]);
436 break;
437 default:
438 assert(0); /* internal error */
439 }
440 gvputs(job, "\"><center>");
441 gvputs(job, html_string(span->str));
442 gvputs(job, "</center></v:textbox>\n");
443 gvputs(job, "</v:rect>\n");
444}
445
446static void vml_ellipse(GVJ_t * job, pointf * A, int filled)
447{
448 double dx, dy, left, right, top, bottom;
449
450 /* A[] contains 2 points: the center and corner. */
451 gvputs(job, " <v:oval style=\"position:absolute;");
452
453 dx=A[1].x-A[0].x;
454 dy=A[1].y-A[0].y;
455
456 top=graphHeight-(A[0].y+dy);
457 bottom=top+dy+dy;
458 left=A[0].x - dx;
459 right=A[1].x;
460 gvprintf(job, " left: %.2f; top: %.2f;",left, top);
461 gvprintf(job, " width: %.2f; height: %.2f\"", 2*dx, 2*dy);
462
463 vml_grfill(job, filled);
464 gvputs(job, " >");
465 vml_grstroke(job, filled);
466 gvputs(job, "</v:oval>\n");
467}
468
469static void
470vml_bezier(GVJ_t * job, pointf * A, int n, int arrow_at_start,
471 int arrow_at_end, int filled)
472{
473 gvputs(job, " <v:shape style=\"position:absolute; ");
474 gvprintf(job, " width: %d; height: %d\"", graphWidth, graphHeight);
475
476 vml_grfill(job, filled);
477 gvputs(job, " >");
478 vml_grstroke(job, filled);
479 gvputs(job, "<v:path v=\"");
480 vml_bzptarray(job, A, n);
481 gvputs(job, "/></v:shape>\n");
482}
483
484static void vml_polygon(GVJ_t * job, pointf * A, int n, int filled)
485{
486 int i;
487 double px,py;
488
489 gvputs(job, " <v:shape style=\"position:absolute; ");
490 gvprintf(job, " width: %d; height: %d\"", graphWidth, graphHeight);
491 vml_grfill(job, filled);
492 gvputs(job, " >");
493 vml_grstroke(job, filled);
494
495 gvputs(job, "<v:path v=\"");
496 for (i = 0; i < n; i++)
497 {
498 px=A[i].x;
499 py= (graphHeight-A[i].y);
500 if (i==0){
501 gvputs(job, "m ");
502 }
503 /* integers only in path */
504 gvprintf(job, "%.0f %.0f ", px, py);
505 if (i==0) gvputs(job, "l ");
506 if (i==n-1) gvputs(job, "x e \"/>");
507 }
508 gvputs(job, "</v:shape>\n");
509}
510
511static void vml_polyline(GVJ_t * job, pointf * A, int n)
512{
513 int i;
514
515 gvputs(job, " <v:shape style=\"position:absolute; ");
516 gvprintf(job, " width: %d; height: %d\" filled=\"false\">", graphWidth, graphHeight);
517 gvputs(job, "<v:path v=\"");
518 for (i = 0; i < n; i++)
519 {
520 if (i==0) gvputs(job, " m ");
521 gvprintf(job, "%.0f,%.0f ", A[i].x, graphHeight-A[i].y);
522 if (i==0) gvputs(job, " l ");
523 if (i==n-1) gvputs(job, " e "); /* no x here for polyline */
524 }
525 gvputs(job, "\"/>");
526 vml_grstroke(job, 0); /* no fill here for polyline */
527 gvputs(job, "</v:shape>\n");
528}
529
530/* color names from
531 http://msdn.microsoft.com/en-us/library/bb250525(VS.85).aspx#t.color
532*/
533/* NB. List must be LANG_C sorted */
534static char *vml_knowncolors[] = {
535 "aqua", "black", "blue", "fuchsia",
536 "gray", "green", "lime", "maroon",
537 "navy", "olive", "purple", "red",
538 "silver", "teal", "white", "yellow"
539};
540
541gvrender_engine_t vml_engine = {
542 vml_begin_job,
543 0, /* vml_end_job */
544 vml_begin_graph,
545 vml_end_graph,
546 0, /* vml_begin_layer */
547 0, /* vml_end_layer */
548 0, /* vml_begin_page */
549 0, /* vml_end_page */
550 0, /* vml_begin_cluster */
551 0, /* vml_end_cluster */
552 0, /* vml_begin_nodes */
553 0, /* vml_end_nodes */
554 0, /* vml_begin_edges */
555 0, /* vml_end_edges */
556 0, /* vml_begin_node */
557 0, /* vml_end_node */
558 0, /* vml_begin_edge */
559 0, /* vml_end_edge */
560 vml_begin_anchor,
561 vml_end_anchor,
562 0, /* vml_begin_label */
563 0, /* vml_end_label */
564 vml_textspan,
565 0, /* vml_resolve_color */
566 vml_ellipse,
567 vml_polygon,
568 vml_bezier,
569 vml_polyline,
570 vml_comment,
571 0, /* vml_library_shape */
572};
573
574gvrender_features_t render_features_vml = {
575 GVRENDER_Y_GOES_DOWN
576 | GVRENDER_DOES_TRANSFORM
577 | GVRENDER_DOES_LABELS
578 | GVRENDER_DOES_MAPS
579 | GVRENDER_DOES_TARGETS
580 | GVRENDER_DOES_TOOLTIPS, /* flags */
581 0., /* default pad - graph units */
582 vml_knowncolors, /* knowncolors */
583 sizeof(vml_knowncolors) / sizeof(char *), /* sizeof knowncolors */
584 RGBA_BYTE, /* color_type */
585};
586
587gvdevice_features_t device_features_vml = {
588 GVDEVICE_DOES_TRUECOLOR, /* flags */
589 {0.,0.}, /* default margin - points */
590 {0.,0.}, /* default page width, height - points */
591 {96.,96.}, /* default dpi */
592};
593
594gvdevice_features_t device_features_vmlz = {
595 GVDEVICE_DOES_TRUECOLOR
596 | GVDEVICE_COMPRESSED_FORMAT, /* flags */
597 {0.,0.}, /* default margin - points */
598 {0.,0.}, /* default page width, height - points */
599 {96.,96.}, /* default dpi */
600};
601
602gvplugin_installed_t gvrender_vml_types[] = {
603 {FORMAT_VML, "vml", 1, &vml_engine, &render_features_vml},
604 {0, NULL, 0, NULL, NULL}
605};
606
607gvplugin_installed_t gvdevice_vml_types[] = {
608 {FORMAT_VML, "vml:vml", 1, NULL, &device_features_vml},
609#if HAVE_LIBZ
610 {FORMAT_VMLZ, "vmlz:vml", 1, NULL, &device_features_vmlz},
611#endif
612 {0, NULL, 0, NULL, NULL}
613};
614
615