| 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 |
| 9 | # pencil and paper: Rectangles, Pattern, Solo, Net. |
| 10 | |
| 11 | # Command-line syntax is |
| 12 | # |
| 13 | # print.py <game-name> <format> |
| 14 | # |
| 15 | # <game-name> is one of `rect', `rectangles', `pattern', `solo', |
| 16 | # `net', `dominosa'. <format> is two numbers separated by an x: |
| 17 | # `2x3', for example, means two columns by three rows. |
| 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 | |
| 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 |
| 126 | squaresize = 0.25 |
| 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. |
| 189 | if v == 1: |
| 190 | # Endpoints have a little empty square at the centre. |
| 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. |
| 196 | psprint(ret, "grestore") |
| 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") |
| 208 | return ret.coords, ret.s |
| 209 | |
| 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 | |
| 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 | |
| 368 | formatters = { |
| 369 | "net": net_format, |
| 370 | "rect": rect_format, |
| 371 | "rectangles": rect_format, |
| 372 | "pattern": pattern_format, |
| 373 | "solo": solo_format, |
| 374 | "dominosa": dominosa_format |
| 375 | } |
| 376 | |
| 377 | if len(sys.argv) < 3: |
| 378 | sys.stderr.write("print.py: expected two arguments (game and format)\n") |
| 379 | sys.exit(1) |
| 380 | |
| 381 | formatter = formatters.get(sys.argv[1], None) |
| 382 | if formatter == None: |
| 383 | sys.stderr.write("print.py: unrecognised game name `%s'\n" % sys.argv[1]) |
| 384 | sys.exit(1) |
| 385 | |
| 386 | try: |
| 387 | format = map(string.atoi, string.split(sys.argv[2], "x")) |
| 388 | except ValueError, e: |
| 389 | format = [] |
| 390 | if len(format) != 2: |
| 391 | sys.stderr.write("print.py: expected format such as `2x3' as second" \ |
| 392 | + " argument\n") |
| 393 | sys.exit(1) |
| 394 | |
| 395 | xx, yy = format |
| 396 | ppp = xx * yy # puzzles per page |
| 397 | |
| 398 | ids = [] |
| 399 | while 1: |
| 400 | s = sys.stdin.readline() |
| 401 | if s == "": break |
| 402 | if s[-1:] == "\n": s = s[:-1] |
| 403 | ids.append(s) |
| 404 | |
| 405 | pages = int((len(ids) + ppp - 1) / ppp) |
| 406 | |
| 407 | # Output initial DSC stuff. |
| 408 | print "%!PS-Adobe-3.0" |
| 409 | print "%%Creator: print.py from Simon Tatham's Puzzle Collection" |
| 410 | print "%%DocumentData: Clean7Bit" |
| 411 | print "%%LanguageLevel: 1" |
| 412 | print "%%Pages:", pages |
| 413 | print "%%DocumentNeededResources:" |
| 414 | print "%%+ font Helvetica" |
| 415 | print "%%DocumentSuppliedResources: procset Puzzles 0 0" |
| 416 | print "%%EndComments" |
| 417 | print "%%BeginProlog" |
| 418 | print "%%BeginResource: procset Puzzles 0 0" |
| 419 | print "/ctshow {" |
| 420 | print " 3 1 roll" |
| 421 | print " newpath 0 0 moveto (X) true charpath flattenpath pathbbox" |
| 422 | print " 3 -1 roll add 2 div 3 1 roll pop pop sub moveto" |
| 423 | print " dup stringwidth pop 0.5 mul neg 0 rmoveto show" |
| 424 | print "} bind def" |
| 425 | print "%%EndResource" |
| 426 | print "%%EndProlog" |
| 427 | print "%%BeginSetup" |
| 428 | print "%%IncludeResource: font Helvetica" |
| 429 | print "%%EndSetup" |
| 430 | |
| 431 | # Now do each page. |
| 432 | puzzle_index = 0; |
| 433 | |
| 434 | for i in xrange(1, pages+1): |
| 435 | print "%%Page:", i, i |
| 436 | print "save" |
| 437 | |
| 438 | # Do the drawing for each puzzle, giving a set of PS fragments |
| 439 | # and bounding boxes. |
| 440 | fragments = [['' for i in xrange(xx)] for i in xrange(yy)] |
| 441 | lrbound = [(0,0) for i in xrange(xx)] |
| 442 | tbbound = [(0,0) for i in xrange(yy)] |
| 443 | |
| 444 | for y in xrange(yy): |
| 445 | for x in xrange(xx): |
| 446 | if puzzle_index >= len(ids): |
| 447 | break |
| 448 | coords, frag = formatter(ids[puzzle_index]) |
| 449 | fragments[y][x] = frag |
| 450 | lb, rb = lrbound[x] |
| 451 | lrbound[x] = (max(lb, coords[0]), max(rb, coords[1])) |
| 452 | tb, bb = tbbound[y] |
| 453 | tbbound[y] = (max(tb, coords[2]), max(bb, coords[3])) |
| 454 | puzzle_index = puzzle_index + 1 |
| 455 | |
| 456 | # Now we know the sizes of everything, do the drawing in such a |
| 457 | # way that we provide equal gutter space at the page edges and |
| 458 | # between puzzle rows/columns. |
| 459 | for y in xrange(yy): |
| 460 | for x in xrange(xx): |
| 461 | if len(fragments[y][x]) > 0: |
| 462 | print "gsave" |
| 463 | print "clippath flattenpath pathbbox pop pop translate" |
| 464 | print "clippath flattenpath pathbbox 4 2 roll pop pop" |
| 465 | # Compute the total height of all puzzles, which |
| 466 | # we'll use it to work out the amount of gutter |
| 467 | # space below this puzzle. |
| 468 | htotal = reduce(lambda a,b:a+b, map(lambda (a,b):a+b, tbbound), 0) |
| 469 | # Now compute the total height of all puzzles |
| 470 | # _below_ this one, plus the height-below-origin of |
| 471 | # this one. |
| 472 | hbelow = reduce(lambda a,b:a+b, map(lambda (a,b):a+b, tbbound[y+1:]), 0) |
| 473 | hbelow = hbelow + tbbound[y][1] |
| 474 | print "%g sub %d mul %d div %g add exch" % (htotal, yy-y, yy+1, hbelow) |
| 475 | # Now do all the same computations for width, |
| 476 | # except we need the total width of everything |
| 477 | # _before_ this one since the coordinates work the |
| 478 | # other way round. |
| 479 | wtotal = reduce(lambda a,b:a+b, map(lambda (a,b):a+b, lrbound), 0) |
| 480 | # Now compute the total height of all puzzles |
| 481 | # _below_ this one, plus the height-below-origin of |
| 482 | # this one. |
| 483 | wleft = reduce(lambda a,b:a+b, map(lambda (a,b):a+b, lrbound[:x]), 0) |
| 484 | wleft = wleft + lrbound[x][0] |
| 485 | print "%g sub %d mul %d div %g add exch" % (wtotal, x+1, xx+1, wleft) |
| 486 | print "translate" |
| 487 | sys.stdout.write(fragments[y][x]) |
| 488 | print "grestore" |
| 489 | |
| 490 | print "restore showpage" |
| 491 | |
| 492 | print "%%EOF" |