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