chiark / gitweb /
Introduce some infrastructure to permit games' print functions to
[sgt-puzzles.git] / ps.c
1 /*
2  * ps.c: PostScript printing functions.
3  */
4
5 #include <stdio.h>
6 #include <stdarg.h>
7 #include <string.h>
8 #include <assert.h>
9
10 #include "puzzles.h"
11
12 #define ROOT2 1.414213562
13
14 struct psdata {
15     FILE *fp;
16     int colour;
17     int ytop;
18     int clipped;
19     float hatchthick, hatchspace;
20     int gamewidth, gameheight;
21     drawing *drawing;
22 };
23
24 static void ps_printf(psdata *ps, char *fmt, ...)
25 {
26     va_list ap;
27
28     va_start(ap, fmt);
29     vfprintf(ps->fp, fmt, ap);
30     va_end(ap);
31 }
32
33 static void ps_fill(psdata *ps, int colour)
34 {
35     int hatch;
36     float r, g, b;
37
38     print_get_colour(ps->drawing, colour, ps->colour, &hatch, &r, &g, &b);
39
40     if (hatch < 0) {
41         if (ps->colour)
42             ps_printf(ps, "%g %g %g setrgbcolor fill\n", r, g, b);
43         else
44             ps_printf(ps, "%g setgray fill\n", r);
45     } else {
46         /* Clip to the region. */
47         ps_printf(ps, "gsave clip\n");
48         /* Hatch the entire game printing area. */
49         ps_printf(ps, "newpath\n");
50         if (hatch == HATCH_VERT || hatch == HATCH_PLUS)
51             ps_printf(ps, "0 %g %d {\n"
52                       "  0 moveto 0 %d rlineto\n"
53                       "} for\n", ps->hatchspace, ps->gamewidth,
54                       ps->gameheight);
55         if (hatch == HATCH_HORIZ || hatch == HATCH_PLUS)
56             ps_printf(ps, "0 %g %d {\n"
57                       "  0 exch moveto %d 0 rlineto\n"
58                       "} for\n", ps->hatchspace, ps->gameheight,
59                       ps->gamewidth);
60         if (hatch == HATCH_SLASH || hatch == HATCH_X)
61             ps_printf(ps, "%d %g %d {\n"
62                       "  0 moveto %d dup rlineto\n"
63                       "} for\n", -ps->gameheight, ps->hatchspace * ROOT2,
64                       ps->gamewidth, max(ps->gamewidth, ps->gameheight));
65         if (hatch == HATCH_BACKSLASH || hatch == HATCH_X)
66             ps_printf(ps, "0 %g %d {\n"
67                       "  0 moveto %d neg dup neg rlineto\n"
68                       "} for\n", ps->hatchspace * ROOT2,
69                       ps->gamewidth+ps->gameheight,
70                       max(ps->gamewidth, ps->gameheight));
71         ps_printf(ps, "0 setgray %g setlinewidth stroke grestore\n",
72                   ps->hatchthick);
73     }
74 }
75
76 static void ps_setcolour_internal(psdata *ps, int colour, char *suffix)
77 {
78     int hatch;
79     float r, g, b;
80
81     print_get_colour(ps->drawing, colour, ps->colour, &hatch, &r, &g, &b);
82
83     /*
84      * Stroking in hatched colours is not permitted.
85      */
86     assert(hatch < 0);
87     
88     if (ps->colour)
89         ps_printf(ps, "%g %g %g setrgbcolor%s\n", r, g, b, suffix);
90     else
91         ps_printf(ps, "%g setgray%s\n", r, suffix);
92 }
93
94 static void ps_setcolour(psdata *ps, int colour)
95 {
96     ps_setcolour_internal(ps, colour, "");
97 }
98
99 static void ps_stroke(psdata *ps, int colour)
100 {
101     ps_setcolour_internal(ps, colour, " stroke");
102 }
103
104 static void ps_draw_text(void *handle, int x, int y, int fonttype,
105                          int fontsize, int align, int colour, char *text)
106 {
107     psdata *ps = (psdata *)handle;
108
109     y = ps->ytop - y;
110     ps_setcolour(ps, colour);
111     ps_printf(ps, "/%s findfont %d scalefont setfont\n",
112               fonttype == FONT_FIXED ? "Courier" : "Helvetica",
113               fontsize);
114     if (align & ALIGN_VCENTRE) {
115         ps_printf(ps, "newpath 0 0 moveto (X) true charpath flattenpath"
116                   " pathbbox\n"
117                   "3 -1 roll add 2 div %d exch sub %d exch moveto pop pop\n",
118                   y, x);
119     } else {
120         ps_printf(ps, "%d %d moveto\n", x, y);
121     }
122     ps_printf(ps, "(");
123     while (*text) {
124         if (*text == '\\' || *text == '(' || *text == ')')
125             ps_printf(ps, "\\");
126         ps_printf(ps, "%c", *text);
127         text++;
128     }
129     ps_printf(ps, ") ");
130     if (align & (ALIGN_HCENTRE | ALIGN_HRIGHT))
131         ps_printf(ps, "dup stringwidth pop %sneg 0 rmoveto show\n",
132                   (align & ALIGN_HCENTRE) ? "2 div " : "");
133     else
134         ps_printf(ps, "show\n");
135 }
136
137 static void ps_draw_rect(void *handle, int x, int y, int w, int h, int colour)
138 {
139     psdata *ps = (psdata *)handle;
140
141     y = ps->ytop - y;
142     /*
143      * Offset by half a pixel for the exactness requirement.
144      */
145     ps_printf(ps, "newpath %g %g moveto %d 0 rlineto 0 %d rlineto"
146               " %d 0 rlineto closepath\n", x - 0.5, y + 0.5, w, -h, -w);
147     ps_fill(ps, colour);
148 }
149
150 static void ps_draw_line(void *handle, int x1, int y1, int x2, int y2,
151                          int colour)
152 {
153     psdata *ps = (psdata *)handle;
154
155     y1 = ps->ytop - y1;
156     y2 = ps->ytop - y2;
157     ps_printf(ps, "newpath %d %d moveto %d %d lineto\n", x1, y1, x2, y2);
158     ps_stroke(ps, colour);
159 }
160
161 static void ps_draw_polygon(void *handle, int *coords, int npoints,
162                             int fillcolour, int outlinecolour)
163 {
164     psdata *ps = (psdata *)handle;
165
166     int i;
167
168     ps_printf(ps, "newpath %d %d moveto\n", coords[0], ps->ytop - coords[1]);
169
170     for (i = 1; i < npoints; i++)
171         ps_printf(ps, "%d %d lineto\n", coords[i*2], ps->ytop - coords[i*2+1]);
172
173     ps_printf(ps, "closepath\n");
174
175     if (fillcolour >= 0) {
176         ps_printf(ps, "gsave\n");
177         ps_fill(ps, fillcolour);
178         ps_printf(ps, "grestore\n");
179     }
180     ps_stroke(ps, outlinecolour);
181 }
182
183 static void ps_draw_circle(void *handle, int cx, int cy, int radius,
184                            int fillcolour, int outlinecolour)
185 {
186     psdata *ps = (psdata *)handle;
187
188     cy = ps->ytop - cy;
189
190     ps_printf(ps, "newpath %d %d %d 0 360 arc closepath\n", cx, cy, radius);
191
192     if (fillcolour >= 0) {
193         ps_printf(ps, "gsave\n");
194         ps_fill(ps, fillcolour);
195         ps_printf(ps, "grestore\n");
196     }
197     ps_stroke(ps, outlinecolour);
198 }
199
200 static void ps_unclip(void *handle)
201 {
202     psdata *ps = (psdata *)handle;
203
204     assert(ps->clipped);
205     ps_printf(ps, "grestore\n");
206     ps->clipped = FALSE;
207 }
208  
209 static void ps_clip(void *handle, int x, int y, int w, int h)
210 {
211     psdata *ps = (psdata *)handle;
212
213     if (ps->clipped)
214         ps_unclip(ps);
215
216     y = ps->ytop - y;
217     /*
218      * Offset by half a pixel for the exactness requirement.
219      */
220     ps_printf(ps, "gsave\n");
221     ps_printf(ps, "newpath %g %g moveto %d 0 rlineto 0 %d rlineto"
222               " %d 0 rlineto closepath\n", x - 0.5, y + 0.5, w, -h, -w);
223     ps_printf(ps, "clip\n");
224     ps->clipped = TRUE;
225 }
226
227 static void ps_line_width(void *handle, float width)
228 {
229     psdata *ps = (psdata *)handle;
230
231     ps_printf(ps, "%g setlinewidth\n", width);
232 }
233
234 static void ps_line_dotted(void *handle, int dotted)
235 {
236     psdata *ps = (psdata *)handle;
237
238     if (dotted) {
239         ps_printf(ps, "[ currentlinewidth 3 mul ] 0 setdash\n");
240     } else {
241         ps_printf(ps, "[ ] 0 setdash\n");
242     }
243 }
244
245 static void ps_begin_doc(void *handle, int pages)
246 {
247     psdata *ps = (psdata *)handle;
248
249     fputs("%!PS-Adobe-3.0\n", ps->fp);
250     fputs("%%Creator: Simon Tatham's Portable Puzzle Collection\n", ps->fp);
251     fputs("%%DocumentData: Clean7Bit\n", ps->fp);
252     fputs("%%LanguageLevel: 1\n", ps->fp);
253     fprintf(ps->fp, "%%%%Pages: %d\n", pages);
254     fputs("%%DocumentNeededResources:\n", ps->fp);
255     fputs("%%+ font Helvetica\n", ps->fp);
256     fputs("%%+ font Courier\n", ps->fp);
257     fputs("%%EndComments\n", ps->fp);
258     fputs("%%BeginSetup\n", ps->fp);
259     fputs("%%IncludeResource: font Helvetica\n", ps->fp);
260     fputs("%%IncludeResource: font Courier\n", ps->fp);
261     fputs("%%EndSetup\n", ps->fp);
262 }
263
264 static void ps_begin_page(void *handle, int number)
265 {
266     psdata *ps = (psdata *)handle;
267
268     fprintf(ps->fp, "%%%%Page: %d %d\ngsave save\n%g dup scale\n",
269             number, number, 72.0 / 25.4);
270 }
271
272 static void ps_begin_puzzle(void *handle, float xm, float xc,
273                             float ym, float yc, int pw, int ph, float wmm)
274 {
275     psdata *ps = (psdata *)handle;
276
277     fprintf(ps->fp, "gsave\n"
278             "clippath flattenpath pathbbox pop pop translate\n"
279             "clippath flattenpath pathbbox 4 2 roll pop pop\n"
280             "exch %g mul %g add exch dup %g mul %g add sub translate\n"
281             "%g dup scale\n"
282             "0 -%d translate\n", xm, xc, ym, yc, wmm/pw, ph);
283     ps->ytop = ph;
284     ps->clipped = FALSE;
285     ps->gamewidth = pw;
286     ps->gameheight = ph;
287     ps->hatchthick = 0.2 * pw / wmm;
288     ps->hatchspace = 1.0 * pw / wmm;
289 }
290
291 static void ps_end_puzzle(void *handle)
292 {
293     psdata *ps = (psdata *)handle;
294
295     fputs("grestore\n", ps->fp);
296 }
297
298 static void ps_end_page(void *handle, int number)
299 {
300     psdata *ps = (psdata *)handle;
301
302     fputs("restore grestore showpage\n", ps->fp);
303 }
304
305 static void ps_end_doc(void *handle)
306 {
307     psdata *ps = (psdata *)handle;
308
309     fputs("%%EOF\n", ps->fp);
310 }
311
312 static const struct drawing_api ps_drawing = {
313     ps_draw_text,
314     ps_draw_rect,
315     ps_draw_line,
316     ps_draw_polygon,
317     ps_draw_circle,
318     NULL /* draw_update */,
319     ps_clip,
320     ps_unclip,
321     NULL /* start_draw */,
322     NULL /* end_draw */,
323     NULL /* status_bar */,
324     NULL /* blitter_new */,
325     NULL /* blitter_free */,
326     NULL /* blitter_save */,
327     NULL /* blitter_load */,
328     ps_begin_doc,
329     ps_begin_page,
330     ps_begin_puzzle,
331     ps_end_puzzle,
332     ps_end_page,
333     ps_end_doc,
334     ps_line_width,
335     ps_line_dotted,
336 };
337
338 psdata *ps_init(FILE *outfile, int colour)
339 {
340     psdata *ps = snew(psdata);
341
342     ps->fp = outfile;
343     ps->colour = colour;
344     ps->ytop = 0;
345     ps->clipped = FALSE;
346     ps->hatchthick = ps->hatchspace = ps->gamewidth = ps->gameheight = 0;
347     ps->drawing = drawing_new(&ps_drawing, NULL, ps);
348
349     return ps;
350 }
351
352 void ps_free(psdata *ps)
353 {
354     drawing_free(ps->drawing);
355     sfree(ps);
356 }
357
358 drawing *ps_drawing_api(psdata *ps)
359 {
360     return ps->drawing;
361 }