| 1 | /* |
| 2 | * printing.c: Cross-platform printing manager. Handles document |
| 3 | * setup and layout. |
| 4 | */ |
| 5 | |
| 6 | #include "puzzles.h" |
| 7 | |
| 8 | struct puzzle { |
| 9 | const game *game; |
| 10 | game_params *par; |
| 11 | game_state *st; |
| 12 | game_state *st2; |
| 13 | }; |
| 14 | |
| 15 | struct document { |
| 16 | int pw, ph; |
| 17 | int npuzzles; |
| 18 | struct puzzle *puzzles; |
| 19 | int puzzlesize; |
| 20 | int got_solns; |
| 21 | float *colwid, *rowht; |
| 22 | float userscale; |
| 23 | }; |
| 24 | |
| 25 | /* |
| 26 | * Create a new print document. pw and ph are the layout |
| 27 | * parameters: they state how many puzzles will be printed across |
| 28 | * the page, and down the page. |
| 29 | */ |
| 30 | document *document_new(int pw, int ph, float userscale) |
| 31 | { |
| 32 | document *doc = snew(document); |
| 33 | |
| 34 | doc->pw = pw; |
| 35 | doc->ph = ph; |
| 36 | doc->puzzles = NULL; |
| 37 | doc->puzzlesize = doc->npuzzles = 0; |
| 38 | doc->got_solns = FALSE; |
| 39 | |
| 40 | doc->colwid = snewn(pw, float); |
| 41 | doc->rowht = snewn(ph, float); |
| 42 | |
| 43 | doc->userscale = userscale; |
| 44 | |
| 45 | return doc; |
| 46 | } |
| 47 | |
| 48 | /* |
| 49 | * Free a document structure, whether it's been printed or not. |
| 50 | */ |
| 51 | void document_free(document *doc) |
| 52 | { |
| 53 | int i; |
| 54 | |
| 55 | for (i = 0; i < doc->npuzzles; i++) { |
| 56 | doc->puzzles[i].game->free_params(doc->puzzles[i].par); |
| 57 | doc->puzzles[i].game->free_game(doc->puzzles[i].st); |
| 58 | if (doc->puzzles[i].st2) |
| 59 | doc->puzzles[i].game->free_game(doc->puzzles[i].st2); |
| 60 | } |
| 61 | |
| 62 | sfree(doc->colwid); |
| 63 | sfree(doc->rowht); |
| 64 | |
| 65 | sfree(doc->puzzles); |
| 66 | sfree(doc); |
| 67 | } |
| 68 | |
| 69 | /* |
| 70 | * Called from midend.c to add a puzzle to be printed. Provides a |
| 71 | * game_params (for initial layout computation), a game_state, and |
| 72 | * optionally a second game_state to be printed in parallel on |
| 73 | * another sheet (typically the solution to the first game_state). |
| 74 | */ |
| 75 | void document_add_puzzle(document *doc, const game *game, game_params *par, |
| 76 | game_state *st, game_state *st2) |
| 77 | { |
| 78 | if (doc->npuzzles >= doc->puzzlesize) { |
| 79 | doc->puzzlesize += 32; |
| 80 | doc->puzzles = sresize(doc->puzzles, doc->puzzlesize, struct puzzle); |
| 81 | } |
| 82 | doc->puzzles[doc->npuzzles].game = game; |
| 83 | doc->puzzles[doc->npuzzles].par = par; |
| 84 | doc->puzzles[doc->npuzzles].st = st; |
| 85 | doc->puzzles[doc->npuzzles].st2 = st2; |
| 86 | doc->npuzzles++; |
| 87 | if (st2) |
| 88 | doc->got_solns = TRUE; |
| 89 | } |
| 90 | |
| 91 | static void get_puzzle_size(document *doc, struct puzzle *pz, |
| 92 | float *w, float *h, float *scale) |
| 93 | { |
| 94 | float ww, hh, ourscale; |
| 95 | |
| 96 | /* Get the preferred size of the game, in mm. */ |
| 97 | pz->game->print_size(pz->par, &ww, &hh); |
| 98 | |
| 99 | /* Adjust for user-supplied scale factor. */ |
| 100 | ourscale = doc->userscale; |
| 101 | |
| 102 | /* |
| 103 | * FIXME: scale it down here if it's too big for the page size. |
| 104 | * Rather than do complicated things involving scaling all |
| 105 | * columns down in proportion, the simplest approach seems to |
| 106 | * me to be to scale down until the game fits within one evenly |
| 107 | * divided cell of the page (i.e. width/pw by height/ph). |
| 108 | * |
| 109 | * In order to do this step we need the page size available. |
| 110 | */ |
| 111 | |
| 112 | *scale = ourscale; |
| 113 | *w = ww * ourscale; |
| 114 | *h = hh * ourscale; |
| 115 | } |
| 116 | |
| 117 | /* |
| 118 | * Having accumulated a load of puzzles, actually do the printing. |
| 119 | */ |
| 120 | void document_print(document *doc, drawing *dr) |
| 121 | { |
| 122 | int ppp; /* puzzles per page */ |
| 123 | int pages, passes; |
| 124 | int page, pass; |
| 125 | int pageno; |
| 126 | |
| 127 | ppp = doc->pw * doc->ph; |
| 128 | pages = (doc->npuzzles + ppp - 1) / ppp; |
| 129 | passes = (doc->got_solns ? 2 : 1); |
| 130 | |
| 131 | print_begin_doc(dr, pages * passes); |
| 132 | |
| 133 | pageno = 1; |
| 134 | for (pass = 0; pass < passes; pass++) { |
| 135 | for (page = 0; page < pages; page++) { |
| 136 | int i, n, offset; |
| 137 | float colsum, rowsum; |
| 138 | |
| 139 | print_begin_page(dr, pageno); |
| 140 | |
| 141 | offset = page * ppp; |
| 142 | n = min(ppp, doc->npuzzles - offset); |
| 143 | |
| 144 | for (i = 0; i < doc->pw; i++) |
| 145 | doc->colwid[i] = 0; |
| 146 | for (i = 0; i < doc->ph; i++) |
| 147 | doc->rowht[i] = 0; |
| 148 | |
| 149 | /* |
| 150 | * Lay the page out by computing all the puzzle sizes. |
| 151 | */ |
| 152 | for (i = 0; i < n; i++) { |
| 153 | struct puzzle *pz = doc->puzzles + offset + i; |
| 154 | int x = i % doc->pw, y = i / doc->pw; |
| 155 | float w, h, scale; |
| 156 | |
| 157 | get_puzzle_size(doc, pz, &w, &h, &scale); |
| 158 | |
| 159 | /* Update the maximum width/height of this column. */ |
| 160 | doc->colwid[x] = max(doc->colwid[x], w); |
| 161 | doc->rowht[y] = max(doc->rowht[y], h); |
| 162 | } |
| 163 | |
| 164 | /* |
| 165 | * Add up the maximum column/row widths to get the |
| 166 | * total amount of space used up by puzzles on the |
| 167 | * page. We will use this to compute gutter widths. |
| 168 | */ |
| 169 | colsum = 0.0; |
| 170 | for (i = 0; i < doc->pw; i++) |
| 171 | colsum += doc->colwid[i]; |
| 172 | rowsum = 0.0; |
| 173 | for (i = 0; i < doc->ph; i++) |
| 174 | rowsum += doc->rowht[i]; |
| 175 | |
| 176 | /* |
| 177 | * Now do the printing. |
| 178 | */ |
| 179 | for (i = 0; i < n; i++) { |
| 180 | struct puzzle *pz = doc->puzzles + offset + i; |
| 181 | int x = i % doc->pw, y = i / doc->pw, j; |
| 182 | float w, h, scale, xm, xc, ym, yc; |
| 183 | int pixw, pixh, tilesize; |
| 184 | |
| 185 | if (pass == 1 && !pz->st2) |
| 186 | continue; /* nothing to do */ |
| 187 | |
| 188 | /* |
| 189 | * The total amount of gutter space is the page |
| 190 | * width minus colsum. This is divided into pw+1 |
| 191 | * gutters, so the amount of horizontal gutter |
| 192 | * space appearing to the left of this puzzle |
| 193 | * column is |
| 194 | * |
| 195 | * (width-colsum) * (x+1)/(pw+1) |
| 196 | * = width * (x+1)/(pw+1) - (colsum * (x+1)/(pw+1)) |
| 197 | */ |
| 198 | xm = (float)(x+1) / (doc->pw + 1); |
| 199 | xc = -xm * colsum; |
| 200 | /* And similarly for y. */ |
| 201 | ym = (float)(y+1) / (doc->ph + 1); |
| 202 | yc = -ym * rowsum; |
| 203 | |
| 204 | /* |
| 205 | * However, the amount of space to the left of this |
| 206 | * puzzle isn't just gutter space: we must also |
| 207 | * count the widths of all the previous columns. |
| 208 | */ |
| 209 | for (j = 0; j < x; j++) |
| 210 | xc += doc->colwid[j]; |
| 211 | /* And similarly for rows. */ |
| 212 | for (j = 0; j < y; j++) |
| 213 | yc += doc->rowht[j]; |
| 214 | |
| 215 | /* |
| 216 | * Now we adjust for this _specific_ puzzle, which |
| 217 | * means centring it within the cell we've just |
| 218 | * computed. |
| 219 | */ |
| 220 | get_puzzle_size(doc, pz, &w, &h, &scale); |
| 221 | xc += (doc->colwid[x] - w) / 2; |
| 222 | yc += (doc->rowht[y] - h) / 2; |
| 223 | |
| 224 | /* |
| 225 | * And now we know where and how big we want to |
| 226 | * print the puzzle, just go ahead and do so. For |
| 227 | * the moment I'll pick a standard pixel tile size |
| 228 | * of 512. |
| 229 | * |
| 230 | * (FIXME: would it be better to pick this value |
| 231 | * with reference to the printer resolution? Or |
| 232 | * permit each game to choose its own?) |
| 233 | */ |
| 234 | tilesize = 512; |
| 235 | pz->game->compute_size(pz->par, tilesize, &pixw, &pixh); |
| 236 | print_begin_puzzle(dr, xm, xc, ym, yc, pixw, pixh, w, scale); |
| 237 | pz->game->print(dr, pass == 0 ? pz->st : pz->st2, tilesize); |
| 238 | print_end_puzzle(dr); |
| 239 | } |
| 240 | |
| 241 | print_end_page(dr, pageno); |
| 242 | pageno++; |
| 243 | } |
| 244 | } |
| 245 | |
| 246 | print_end_doc(dr); |
| 247 | } |