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 | |
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 | } |