d91e1fc9 |
1 | #!/usr/bin/env python |
2 | |
3 | # This program accepts a series of newline-separated game IDs on |
4 | # stdin and formats them into PostScript to be printed out. You |
5 | # specify using command-line options which game the IDs are for, |
6 | # and how many you want per page. |
7 | |
8 | # Supported games are those which are sensibly solvable using |
c3ea5dc7 |
9 | # pencil and paper: Rectangles, Pattern, Solo, Net. |
d91e1fc9 |
10 | |
11 | # Command-line syntax is |
12 | # |
13 | # print.py <game-name> <format> |
14 | # |
c3ea5dc7 |
15 | # <game-name> is one of `rect', `rectangles', `pattern', `solo', |
b71ab32e |
16 | # `net', `dominosa'. <format> is two numbers separated by an x: |
17 | # `2x3', for example, means two columns by three rows. |
d91e1fc9 |
18 | # |
19 | # The program will then read game IDs from stdin until it sees EOF, |
20 | # and generate as many PostScript pages on stdout as it needs. |
21 | # |
22 | # The resulting PostScript will automatically adapt itself to the |
23 | # size of the clip rectangle, so that the puzzles are sensibly |
24 | # distributed across whatever paper size you decide to use. |
25 | |
26 | import sys |
27 | import string |
28 | import re |
29 | |
30 | class Holder: |
31 | pass |
32 | |
33 | def psvprint(h, a): |
34 | for i in xrange(len(a)): |
35 | h.s = h.s + str(a[i]) |
36 | if i < len(a)-1: |
37 | h.s = h.s + " " |
38 | else: |
39 | h.s = h.s + "\n" |
40 | |
41 | def psprint(h, *a): |
42 | psvprint(h, a) |
43 | |
44 | def rect_format(s): |
45 | # Parse the game ID. |
46 | ret = Holder() |
47 | ret.s = "" |
48 | params, seed = string.split(s, ":") |
49 | w, h = map(string.atoi, string.split(params, "x")) |
50 | grid = [] |
51 | while len(seed) > 0: |
52 | if seed[0] in '_'+string.lowercase: |
53 | if seed[0] in string.lowercase: |
54 | grid.extend([-1] * (ord(seed[0]) - ord('a') + 1)) |
55 | seed = seed[1:] |
56 | elif seed[0] in string.digits: |
57 | ns = "" |
58 | while len(seed) > 0 and seed[0] in string.digits: |
59 | ns = ns + seed[0] |
60 | seed = seed[1:] |
61 | grid.append(string.atoi(ns)) |
62 | assert w * h == len(grid) |
63 | # I'm going to arbitrarily choose to use 7pt text for the |
64 | # numbers, and a 14pt grid pitch. |
65 | textht = 7 |
66 | gridpitch = 14 |
67 | # Set up coordinate system. |
68 | pw = gridpitch * w |
69 | ph = gridpitch * h |
70 | ret.coords = (pw/2, pw/2, ph/2, ph/2) |
71 | psprint(ret, "%g %g translate" % (-ret.coords[0], -ret.coords[2])) |
72 | # Draw the internal grid lines, _very_ thin (the player will |
73 | # need to draw over them visibly). |
74 | psprint(ret, "newpath 0.01 setlinewidth") |
75 | for x in xrange(1,w): |
76 | psprint(ret, "%g 0 moveto 0 %g rlineto" % (x * gridpitch, h * gridpitch)) |
77 | for y in xrange(1,h): |
78 | psprint(ret, "0 %g moveto %g 0 rlineto" % (y * gridpitch, w * gridpitch)) |
79 | psprint(ret, "stroke") |
80 | # Draw round the grid exterior, much thicker. |
81 | psprint(ret, "newpath 1.5 setlinewidth") |
82 | psprint(ret, "0 0 moveto 0 %g rlineto %g 0 rlineto 0 %g rlineto" % \ |
83 | (h * gridpitch, w * gridpitch, -h * gridpitch)) |
84 | psprint(ret, "closepath stroke") |
85 | # And draw the numbers. |
86 | psprint(ret, "/Helvetica findfont %g scalefont setfont" % textht) |
87 | for y in xrange(h): |
88 | for x in xrange(w): |
89 | n = grid[y*w+x] |
90 | if n > 0: |
91 | psprint(ret, "%g %g (%d) ctshow" % \ |
92 | ((x+0.5)*gridpitch, (h-y-0.5)*gridpitch, n)) |
93 | return ret.coords, ret.s |
94 | |
c3ea5dc7 |
95 | def net_format(s): |
96 | # Parse the game ID. |
97 | ret = Holder() |
98 | ret.s = "" |
99 | params, seed = string.split(s, ":") |
100 | wrapping = 0 |
101 | if params[-1:] == "w": |
102 | wrapping = 1 |
103 | params = params[:-1] |
104 | w, h = map(string.atoi, string.split(params, "x")) |
105 | grid = [] |
106 | hbarriers = [] |
107 | vbarriers = [] |
108 | while len(seed) > 0: |
109 | n = string.atoi(seed[0], 16) |
110 | seed = seed[1:] |
111 | while len(seed) > 0 and seed[0] in 'hv': |
112 | x = len(grid) % w |
113 | y = len(grid) / w |
114 | if seed[0] == 'h': |
115 | hbarriers.append((x, y+1)) |
116 | else: |
117 | vbarriers.append((x+1, y)) |
118 | seed = seed[1:] |
119 | grid.append(n) |
120 | assert w * h == len(grid) |
121 | # I'm going to arbitrarily choose a 24pt grid pitch. |
122 | gridpitch = 24 |
123 | scale = 0.25 |
124 | bigoffset = 0.25 |
125 | smalloffset = 0.17 |
5ad01ca4 |
126 | squaresize = 0.25 |
c3ea5dc7 |
127 | # Set up coordinate system. |
128 | pw = gridpitch * w |
129 | ph = gridpitch * h |
130 | ret.coords = (pw/2, pw/2, ph/2, ph/2) |
131 | psprint(ret, "%g %g translate" % (-ret.coords[0], -ret.coords[2])) |
132 | # Draw the base grid lines. |
133 | psprint(ret, "newpath 0.02 setlinewidth") |
134 | for x in xrange(1,w): |
135 | psprint(ret, "%g 0 moveto 0 %g rlineto" % (x * gridpitch, h * gridpitch)) |
136 | for y in xrange(1,h): |
137 | psprint(ret, "0 %g moveto %g 0 rlineto" % (y * gridpitch, w * gridpitch)) |
138 | psprint(ret, "stroke") |
139 | # Draw round the grid exterior. |
140 | psprint(ret, "newpath") |
141 | if not wrapping: |
142 | psprint(ret, "2 setlinewidth") |
143 | psprint(ret, "0 0 moveto 0 %g rlineto %g 0 rlineto 0 %g rlineto" % \ |
144 | (h * gridpitch, w * gridpitch, -h * gridpitch)) |
145 | psprint(ret, "closepath stroke") |
146 | # Draw any barriers. |
147 | psprint(ret, "newpath 2 setlinewidth 1 setlinecap") |
148 | for x, y in hbarriers: |
149 | psprint(ret, "%g %g moveto %g 0 rlineto" % \ |
150 | (x * gridpitch, (h - y) * gridpitch, gridpitch)) |
151 | for x, y in vbarriers: |
152 | psprint(ret, "%g %g moveto 0 -%g rlineto" % \ |
153 | (x * gridpitch, (h - y) * gridpitch, gridpitch)) |
154 | psprint(ret, "stroke") |
155 | # And draw the symbol in each box. |
156 | for i in xrange(len(grid)): |
157 | x = i % w |
158 | y = i / w |
159 | v = grid[i] |
160 | # Rotate to canonical form. |
161 | if v in (1,2,4,8): |
162 | v = 1 |
163 | elif v in (5,10): |
164 | v = 5 |
165 | elif v in (3,6,9,12): |
166 | v = 9 |
167 | elif v in (7,11,13,14): |
168 | v = 13 |
169 | # Centre on an area in the corner of the tile. |
170 | psprint(ret, "gsave") |
171 | if v & 4: |
172 | hoffset = bigoffset |
173 | else: |
174 | hoffset = smalloffset |
175 | if v & 2: |
176 | voffset = bigoffset |
177 | else: |
178 | voffset = smalloffset |
179 | psprint(ret, "%g %g translate" % \ |
180 | ((x + hoffset) * gridpitch, (h - y - voffset) * gridpitch)) |
181 | psprint(ret, "%g dup scale" % (float(gridpitch) * scale / 2)) |
182 | psprint(ret, "newpath 0.07 setlinewidth") |
183 | # Draw the radial lines. |
184 | for dx, dy, z in ((1,0,1), (0,1,2), (-1,0,4), (0,-1,8)): |
185 | if v & z: |
186 | psprint(ret, "0 0 moveto %d %d lineto" % (dx, dy)) |
187 | psprint(ret, "stroke") |
188 | # Draw additional figures if desired. |
5ad01ca4 |
189 | if v == 1: |
c3ea5dc7 |
190 | # Endpoints have a little empty square at the centre. |
5ad01ca4 |
191 | psprint(ret, "newpath %g %g moveto 0 -%g rlineto" % \ |
192 | (squaresize, squaresize, squaresize * 2)) |
193 | psprint(ret, "-%g 0 rlineto 0 %g rlineto closepath fill" % \ |
194 | (squaresize * 2, squaresize * 2)) |
195 | # Get back out of the centre section. |
c3ea5dc7 |
196 | psprint(ret, "grestore") |
5ad01ca4 |
197 | # Draw the endpoint square in large in the middle. |
198 | if v == 1: |
199 | psprint(ret, "gsave") |
200 | psprint(ret, "%g %g translate" % \ |
201 | ((x + 0.5) * gridpitch, (h - y - 0.5) * gridpitch)) |
202 | psprint(ret, "%g dup scale" % (float(gridpitch) / 2)) |
203 | psprint(ret, "newpath %g %g moveto 0 -%g rlineto" % \ |
204 | (squaresize, squaresize, squaresize * 2)) |
205 | psprint(ret, "-%g 0 rlineto 0 %g rlineto closepath fill" % \ |
206 | (squaresize * 2, squaresize * 2)) |
207 | psprint(ret, "grestore") |
c3ea5dc7 |
208 | return ret.coords, ret.s |
209 | |
d91e1fc9 |
210 | def pattern_format(s): |
211 | ret = Holder() |
212 | ret.s = "" |
213 | # Parse the game ID. |
214 | params, seed = string.split(s, ":") |
215 | w, h = map(string.atoi, string.split(params, "x")) |
216 | rowdata = map(lambda s: string.split(s, "."), string.split(seed, "/")) |
217 | assert len(rowdata) == w+h |
218 | # I'm going to arbitrarily choose to use 7pt text for the |
219 | # numbers, and a 14pt grid pitch. |
220 | textht = 7 |
221 | gridpitch = 14 |
222 | gutter = 8 # between the numbers and the grid |
223 | # Find the maximum number of numbers in each dimension, to |
224 | # determine the border size required. |
225 | xborder = reduce(max, map(len, rowdata[w:])) |
226 | yborder = reduce(max, map(len, rowdata[:w])) |
227 | # Set up coordinate system. I'm going to put the origin at the |
228 | # _top left_ of the grid, so that both sets of numbers get |
229 | # drawn the same way. |
230 | pw = (w + xborder) * gridpitch + gutter |
231 | ph = (h + yborder) * gridpitch + gutter |
232 | ret.coords = (xborder * gridpitch + gutter, w * gridpitch, \ |
233 | yborder * gridpitch + gutter, h * gridpitch) |
234 | # Draw the internal grid lines. Every fifth one is thicker, as |
235 | # a visual aid. |
236 | psprint(ret, "newpath 0.1 setlinewidth") |
237 | for x in xrange(1,w): |
238 | if x % 5 != 0: |
239 | psprint(ret, "%g 0 moveto 0 %g rlineto" % (x * gridpitch, -h * gridpitch)) |
240 | for y in xrange(1,h): |
241 | if y % 5 != 0: |
242 | psprint(ret, "0 %g moveto %g 0 rlineto" % (-y * gridpitch, w * gridpitch)) |
243 | psprint(ret, "stroke") |
244 | psprint(ret, "newpath 0.75 setlinewidth") |
245 | for x in xrange(5,w,5): |
246 | psprint(ret, "%g 0 moveto 0 %g rlineto" % (x * gridpitch, -h * gridpitch)) |
247 | for y in xrange(5,h,5): |
248 | psprint(ret, "0 %g moveto %g 0 rlineto" % (-y * gridpitch, w * gridpitch)) |
249 | psprint(ret, "stroke") |
250 | # Draw round the grid exterior. |
251 | psprint(ret, "newpath 1.5 setlinewidth") |
252 | psprint(ret, "0 0 moveto 0 %g rlineto %g 0 rlineto 0 %g rlineto" % \ |
253 | (-h * gridpitch, w * gridpitch, h * gridpitch)) |
254 | psprint(ret, "closepath stroke") |
255 | # And draw the numbers. |
256 | psprint(ret, "/Helvetica findfont %g scalefont setfont" % textht) |
257 | for i in range(w+h): |
258 | ns = rowdata[i] |
259 | if i < w: |
260 | xo = (i + 0.5) * gridpitch |
261 | yo = (gutter + 0.5 * gridpitch) |
262 | else: |
263 | xo = -(gutter + 0.5 * gridpitch) |
264 | yo = ((i-w) + 0.5) * -gridpitch |
265 | for j in range(len(ns)-1, -1, -1): |
266 | psprint(ret, "%g %g (%s) ctshow" % (xo, yo, ns[j])) |
267 | if i < w: |
268 | yo = yo + gridpitch |
269 | else: |
270 | xo = xo - gridpitch |
271 | return ret.coords, ret.s |
272 | |
273 | def solo_format(s): |
274 | ret = Holder() |
275 | ret.s = "" |
276 | # Parse the game ID. |
277 | params, seed = string.split(s, ":") |
278 | c, r = map(string.atoi, string.split(params, "x")) |
279 | cr = c*r |
280 | grid = [] |
281 | while len(seed) > 0: |
282 | if seed[0] in '_'+string.lowercase: |
283 | if seed[0] in string.lowercase: |
284 | grid.extend([-1] * (ord(seed[0]) - ord('a') + 1)) |
285 | seed = seed[1:] |
286 | elif seed[0] in string.digits: |
287 | ns = "" |
288 | while len(seed) > 0 and seed[0] in string.digits: |
289 | ns = ns + seed[0] |
290 | seed = seed[1:] |
291 | grid.append(string.atoi(ns)) |
292 | assert cr * cr == len(grid) |
293 | # I'm going to arbitrarily choose to use 9pt text for the |
294 | # numbers, and a 16pt grid pitch. |
295 | textht = 9 |
296 | gridpitch = 16 |
297 | # Set up coordinate system. |
298 | pw = ph = gridpitch * cr |
299 | ret.coords = (pw/2, pw/2, ph/2, ph/2) |
300 | psprint(ret, "%g %g translate" % (-ret.coords[0], -ret.coords[2])) |
301 | # Draw the thin internal grid lines. |
302 | psprint(ret, "newpath 0.1 setlinewidth") |
303 | for x in xrange(1,cr): |
304 | if x % r != 0: |
305 | psprint(ret, "%g 0 moveto 0 %g rlineto" % (x * gridpitch, cr * gridpitch)) |
306 | for y in xrange(1,cr): |
307 | if y % c != 0: |
308 | psprint(ret, "0 %g moveto %g 0 rlineto" % (y * gridpitch, cr * gridpitch)) |
309 | psprint(ret, "stroke") |
310 | # Draw the thicker internal grid lines. |
311 | psprint(ret, "newpath 1 setlinewidth") |
312 | for x in xrange(r,cr,r): |
313 | psprint(ret, "%g 0 moveto 0 %g rlineto" % (x * gridpitch, cr * gridpitch)) |
314 | for y in xrange(c,cr,c): |
315 | psprint(ret, "0 %g moveto %g 0 rlineto" % (y * gridpitch, cr * gridpitch)) |
316 | psprint(ret, "stroke") |
317 | # Draw round the grid exterior, thicker still. |
318 | psprint(ret, "newpath 1.5 setlinewidth") |
319 | psprint(ret, "0 0 moveto 0 %g rlineto %g 0 rlineto 0 %g rlineto" % \ |
320 | (cr * gridpitch, cr * gridpitch, -cr * gridpitch)) |
321 | psprint(ret, "closepath stroke") |
322 | # And draw the numbers. |
323 | psprint(ret, "/Helvetica findfont %g scalefont setfont" % textht) |
324 | for y in xrange(cr): |
325 | for x in xrange(cr): |
326 | n = grid[y*cr+x] |
327 | if n > 0: |
328 | if n > 9: |
329 | s = chr(ord('a') + n - 10) |
330 | else: |
331 | s = chr(ord('0') + n) |
332 | psprint(ret, "%g %g (%s) ctshow" % \ |
333 | ((x+0.5)*gridpitch, (cr-y-0.5)*gridpitch, s)) |
334 | return ret.coords, ret.s |
335 | |
b71ab32e |
336 | def dominosa_format(s): |
337 | ret = Holder() |
338 | ret.s = "" |
339 | params, seed = string.split(s, ":") |
340 | n = string.atoi(params) |
341 | w = n+2 |
342 | h = n+1 |
343 | grid = [] |
344 | while len(seed) > 0: |
345 | if seed[0] == '[': # XXX |
346 | d, seed = string.split(seed[1:], "]") |
347 | grid.append(string.atoi(d)) |
348 | else: |
349 | assert seed[0] in string.digits |
350 | grid.append(string.atoi(seed[0:1])) |
351 | seed = seed[1:] |
352 | assert w*h == len(grid) |
353 | # I'm going to arbitrarily choose to use 9pt text for the |
354 | # numbers, and a 16pt grid pitch. |
355 | textht = 9 |
356 | gridpitch = 16 |
357 | pw = gridpitch * w |
358 | ph = gridpitch * h |
359 | psprint(ret, "/Helvetica findfont %g scalefont setfont" % textht) |
360 | ret.coords = (pw/2, pw/2, ph/2, ph/2) |
361 | psprint(ret, "%g %g translate" % (-ret.coords[0], -ret.coords[2])) |
362 | for y in xrange(h): |
363 | for x in xrange(w): |
364 | psprint(ret, "%g %g (%d) ctshow" % \ |
365 | ((x+0.5)*gridpitch, (h-y-0.5)*gridpitch, grid[y*w+x])) |
366 | return ret.coords, ret.s |
367 | |
f1010613 |
368 | def slant_format(s): |
369 | # Parse the game ID. |
370 | ret = Holder() |
371 | ret.s = "" |
372 | params, seed = string.split(s, ":") |
373 | w, h = map(string.atoi, string.split(params, "x")) |
374 | W = w+1 |
375 | H = h+1 |
376 | grid = [] |
377 | while len(seed) > 0: |
378 | if seed[0] in string.lowercase: |
379 | grid.extend([-1] * (ord(seed[0]) - ord('a') + 1)) |
380 | seed = seed[1:] |
381 | elif seed[0] in "01234": |
382 | grid.append(string.atoi(seed[0])) |
383 | seed = seed[1:] |
384 | assert W * H == len(grid) |
385 | # I'm going to arbitrarily choose to use 7pt text for the |
386 | # numbers, and a 14pt grid pitch. |
387 | textht = 7 |
388 | gridpitch = 14 |
389 | radius = textht * 2.0 / 3.0 |
390 | # Set up coordinate system. |
391 | pw = gridpitch * w |
392 | ph = gridpitch * h |
393 | ret.coords = (pw/2, pw/2, ph/2, ph/2) |
394 | psprint(ret, "%g %g translate" % (-ret.coords[0], -ret.coords[2])) |
395 | # Draw round the grid exterior, thickly. |
396 | psprint(ret, "newpath 1 setlinewidth") |
397 | psprint(ret, "0 0 moveto 0 %g rlineto %g 0 rlineto 0 %g rlineto" % \ |
398 | (h * gridpitch, w * gridpitch, -h * gridpitch)) |
399 | psprint(ret, "closepath stroke") |
400 | # Draw the internal grid lines, _very_ thin (the player will |
401 | # need to draw over them visibly). |
402 | psprint(ret, "newpath 0.01 setlinewidth") |
403 | for x in xrange(1,w): |
404 | psprint(ret, "%g 0 moveto 0 %g rlineto" % (x * gridpitch, h * gridpitch)) |
405 | for y in xrange(1,h): |
406 | psprint(ret, "0 %g moveto %g 0 rlineto" % (y * gridpitch, w * gridpitch)) |
407 | psprint(ret, "stroke") |
408 | # And draw the numbers. |
409 | psprint(ret, "/Helvetica findfont %g scalefont setfont" % textht) |
410 | for y in xrange(H): |
411 | for x in xrange(W): |
412 | n = grid[y*W+x] |
413 | if n >= 0: |
414 | psprint(ret, "newpath %g %g %g 0 360 arc" % \ |
415 | ((x)*gridpitch, (h-y)*gridpitch, radius), |
416 | "gsave 1 setgray fill grestore stroke") |
417 | psprint(ret, "%g %g (%d) ctshow" % \ |
418 | ((x)*gridpitch, (h-y)*gridpitch, n)) |
419 | return ret.coords, ret.s |
420 | |
e3478a4b |
421 | def lightup_format(s): |
422 | # Parse the game ID. |
423 | ret = Holder() |
424 | ret.s = "" |
425 | params, seed = string.split(s, ":") |
426 | w, h = map(string.atoi, string.split(params, "x")) |
427 | grid = [] |
428 | while len(seed) > 0: |
429 | if seed[0] in string.lowercase: |
430 | grid.extend([-2] * (ord(seed[0]) - ord('a') + 1)) |
431 | seed = seed[1:] |
432 | elif seed[0] == "B": |
433 | grid.append(-1) |
434 | seed = seed[1:] |
435 | elif seed[0] in "01234": |
436 | grid.append(string.atoi(seed[0])) |
437 | seed = seed[1:] |
438 | assert w * h == len(grid) |
439 | # I'm going to arbitrarily choose to use 9pt text for the |
440 | # numbers, and a 14pt grid pitch. |
441 | textht = 10 |
442 | gridpitch = 14 |
443 | # Set up coordinate system. |
444 | pw = gridpitch * w |
445 | ph = gridpitch * h |
446 | ret.coords = (pw/2, pw/2, ph/2, ph/2) |
447 | psprint(ret, "%g %g translate" % (-ret.coords[0], -ret.coords[2])) |
448 | # Draw round the grid exterior, thickly. |
449 | psprint(ret, "newpath 1 setlinewidth") |
450 | psprint(ret, "0 0 moveto 0 %g rlineto %g 0 rlineto 0 %g rlineto" % \ |
451 | (h * gridpitch, w * gridpitch, -h * gridpitch)) |
452 | psprint(ret, "closepath stroke") |
453 | # Draw the internal grid lines. |
454 | psprint(ret, "newpath 0.02 setlinewidth") |
455 | for x in xrange(1,w): |
456 | psprint(ret, "%g 0 moveto 0 %g rlineto" % (x * gridpitch, h * gridpitch)) |
457 | for y in xrange(1,h): |
458 | psprint(ret, "0 %g moveto %g 0 rlineto" % (y * gridpitch, w * gridpitch)) |
459 | psprint(ret, "stroke") |
460 | # And draw the black squares and numbers. |
461 | psprint(ret, "/Helvetica-Bold findfont %g scalefont setfont" % textht) |
462 | for y in xrange(h): |
463 | for x in xrange(w): |
464 | n = grid[y*w+x] |
465 | if n >= -1: |
466 | psprint(ret, ("newpath %g %g moveto 0 %g rlineto " + |
467 | "%g 0 rlineto 0 %g rlineto closepath fill") % \ |
468 | ((x)*gridpitch, (h-1-y)*gridpitch, gridpitch, gridpitch, \ |
469 | -gridpitch)) |
470 | if n >= 0: |
471 | psprint(ret, "gsave 1 setgray %g %g (%d) ctshow grestore" % \ |
472 | ((x+0.5)*gridpitch, (h-y-0.5)*gridpitch, n)) |
473 | return ret.coords, ret.s |
474 | |
d91e1fc9 |
475 | formatters = { |
c3ea5dc7 |
476 | "net": net_format, |
d91e1fc9 |
477 | "rect": rect_format, |
478 | "rectangles": rect_format, |
479 | "pattern": pattern_format, |
b71ab32e |
480 | "solo": solo_format, |
f1010613 |
481 | "dominosa": dominosa_format, |
e3478a4b |
482 | "slant": slant_format, |
483 | "lightup": lightup_format |
d91e1fc9 |
484 | } |
485 | |
486 | if len(sys.argv) < 3: |
487 | sys.stderr.write("print.py: expected two arguments (game and format)\n") |
488 | sys.exit(1) |
489 | |
490 | formatter = formatters.get(sys.argv[1], None) |
491 | if formatter == None: |
492 | sys.stderr.write("print.py: unrecognised game name `%s'\n" % sys.argv[1]) |
493 | sys.exit(1) |
494 | |
495 | try: |
496 | format = map(string.atoi, string.split(sys.argv[2], "x")) |
497 | except ValueError, e: |
498 | format = [] |
499 | if len(format) != 2: |
500 | sys.stderr.write("print.py: expected format such as `2x3' as second" \ |
501 | + " argument\n") |
502 | sys.exit(1) |
503 | |
504 | xx, yy = format |
505 | ppp = xx * yy # puzzles per page |
506 | |
507 | ids = [] |
508 | while 1: |
509 | s = sys.stdin.readline() |
510 | if s == "": break |
511 | if s[-1:] == "\n": s = s[:-1] |
512 | ids.append(s) |
513 | |
514 | pages = int((len(ids) + ppp - 1) / ppp) |
515 | |
516 | # Output initial DSC stuff. |
517 | print "%!PS-Adobe-3.0" |
518 | print "%%Creator: print.py from Simon Tatham's Puzzle Collection" |
519 | print "%%DocumentData: Clean7Bit" |
520 | print "%%LanguageLevel: 1" |
521 | print "%%Pages:", pages |
522 | print "%%DocumentNeededResources:" |
523 | print "%%+ font Helvetica" |
524 | print "%%DocumentSuppliedResources: procset Puzzles 0 0" |
525 | print "%%EndComments" |
526 | print "%%BeginProlog" |
527 | print "%%BeginResource: procset Puzzles 0 0" |
528 | print "/ctshow {" |
529 | print " 3 1 roll" |
530 | print " newpath 0 0 moveto (X) true charpath flattenpath pathbbox" |
531 | print " 3 -1 roll add 2 div 3 1 roll pop pop sub moveto" |
532 | print " dup stringwidth pop 0.5 mul neg 0 rmoveto show" |
533 | print "} bind def" |
534 | print "%%EndResource" |
535 | print "%%EndProlog" |
536 | print "%%BeginSetup" |
537 | print "%%IncludeResource: font Helvetica" |
538 | print "%%EndSetup" |
539 | |
540 | # Now do each page. |
541 | puzzle_index = 0; |
542 | |
543 | for i in xrange(1, pages+1): |
544 | print "%%Page:", i, i |
545 | print "save" |
546 | |
547 | # Do the drawing for each puzzle, giving a set of PS fragments |
548 | # and bounding boxes. |
549 | fragments = [['' for i in xrange(xx)] for i in xrange(yy)] |
550 | lrbound = [(0,0) for i in xrange(xx)] |
551 | tbbound = [(0,0) for i in xrange(yy)] |
552 | |
553 | for y in xrange(yy): |
554 | for x in xrange(xx): |
555 | if puzzle_index >= len(ids): |
556 | break |
557 | coords, frag = formatter(ids[puzzle_index]) |
558 | fragments[y][x] = frag |
559 | lb, rb = lrbound[x] |
560 | lrbound[x] = (max(lb, coords[0]), max(rb, coords[1])) |
561 | tb, bb = tbbound[y] |
562 | tbbound[y] = (max(tb, coords[2]), max(bb, coords[3])) |
563 | puzzle_index = puzzle_index + 1 |
564 | |
565 | # Now we know the sizes of everything, do the drawing in such a |
566 | # way that we provide equal gutter space at the page edges and |
567 | # between puzzle rows/columns. |
568 | for y in xrange(yy): |
569 | for x in xrange(xx): |
570 | if len(fragments[y][x]) > 0: |
571 | print "gsave" |
572 | print "clippath flattenpath pathbbox pop pop translate" |
573 | print "clippath flattenpath pathbbox 4 2 roll pop pop" |
574 | # Compute the total height of all puzzles, which |
575 | # we'll use it to work out the amount of gutter |
576 | # space below this puzzle. |
577 | htotal = reduce(lambda a,b:a+b, map(lambda (a,b):a+b, tbbound), 0) |
578 | # Now compute the total height of all puzzles |
579 | # _below_ this one, plus the height-below-origin of |
580 | # this one. |
581 | hbelow = reduce(lambda a,b:a+b, map(lambda (a,b):a+b, tbbound[y+1:]), 0) |
582 | hbelow = hbelow + tbbound[y][1] |
583 | print "%g sub %d mul %d div %g add exch" % (htotal, yy-y, yy+1, hbelow) |
584 | # Now do all the same computations for width, |
585 | # except we need the total width of everything |
586 | # _before_ this one since the coordinates work the |
587 | # other way round. |
588 | wtotal = reduce(lambda a,b:a+b, map(lambda (a,b):a+b, lrbound), 0) |
589 | # Now compute the total height of all puzzles |
590 | # _below_ this one, plus the height-below-origin of |
591 | # this one. |
592 | wleft = reduce(lambda a,b:a+b, map(lambda (a,b):a+b, lrbound[:x]), 0) |
593 | wleft = wleft + lrbound[x][0] |
594 | print "%g sub %d mul %d div %g add exch" % (wtotal, x+1, xx+1, wleft) |
595 | print "translate" |
596 | sys.stdout.write(fragments[y][x]) |
597 | print "grestore" |
598 | |
599 | print "restore showpage" |
600 | |
601 | print "%%EOF" |