Previous | Contents | Next

Chapter 3: The drawing API

The back end function redraw() (section 2.8.10) is required to draw the puzzle's graphics on the window's drawing area, or on paper if the puzzle is printable. To do this portably, it is provided with a drawing API allowing it to talk directly to the front end. In this chapter I document that API, both for the benefit of back end authors trying to use it and for front end authors trying to implement it.

The drawing API as seen by the back end is a collection of global functions, each of which takes a pointer to a drawing structure (a ‘drawing object’). These objects are supplied as parameters to the back end's redraw() and print() functions.

In fact these global functions are not implemented directly by the front end; instead, they are implemented centrally in drawing.c and form a small piece of middleware. The drawing API as supplied by the front end is a structure containing a set of function pointers, plus a ‘void *’ handle which is passed to each of those functions. This enables a single front end to switch between multiple implementations of the drawing API if necessary. For example, the Windows API supplies a printing mechanism integrated into the same GDI which deals with drawing in windows, and therefore the same API implementation can handle both drawing and printing; but on Unix, the most common way for applications to print is by producing PostScript output directly, and although it would be possible to write a single (say) draw_rect() function which checked a global flag to decide whether to do GTK drawing operations or output PostScript to a file, it's much nicer to have two separate functions and switch between them as appropriate.

When drawing, the puzzle window is indexed by pixel coordinates, with the top left pixel defined as (0,0) and the bottom right pixel (w-1,h-1), where w and h are the width and height values returned by the back end function compute_size() (section 2.8.4).

When printing, the puzzle's print area is indexed in exactly the same way (with an arbitrary tile size provided by the printing module printing.c), to facilitate sharing of code between the drawing and printing routines. However, when printing, puzzles may no longer assume that the coordinate unit has any relationship to a pixel; the printer's actual resolution might very well not even be known at print time, so the coordinate unit might be smaller or larger than a pixel. Puzzles' print functions should restrict themselves to drawing geometric shapes rather than fiddly pixel manipulation.

Puzzles' redraw functions may assume that the surface they draw on is persistent. It is the responsibility of every front end to preserve the puzzle's window contents in the face of GUI window expose issues and similar. It is not permissible to request that the back end redraw any part of a window that it has already drawn, unless something has actually changed as a result of making moves in the puzzle.

Most front ends accomplish this by having the drawing routines draw on a stored bitmap rather than directly on the window, and copying the bitmap to the window every time a part of the window needs to be redrawn. Therefore, it is vitally important that whenever the back end does any drawing it informs the front end of which parts of the window it has accessed, and hence which parts need repainting. This is done by calling draw_update() (section 3.1.11).

Persistence of old drawing is convenient. However, a puzzle should be very careful about how it updates its drawing area. The problem is that some front ends do anti-aliased drawing: rather than simply choosing between leaving each pixel untouched or painting it a specified colour, an antialiased drawing function will blend the original and new colours in pixels at a figure's boundary according to the proportion of the pixel occupied by the figure (probably modified by some heuristic fudge factors). All of this produces a smoother appearance for curves and diagonal lines.

An unfortunate effect of drawing an anti-aliased figure repeatedly is that the pixels around the figure's boundary come steadily more saturated with ‘ink’ and the boundary appears to ‘spread out’. Worse, redrawing a figure in a different colour won't fully paint over the old boundary pixels, so the end result is a rather ugly smudge.

A good strategy to avoid unpleasant anti-aliasing artifacts is to identify a number of rectangular areas which need to be redrawn, clear them to the background colour, and then redraw their contents from scratch, being careful all the while not to stray beyond the boundaries of the original rectangles. The clip() function (section 3.1.9) comes in very handy here. Games based on a square grid can often do this fairly easily. Other games may need to be somewhat more careful. For example, Loopy's redraw function first identifies portions of the display which need to be updated. Then, if the changes are fairly well localised, it clears and redraws a rectangle containing each changed area. Otherwise, it gives up and redraws the entire grid from scratch.

