/* 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 "Fetch.h"
#include "FontManage.h"
#include "Forms.h"
#include "Frames.h"
#include "Images.h"
#include "Reformat.h"
#include "Tables.h"
#include "TokenUtils.h"
#include "Toolbars.h"

#include "Redraw.h"

/* Static function prototypes */

static void redraw_border_around_box (BBox * box, int colour);
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);

/* 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.col_back = %p\n",(void *) choices.col_back);
  #endif

  return (((b->backgroundcol == -1) || (!b->sourcecolours)) ? (choices.col_back) : (b->backgroundcol));
}

/*************************************************/
/* 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->plainback) return redraw_backcol(b);

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

    case -1: return foregroundcolour;

    /* If aacol 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->backgroundcol == -1 ? foregroundcolour : b->backgroundcol);
  }

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

  return (!b->sourcecolours ? choices.col_back : b->aacol);
}

/*************************************************/
/* 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->style & (INPUT | TEXTAREA | SELECT)) return 0;

  /* 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->sourcecolours ? b->follcol : choices.col_foll);

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

    if (redraw_selected(b, t)) return (b->sourcecolours ? b->selecol : choices.col_sele);

    /* 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->sourcecolours ? b->usedcol : choices.col_used);

    return (b->sourcecolours ? b->linkcol : choices.col_link);
  }

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

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

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

  return (b->sourcecolours ? b->textcol : choices.col_text);
}

/*************************************************/
/* 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->table) return b->display_width;
  else
  {
    int osw;

    convert_to_os(d->cellwidth, &osw);

    return osw;
  }
}

/*************************************************/
/* 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->table) return b->leftmargin;
  else           return 0; /* Cell margins (cellpaddings) are handled in the tables routines */
}

/*************************************************/
/* 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->table) return b->rightmargin;
  else           return 0; /* Cell margins (cellpaddings) are handled in the tables routines */
}

/*************************************************/
/* redraw_margin()                               */
/*                                               */
/* Works out the left hand indented margin 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;     */
/*                                               */
/*             Pointer to a token holding        */
/*             indentation information.          */
/*                                               */
/* Returns:    The left hand margin, taking      */
/*             account of list indentations      */
/*             etc., in millipoints.             */
/*************************************************/

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

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

  if (t->tag == TABLE && ISBODY(t)) return redraw_left_margin(b, d);

  /* Indent more depending on the header type, if the */
  /* HStream represents a header.                     */

  if (redraw_header(s)) return i + redraw_left_margin(b, d);

//  if (redraw_header(s) == 1 || redraw_header(s) == 2) return (i + 4);
//  if (redraw_header(s) == 3)                          return (i + 16 + 4);
//  if (redraw_header(s) == 4 || redraw_header(s) == 5) return (i + 32 + 4);

  /* Add an appropriate amount for a bullet point; the */
  /* ISBULLET macro is defined in Fetch.h              */

  if (t->indent && !ISBULLET(t))
  {
    int width;

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

    i += width;
  }

  /* Add an amount for block quote or address text; the */
  /* constants are defined in HTMLLib:tags.h            */

  if (s & (BLOCKQUOTE | ADDRESS)) return i + redraw_left_margin(b, d) + b->quotemargin;

  /* Return a general indent based on the total summed so */
  /* far plus an extra amount for lists etc.              */

  return i + redraw_left_margin(b, d);
}

/*************************************************/
/* 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 positioning 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 = cell->ldata[line].chunks;
  int align = 0;

  /* If the chunk is a horizontal rule, want to be able to have this */
  /* starting at the far left hand edge.                             */

  if (t->style & HR) return 0;

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


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

  if (align)
  {
    int i;

    /* 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++);

    /* For centred objects, divide by 2 to get the indent in millipoints */

    if (align == 1) x /= 2;

    /* Sanity check */

    if (x < 0) x = redraw_left_margin(b, cell);

    /* Convert back to OS units */

    convert_to_os(x, &x);

    return x;
  }

  convert_to_os(redraw_margin(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;

  /* 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.       */
/*                                               */
/* 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;

  /* 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;

  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()).             */
/*************************************************/

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

  box = *rbox;

  box.xmin &= ~1;
  box.ymin &= ~1;
  box.xmax &= ~1;
  box.ymax &= ~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->selecol);
  }
}

