Browse Source
modified from Giles Atkinson's patch:
modified from Giles Atkinson's patch:
use hardcopy entries to set variables maybe overridden by stropts and intopts list variablespre-master-46
2 changed files with 561 additions and 0 deletions
@ -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("<?xml version=\"1.0\" standalone=\"yes\"?>\n", plotfile); |
|||
fputs("<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n" |
|||
" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n", |
|||
plotfile); |
|||
fputs("<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\"\n", |
|||
plotfile); |
|||
fprintf(plotfile, |
|||
" width=\"100%%\" height=\"100%%\" viewBox=\"0 0 %d %d\"\n", |
|||
dispdev->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<!-- Creator: NGspice -->\n\n", plotfile); |
|||
|
|||
/* Fill background. */ |
|||
|
|||
fprintf(plotfile, |
|||
"<rect x=\"0\" y=\"0\" width=\"%d\" height=\"%d\" " |
|||
"fill=\"%s\" stroke=\"none\"/>\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, "</svg>\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("<text", plotfile); |
|||
if (angle != 0) { |
|||
fprintf(plotfile, " transform=\"rotate(%d, %d, %d)\" ", |
|||
-angle, x, y); |
|||
} |
|||
fprintf(plotfile, |
|||
" stroke=\"none\" fill=\"%s\" font-size=\"%d\" " |
|||
"x=\"%d\" y=\"%d\">\n%s\n</text>\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, "<path stroke=\"%s\" ", |
|||
colors[currentgraph->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; |
|||
} |
|||
@ -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 |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue