/* Copyright 1997 Acorn Computers Ltd
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/***************************************************/
/* File   : Redraw.c                               */
/*                                                 */
/* Purpose: Redraw functions for the browser.      */
/*                                                 */
/* Author : A.D.Hodgkinson                         */
/*                                                 */
/* History: 29-Nov-96: Created.                    */
/***************************************************/

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "swis.h"
#include "flex.h"

#include "HTMLLib.h" /* HTML library API, Which will include html2_ext.h, tags.h and struct.h */

#include "wimp.h"
#include "wimplib.h"
#include "event.h"

#include "svcprint.h"
#include "Global.h"
#include "FromROSLib.h"
#include "MiscDefs.h"
#include "Utils.h"

#include "Browser.h"
#include "ChoiceDefs.h"
#include "CSIM.h"
#include "Fetch.h" /* (Which itself includes URLstat.h) */
#include "FontManage.h"
#include "Forms.h"
#include "Frames.h"
#include "Images.h"
#include "Object.h"
#include "Printing.h" /* Only for the PrintSplitFraction definition at present */
#include "PrintStyle.h"
#include "Reformat.h"
#include "Tables.h"
#include "TokenUtils.h"
#include "Toolbars.h"

#ifdef UNIFONT
  #include "Unicode/iso10646.h"
  #include "Unifont.h"
#endif

#include "Redraw.h"

/* Static function prototypes */

static void redraw_input_field (browser_data * b, HStream * t, BBox * box, int colour, int menu);
static void redraw_button      (browser_data * b, HStream * t, BBox * box, int in);
static void redraw_switch      (browser_data * b, HStream * t, int x, int y, char * spr, WimpRedrawWindowBlock * r);
static void redraw_bullet      (int x, int y, int bullet, WimpRedrawWindowBlock * r);

/* Used for printing */

static int use_noback = 0;

/* Internal recursive functions. These do the actual work that their */
/* similarly named and oft externally visible counterparts claim to  */
/* do, but are part of the recursive code needed for e.g. tables.    */

static _kernel_oserror * redraw_draw_r (int toplevel, int xorg, int yorg, browser_data * b, reformat_cell * d, WimpRedrawWindowBlock * r, int noback,  HStream * nocontent);

/*************************************************/
/* redraw_header()                               */
/*                                               */
/* Returns the header type (<H1>, <H2> etc. as   */
/* a number from 1 - 7) extracted from the flags */
/* bits of an HStream structure.                 */
/*                                               */
/* Parameters: The flags word.                   */
/*************************************************/

int redraw_header(unsigned int flags)
{
  /* H_MASK and H_SHIFT are defined in HTMLLib:tags.h */

  flags &= H_MASK;
  flags = (flags >> H_SHIFT);

  return flags;
}

/*************************************************/
/* redraw_backcol()                              */
/*                                               */
/* Small function to return the actual           */
/* background colour of a browser window.        */
/*                                               */
/* Parameters: A pointer to a browser_data       */
/*             structure associated with the     */
/*             window in question.               */
/*************************************************/

int redraw_backcol(browser_data * b)
{
  /* If the background colour isn't set or the Choices say  */
  /* to override document colours, return the default; else */
  /* return the document-specified background colour.       */

  #ifdef TRACE
    if (tl & (1u<<9)) Printf("redraw_backcol: Called with choices.background_colour = %p\n",(void *) choices.background_colour);
  #endif

  return (((b->background_colour == -1) || (!b->use_source_cols)) ? (choices.background_colour) : (b->background_colour));
}

/*************************************************/
/* redraw_background_colour()                    */
/*                                               */
/* Returns a background colour hint for text of  */
/* a given foreground colour.                    */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             holding the background info;      */
/*                                               */
/*             A foreground colour (as a palette */
/*             entry, for more details see       */
/*             redraw_set_colour()).             */
/*                                               */
/* Returns:    A background colour.              */
/*************************************************/

int redraw_background_colour(browser_data * b, int foregroundcolour)
{
  if (!b->show_background) return redraw_backcol(b);

  switch (b->antialias_colour)
  {
    /* Defeat anti-aliasing by giving the same background colour */
    /* as the foreground if antialias_colour is -1.              */

    case -1: return foregroundcolour;

    /* If antialias_colour is -2, return the background colour from the */
    /* browser_data struct unless this is -1, in which case return the  */
    /* foreground colour again.                                         */

    case -2: return (b->background_colour == -1 ? foregroundcolour : b->background_colour);
  }

  /* Return either the default background colour or the anti-alias */
  /* colour, depending on whether document colour overriding is    */
  /* on or off respectively.                                       */

  return (!b->use_source_cols ? choices.background_colour : b->antialias_colour);
}

/*************************************************/
/* redraw_token_colour()                         */
/*                                               */
/* Returns the colour to plot a token in, on the */
/* assumption that it contains some sort of text */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             with details of the token stream  */
/*             within it;                        */
/*                                               */
/*             Pointer to the token.             */
/*                                               */
/* Returns:    The colour to plot in, as a       */
/*             palette entry (see                */
/*             redraw_set_colour()).             */
/*************************************************/

int redraw_token_colour(browser_data * b, HStream * t)
{
  if (t->tagno == TAG_INPUT || t->tagno == TAG_TEXTAREA || t->tagno == TAG_SELECT) return 0;

  /* If we're printing, see if the Print Style dictates that any */
  /* text should be black.                                       */

  if (printing)
  {
    if (printstyle_always_use_black())                  return Redraw_Colour_Black;
    if (printstyle_black_no_background() && use_noback) return Redraw_Colour_Black;
  }

  /* If the token represents a link, use different colours according */
  /* to the state of that link (followed, unfollowed etc).           */

  if (ISLINK(t))
  {
    /* If tokens are highlighted, return the appropriate colour */

    if (b->highlight) return (b->use_source_cols ? b->followed_colour : choices.followed_colour);

    /* If tokens are selected, return the appropriate colour */

    if (redraw_selected(b, t)) return (b->use_source_cols ? b->selected_colour : choices.selected_colour);

    /* If the token has been followed in the past, give the used colour */
    /* - otherwise give the unfollowed link colour.                     */

    if (!printing && (t->flags & HFlags_LinkVisited)) return (b->use_source_cols ? b->used_colour : choices.used_colour);

    return (b->use_source_cols ? b->link_colour : choices.link_colour);
  }

  /* If the token has attached specific colour information, return that */

  if ((t->type & TYPE_COLOURED) && b->use_source_cols) return (t->colour << 8);

  /* If the token is just text, return the normal text colour */

  return (b->use_source_cols ? b->text_colour : choices.text_colour);
}

/*************************************************/
/* redraw_set_colour()                           */
/*                                               */
/* Sets the foreground colour for future plots.  */
/*                                               */
/* Parameters: A 32-bit colour number in the     */
/*             form BBGGRRcc where cc = GCOL,    */
/*             or BBGGRR are blue, green and     */
/*             red components.                   */
/*************************************************/

void redraw_set_colour(int colour)
{
  #ifdef TRACE
    if (tl & (1u<<9)) Printf("redraw_set_colour: Called with colour = %p\n",(void *) colour);
  #endif

  /* Don't use dithering if anti-twittering redraws */

  #ifdef ANTI_TWITTER

    _swix(ColourTrans_SetGCOL,
          _IN(0) | _INR(3,4),

          colour, /* Colour to change to */
          0,      /* No dithering        */
          0);     /* GCOL action 0       */

  #else

    _swix(ColourTrans_SetGCOL,
          _IN(0) | _INR(3,4),

          colour, /* Colour to change to                            */
          1<<8,   /* Use ECFs (dithering) for better representation */
          0);     /* GCOL action 0                                  */

  #endif
}

/*************************************************/
/* redraw_display_width()                        */
/*                                               */
/* Returns the available display width for a     */
/* given browser redraw cell, in OS units.       */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the cell;             */
/*                                               */
/*             Pointer to a reformat_cell struct */
/*             representing the redraw cell.     */
/*                                               */
/* Returns:    The display width, in OS units.   */
/*************************************************/

int redraw_display_width(browser_data * b, reformat_cell * d)
{
  if (!d || !d->table) return b->display_width;
  else
  {
    int osw;

    convert_to_os(d->cellwidth, &osw);

    return osw;
  }
}

/*************************************************/
/* redraw_display_height()                       */
/*                                               */
/* Returns the available display height for a    */
/* given browser redraw cell, in OS units; for a */
/* base browser window, it will subtract the     */
/* toolbar heights as required.                  */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the cell;             */
/*                                               */
/*             Pointer to a reformat_cell struct */
/*             representing the redraw cell.     */
/*                                               */
/* Returns:    The display width, in OS units.   */
/*************************************************/

int redraw_display_height(browser_data * b, reformat_cell * d)
{
  if (!d || !d->table)
  {
    /* Subtract a bit for aesthetics and to account for the */
    /* amount a line might naturally be overheight, so that */
    /* (say) images scaled to 100% height don't lead to a   */
    /* vertically scrollable page.                          */

    return b->display_height - b->leading * 3;
  }
  else
  {
    int osh;

    convert_to_os(d->cellheight, &osh);

    return osh;
  }
}

/*************************************************/
/* redraw_left_margin()                          */
/*                                               */
/* Returns the left hand margin width for a      */
/* given browser redraw cell, in millipoints.    */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the cell;             */
/*                                               */
/*             Pointer to a reformat_cell struct */
/*             representing the redraw cell.     */
/*                                               */
/* Returns:    The left hand margin, in          */
/*             millipoints.                      */
/*************************************************/

int redraw_left_margin(browser_data * b, reformat_cell * d)
{
  if (!d || !d->table) return b->left_margin;
  else
  {
    /* Left margin -> cellpadding for a table cell */

    int cellpadmp = d->table->cellpadding * 2; /* 1 'web pixel' = 2 OS units */

    convert_to_points(cellpadmp, &cellpadmp);

    return cellpadmp;
  }
}

/*************************************************/
/* redraw_right_margin()                         */
/*                                               */
/* Returns the right hand margin width for a     */
/* given browser redraw cell, in millipoints.    */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the cell;             */
/*                                               */
/*             Pointer to a reformat_cell struct */
/*             representing the redraw cell.     */
/*                                               */
/* Returns:    The right hand margin, in         */
/*             millipoints.                      */
/*************************************************/

int redraw_right_margin(browser_data * b, reformat_cell * d)
{
  if (!d || !d->table) return b->right_margin;
  else
  {
    /* Left margin -> cellpadding for a table cell */

    int cellpadmp = d->table->cellpadding * 2; /* 1 'web pixel' = 2 OS units */

    convert_to_points(cellpadmp, &cellpadmp);

    return cellpadmp;
  }
//  else           return 0; /* No margin on table cells - cellpadding/spacing handled separately by reformatter */
}

/*************************************************/
/* redraw_left_gap()                             */
/*                                               */
/* Works out the left hand indented margin for a */
/* given browser redraw cell, in millipoints.    */
/* This will be redraw_left_margin plus a value  */
/* dependent upon the given token (to allow e.g. */
/* list items to be indented).                   */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the cell;             */
/*                                               */
/*             Pointer to a reformat_cell struct */
/*             representing the redraw cell;     */
/*                                               */
/*             Pointer to a token holding        */
/*             indentation information.          */
/*                                               */
/* Returns:    The left hand margin, taking      */
/*             account of list indentations      */
/*             etc., in millipoints.             */
/*************************************************/

int redraw_left_gap(browser_data * b, reformat_cell * d, HStream * t)
{
  int s, i;

  s = t->style;
  i = t->indent * b->left_indent;

  /* Play about outdenting bullets and numbered list items. */

  if (t->tagno == TAG_LI)
  {
    if (t->text)
    {
      _kernel_oserror * e;
      int               h, width, bytes;

      /* We need to right-align the text. Find out its width... */

      h = fm_find_token_font(b, t, 0);

      e = fm_get_string_width(h,
                              t->text,
                              0x40000000,
                              0x40000000,
                              -1,
                              &bytes,
                              &width);

      if (!e) i -= width;
    }
    else
    {
      int bullet_width;

      /* Outdent the bullet */

      convert_to_points(reformat_bullet_width(t->indent), &bullet_width);

      i -= bullet_width;
    }
  }

  /* Add an amount for block quote or address text */

  if (s & (BLOCKQUOTE | ADDRESS)) i += b->quote_margin;

  /* Return the calculated left indent plus the left */
  /* margin value.                                   */

  i += redraw_left_margin(b, d);

  return i > 0 ? i : 0;
}

/*************************************************/
/* redraw_right_gap()                            */
/*                                               */
/* As redraw_left_gap, but for the right hand    */
/* edge of a given redraw browser cell.          */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the cell;             */
/*                                               */
/*             Pointer to a reformat_cell struct */
/*             representing the redraw cell;     */
/*                                               */
/*             Pointer to a token holding        */
/*             indentation information.          */
/*                                               */
/* Returns:    The right hand margin, taking     */
/*             account of block quote indents    */
/*             etc., in millipoints.             */
/*************************************************/

int redraw_right_gap(browser_data * b, reformat_cell * d, HStream * t)
{
  int i = 0;

  /* Add an amount for block quote text */

  if (t->style & BLOCKQUOTE) i += b->quote_margin;

  /* Return the calculated right indent plus the right */
  /* margin value.                                     */

  i += redraw_right_margin(b, d);

  return i > 0 ? i : 0;
}

/*************************************************/
/* redraw_start_x()                              */
/*                                               */
/* Examines current token and line structure     */
/* information within a redraw cell to return an */
/* indent from the left edge of the page at      */
/* which something should be drawn - handles     */
/* centre and right aligning of lines.           */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the cell;             */
/*                                               */
/*             Pointer to a reformat_cell struct */
/*             representing the redraw cell;     */
/*                                               */
/*             Pointer to an HStream (token)     */
/*             so that a margin can be found;    */
/*                                               */
/*             Line number which the x offset    */
/*             needs to be found for.            */
/*                                               */
/* Returns:    The x offset to plot at, in OS    */
/*             units.                            */
/*************************************************/

int redraw_start_x(browser_data * b, reformat_cell * cell, HStream * t, int line)
{
  int x, cn;
  int align = 0;

  if (!cell) cell = b->cell;

  cn = cell->ldata[line].chunks;

  /* If the token isn't centred just return the margin value; */
  /* else work out centre or right alignment indentation.     */

  if      ((t->style & CENTER) || (t->type & TYPE_ALIGN_MASK) == TYPE_CENTRE) align = 1;
  else if ((t->style & RIGHT)  || (t->type & TYPE_ALIGN_MASK) == TYPE_RIGHT)  align = 2;

  if (align)
  {
    int i;
    int left  = redraw_left_gap (b, cell, t);
    int right = redraw_right_gap(b, cell, t);

    /* Get the window's display width in millipoints */

    convert_to_points(redraw_display_width(b, cell), &x);

    /* Subtract the width of each chunk from this value */

    for (i = 0; i < cell->ldata[line].n; x -= cell->cdata[cn].w, i++, cn++);

    /* Subtract the right hand gap value */

    x -= right;

    /* For centred objects, center between the margins */

    if (align == 1) x = left + ((x - left) / 2);

    /* Sanity check */

    if (x < left) x = left;

    /* Convert back to OS units */

    convert_to_os(x, &x);

    return x;
  }

  convert_to_os(redraw_left_gap(b, cell, t), &x);

  return x;
}

/*************************************************/
/* redraw_token_x()                              */
/*                                               */
/* Examines current token and line structure     */
/* information within a redraw cell to return an */
/* indent from the left edge of the page at      */
/* which a specific token should be drawn.       */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the cell;             */
/*                                               */
/*             Pointer to a reformat_cell struct */
/*             representing the redraw cell;     */
/*                                               */
/*             Pointer to the token;             */
/*                                               */
/*             Line number which the token lies  */
/*             in;                               */
/*                                               */
/* Returns:    The x offset from the left of the */
/*             page that the token starts at, in */
/*             OS units.                         */
/*************************************************/

int redraw_token_x(browser_data * b, reformat_cell * cell, HStream * t, int line)
{
  int x, chunk, mchunk;

  if (!cell) cell = b->cell;

  /* Find the starting left hand edge */

  convert_to_points(redraw_start_x(b,
                                   cell,
                                   cell->cdata[cell->ldata[line].chunks].t,
                                   line),
                    &x);

  /* Add up chunk widths */

  chunk  = cell->ldata[line].chunks;
  mchunk = cell->ldata[line].n + chunk;

  while (
          chunk < mchunk         &&
          cell->cdata[chunk].t != t
        )
        x += cell->cdata[chunk].w, chunk++;

  convert_to_os(x, &x);

  /* Return the total */

  return x;
}

/*************************************************/
/* redraw_chunk_x()                              */
/*                                               */
/* Examines current token and line structure     */
/* information within a redraw cell to return an */
/* indent from the left edge of the page at      */
/* which a specific chunk should be drawn (i.e.  */
/* as redraw_token_x, but you supply a chunk     */
/* number rather than a token).                  */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the cell;             */
/*                                               */
/*             Pointer to a reformat_cell struct */
/*             representing the redraw cell;     */
/*                                               */
/*             The chunk number;                 */
/*                                               */
/*             Line number the chunk lies in.    */
/*                                               */
/* Returns:    The x offset from the left of the */
/*             page that the chunk starts at, in */
/*             OS units.                         */
/*                                               */
/* Assumes:    That the given line does indeed   */
/*             include the given chunk.          */
/*************************************************/

int redraw_chunk_x(browser_data * b, reformat_cell * cell, int chunk, int line)
{
  int x, cchunk, mchunk;

  if (!cell) cell = b->cell;

  /* Find the starting left hand edge */

  convert_to_points(redraw_start_x(b,
                                   cell,
                                   cell->cdata[cell->ldata[line].chunks].t,
                                   line),
                    &x);

  /* Add up chunk widths */

  cchunk = cell->ldata[line].chunks;
  mchunk = cchunk + cell->ldata[line].n;

  while (
          cchunk < mchunk &&
          cchunk < chunk
        )
        x += cell->cdata[cchunk].w, cchunk++;

  convert_to_os(x, &x);

  /* Return the total */

  return x;
}

/*************************************************/
/* redraw_selected()                             */
/*                                               */
/* Looks at the 'selected' field for the given   */
/* browser_data struct, and returns 1 if the     */
/* given token should be part of the selection   */
/* that 'selected' lies in.                      */
/*                                               */
/* This is for whole token selection, e.g. when  */
/* keyboard navigating a page - it isn't part of */
/* a more general mouse-driven text selection    */
/* model.                                        */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the selection;        */
/*             Pointer to the token to compare.  */
/*                                               */
/* Returns:    1 if the token is part of the     */
/*             selection, else 0.                */
/*************************************************/

int redraw_selected(browser_data * b, HStream * token)
{
  HStream      * top;
  HStream      * end;
  browser_data * owner;
  browser_data * ancestor = utils_ancestor(b);
  int            found    = 0;

  /* If printing, don't want to show anything as selected */

  if (printing) return 0;

  /* Otherwise, find out if the token is part of a selection. */

  owner = ancestor->selected_owner;

  if (!ancestor->selected) return 0;
  if (ancestor->selected == token) return 1;

  tokenutils_anchor_range(owner, ancestor->selected, &top, &end);

  if (top && end)
  {
    do
    {
      if (token == top) found = 1;
      else top = top->next;
    }
    while (top && top != end->next && !found);
  }

  return found;
}

/*************************************************/
/* redraw_border_around_box()                    */
/*                                               */
/* Draws a 2 pixel thick border around a given   */
/* bounding box, in a given colour.              */
/*                                               */
/* Parameters: Pointer to the BBox;              */
/*                                               */
/*             Colour to use, as a palette entry */
/*             (for more details see             */
/*             redraw_set_colour()).             */
/*************************************************/

void redraw_border_around_box(BBox * rbox, int colour)
{
  BBox box;

  box = *rbox;

  box.xmin &= ~(wimpt_dx() - 1);
  box.ymin &= ~(wimpt_dy() - 1);
  box.xmax &= ~(wimpt_dx() - 1);
  box.ymax &= ~(wimpt_dy() - 1);

  redraw_set_colour(colour);

  bbc_rectangle(box.xmin - 4, box.ymin - 4, box.xmax - box.xmin + 7, box.ymax - box.ymin + 7);
  bbc_rectangle(box.xmin - 2, box.ymin - 2, box.xmax - box.xmin + 3, box.ymax - box.ymin + 3);
}

/*************************************************/
/* redraw_input_field()                          */
/*                                               */
/* For forms, redraws an input field element.    */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the element;          */
/*                                               */
/*             Pointer to the token representing */
/*             this element;                     */
/*                                               */
/*             BBox of the field, in window      */
/*             coords (and thus OS units);       */
/*                                               */
/*             Border colour (as a palette       */
/*             entry, for more details see       */
/*             redraw_set_colour());             */
/*                                               */
/*             1 if this is a display field,     */
/*             i.e. it has a menu icon, and the  */
/*             border colour is ignored; else 0. */
/*************************************************/

static void redraw_input_field(browser_data * b, HStream * t, BBox * box, int colour, int menu)
{
  int w, h;

  w = box->xmax - box->xmin - 1;
  h = box->ymax - box->ymin - 1;

  if (menu)
  {
    int  sw;
    BBox shorter;

    shorter = *box;

    if (read_sprite_size("fgright", &sw, NULL)) sw = 44;

    shorter.xmax -= (sw + 8);

    if (shorter.xmax < shorter.xmin) shorter.xmax = shorter.xmin + sw;

    /* Redraw the display region as a slabbed button */

    redraw_button(b, t, &shorter, 2);
  }
  else
  {
    /* Redraw the inside in white */

    redraw_set_colour(Redraw_Colour_White);

    bbc_rectanglefill(box->xmin, box->ymin, w, h);

    /* Redraw the border */

    redraw_set_colour(colour);

    bbc_rectanglefill(box->xmin,     box->ymin,     3,     h);
    bbc_rectanglefill(box->xmax - 4, box->ymin,     3,     h);
    bbc_rectanglefill(box->xmin + 4, box->ymin,     w - 8, 3);
    bbc_rectanglefill(box->xmin + 4, box->ymax - 4, w - 8, 3);

    /* Draw a wider border if selected */

    if (redraw_selected(b, t)) redraw_border_around_box(box, b->selected_colour);
  }
}

/*************************************************/
/* redraw_button()                               */
/*                                               */
/* For forms, redraws a button element.          */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the element;          */
/*                                               */
/*             Pointer to the token representing */
/*             this element;                     */
/*                                               */
/*             BBox of the field, in window      */
/*             coords (and thus OS units);       */
/*                                               */
/*             1 to be slabbed in, 2 to be       */
/*             slabbed in but with a light grey  */
/*             background rather than dark, else */
/*             0.                                */
/*************************************************/

static void redraw_button(browser_data * b, HStream * t, BBox * box, int in)
{
  int w, h;

  w = box->xmax - box->xmin - 1;
  h = box->ymax - box->ymin - 1;

  redraw_set_colour((in == 1) ? Redraw_Colour_MidGrey : Redraw_Colour_BackGrey);

  bbc_rectanglefill(box->xmin, box->ymin, w, h);

  redraw_set_colour(in ? Redraw_Colour_PlinthGrey : Redraw_Colour_AlmostWhite);

  bbc_rectanglefill(box->xmin,     box->ymin,     1,     h);
  bbc_rectanglefill(box->xmin + 2, box->ymin + 2, 1,     h - 2);
  bbc_rectanglefill(box->xmin + 4, box->ymax - 2, w - 4, 1);
  bbc_rectanglefill(box->xmin + 4, box->ymax - 4, w - 6, 1);

  redraw_set_colour(in ? Redraw_Colour_AlmostWhite : Redraw_Colour_PlinthGrey);

  bbc_rectanglefill(box->xmin + 2, box->ymin,     w - 2, 1);
  bbc_rectanglefill(box->xmin + 4, box->ymin + 2, w - 4, 1);
  bbc_rectanglefill(box->xmax - 4, box->ymin + 4, 1,     h - 8);
  bbc_rectanglefill(box->xmax - 2, box->ymin + 4, 1,     h - 6);

  if (redraw_selected(b, t)) redraw_border_around_box(box, b->selected_colour);
}

/*************************************************/
/* redraw_switch()                               */
/*                                               */
/* For forms, redraws a switch (radio or option) */
/* element.                                      */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the element;          */
/*                                               */
/*             Pointer to the token representing */
/*             this element;                     */
/*                                               */
/*             x coordinate (points, in screen   */
/*             coords) of left hand edge;        */
/*                                               */
/*             y coordinate (points, in screen   */
/*             coords) of right hand edge;       */
/*                                               */
/*             Pointer to sprite name to use;    */
/*                                               */
/*             A WimpRedrawWindowBlock pointer,  */
/*             if in a redraw loop (can be NULL  */
/*             if not in a redraw loop).         */
/*************************************************/

static void redraw_switch(browser_data * b, HStream * t, int x, int y, char * spr, WimpRedrawWindowBlock * r)
{
  int               ox, oy, w, h;
  WimpPlotIconBlock block;
  BBox              icon;

  convert_pair_to_os(x, y, &ox, &oy);

  oy -= 8;

  if (read_sprite_size(spr, &w, &h)) w = h = 44;

  icon.xmin = ox;
  icon.ymin = oy;
  icon.xmax = icon.xmin + w;
  icon.ymax = icon.ymin + h;

  if (r) coords_box_toworkarea(&icon, r);

  block.bbox                       = icon;
  block.flags                      = 0x1700311A;
  block.data.is.sprite             = spr;
  block.data.is.sprite_area        = (void *) sprite_block;
  block.data.is.sprite_name_length = strlen(spr);

  _swix(Wimp_PlotIcon,
        _IN(1) | _INR(4,5),

        &block,
        0,
        0);

  if (redraw_selected(b, t))
  {
    if (r) coords_box_toscreen(&icon, r);
    redraw_border_around_box(&icon, b->selected_colour);
  }
}

/*************************************************/
/* redraw_bullet()                               */
/*                                               */
/* Redraws a bullet point.                       */
/*                                               */
/* Parameters: x coordinate (points, in screen   */
/*             coords) of left hand edge;        */
/*                                               */
/*             y coordinate (points, in screen   */
/*             coords) of right hand edge;       */
/*                                               */
/*             The bullet number;                */
/*                                               */
/*             A WimpRedrawWindowBlock pointer,  */
/*             if in a redraw loop (can be NULL  */
/*             if not in a redraw loop).         */
/*************************************************/

static void redraw_bullet(int x, int y, int bullet, WimpRedrawWindowBlock * r)
{
  char              spr[32];
  int               w, h;
  BBox              icon;
  WimpPlotIconBlock block;

  sprintf(spr, "b%d", (bullet + bullets - 1) % bullets);

  if (read_sprite_size(spr, &w, &h)) w = h = 32;

  icon.xmin = x;
  icon.ymin = y;
  icon.xmax = icon.xmin + w;
  icon.ymax = icon.ymin + h;

  if (r) coords_box_toworkarea(&icon, r);

  block.bbox                       = icon;
  block.flags                      = 0x1700311A; /* 11a */
  block.data.is.sprite             = spr;
  block.data.is.sprite_area        = (void *) sprite_block;
  block.data.is.sprite_name_length = strlen(spr);

  _swix(Wimp_PlotIcon,
        _IN(1) | _INR(4,5),

        &block,
        0,
        0);
}

/*************************************************/
/* redraw_draw_placeholder()                     */
/*                                               */
/* Redraws a slabbed in place holder (unless the */
/* item is very small, in which case just at     */
/* thin black border is plotted) for a given     */
/* token, with optional text inside.             */
/*                                               */
/* Parameters: A pointer to a browser_data       */
/*             structure relevant to the redraw; */
/*                                               */
/*             A WimpRedrawWindowBlock pointer,  */
/*             with window area and redraw       */
/*             rectangle details filled in;      */
/*                                               */
/*             Pointer to a BBox in which xmin   */
/*             and ymin hold the screen coords   */
/*             for the bottom left hand corner,  */
/*             and xmax and ymax hold the width  */
/*             and height of the placeholder in  */
/*             OS units;                         */
/*                                               */
/*             Pointer to the HStream struct the */
/*             placeholder is to represent;      */
/*                                               */
/*             Pointer to a null-terminated      */
/*             piece of to plot inside, or NULL. */
/*************************************************/

void redraw_draw_placeholder(browser_data * b, WimpRedrawWindowBlock * r, BBox * holder, HStream * token, const char * text)
{
  BBox ph = *holder; // In case we want to adjust it later, e.g. for H/VSPACE

  /* A slabbed box if the size is great enough */

  if (ph.xmax > 8 && ph.ymax > 8)
  {
    /* xmin, ymin hold the bottom left hand corner coordinates, whilst */
    /* xmax, ymax hold the width and height. The adjustments are to    */
    /* account for the way the bbc_rectanglefill function works; e.g., */
    /* to get a width of 4 OS units, ask for 3 (as it adds this to the */
    /* x coordinate and treats it as an inclusive x coordinate max).   */
    /* There are corrections to plot 2 OS units inside of the real     */
    /* bounding box (looks better when images touch each other) and to */
    /* get the darker sides of the 'slabbed in' box overlapping the    */
    /* lighter sides by the right amount.                              */

    redraw_set_colour(Redraw_Colour_AlmostWhite);
    bbc_rectanglefill(ph.xmin + 2,
                      ph.ymin + 2,
                      ph.xmax - 5,
                      3);
    bbc_rectanglefill(ph.xmax + ph.xmin - 6,
                      ph.ymin + 2,
                      3,
                      ph.ymax - 5);

    redraw_set_colour(Redraw_Colour_MidGrey);
    bbc_rectanglefill(ph.xmin + 2,
                      ph.ymax + ph.ymin - 6,
                      ph.xmax - 7,
                      3);
    bbc_rectanglefill(ph.xmin + 2,
                      ph.ymin + 4,
                      3,
                      ph.ymax - 7);
  }

  /* Otherwise a thin black frame */

  else
  {
    if (ph.xmax < 2) ph.xmax = 2;
    if (ph.ymax < 2) ph.ymax = 2;

    redraw_set_colour(0);
    bbc_rectangle(ph.xmin,ph.ymin,ph.xmax - 1,ph.ymax - 1);
  }

  /* Plot any text that there is */

  if (text && *text)
  {
    /* Find out the bounding box needed to contain the text */

    int    h, xpos, vcent, stringwidth, stringheight, size;
    BBox   fbox;
    BBox * ibox = NULL;

    fbox.xmin = fbox.ymin = 0;

    /* Claim the font */

    size = (fm_size(token->fontsize) * 80) / 100;

    h = fm_find_font(b,
                     "sans",
                     size,
                     size,
                     0,
                     0);

    /* Find the string width of the ALT text */

    fm_get_string_width(h,
                        text,
                        Reformat_AsWideAsPossible_MP,
                        strlen(text),
                        -1,
                        NULL,
                        &stringwidth);

    convert_to_os(stringwidth, &stringwidth);

    /* Find the font height */

    fm_font_box(h, &fbox);

    stringheight = fbox.ymax - fbox.ymin;

    /* Set xpos to the horizontal offset to plot at. */
    /* Remember that 'box' contains the bottom       */
    /* left coordinates of the image, then the width */
    /* and height in OS units in xmax and ymax.      */

    /* Similarly, centre vertically */

    vcent = (ph.ymax - stringheight) / 2 - fbox.ymin;
    if (vcent <= 0) vcent = 10;
    vcent += ph.ymin;

    xpos = (ph.xmax - stringwidth) / 2;
    if (xpos <= 0) xpos = 10;
    xpos += ph.xmin;

    /* Now set the graphics window to the image bounding box,    */
    /* taking account of the slabbed border already drawn above. */
    /* Need to set this to the intersection of the current       */
    /* graphics window though, or could end up scribbling over   */
    /* things that aren't meant to be touched.                   */

    fbox.xmin = ph.xmin + 8;
    fbox.xmax = ph.xmin + ph.xmax - 9;
    fbox.ymin = ph.ymin + 8;
    fbox.ymax = ph.ymin + ph.ymax - 9;

    /* If the max coordinates are less than the min, the image BBox */
    /* is too small to fit anything in. Don't proceed, as the       */
    /* attempt to set the graphics rectangle would fail, default to */
    /* the whole screen, and then random bits of ALT text would get */
    /* scribbled all over the place...                              */

    if (fbox.xmin < fbox.xmax && fbox.ymin < fbox.ymax)
    {
      /* Need to ensure a graphics window is set up for the plot, as  */
      /* text may be clipped, but this needs to take the current      */
      /* redraw rectangle into account too - hence the function call. */

      ibox = set_graphics_intersection(&fbox, &r->redraw_area);

      if (ibox)
      {
        int colour;

        colour = redraw_token_colour(b, token);

        fm_set_font_colour(h,
                           colour,
                           redraw_background_colour(b, colour));
        fm_puts(h,
                xpos,
                vcent,
                text,
                1,
                b->background_image >= 0 && b->show_background);

        /* Underline text if it's a link and the browser is set to underline links */

        if (b->underline_links && ISLINK(token))
        {
          redraw_set_colour(colour);
          bbc_move(xpos, vcent - 7);
          bbc_draw(xpos + stringwidth, vcent - 7);
        }

        /* Put the old graphics window back again. */

        restore_graphics_intersection(&r->redraw_area);
      }
    }
  }
}

/*************************************************/
/* redraw_draw()                                 */
/*                                               */
/* The main browser redraw engine.               */
/*                                               */
/* Parameters: A pointer to a browser_data       */
/*             structure relevant to the redraw; */
/*                                               */
/*             A WimpRedrawWindowBlock pointer,  */
/*             with window area and redraw       */
/*             rectangle details filled in;      */
/*                                               */
/*             1 to plot no backgrounds at all,  */
/*             else they will be shown;          */
/*                                               */
/*             0 for normal redraw, else pointer */
/*             to a token where no content is to */
/*             be drawn - only the elements that */
/*             are needed to indicate selection  */
/*             should be shown. This is used     */
/*             mostly for things like removing   */
/*             borders around images; if bits of */
/*             the image have to be redrawn this */
/*             can make the removal slow. Only   */
/*             one token is allowed as any       */
/*             adjacent images must be redrawn   */
/*             if the border was plotted over    */
/*             them, or redraw anomalies will be */
/*             seen as 'holes' are left behind.  */
/*             There is some intelligence to     */
/*             give different behaviour if       */
/*             selecting or deselecting things.  */
/*************************************************/

_kernel_oserror * redraw_draw(browser_data * b, WimpRedrawWindowBlock * r, int noback, HStream * nocontent)
{
  #ifdef UNIFONT

    /* Somewhat horrible code for the system font Unicode stuff. */
    /* Should be able to lose this eventually.                   */

    _kernel_oserror * e;

    e = unifont_start_redraw();
    if (e) return e;

    use_noback = noback;

    e = redraw_draw_r(1, 0, 0, b, b->cell, r, noback, nocontent);

    if (e)
    {
      unifont_end_redraw();
      return e;
    }

    return unifont_end_redraw();

  #else

    use_noback = noback;

    return redraw_draw_r(1, 0, 0, b, b->cell, r, noback, nocontent);

  #endif
}

/*************************************************/
/* redraw_draw_r()                               */
/*                                               */
/* Recursive back-end to redraw_draw.            */
/*                                               */
/* Parameters: 1 for a top level call, else 0 if */
/*             being called recursively;         */
/*                                               */
/*             X origin for plotting (window     */
/*             coords);                          */
/*                                               */
/*             Y origin for plotting (window     */
/*             coords);                          */
/*                                               */
/*             A pointer to a browser_data       */
/*             structure relevant to the redraw; */
/*                                               */
/*             Pointer to a reformat_cell struct */
/*             holding the lines and chunks to   */
/*             redraw;                           */
/*                                               */
/*             A WimpRedrawWindowBlock pointer,  */
/*             with window area and redraw       */
/*             rectangle details filled in;      */
/*                                               */
/*             1 to plot no backgrounds at all,  */
/*             else they will be shown;          */
/*                                               */
/*             0 for normal redraw, else pointer */
/*             to a token where no content is to */
/*             be drawn - only the elements that */
/*             are needed to indicate selection  */
/*             should be shown. This is used     */
/*             mostly for things like removing   */
/*             borders around images; if bits of */
/*             the image have to be redrawn this */
/*             can make the removal slow. Only   */
/*             one token is allowed as any       */
/*             adjacent images must be redrawn   */
/*             if the border was plotted over    */
/*             them, or redraw anomalies will be */
/*             seen as 'holes' are left behind.  */
/*             There is some intelligence to     */
/*             give different behaviour if       */
/*             selecting or deselecting things.  */
/*                                               */
/* Assumes:    Pointers to items may NOT be NULL */
/*             unless explicitly stated above.   */
/*************************************************/

