/* * Postscript output for xgraph * * Rick Spickelmier * David Harrison */ #include "copyright.h" #include #include "xgraph.h" /* * Basic scaling parameters */ #define VDPI 1200.0 #define LDIM 11.0 #define SDIM 8.5 #define MICRONS_PER_INCH 2.54E+04 #define POINTS_PER_INCH 72.0 #define INCHES_PER_POINT 1.0/72.0 /* * Aesthetic parameters (inches) */ #define PS_BDR_PAD 0.075 #define PS_AXIS_PAD 0.1 #define PS_LEG_PAD 0.025 #define PS_TICK_LEN 0.125 #define BASE_DASH (1.0/48.0) #define BASE_WIDTH (1.0/8.0) #define PS_AXIS_WBASE 1 #define PS_ZERO_WBASE 4 #define PS_DATA_WBASE 7 #define PS_PIXEL 4 #define PS_DOT 12 #define PS_MARK 12 /* * Other constants */ #define FONT_WIDTH_EST 0.55 #define PS_MAX_SEGS 1000 #define PS_NO_TSTYLE -1 #define PS_NO_DSTYLE -1 #define PS_NO_WIDTH -1 #define PS_NO_LSTYLE -1 #define PS_NO_COLOR -1 /* * Working macros */ #define OUT (void) fprintf #define PS(str) OUT(psFile, str) #define PSU(str) OUT(ui->psFile, str) #define IY(val) (ui->height_devs - val) #define TEXTCOLOR 0 #define MAXCOLOR 8 /* Number of gray scales supported */ /* * Globals */ static double PS_scale; /* devs/micron */ /* * Externals and forwards */ static void psScale(), psFonts(), psMarks(), psText(), psSeg(), psDot(), psEnd(); /* * Local structures */ struct userInfo { FILE *psFile; int currentTextStyle; int currentDashStyle; int currentWidth; int currentLStyle; int currentColor; int baseWidth; int height_devs; char *title_family; double title_size; char *axis_family; double axis_size; int flags; }; int rd(dbl) double dbl; /* Short and sweet rounding function */ { if (dbl < 0.0) { return ((int) (dbl - 0.5)); } else { return ((int) (dbl + 0.5)); } } /*ARGSUSED*/ int psInit(psFile, width, height, tf, ts, af, as, flags, outInfo, errmsg) FILE *psFile; /* Output file */ int width, height; /* In microns */ char *tf, *af; /* Title and axis font */ double ts, as; /* Title and axis size */ int flags; /* Predicate flags */ xgOut *outInfo; /* Returned device info */ char errmsg[ERRBUFSIZE]; /* Returned error message */ /* * The basic coordinate system is points (roughly 1/72 inch). * However, most laser printers can do much better than that. * We invent a coordinate system based on VDPI dots per inch. * This goes along the long side of the page. The long side * of the page is LDIM inches in length, the short side * SDIM inches in length. We we call this unit a `dev'. * We map `width' and `height' into devs. */ { struct userInfo *ui; double font_size; ui = (struct userInfo *) Malloc(sizeof(struct userInfo)); ui->psFile = psFile; ui->currentTextStyle = PS_NO_TSTYLE; ui->currentDashStyle = PS_NO_DSTYLE; ui->currentWidth = PS_NO_WIDTH; ui->currentLStyle = PS_NO_LSTYLE; ui->currentColor = PS_NO_COLOR; ui->title_family = tf; ui->title_size = ts; ui->axis_family = af; ui->axis_size = as; /* Roughly, one-eighth a point in devs */ ui->baseWidth = rd(VDPI / POINTS_PER_INCH * BASE_WIDTH); ui->flags = flags; PS_scale = VDPI / MICRONS_PER_INCH; outInfo->dev_flags = 0; outInfo->area_w = rd(((double) width) * PS_scale); outInfo->area_h = rd(((double) height) * PS_scale); ui->height_devs = outInfo->area_h; outInfo->bdr_pad = rd(PS_BDR_PAD * VDPI); outInfo->axis_pad = rd(PS_AXIS_PAD * VDPI); outInfo->legend_pad = rd(PS_LEG_PAD * VDPI); outInfo->tick_len = rd(PS_TICK_LEN * VDPI); /* Font estimates */ font_size = as * INCHES_PER_POINT * VDPI; outInfo->axis_height = rd(font_size); outInfo->axis_width = rd(font_size * FONT_WIDTH_EST); font_size = ts * INCHES_PER_POINT * VDPI; outInfo->title_height = rd(font_size); outInfo->title_width = rd(font_size * FONT_WIDTH_EST); outInfo->max_segs = PS_MAX_SEGS; outInfo->xg_text = psText; outInfo->xg_seg = psSeg; outInfo->xg_dot = psDot; outInfo->xg_end = psEnd; outInfo->user_state = (char *) ui; /* Postscript file identification */ PS("%%!\n"); /* Definitions */ psScale(psFile, width, height, flags); psFonts(psFile); psMarks(psFile); PS("%%\n%% Main body begins here\n%%\n"); return 1; } static void psHeader(psFile, docu_flag) FILE *psFile; int docu_flag; /* * Prints out a standard greeting to the Postscript file. */ { PS("%%%%EndComments\n"); PS("%%\n"); PS("%% Xgraph postscript output\n"); PS("%% Rick Spickelmier and David Harrison\n"); PS("%% University of California, Berkeley\n"); if (docu_flag) { PS("%%\n"); PS("%% Output produced for inclusion in another document.\n"); PS("%% This file will not work properly if sent directly to a printer.\n"); } PS("%%\n"); } static void psScale(psFile, width, height, flags) FILE *psFile; /* Output stream */ int width; /* Output width */ int height; /* Output height */ int flags; /* Output options */ /* * This routine figures out how transform the basic postscript * transformation into one suitable for direct use by * the drawing primitives. Two variables X-CENTER-PLOT * and Y-CENTER-PLOT determine whether the plot is centered * on the page. If `flags' has D_DOCU set, then the plot * will not be rotated or centered and a bounding box will * be displayed. */ { double factor; double pnt_width, pnt_height; if (flags & D_DOCU) { OUT(psFile, "%%%%BoundingBox: %ld %ld %ld %ld\n", 0, 0, (int) (((double) width) / (MICRONS_PER_INCH * INCHES_PER_POINT) + 0.5), (int) (((double) height) / (MICRONS_PER_INCH * INCHES_PER_POINT) + 0.5) ); psHeader(psFile, 1); PS("%% Rotation and centering are turned off for inclusion in a document\n"); } else { psHeader(psFile, 0); PS("%% Scaling information\n"); PS("%%\n"); PS("%% Change these if you would like to change the centering\n"); PS("%% of the plot in either dimension\n"); PS("/X-CENTER-PLOT 1 def\n"); PS("/Y-CENTER-PLOT 1 def\n"); PS("%%\n"); /* * Determine page size */ PS("%% Page size computation\n"); PS("clippath pathbbox\n"); PS("/page-height exch def\n"); PS("/page-width exch def\n"); PS("pop pop\n"); /* * First: rotation. If the width is greater than the short dimension, * do the rotation. */ pnt_width = ((double) width) / MICRONS_PER_INCH * POINTS_PER_INCH; pnt_height = ((double) height) / MICRONS_PER_INCH * POINTS_PER_INCH; PS("%% Determine whether rotation is required\n"); OUT(psFile, "%lg page-width gt\n", pnt_width); PS("{ %% Rotation required\n"); PS(" 90 rotate\n"); PS(" 0 page-width neg translate\n"); PS(" %% Handle centering\n"); PS(" Y-CENTER-PLOT 1 eq { %% Center in y\n"); OUT(psFile, " page-height %lg sub 2 div\n", pnt_width); PS(" } { %% Don't center in y\n"); PS(" 0\n"); PS(" } ifelse\n"); PS(" X-CENTER-PLOT 1 eq { %% Center in x\n"); OUT(psFile, " page-width %lg sub 2 div\n", pnt_height); PS(" } { %% Don't center in x\n"); PS(" 0\n"); PS(" } ifelse\n"); PS(" translate\n"); PS("} { %% No rotation - just handle centering\n"); PS(" X-CENTER-PLOT 1 eq { %% Center in x\n"); OUT(psFile, " page-width %lg sub 2 div\n", pnt_width); PS(" } { %% Don't center in x\n"); PS(" 0\n"); PS(" } ifelse\n"); PS(" Y-CENTER-PLOT 1 eq { %% Center in y\n"); OUT(psFile, " page-height %lg sub 2 div\n", pnt_height); PS(" } { %% Don't center in y\n"); PS(" 0\n"); PS(" } ifelse\n"); PS(" translate\n"); PS("} ifelse\n"); } /* * Now: scaling. We have points. We want devs. */ factor = POINTS_PER_INCH / VDPI; PS("%% Set the scale\n"); OUT(psFile, "%lg %lg scale\n", factor, factor); } static void psFonts(psFile) FILE *psFile; /* Output stream */ /* * Downloads code for drawing title and axis labels */ { PS("%% Font Handling Functions\n"); PS("%%\n"); PS("%% Function giving y-offset to center of font\n"); PS("%% Assumes font is set and uses numbers to gauge center\n"); PS("%%\n"); PS("/choose-font %% stack: fontsize fontname => ---\n"); PS("{\n"); PS(" findfont \n"); PS(" exch scalefont \n"); PS(" setfont\n"); PS(" newpath\n"); PS(" 0 0 moveto (0) true charpath flattenpath pathbbox\n"); PS(" /top exch def pop\n"); PS(" /bottom exch def pop\n"); PS(" bottom top bottom top add 2 div\n"); PS(" /center-font-val exch def \n"); PS(" /upper-font-val exch def \n"); PS(" /lower-font-val exch def\n"); PS("} def\n"); PS("%%\n"); PS("%% Justfication offset routines\n"); PS("%%\n"); PS("/center-x-just %% stack: (string) x y => (string) newx y\n"); PS("{\n"); PS(" exch 2 index stringwidth pop 2 div sub exch\n"); PS("} def\n"); PS("%%\n"); PS("/left-x-just %% stack: (string) x y => (string) newx y\n"); PS("{ \n"); PS("} def\n"); PS("%%\n"); PS("/right-x-just %% stack: (string) x y => (string) newx y\n"); PS("{\n"); PS(" exch 2 index stringwidth pop sub exch\n"); PS("} def\n"); PS("%%\n"); PS("/center-y-just %% stack: (string) x y => (string) x newy\n"); PS("{\n"); PS(" center-font-val sub\n"); PS("} def\n"); PS("%%\n"); PS("/lower-y-just %% stack: (string) x y => (string) x newy\n"); PS("{\n"); PS(" lower-font-val sub\n"); PS("} def\n"); PS("%%\n"); PS("/upper-y-just %% stack: (string) x y => (string) x newy\n"); PS("{\n"); PS(" upper-font-val sub\n"); PS("} def\n"); PS("%%\n"); PS("%% Shows a string on the page subject to justification\n"); PS("%% \n"); PS("/just-string %% stack: (string) x y just => ---\n"); PS("{\n"); PS(" dup 0 eq { pop center-x-just center-y-just } if\n"); PS(" dup 1 eq { pop left-x-just center-y-just } if\n"); PS(" dup 2 eq { pop left-x-just upper-y-just } if\n"); PS(" dup 3 eq { pop center-x-just upper-y-just } if\n"); PS(" dup 4 eq { pop right-x-just upper-y-just } if\n"); PS(" dup 5 eq { pop right-x-just center-y-just } if\n"); PS(" dup 6 eq { pop right-x-just lower-y-just } if\n"); PS(" dup 7 eq { pop center-x-just lower-y-just } if\n"); PS(" dup 8 eq { pop left-x-just lower-y-just } if\n"); PS(" moveto show\n"); PS("} def\n"); PS("%%\n"); } static void psMarks(psFile) FILE *psFile; /* * Writes out marker definitions */ { PS("%% Marker definitions\n"); PS("/mark0 {/size exch def /y exch def /x exch def\n"); PS("newpath x size sub y size sub moveto\n"); PS("size size add 0 rlineto 0 size size add rlineto\n"); PS("0 size size add sub 0 rlineto closepath fill} def\n"); PS("/mark1 {/size exch def /y exch def /x exch def\n"); PS("newpath x size sub y size sub moveto\n"); PS("size size add 0 rlineto 0 size size add rlineto\n"); PS("0 size size add sub 0 rlineto closepath stroke} def\n"); PS("/mark2 {/size exch def /y exch def /x exch def\n"); PS("newpath x y moveto x y size 0 360 arc stroke} def\n"); PS("/mark3 {/size exch def /y exch def /x exch def\n"); PS("newpath x size sub y size sub moveto x size add y size add lineto\n"); PS("x size sub y size add moveto x size add y size sub lineto stroke} def\n"); PS("/mark4 {/size exch def /y exch def /x exch def\n"); PS("newpath x size sub y moveto x y size add lineto\n"); PS("x size add y lineto x y size sub lineto\n"); PS("closepath stroke} def\n"); PS("/mark5 {/size exch def /y exch def /x exch def\n"); PS("x y size mark1\n"); PS("newpath x size sub y moveto size size add 0 rlineto stroke} def\n"); PS("/mark6 {/size exch def /y exch def /x exch def\n"); PS("newpath x y moveto x y size 0 360 arc fill} def\n"); PS("/mark7 {/size exch def /y exch def /x exch def\n"); PS("newpath x y moveto x size sub y size sub lineto\n"); PS("x size add y size sub lineto closepath fill\n"); PS("newpath x y moveto x size add y size add lineto\n"); PS("x size sub y size add lineto closepath fill} def\n"); } static void psText(state, x, y, text, just, style) char *state; /* Really (struct userInfo *) */ int x, y; /* Text position (devs) */ char *text; /* Text itself */ int just; /* Justification */ int style; /* Style */ /* * Draws text at the given location with the given justification * and style. */ { struct userInfo *ui = (struct userInfo *) state; if (TEXTCOLOR != ui->currentColor) { OUT(ui->psFile, "%lg setgray\n", (double) TEXTCOLOR / 8); ui->currentColor = TEXTCOLOR; } if (style != ui->currentTextStyle) { switch (style) { case T_AXIS: OUT(ui->psFile, "%lg /%s choose-font\n", ui->axis_size * INCHES_PER_POINT * VDPI, ui->axis_family); break; case T_TITLE: OUT(ui->psFile, "%lg /%s choose-font\n", ui->title_size * INCHES_PER_POINT * VDPI, ui->title_family); break; } ui->currentTextStyle = style; } OUT(ui->psFile, "(%s) %d %d %d just-string\n", text, x, IY(y), just); } /*ARGSUSED*/ static void psSeg(state, ns, seglist, width, style, lappr, color) char *state; /* Really (struct userInfo *) */ int ns; /* Number of segments */ XSegment *seglist; /* X array of segments */ int width; /* Width of lines (devcoords) */ int style; /* L_AXIS, L_ZERO, L_VAR */ int lappr; /* Zero to seven */ int color; /* Zero to seven */ /* * Draws a number of line segments. Grid lines are drawn using * light lines. Variable lines (L_VAR) are drawn wider. This * version ignores the color argument. */ { struct userInfo *ui = (struct userInfo *) state; int newwidth = 0, i; if ((style != ui->currentLStyle) || (width != ui->currentWidth)) { switch (style) { case L_AXIS: newwidth = PS_AXIS_WBASE * ui->baseWidth; PSU("[] 0 setdash\n"); break; case L_ZERO: newwidth = PS_ZERO_WBASE * ui->baseWidth; PSU("[] 0 setdash\n"); break; case L_VAR: newwidth = PS_DATA_WBASE * ui->baseWidth; break; } ui->currentWidth = MAX(newwidth, width); ui->currentLStyle = style; OUT(ui->psFile, "%d setlinewidth\n", ui->currentWidth); } if (width > 4) { if (color > MAXCOLOR) color -= MAXCOLOR; else lappr = 0; } else color = TEXTCOLOR; if ((lappr != ui->currentDashStyle) && (style == L_VAR)) { if (lappr == 0) { PSU("[] 0 setdash\n"); } else { OUT(ui->psFile, "[%lg] 0 setdash\n", ((double) lappr) * BASE_DASH * VDPI); } ui->currentDashStyle = lappr; } if ((color != ui->currentColor) && (style == L_VAR)) { OUT(ui->psFile, "%lg setgray\n", (double) color / MAXCOLOR); ui->currentColor = color; } PSU("newpath\n"); OUT(ui->psFile, " %d %d moveto\n", seglist[0].x1, IY(seglist[0].y1)); OUT(ui->psFile, " %d %d lineto\n", seglist[0].x2, IY(seglist[0].y2)); for (i = 1; i < ns; i++) { if ((seglist[i].x1 != seglist[i - 1].x2) || (seglist[i].y1 != seglist[i - 1].y2)) { OUT(ui->psFile, " %d %d moveto\n", seglist[i].x1, IY(seglist[i].y1)); } OUT(ui->psFile, " %d %d lineto\n", seglist[i].x2, IY(seglist[i].y2)); } PSU("stroke\n"); } /*ARGSUSED*/ static void psDot(state, x, y, style, type, color) char *state; /* state information */ int x, y; /* coord of dot */ int style; /* type of dot */ int type; /* dot style variation */ int color; /* color of dot */ /* * Prints out a dot at the given location */ { struct userInfo *ui = (struct userInfo *) state; if (ui->currentDashStyle != PS_NO_DSTYLE) { OUT(ui->psFile, "[] 0 setdash "); ui->currentDashStyle = PS_NO_DSTYLE; } if (ui->currentWidth != PS_ZERO_WBASE * ui->baseWidth) { ui->currentWidth = PS_ZERO_WBASE * ui->baseWidth; OUT(ui->psFile, "%d setlinewidth ", ui->currentWidth); } if (color > MAXCOLOR) color -= MAXCOLOR; if ((color != ui->currentColor)) { OUT(ui->psFile, "%lg setgray\n", (double) color / MAXCOLOR); ui->currentColor = color; } switch (style) { case P_PIXEL: OUT(ui->psFile, "newpath %d %d moveto %d %d %d 0 360 arc fill\n", x, IY(y), x, IY(y), PS_PIXEL * ui->baseWidth); break; case P_DOT: OUT(ui->psFile, "newpath %d %d moveto %d %d %d 0 360 arc fill\n", x, IY(y), x, IY(y), PS_DOT * ui->baseWidth); break; case P_MARK: OUT(ui->psFile, "%d %d %d mark%d\n", x, IY(y), PS_MARK * ui->baseWidth, type); break; } return; } static void psEnd(userState) char *userState; /* state information */ { struct userInfo *ui = (struct userInfo *) userState; if (!(ui->flags & D_DOCU)) { PSU("showpage\n"); } PSU("%% End of xgraph output\n"); }