1219b2ee8SDavid du Colombier #include <u.h>
2219b2ee8SDavid du Colombier #include <libc.h>
3219b2ee8SDavid du Colombier #include <bio.h>
480ee5cbfSDavid du Colombier #include <ctype.h>
5219b2ee8SDavid du Colombier #include "../common/common.h"
6219b2ee8SDavid du Colombier #include "tr2post.h"
7219b2ee8SDavid du Colombier
8219b2ee8SDavid du Colombier BOOLEAN drawflag = FALSE;
980ee5cbfSDavid du Colombier BOOLEAN inpath = FALSE; /* TRUE if we're putting pieces together */
10219b2ee8SDavid du Colombier
11219b2ee8SDavid du Colombier void
cover(double x,double y)12219b2ee8SDavid du Colombier cover(double x, double y) {
13*456a8764SDavid du Colombier USED(x, y);
14219b2ee8SDavid du Colombier }
15219b2ee8SDavid du Colombier
16219b2ee8SDavid du Colombier void
drawspline(Biobufhdr * Bp,int flag)17219b2ee8SDavid du Colombier drawspline(Biobufhdr *Bp, int flag) { /* flag!=1 connect end points */
18219b2ee8SDavid du Colombier int x[100], y[100];
19219b2ee8SDavid du Colombier int i, N;
20219b2ee8SDavid du Colombier /*
21219b2ee8SDavid du Colombier * Spline drawing routine for Postscript printers. The complicated stuff is
22219b2ee8SDavid du Colombier * handled by procedure Ds, which should be defined in the library file. I've
23219b2ee8SDavid du Colombier * seen wrong implementations of troff's spline drawing, so fo the record I'll
24219b2ee8SDavid du Colombier * write down the parametric equations and the necessary conversions to Bezier
25219b2ee8SDavid du Colombier * cubic splines (as used in Postscript).
26219b2ee8SDavid du Colombier *
27219b2ee8SDavid du Colombier * Parametric equation (x coordinate only):
28219b2ee8SDavid du Colombier *
29219b2ee8SDavid du Colombier * (x2 - 2 * x1 + x0) 2 (x0 + x1)
30219b2ee8SDavid du Colombier * x = ------------------ * t + (x1 - x0) * t + ---------
31219b2ee8SDavid du Colombier * 2 2
32219b2ee8SDavid du Colombier *
33219b2ee8SDavid du Colombier * The coefficients in the Bezier cubic are,
34219b2ee8SDavid du Colombier *
35219b2ee8SDavid du Colombier * A = 0
36219b2ee8SDavid du Colombier * B = (x2 - 2 * x1 + x0) / 2
37219b2ee8SDavid du Colombier * C = x1 - x0
38219b2ee8SDavid du Colombier *
39219b2ee8SDavid du Colombier * while the current point is,
40219b2ee8SDavid du Colombier *
41219b2ee8SDavid du Colombier * current-point = (x0 + x1) / 2
42219b2ee8SDavid du Colombier *
43219b2ee8SDavid du Colombier * Using the relationships given in the Postscript manual (page 121) it's easy to
44219b2ee8SDavid du Colombier * see that the control points are given by,
45219b2ee8SDavid du Colombier *
46219b2ee8SDavid du Colombier * x0' = (x0 + 5 * x1) / 6
47219b2ee8SDavid du Colombier * x1' = (x2 + 5 * x1) / 6
48219b2ee8SDavid du Colombier * x2' = (x1 + x2) / 2
49219b2ee8SDavid du Colombier *
50219b2ee8SDavid du Colombier * where the primed variables are the ones used by curveto. The calculations
51219b2ee8SDavid du Colombier * shown above are done in procedure Ds using the coordinates set up in both
52219b2ee8SDavid du Colombier * the x[] and y[] arrays.
53219b2ee8SDavid du Colombier *
54219b2ee8SDavid du Colombier * A simple test of whether your spline drawing is correct would be to use cip
55219b2ee8SDavid du Colombier * to draw a spline and some tangent lines at appropriate points and then print
56219b2ee8SDavid du Colombier * the file.
57219b2ee8SDavid du Colombier */
58219b2ee8SDavid du Colombier for (N=2; N<sizeof(x)/sizeof(x[0]); N++)
59219b2ee8SDavid du Colombier if (Bgetfield(Bp, 'd', &x[N], 0)<=0 || Bgetfield(Bp, 'd', &y[N], 0)<=0)
60219b2ee8SDavid du Colombier break;
61219b2ee8SDavid du Colombier
62219b2ee8SDavid du Colombier x[0] = x[1] = hpos;
63219b2ee8SDavid du Colombier y[0] = y[1] = vpos;
64219b2ee8SDavid du Colombier
65219b2ee8SDavid du Colombier for (i = 1; i < N; i++) {
66219b2ee8SDavid du Colombier x[i+1] += x[i];
67219b2ee8SDavid du Colombier y[i+1] += y[i];
68219b2ee8SDavid du Colombier }
69219b2ee8SDavid du Colombier
70219b2ee8SDavid du Colombier x[N] = x[N-1];
71219b2ee8SDavid du Colombier y[N] = y[N-1];
72219b2ee8SDavid du Colombier
73219b2ee8SDavid du Colombier for (i = ((flag!=1)?0:1); i < ((flag!=1)?N-1:N-2); i++) {
74219b2ee8SDavid du Colombier endstring();
75219b2ee8SDavid du Colombier if (pageon())
76219b2ee8SDavid du Colombier Bprint(Bstdout, "%d %d %d %d %d %d Ds\n", x[i], y[i], x[i+1], y[i+1], x[i+2], y[i+2]);
77219b2ee8SDavid du Colombier /* if (dobbox == TRUE) { /* could be better */
78219b2ee8SDavid du Colombier /* cover((double)(x[i] + x[i+1])/2,(double)-(y[i] + y[i+1])/2);
79219b2ee8SDavid du Colombier /* cover((double)x[i+1], (double)-y[i+1]);
80219b2ee8SDavid du Colombier /* cover((double)(x[i+1] + x[i+2])/2, (double)-(y[i+1] + y[i+2])/2);
81219b2ee8SDavid du Colombier /* }
82219b2ee8SDavid du Colombier */
83219b2ee8SDavid du Colombier }
84219b2ee8SDavid du Colombier
85219b2ee8SDavid du Colombier hpos = x[N]; /* where troff expects to be */
86219b2ee8SDavid du Colombier vpos = y[N];
87219b2ee8SDavid du Colombier }
88219b2ee8SDavid du Colombier
89219b2ee8SDavid du Colombier void
draw(Biobufhdr * Bp)90219b2ee8SDavid du Colombier draw(Biobufhdr *Bp) {
91219b2ee8SDavid du Colombier
92219b2ee8SDavid du Colombier int r, x1, y1, x2, y2, i;
93219b2ee8SDavid du Colombier int d1, d2;
94219b2ee8SDavid du Colombier
95219b2ee8SDavid du Colombier drawflag = TRUE;
96219b2ee8SDavid du Colombier r = Bgetrune(Bp);
97219b2ee8SDavid du Colombier switch(r) {
98219b2ee8SDavid du Colombier case 'l':
99219b2ee8SDavid du Colombier if (Bgetfield(Bp, 'd', &x1, 0)<=0 || Bgetfield(Bp, 'd', &y1, 0)<=0 || Bgetfield(Bp, 'r', &i, 0)<=0)
100219b2ee8SDavid du Colombier error(FATAL, "draw line function, destination coordinates not found.\n");
101219b2ee8SDavid du Colombier
102219b2ee8SDavid du Colombier endstring();
103219b2ee8SDavid du Colombier if (pageon())
104219b2ee8SDavid du Colombier Bprint(Bstdout, "%d %d %d %d Dl\n", hpos, vpos, hpos+x1, vpos+y1);
105219b2ee8SDavid du Colombier hpos += x1;
106219b2ee8SDavid du Colombier vpos += y1;
107219b2ee8SDavid du Colombier break;
108219b2ee8SDavid du Colombier case 'c':
109219b2ee8SDavid du Colombier if (Bgetfield(Bp, 'd', &d1, 0)<=0)
110219b2ee8SDavid du Colombier error(FATAL, "draw circle function, diameter coordinates not found.\n");
111219b2ee8SDavid du Colombier
112219b2ee8SDavid du Colombier endstring();
113219b2ee8SDavid du Colombier if (pageon())
114219b2ee8SDavid du Colombier Bprint(Bstdout, "%d %d %d %d De\n", hpos, vpos, d1, d1);
115219b2ee8SDavid du Colombier hpos += d1;
116219b2ee8SDavid du Colombier break;
117219b2ee8SDavid du Colombier case 'e':
118219b2ee8SDavid du Colombier if (Bgetfield(Bp, 'd', &d1, 0)<=0 || Bgetfield(Bp, 'd', &d2, 0)<=0)
119219b2ee8SDavid du Colombier error(FATAL, "draw ellipse function, diameter coordinates not found.\n");
120219b2ee8SDavid du Colombier
121219b2ee8SDavid du Colombier endstring();
122219b2ee8SDavid du Colombier if (pageon())
123219b2ee8SDavid du Colombier Bprint(Bstdout, "%d %d %d %d De\n", hpos, vpos, d1, d2);
124219b2ee8SDavid du Colombier hpos += d1;
125219b2ee8SDavid du Colombier break;
126219b2ee8SDavid du Colombier case 'a':
127219b2ee8SDavid du Colombier if (Bgetfield(Bp, 'd', &x1, 0)<=0 || Bgetfield(Bp, 'd', &y1, 0)<=0 || Bgetfield(Bp, 'd', &x2, 0)<=0 || Bgetfield(Bp, 'd', &y2, 0)<=0)
128219b2ee8SDavid du Colombier error(FATAL, "draw arc function, coordinates not found.\n");
129219b2ee8SDavid du Colombier
130219b2ee8SDavid du Colombier endstring();
131219b2ee8SDavid du Colombier if (pageon())
132219b2ee8SDavid du Colombier Bprint(Bstdout, "%d %d %d %d %d %d Da\n", hpos, vpos, x1, y1, x2, y2);
133219b2ee8SDavid du Colombier hpos += x1 + x2;
134219b2ee8SDavid du Colombier vpos += y1 + y2;
135219b2ee8SDavid du Colombier break;
136219b2ee8SDavid du Colombier case 'q':
137219b2ee8SDavid du Colombier drawspline(Bp, 1);
138219b2ee8SDavid du Colombier break;
139219b2ee8SDavid du Colombier case '~':
140219b2ee8SDavid du Colombier drawspline(Bp, 2);
141219b2ee8SDavid du Colombier break;
142219b2ee8SDavid du Colombier default:
143219b2ee8SDavid du Colombier error(FATAL, "unknown draw function <%c>\n", r);
144219b2ee8SDavid du Colombier break;
145219b2ee8SDavid du Colombier }
146219b2ee8SDavid du Colombier }
1477dd7cddfSDavid du Colombier
1487dd7cddfSDavid du Colombier void
beginpath(char * buf,int copy)14980ee5cbfSDavid du Colombier beginpath(char *buf, int copy) {
1507dd7cddfSDavid du Colombier
1517dd7cddfSDavid du Colombier /*
1527dd7cddfSDavid du Colombier * Called from devcntrl() whenever an "x X BeginPath" command is read. It's used
1537dd7cddfSDavid du Colombier * to mark the start of a sequence of drawing commands that should be grouped
1547dd7cddfSDavid du Colombier * together and treated as a single path. By default the drawing procedures in
1557dd7cddfSDavid du Colombier * *drawfile treat each drawing command as a separate object, and usually start
1567dd7cddfSDavid du Colombier * with a newpath (just as a precaution) and end with a stroke. The newpath and
1577dd7cddfSDavid du Colombier * stroke isolate individual drawing commands and make it impossible to deal with
1587dd7cddfSDavid du Colombier * composite objects. "x X BeginPath" can be used to mark the start of drawing
1597dd7cddfSDavid du Colombier * commands that should be grouped together and treated as a single object, and
1607dd7cddfSDavid du Colombier * part of what's done here ensures that the PostScript drawing commands defined
1617dd7cddfSDavid du Colombier * in *drawfile skip the newpath and stroke, until after the next "x X DrawPath"
1627dd7cddfSDavid du Colombier * command. At that point the path that's been built up can be manipulated in
1637dd7cddfSDavid du Colombier * various ways (eg. filled and/or stroked with a different line width).
1647dd7cddfSDavid du Colombier *
1657dd7cddfSDavid du Colombier * Color selection is one of the options that's available in parsebuf(),
1667dd7cddfSDavid du Colombier * so if we get here we add *colorfile to the output file before doing
1677dd7cddfSDavid du Colombier * anything important.
1687dd7cddfSDavid du Colombier *
1697dd7cddfSDavid du Colombier */
1707dd7cddfSDavid du Colombier if (inpath == FALSE) {
1717dd7cddfSDavid du Colombier endstring();
17280ee5cbfSDavid du Colombier /* getdraw(); */
17380ee5cbfSDavid du Colombier /* getcolor(); */
1747dd7cddfSDavid du Colombier Bprint(Bstdout, "gsave\n");
1757dd7cddfSDavid du Colombier Bprint(Bstdout, "newpath\n");
1767dd7cddfSDavid du Colombier Bprint(Bstdout, "%d %d m\n", hpos, vpos);
1777dd7cddfSDavid du Colombier Bprint(Bstdout, "/inpath true def\n");
1787dd7cddfSDavid du Colombier if ( copy == TRUE )
17980ee5cbfSDavid du Colombier Bprint(Bstdout, "%s\n", buf);
1807dd7cddfSDavid du Colombier inpath = TRUE;
1817dd7cddfSDavid du Colombier }
1827dd7cddfSDavid du Colombier }
18380ee5cbfSDavid du Colombier
18480ee5cbfSDavid du Colombier static void parsebuf(char*);
18580ee5cbfSDavid du Colombier
18680ee5cbfSDavid du Colombier void
drawpath(char * buf,int copy)18780ee5cbfSDavid du Colombier drawpath(char *buf, int copy) {
18880ee5cbfSDavid du Colombier
18980ee5cbfSDavid du Colombier /*
19080ee5cbfSDavid du Colombier *
19180ee5cbfSDavid du Colombier * Called from devcntrl() whenever an "x X DrawPath" command is read. It marks the
19280ee5cbfSDavid du Colombier * end of the path started by the last "x X BeginPath" command and uses whatever
19380ee5cbfSDavid du Colombier * has been passed along in *buf to manipulate the path (eg. fill and/or stroke
19480ee5cbfSDavid du Colombier * the path). Once that's been done the drawing procedures are restored to their
19580ee5cbfSDavid du Colombier * default behavior in which each drawing command is treated as an isolated path.
19680ee5cbfSDavid du Colombier * The new version (called after "x X DrawPath") has copy set to FALSE, and calls
19780ee5cbfSDavid du Colombier * parsebuf() to figure out what goes in the output file. It's a feeble attempt
19880ee5cbfSDavid du Colombier * to free users and preprocessors (like pic) from having to know PostScript. The
19980ee5cbfSDavid du Colombier * comments in parsebuf() describe what's handled.
20080ee5cbfSDavid du Colombier *
20180ee5cbfSDavid du Colombier * In the early version a path was started with "x X BeginObject" and ended with
20280ee5cbfSDavid du Colombier * "x X EndObject". In both cases *buf was just copied to the output file, and
20380ee5cbfSDavid du Colombier * was expected to be legitimate PostScript that manipulated the current path.
20480ee5cbfSDavid du Colombier * The old escape sequence will be supported for a while (for Ravi), and always
20580ee5cbfSDavid du Colombier * call this routine with copy set to TRUE.
20680ee5cbfSDavid du Colombier *
20780ee5cbfSDavid du Colombier *
20880ee5cbfSDavid du Colombier */
20980ee5cbfSDavid du Colombier
21080ee5cbfSDavid du Colombier if ( inpath == TRUE ) {
21180ee5cbfSDavid du Colombier if ( copy == TRUE )
21280ee5cbfSDavid du Colombier Bprint(Bstdout, "%s\n", buf);
21380ee5cbfSDavid du Colombier else
21480ee5cbfSDavid du Colombier parsebuf(buf);
21580ee5cbfSDavid du Colombier Bprint(Bstdout, "grestore\n");
21680ee5cbfSDavid du Colombier Bprint(Bstdout, "/inpath false def\n");
21780ee5cbfSDavid du Colombier /* reset(); */
21880ee5cbfSDavid du Colombier inpath = FALSE;
21980ee5cbfSDavid du Colombier }
22080ee5cbfSDavid du Colombier }
22180ee5cbfSDavid du Colombier
22280ee5cbfSDavid du Colombier
22380ee5cbfSDavid du Colombier static void
parsebuf(char * buf)22480ee5cbfSDavid du Colombier parsebuf(char *buf)
22580ee5cbfSDavid du Colombier {
22680ee5cbfSDavid du Colombier char *p; /* usually the next token */
22780ee5cbfSDavid du Colombier char *q;
22880ee5cbfSDavid du Colombier int gsavelevel = 0; /* non-zero if we've done a gsave */
22980ee5cbfSDavid du Colombier
23080ee5cbfSDavid du Colombier /*
23180ee5cbfSDavid du Colombier * Simple minded attempt at parsing the string that followed an "x X DrawPath"
23280ee5cbfSDavid du Colombier * command. Everything not recognized here is simply ignored - there's absolutely
23380ee5cbfSDavid du Colombier * no error checking and what was originally in buf is clobbered by strtok().
23480ee5cbfSDavid du Colombier * A typical *buf might look like,
23580ee5cbfSDavid du Colombier *
23680ee5cbfSDavid du Colombier * gray .9 fill stroke
23780ee5cbfSDavid du Colombier *
23880ee5cbfSDavid du Colombier * to fill the current path with a gray level of .9 and follow that by stroking the
23980ee5cbfSDavid du Colombier * outline of the path. Since unrecognized tokens are ignored the last example
24080ee5cbfSDavid du Colombier * could also be written as,
24180ee5cbfSDavid du Colombier *
24280ee5cbfSDavid du Colombier * with gray .9 fill then stroke
24380ee5cbfSDavid du Colombier *
24480ee5cbfSDavid du Colombier * The "with" and "then" strings aren't recognized tokens and are simply discarded.
24580ee5cbfSDavid du Colombier * The "stroke", "fill", and "wfill" force out appropriate PostScript code and are
24680ee5cbfSDavid du Colombier * followed by a grestore. In otherwords changes to the grahics state (eg. a gray
24780ee5cbfSDavid du Colombier * level or color) are reset to default values immediately after the stroke, fill,
24880ee5cbfSDavid du Colombier * or wfill tokens. For now "fill" gets invokes PostScript's eofill operator and
24980ee5cbfSDavid du Colombier * "wfill" calls fill (ie. the operator that uses the non-zero winding rule).
25080ee5cbfSDavid du Colombier *
25180ee5cbfSDavid du Colombier * The tokens that cause temporary changes to the graphics state are "gray" (for
25280ee5cbfSDavid du Colombier * setting the gray level), "color" (for selecting a known color from the colordict
25380ee5cbfSDavid du Colombier * dictionary defined in *colorfile), and "line" (for setting the line width). All
25480ee5cbfSDavid du Colombier * three tokens can be extended since strncmp() makes the comparison. For example
25580ee5cbfSDavid du Colombier * the strings "line" and "linewidth" accomplish the same thing. Colors are named
25680ee5cbfSDavid du Colombier * (eg. "red"), but must be appropriately defined in *colorfile. For now all three
25780ee5cbfSDavid du Colombier * tokens must be followed immediately by their single argument. The gray level
25880ee5cbfSDavid du Colombier * (ie. the argument that follows "gray") should be a number between 0 and 1, with
25980ee5cbfSDavid du Colombier * 0 for black and 1 for white.
26080ee5cbfSDavid du Colombier *
26180ee5cbfSDavid du Colombier * To pass straight PostScript through enclose the appropriate commands in double
26280ee5cbfSDavid du Colombier * quotes. Straight PostScript is only bracketed by the outermost gsave/grestore
26380ee5cbfSDavid du Colombier * pair (ie. the one from the initial "x X BeginPath") although that's probably
26480ee5cbfSDavid du Colombier * a mistake. Suspect I may have to change the double quote delimiters.
26580ee5cbfSDavid du Colombier */
266*456a8764SDavid du Colombier for(p = buf; p != nil; p = q) {
267*456a8764SDavid du Colombier if( q = strchr(p, ' ') )
26880ee5cbfSDavid du Colombier *q++ = '\0';
26980ee5cbfSDavid du Colombier
27080ee5cbfSDavid du Colombier if ( gsavelevel == 0 ) {
27180ee5cbfSDavid du Colombier Bprint(Bstdout, "gsave\n");
27280ee5cbfSDavid du Colombier gsavelevel++;
27380ee5cbfSDavid du Colombier }
27480ee5cbfSDavid du Colombier if ( strcmp(p, "stroke") == 0 ) {
27580ee5cbfSDavid du Colombier Bprint(Bstdout, "closepath stroke\ngrestore\n");
27680ee5cbfSDavid du Colombier gsavelevel--;
27780ee5cbfSDavid du Colombier } else if ( strcmp(p, "openstroke") == 0 ) {
27880ee5cbfSDavid du Colombier Bprint(Bstdout, "stroke\ngrestore\n");
27980ee5cbfSDavid du Colombier gsavelevel--;
28080ee5cbfSDavid du Colombier } else if ( strcmp(p, "fill") == 0 ) {
28180ee5cbfSDavid du Colombier Bprint(Bstdout, "eofill\ngrestore\n");
28280ee5cbfSDavid du Colombier gsavelevel--;
28380ee5cbfSDavid du Colombier } else if ( strcmp(p, "wfill") == 0 ) {
28480ee5cbfSDavid du Colombier Bprint(Bstdout, "fill\ngrestore\n");
28580ee5cbfSDavid du Colombier gsavelevel--;
28680ee5cbfSDavid du Colombier } else if ( strcmp(p, "sfill") == 0 ) {
28780ee5cbfSDavid du Colombier Bprint(Bstdout, "eofill\ngrestore\ngsave\nstroke\ngrestore\n");
28880ee5cbfSDavid du Colombier gsavelevel--;
28980ee5cbfSDavid du Colombier } else if ( strncmp(p, "gray", strlen("gray")) == 0 ) {
29080ee5cbfSDavid du Colombier if( q ) {
29180ee5cbfSDavid du Colombier p = q;
29280ee5cbfSDavid du Colombier if ( q = strchr(p, ' ') )
29380ee5cbfSDavid du Colombier *q++ = '\0';
29480ee5cbfSDavid du Colombier Bprint(Bstdout, "%s setgray\n", p);
29580ee5cbfSDavid du Colombier }
29680ee5cbfSDavid du Colombier } else if ( strncmp(p, "color", strlen("color")) == 0 ) {
29780ee5cbfSDavid du Colombier if( q ) {
29880ee5cbfSDavid du Colombier p = q;
29980ee5cbfSDavid du Colombier if ( q = strchr(p, ' ') )
30080ee5cbfSDavid du Colombier *q++ = '\0';
30180ee5cbfSDavid du Colombier Bprint(Bstdout, "/%s setcolor\n", p);
30280ee5cbfSDavid du Colombier }
30380ee5cbfSDavid du Colombier } else if ( strncmp(p, "line", strlen("line")) == 0 ) {
30480ee5cbfSDavid du Colombier if( q ) {
30580ee5cbfSDavid du Colombier p = q;
30680ee5cbfSDavid du Colombier if ( q = strchr(p, ' ') )
30780ee5cbfSDavid du Colombier *q++ = '\0';
30880ee5cbfSDavid du Colombier Bprint(Bstdout, "%s resolution mul 2 div setlinewidth\n", p);
30980ee5cbfSDavid du Colombier }
31080ee5cbfSDavid du Colombier } else if ( strncmp(p, "reverse", strlen("reverse")) == 0 )
31180ee5cbfSDavid du Colombier Bprint(Bstdout, "reversepath\n");
31280ee5cbfSDavid du Colombier else if ( *p == '"' ) {
31380ee5cbfSDavid du Colombier for ( ; gsavelevel > 0; gsavelevel-- )
31480ee5cbfSDavid du Colombier Bprint(Bstdout, "grestore\n");
31580ee5cbfSDavid du Colombier if ( q != nil )
31680ee5cbfSDavid du Colombier *--q = ' ';
31780ee5cbfSDavid du Colombier if ( (q = strchr(p, '"')) != nil ) {
31880ee5cbfSDavid du Colombier *q++ = '\0';
31980ee5cbfSDavid du Colombier Bprint(Bstdout, "%s\n", p);
32080ee5cbfSDavid du Colombier }
32180ee5cbfSDavid du Colombier }
32280ee5cbfSDavid du Colombier }
32380ee5cbfSDavid du Colombier
32480ee5cbfSDavid du Colombier for ( ; gsavelevel > 0; gsavelevel-- )
32580ee5cbfSDavid du Colombier Bprint(Bstdout, "grestore\n");
32680ee5cbfSDavid du Colombier
32780ee5cbfSDavid du Colombier }
328