/*************************************************/
/* 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->selecol);
}

/*************************************************/
/* 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;
  char              validation[32];

  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 = ox + w;
  icon.ymax = oy + h;

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

  sprintf(validation,"S%s",spr);

  block.bbox  = icon;
  block.flags = 0x1700311B;
  block.data.ist.buffer = "";
  block.data.ist.validation = validation;
  block.data.ist.buffer_size = 4;

  _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->selecol);
  }
}

/*************************************************/
/* 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[20];
  int               w, h;
  BBox              icon;
  WimpPlotIconBlock block;

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

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

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

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

  block.bbox  = icon;
  block.flags = 0x1700311B;
  block.data.ist.buffer = "";
  block.data.ist.validation = spr;
  block.data.ist.buffer_size = 4;

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

        &block,
        0,
        0);
}

/*************************************************/
/* 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)
{
  return redraw_draw_r(1, 0, 0, b, b->cell, r, noback, nocontent);
}

/*************************************************/
/* 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.  */
/*************************************************/

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

  #ifdef TRACE
    if (tl & (1u<<9)) Printf("\nredraw_draw: 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. */

    wbox = r->redraw_area; /* Set wbox 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.                                   */

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

    /* Retain the screen coord version of the redraw rectangle in sbox */
    /* (wbox holds it now, but this is converted to work area coords   */
    /* in a moment). Put the screen coordinate version in fbox, with   */
    /* the OS unit coords converted to millipoints.                    */

    sbox = wbox;

    convert_box_to_points(&sbox, &fbox);

    /* Convert the visible area of the window to work area */
    /* coords, putting the result in wbox.                 */

    coords_box_toworkarea(&wbox,r);

    /* Get the page bottom in work area coordinates. This is a printing only */
    /* concept, where the visible_area BBox will in fact hold the entire     */
    /* page bounding box.                                                    */

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

// if (b->nlines)
// {
//   Printf("\n*** LINE ENUMERATION ***\n\nNumber of lines: %d\n\n",b->nlines);
//
//   for (l = 0; l < b->nlines; l++)
//   {
//     Printf("Line %d\n-y    : %d\nh     : %d\nb     : %d\nn      : %d\nchunks: %d\n\n",
//             l,
//             -d->ldata[l].y,
//             d->ldata[l].h,
//             d->ldata[l].b,
//             d->ldata[l].n,
//             d->ldata[l].chunks);
//   }
//
//   Printf("\n************************\n\n");
// }
// else Printf("\n******* NO LINES *******\n\n");

    if (b->nchildren)
    {
      int                         child;
      unsigned int                colour;
      browser_data              * c;
      WimpGetWindowOutlineBlock   co;

      /* If the window has children, want to draw frame borders, not */
      /* any document content.                                       */

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

      /* The bottom bit of the colour field is set to indicate */
      /* that a colour is present, as opposed to having no     */
      /* colour set. In the latter case default to background  */
      /* grey, in the former strip off the set bit. Then set   */
      /* that colour.                                          */

      colour = b->frameset->maxlen;
      if (!(colour & 1)) colour  = Redraw_Colour_WNGrey;
      else               colour &= ~1;
      redraw_set_colour(colour);
      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

      /* Now loop round the children, drawing a 3D border around them */
      /* for border spacings >= 2, a black line for spacing 1, or     */
      /* nothing for spacing 0 (shouldn't then get redraw events for  */
      /* the parent, but you never know - e.g. user could have got a  */
      /* patch that allowed the frames to be moved by dragging on     */
      /* their work area).                                            */

      if (b->frameset->indent) /* Holds frame spacing (equiv. to border width) */
      {
        for (child = 0; child < b->nchildren; child ++)
        {
          c                = b->children[child];
          co.window_handle = c->window_handle;

          if (!wimp_get_window_outline(&co))
          {
            if (b->frameset->indent >= 2) redraw_set_colour(Redraw_Colour_AlmostWhite);
            else                          redraw_set_colour(0);

            /* Bottom edge */

            bbc_rectanglefill(co.outline.xmin,
                              co.outline.ymin - 2,
                              co.outline.xmax - co.outline.xmin + 1,
                              1);

            bbc_rectanglefill(co.outline.xmax,
                              co.outline.ymin,
                              1,
                              co.outline.ymax - co.outline.ymin - 1);

            /* Right hand edge */

            if (b->frameset->indent >= 2) redraw_set_colour(Redraw_Colour_PlinthGrey);

            /* Top edge */

            bbc_rectanglefill(co.outline.xmin - 2,
                              co.outline.ymax,
                              co.outline.xmax - co.outline.xmin + 3,
                              1);

            /* Left hand edge */

            bbc_rectanglefill(co.outline.xmin - 2,
                              co.outline.ymin - 2,
                              1,
                              co.outline.ymax - co.outline.ymin + 1);

            /* If the edges are draggable - for frame resizing - plot icons */
            /* indicate this.                                               */

            {
              int  width, height;
              BBox icon;

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

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

              /* Can only plot sprites if there's room... The additions to width and */
              /* height account for the border plotted above.                        */

              if (
                   width  + 4 <= b->frameset->indent * wimpt_dx() &&
                   height + 4 <= b->frameset->indent * wimpt_dy()
                 )
              {
                WimpPlotIconBlock block;

                block.flags = 0x1700311B;
                block.data.ist.buffer = "";
                block.data.ist.validation = "Sresizeframe";
                block.data.ist.buffer_size = 4;

                if (frames_can_resize_right(b, child))
                {
                  /* Plot to right of frame */

                  icon.xmin = co.outline.xmax + b->frameset->indent - width / 2;
                  icon.ymin = co.outline.ymin + (co.outline.ymax - co.outline.ymin - height) / 2;
                  icon.xmax = icon.xmin + width;
                  icon.ymax = icon.ymin + height;

                  coords_box_toworkarea(&icon, r);
                  block.bbox  = icon;

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

                        &block,
                        0,
                        0);
                }

                if (frames_can_resize_bottom(b, child))
                {
                  /* Plot below the frame */

                  icon.xmin = co.outline.xmin + (co.outline.xmax - co.outline.xmin - width) / 2;
                  icon.ymin = co.outline.ymin - height / 2 - b->frameset->indent;
                  icon.xmax = icon.xmin + width;
                  icon.ymax = icon.ymin + height;

                  coords_box_toworkarea(&icon, r);
                  block.bbox  = icon;

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

                        &block,
                        0,
                        0);
                }

              /* Closure of long 'if' checking that there is room to plot */
              /* a resize handle for this frame, given that the frame     */
              /* itself doesn't have noresize specified. The code above   */
              /* executes if noresize is not specified and there's room   */
              /* to do the plot.                                          */
              }

            /* Closure of code block dealing with resize handle plotting */
            }

          /* Closure of long 'if' ensuring a wimp_get_window_outline */
          /* call didn't return an error - the code above executes   */
          /* if there was no error.                                  */
          }

        /* Closure of 'for' looping round all children */
        }

      /* Closure of long 'if' checking that the frameset has spacing */
      /* between frames. The code above executes if so.              */
      }

    /* Closure of first part of long 'if' checking if the browser */
    /* has children. The code above executes if so, the code      */
    /* below executes if not.                                     */
    }

    /* If the browser doesn't have child frames, want to draw the document it holds. */

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

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

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

        if (!noback)
        {
          /* Then if the plain background flag is set, 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->plainback ||
               !image_tile_window(b,
                                  r,
                                  0,
                                  toplevel ? -toolbars_button_height(b) - toolbars_url_height(b) : 0)
             )
          {
            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

  //      else
  //      {
  //        /* If plotting no specific backgrounds, show the default one. */
  //        /* If printing, assume we want to plot no background at all.  */
  //
  //        if (!printing)
  //        {
  //          if (b->sourcecolours) redraw_set_colour(redraw_backcol(b));
  //          else redraw_set_colour(choices.col_back);
  //          bbc_rectanglefill(sbox.xmin, sbox.ymin, sbox.xmax - sbox.xmin + 4,sbox.ymax - sbox.ymin + 4);
  //        }
  //      }

        /* For each line that is present in the browser window... */

        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)
            {
              /* If the line would only partially fit on the page, and */
              /* we're printing, return it's top coordinate (i.e., if  */
              /* it falls off the bottom). Note that if printing = 2,  */
              /* this will not be done (so the printing routine can    */
              /* signal not to split the line in this way).            */

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

            /* 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);

            y = coords_y_toscreen(0, r);
            x = coords_x_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->tag == TABLE && ISBODY(tp))
                {
                  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              cx, cy;
                    int              cw, ch;
                    int              oldback    = 0;
                    int              oldaa      = 0;
                    int              oldbgimage = -1;
                    int              noback     = 1;
                    int              padding;
                    BBox             rbox;
                    BBox           * ibox;
                    int              cellindex;
                    int              cellcount  = 0;
                    int              cellmax    = table->ColSpan * table->RowSpan;
                    int              swap;

                    /* Only proceed if there are table cells to redraw */

                    if (cellarray)
                    {
                      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];
                                padding = tables_cell_padding(b, cell);

                                #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->backgroundcol;
                                  oldaa            = b->aacol;
                                  oldbgimage       = b->backimage;
                                  b->backgroundcol = TD_BGCOL(head);
                                  b->aacol         = b->backgroundcol;
                                  b->backimage     = -1; /* For now, no background images in table cells. */

                                  noback = 0;
                                }

                                /* 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--;

//                                /* Rounding errors - give some tolerance in all directions */
//
//                                rbox.xmin -= 4;
//                                rbox.ymin -= 4;
//                                rbox.xmax += 3; /* (- 1 to make the coordinate inclusive, then + 2) */
//                                rbox.ymax += 3;
//
                                /* 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      + padding,
                                                keepy + cell->y + oh - padding,
                                                b,
                                                cell,
                                                r,
                                                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 (!noback)
                                {
                                  b->backgroundcol = oldback;
                                  b->aacol         = oldaa;
                                  b->backimage     = oldbgimage;
                                }

                                /* Convert back to OS units ready for drawing the border */

                                convert_pair_to_os(x + cell->x,
                                                   y + cell->y + oh - cell->cellheight,

                                                   &cx,
                                                   &cy);

        //                        cx += 2;
        //                        cy -= 2;

                                convert_pair_to_os(cell->cellwidth,
                                                   cell->cellheight,

                                                   &cw,
                                                   &ch);


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


                                /* Draw the slabbed in cell border */

