Substantial infrastructure upheaval. I've separated the drawing API
[sgt/puzzles] / ps.c
CommitLineData
dafd6cf6 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
14struct 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
24static 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
33static 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
75static 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
96static void ps_setcolour(psdata *ps, int colour)
97{
98 ps_setcolour_internal(ps, colour, "");
99}
100
101static void ps_stroke(psdata *ps, int colour)
102{
103 ps_setcolour_internal(ps, colour, " stroke");
104}
105
106static 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
139static 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
152static 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
163static 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
185static 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
202static 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
211static 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
229static 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
236static 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
255static 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
263static 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
282static void ps_end_puzzle(void *handle)
283{
284 psdata *ps = (psdata *)handle;
285
286 fputs("grestore\n", ps->fp);
287}
288
289static 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
296static void ps_end_doc(void *handle)
297{
298 psdata *ps = (psdata *)handle;
299
300 fputs("%%EOF\n", ps->fp);
301}
302
303static 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
328psdata *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
342void ps_free(psdata *ps)
343{
344 drawing_free(ps->drawing);
345 sfree(ps);
346}
347
348drawing *ps_drawing_api(psdata *ps)
349{
350 return ps->drawing;
351}