chiark / gitweb /
Update changelog for 20170923.ff218728-0+iwj2~3.gbpc58e0c release
[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, const 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, const 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,
106                          const char *text)
107 {
108     psdata *ps = (psdata *)handle;
109
110     y = ps->ytop - y;
111     ps_setcolour(ps, colour);
112     ps_printf(ps, "/%s findfont %d scalefont setfont\n",
113               fonttype == FONT_FIXED ? "Courier-L1" : "Helvetica-L1",
114               fontsize);
115     if (align & ALIGN_VCENTRE) {
116         ps_printf(ps, "newpath 0 0 moveto (X) true charpath flattenpath"
117                   " pathbbox\n"
118                   "3 -1 roll add 2 div %d exch sub %d exch moveto pop pop\n",
119                   y, x);
120     } else {
121         ps_printf(ps, "%d %d moveto\n", x, y);
122     }
123     ps_printf(ps, "(");
124     while (*text) {
125         if (*text == '\\' || *text == '(' || *text == ')')
126             ps_printf(ps, "\\");
127         ps_printf(ps, "%c", *text);
128         text++;
129     }
130     ps_printf(ps, ") ");
131     if (align & (ALIGN_HCENTRE | ALIGN_HRIGHT))
132         ps_printf(ps, "dup stringwidth pop %sneg 0 rmoveto show\n",
133                   (align & ALIGN_HCENTRE) ? "2 div " : "");
134     else
135         ps_printf(ps, "show\n");
136 }
137
138 static void ps_draw_rect(void *handle, int x, int y, int w, int h, int colour)
139 {
140     psdata *ps = (psdata *)handle;
141
142     y = ps->ytop - y;
143     /*
144      * Offset by half a pixel for the exactness requirement.
145      */
146     ps_printf(ps, "newpath %g %g moveto %d 0 rlineto 0 %d rlineto"
147               " %d 0 rlineto closepath\n", x - 0.5, y + 0.5, w, -h, -w);
148     ps_fill(ps, colour);
149 }
150
151 static void ps_draw_line(void *handle, int x1, int y1, int x2, int y2,
152                          int colour)
153 {
154     psdata *ps = (psdata *)handle;
155
156     y1 = ps->ytop - y1;
157     y2 = ps->ytop - y2;
158     ps_printf(ps, "newpath %d %d moveto %d %d lineto\n", x1, y1, x2, y2);
159     ps_stroke(ps, colour);
160 }
161
162 static void ps_draw_polygon(void *handle, int *coords, int npoints,
163                             int fillcolour, int outlinecolour)
164 {
165     psdata *ps = (psdata *)handle;
166
167     int i;
168
169     ps_printf(ps, "newpath %d %d moveto\n", coords[0], ps->ytop - coords[1]);
170
171     for (i = 1; i < npoints; i++)
172         ps_printf(ps, "%d %d lineto\n", coords[i*2], ps->ytop - coords[i*2+1]);
173
174     ps_printf(ps, "closepath\n");
175
176     if (fillcolour >= 0) {
177         ps_printf(ps, "gsave\n");
178         ps_fill(ps, fillcolour);
179         ps_printf(ps, "grestore\n");
180     }
181     ps_stroke(ps, outlinecolour);
182 }
183
184 static void ps_draw_circle(void *handle, int cx, int cy, int radius,
185                            int fillcolour, int outlinecolour)
186 {
187     psdata *ps = (psdata *)handle;
188
189     cy = ps->ytop - cy;
190
191     ps_printf(ps, "newpath %d %d %d 0 360 arc closepath\n", cx, cy, radius);
192
193     if (fillcolour >= 0) {
194         ps_printf(ps, "gsave\n");
195         ps_fill(ps, fillcolour);
196         ps_printf(ps, "grestore\n");
197     }
198     ps_stroke(ps, outlinecolour);
199 }
200
201 static void ps_unclip(void *handle)
202 {
203     psdata *ps = (psdata *)handle;
204
205     assert(ps->clipped);
206     ps_printf(ps, "grestore\n");
207     ps->clipped = FALSE;
208 }
209  
210 static void ps_clip(void *handle, int x, int y, int w, int h)
211 {
212     psdata *ps = (psdata *)handle;
213
214     if (ps->clipped)
215         ps_unclip(ps);
216
217     y = ps->ytop - y;
218     /*
219      * Offset by half a pixel for the exactness requirement.
220      */
221     ps_printf(ps, "gsave\n");
222     ps_printf(ps, "newpath %g %g moveto %d 0 rlineto 0 %d rlineto"
223               " %d 0 rlineto closepath\n", x - 0.5, y + 0.5, w, -h, -w);
224     ps_printf(ps, "clip\n");
225     ps->clipped = TRUE;
226 }
227
228 static void ps_line_width(void *handle, float width)
229 {
230     psdata *ps = (psdata *)handle;
231
232     ps_printf(ps, "%g setlinewidth\n", width);
233 }
234
235 static void ps_line_dotted(void *handle, int dotted)
236 {
237     psdata *ps = (psdata *)handle;
238
239     if (dotted) {
240         ps_printf(ps, "[ currentlinewidth 3 mul ] 0 setdash\n");
241     } else {
242         ps_printf(ps, "[ ] 0 setdash\n");
243     }
244 }
245
246 static char *ps_text_fallback(void *handle, const char *const *strings,
247                               int nstrings)
248 {
249     /*
250      * We can handle anything in ISO 8859-1, and we'll manually
251      * translate it out of UTF-8 for the purpose.
252      */
253     int i, maxlen;
254     char *ret;
255
256     maxlen = 0;
257     for (i = 0; i < nstrings; i++) {
258         int len = strlen(strings[i]);
259         if (maxlen < len) maxlen = len;
260     }
261
262     ret = snewn(maxlen + 1, char);
263
264     for (i = 0; i < nstrings; i++) {
265         const char *p = strings[i];
266         char *q = ret;
267
268         while (*p) {
269             int c = (unsigned char)*p++;
270             if (c < 0x80) {
271                 *q++ = c;              /* ASCII */
272             } else if ((c == 0xC2 || c == 0xC3) && (*p & 0xC0) == 0x80) {
273                 *q++ = (c << 6) | (*p++ & 0x3F);   /* top half of 8859-1 */
274             } else {
275                 break;
276             }
277         }
278
279         if (!*p) {
280             *q = '\0';
281             return ret;
282         }
283     }
284
285     assert(!"Should never reach here");
286     return NULL;
287 }
288
289 static void ps_begin_doc(void *handle, int pages)
290 {
291     psdata *ps = (psdata *)handle;
292
293     fputs("%!PS-Adobe-3.0\n", ps->fp);
294     fputs("%%Creator: Simon Tatham's Portable Puzzle Collection\n", ps->fp);
295     fputs("%%DocumentData: Clean7Bit\n", ps->fp);
296     fputs("%%LanguageLevel: 1\n", ps->fp);
297     fprintf(ps->fp, "%%%%Pages: %d\n", pages);
298     fputs("%%DocumentNeededResources:\n", ps->fp);
299     fputs("%%+ font Helvetica\n", ps->fp);
300     fputs("%%+ font Courier\n", ps->fp);
301     fputs("%%EndComments\n", ps->fp);
302     fputs("%%BeginSetup\n", ps->fp);
303     fputs("%%IncludeResource: font Helvetica\n", ps->fp);
304     fputs("%%IncludeResource: font Courier\n", ps->fp);
305     fputs("%%EndSetup\n", ps->fp);
306     fputs("%%BeginProlog\n", ps->fp);
307     /*
308      * Re-encode Helvetica and Courier into ISO-8859-1, which gives
309      * us times and divide signs - and also (according to the
310      * Language Reference Manual) a bonus in that the ASCII '-' code
311      * point now points to a minus sign instead of a hyphen.
312      */
313     fputs("/Helvetica findfont " /* get the font dictionary */
314           "dup maxlength dict dup begin " /* create and open a new dict */
315           "exch " /* move the original font to top of stack */
316           "{1 index /FID ne {def} {pop pop} ifelse} forall "
317                                        /* copy everything except FID */
318           "/Encoding ISOLatin1Encoding def "
319                               /* set the thing we actually wanted to change */
320           "/FontName /Helvetica-L1 def " /* set a new font name */
321           "FontName end exch definefont" /* and define the font */
322           "\n", ps->fp);
323     fputs("/Courier findfont " /* get the font dictionary */
324           "dup maxlength dict dup begin " /* create and open a new dict */
325           "exch " /* move the original font to top of stack */
326           "{1 index /FID ne {def} {pop pop} ifelse} forall "
327                                        /* copy everything except FID */
328           "/Encoding ISOLatin1Encoding def "
329                               /* set the thing we actually wanted to change */
330           "/FontName /Courier-L1 def " /* set a new font name */
331           "FontName end exch definefont" /* and define the font */
332           "\n", ps->fp);
333     fputs("%%EndProlog\n", ps->fp);
334 }
335
336 static void ps_begin_page(void *handle, int number)
337 {
338     psdata *ps = (psdata *)handle;
339
340     fprintf(ps->fp, "%%%%Page: %d %d\ngsave save\n%g dup scale\n",
341             number, number, 72.0 / 25.4);
342 }
343
344 static void ps_begin_puzzle(void *handle, float xm, float xc,
345                             float ym, float yc, int pw, int ph, float wmm)
346 {
347     psdata *ps = (psdata *)handle;
348
349     fprintf(ps->fp, "gsave\n"
350             "clippath flattenpath pathbbox pop pop translate\n"
351             "clippath flattenpath pathbbox 4 2 roll pop pop\n"
352             "exch %g mul %g add exch dup %g mul %g add sub translate\n"
353             "%g dup scale\n"
354             "0 -%d translate\n", xm, xc, ym, yc, wmm/pw, ph);
355     ps->ytop = ph;
356     ps->clipped = FALSE;
357     ps->gamewidth = pw;
358     ps->gameheight = ph;
359     ps->hatchthick = 0.2 * pw / wmm;
360     ps->hatchspace = 1.0 * pw / wmm;
361 }
362
363 static void ps_end_puzzle(void *handle)
364 {
365     psdata *ps = (psdata *)handle;
366
367     fputs("grestore\n", ps->fp);
368 }
369
370 static void ps_end_page(void *handle, int number)
371 {
372     psdata *ps = (psdata *)handle;
373
374     fputs("restore grestore showpage\n", ps->fp);
375 }
376
377 static void ps_end_doc(void *handle)
378 {
379     psdata *ps = (psdata *)handle;
380
381     fputs("%%EOF\n", ps->fp);
382 }
383
384 static const struct drawing_api ps_drawing = {
385     ps_draw_text,
386     ps_draw_rect,
387     ps_draw_line,
388     ps_draw_polygon,
389     ps_draw_circle,
390     NULL /* draw_update */,
391     ps_clip,
392     ps_unclip,
393     NULL /* start_draw */,
394     NULL /* end_draw */,
395     NULL /* status_bar */,
396     NULL /* blitter_new */,
397     NULL /* blitter_free */,
398     NULL /* blitter_save */,
399     NULL /* blitter_load */,
400     ps_begin_doc,
401     ps_begin_page,
402     ps_begin_puzzle,
403     ps_end_puzzle,
404     ps_end_page,
405     ps_end_doc,
406     ps_line_width,
407     ps_line_dotted,
408     ps_text_fallback,
409 };
410
411 psdata *ps_init(FILE *outfile, int colour)
412 {
413     psdata *ps = snew(psdata);
414
415     ps->fp = outfile;
416     ps->colour = colour;
417     ps->ytop = 0;
418     ps->clipped = FALSE;
419     ps->hatchthick = ps->hatchspace = ps->gamewidth = ps->gameheight = 0;
420     ps->drawing = drawing_new(&ps_drawing, NULL, ps);
421
422     return ps;
423 }
424
425 void ps_free(psdata *ps)
426 {
427     drawing_free(ps->drawing);
428     sfree(ps);
429 }
430
431 drawing *ps_drawing_api(psdata *ps)
432 {
433     return ps->drawing;
434 }