Files
puzzles/printing.c
Simon Tatham bb1ab36108 Keep a set of preferences in the midend.
This commit introduces a serialisation format for the user preferences
stored in game_ui, using the keyword identifiers that get_prefs is
required to write into its list of config_item. As a result, the
serialisation format looks enough like an ordinary config file that a
user could write one by hand.

The preferences for the game backend are kept in serialised form in
me->be_prefs. The typical use of this is to apply it to a just-created
game_ui by calling midend_apply_prefs(), which deserialises the prefs
buffer into a list of config_item and passes it to the backend's
set_prefs function, overwriting the preference fields (but no others)
of the game_ui.

This is duly done when creating a new game, when loading a game from a
save file, and also when printing a puzzle. To make the latter work,
document_add_puzzle now takes a game_ui (and keeps ownership of it
afterwards), and passes that to the backend's compute_size and print
functions.

The backend's own get_prefs and set_prefs functions are wrapped by
midend_get_prefs and midend_set_prefs. This is partly as a convenience
(it deals with optionally constructing a game_ui specially to call the
backend with), but mostly so that there will be a convenient place in
the midend to add standard preferences applying across all puzzles.
No cross-puzzle preferences are provided yet.

There are two external interfaces to all this, and in this commit,
neither one is yet called by any frontend:

A new pair of midend functions is exposed to the front end, called
midend_load_prefs and midend_save_prefs. These have a similar API to
midend_serialise and midend_deserialise, taking a read/write function
pointer and a context. So front ends that can already load/save a game
to a file on disk should find it easy to add a similar set of
functions loading/saving user preferences.

Secondly, a new value CFG_PREFS is added to the enumeration of
configuration dialog types, alongside the ones for the Custom game
type, entering a game description and entering a random seed. This
should make it easy for frontends to offer a Preferences dialog,
because it will operate almost exactly like three dialogs they already
handle.
2023-04-23 13:25:57 +01:00

301 lines
7.8 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_ui *ui;
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_ui(doc->puzzles[i].ui);
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_ui *ui, 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].ui = ui;
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. */
{
game_ui *ui = pz->game->new_ui(pz->st);
pz->game->print_size(pz->par, ui, &ww, &hh);
pz->game->free_ui(ui);
}
/* 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, pz->ui, &pixw, &pixh);
print_begin_puzzle(dr, xm, xc, ym, yc, pixw, pixh, w, scale);
pz->game->print(dr, pass == 0 ? pz->st : pz->st2, pz->ui, 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);
}