//                                if (TABLE_BORDER(table))
//                                {
//                                  redraw_set_colour(Redraw_Colour_PlinthGrey);
//
//                                  bbc_rectanglefill(cx, cy + ch, cw - 1, 1);
//                                  bbc_rectanglefill(cx, cy,      1,      ch - 1);
//
//                                  redraw_set_colour(Redraw_Colour_AlmostWhite);
//
//                                  bbc_rectanglefill(cx,      cy, cw - 1, 1);
//                                  bbc_rectanglefill(cx + cw, cy, 1,      ch - 1);
//                                }

                              /* 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 && ...)' */
                      }

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

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

//                  /* Draw the slabbed out table border */
//
//                  {
//                    int cx, cy, cw, ch;
//
//                    convert_pair_to_os(x, y, &cx, &cy);
//  //                  x -= 2;
//
//                    convert_to_os(d->cdata[cn].w, &cw);
//                    ch = d->ldata[l].h;
//
//                    #ifdef TRACE
//                      if (tl & (1u<<20)) Printf("table box at %d %d %d %d\n",cx,cy,cw,ch);
//                    #endif
//
//                    if (TABLE_BORDER((table_stream *) tp))
//                    {
//                      redraw_set_colour(Redraw_Colour_AlmostWhite);
//
//                      bbc_rectanglefill(cx, cy + ch, cw - 1, 1);
//                      bbc_rectanglefill(cx, cy,      1,      ch - 1);
//
//                      redraw_set_colour(Redraw_Colour_PlinthGrey);
//
//                      bbc_rectanglefill(cx,      cy, cw - 1, 1);
//                      bbc_rectanglefill(cx + cw, cy, 1,      ch - 1);
//                    }
//                  }
                }

                /* Deal with forms elements */

                if (tp->style & (INPUT | TEXTAREA | SELECT))
                {
                  /* A text-based element */

                  if (
                       (tp->style & (TEXTAREA | 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);
                    fm_font_box(fh, &box);

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

                    if (tp->style & TEXTAREA)
                    {
                      int r;

                      r = tp->rows;
                      if (r < 2) r = 2;
                      box.ymax = (box.ymax - box.ymin + 4) * r + box.ymin;
                    }

                    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->style & SELECT));

                      fm_set_font_colour(fh,
                                         redraw_token_colour(b, tp),
                                         (tp->style & SELECT) ? Redraw_Colour_BackGrey : Redraw_Colour_White);

                      form_textarea_redraw(b,
                                           d->cdata[cn].t,
                                           &box,
                                           &r->redraw_area,
                                           fh,
                                           !!(tp->style & TEXTAREA),
                                           (tp->style & INPUT) && HtmlINPUTtype(tp) == inputtype_PASSWORD);
                    }

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

                    if (tp->style & 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->selecol);

                      coords_box_toworkarea(&icon, r);

                      if (nocontent != tp)
                      {
                        WimpPlotIconBlock block;

                        block.bbox  = icon;
                        block.flags = 0x1700311B;
                        if (redraw_backcol(b) != Redraw_Colour_BackGrey) block.flags |= (1<<2); /* Border if not using Wimp grey background */
                        block.data.ist.buffer = "";
                        block.data.ist.validation = "Sfgright";
                        block.data.ist.buffer_size = 4;

                        wimp_plot_icon(&block);
                      }