It is possible to avoid clearing to background and redrawing from scratch if one is very careful about which drawing functions one uses: if a function is documented as not anti-aliasing under some circumstances, you can rely on each pixel in a drawing either being left entirely alone or being set to the requested colour, with no blending being performed.

In the following sections I first discuss the drawing API as seen by the back end, and then the almost identical function-pointer form seen by the front end.

3.1 Drawing API as seen by the back end

This section documents the back-end drawing API, in the form of functions which take a drawing object as an argument.

3.1.1 draw_rect()

void draw_rect(drawing *dr, int x, int y, int w, int h,
               int colour);

Draws a filled rectangle in the puzzle window.

x and y give the coordinates of the top left pixel of the rectangle. w and h give its width and height. Thus, the horizontal extent of the rectangle runs from x to x+w-1 inclusive, and the vertical extent from y to y+h-1 inclusive.

colour is an integer index into the colours array returned by the back end function colours() (section 2.8.6).

There is no separate pixel-plotting function. If you want to plot a single pixel, the approved method is to use draw_rect() with width and height set to 1.

Unlike many of the other drawing functions, this function is guaranteed to be pixel-perfect: the rectangle will be sharply defined and not anti-aliased or anything like that.

This function may be used for both drawing and printing.

3.1.2 draw_rect_outline()

void draw_rect_outline(drawing *dr, int x, int y, int w, int h,
                       int colour);

Draws an outline rectangle in the puzzle window.

x and y give the coordinates of the top left pixel of the rectangle. w and h give its width and height. Thus, the horizontal extent of the rectangle runs from x to x+w-1 inclusive, and the vertical extent from y to y+h-1 inclusive.

colour is an integer index into the colours array returned by the back end function colours() (section 2.8.6).

From a back end perspective, this function may be considered to be part of the drawing API. However, front ends are not required to implement it, since it is actually implemented centrally (in misc.c) as a wrapper on draw_polygon().

This function may be used for both drawing and printing.

3.1.3 draw_line()

void draw_line(drawing *dr, int x1, int y1, int x2, int y2,
               int colour);

Draws a straight line in the puzzle window.

x1 and y1 give the coordinates of one end of the line. x2 and y2 give the coordinates of the other end. The line drawn includes both those points.

colour is an integer index into the colours array returned by the back end function colours() (section 2.8.6).

Some platforms may perform anti-aliasing on this function. Therefore, do not assume that you can erase a line by drawing the same line over it in the background colour; anti-aliasing might lead to perceptible ghost artefacts around the vanished line. Horizontal and vertical lines, however, are pixel-perfect and not anti-aliased.

This function may be used for both drawing and printing.

3.1.4 draw_polygon()

void draw_polygon(drawing *dr, int *coords, int npoints,
                  int fillcolour, int outlinecolour);

Draws an outlined or filled polygon in the puzzle window.

coords is an array of (2*npoints) integers, containing the x and y coordinates of npoints vertices.

fillcolour and outlinecolour are integer indices into the colours array returned by the back end function colours() (section 2.8.6). fillcolour may also be -1 to indicate that the polygon should be outlined only.

The polygon defined by the specified list of vertices is first filled in fillcolour, if specified, and then outlined in outlinecolour.

outlinecolour may not be -1; it must be a valid colour (and front ends are permitted to enforce this by assertion). This is because different platforms disagree on whether a filled polygon should include its boundary line or not, so drawing only a filled polygon would have non-portable effects. If you want your filled polygon not to have a visible outline, you must set outlinecolour to the same as fillcolour.

Some platforms may perform anti-aliasing on this function. Therefore, do not assume that you can erase a polygon by drawing the same polygon over it in the background colour. Also, be prepared for the polygon to extend a pixel beyond its obvious bounding box as a result of this; if you really need it not to do this to avoid interfering with other delicate graphics, you should probably use clip() (section 3.1.9). You can rely on horizontal and vertical lines not being anti-aliased.

This function may be used for both drawing and printing.

3.1.5 draw_circle()

void draw_circle(drawing *dr, int cx, int cy, int radius,
                 int fillcolour, int outlinecolour);

Draws an outlined or filled circle in the puzzle window.

