New puzzle: `Light Up', by James H.
[sgt/puzzles] / print.py
CommitLineData
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
26import sys
27import string
28import re
29
30class Holder:
31 pass
32
33def 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
41def psprint(h, *a):
42 psvprint(h, a)
43
44def 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 95def 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 210def 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
273def 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 336def 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 368def 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 421def 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 475formatters = {
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
486if len(sys.argv) < 3:
487 sys.stderr.write("print.py: expected two arguments (game and format)\n")
488 sys.exit(1)
489
490formatter = formatters.get(sys.argv[1], None)
491if formatter == None:
492 sys.stderr.write("print.py: unrecognised game name `%s'\n" % sys.argv[1])
493 sys.exit(1)
494
495try:
496 format = map(string.atoi, string.split(sys.argv[2], "x"))
497except ValueError, e:
498 format = []
499if len(format) != 2:
500 sys.stderr.write("print.py: expected format such as `2x3' as second" \
501 + " argument\n")
502 sys.exit(1)
503
504xx, yy = format
505ppp = xx * yy # puzzles per page
506
507ids = []
508while 1:
509 s = sys.stdin.readline()
510 if s == "": break
511 if s[-1:] == "\n": s = s[:-1]
512 ids.append(s)
513
514pages = int((len(ids) + ppp - 1) / ppp)
515
516# Output initial DSC stuff.
517print "%!PS-Adobe-3.0"
518print "%%Creator: print.py from Simon Tatham's Puzzle Collection"
519print "%%DocumentData: Clean7Bit"
520print "%%LanguageLevel: 1"
521print "%%Pages:", pages
522print "%%DocumentNeededResources:"
523print "%%+ font Helvetica"
524print "%%DocumentSuppliedResources: procset Puzzles 0 0"
525print "%%EndComments"
526print "%%BeginProlog"
527print "%%BeginResource: procset Puzzles 0 0"
528print "/ctshow {"
529print " 3 1 roll"
530print " newpath 0 0 moveto (X) true charpath flattenpath pathbbox"
531print " 3 -1 roll add 2 div 3 1 roll pop pop sub moveto"
532print " dup stringwidth pop 0.5 mul neg 0 rmoveto show"
533print "} bind def"
534print "%%EndResource"
535print "%%EndProlog"
536print "%%BeginSetup"
537print "%%IncludeResource: font Helvetica"
538print "%%EndSetup"
539
540# Now do each page.
541puzzle_index = 0;
542
543for 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
601print "%%EOF"