//                      sprite_id   sid;
//                      sprite_info info;
//
//                      sid.tag=sprite_id_name;
//                      sid.s.name="fgright";
//                      if(!sprite_readsize(resspr_area(),&sid,&info))
//                      {
//                        int w,h;
//
//                        w=info.width<<bbc_modevar(info.mode,bbc_XEigFactor);
//                        h=info.height<<bbc_modevar(info.mode,bbc_YEigFactor);
//                        wimp_setcolour(7);
//                        bbc_rectanglefill(box.xmax-w-8,box.ymin+4,w+8-1,h+4-1);
//                        plotspr_plot(resspr_area(),&sid,8,box.xmax-w-4,box.ymin+4,NULL);
//                      }
                    }
                  }
                  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;

                    case inputtype_HIDDEN: break;

                    case inputtype_SUBMIT: /* SUBMIT same as RESET: 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);

                      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, temp, 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,
                                                &temp,
                                                &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:

                  o = 0;

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

                  /* 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(ox, r),
                                                 coords_y_toworkarea(oy, r));

                  if (!reformat_get_image_size(b, tp, &box))
                  {
                    ox -= box.xmin;

                    /* Draw a border of tp->maxlen * 2 OS units width around an */
                    /* image, if it represents a link. (I.e. if 'border="3"'    */
                    /* was specified in the document source, a 3 pixel wide     */
                    /* border would be drawn in mode 20, say).                  */

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

                    if (ISLINK(tp))
                    {
                      o = (tp->style & IMG) ? tp->maxlen * 2 : 4;

                      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->selecol);
                    }
                    else if ((tp->style & INPUT) && redraw_selected(b, tp))
                      redraw_border_around_box(&box, b->selecol);

                    /* Redraw the image itself */

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

                /* Plot a horizontal rule */

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

                  convert_to_os(y, &oy);

                  available = redraw_display_width(b, d);

                  /* Work out margins */

                  lmarg = redraw_margin(b, d, tp);

                  if (tp->style & BLOCKQUOTE) rmarg = lmarg;
                  else                        rmarg = redraw_right_margin(b, d);

                  convert_pair_to_os(lmarg, rmarg, &lmarg, &rmarg);

                  /* In (for example) Netscape Navigator (TM), a 100% width HR */
                  /* will only span between the left and right margins. So     */
                  /* want to reduce apparent available space by this amount.   */

                  available -= (lmarg + rmarg);
                  if (available < 0) available = 0;

                  /* Deal with a width specifier */

                  if (HR_HAS_WIDTH(tp))
                  {
                    /* Currently recognise pixels and percentages */

                    switch (HR_WIDTH_UNITS(tp))
                    {
                      case UNITS_PIXELS:  w = HR_WIDTH(tp) * 2;               break;
                      case UNITS_PERCENT: w = available * HR_WIDTH(tp) / 100; break;

                      default: w = available; break;
                    }
                  }
                  else w = available;

                  /* 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;
                  oy += 6;

                  if (HR_ALIGN(tp) == ALIGN_LEFT || w > available) ox = lmarg;
                  else if (HR_ALIGN(tp) == ALIGN_RIGHT)            ox = redraw_display_width(b, d) - rmarg - w;
                  else                                             ox = ((redraw_display_width(b, d) - w) >> 1);

                  ox = (coords_x_toscreen(ox + 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 = fetch_token_data_address(b,d->cdata[cn].t);

                  if (dp)
                  {
                    int c;

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

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

  // Printf("h: %d, c: %d, string: '%s'\n",h,c,dp + d->cdata[cn].o);

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

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

  // Printf("x: %d, y: %d, base: %d\n",x,y,base);

                    if (dp) fm_putsl(h,
                                     x,
                                     y + base,
                                     dp + d->cdata[cn].o,
                                     d->cdata[cn].l,
                                     0,
                                     b->backimage >= 0 && !b->plainback);

                    if (ISLINK(tp) && !(tp->tag == TABLE && ISBODY(tp)))
                    {
                      /* Underline the item - set the colour, and start at the item's x coordinate... */

                      int ox,oy;

                      if (b->underlinelks)
                      {
                        redraw_set_colour(redraw_token_colour(b, tp));

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

                        bbc_move(ox,oy - 7);

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

                        convert_pair_to_os(x + d->cdata[cn].w, y + base, &ox, &oy);

                        bbc_draw(ox,oy - 7);
                      }
                    }

  //                  fm_lose_font(b, h);
                  }
                }

                // 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.                                                 */
            }

          /* 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)
        {
          /* Set the graphics background colour to the default  */
          /* and clear the graphics rectangle [to this colour]. */

          redraw_set_colour(choices.col_back);

          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;
          BBox    size;

          /* Claim a font */

          h = fm_find_font(b, "sans", (int) (choices.fontsize * 1.5), (int) (choices.fontsize * 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 */

          y = coords_y_toscreen(-toolbars_button_height(b) - toolbars_url_height(b) - size.ymax - size.ymin - 40, r);

          /* 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: 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.col_text, choices.col_back);

          /* Write the string */

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

  //        fm_lose_font(b, h);

          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);


  return NULL;
}

/*************************************************/