_kernel_oserror * redraw_draw_r(int toplevel, int xorg, int yorg, browser_data * b, reformat_cell * d, WimpRedrawWindowBlock * r, int noback, HStream * nocontent)
{
  _kernel_oserror * e;
  browser_data    * ancestor = utils_ancestor(b);
  int               more;
  int               l = 0;
  int               page_bottom, page_height;
  int               osxorg, osyorg;
  BBox              wbox, fbox, sbox;

  #ifdef TRACE
    if (tl & (1u<<9)) Printf("\nredraw_draw_r: Called\n");
  #endif

   /* Place the x and y origin in OS units into osxorg and osyorg */

  convert_pair_to_os(xorg, yorg, &osxorg, &osyorg);

  /* Start redraw */

  do
  {
    /* There is an HTML stream, so there is something to redraw. */

    sbox = r->redraw_area; /* Set sbox to hold the redraw rectangle details */

    /* These corrections are to ensure everything is fully redrawn.   */
    /* In particular, Font_StringWidth returns widths based on the    */
    /* distances between adjacent characters. In kerned or italicised */
    /* fonts, one character will typically have a leftmost x coord    */
    /* less than the rightmost point of the previous character. The   */
    /* reformatter uses StringWidth boxes to maintain correct char    */
    /* spacing, but then the redraw routines can go wrong, as part of */
    /* the (say) rightmost letter may fall outside of the StringWidth */
    /* box. Rather than keep two width indications, it's easier just  */
    /* to add a general correction factor to ensure that all line     */
    /* chunks are redrawn within a certain 'tolerance' / distance of  */
    /* the actual redraw rectangle.                                   */

    sbox.xmin -= 12, sbox.xmax += 12;
    sbox.ymin -=  2, sbox.ymax +=  2;

    /* Convert the screen coord redraw rectangle into millipoints */

    convert_box_to_points(&sbox, &fbox);

    /* Convert the screen coord redraw region into work */
    /* area coords, putting the result in wbox.         */

    wbox = sbox;
    coords_box_toworkarea(&wbox, r);

    /* Get the page bottom in work area coordinates, and the page height. */
    /* This is really a printing only concept, where the visible_area     */
    /* BBox will in fact hold the entire page bounding box. The variables */
    /* aren't used for anything else (at present, hence no 'if' wrapper). */

    page_bottom = coords_y_toworkarea(r->visible_area.ymin, r);
    page_height = r->visible_area.ymax - r->visible_area.ymin;

    /* Now the main redraw section. */

    if (b->nchildren)
    {
      /* If this browser has children, it has no directly redrawable content;  */
      /* however, the frames it contains may need borders drawing around them. */

      #ifdef TRACE
        if (tl & (1u<<9)) Printf("redraw_draw_r: Have children\n");
      #endif

      frames_redraw_borders(b, r);
    }
    else
    {
      /* If the browser doesn't have child frames, want to draw */
      /* the document it holds instead.                         */

      #ifdef TRACE
        if (tl & (1u<<9)) Printf("redraw_draw_r: Have no children\n");
      #endif

      if (b->stream)
      {
        #ifdef TRACE
          if (tl &512) Printf("redraw_draw_r: Have a document\n");
        #endif

        /* If printing, handle display style override for backgrounds */

        if (printing)
        {
          /* Force background off for some cases... */

          if      (printstyle_show_none()) noback = 1;
          else if (printstyle_show_in_tables_only)
          {
            if (toplevel) noback = 1;
            else
            {
              /* Need to work out if this table cell has a background colour */

              if (d->cdata && d->cdata[0].t->parent)
              {
                if (TD_HAS_BGCOL(d->cdata[0].t->parent)) noback = 0;
                else                                     noback = 1;
              }
            }
          }

          /* ...and on for others */

          if (printstyle_show_all() && toplevel) noback = 0;
        }

        use_noback = noback;

        /* If we've *not* been told *not* to plot any backgrounds... */

        if (!noback)
        {
          int htop;

          if (!controls.swap_bars) htop = toolbars_button_height(b) + toolbars_url_height(b);
          else                     htop = toolbars_status_height(b);

          if (htop) htop += wimpt_dy();

          /* If background images are not to be shown, or there's no image */
          /* to tile on the background, set the background to a uniform    */
          /* colour. The 'if' statement implicitly calls the background    */
          /* image tiler.                                                  */

          if (
               !b->show_background ||
               (
                 !image_tile_window(b,
                                    r,
                                    0,
                                    -htop)
               )
             )
          {
            redraw_set_colour(redraw_backcol(b));
            bbc_rectanglefill(sbox.xmin, sbox.ymin, sbox.xmax - sbox.xmin + 4, sbox.ymax - sbox.ymin + 4);
          }
        }

        #ifdef TRACE

          if (tl & (1u<<23))
          {
            BBox rectangle = r->redraw_area;

            redraw_set_colour(0xff884400);
            bbc_rectangle(rectangle.xmin,rectangle.ymin,rectangle.xmax-rectangle.xmin-1,rectangle.ymax-rectangle.ymin-1);

            redraw_set_colour(0xffaa6600);
            bbc_rectangle(rectangle.xmin+2,rectangle.ymin+2,rectangle.xmax-rectangle.xmin-5,rectangle.ymax-rectangle.ymin-5);

            redraw_set_colour(0xffcc8800);
            bbc_rectangle(rectangle.xmin+4,rectangle.ymin+4,rectangle.xmax-rectangle.xmin-9,rectangle.ymax-rectangle.ymin-9);
          }

        #endif

        /* Loop over every line in this cell. */

        for (l = 0; l < d->nlines; l++)
        {
          /* If there are line chunks for this line, and the bounding box y */
          /* coordinates lie within the redraw rectangle, process the line. */

          if (
               d->ldata[l].n                                      &&
               osyorg + d->ldata[l].y < wbox.ymax                 &&
               osyorg + d->ldata[l].y + d->ldata[l].h > wbox.ymin
             )
          {
            HStream * tp;            /* Token Pointer */
            fm_face   h;
            int       x, y;          /* Plotting origin */
            int       keepx, keepy;  /* Line's bottom left corner */
            int       base, i;
            char    * dp;            /* Data Pointer */
            int       cn;            /* Chunk Number */

            if (printing == 1 && toplevel)
            {
              /* If printing = 1, it signals that we're not to split lines */
              /* over the bottom of the page. So if this line will drop    */
              /* off the bottom, we need to be careful. This gets a bit    */
              /* hacky now...                                              */
              /*                                                           */
              /* To signal to the printing loop that a line was about to   */
              /* be split but wasn't drawn, the top coord of the line is   */
              /* returned in the xscroll field of the redraw block passed  */
              /* to the function. The printing routines use this to work   */
              /* out where to start the next redraw from.                  */
              /*                                                           */
              /* However, for large lines - e.g. very big images, tables,  */
              /* or lines taller than the whole page - we either should,   */
              /* or in the latter case must, split that line. So, if the   */
              /* line is taller than 1/PrintSplitFraction of the page      */
              /* height, will indeed be split over the page boundary.      */
              /*                                                           */
              /* If you add code here, remember that xscroll must be       */
              /* filled in eventually or the printing loop will exit,      */
              /* assuming there's no more page to draw.                    */

              if (osyorg + d->ldata[l].y < page_bottom)
              {
                if (d->ldata[l].h <= page_height / PrintSplitFraction) /* (See Print.h) */
                {
                  r->xscroll = osyorg + d->ldata[l].y + d->ldata[l].h;
                  return NULL;
                }

                /* The effective 'else' case here has to be handled at the end */
                /* of printing, or you've just scrolled the page a long way to */
                /* right...                                                    */
              }
            }

            /* Put the base address of the line's chunks into cp, */
            /* and point to its associated token in tp.           */

            cn = d->ldata[l].chunks;
            tp = d->cdata[cn].t;

            /* Get the x and y coordinates of the bottom left of the line in */
            /* millipoints into keepx and keepy, and the x and y coordinates */
            /* of the window origin in millipoints into x and y.             */

            keepy = d->ldata[l].y;
            keepx = redraw_start_x(b, d, tp, l);

            convert_pair_to_points(keepx, keepy, &keepx, &keepy);

            x = coords_x_toscreen(0, r);
            y = coords_y_toscreen(0, r);

            convert_pair_to_points(x, y, &x, &y);

            /* Offset the line x and y coordinates by the origin passed into the */
            /* function, and thus get the actual screen coordinates, in milli-   */
            /* points, into x and y.                                             */

            keepx += xorg;
            keepy += yorg;

            x += keepx;
            y += keepy;

            /* Set 'base' to hold the baseline offset in millipoints */

            convert_to_points(d->ldata[l].b, &base);

            /* Loop round for up to all the line chunks whilst staying */
            /* within the redraw rectangle horizontally.               */

            for (
                  i = 0;
                  i < d->ldata[l].n && x < fbox.xmax;
                  x += d->cdata[cn].w, i++, cn++
                )
            {
              if (x + d->cdata[cn].w > fbox.xmin)
              {
                /* Get the token address for this line chunk */

                tp = d->cdata[cn].t;

                /* If 'selected' is not NULL, and nocontent is specifying th at a */
                /* token shouldn't have its contents drawn, then a borders-only   */
                /* redraw is in progress. For removing a border, when 'selected'  */
                /* *is* NULL, want to not draw the contents of the given token    */
                /* but must redraw all others fully, else edge effects will occur */
                /* where tokens directly abut the given one (bits can get knocked */
                /* out as the border goes but the tokens it was plotted over are  */
                /* not redraw).                                                   */
                /*                                                                */
                /* However, for a borders-only redraw when something is being     */
                /* selected, don't want to draw the contents of *anything* as the */
                /* border wants to overplot it. To effect this, set the nocontent */
                /* token to always be the same as the current one.                */

                if (ancestor->selected && nocontent) nocontent = tp;

                /* Deal with table tags */

                if (tp->tagno == TAG_TABLE)
                {
                  int  oh;

                  convert_to_points(d->ldata[l].h, &oh);

                  /* Use of recursion for redraw... So need to keep this code block */
                  /* as a code block, don't try to collapse it down a level and     */
                  /* merge in 'oh' above, etc.                                      */

                  {
                    /* In this case there are table streams hung from d->cdata */

                    table_stream   * table      = (table_stream *) tp;
                    table_row      * row        = NULL;
                    table_headdata * head       = NULL;
                    reformat_cell  * cellarray  = table->cells;
                    reformat_cell  * cell;

                    int              oldback    = 0;
                    int              oldaa      = 0;
                    int              oldbgimage = -1;
                    int              t_noback;

                    BBox             rbox;
                    BBox           * ibox;
                    BBox             tbox;

                    int              cellindex;
                    int              cellcount;
                    int              cellmax = table->ColSpan * table->RowSpan;

                    int              swap;

                    tbox.xmin = tbox.ymin = 0x1000000;
                    tbox.xmax = tbox.ymax = 0;

                    /* Find out the overall bounding box of the table, and put this in */
                    /* 'tbox'.                                                         */

                    cellcount = 0;

                    if (cellarray)
                    {
                      cellcount = 0;
                      row       = table->List;

                      while (row && cellcount < cellmax)
                      {
                        head = row->List;

                        while (head && cellcount < cellmax)
                        {
                          switch (head->Tag)
                          {
                            case TagTableData:
                            case TagTableHead:
                            {
                              cellindex = head->RowOffs * table->ColSpan + head->ColOffs;

                              if (cellindex < cellmax)
                              {
                                BBox cbox;

                                cell = &cellarray[cellindex];

                                /* Set the graphics rectangle up for the redraw */

                                cbox.xmin = x + cell->x;
                                cbox.ymin = y + cell->y + oh - cell->cellheight;
                                cbox.xmax = cbox.xmin + cell->cellwidth;
                                cbox.ymax = cbox.ymin + cell->cellheight;

                                convert_box_to_os(&cbox, &cbox);

                                /* Update the table bounding box as required */

                                if (cbox.xmin < tbox.xmin) tbox.xmin = cbox.xmin;
                                if (cbox.ymin < tbox.ymin) tbox.ymin = cbox.ymin;
                                if (cbox.xmax > tbox.xmax) tbox.xmax = cbox.xmax;
                                if (cbox.ymax > tbox.ymax) tbox.ymax = cbox.ymax;

                              /* Closure of 'if (cellindex < cellmax)' */
                              }

                            /* Closure of specific 'case' item */
                            }
                            break;

                          /* Closure of 'switch (head->Tag)' */
                          }

                          cellcount ++;

                          head = head->Next;

                        /* Closure of 'while (head && ...)' */
                        }

                        row = row->Next;

                      /* Closure of 'while (row && ...)' */
                      }

                      /* tbox doesn't take account of cell spacing yet - correct */
                      /* for this now.                                           */

                      tbox.xmin -= table->cellspacing * 2;
                      tbox.ymin -= table->cellspacing * 2;
                      tbox.xmax += table->cellspacing * 2;
                      tbox.ymax += table->cellspacing * 2;

                      /* If the table has a background colour, draw this */

                      if (TABLE_HAS_BGCOL(table))
                      {
                        int tx, ty;
                        int tw, th;

                        /* Work out the x and y coordinates of the lower left hand pixel */
                        /* of the table border, and the width and height of the table    */
                        /* including the border.                                         */

                        tx = tbox.xmin;
                        ty = tbox.ymin;
                        tw = tbox.xmax - tbox.xmin;
                        th = tbox.ymax - tbox.ymin;

                        /* Draw the background */

                        redraw_set_colour(TABLE_BGCOL(table));

                        bbc_rectanglefill(tx, ty, tw - 1, th - 1);
                      }

                      /* Now redraw the table cells */

                      cellcount = 0;
                      row       = table->List;

                      while (row && cellcount < cellmax)
                      {
                        head = row->List;

                        while (head && cellcount < cellmax)
                        {
                          switch (head->Tag)
                          {
                            case TagTableData:
                            case TagTableHead:
                            {
                              cellindex = head->RowOffs * table->ColSpan + head->ColOffs;

                              if (cellindex < cellmax)
                              {
                                cell = &cellarray[cellindex];

                                #ifdef TRACE
                                  if (tl & (1u<<20)) Printf("redraw_draw call: %d -%d\n", keepx + cell->x, -(keepy + cell->y));
                                #endif

                                if (TD_HAS_BGCOL(head))
                                {
                                  oldback              = b->background_colour;
                                  oldaa                = b->antialias_colour;
                                  oldbgimage           = b->background_image;
                                  b->background_colour = TD_BGCOL(head);
                                  b->antialias_colour  = b->background_colour;
                                  b->background_image  = -1; /* For now, no background images in table cells. */

                                  t_noback = 0;
                                }
                                else t_noback = 1;

                                /* Set the graphics rectangle up for the redraw */

                                rbox.xmin = x + cell->x;
                                rbox.ymin = y + cell->y + oh - cell->cellheight;
                                rbox.xmax = rbox.xmin + cell->cellwidth;
                                rbox.ymax = rbox.ymin + cell->cellheight;

                                convert_box_to_os(&rbox, &rbox);

                                /* (rbox's max coords should be inclusive, not exclusive) */

                                rbox.xmax--;
                                rbox.ymax--;

                                /* If necessary, swap min and max coords */

                                if (rbox.xmax < rbox.xmin) swap = rbox.xmax, rbox.xmax = rbox.xmin, rbox.xmin = swap;
                                if (rbox.ymax < rbox.ymin) swap = rbox.ymax, rbox.ymax = rbox.ymin, rbox.ymin = swap;

                                /* See if the cell redraw box intersects the overall redraw rectangle */

                                if (rbox.xmin == rbox.xmax || rbox.ymin == rbox.ymax) ibox = NULL;
                                else                                                  ibox = set_graphics_intersection(&rbox, &r->redraw_area);

                                if (ibox)
                                {
                                  BBox oldrect;

                                  /* Other routines that set the graphics window do it the same way as */
                                  /* here - they assume the redraw rectangle = the graphics window, as */
                                  /* during printing it's not possible to read the VDU variables to    */
                                  /* obtain the actual window. To ensure that this holds true, before  */
                                  /* recursively calling the redraw functions, the redraw rectangle    */
                                  /* must be set to match the graphics rectangle (and then restored    */
                                  /* afterwards).                                                      */

                                  oldrect        = r->redraw_area;
                                  r->redraw_area = *ibox;

                                  /* All coords in ibox are inclusive; the max coords in a redraw rectangle */
                                  /* need to be exclusive.                                                  */

                                  r->redraw_area.xmax ++;
                                  r->redraw_area.ymax ++;

                                  /* Recursive call to redraw the cell contents */

                                  redraw_draw_r(0,
                                                keepx + cell->x,
                                                keepy + cell->y + oh,
                                                b,
                                                cell,
                                                r,
                                                t_noback,
                                                nocontent);

                                  /* Restore the WimpRedrawWindowBlock redraw rectangle */

                                  r->redraw_area = oldrect;

                                  /* Restore the actual graphics rectangle */

                                  restore_graphics_intersection(&oldrect);
                                }

                                #ifdef TRACE
                                  if (tl & (1u<<11))
                                  {
                                    /* Outline the cell BBox */

                                    redraw_set_colour(0xff00aa00);
                                    bbc_rectangle(rbox.xmin, rbox.ymin, rbox.xmax - rbox.xmin - 1, rbox.ymax - rbox.ymin - 1);

                                    redraw_set_colour(0xff22cc00);
                                    bbc_rectangle(rbox.xmin + 2, rbox.ymin + 2, rbox.xmax - rbox.xmin - 5, rbox.ymax - rbox.ymin - 5);
                                  }
                                #endif

                                /* Restore any data altered in b */

                                if (!t_noback)
                                {
                                  b->background_colour = oldback;
                                  b->antialias_colour  = oldaa;
                                  b->background_image  = oldbgimage;
                                }

                                /* Draw the slabbed in cell border. */

                                if (TABLE_BORDER(table))
                                {
                                  int dx = wimpt_dx();
                                  int dy = wimpt_dy();
                                  int hx = dx - 1;
                                  int hy = dy - 1;

                                  int max;

                                  int cx, cy;
                                  int cw, ch;

                                  /* Get the cell x,y and w,h in OS units from the redraw box. We want to */
                                  /* use this information rather than cell->x etc. as the redraw box is   */
                                  /* the item that any internal redraws will have adhered to, including   */
                                  /* plotting cell backgrounds (if present). Thus, we want any table      */
                                  /* borders to be based on those same coordinates.                       */

                                  cx = rbox.xmin;
                                  cy = rbox.ymin;
                                  cw = rbox.xmax - rbox.xmin + 1;
                                  ch = rbox.ymax - rbox.ymin + 1;

                                  /* Get the maximum horizontal OS to pixel scaling values, as this is */
                                  /* used as a threshold for the 2D border / 3D border switching.      */

                                  if (dy > dx) max = dy;
                                  else         max = dx;

                                  #ifdef TRACE
                                    if (tl & (1u<<20)) Printf("cell box at %d %d %d %d\n",cx,cy,cw,ch);
                                  #endif

                                  /* Don't do any actual drawing if the Choices don't say so. We have to */
                                  /* do the calculation stuff above (well, most of it...!) so that the   */
                                  /* outer border plotter code (below) will work, whether inner borders  */
                                  /* are plotted or not.                                                 */

                                  if (choices.table_inner != Choices_TableInner_Never)
                                  {
                                    /* For tables with a cell spacing greater than the OS unit */
                                    /* to pixel scaling value, use a 3D border (depending on   */
                                    /* what is specified in the Choices).                      */

                                    if (
                                         choices.table_inner != Choices_TableInner_Always2D &&
                                         (
                                           table->cellspacing > max ||
                                           choices.table_inner == Choices_TableInner_Always3D
                                         )
                                       )
                                    {
                                      redraw_set_colour(Redraw_Colour_AlmostWhite);

                                      bbc_rectanglefill(cx,      cy - dy, cw - hx, hy);
                                      bbc_rectanglefill(cx + cw, cy - dy, hx,      ch + hy);

                                      redraw_set_colour(Redraw_Colour_PlinthGrey);

                                      bbc_rectanglefill(cx - dx, cy - dy,      hx, ch + dy + hy);
                                      bbc_rectanglefill(cx,      cy + ch, cw + hx, hy);
                                    }

                                    /* Otherwise use a black 2D border. If we've got half of the OS unit to */
                                    /* pixel spacing available, then there'll be at least 1 pixel between   */
                                    /* all cells so we can draw in that gap. Otherwise, we must plot over   */
                                    /* the cell contents.                                                   */

                                    else if (table->cellspacing >= (max >> 1))
                                    {
                                      redraw_set_colour(Redraw_Colour_Black);

                                      bbc_rectanglefill(cx - dx, cy - dy, cw + hx, hy);
                                      bbc_rectanglefill(cx + cw, cy - dy, hx,      ch + hy);
                                      bbc_rectanglefill(cx - dx, cy,      hx,      ch + hy);
                                      bbc_rectanglefill(cx,      cy + ch, cw + hx, hy);
                                    }
                                    else
                                    {
                                      redraw_set_colour(Redraw_Colour_Black);

                                      bbc_rectanglefill(cx,           cy,           cw - hx, hy);
                                      bbc_rectanglefill(cx + cw - dx, cy,           hx,      ch - hy);
                                      bbc_rectanglefill(cx,           cy,           hx,      ch - hy);
                                      bbc_rectanglefill(cx,           cy + ch - dy, cw - hx, hy);
                                    }
                                  }
                                }

                              /* Closure of 'if (cellindex < cellmax)' */
                              }

                            /* Closure of specific 'case' item */
                            }
                            break;

                          /* Closure of 'switch (head->Tag)' */
                          }

                          cellcount ++;

                          head = head->Next;

                        /* Closure of 'while (head && ...)' */
                        }

                        row = row->Next;

                      /* Closure of 'while (row && ...)' */
                      }

                      /* Right, now redraw the slabbed out outer table border */

                      if (TABLE_BORDER(table) && choices.table_outer != Choices_TableOuter_Never)
                      {
                        int dx = wimpt_dx();
                        int dy = wimpt_dy();

                        int max;

                        int tx, ty;
                        int tw, th;
                        int tb;

                        tb = TABLE_BORDER(table) * 2; /* 1 'web pixel' = 2 OS */

                        /* Work out the x and y coordinates of the lower left hand pixel */
                        /* of the table border, and the width and height of the table    */
                        /* including the border.                                         */

                        tx = tbox.xmin - tb;
                        ty = tbox.ymin - tb;
                        tw = tbox.xmax - tbox.xmin + tb * 2;
                        th = tbox.ymax - tbox.ymin + tb * 2;

                        /* Get the maximum horizontal OS to pixel scaling values, as this is */
                        /* used as a threshold for the 2D border / 3D border switching.      */

                        if (dy > dx) max = dy;
                        else         max = dx;

                        /* Use the same threshold value on the border as for internal cell borders */

                        if (
                             choices.table_outer != Choices_TableOuter_Always2D &&
                             (
                               table->cellspacing > max ||
                               choices.table_outer == Choices_TableOuter_Always3D
                             )
                           )
                        {
                          int x[4], y[4];

                          /* There are 8 corners to a plinth,  6------4  */
                          /* we draw using 8 triangles. Work   |\    /|  */
                          /* these out first in the arrays     | 7--5 |  */
                          /* declared above to make the        | |  | |  */
                          /* plotting code tidier and avoid    | 1--3 |  */
                          /* unnecessary recalculation of      |/    \|  */
                          /* corner coordinates.               0------2  */
                          /*                                             */
                          /* Note how there are only 4 unique x or y     */
                          /* values, so that's all we need to work out.  */

                          x[0] = tx;
                          x[1] = tx + tb - 1;
                          x[2] = tx + tw - tb;
                          x[3] = tx + tw - 1;

                          y[0] = ty;
                          y[1] = ty + tb - 1;
                          y[2] = ty + th - tb;
                          y[3] = ty + th - 1;

                          /* OK, now do the drawing. We need to be careful about  */
                          /* the direction that the drawing occurs to ensure that */
                          /* adjacent diagonal lines meet up correctly.           */

                          redraw_set_colour(Redraw_Colour_PlinthGrey);

                          /* Bottom edge */

                          bbc_trianglefill(x[0], y[0], x[1], y[1], x[3], y[0]); /* 0 -> 1 -> 2 */
                          bbc_trianglefill(x[1], y[1], x[3], y[0], x[2], y[1]); /* 1 -> 2 -> 3 */

                          /* Right hand edge */

                          bbc_trianglefill(x[3], y[0], x[2], y[1], x[3], y[3]); /* 2 -> 3 -> 4 */
                          bbc_trianglefill(x[2], y[1], x[3], y[3], x[2], y[2]); /* 3 -> 4 -> 5 */

                          /* Now the lighter section */

                          redraw_set_colour(Redraw_Colour_AlmostWhite);

                          /* Top edge */

                          bbc_trianglefill(x[3], y[3], x[2], y[2], x[0], y[3]); /* 4 -> 5 -> 6 */
                          bbc_trianglefill(x[2], y[2], x[0], y[3], x[1], y[2]); /* 5 -> 6 -> 7 */

                          /* Finally, the left hand edge. */

                          bbc_trianglefill(x[0], y[3], x[1], y[2], x[0], y[0]); /* 6 -> 7 -> 0 */
                          bbc_trianglefill(x[1], y[2], x[0], y[0], x[1], y[1]); /* 7 -> 0 -> 1 */
                        }

                        /* Otherwise, use a 2D outer border. */

                        else
                        {
                          redraw_set_colour(Redraw_Colour_Black);

                          bbc_rectanglefill(tx,           ty,           tw - 1, tb - 1);
                          bbc_rectanglefill(tx,           ty + th - tb, tw - 1, tb - 1);
                          bbc_rectanglefill(tx,           ty,           tb - 1, th - 1);
                          bbc_rectanglefill(tx + tw - tb, ty,           tb - 1, th - 1);
                        }
                      }

                    /* Closure of 'if (cellarray)' */
                    }

                  /* Closure of unconditional code block dealing with redrawing */
                  /* the body of a table.                                       */
                  }
                }

                /* Deal with forms elements */

                else if (tp->tagno == TAG_INPUT || tp->tagno == TAG_TEXTAREA || tp->tagno == TAG_SELECT)
                {
                  /* A text-based element */

                  if (
                       tp->tagno == TAG_TEXTAREA               ||
                       tp->tagno == TAG_SELECT                 ||
                       HtmlINPUTtype(tp) == inputtype_TEXT     ||
                       HtmlINPUTtype(tp) == inputtype_PASSWORD
                     )
                  {
                    BBox    box;
                    int     ox, oy;
                    fm_face fh;

                    convert_pair_to_os(x, y + base, &ox, &oy);

                    fh = fm_find_token_font(b, tp, 0);
                    fm_font_box(fh, &box);

                    /* Set up the bounding box for a text area, with a minimum of 2 rows */

                    if (tp->tagno == TAG_TEXTAREA)
                    {
                      int r;
                      int lh, lb;

                      form_get_linesize(&box, &lh, &lb);

                      r = tp->rows;
                      if (r < 2) r = 2;

                      box.ymin -= lh * (r - 1); /* ymin is already below the first line, so want to drop it by (rows - 1) more */
                    }

                    /* Account for the borders */

                    box.ymin = box.ymin + oy - 8;
                    box.ymax = box.ymax + oy + 8;
                    box.xmin = ox + 4;

                    convert_to_os(x + d->cdata[cn].w, &box.xmax);

                    /* Account for a border */

                    box.xmax -= 4;

                    if (nocontent != tp)
                    {
                      redraw_input_field(b, tp, &box, redraw_token_colour(b, tp), tp->tagno == TAG_SELECT);

                      fm_set_font_colour(fh,
                                         redraw_token_colour(b, tp),
                                         (tp->tagno == TAG_SELECT) ? Redraw_Colour_BackGrey : Redraw_Colour_White);

                      form_textarea_redraw(b,
                                           d->cdata[cn].t,
                                           &box,
                                           &r->redraw_area,
                                           fh,
                                           tp->tagno == TAG_TEXTAREA,
                                           tp->tagno == TAG_INPUT && HtmlINPUTtype(tp) == inputtype_PASSWORD);
                    }

                    /* If the element is a SELECT field, it needs a menu icon too */

                    if (tp->tagno == TAG_SELECT)
                    {
                      int  width, height, offset;
                      BBox icon;

                      /* Get the sprite size, work out a bounding box and plot */
                      /* this as a virtual icon.                               */

                      read_sprite_size("fgright", &width, &height);

                      /* Allow for the border */

                      width  += 4;
                      height += 4;

                      /* Work out the vertical offset */

                      offset = (box.ymax - box.ymin - height) / 2;

                      icon.xmin = box.xmax - width;
                      icon.ymin = box.ymin + offset;
                      icon.xmax = box.xmax;
                      icon.ymax = box.ymin + offset + height;

                      if (redraw_selected(b, tp)) redraw_border_around_box(&icon, b->selected_colour);

                      coords_box_toworkarea(&icon, r);

                      if (nocontent != tp)
                      {
                        WimpPlotIconBlock block;

                        block.bbox  = icon;
                        block.flags = 0x1700311A;

                        if (redraw_backcol(b) != Redraw_Colour_BackGrey) block.flags |= (1<<2); /* Border if not using Wimp grey background */

                        block.data.is.sprite             = "fgright";
                        block.data.is.sprite_area        = (void *) sprite_block;
                        block.data.is.sprite_name_length = sizeof("fgright") - 1;

                        wimp_plot_icon(&block);
                      }
                    }
                  }
                  else switch(HtmlINPUTtype(tp))
                  {
                    /* Graphics-based forms elements */

                    case inputtype_CHECKBOX:
                    {
                      if (nocontent != tp) redraw_switch(b,
                                                         tp,
                                                         x,
                                                         y + base,
                                                         form_get_field(b, d->cdata[cn].t) -> checked ? "fopton" : "foptoff",
                                                         r);
                    }
                    break;

                    case inputtype_RADIO:
                    {
                      if (nocontent != tp) redraw_switch(b,
                                                         tp,
                                                         x,
                                                         y + base,
                                                         form_get_field(b, d->cdata[cn].t) -> checked ? "fradioon" : "fradiooff",
                                                         r);
                    }
                    break;

                    case inputtype_IMAGE: goto do_image; /* See a short distance below */

                    case inputtype_HIDDEN: break;

                    case inputtype_SUBMIT: /* SUBMIT same as RESET: no break */
                    case inputtype_BUTTON: /* Again, no break                */
                    case inputtype_RESET:
                    {
                      BBox         box;
                      int          fh, ox, oy, colour;
                      const char * p;

                      p = form_button_text(tp);

                      convert_pair_to_os(x, y + base, &ox, &oy);

                      fh = fm_find_token_font(b, tp, 0);

                      fm_font_box(fh,&box);

                      box.ymin = box.ymin + oy - 8;
                      box.ymax = box.ymax + oy + 8;
                      box.xmin = ox + 4;

                      convert_to_os(x + d->cdata[cn].w,&box.xmax);

                      /* Account for a border */

                      box.xmax -= 4;

                      /* Draw the button's plinth */

                      colour = redraw_token_colour(b, tp);

                      if (b->highlight == tp)
                      {
                        if (nocontent != tp)
                        {
                          redraw_button(b, tp, &box, 1);
                          fm_set_font_colour(fh, colour, Redraw_Colour_MidGrey);
                        }
                      }
                      else
                      {
                        if (nocontent != tp)
                        {
                          redraw_button(b, tp, &box, 0);
                          fm_set_font_colour(fh, colour, Redraw_Colour_BackGrey);
                        }
                      }

                      /* Plot the text, centred horizontally */

                      if (p && nocontent != tp)
                      {
                        int length, end, width;

                        length = strlen(p);
                        end    = 0;

                        while(end < length && p[end] != '\n') end++;

                        e = fm_get_string_width(fh,
                                                p,
                                                0x1000000,
                                                end - d->cdata[cn].o,
                                                -1,
                                                NULL,
                                                &width);

                        width = (d->cdata[cn].w - width) / 2 + 4;
                        if (width < 0) width = 0;

                        fm_puts(fh, x + width, y + base, p, 0, 0);
                      }
                    }
                    break;
                  }
                }

                /* Plot an image */

                else if (tp->style & IMG)
                {
                  BBox box;
                  int  ox, oy, o;

do_image: /* (This code is also used for form INPUT TYPE=IMAGE tags; see above) */

                  convert_pair_to_os(x, y + base, &ox, &oy);

                  if (!reformat_get_image_size(b, tp, &box))
                  {
                    /* Correct the coordinates for plotting */

                    ox -= box.xmin;

                    box.xmin += ox;
                    box.ymin += oy;
                    box.xmax += ox;
                    box.ymax += oy;

                    /* Find the border width (if any) */

                    o = (tp->style & IMG) ? tp->maxlen * 2 : 0;

                    /* Ensure the image has the plotting position recorded within */
                    /* it's associated image_info structure, so that update       */
                    /* routines elsewhere will know where to plot it              */

                    image_set_token_image_position(b,
                                                   tp,
                                                   coords_x_toworkarea(box.xmin + o, r),
                                                   coords_y_toworkarea(box.ymin + o, r));

                    /* Draw a border of tp->maxlen * 2 OS units width around an image. */
                    /* This comes from the image's BORDER attribute. Links default to  */
                    /* a 2 pixel (4 OS unit) border in HTMLLib, whereas non-link       */
                    /* images default to no colour.                                    */

                    if (tp->tagno == TAG_INPUT && redraw_selected(b, tp))
                    {
                      redraw_border_around_box(&box, b->selected_colour);
                    }
                    else
                    {
                      if (o)
                      {
                        redraw_set_colour(redraw_token_colour(b, tp));

                        bbc_rectanglefill(box.xmin,     box.ymin,     box.xmax - box.xmin - 1, o - 1);
                        bbc_rectanglefill(box.xmin,     box.ymin,     o - 1,                   box.ymax - box.ymin - 1);
                        bbc_rectanglefill(box.xmin,     box.ymax - o, box.xmax - box.xmin - 1, o - 1);
                        bbc_rectanglefill(box.xmax - o, box.ymin,     o - 1,                   box.ymax - box.ymin - 1);
                      }
                      else if (redraw_selected(b, tp))
                      {
                        redraw_border_around_box(&box, b->selected_colour);

// Hmm. Need to find a
// way of invoking this
// reliably, and clearing
// the highlight without
// flicking horribly.
//
//                        /* Things get rather more complicated for client side maps, if */
//                        /* the pointer is over them.                                   */
//
//                        if (b->pointer_over == tp && (tp->type & TYPE_ISCLIENTMAP))
//                        {
//                          /* Ask the client side map handler to do this bit */
//
//                          csim_highlight_region(b, b->selected_colour, box.xmin + o, box.ymax - o);
//                        }
                      }
                    }

                    /* Redraw the image itself */

                    if (nocontent != tp) RetError(image_redraw(b, r, d->cdata[cn].t, o + box.xmin, o + box.ymin));
                  }
                }

                /* Plot an OBJECT, EMBED or APPLET tag */

                else if (ISOBJECT(tp))
                {
                  BBox box;
                  int  ox, oy, o;

                  convert_pair_to_os(x, y + base, &ox, &oy);

                  if (!reformat_get_object_size(b, tp, &box))
                  {
                    o = HtmlOBJECTborder(tp) * 2;

                    ox -= box.xmin;

                    box.xmin += ox;
                    box.ymin += oy;
                    box.xmax += ox;
                    box.ymax += oy;

                    object_set_token_object_position(b,
                                                     tp,
                                                     coords_x_toworkarea(box.xmin + o, r),
                                                     coords_y_toworkarea(box.ymin + o, r));

                    /* Draw a border, if required */

                    if (HtmlOBJECTborder(tp))
                    {
                      if (o)
                      {
                        redraw_set_colour(redraw_token_colour(b, tp));

                        bbc_rectanglefill(box.xmin,     box.ymin,     box.xmax - box.xmin - 1, o - 1);
                        bbc_rectanglefill(box.xmin,     box.ymin,     o - 1,                   box.ymax - box.ymin - 1);
                        bbc_rectanglefill(box.xmin,     box.ymax - o, box.xmax - box.xmin - 1, o - 1);
                        bbc_rectanglefill(box.xmax - o, box.ymin,     o - 1,                   box.ymax - box.ymin - 1);
                      }
                      else if (redraw_selected(b, tp))
                      {
                        redraw_border_around_box(&box, b->selected_colour);
                      }
                    }

                    if (nocontent != tp) RetError(object_redraw(b, r, d->cdata[cn].t, o + box.xmin, o + box.ymin));
                  }
                }

                /* Plot a horizontal rule */

                else if (tp->style & HR)
                {
                  int w, h, lmarg, ox, oy = 0;

                  convert_to_os(y, &oy);

                  lmarg = redraw_start_x(b, d, tp, l);

                  convert_to_os(d->cdata[cn].w, &w);

                  /* Round width to a multiple of 2 and limit check it. */
                  /* Allow sizes greater than the available width, in   */
                  /* which case align to the left and draw to whatever  */
                  /* width was requested.                               */

                  w = w &~ 1;
                  if (w < 2) w = 2;

                  /* Deal with a size (height) specifier */

                  if (HR_HAS_SIZE(tp))
                  {
                    /* Currently only recognise pixels */

                    switch (HR_SIZE_UNITS(tp))
                    {
                      case UNITS_PIXELS: h = HR_SIZE(tp) * 2; break;

                      /* (IMPORTANT: If adding extra units, ensure h ends up a multiple of 2) */

                      default: h = 4; break;
                    }
                  }
                  else h = 4;

                  /* Limit check the height */

                  if (h < 2) h = 2;

                  /* Sort out the horizontal and vertical plotting offsets; */
                  /* centre vertically, and align horizontally as specified */
                  /* in the token.                                          */

                  oy += ((d->ldata[l].h - h) / 2) &~3 - 4;
                  oy += 6;

                  ox = (coords_x_toscreen(lmarg + osxorg, r) &~1);

                  /* Plot a black rule if NOSHADE is specified or the height */
                  /* or width are less than 4 OS units, else plot a '3D'     */
                  /* rule.                                                   */

                  if (HR_NOSHADE(tp) || h < 4 || w < 4)
                  {
                    redraw_set_colour(0);
                    bbc_rectanglefill(ox, oy, w - 1, h - 1);
                  }
                  else
                  {
                    if (h == 4)
                    {
                      /* Simple 'groove' rule */

                      h = h / 2;

                      redraw_set_colour(Redraw_Colour_MidGrey);
                      bbc_rectanglefill(ox, oy, w - 1, h - 1);
                      redraw_set_colour(Redraw_Colour_AlmostWhite);
                      bbc_rectanglefill(ox, oy - h, w - 1, h - 1);
                    }
                    else
                    {
                      /* 3D 'box' rule */

                      redraw_set_colour(Redraw_Colour_AlmostWhite);
                      bbc_rectanglefill(ox, oy, w - 1, 1);
                      bbc_rectanglefill(ox + w - 2, oy, 1, h - 1);
                      redraw_set_colour(Redraw_Colour_MidGrey);
                      bbc_rectanglefill(ox, oy + h - 2, w - 1, 1);
                      bbc_rectanglefill(ox, oy, 1, h - 1);
                    }
                  }
                }

                /* Plot a bullet point */

                else if(ISBULLET(tp))
                {
                  int ox,oy;

                  convert_pair_to_os(x, y + base, &ox, &oy);
                  redraw_bullet(ox, oy, tp->indent, r);
                }

                /* Plot some text */

                else
                {
                  dp = d->cdata[cn].t->text;

                  if (dp)
                  {
                    BBox size;
                    int  c, yofs, height;

                    /* Find the font handle for the token, and its colour */

                    h = fm_find_token_font(b, tp, 0);
                    c = redraw_token_colour(b, tp);

                    /* Find the text height in OS units */

                    fm_font_box(h, &size);
                    height = size.ymax - size.ymin;

                    /* Work out the y offset to plot at */

                    if (ISSUP(tp))
                    {
                      /* Shift baseline up for superscript text. The following */
                      /* will be for the SUP size text, remember...            */

                      convert_to_points(height, &yofs);

                      /* SUP height = normht * 3 / 5, so to get normal  */
                      /* height from SUP do height * 5 / 3. Then want   */
                      /* to get the height remaining and use this as an */
                      /* addition for the y positioning, so need to add */
                      /* (normht - hormht * 3 / 5) = normht * 2 / 5.    */
                      /* This all simplifies out to height * 2 / 3, but */
                      /* this looks too high in practice, so it's taken */
                      /* down a bit from that!                          */

                      yofs = y + base + (yofs / 2);
                    }
                    else if (ISSUB(tp))
                    {
                      /* Shift the baseline down a bit for subscript text */

                      yofs = y + (base * 4) / 5;
                    }
                    else yofs = y + base;

                    /* Set the font colour and plot the text */

                    fm_set_font_colour(h,c,redraw_background_colour(b,c));

                    if (dp) fm_putsl(h,
                                     x,
                                     yofs,
                                     dp + d->cdata[cn].o,
                                     d->cdata[cn].l,
                                     0,
                                     b->background_image >= 0 && b->show_background);

                    /* Deal with underlining. The position should not be affected */
                    /* by SUB or SUP text.                                        */

                    if (
                         (
                           (
                             ISLINK(tp) &&
                             b->underline_links
                           )
                           || ISUNDERLINE(tp)
                         )
                         && !(tp->tagno == TAG_TABLE)
                       )
                    {
                      /* Underline the item - set the colour, and start at the item's x coordinate... */

                      int ox,oy;

                      /* Set the colour */

                      redraw_set_colour(redraw_token_colour(b, tp));

                      /* Work out the coordinates (in OS units) */

                      convert_pair_to_os(x, y + base, &ox, &oy);
                      oy -= 7;

                      /* Move to the start point */

                      bbc_move(ox, oy);

                      /* ...finish at x plus its width. */

                      convert_to_os(x + d->cdata[cn].w, &ox);

                      bbc_draw(ox, oy);
                    }

                    /* Deal with STRIKE text. This needs to have the strikethrough */
                    /* line through the text middle, as opposed to following the   */
                    /* body text font baseline (so SUB and SUP *will* have an      */
                    /* effect on the positioning).                                 */

                    if (ISSTRIKE(tp) && !(tp->tagno == TAG_TABLE))
                    {
                      int ox, oy;
                      int hs;

                      redraw_set_colour(redraw_token_colour(b, tp));

                      convert_pair_to_os(x, yofs, &ox, &oy);
                      hs = height / 4;
                      oy += hs;

                      bbc_move(ox, oy);

                      convert_to_os(x + d->cdata[cn].w, &ox);

                      bbc_draw(ox, oy);
                    }
                  }
                }

                // Plot the bounding box of any object; green to
                // mark an image, else red

                #ifdef TRACE

                  if ((tl & (1u<<11)) || (tl & (1u<<19)))
                  {
                    int ox, oy, ow, oh;

                    convert_pair_to_os(x, y, &ox, &oy);

                    convert_to_os(d->cdata[cn].w, &ow);

                    oh = d->ldata[l].h;

                    if (tl & (1u<<11))
                    {
                      _swix(Wimp_SetColour,
                            _IN(0),

                            (tp->style & IMG) ? 10 : 11);

                      bbc_rectangle(ox, oy, ow - 1, oh - 1);
                    }

                    // Mark tokens with no lower bits set in the type word
                    // (so not head, body, frameset etc.) and a NULL text
                    // field, with a magenta dot in the bottom *right* of
                    // the token BBox and a cyan dot in the top right of the
                    // BBox respectively.

                    if (tl & (1u<<19))
                    {
                      if (!(tp->type & 0xff))
                      {
                        redraw_set_colour(0xff00ff00);
                        bbc_circlefill(ox + ow - 1, oy, 6);
                        redraw_set_colour(0);
                        bbc_circle(ox + ow - 1, oy, 6);
                      }

                      if (!tp->text)
                      {
                        redraw_set_colour(0xffff0000);
                        bbc_circlefill(ox + ow - 1, oy + oh - 1, 6);
                        redraw_set_colour(0);
                        bbc_circle(ox + ow - 1, oy + oh - 1, 6);
                      }
                    }
                  }
                #endif

              /* Closure of long 'if' checking if the current chunk */
              /* lies partially or entirely within the redraw area. */
              /* If it does, the code above executes.               */
              }

            /* Closure of 'for' looping round chunks on a given line */
            /* that lies partially or entirely within the redraw     */
            /* area.                                                 */
            }

            /* For printing, tell the print routines where we were up to */

            if (
                 printing == 1 &&
                 toplevel      &&
                 osyorg + d->ldata[l].y < page_bottom
               )
               r->xscroll = osyorg + page_bottom;

          /* Closure of long 'if' checking if the current line lies */
          /* partially or entirely within the redraw area. The code */
          /* above executes if it does.                             */
          }

        /* Closure of 'for' looping for all lines in the document. */
        }

      /* Closure of long 'if' checking if d->stream was not NULL. */
      /* If not, then there is a document to plot; so execute the */
      /* above code. Else, execute the code below.                */
      }

      else
      {
        if (!printing || !toplevel)
        {
          /* Set the graphics background colour to the default  */
          /* and clear the graphics rectangle [to this colour]. */

          redraw_set_colour(choices.background_colour);

          bbc_rectanglefill(r->redraw_area.xmin,
                            r->redraw_area.ymin,
                            r->redraw_area.xmax - r->redraw_area.xmin,
                            r->redraw_area.ymax - r->redraw_area.ymin);
        }

        /* If there's a fetch URL but no stream, the document was empty */

        if (browser_current_url(b) && !fetch_fetching(b))
        {
          fm_face h;
          int     x, y, htop;
          BBox    size;

          /* Claim a font */

          h = fm_find_font(b, "sans", (int) (choices.font_size * 1.5), (int) (choices.font_size * 1.5), 0, 1);

          /* Find the height of the tallest character */

          fm_font_box(h, &size);

          /* Use that height, and the toolbar sizes to work out the y coordinate to plot at */

          if (!controls.swap_bars) htop = toolbars_button_height(b) + toolbars_url_height(b);
          else                     htop = toolbars_status_height(b);

          if (htop) htop += wimpt_dy();

          y = coords_y_toscreen(htop - size.ymax - size.ymin - 40, r); /* -40 = arbitrary constant, aesthetic consideration */

          /* The x coordinate has a fixed offset from the left */

          x = coords_x_toscreen(32,r);

          #ifdef TRACE
            if (tl & (1u<<9))
            {
              Printf("redraw_draw_r: Empty page, claimed font %p\n",(void *) h);
              Printf("               Plotting x,y %d,%d\n",x,y);
            }
          #endif

          /* Set a black-on-grey font colour */

          fm_set_font_colour(h, choices.text_colour, choices.background_colour);

          /* Write the string */

          fm_puts(h,
                  x,
                  y,
                  lookup_token("NoData:The server returned a blank page.",
                               0,
                               0),
                  1,
                  1);

          if (printing)
          {
            r->xscroll = 0;
            return NULL;
          }
        }
      }

    /* Closure of long 'if' checking if the browser window had */
    /* children. If not, the code immediately above - normal   */
    /* redraw - may be run, else special frame border redraw   */
    /* code is run.                                            */
    }

    #ifdef ANTI_TWITTER

      if (!printing && toplevel) anti_twitter(r);

    #endif

    if (!printing && toplevel) wimp_get_rectangle(r,&more);
    else more = 0;
  }
  while (more);

  /* Finished... */

  return NULL;
}