/********** Copyright 1990 Regents of the University of California. All rights reserved. Copyright 2020 Giles Atkinson Original author (postsc.c): 1988 Jeffrey M. Hsu **********/ /* SVG driver. */ #include "ngspice/ngspice.h" #include "ngspice/graph.h" #include "ngspice/ftedbgra.h" #include "ngspice/ftedev.h" #include "ngspice/fteext.h" #include "svg.h" #include "plotting/graphdb.h" #include "variable.h" /* String and int options, if changed, change SVGxxxxx macros below. */ #define SVG_WIDTH 0 #define SVG_HEIGHT 1 #define SVG_FONT_SIZE 2 #define SVG_FONT_WIDTH 3 #define SVG_USE_COLOR 4 /* 0 = no 1 = yes 2 = yes, with dashes */ #define SVG_STROKE_WIDTH 5 #define SVG_GRID_WIDTH 6 #define NUM_INTS 7 #define SVG_BACKGROUND 0 #define SVG_FONT_FAMILY 1 #define SVG_FONT 2 #define NUM_STRINGS 3 static struct { int ints[NUM_INTS]; char *strings[NUM_STRINGS]; } Cfg = {{1024, 768, 16, 0, 1, 2, 0}, {NULL,}}; /* Macros to examine configuration options. */ #define SVGwidth (Cfg.ints[SVG_WIDTH]) #define SVGheight (Cfg.ints[SVG_HEIGHT]) #define SVGfont_size (Cfg.ints[SVG_FONT_SIZE]) #define SVGfont_width (Cfg.ints[SVG_FONT_WIDTH]) #define SVGuse_color (Cfg.ints[SVG_USE_COLOR]) #define SVGstroke_width (Cfg.ints[SVG_STROKE_WIDTH]) #define SVGgrid_width (Cfg.ints[SVG_GRID_WIDTH]) #define SVGbackground (Cfg.strings[SVG_BACKGROUND]) #define SVGfont_family (Cfg.strings[SVG_FONT_FAMILY]) #define SVGfont (Cfg.strings[SVG_FONT]) /* svg_intopts are: "svgwidth", "svgheight", "svgfont-size", "svgfont-width", "svguse-color", "svgstroke-width", "svggrid-width" svg_stropts are: "svgbackground", "svgfont-family", "svgfont" */ typedef struct { int lastx, lasty; int inpath; int linelen; bool isgrid; } SVGdevdep; #define DEVDEP_P(g) ((SVGdevdep *)(g)->devdep) /* Values for DEVDEP_P(g)->inpath. */ #define NOPATH 0 #define PATH 1 #define LINE 2 #define CHECK_PATH \ if (ddp->inpath == NOPATH || ddp->linelen > 240) startpath(ddp) static void closepath(SVGdevdep *ddp), startpath(SVGdevdep *ddp); static void startpath_width(SVGdevdep *ddp, int width); /* Values for 'stroke-dasharray'. */ static char *linestyles[] = { NULL, /* Solid */ "1,2", /* Dotted */ "7,7", /* Long dashes */ "3,3", /* Short dashes */ "7,2,2,2", /* Long/dot dashes */ "3,2,1,2", /* Short/dot dashes */ "8,3,2,3", "14,2", "3,5,1,5" /* Short/dot, longp dashes */ }; static FILE *plotfile; static int screenflag = 0; static int hcopygraphid; const char *svgcolors[] = {"black", "white", "red", "blue", "#FFA500", /*4: orange */ "green", "#FFC0C5", /*6: pink */ "#A52A2A", /*7: brown */ "#F0E68C", /*8: khaki */ "#DDA0DD", /*9: plum */ "#DA70D6", /*10: orchid */ "#EE82EE", /*11: violet */ "#B03060", /*12: maroon */ "#40E0D0", /*13: turqoise */ "#A0522D", /*14: sienna */ "#FF7F50", /*15: coral */ "cyan", "magenta", "#666", /*18: gray for smith grid */ "#949494", /*19: gray for smith grid */ "#888"}; /*20: gray for normal grid */ static char** colors; /* Set scale, color and size of the plot */ int SVG_Init(void) { char colorN[16], colorstring[30], strbuf[512]; unsigned int colorid, i; struct variable *vb, *va; bool intopts_isset = FALSE, stropts_isset = FALSE; unsigned int numberofcolors = NUMELEMS(svgcolors); /* set intopts and stropts as basic data */ if (cp_getvar("svg_intopts", CP_LIST, &va, 0)) { i = 0; while (i < NUM_INTS && va) { Cfg.ints[i++] = va->va_num; va = va->va_next; } intopts_isset = TRUE; } if (cp_getvar("svg_stropts", CP_LIST, &vb, 0)) { i = 0; while (i < NUM_STRINGS && vb) { tfree(Cfg.strings[i]); Cfg.strings[i++] = strdup(vb->va_string); vb = vb->va_next; } stropts_isset = TRUE; } /* plot size */ if (!cp_getvar("hcopywidth", CP_STRING, &Cfg.ints[SVG_WIDTH], sizeof(Cfg.ints[SVG_WIDTH]))) { dispdev->width = Cfg.ints[SVG_WIDTH]; } else { dispdev->width = SVGwidth; } if (!cp_getvar("hcopyheight", CP_STRING, &Cfg.ints[SVG_HEIGHT], sizeof(Cfg.ints[SVG_HEIGHT]))) { dispdev->height = Cfg.ints[SVG_HEIGHT]; } else { dispdev->height = SVGheight; } /* get linewidth information from spinit */ if (!cp_getvar("xbrushwidth", CP_NUM, &Cfg.ints[SVG_STROKE_WIDTH], 0)) Cfg.ints[SVG_STROKE_WIDTH] = 0; if (Cfg.ints[SVG_STROKE_WIDTH] < 0) Cfg.ints[SVG_STROKE_WIDTH] = 0; /* get linewidth for grid from spinit */ if (!cp_getvar("xgridwidth", CP_NUM, &Cfg.ints[SVG_GRID_WIDTH], 0)) Cfg.ints[SVG_GRID_WIDTH] = Cfg.ints[SVG_STROKE_WIDTH]; if (Cfg.ints[SVG_GRID_WIDTH] < 0) Cfg.ints[SVG_GRID_WIDTH] = 0; if (cp_getvar("hcopyfont", CP_STRING, &strbuf, sizeof(strbuf))) { tfree(Cfg.strings[SVG_FONT]); Cfg.strings[SVG_FONT] = strdup(strbuf); } else if (!stropts_isset) { tfree(Cfg.strings[SVG_FONT]); Cfg.strings[SVG_FONT] = strdup("Helvetica"); } if (cp_getvar("hcopyfontfamily", CP_STRING, &strbuf, sizeof(strbuf))) { tfree(Cfg.strings[SVG_FONT_FAMILY]); Cfg.strings[SVG_FONT_FAMILY] = strdup(strbuf); } else if (!stropts_isset){ tfree(Cfg.strings[SVG_FONT_FAMILY]); Cfg.strings[SVG_FONT_FAMILY] = strdup("Helvetica"); } cp_getvar("hcopyfontsize", CP_NUM, &Cfg.ints[SVG_FONT_SIZE], 0); colors = TMALLOC(char*, numberofcolors); /* Look for colour overrides: HTML/X11 #xxxxxx style. */ for (colorid = 0; colorid < numberofcolors; ++colorid) { sprintf(colorN, "color%d", colorid); if (cp_getvar(colorN, CP_STRING, colorstring, sizeof(colorstring))) { colors[colorid] = strdup(colorstring); if (colorid == 0) { tfree(Cfg.strings[SVG_BACKGROUND]); Cfg.strings[SVG_BACKGROUND] = strdup(colors[0]); } } else { colors[colorid] = strdup(svgcolors[colorid]); } } /* Get other options. */ if (SVGgrid_width == 0) SVGgrid_width = (2 * SVGstroke_width) / 3; if (SVGuse_color == 0) { /* Black and white. */ dispdev->numcolors = 2; } else { dispdev->numcolors = NUMELEMS(svgcolors); } if (SVGuse_color == 1) { /* Suppress dashes in favour of color. */ dispdev->numlinestyles = 2; } else { dispdev->numlinestyles = NUMELEMS(linestyles); } dispdev->minx = 0; dispdev->miny = 0; return 0; } /* Plot and fill bounding box */ int SVG_NewViewport(GRAPH *graph) { SVGdevdep *ddp; hcopygraphid = graph->graphid; if (graph->absolute.width) { /* hardcopying from the screen */ screenflag = 1; } graph->absolute.width = dispdev->width; graph->absolute.height = dispdev->height; if (SVGfont_width) graph->fontwidth = SVGfont_width; else graph->fontwidth = (2 * SVGfont_size) / 3; // Ugly! graph->fontheight = SVGfont_size; /* Start file off. * devdep initially contains name of output file. */ if ((plotfile = fopen((char*)graph->devdep, "w")) == NULL) { perror((char*)graph->devdep); graph->devdep = NULL; return 1; } fputs("\n", plotfile); fputs("\n", plotfile); fputs("width, dispdev->height); /* Style paths. */ fputs(" style=\"fill: none;", plotfile); if (SVGstroke_width > 0) { fprintf(plotfile, " stroke-width: %d;", SVGstroke_width); } /* Style text. */ if (SVGfont_family) { fprintf(plotfile, " font-family: %s;\n", SVGfont_family); } if (SVGfont) { fprintf(plotfile, " font: %s;\n", SVGfont_family); } fputs("\">\n\n\n\n", plotfile); /* Fill background. */ fprintf(plotfile, "\n", graph->absolute.width, graph->absolute.height, SVGbackground ? SVGbackground : "black"); /* Allocate and initialise per-graph data. */ tfree(graph->devdep); graph->devdep = TMALLOC(SVGdevdep, 1); ddp = DEVDEP_P(graph); ddp->lastx = ddp->lasty = -1; return 0; } int SVG_Close(void) { /* Test for activity in case SVG_Close is called as part of an abort, * without having reached SVG_NewViewport */ size_t i; if (plotfile) { closepath(DEVDEP_P(currentgraph)); fprintf(plotfile, "\n"); fclose(plotfile); plotfile = NULL; } if (colors) { for (i = 0; i < NUMELEMS(svgcolors); i++) tfree(colors[i]); tfree(colors); } for (i = 0; i < NUM_STRINGS; i++) tfree(Cfg.strings[i]); /* In case of hardcopy command destroy the hardcopy graph * and reset currentgraph to graphid 1, if possible */ if (!screenflag) { DestroyGraph(hcopygraphid); currentgraph = FindGraph(1); } return 0; } int SVG_Clear(void) { /* do nothing */ return 0; } int SVG_DrawLine(int x1, int y1, int x2, int y2, bool isgrid) { SVGdevdep *ddp; if (x1 == x2 && y1 == y2) return 0; ddp = DEVDEP_P(currentgraph); if (isgrid != ddp->isgrid) { closepath(ddp); ddp->isgrid = isgrid; } if (isgrid && ddp->inpath == NOPATH) startpath_width(ddp, SVGgrid_width); CHECK_PATH; if (x1 == ddp->lastx && y1 == ddp->lasty) { putc((ddp->inpath == LINE) ? ' ' : 'l', plotfile); ++ddp->linelen; } else { ddp->linelen += fprintf(plotfile, "M%d %dl", x1, dispdev->height - y1); } ddp->linelen += fprintf(plotfile, "%d %d", x2 - x1, y1 - y2); ddp->lastx = x2; ddp->lasty = y2; ddp->inpath = LINE; return 0; } int SVG_Arc(int x0, int y0, int r, double theta, double delta_theta) { double x1, y1, x2, y2, left; SVGdevdep *ddp; /* SVG will not draw full circles, so do them in pieces. */ if (delta_theta < 0.0) { theta += delta_theta; delta_theta = -delta_theta; } if (delta_theta > M_PI) { left = delta_theta - M_PI; if (left > M_PI) left = M_PI; delta_theta = M_PI; } else { left = 0.0; } ddp = DEVDEP_P(currentgraph); CHECK_PATH; x1 = (double) x0 + r * cos(theta); y1 = (double) y0 + r * sin(theta); x2 = (double) x0 + r * cos(theta + delta_theta); y2 = (double) y0 + r * sin(theta + delta_theta); ddp->linelen += fprintf(plotfile, "M%f %fA%d %d 0 0 0 %f %f", x1, dispdev->height - y1, r, r, x2, dispdev->height - y2); if (left != 0.0) { x2 = (double) x0 + r * cos(theta + M_PI + left); y2 = (double) y0 + r * sin(theta + M_PI + left); ddp->linelen += fprintf(plotfile, " %d %d 0 0 0 %f %f", r, r, x2, dispdev->height - y2); } ddp->lastx = -1; ddp->lasty = -1; ddp->inpath = PATH; return 0; } int SVG_Text(const char *text, int x, int y, int angle) { SVGdevdep *ddp; ddp = DEVDEP_P(currentgraph); if (ddp->inpath != NOPATH) closepath(ddp); y = dispdev->height - y; fputs("\n%s\n\n", colors[currentgraph->currentcolor], SVGfont_size, x, y, text); return 0; } /* SVG_DefineColor() and SVG_DefineLinestyle() are never used. */ int SVG_SetLinestyle(int linestyleid) { /* special case: get it when SVG_Text restores a -1 linestyle */ if (linestyleid == -1) { currentgraph->linestyle = -1; return 0; } if (SVGuse_color == 1 && linestyleid > 1) { /* Caller ignores dispdev->numlinestyles. Keep quiet, * but allow dotted grid. */ currentgraph->linestyle = 0; return 0; } if (linestyleid < 0 || linestyleid > dispdev->numlinestyles) { internalerror("bad linestyleid inside SVG_SetLinestyle"); fprintf(cp_err, "linestyleid is: %d\n", linestyleid); return 1; } if (linestyleid != currentgraph->linestyle) { closepath(DEVDEP_P(currentgraph)); currentgraph->linestyle = linestyleid; } return 0; } int SVG_SetColor(int colorid) { if (colorid < 0 || colorid > (int)NUMELEMS(svgcolors)) { internalerror("bad colorid inside SVG_SelectColor"); return 1; } if (colorid != currentgraph->currentcolor) { closepath(DEVDEP_P(currentgraph)); currentgraph->currentcolor = colorid; } return 0; } int SVG_Update(void) { fflush(plotfile); return 0; } /**************** PRIVATE FUNCTIONS OF SVG FRONTEND ***************************/ /* Start a new path. */ static void startpath_width(SVGdevdep *ddp, int width) { if (ddp->inpath != NOPATH) closepath(ddp); ddp->linelen = 3 + fprintf(plotfile, "currentcolor]); if (width) { ddp->linelen += fprintf(plotfile, "stroke-width=\"%d\" ", width); } /* Kludgy, but allow dash style 1 (the grid) when suppressing dashes. */ if (SVGuse_color != 1 || currentgraph->linestyle == 1) { ddp->linelen += fprintf(plotfile, "stroke-dasharray=\"%s\" ", linestyles[currentgraph->linestyle]); } fputs("d=\"", plotfile); ddp->inpath = PATH; } static void startpath(SVGdevdep *ddp) { startpath_width(ddp, 0); } static void closepath(SVGdevdep *ddp) { if (ddp->inpath != NOPATH) { fputs("\"/>\n", plotfile); ddp->inpath = NOPATH; } ddp->lastx = -1; ddp->lasty = -1; }