Corrected a long-standing error in which ending text with a literal
[xcircuit.git] / svg.c
blob7788dfef845c24bdbd330bf076b1c77b0441228a
1 /*----------------------------------------------------------------------*/
2 /* svg.c */
3 /* Copyright (c) 2009 Tim Edwards, Open Circuit Design */
4 /*----------------------------------------------------------------------*/
6 #include <stdio.h>
7 #include <stdlib.h>
8 #include <stdint.h>
9 #include <string.h>
10 #ifndef XC_WIN32
11 #include <unistd.h>
12 #endif
13 #include <math.h>
14 #include <limits.h>
15 #include <sys/stat.h>
16 #ifndef XC_WIN32
17 #include <sys/wait.h>
18 #else
19 #include <process.h>
20 #endif
22 #ifndef _MSC_VER
23 #include <X11/Intrinsic.h>
24 #include <X11/StringDefs.h>
25 #endif
27 #ifndef HAVE_VFORK
28 #define vfork fork
29 #endif
31 /*----------------------------------------------------------------------*/
32 /* Local includes */
33 /*----------------------------------------------------------------------*/
35 #ifdef TCL_WRAPPER
36 #include <tk.h>
37 #endif
39 #include "colordefs.h"
40 #include "xcircuit.h"
42 /*----------------------------------------------------------------------*/
43 /* Function prototype declarations */
44 /*----------------------------------------------------------------------*/
45 #include "prototypes.h"
47 void SVGDrawString(labelptr, int, objinstptr);
49 /*----------------------------------------------------------------------*/
50 /* External Variable definitions */
51 /*----------------------------------------------------------------------*/
53 extern Display *dpy;
54 extern Pixmap STIPPLE[8];
55 extern XCWindowData *areawin;
56 extern Globaldata xobjs;
57 extern colorindex *colorlist;
58 extern int number_colors;
59 extern fontinfo *fonts;
60 extern short fontcount;
62 /*----------------------------------------------------------------------*/
63 /* The output file is a global variable used by all routines. */
64 /*----------------------------------------------------------------------*/
66 FILE *svgf;
68 /*----------------------------------------------------------------------*/
69 /*----------------------------------------------------------------------*/
71 void svg_printcolor(int passcolor, char *prefix)
73 if ((passcolor < number_colors) && (passcolor != DEFAULTCOLOR)) {
74 fprintf(svgf, "%s\"#%02x%02x%02x\" ",
75 prefix,
76 (colorlist[passcolor].color.red >> 8),
77 (colorlist[passcolor].color.green >> 8),
78 (colorlist[passcolor].color.blue >> 8));
82 /*----------------------------------------------------------------------*/
83 /* Since we can't do stipples in SVG, and since the whole stipple thing */
84 /* was only put in because we can't (easily) do transparency effects */
85 /* in X11, we convert stipples to transparency when the stipple is a */
86 /* mask, and do a color blend with white when the stipple is opaque. */
87 /* */
88 /* Blend amount is 1 (almost original color) to 7 (almost white) */
89 /*----------------------------------------------------------------------*/
91 void svg_blendcolor(int passcolor, char *prefix, int amount)
93 int i, bred, bgreen, bblue;
95 if (passcolor != DEFAULTCOLOR) {
96 for (i = 0; i < number_colors; i++) {
97 if (colorlist[i].color.pixel == passcolor) break;
99 if (i < number_colors) {
100 bred = colorlist[i].color.red >> 8;
101 bgreen = colorlist[i].color.green >> 8;
102 bblue = colorlist[i].color.blue >> 8;
105 else {
106 bred = bgreen = bblue = 0;
108 bred = ((bred * amount) + (255 * (8 - amount))) >> 3;
109 bgreen = ((bgreen * amount) + (255 * (8 - amount))) >> 3;
110 bblue = ((bblue * amount) + (255 * (8 - amount))) >> 3;
112 fprintf(svgf, "%s\"#%02x%02x%02x\" ", prefix, bred, bgreen, bblue);
115 /*----------------------------------------------------------------------*/
116 /* Fill and/or draw a border around an element */
117 /*----------------------------------------------------------------------*/
119 void svg_stroke(int passcolor, short style, float width)
121 float tmpwidth;
122 short minwidth, solidpart, shade;
124 tmpwidth = UTopTransScale(xobjs.pagelist[areawin->page]->wirewidth * width);
125 minwidth = max(1, (short)tmpwidth);
127 if (style & FILLED || (!(style & FILLED) && style & OPAQUE)) {
128 if ((style & FILLSOLID) == FILLSOLID) {
129 svg_printcolor(passcolor, "fill=");
131 else if (!(style & FILLED)) {
132 fprintf(svgf, "fill=\"white\" ");
134 else {
135 shade = 1 + ((style & FILLSOLID) >> 5);
136 if (style & OPAQUE) {
137 svg_blendcolor(passcolor, "fill=", shade);
139 else {
140 svg_printcolor(passcolor, "fill=");
141 fprintf(svgf, "fill-opacity=\"%g\" ", (float)shade / 8);
145 else
146 fprintf(svgf, "fill=\"none\" ");
148 if (!(style & NOBORDER)) {
149 /* set up dots or dashes */
150 if (style & DASHED) solidpart = 4 * minwidth;
151 else if (style & DOTTED) solidpart = minwidth;
152 if (style & (DASHED | DOTTED)) {
153 fprintf(svgf, "style=\"stroke-dasharray:%d,%d\" ", solidpart, 4 * minwidth);
155 fprintf(svgf, "stroke-width=\"%g\" ", tmpwidth);
156 fprintf(svgf, "stroke-linecap=\"butt\" ");
157 if (style & SQUARECAP)
158 fprintf(svgf, "stroke-linejoin=\"miter\" ");
159 else
160 fprintf(svgf, "stroke-linejoin=\"bevel\" ");
162 else {
163 fprintf(svgf, "stroke-width=\"%g\" ", tmpwidth);
164 if (style & SQUARECAP) {
165 fprintf(svgf, "stroke-linejoin=\"miter\" ");
166 fprintf(svgf, "stroke-linecap=\"projecting\" ");
168 else {
169 fprintf(svgf, "stroke-linejoin=\"bevel\" ");
170 fprintf(svgf, "stroke-linecap=\"round\" ");
173 svg_printcolor(passcolor, "stroke=");
175 else
176 fprintf(svgf, "stroke=\"none\" ");
177 fprintf(svgf, "/>\n");
180 /*----------------------------------------------------------------------*/
181 /* Finish a path and fill and/or stroke */
182 /*----------------------------------------------------------------------*/
184 void svg_strokepath(int passcolor, short style, float width)
186 /* Finish the path, closing if necessary */
187 if (!(style & UNCLOSED))
188 fprintf(svgf, "z\" ");
189 else
190 fprintf(svgf, "\" ");
192 svg_stroke(passcolor, style, width);
195 /*-------------------------------------------------------------------------*/
197 void SVGCreateImages(int page)
199 Imagedata *img;
200 int i, x, y;
201 short *glist;
202 FILE *ppf;
203 union {
204 u_char b[4];
205 u_long i;
206 } pixel;
207 char *fname, outname[128], *pptr;
208 #ifndef XC_WIN32
209 pid_t pid;
210 #endif
212 /* Check which images are used on this page */
213 glist = (short *)malloc(xobjs.images * sizeof(short));
214 for (i = 0; i < xobjs.images; i++) glist[i] = 0;
215 count_graphics(xobjs.pagelist[page]->pageinst->thisobject, glist);
217 for (i = 0; i < xobjs.images; i++) {
218 if (glist[i] == 0) continue;
219 img = xobjs.imagelist + i;
221 /* Generate a PPM file, then convert it to PNG */
223 fname = tmpnam(NULL);
224 ppf = fopen(fname, "w");
225 if (ppf != NULL) {
226 int width, height;
227 width = xcImageGetWidth(img->image);
228 height = xcImageGetWidth(img->image);
229 fprintf(ppf, "P6 %d %d 255\n", width, height);
230 for (y = 0; y < height; y++) {
231 for (x = 0; x < width; x++) {
232 u_char r, g, b;
233 xcImageGetPixel(img->image, x, y, &r, &g, &b);
234 fwrite(&r, 1, 1, ppf);
235 fwrite(&g, 1, 1, ppf);
236 fwrite(&b, 1, 1, ppf);
240 fclose(ppf);
242 /* Run "convert" to make this into a png file */
244 strcpy(outname, img->filename);
245 if ((pptr = strrchr(outname, '.')) != NULL)
246 strcpy(pptr, ".png");
247 else
248 strcat(outname, ".png");
250 #ifndef XC_WIN32
251 if ((pid = vfork()) == 0) {
252 execlp("convert", "convert", fname, outname, NULL);
253 exit(0); /* not reached */
255 waitpid(pid, NULL, 0);
256 unlink(fname);
257 #else
258 _spawnl(_P_WAIT, GM_EXEC, GM_EXEC, "convert", fname, outname, NULL);
259 _unlink(fname);
260 #endif
261 Fprintf(stdout, "Generated standalone PNG image file %s\n", outname);
263 free(glist);
266 /*-------------------------------------------------------------------------*/
268 void SVGDrawGraphic(graphicptr gp)
270 XPoint ppt, corner;
271 Imagedata *img;
272 int i;
273 char outname[128], *pptr;
274 float tscale;
275 float rotation;
276 int width = xcImageGetWidth(gp->source);
277 int height = xcImageGetHeight(gp->source);
279 for (i = 0; i < xobjs.images; i++) {
280 img = xobjs.imagelist + i;
281 if (img->image == gp->source)
282 break;
284 if (i == xobjs.images) return;
286 strcpy(outname, img->filename);
287 if ((pptr = strrchr(outname, '.')) != NULL)
288 strcpy(pptr, ".png");
289 else
290 strcat(outname, ".png");
292 UPushCTM();
293 UPreMultCTM(DCTM, gp->position, gp->scale, gp->rotation);
294 corner.x = -(width >> 1);
295 corner.y = (height >> 1);
296 UTransformbyCTM(DCTM, &corner, &ppt, 1);
297 UPopCTM();
299 tscale = gp->scale * UTopScale();
300 rotation = gp->rotation + UTopRotation();
301 if (rotation >= 360.0) rotation -= 360.0;
302 else if (rotation < 0.0) rotation += 360.0;
304 fprintf(svgf, "<image transform=\"translate(%d,%d) scale(%g) rotate(%f)\"\n",
305 ppt.x, ppt.y, tscale, rotation);
306 fprintf(svgf, " width=\"%dpx\" height=\"%dpx\"", width, height);
307 fprintf(svgf, " xlink:href=\"%s\">\n", outname);
308 fprintf(svgf, "</image>\n");
311 /*-------------------------------------------------------------------------*/
313 void SVGDrawSpline(splineptr thespline, int passcolor)
315 XPoint tmppoints[4];
317 UTransformbyCTM(DCTM, thespline->ctrl, tmppoints, 4);
319 fprintf(svgf, "<path d=\"M%d,%d C%d,%d %d,%d %d,%d ",
320 tmppoints[0].x, tmppoints[0].y,
321 tmppoints[1].x, tmppoints[1].y,
322 tmppoints[2].x, tmppoints[2].y,
323 tmppoints[3].x, tmppoints[3].y);
324 svg_strokepath(passcolor, thespline->style, thespline->width);
327 /*-------------------------------------------------------------------------*/
329 void SVGDrawPolygon(polyptr thepoly, int passcolor)
331 int i;
332 XPoint *tmppoints = (pointlist) malloc(thepoly->number * sizeof(XPoint));
334 UTransformbyCTM(DCTM, thepoly->points, tmppoints, thepoly->number);
336 fprintf(svgf, "<path ");
337 if (thepoly->style & BBOX) fprintf(svgf, "visibility=\"hidden\" ");
338 fprintf(svgf, "d=\"M%d,%d L", tmppoints[0].x, tmppoints[0].y);
339 for (i = 1; i < thepoly->number; i++) {
340 fprintf(svgf, "%d,%d ", tmppoints[i].x, tmppoints[i].y);
343 svg_strokepath(passcolor, thepoly->style, thepoly->width);
344 free(tmppoints);
347 /*-------------------------------------------------------------------------*/
349 void SVGDrawArc(arcptr thearc, int passcolor)
351 XPoint endpoints[2];
352 int radius[2];
353 int tarc;
355 radius[0] = UTopTransScale(thearc->radius);
356 radius[1] = UTopTransScale(thearc->yaxis);
358 tarc = (thearc->angle2 - thearc->angle1);
359 if (tarc == 360) {
360 UTransformbyCTM(DCTM, &(thearc->position), endpoints, 1);
361 fprintf(svgf, "<ellipse cx=\"%d\" cy=\"%d\" rx=\"%d\" ry=\"%d\" ",
362 endpoints[0].x, endpoints[0].y, radius[0], radius[1]);
363 svg_stroke(passcolor, thearc->style, thearc->width);
365 else {
366 UfTransformbyCTM(DCTM, thearc->points, endpoints, 1);
367 UfTransformbyCTM(DCTM, thearc->points + thearc->number - 1, endpoints + 1, 1);
369 /* When any arc is flipped, the direction of travel reverses. */
370 fprintf(svgf, "<path d=\"M%d,%d A%d,%d 0 %d,%d %d,%d ",
371 endpoints[0].x, endpoints[0].y,
372 radius[0], radius[1],
373 ((tarc > 180) ? 1 : 0),
374 (((DCTM->a * DCTM->e) >= 0) ? 1 : 0),
375 endpoints[1].x, endpoints[1].y);
376 svg_strokepath(passcolor, thearc->style, thearc->width);
380 /*-------------------------------------------------------------------------*/
382 void SVGDrawPath(pathptr thepath, int passcolor)
384 XPoint *tmppoints = (pointlist) malloc(sizeof(XPoint));
385 genericptr *genpath;
386 polyptr thepoly;
387 splineptr thespline;
388 int i, firstpt = 1;
390 fprintf(svgf, "<path d=\"");
392 for (genpath = thepath->plist; genpath < thepath->plist + thepath->parts;
393 genpath++) {
394 switch(ELEMENTTYPE(*genpath)) {
395 case POLYGON:
396 thepoly = TOPOLY(genpath);
397 tmppoints = (pointlist) realloc(tmppoints, thepoly->number * sizeof(XPoint));
398 UTransformbyCTM(DCTM, thepoly->points, tmppoints, thepoly->number);
399 if (firstpt) {
400 fprintf(svgf, "M%d,%d ", tmppoints[0].x, tmppoints[0].y);
401 firstpt = 0;
403 fprintf(svgf, "L");
404 for (i = 1; i < thepoly->number; i++) {
405 fprintf(svgf, "%d,%d ", tmppoints[i].x, tmppoints[i].y);
407 break;
408 case SPLINE:
409 thespline = TOSPLINE(genpath);
410 tmppoints = (pointlist) realloc(tmppoints, 4 * sizeof(XPoint));
411 UTransformbyCTM(DCTM, thespline->ctrl, tmppoints, 4);
412 if (firstpt) {
413 fprintf(svgf, "M%d,%d ", tmppoints[0].x, tmppoints[0].y);
414 firstpt = 0;
416 fprintf(svgf, "C%d,%d %d,%d %d,%d ",
417 tmppoints[1].x, tmppoints[1].y,
418 tmppoints[2].x, tmppoints[2].y,
419 tmppoints[3].x, tmppoints[3].y);
420 break;
423 svg_strokepath(passcolor, thepath->style, thepath->width);
424 free(tmppoints);
427 /*----------------------------------------------------------------------*/
428 /* Main recursive object instance drawing routine. */
429 /* context is the instance information passed down from above */
430 /* theinstance is the object instance to be drawn */
431 /* level is the level of recursion */
432 /* passcolor is the inherited color value passed to object */
433 /*----------------------------------------------------------------------*/
435 void SVGDrawObject(objinstptr theinstance, short level, int passcolor, pushlistptr *stack)
437 genericptr *areagen;
438 float tmpwidth;
439 int defaultcolor = passcolor;
440 int curcolor = passcolor;
441 int thispart;
442 objectptr theobject = theinstance->thisobject;
444 /* All parts are given in the coordinate system of the object, unless */
445 /* this is the top-level object, in which they will be interpreted as */
446 /* relative to the screen. */
448 UPushCTM();
450 if (stack) push_stack(stack, theinstance, NULL);
451 if (level != 0)
452 UPreMultCTM(DCTM, theinstance->position, theinstance->scale,
453 theinstance->rotation);
455 /* make parameter substitutions */
456 psubstitute(theinstance);
458 /* draw all of the elements */
460 tmpwidth = UTopTransScale(xobjs.pagelist[areawin->page]->wirewidth);
462 /* Here---set a default style using "g" like PostScript "gsave" */
463 /* stroke-width = tmpwidth, stroke = passcolor */
465 /* guard against plist being regenerated during a redraw by the */
466 /* expression parameter mechanism (should that be prohibited?) */
468 for (thispart = 0; thispart < theobject->parts; thispart++) {
469 areagen = theobject->plist + thispart;
470 if ((*areagen)->type & DRAW_HIDE) continue;
472 if (defaultcolor != DOFORALL) {
473 if ((*areagen)->color != curcolor) {
474 if ((*areagen)->color == DEFAULTCOLOR)
475 curcolor = defaultcolor;
476 else
477 curcolor = (*areagen)->color;
481 switch(ELEMENTTYPE(*areagen)) {
482 case(POLYGON):
483 if (level == 0 || !((TOPOLY(areagen))->style & BBOX))
484 SVGDrawPolygon(TOPOLY(areagen), curcolor);
485 break;
487 case(SPLINE):
488 SVGDrawSpline(TOSPLINE(areagen), curcolor);
489 break;
491 case(ARC):
492 SVGDrawArc(TOARC(areagen), curcolor);
493 break;
495 case(PATH):
496 SVGDrawPath(TOPATH(areagen), curcolor);
497 break;
499 case(GRAPHIC):
500 SVGDrawGraphic(TOGRAPHIC(areagen));
501 break;
503 case(OBJINST):
504 if (areawin->editinplace && stack && (TOOBJINST(areagen)
505 == areawin->topinstance)) {
506 /* If stack matches areawin->stack, then don't */
507 /* draw because it would be redundant. */
508 pushlistptr alist = *stack, blist = areawin->stack;
509 while (alist && blist) {
510 if (alist->thisinst != blist->thisinst) break;
511 alist = alist->next;
512 blist = blist->next;
514 if ((!alist) || (!blist)) break;
516 SVGDrawObject(TOOBJINST(areagen), level + 1, curcolor, stack);
517 break;
519 case(LABEL):
520 if (level == 0 || TOLABEL(areagen)->pin == False ||
521 (TOLABEL(areagen)->anchor & PINVISIBLE))
522 SVGDrawString(TOLABEL(areagen), curcolor, theinstance);
523 break;
527 UPopCTM();
528 if (stack) pop_stack(stack);
531 /*----------------------------------------------------------------------*/
533 #define addlinepoint(pointlist, numvals, xval, yval) \
534 { if (!numvals) pointlist = (XPoint *)malloc(sizeof(XPoint)); \
535 else pointlist = (XPoint *)realloc(pointlist, (numvals + 1) * sizeof(XPoint)); \
536 pointlist[numvals].x = xval; \
537 pointlist[numvals++].y = -yval; \
540 /*----------------------------------------------------------------------*/
541 /* Draw an entire string, including parameter substitutions */
542 /*----------------------------------------------------------------------*/
544 void SVGDrawString(labelptr drawlabel, int passcolor, objinstptr localinst)
546 stringpart *strptr;
547 char *textptr;
548 short fstyle, ffont, tmpanchor, baseline, deltay;
549 int pos, defaultcolor, curcolor, scolor;
550 short oldx, oldfont, oldstyle;
551 int olinerise = 4;
552 float tmpscale = 1.0, natscale = 1.0;
553 XPoint newpoint;
554 TextExtents tmpext;
555 TextLinesInfo tlinfo;
556 short *tabstops = NULL;
557 short tabno, numtabs = 0, group = 0;
558 short linenum = 0;
559 int open_text, open_span, open_decor;
560 XPoint *decorations = NULL;
561 short nvals = 0;
563 char *symbol_html_encoding[] = {
564 " ", "!", "&#8704;", "#", "&#8707;", "%", "&", "?", "(", ")",
565 "*", "+", ",", "&#8722;", ".", "/", "0", "1", "2", "3", "4",
566 "5", "6", "7", "8", "9", ":", ";", "&lt;", "=", "&gt;", "?", "&#8773;",
567 "&#913;", "&#914;", "&#935;", "&#916;", "&#917;", "&#934;",
568 "&#915;", "&#919;", "&#921;", "&#977;", "&#922;", "&#923;",
569 "&#924;", "&#925;", "&#927;", "&#928;", "&#920;", "&#929;",
570 "&#931;", "&#932;", "&#933;", "&#963;", "&#937;", "&#926;",
571 "&#936;", "&#918;", "[", "&#8756;", "]", "&#8869;", "_",
572 "&#8254;", "&#945;", "&#946;", "&#967;", "&#948;", "&#949;",
573 "&#966;", "&#947;", "&#951;", "&#953;", "&#966;", "&#954;",
574 "&#955;", "&#956;", "&#957;", "&#959;", "&#960;", "&#952;",
575 "&#961;", "&#963;", "&#964;", "&#965;", "&#969;", "&#969;",
576 "&#958;", "&#968;", "&#950;", "{", "|", "}", "~", "", "", "",
577 "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
578 "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
579 "&#978;", "&#8242;", "&#8804;", "&#8260;", "&#8734;", "&#402;",
580 "&#9827;", "&#9830;", "&#9829;", "&#9824;", "&#8596;",
581 "&#8592;", "&#8593;", "&#8594;", "&#8595;", "&#176;", "&#177;",
582 "&#8243;", "&#8805;", "&#215;", "&#8733;", "&#8706;", "&#8226;",
583 "&#247;", "&#8800;", "&#8801;", "&#8773;", "&#8230;"
586 /* Standard encoding vector, in HTML, from character 161 to 255 */
587 u_int standard_html_encoding[] = {
588 161, 162, 163, 8725, 165, 131, 167, 164, 146, 147, 171, 8249,
589 8250, 64256, 64258, 0, 8211, 8224, 8225, 183, 0, 182, 8226,
590 8218, 8222, 8221, 187, 8230, 8240, 0, 191, 0, 96, 180, 710,
591 126, 713, 728, 729, 168, 0, 730, 184, 0, 733, 731, 711, 8212,
592 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 508, 0,
593 170, 0, 0, 0, 0, 321, 216, 338, 186, 0, 0, 0, 0, 0, 230, 0,
594 0, 0, 185, 0, 0, 322, 248, 339, 223};
596 if (fontcount == 0) return;
598 /* Don't draw temporary labels from schematic capture system */
599 if (drawlabel->string->type != FONT_NAME) return;
601 if (passcolor == DOSUBSTRING)
602 defaultcolor = curcolor = drawlabel->color;
603 else
604 defaultcolor = curcolor = passcolor;
606 if (defaultcolor != DOFORALL) {
607 if (drawlabel->color != DEFAULTCOLOR)
608 curcolor = drawlabel->color;
609 else
610 curcolor = defaultcolor;
613 /* calculate the transformation matrix for this object */
614 /* in natural units of the alphabet vectors */
615 /* (conversion to window units) */
617 /* Labels don't rotate in Firefox, so use <g> record for transform */
619 UPushCTM();
620 UPreMultCTM(DCTM, drawlabel->position, drawlabel->scale, drawlabel->rotation);
622 /* check for flip invariance; recompute CTM and anchoring if necessary */
623 tmpanchor = flipadjust(drawlabel->anchor);
625 /* Note that the Y-scale is inverted or text comes out upside-down. But we */
626 /* need to adjust to the Y baseline. */
628 fprintf(svgf, "<g transform=\"matrix(%4g %4g %4g %4g %3g %3g)\" ",
629 DCTM->a, DCTM->d, -(DCTM->b), -(DCTM->e), DCTM->c, DCTM->f);
631 svg_printcolor(passcolor, "fill=");
632 fprintf(svgf, ">\n");
634 /* "natural" (unscaled) length */
635 tlinfo.dostop = 0;
636 tlinfo.tbreak = NULL;
637 tlinfo.padding = NULL;
638 tmpext = ULength(drawlabel, localinst, &tlinfo);
640 newpoint.x = (tmpanchor & NOTLEFT ?
641 (tmpanchor & RIGHT ? -tmpext.maxwidth : -tmpext.maxwidth >> 1) : 0);
642 newpoint.y = (tmpanchor & NOTBOTTOM ?
643 (tmpanchor & TOP ? -tmpext.ascent : -(tmpext.ascent + tmpext.base) >> 1)
644 : -tmpext.base);
646 /* Pinlabels have an additional offset spacing to pad */
647 /* them from the circuit point to which they attach. */
649 if (drawlabel->pin) {
650 pinadjust(tmpanchor, &(newpoint.x), &(newpoint.y), 1);
653 oldx = newpoint.x;
654 baseline = newpoint.y;
656 open_text = -1;
657 open_span = 0;
658 open_decor = 0;
659 pos = 0;
661 /* Adjust for center or right justification */
662 if (tlinfo.padding != NULL) {
663 if (tmpanchor & JUSTIFYRIGHT)
664 newpoint.x += tlinfo.padding[linenum];
665 else if (tmpanchor & TEXTCENTERED)
666 newpoint.x += 0.5 * tlinfo.padding[linenum];
667 linenum++;
670 for (strptr = drawlabel->string; strptr != NULL;
671 strptr = nextstringpart(strptr, localinst)) {
673 /* All segments other than text cancel any */
674 /* existing overline/underline in effect. */
676 if (strptr->type != TEXT_STRING)
677 fstyle &= 0xfc7;
679 switch(strptr->type) {
680 case RETURN:
681 while (open_span > 0) {
682 fprintf(svgf, "</tspan>");
683 open_span--;
685 while (open_text > 0) {
686 fprintf(svgf, "</text>");
687 open_text--;
689 if (open_decor) {
690 addlinepoint(decorations, nvals, newpoint.x, group);
691 open_decor--;
693 break;
695 case FONT_SCALE:
696 case FONT_NAME:
697 while (open_span > 0) {
698 fprintf(svgf, "</tspan>");
699 open_span--;
701 while (open_text > 0) {
702 fprintf(svgf, "</text>");
703 open_text--;
705 if (open_decor) {
706 addlinepoint(decorations, nvals, newpoint.x, group);
707 open_decor--;
709 break;
711 case KERN:
712 case TABFORWARD:
713 case TABBACKWARD:
714 case TABSTOP:
715 case HALFSPACE:
716 case QTRSPACE:
717 case NOLINE:
718 case UNDERLINE:
719 case OVERLINE:
720 case SUBSCRIPT:
721 case SUPERSCRIPT:
722 case NORMALSCRIPT:
723 while (open_span > 1) {
724 fprintf(svgf, "</tspan>");
725 open_span--;
727 if (open_decor) {
728 addlinepoint(decorations, nvals, newpoint.x, group);
729 open_decor--;
731 break;
733 /* These do not need to be handled */
734 case TEXT_STRING:
735 case PARAM_START:
736 case PARAM_END:
737 break;
739 /* These are not handled yet, but should be */
740 case FONT_COLOR:
741 break;
743 default:
744 break;
747 /* deal with each text segment type */
749 switch(strptr->type) {
750 case FONT_SCALE:
751 case FONT_NAME:
752 if (strptr->data.font < fontcount) {
753 ffont = strptr->data.font;
754 fstyle = 0; /* style reset by font change */
755 if (baseline == newpoint.y) { /* set top-level font and style */
756 oldfont = ffont;
757 oldstyle = fstyle;
760 fprintf(svgf, "<text stroke=\"none\" ");
761 fprintf(svgf, "font-family=");
762 if (issymbolfont(ffont))
763 fprintf(svgf, "\"Times\" ");
764 else if (!strncmp(fonts[ffont].family, "Times", 5))
765 fprintf(svgf, "\"Times\" ");
766 else
767 fprintf(svgf, "\"%s\" ", fonts[ffont].family);
769 if (fonts[ffont].flags & 0x1)
770 fprintf(svgf, " font-weight=\"bold\" ");
771 if (fonts[ffont].flags & 0x2) {
772 if (issansfont(ffont))
773 fprintf(svgf, " font-style=\"oblique\" ");
774 else
775 fprintf(svgf, " font-style=\"italic\" ");
777 olinerise = (issansfont(ffont)) ? 7 : 4;
779 if (strptr->type == FONT_SCALE) {
780 tmpscale = natscale * strptr->data.scale;
781 if (baseline == newpoint.y) /* reset top-level scale */
782 natscale = tmpscale;
784 else
785 tmpscale = 1;
787 /* Actual scale taken care of by transformation matrix */
788 fprintf(svgf, "font-size=\"%g\" >", tmpscale * 40);
789 fprintf(svgf, "<tspan x=\"%d\" y=\"%d\">", newpoint.x, -newpoint.y);
790 open_text++;
791 open_span++;
792 break;
794 case KERN:
795 newpoint.x += strptr->data.kern[0];
796 newpoint.y += strptr->data.kern[1];
797 fprintf(svgf, "<text dx=\"%d\" dy=\"%d\">",
798 strptr->data.kern[0], strptr->data.kern[1]);
799 open_text++;
800 break;
802 case FONT_COLOR:
803 if (defaultcolor != DOFORALL) {
804 if (strptr->data.color != DEFAULTCOLOR)
805 curcolor = colorlist[strptr->data.color].color.pixel;
806 else {
807 curcolor = DEFAULTCOLOR;
810 break;
812 case TABBACKWARD: /* find first tab value with x < xtotal */
813 for (tabno = numtabs - 1; tabno >= 0; tabno--) {
814 if (tabstops[tabno] < newpoint.x) {
815 newpoint.x = tabstops[tabno];
816 break;
819 fprintf(svgf, "<tspan x=\"%d\">", newpoint.x);
820 open_span++;
821 break;
823 case TABFORWARD: /* find first tab value with x > xtotal */
824 for (tabno = 0; tabno < numtabs; tabno++) {
825 if (tabstops[tabno] > newpoint.x) {
826 newpoint.x = tabstops[tabno];
827 break;
830 fprintf(svgf, "<tspan x=\"%d\">", newpoint.x);
831 open_span++;
832 break;
834 case TABSTOP:
835 numtabs++;
836 if (tabstops == NULL) tabstops = (short *)malloc(sizeof(short));
837 else tabstops = (short *)realloc(tabstops, numtabs * sizeof(short));
838 tabstops[numtabs - 1] = newpoint.x;
839 /* Force a tab at this point so that the output aligns */
840 /* to our computation of the position, not its own. */
841 fprintf(svgf, "<tspan x=\"%d\">", newpoint.x);
842 open_span++;
843 break;
845 case RETURN:
846 tmpscale = natscale = 1.0;
847 baseline -= BASELINE;
848 newpoint.y = baseline;
849 newpoint.x = oldx;
851 if (tlinfo.padding != NULL) {
852 if (tmpanchor & JUSTIFYRIGHT)
853 newpoint.x += tlinfo.padding[linenum];
854 else if (tmpanchor & TEXTCENTERED)
855 newpoint.x += 0.5 * tlinfo.padding[linenum];
856 linenum++;
859 fprintf(svgf, "<tspan x=\"%d\" y=\"%d\">", newpoint.x, -newpoint.y);
860 open_span++;
861 break;
863 case SUBSCRIPT:
864 natscale *= SUBSCALE;
865 tmpscale = natscale;
866 deltay = (short)((TEXTHEIGHT >> 1) * natscale);
867 newpoint.y -= deltay;
868 fprintf(svgf, "<tspan dy=\"%d\" font-size=\"%g\">", deltay,
869 40 * natscale);
870 open_span++;
871 break;
873 case SUPERSCRIPT:
874 natscale *= SUBSCALE;
875 tmpscale = natscale;
876 deltay = (short)(TEXTHEIGHT * natscale);
877 newpoint.y += deltay;
878 fprintf(svgf, "<tspan dy=\"%d\" font-size=\"%g\">", -deltay,
879 40 * natscale);
880 open_span++;
881 break;
883 case NORMALSCRIPT:
884 tmpscale = natscale = 1.0;
885 ffont = oldfont; /* revert to top-level font and style */
886 fstyle = oldstyle;
887 newpoint.y = baseline;
888 fprintf(svgf, "<tspan y=\"%d\">", baseline);
889 open_span++;
890 break;
892 case UNDERLINE:
893 fstyle |= 8;
894 group = newpoint.y - 6;
895 addlinepoint(decorations, nvals, newpoint.x, group);
896 open_decor++;
897 break;
899 case OVERLINE:
900 if (strptr->nextpart != NULL && strptr->nextpart->type == TEXT_STRING) {
901 objectptr charptr;
902 int tmpheight;
904 group = 0;
905 for (textptr = strptr->nextpart->data.string;
906 textptr && *textptr != '\0'; textptr++) {
907 charptr = fonts[ffont].encoding[*(u_char *)textptr];
908 tmpheight = (int)((float)charptr->bbox.height
909 * fonts[ffont].scale);
910 if (group < tmpheight) group = (short)tmpheight;
912 fstyle |= 16;
913 group += olinerise + newpoint.y;
914 addlinepoint(decorations, nvals, newpoint.x, group);
916 open_decor++;
917 break;
919 case NOLINE:
920 break;
922 case HALFSPACE: case QTRSPACE: {
923 short addx;
924 objectptr drawchar = fonts[ffont].encoding[(u_char)32];
925 addx = (drawchar->bbox.lowerleft.x + drawchar->bbox.width) *
926 fonts[ffont].scale;
927 addx >>= ((strptr->type == HALFSPACE) ? 1 : 2);
928 newpoint.x += addx;
929 fprintf(svgf, "<tspan dx=\"%d\">", addx);
930 open_span++;
932 } break;
934 case TEXT_STRING:
935 textptr = strptr->data.string;
937 if (issymbolfont(ffont)) {
938 for (; *textptr != '\0'; textptr++)
939 if (((u_char)(*textptr) >= 32) && ((u_char)(*textptr) < 158))
940 fprintf(svgf, "%s", symbol_html_encoding[(*textptr) - 32]);
942 else {
943 /* Handle "&" and non-ASCII characters in the text */
944 if (isisolatin1(ffont)) {
945 for (; *textptr != '\0'; textptr++) {
946 if (*textptr == '&')
947 fprintf(svgf, "&amp;");
948 else if ((u_char)(*textptr) >= 128)
949 fprintf(svgf, "&#%d;", (int)((u_char)*textptr));
950 else if ((u_char)(*textptr) >= 32)
951 fprintf(svgf, "%c", *textptr);
954 else {
955 for (; *textptr != '\0'; textptr++) {
956 if (*textptr == '&')
957 fprintf(svgf, "&amp;");
958 else if ((u_char)(*textptr) >= 161)
959 fprintf(svgf, "&#%d;",
960 standard_html_encoding[(u_char)(*textptr)
961 - 161]);
962 else if ((u_char)(*textptr) >= 32 && (u_char)(*textptr) < 161)
963 fprintf(svgf, "%c", *textptr);
967 pos--;
969 /* Compute the new X position */
971 for (textptr = strptr->data.string; *textptr != '\0'; textptr++) {
972 objectptr drawchar = fonts[ffont].encoding[(u_char)(*textptr)];
973 short addx = (drawchar->bbox.lowerleft.x + drawchar->bbox.width) *
974 fonts[ffont].scale;
975 newpoint.x += addx;
977 break;
979 pos++;
981 while (open_span > 0) {
982 fprintf(svgf, "</tspan>");
983 open_span--;
985 while (open_text > 0) {
986 fprintf(svgf, "</text>");
987 open_text--;
989 fprintf(svgf, "\n</text>");
991 UPopCTM();
993 if (tabstops != NULL) free(tabstops);
994 if (tlinfo.padding != NULL) free(tlinfo.padding);
996 /* If there were decorations (underlines, overlines), generate them */
998 if (decorations != NULL) {
999 int i;
1000 if (open_decor) {
1001 addlinepoint(decorations, nvals, newpoint.x, group);
1003 for (i = 0; i < nvals; i += 2) {
1004 fprintf(svgf, "\n<line stroke-width=\"2\" stroke-linecap=\"square\" "
1005 "x1=\"%d\" y1=\"%d\" x2=\"%d\" y2=\"%d\" />",
1006 decorations[i].x, decorations[i].y, decorations[i + 1].x,
1007 decorations[i + 1].y);
1009 free(decorations);
1011 fprintf(svgf, "</g>\n");
1014 /*----------------------------------------------------------------------*/
1015 /* Write the SVG file output */
1016 /*----------------------------------------------------------------------*/
1018 #define PMARGIN 6 /* Pixel margin around drawing */
1020 void
1021 OutputSVG(char *filename, Boolean fullscale)
1023 short savesel;
1024 objinstptr pinst;
1025 int cstyle;
1026 float outwidth, outheight, cscale;
1028 svgf = fopen(filename, "w");
1029 if (svgf == NULL) {
1030 Fprintf(stderr, "Cannot open file %s for writing.\n", filename);
1031 return;
1034 /* Generate external image files, if necessary */
1035 SVGCreateImages(areawin->page);
1037 /* Save the number of selections and set it to zero while we do the */
1038 /* object drawing. */
1040 savesel = areawin->selects;
1041 areawin->selects = 0;
1042 pinst = xobjs.pagelist[areawin->page]->pageinst;
1044 UPushCTM(); /* Save the top-level graphics state */
1046 /* This is like UMakeWCTM()---it inverts the whole picture so that */
1047 /* The origin is at the top left, and all data points fit in a box */
1048 /* at (0, 0) to the object (width, height) */
1050 DCTM->a = 1.0;
1051 DCTM->b = 0.0;
1052 DCTM->c = -pinst->bbox.lowerleft.x;
1053 DCTM->d = 0.0;
1054 DCTM->e = -1.0;
1055 DCTM->f = pinst->bbox.lowerleft.y + pinst->bbox.height;
1057 fprintf(svgf, "<svg xmlns=\"http://www.w3.org/2000/svg\"\n");
1058 fprintf(svgf, " xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n");
1059 fprintf(svgf, " version=\"1.1\"\n");
1060 fprintf(svgf, " id=\"%s\" ", pinst->thisobject->name);
1062 if (fullscale) {
1063 fprintf(svgf, "width=\"100%%\" height=\"100%%\" ");
1065 else {
1066 cscale = getpsscale(xobjs.pagelist[areawin->page]->outscale, areawin->page);
1067 cstyle = xobjs.pagelist[areawin->page]->coordstyle;
1069 outwidth = toplevelwidth(pinst, NULL) * cscale;
1070 outwidth /= (cstyle == CM) ? IN_CM_CONVERT : 72.0;
1071 outheight = toplevelheight(pinst, NULL) * cscale;
1072 outheight /= (cstyle == CM) ? IN_CM_CONVERT : 72.0;
1074 /* Set display height to that specified in the output properties (in inches) */
1075 fprintf(svgf, "width=\"%.3g%s\" height=\"%.3g%s\" ",
1076 outwidth, (cstyle == CM) ? "cm" : "in",
1077 outheight, (cstyle == CM) ? "cm" : "in");
1079 fprintf(svgf, " viewBox=\"%d %d %d %d\">\n",
1080 -PMARGIN, -PMARGIN, pinst->bbox.width + PMARGIN,
1081 pinst->bbox.height + PMARGIN);
1083 fprintf(svgf, "<desc>\n");
1084 fprintf(svgf, "XCircuit Version %s\n", PROG_VERSION);
1085 fprintf(svgf, "File \"%s\" Page %d\n", xobjs.pagelist[areawin->page]->filename,
1086 areawin->page + 1);
1087 fprintf(svgf, "</desc>\n");
1089 /* Set default color to black */
1090 fprintf(svgf, "<g stroke=\"black\">\n");
1092 if (areawin->hierstack) free_stack(&areawin->hierstack);
1093 SVGDrawObject(areawin->topinstance, TOPLEVEL, FOREGROUND, &areawin->hierstack);
1094 if (areawin->hierstack) free_stack(&areawin->hierstack);
1096 /* restore the selection list (if any) */
1097 areawin->selects = savesel;
1099 fprintf(svgf, "</g>\n</svg>\n");
1100 fclose(svgf);
1102 UPopCTM(); /* Restore the top-level graphics state */
1105 #ifdef TCL_WRAPPER
1107 /*----------------------------------------------------------------------*/
1108 /* The TCL command-line for the SVG file write routine. */
1109 /*----------------------------------------------------------------------*/
1111 int xctcl_svg(ClientData clientData, Tcl_Interp *interp,
1112 int objc, Tcl_Obj *CONST objv[])
1114 char filename[128], *pptr;
1115 Boolean fullscale = 0;
1116 int locobjc = objc;
1117 char *lastarg;
1119 /* Argument "-full" forces full scale (not scaled per page output settings) */
1120 if (objc > 1) {
1121 lastarg = Tcl_GetString(objv[objc - 1]);
1122 if (lastarg[0] == '-') {
1123 if (!strncmp(lastarg + 1, "full", 4))
1124 fullscale = 1;
1125 else {
1126 Tcl_SetResult(interp, "Unknown option.\n", NULL);
1127 return TCL_ERROR;
1129 locobjc--;
1134 if (locobjc >= 2) {
1135 /* If there is a non-option argument, use it for the output filename */
1136 sprintf(filename, "%s", Tcl_GetString(objv[1]));
1138 else if (xobjs.pagelist[areawin->page]->pageinst->thisobject->name == NULL)
1139 sprintf(filename, "%s", xobjs.pagelist[areawin->page]->filename);
1140 else
1141 sprintf(filename, "%s", xobjs.pagelist[areawin->page]->pageinst->thisobject->name);
1143 pptr = strrchr(filename, '.');
1144 if (pptr != NULL)
1145 sprintf(pptr + 1, "svg");
1146 else if (strcmp(filename + strlen(filename) - 3, "svg"))
1147 strcat(filename, ".svg");
1149 OutputSVG(filename, fullscale);
1150 Fprintf(stdout, "Saved page as SVG format file \"%s\"\n", filename);
1151 return XcTagCallback(interp, objc, objv);
1154 #endif /* TCL_WRAPPER */
1156 /*-------------------------------------------------------------------------*/