cx and cy give the coordinates of the centre of the circle. radius gives its radius. The total horizontal pixel extent of the circle is from cx-radius+1 to cx+radius-1 inclusive, and the vertical extent similarly around cy.

fillcolour and outlinecolour are integer indices into the colours array returned by the back end function colours() (section 2.8.6). fillcolour may also be -1 to indicate that the circle should be outlined only.

The circle is first filled in fillcolour, if specified, and then outlined in outlinecolour.

outlinecolour may not be -1; it must be a valid colour (and front ends are permitted to enforce this by assertion). This is because different platforms disagree on whether a filled circle should include its boundary line or not, so drawing only a filled circle would have non-portable effects. If you want your filled circle not to have a visible outline, you must set outlinecolour to the same as fillcolour.

Some platforms may perform anti-aliasing on this function. Therefore, do not assume that you can erase a circle by drawing the same circle over it in the background colour. Also, be prepared for the circle to extend a pixel beyond its obvious bounding box as a result of this; if you really need it not to do this to avoid interfering with other delicate graphics, you should probably use clip() (section 3.1.9).

This function may be used for both drawing and printing.

3.1.6 draw_thick_line()

void draw_thick_line(drawing *dr, float thickness,
                     float x1, float y1, float x2, float y2,
                     int colour)

Draws a line in the puzzle window, giving control over the line's thickness.

x1 and y1 give the coordinates of one end of the line. x2 and y2 give the coordinates of the other end. thickness gives the thickness of the line, in pixels.

Note that the coordinates and thickness are floating-point: the continuous coordinate system is in effect here. It's important to be able to address points with better-than-pixel precision in this case, because one can't otherwise properly express the endpoints of lines with both odd and even thicknesses.

Some platforms may perform anti-aliasing on this function. The precise pixels affected by a thick-line drawing operation may vary between platforms, and no particular guarantees are provided. Indeed, even horizontal or vertical lines may be anti-aliased.

This function may be used for both drawing and printing.

3.1.7 draw_text()

void draw_text(drawing *dr, int x, int y, int fonttype,
               int fontsize, int align, int colour, char *text);

Draws text in the puzzle window.

x and y give the coordinates of a point. The relation of this point to the location of the text is specified by align, which is a bitwise OR of horizontal and vertical alignment flags:

ALIGN_VNORMAL
Indicates that y is aligned with the baseline of the text.
ALIGN_VCENTRE
Indicates that y is aligned with the vertical centre of the text. (In fact, it's aligned with the vertical centre of normal capitalised text: displaying two pieces of text with ALIGN_VCENTRE at the same y-coordinate will cause their baselines to be aligned with one another, even if one is an ascender and the other a descender.)
ALIGN_HLEFT
Indicates that x is aligned with the left-hand end of the text.
ALIGN_HCENTRE
Indicates that x is aligned with the horizontal centre of the text.
ALIGN_HRIGHT
Indicates that x is aligned with the right-hand end of the text.

fonttype is either FONT_FIXED or FONT_VARIABLE, for a monospaced or proportional font respectively. (No more detail than that may be specified; it would only lead to portability issues between different platforms.)

fontsize is the desired size, in pixels, of the text. This size corresponds to the overall point size of the text, not to any internal dimension such as the cap-height.

colour is an integer index into the colours array returned by the back end function colours() (section 2.8.6).

This function may be used for both drawing and printing.

The character set used to encode the text passed to this function is specified by the drawing object, although it must be a superset of ASCII. If a puzzle wants to display text that is not contained in ASCII, it should use the text_fallback() function (section 3.1.8) to query the drawing object for an appropriate representation of the characters it wants.

3.1.8 text_fallback()

char *text_fallback(drawing *dr, const char *const *strings,
                    int nstrings);

This function is used to request a translation of UTF-8 text into whatever character encoding is expected by the drawing object's implementation of draw_text().

The input is a list of strings encoded in UTF-8: nstrings gives the number of strings in the list, and strings[0], strings[1], ..., strings[nstrings-1] are the strings themselves.

The returned string (which is dynamically allocated and must be freed when finished with) is derived from the first string in the list that the drawing object expects to be able to display reliably; it will consist of that string translated into the character set expected by draw_text().

Drawing implementations are not required to handle anything outside ASCII, but are permitted to assume that some string will be successfully translated. So every call to this function must include a string somewhere in the list (presumably the last element) which consists of nothing but ASCII, to be used by any front end which cannot handle anything else.

For example, if a puzzle wished to display a string including a multiplication sign (U+00D7 in Unicode, represented by the bytes C3 97 in UTF-8), it might do something like this:

static const char *const times_signs[] = { "\xC3\x97", "x" };
char *times_sign = text_fallback(dr, times_signs, 2);
sprintf(buffer, "%d%s%d", width, times_sign, height);
draw_text(dr, x, y, font, size, align, colour, buffer);
sfree(buffer);

which would draw a string with a times sign in the middle on platforms that support it, and fall back to a simple ASCII ‘x’ where there was no alternative.

3.1.9 clip()

void clip(drawing *dr, int x, int y, int w, int h);

Establishes a clipping rectangle in the puzzle window.

x and y give the coordinates of the top left pixel of the clipping rectangle. w and h give its width and height. Thus, the horizontal extent of the rectangle runs from x to x+w-1 inclusive, and the vertical extent from y to y+h-1 inclusive. (These are exactly the same semantics as draw_rect().)

After this call, no drawing operation will affect anything outside the specified rectangle. The effect can be reversed by calling unclip() (section 3.1.10). The clipping rectangle is pixel-perfect: pixels within the rectangle are affected as usual by drawing functions; pixels outside are completely untouched.

Back ends should not assume that a clipping rectangle will be automatically cleared up by the front end if it's left lying around; that might work on current front ends, but shouldn't be relied upon. Always explicitly call unclip().

This function may be used for both drawing and printing.

3.1.10 unclip()

void unclip(drawing *dr);

Reverts the effect of a previous call to clip(). After this call, all drawing operations will be able to affect the entire puzzle window again.

This function may be used for both drawing and printing.

3.1.11 draw_update()

void draw_update(drawing *dr, int x, int y, int w, int h);

Informs the front end that a rectangular portion of the puzzle window has been drawn on and needs to be updated.

x and y give the coordinates of the top left pixel of the update rectangle. w and h give its width and height. Thus, the horizontal extent of the rectangle runs from x to x+w-1 inclusive, and the vertical extent from y to y+h-1 inclusive. (These are exactly the same semantics as draw_rect().)

The back end redraw function must call this function to report any changes it has made to the window. Otherwise, those changes may not become immediately visible, and may then appear at an unpredictable subsequent time such as the next time the window is covered and re-exposed.

This function is only important when drawing. It may be called when printing as well, but doing so is not compulsory, and has no effect. (So if you have a shared piece of code between the drawing and printing routines, that code may safely call draw_update().)

3.1.12 status_bar()

void status_bar(drawing *dr, char *text);

Sets the text in the game's status bar to text. The text is copied from the supplied buffer, so the caller is free to deallocate or modify the buffer after use.

(This function is not exactly a drawing function, but it shares with the drawing API the property that it may only be called from within the back end redraw function, so this is as good a place as any to document it.)

The supplied text is filtered through the mid-end for optional rewriting before being passed on to the front end; the mid-end will prepend the current game time if the game is timed (and may in future perform other rewriting if it seems like a good idea).

This function is for drawing only; it must never be called during printing.

3.1.13 Blitter functions

This section describes a group of related functions which save and restore a section of the puzzle window. This is most commonly used to implement user interfaces involving dragging a puzzle element around the window: at the end of each call to redraw(), if an object is currently being dragged, the back end saves the window contents under that location and then draws the dragged object, and at the start of the next redraw() the first thing it does is to restore the background.

The front end defines an opaque type called a blitter, which is capable of storing a rectangular area of a specified size.

Blitter functions are for drawing only; they must never be called during printing.

3.1.13.1 blitter_new()

blitter *blitter_new(drawing *dr, int w, int h);

Creates a new blitter object which stores a rectangle of size w by h pixels. Returns a pointer to the blitter object.

Blitter objects are best stored in the game_drawstate. A good time to create them is in the set_size() function (section 2.8.5), since it is at this point that you first know how big a rectangle they will need to save.

3.1.13.2 blitter_free()

void blitter_free(drawing *dr, blitter *bl);

Disposes of a blitter object. Best called in free_drawstate(). (However, check that the blitter object is not NULL before attempting to free it; it is possible that a draw state might be created and freed without ever having set_size() called on it in between.)

3.1.13.3 blitter_save()

void blitter_save(drawing *dr, blitter *bl, int x, int y);

This is a true drawing API function, in that it may only be called from within the game redraw routine. It saves a rectangular portion of the puzzle window into the specified blitter object.

x and y give the coordinates of the top left corner of the saved rectangle. The rectangle's width and height are the ones specified when the blitter object was created.

This function is required to cope and do the right thing if x and y are out of range. (The right thing probably means saving whatever part of the blitter rectangle overlaps with the visible area of the puzzle window.)

3.1.13.4 blitter_load()

void blitter_load(drawing *dr, blitter *bl, int x, int y);

This is a true drawing API function, in that it may only be called from within the game redraw routine. It restores a rectangular portion of the puzzle window from the specified blitter object.

x and y give the coordinates of the top left corner of the rectangle to be restored. The rectangle's width and height are the ones specified when the blitter object was created.

Alternatively, you can specify both x and y as the special value BLITTER_FROMSAVED, in which case the rectangle will be restored to exactly where it was saved from. (This is probably what you want to do almost all the time, if you're using blitters to implement draggable puzzle elements.)

This function is required to cope and do the right thing if x and y (or the equivalent ones saved in the blitter) are out of range. (The right thing probably means restoring whatever part of the blitter rectangle overlaps with the visible area of the puzzle window.)

If this function is called on a blitter which had previously been saved from a partially out-of-range rectangle, then the parts of the saved bitmap which were not visible at save time are undefined. If the blitter is restored to a different position so as to make those parts visible, the effect on the drawing area is undefined.

3.1.14 print_mono_colour()

int print_mono_colour(drawing *dr, int grey);

This function allocates a colour index for a simple monochrome colour during printing.

grey must be 0 or 1. If grey is 0, the colour returned is black; if grey is 1, the colour is white.

3.1.15 print_grey_colour()

int print_grey_colour(drawing *dr, float grey);

This function allocates a colour index for a grey-scale colour during printing.

grey may be any number between 0 (black) and 1 (white); for example, 0.5 indicates a medium grey.

The chosen colour will be rendered to the limits of the printer's halftoning capability.

3.1.16 print_hatched_colour()

int print_hatched_colour(drawing *dr, int hatch);

This function allocates a colour index which does not represent a literal colour. Instead, regions shaded in this colour will be hatched with parallel lines. The hatch parameter defines what type of hatching should be used in place of this colour:

HATCH_SLASH
This colour will be hatched by lines slanting to the right at 45 degrees.
HATCH_BACKSLASH
This colour will be hatched by lines slanting to the left at 45 degrees.
HATCH_HORIZ
This colour will be hatched by horizontal lines.
HATCH_VERT
This colour will be hatched by vertical lines.
HATCH_PLUS
This colour will be hatched by criss-crossing horizontal and vertical lines.
HATCH_X
This colour will be hatched by criss-crossing diagonal lines.

Colours defined to use hatching may not be used for drawing lines or text; they may only be used for filling areas. That is, they may be used as the fillcolour parameter to draw_circle() and draw_polygon(), and as the colour parameter to draw_rect(), but may not be used as the outlinecolour parameter to draw_circle() or draw_polygon(), or with draw_line() or draw_text().

3.1.17 print_rgb_mono_colour()

int print_rgb_mono_colour(drawing *dr, float r, float g,
                          float b, float grey);

This function allocates a colour index for a fully specified RGB colour during printing.

r, g and b may each be anywhere in the range from 0 to 1.

If printing in black and white only, these values will be ignored, and either pure black or pure white will be used instead, according to the ‘grey’ parameter. (The fallback colour is the same as the one which would be allocated by print_mono_colour(grey).)

3.1.18 print_rgb_grey_colour()

int print_rgb_grey_colour(drawing *dr, float r, float g,
                          float b, float grey);

This function allocates a colour index for a fully specified RGB colour during printing.

r, g and b may each be anywhere in the range from 0 to 1.

If printing in black and white only, these values will be ignored, and a shade of grey given by the grey parameter will be used instead. (The fallback colour is the same as the one which would be allocated by print_grey_colour(grey).)

3.1.19 print_rgb_hatched_colour()

int print_rgb_hatched_colour(drawing *dr, float r, float g,
                             float b, float hatched);

This function allocates a colour index for a fully specified RGB colour during printing.

r, g and b may each be anywhere in the range from 0 to 1.

If printing in black and white only, these values will be ignored, and a form of cross-hatching given by the hatch parameter will be used instead; see section 3.1.16 for the possible values of this parameter. (The fallback colour is the same as the one which would be allocated by print_hatched_colour(hatch).)

3.1.20 print_line_width()

void print_line_width(drawing *dr, int width);

This function is called to set the thickness of lines drawn during printing. It is meaningless in drawing: all lines drawn by draw_line(), draw_circle and draw_polygon() are one pixel in thickness. However, in printing there is no clear definition of a pixel and so line widths must be explicitly specified.

The line width is specified in the usual coordinate system. Note, however, that it is a hint only: the central printing system may choose to vary line thicknesses at user request or due to printer capabilities.

3.1.21 print_line_dotted()

void print_line_dotted(drawing *dr, int dotted);

This function is called to toggle the drawing of dotted lines during printing. It is not supported during drawing.

The parameter ‘dotted’ is a boolean; TRUE means that future lines drawn by draw_line(), draw_circle and draw_polygon() will be dotted, and FALSE means that they will be solid.

Some front ends may impose restrictions on the width of dotted lines. Asking for a dotted line via this front end will override any line width request if the front end requires it.

3.2 The drawing API as implemented by the front end

This section describes the drawing API in the function-pointer form in which it is implemented by a front end.

(It isn't only platform-specific front ends which implement this API; the platform-independent module ps.c also provides an implementation of it which outputs PostScript. Thus, any platform which wants to do PS printing can do so with minimum fuss.)

The following entries all describe function pointer fields in a structure called drawing_api. Each of the functions takes a ‘void *’ context pointer, which it should internally cast back to a more useful type. Thus, a drawing object (drawing *) suitable for passing to the back end redraw or printing functions is constructed by passing a drawing_api and a ‘void *’ to the function drawing_new() (see section 3.3.1).

3.2.1 draw_text()

void (*draw_text)(void *handle, int x, int y, int fonttype,
                  int fontsize, int align, int colour, char *text);

This function behaves exactly like the back end draw_text() function; see section 3.1.7.

3.2.2 draw_rect()

void (*draw_rect)(void *handle, int x, int y, int w, int h,
                  int colour);

This function behaves exactly like the back end draw_rect() function; see section 3.1.1.

3.2.3 draw_line()

void (*draw_line)(void *handle, int x1, int y1, int x2, int y2,
                  int colour);

This function behaves exactly like the back end draw_line() function; see section 3.1.3.

3.2.4 draw_polygon()

void (*draw_polygon)(void *handle, int *coords, int npoints,
                     int fillcolour, int outlinecolour);

This function behaves exactly like the back end draw_polygon() function; see section 3.1.4.

3.2.5 draw_circle()

void (*draw_circle)(void *handle, int cx, int cy, int radius,
                    int fillcolour, int outlinecolour);

This function behaves exactly like the back end draw_circle() function; see section 3.1.5.

3.2.6 draw_thick_line()

void draw_thick_line(drawing *dr, float thickness,
                     float x1, float y1, float x2, float y2,
                     int colour)

This function behaves exactly like the back end draw_thick_line() function; see section 3.1.6.

An implementation of this API which doesn't provide high-quality rendering of thick lines is permitted to define this function pointer to be NULL. The middleware in drawing.c will notice and provide a low-quality alternative using draw_polygon().

3.2.7 draw_update()

void (*draw_update)(void *handle, int x, int y, int w, int h);

This function behaves exactly like the back end draw_update() function; see section 3.1.11.

An implementation of this API which only supports printing is permitted to define this function pointer to be NULL rather than bothering to define an empty function. The middleware in drawing.c will notice and avoid calling it.

3.2.8 clip()

void (*clip)(void *handle, int x, int y, int w, int h);

This function behaves exactly like the back end clip() function; see section 3.1.9.

3.2.9 unclip()

void (*unclip)(void *handle);

This function behaves exactly like the back end unclip() function; see section 3.1.10.

3.2.10 start_draw()

void (*start_draw)(void *handle);

This function is called at the start of drawing. It allows the front end to initialise any temporary data required to draw with, such as device contexts.

Implementations of this API which do not provide drawing services may define this function pointer to be NULL; it will never be called unless drawing is attempted.

3.2.11 end_draw()

void (*end_draw)(void *handle);

This function is called at the end of drawing. It allows the front end to do cleanup tasks such as deallocating device contexts and scheduling appropriate GUI redraw events.

Implementations of this API which do not provide drawing services may define this function pointer to be NULL; it will never be called unless drawing is attempted.

3.2.12 status_bar()

void (*status_bar)(void *handle, char *text);

This function behaves exactly like the back end status_bar() function; see section 3.1.12.

Front ends implementing this function need not worry about it being called repeatedly with the same text; the middleware code in status_bar() will take care of this.

Implementations of this API which do not provide drawing services may define this function pointer to be NULL; it will never be called unless drawing is attempted.

3.2.13 blitter_new()

blitter *(*blitter_new)(void *handle, int w, int h);

This function behaves exactly like the back end blitter_new() function; see section 3.1.13.1.

Implementations of this API which do not provide drawing services may define this function pointer to be NULL; it will never be called unless drawing is attempted.

3.2.14 blitter_free()

void (*blitter_free)(void *handle, blitter *bl);

This function behaves exactly like the back end blitter_free() function; see section 3.1.13.2.

Implementations of this API which do not provide drawing services may define this function pointer to be NULL; it will never be called unless drawing is attempted.

3.2.15 blitter_save()

void (*blitter_save)(void *handle, blitter *bl, int x, int y);

This function behaves exactly like the back end blitter_save() function; see section 3.1.13.3.

Implementations of this API which do not provide drawing services may define this function pointer to be NULL; it will never be called unless drawing is attempted.

3.2.16 blitter_load()

void (*blitter_load)(void *handle, blitter *bl, int x, int y);

This function behaves exactly like the back end blitter_load() function; see section 3.1.13.4.

Implementations of this API which do not provide drawing services may define this function pointer to be NULL; it will never be called unless drawing is attempted.

3.2.17 begin_doc()

void (*begin_doc)(void *handle, int pages);

This function is called at the beginning of a printing run. It gives the front end an opportunity to initialise any required printing subsystem. It also provides the number of pages in advance.

Implementations of this API which do not provide printing services may define this function pointer to be NULL; it will never be called unless printing is attempted.

3.2.18 begin_page()

void (*begin_page)(void *handle, int number);

This function is called during printing, at the beginning of each page. It gives the page number (numbered from 1 rather than 0, so suitable for use in user-visible contexts).

Implementations of this API which do not provide printing services may define this function pointer to be NULL; it will never be called unless printing is attempted.

3.2.19 begin_puzzle()

void (*begin_puzzle)(void *handle, float xm, float xc,
                     float ym, float yc, int pw, int ph, float wmm);

This function is called during printing, just before printing a single puzzle on a page. It specifies the size and location of the puzzle on the page.

xm and xc specify the horizontal position of the puzzle on the page, as a linear function of the page width. The front end is expected to multiply the page width by xm, add xc (measured in millimetres), and use the resulting x-coordinate as the left edge of the puzzle.

Similarly, ym and yc specify the vertical position of the puzzle as a function of the page height: the page height times ym, plus yc millimetres, equals the desired distance from the top of the page to the top of the puzzle.

(This unwieldy mechanism is required because not all printing systems can communicate the page size back to the software. The PostScript back end, for example, writes out PS which determines the page size at print time by means of calling ‘clippath’, and centres the puzzles within that. Thus, exactly the same PS file works on A4 or on US Letter paper without needing local configuration, which simplifies matters.)

pw and ph give the size of the puzzle in drawing API coordinates. The printing system will subsequently call the puzzle's own print function, which will in turn call drawing API functions in the expectation that an area pw by ph units is available to draw the puzzle on.

Finally, wmm gives the desired width of the puzzle in millimetres. (The aspect ratio is expected to be preserved, so if the desired puzzle height is also needed then it can be computed as wmm*ph/pw.)

Implementations of this API which do not provide printing services may define this function pointer to be NULL; it will never be called unless printing is attempted.

3.2.20 end_puzzle()

void (*end_puzzle)(void *handle);

This function is called after the printing of a specific puzzle is complete.

Implementations of this API which do not provide printing services may define this function pointer to be NULL; it will never be called unless printing is attempted.

3.2.21 end_page()

void (*end_page)(void *handle, int number);

This function is called after the printing of a page is finished.

Implementations of this API which do not provide printing services may define this function pointer to be NULL; it will never be called unless printing is attempted.

3.2.22 end_doc()

void (*end_doc)(void *handle);

This function is called after the printing of the entire document is finished. This is the moment to close files, send things to the print spooler, or whatever the local convention is.

Implementations of this API which do not provide printing services may define this function pointer to be NULL; it will never be called unless printing is attempted.

3.2.23 line_width()

void (*line_width)(void *handle, float width);

This function is called to set the line thickness, during printing only. Note that the width is a float here, where it was an int as seen by the back end. This is because drawing.c may have scaled it on the way past.

However, the width is still specified in the same coordinate system as the rest of the drawing.

Implementations of this API which do not provide printing services may define this function pointer to be NULL; it will never be called unless printing is attempted.

3.2.24 text_fallback()

char *(*text_fallback)(void *handle, const char *const *strings,
                       int nstrings);

This function behaves exactly like the back end text_fallback() function; see section 3.1.8.

Implementations of this API which do not support any characters outside ASCII may define this function pointer to be NULL, in which case the central code in drawing.c will provide a default implementation.

3.3 The drawing API as called by the front end

There are a small number of functions provided in drawing.c which the front end needs to call, rather than helping to implement. They are described in this section.

3.3.1 drawing_new()

drawing *drawing_new(const drawing_api *api, midend *me,
                     void *handle);

This function creates a drawing object. It is passed a drawing_api, which is a structure containing nothing but function pointers; and also a ‘void *’ handle. The handle is passed back to each function pointer when it is called.

The midend parameter is used for rewriting the status bar contents: status_bar() (see section 3.1.12) has to call a function in the mid-end which might rewrite the status bar text. If the drawing object is to be used only for printing, or if the game is known not to call status_bar(), this parameter may be NULL.

3.3.2 drawing_free()

void drawing_free(drawing *dr);

This function frees a drawing object. Note that the ‘void *’ handle is not freed; if that needs cleaning up it must be done by the front end.

3.3.3 print_get_colour()

void print_get_colour(drawing *dr, int colour, int printincolour,
                      int *hatch, float *r, float *g, float *b)

This function is called by the implementations of the drawing API functions when they are called in a printing context. It takes a colour index as input, and returns the description of the colour as requested by the back end.

printincolour is TRUE iff the implementation is printing in colour. This will alter the results returned if the colour in question was specified with a black-and-white fallback value.

If the colour should be rendered by hatching, *hatch is filled with the type of hatching desired. See section 3.1.15 for details of the values this integer can take.

If the colour should be rendered as solid colour, *hatch is given a negative value, and *r, *g and *b are filled with the RGB values of the desired colour (if printing in colour), or all filled with the grey-scale value (if printing in black and white).