Files
puzzles/printing.c
Asher Gordon 79a5378b5a Improve const-correctness in printing API.
Most of the functions in printing.c do not modify their 'document *'
argument, and therefore can declare them as 'const'.
2019-12-30 08:10:34 +00:00

294 lines
7.5 KiB
C

/*
* printing.c: Cross-platform printing manager. Handles document
* setup and layout.
*/
#include <assert.h>
#include "puzzles.h"
struct puzzle {
const game *game;
game_params *par;
game_state *st;
game_state *st2;
};
struct document {
int pw, ph;
int npuzzles;
struct puzzle *puzzles;
int puzzlesize;
bool got_solns;
float *colwid, *rowht;
float userscale;
};
/*
* Create a new print document. pw and ph are the layout
* parameters: they state how many puzzles will be printed across
* the page, and down the page.
*/
document *document_new(int pw, int ph, float userscale)
{
document *doc = snew(document);
doc->pw = pw;
doc->ph = ph;
doc->puzzles = NULL;
doc->puzzlesize = doc->npuzzles = 0;
doc->got_solns = false;
doc->colwid = snewn(pw, float);
doc->rowht = snewn(ph, float);
doc->userscale = userscale;
return doc;
}
/*
* Free a document structure, whether it's been printed or not.
*/
void document_free(document *doc)
{
int i;
for (i = 0; i < doc->npuzzles; i++) {
doc->puzzles[i].game->free_params(doc->puzzles[i].par);
doc->puzzles[i].game->free_game(doc->puzzles[i].st);
if (doc->puzzles[i].st2)
doc->puzzles[i].game->free_game(doc->puzzles[i].st2);
}
sfree(doc->colwid);
sfree(doc->rowht);
sfree(doc->puzzles);
sfree(doc);
}
/*
* Called from midend.c to add a puzzle to be printed. Provides a
* game_params (for initial layout computation), a game_state, and
* optionally a second game_state to be printed in parallel on
* another sheet (typically the solution to the first game_state).
*/
void document_add_puzzle(document *doc, const game *game, game_params *par,
game_state *st, game_state *st2)
{
if (doc->npuzzles >= doc->puzzlesize) {
doc->puzzlesize += 32;
doc->puzzles = sresize(doc->puzzles, doc->puzzlesize, struct puzzle);
}
doc->puzzles[doc->npuzzles].game = game;
doc->puzzles[doc->npuzzles].par = par;
doc->puzzles[doc->npuzzles].st = st;
doc->puzzles[doc->npuzzles].st2 = st2;
doc->npuzzles++;
if (st2)
doc->got_solns = true;
}
static void get_puzzle_size(const document *doc, struct puzzle *pz,
float *w, float *h, float *scale)
{
float ww, hh, ourscale;
/* Get the preferred size of the game, in mm. */
pz->game->print_size(pz->par, &ww, &hh);
/* Adjust for user-supplied scale factor. */
ourscale = doc->userscale;
/*
* FIXME: scale it down here if it's too big for the page size.
* Rather than do complicated things involving scaling all
* columns down in proportion, the simplest approach seems to
* me to be to scale down until the game fits within one evenly
* divided cell of the page (i.e. width/pw by height/ph).
*
* In order to do this step we need the page size available.
*/
*scale = ourscale;
*w = ww * ourscale;
*h = hh * ourscale;
}
/*
* Calculate the the number of pages for a document.
*/
int document_npages(const document *doc)
{
int ppp; /* puzzles per page */
int pages, passes;
ppp = doc->pw * doc->ph;
pages = (doc->npuzzles + ppp - 1) / ppp;
passes = (doc->got_solns ? 2 : 1);
return pages * passes;
}
/*
* Begin a document.
*/
void document_begin(const document *doc, drawing *dr)
{
print_begin_doc(dr, document_npages(doc));
}
/*
* End a document.
*/
void document_end(const document *doc, drawing *dr)
{
print_end_doc(dr);
}
/*
* Print a single page of a document.
*/
void document_print_page(const document *doc, drawing *dr, int page_nr)
{
int ppp; /* puzzles per page */
int pages;
int page, pass;
int pageno;
int i, n, offset;
float colsum, rowsum;
ppp = doc->pw * doc->ph;
pages = (doc->npuzzles + ppp - 1) / ppp;
/* Get the current page, pass, and pageno based on page_nr. */
if (page_nr < pages) {
page = page_nr;
pass = 0;
}
else {
assert(doc->got_solns);
page = page_nr - pages;
pass = 1;
}
pageno = page_nr + 1;
offset = page * ppp;
n = min(ppp, doc->npuzzles - offset);
print_begin_page(dr, pageno);
for (i = 0; i < doc->pw; i++)
doc->colwid[i] = 0;
for (i = 0; i < doc->ph; i++)
doc->rowht[i] = 0;
/*
* Lay the page out by computing all the puzzle sizes.
*/
for (i = 0; i < n; i++) {
struct puzzle *pz = doc->puzzles + offset + i;
int x = i % doc->pw, y = i / doc->pw;
float w, h, scale;
get_puzzle_size(doc, pz, &w, &h, &scale);
/* Update the maximum width/height of this column. */
doc->colwid[x] = max(doc->colwid[x], w);
doc->rowht[y] = max(doc->rowht[y], h);
}
/*
* Add up the maximum column/row widths to get the
* total amount of space used up by puzzles on the
* page. We will use this to compute gutter widths.
*/
colsum = 0.0;
for (i = 0; i < doc->pw; i++)
colsum += doc->colwid[i];
rowsum = 0.0;
for (i = 0; i < doc->ph; i++)
rowsum += doc->rowht[i];
/*
* Now do the printing.
*/
for (i = 0; i < n; i++) {
struct puzzle *pz = doc->puzzles + offset + i;
int x = i % doc->pw, y = i / doc->pw, j;
float w, h, scale, xm, xc, ym, yc;
int pixw, pixh, tilesize;
if (pass == 1 && !pz->st2)
continue; /* nothing to do */
/*
* The total amount of gutter space is the page
* width minus colsum. This is divided into pw+1
* gutters, so the amount of horizontal gutter
* space appearing to the left of this puzzle
* column is
*
* (width-colsum) * (x+1)/(pw+1)
* = width * (x+1)/(pw+1) - (colsum * (x+1)/(pw+1))
*/
xm = (float)(x+1) / (doc->pw + 1);
xc = -xm * colsum;
/* And similarly for y. */
ym = (float)(y+1) / (doc->ph + 1);
yc = -ym * rowsum;
/*
* However, the amount of space to the left of this
* puzzle isn't just gutter space: we must also
* count the widths of all the previous columns.
*/
for (j = 0; j < x; j++)
xc += doc->colwid[j];
/* And similarly for rows. */
for (j = 0; j < y; j++)
yc += doc->rowht[j];
/*
* Now we adjust for this _specific_ puzzle, which
* means centring it within the cell we've just
* computed.
*/
get_puzzle_size(doc, pz, &w, &h, &scale);
xc += (doc->colwid[x] - w) / 2;
yc += (doc->rowht[y] - h) / 2;
/*
* And now we know where and how big we want to
* print the puzzle, just go ahead and do so. For
* the moment I'll pick a standard pixel tile size
* of 512.
*
* (FIXME: would it be better to pick this value
* with reference to the printer resolution? Or
* permit each game to choose its own?)
*/
tilesize = 512;
pz->game->compute_size(pz->par, tilesize, &pixw, &pixh);
print_begin_puzzle(dr, xm, xc, ym, yc, pixw, pixh, w, scale);
pz->game->print(dr, pass == 0 ? pz->st : pz->st2, tilesize);
print_end_puzzle(dr);
}
print_end_page(dr, pageno);
}
/*
* Having accumulated a load of puzzles, actually do the printing.
*/
void document_print(const document *doc, drawing *dr)
{
int page, pages;
pages = document_npages(doc);
print_begin_doc(dr, pages);
for (page = 0; page < pages; page++)
document_print_page(doc, dr, page);
print_end_doc(dr);
}