From bc678baaca90170c45fa52ac6c6cc0601b777d7c Mon Sep 17 00:00:00 2001 From: Holger Vogt Date: Sat, 15 Aug 2020 21:59:58 +0200 Subject: [PATCH] modified from Giles Atkinson's patch: use hardcopy entries to set variables maybe overridden by stropts and intopts list variables --- src/frontend/svg.c | 544 +++++++++++++++++++++++++++++++++++++++++++++ src/frontend/svg.h | 17 ++ 2 files changed, 561 insertions(+) create mode 100644 src/frontend/svg.c create mode 100644 src/frontend/svg.h diff --git a/src/frontend/svg.c b/src/frontend/svg.c new file mode 100644 index 000000000..1aee689bd --- /dev/null +++ b/src/frontend/svg.c @@ -0,0 +1,544 @@ +/********** +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]) + +static char * const intopts[] = { + "svgwidth", "svgheight", "svgfont-size", "svgfont-width", "svguse-color", + "svgstroke-width", "svggrid-width", +}; + +static char * const stropts[] = { + "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 *colors[] = {"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 */ + +void SVG_LinestyleColor(int linestyleid, int colorid); +void SVG_SelectColor(int colorid); +void SVG_Stroke(void); + + +/* 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; + + /* Look for colour overrides: HTML/X11 #xxxxxx style. */ + + for (colorid = 0; colorid < NUMELEMS(colors); ++colorid) { + sprintf(colorN, "svgcolor%d", colorid); + if (cp_getvar(colorN, CP_STRING, colorstring, sizeof(colorstring))) { + colors[colorid] = strdup(colorstring); + } + } + + /* 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))) { + Cfg.strings[SVG_FONT] = strdup(strbuf); + } else { + Cfg.strings[SVG_FONT] = strdup("Helvetica"); + } + if (cp_getvar("hcopyfontfamily", CP_STRING, &strbuf, sizeof(strbuf))) { + Cfg.strings[SVG_FONT_FAMILY] = strdup(strbuf); + } + else { + Cfg.strings[SVG_FONT_FAMILY] = strdup("Helvetica"); + } + + if (!cp_getvar("hcopyfontsize", CP_NUM, &Cfg.ints[SVG_FONT_SIZE], 0)) { + Cfg.ints[SVG_FONT_SIZE] = 10; + Cfg.ints[SVG_FONT_WIDTH] = 6; + } + else { + if ((Cfg.ints[SVG_FONT_SIZE] < 10) || (Cfg.ints[SVG_FONT_SIZE] > 18)) + Cfg.ints[SVG_FONT_SIZE] = 10; + } + + /* override the above when intopts and stropts are set */ + if (cp_getvar("intopts", CP_LIST, &va, 0)) { + i = 0; + while (i < NUM_INTS && va) { + Cfg.ints[i++] = va->va_num; + va = va->va_next; + } + } + if (cp_getvar("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; + } + } + + /* 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(colors); + } + + 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. */ + + 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 + */ + + if (plotfile) { + closepath(DEVDEP_P(currentgraph)); + fprintf(plotfile, "\n"); + fclose(plotfile); + plotfile = NULL; + } + + /* 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(colors)) { + 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; +} diff --git a/src/frontend/svg.h b/src/frontend/svg.h new file mode 100644 index 000000000..5f89c2f39 --- /dev/null +++ b/src/frontend/svg.h @@ -0,0 +1,17 @@ +/* Header file for SVG.c */ + +#ifndef ngspice_SVG_H +#define ngspice_SVG_H + +disp_fn_Init_t SVG_Init; +disp_fn_NewViewport_t SVG_NewViewport; +disp_fn_Close_t SVG_Close; +disp_fn_Clear_t SVG_Clear; +disp_fn_DrawLine_t SVG_DrawLine; +disp_fn_Arc_t SVG_Arc; +disp_fn_Text_t SVG_Text; +disp_fn_SetLinestyle_t SVG_SetLinestyle; +disp_fn_SetColor_t SVG_SetColor; +disp_fn_Update_t SVG_Update; + +#endif