/* 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   : SaveDraw.c                             */
/*                                                 */
/* Purpose: Save a web page as a Draw file.        */
/*                                                 */
/* Author : Merlyn Kline for Customer browser;    */
/*          This source adapted by A.D.Hodgkinson  */
/*          including bits from RISC_OSLib.        */
/*                                                 */
/* History: 13-Dec-97: 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 "FileTypes.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 "Redraw.h"
#include "Reformat.h"
#include "Save.h"
#include "Tables.h"
#include "TokenUtils.h"
#include "Toolbars.h"

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

#include "SaveDraw.h"

/* Local definitions */

#define DRAWMARGIN 0

/* Local statics */

static FILE * outfile = NULL;

/* Static function prototypes */

static int  savedraw_triangle_fill    (int x1, int y1, int x2, int y2, int x3, int y3, int c);
static void savedraw_font_info        (HStream * t, int * number, int * size);
static int  savedraw_input_field      (browser_data * b, HStream * t, BBox * box, int colour, int menu, int * size);
static int  savedraw_button           (browser_data * b, HStream * t, BBox * box, int in, int * size);
static int  savedraw_switch           (int x, int y, char * spr, int * size);
static int  savedraw_texthdr          (browser_data * b, HStream * tp, int x, int y, int w, int h, int base, int length, int bgnd);
static int  savedraw_draw_placeholder (browser_data * b, WimpRedrawWindowBlock * r, BBox * holder, HStream * token, const char * text, int * size);
static int  savedraw_textarea_redraw  (browser_data * b, HStream * token, BBox *box, BBox *gw, int fh, int multiline, int password, int * size);

static int  savedraw_create           (browser_data * b, int bgimages, int * size);
static int  savedraw_create_r         (int toplevel, int xorg, int yorg, browser_data * b, reformat_cell * d, int noback, WimpRedrawWindowBlock * r, int bgimages, int * size, int * tsize);

/*************************************************/
/* savedraw_write_bytes()                        */
/*                                               */
/* Outputs a given number of bytes of a given    */
/* string to the FILE * set up in the global     */
/* 'outfile', returning a success flag.          */
/*                                               */
/* Parameters: Pointer to an array of bytes;     */
/*                                               */
/*             Number of characters from the     */
/*             array to save.                    */
/*                                               */
/* Returns:    1 if successful, 0 if failed.     */
/*                                               */
/* Assumes:    An appropriately opened file is   */
/*             accessible through the global     */
/*             FILE * 'outfile'.                 */
/*************************************************/

int savedraw_write_bytes(const char * s, unsigned int n)
{
  _kernel_swi_regs r;

  if (!s || !outfile) return 0;

  /* Use _kernel_swi as it sets up */
  /* _kernel_last_oserror.         */

  r.r[0] = 2;
  r.r[1] = outfile->__file;
  r.r[2] = (int) s;
  r.r[3] = n;

  if (!r.r[1] || _kernel_swi(OS_GBPB, &r, &r)) return 0;

  return 1;
}

/*************************************************/
/* savedraw_rectangle_fill()                     */
/*                                               */
/* Output a filled rectangle.                    */
/*                                               */
/* Parameters: x position of the item in Draw    */
/*             coordinates;                      */
/*                                               */
/*             y position of the item in Draw    */
/*             coordinates;                      */
/*                                               */
/*             Item's width in Draw coordinates; */
/*                                               */
/*             Item's height in Draw             */
/*             coordinates;                      */
/*                                               */
/*             Colour of the rectangle.          */
/*************************************************/

int savedraw_rectangle_fill(int x, int y, int w, int h, int c)
{
  draw_pathstrhdr * ph;
  int             * i;
  char              d[DSIZE_FRECT];

  if (w <= 0 || h <= 0)
  {
    #ifdef TRACE

      erb.errnum = Utils_Error_Custom_Normal;

      StrNCpy0(erb.errmess,
               "Rectangle width or height is zero in savedraw_rectangle_fill, so no object written out - if calculating file size, the value will be incorrect.");

      show_error_ret(&erb);

    #endif

    return 1;
  }

  /* To ensure the lines are visible, should round x and y */
  /* to a multiple of 2 OS units.                          */

  x = x & ~(OTD(2) - 1);
  y = y & ~(OTD(2) - 1);

  /* We'll compile the structure in 'd' */

  ph = (draw_pathstrhdr *) d;

  /* Fill in the header items */

  ph->tag                   = draw_OBJPATH;
  ph->size                  = DSIZE_FRECT;

  ph->bbox.xmin             = x;
  ph->bbox.xmax             = x + w;
  ph->bbox.ymin             = y;
  ph->bbox.ymax             = y + h;

  ph->fillcolour            = c;
  ph->pathcolour            = -1;
  ph->pathwidth             = 0;

  ph->pathstyle.joincapwind = 0;
  ph->pathstyle.reserved8   = 0;
  ph->pathstyle.tricapwid   = 0;
  ph->pathstyle.tricaphei   = 0;

  /* Now do the body */

  i = (int *) (d + sizeof(draw_pathstrhdr));

  *i++ = draw_PathMOVE;
  *i++ = x;
  *i++ = y;

  *i++ = draw_PathLINE;
  *i++ = x;
  *i++ = y + h - 1;

  *i++ = draw_PathLINE;
  *i++ = x + w - 1;
  *i++ = y + h - 1;

  *i++ = draw_PathLINE;
  *i++ = x + w - 1;
  *i++ = y;

  *i++ = draw_PathLINE;
  *i++ = x;
  *i++ = y;
  *i++ = draw_PathTERM;

  return savedraw_write_bytes(d, sizeof(d));
}

/*************************************************/
/* savedraw_triangle_fill()                      */
/*                                               */
/* Output a filled triangle.                     */
/*                                               */
/* Parameters: x and y coordinates of the three  */
/*             vertices, in Draw coordinates;    */
/*                                               */
/*             Colour of the triangle.           */
/*************************************************/

static int savedraw_triangle_fill(int x1, int y1, int x2, int y2, int x3, int y3, int c)
{
  draw_pathstrhdr * ph;
  int             * i;
  char              d[DSIZE_FTRIA];

  /* We'll compile the structure in 'd' */

  ph = (draw_pathstrhdr *) d;

  /* Fill in the header items */

  ph->tag                   = draw_OBJPATH;
  ph->size                  = DSIZE_FTRIA;


  if (x1 < x2) ph->bbox.xmin = x1, ph->bbox.xmax = x2;
  else         ph->bbox.xmin = x2, ph->bbox.xmax = x1;
  if (y1 < y2) ph->bbox.ymin = y1, ph->bbox.ymax = y2;
  else         ph->bbox.ymin = y2, ph->bbox.ymax = y1;

  if (x3 < ph->bbox.xmin) ph->bbox.xmin = x3;
  if (y3 < ph->bbox.ymin) ph->bbox.ymin = y3;
  if (x3 > ph->bbox.xmax) ph->bbox.xmax = x3;
  if (y3 > ph->bbox.ymax) ph->bbox.ymax = y3;

  ph->fillcolour            = c;
  ph->pathcolour            = -1;
  ph->pathwidth             = 0;

  ph->pathstyle.joincapwind = 0;
  ph->pathstyle.reserved8   = 0;
  ph->pathstyle.tricapwid   = 0;
  ph->pathstyle.tricaphei   = 0;

  /* Now do the body */

  i = (int *) (d + sizeof(draw_pathstrhdr));

  *i++ = draw_PathMOVE;
  *i++ = x1;
  *i++ = y1;

  *i++ = draw_PathLINE;
  *i++ = x2;
  *i++ = y2;

  *i++ = draw_PathLINE;
  *i++ = x3;
  *i++ = y3;

  *i++ = draw_PathLINE;
  *i++ = x1;
  *i++ = y1;
  *i++ = draw_PathTERM;

  return savedraw_write_bytes(d, sizeof(d));
}

/*************************************************/
/* savedraw_font_info()                          */
/*                                               */
/* Find details of the font a token would use.   */
/*                                               */
/* Parameters: Pointer to the HStream struct     */
/*             relevant to the enquiry;          */
/*                                               */
/*             Pointer to an int, in which the   */
/*             number of the font (from the Draw */
/*             file's font table) is returned;   */
/*                                               */
/*             Pointer to an int, in which the   */
/*             font size in 16ths pt. is         */
/*             written.                          */
/*                                               */
/* Assumes:    Neither int pointer may be NULL.  */
/*************************************************/

static void savedraw_font_info(HStream * t, int * number, int * size)
{
  int f, i, b;

  /* Find details of the font in use */

  fm_token_font_info(t, &f, size, &i, &b);

  /* Convert the given face, italic and bold flags into a font number */
  /* for the Draw file                                                */

  *number = (f * 4 + (i ? 1 : 0) + (b ? 2 : 0) + 1);

  /* Finished */

  return;
}

/*************************************************/
/* savedraw_input_field()                        */
/*                                               */
/* For forms, create 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; */
/*                                               */
/*             Pointer to an int if wanting to   */
/*             know the size of the item only,   */
/*             else NULL (NB the int contents    */
/*             are not updated - the pointer is  */
/*             acting like a flag, basically).   */
/*                                               */
/* Returns:    Size of item written out, or 0 if */
/*             it failed.                        */
/*************************************************/

static int savedraw_input_field(browser_data * b, HStream * t, BBox * box, int colour, int menu, int * size)
{
  int w, h;
  int tsize;

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

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

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

    tsize = DSIZE_FRECT;

    if (!savedraw_rectangle_fill(OTD(box->xmin), OTD(box->ymin), OTD(w), OTD(h), Redraw_Colour_White)) return 0;

    /* Redraw the border */

    tsize += DSIZE_FRECT * 4;

    if (!size)
    {
      if (!savedraw_rectangle_fill(OTD(box->xmin),     OTD(box->ymin),     OTD(4),     OTD(h), colour)) return 0;
      if (!savedraw_rectangle_fill(OTD(box->xmax - 4), OTD(box->ymin),     OTD(4),     OTD(h), colour)) return 0;
      if (!savedraw_rectangle_fill(OTD(box->xmin + 4), OTD(box->ymin),     OTD(w - 8), OTD(4), colour)) return 0;
      if (!savedraw_rectangle_fill(OTD(box->xmin + 4), OTD(box->ymax - 4), OTD(w - 8), OTD(4), colour)) return 0;
    }
  }

  return tsize;
}

/*************************************************/
/* savedraw_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;                                */
/*                                               */
/*             Pointer to an int if wanting to   */
/*             know the size of the item only,   */
/*             else NULL (NB the int contents    */
/*             are not updated - the pointer is  */
/*             acting like a flag, basically).   */
/*                                               */
/* Returns:    Size of item written out, or 0 if */
/*             it failed.                        */
/*************************************************/

static int savedraw_button(browser_data * b, HStream * t, BBox * box, int in, int * size)
{
  int w, h, c;
  int tsize;

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

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

  tsize = DSIZE_FRECT;

  if (!size && !savedraw_rectangle_fill(OTD(box->xmin), OTD(box->ymin), OTD(w), OTD(h), c)) return 0;

  tsize += DSIZE_FRECT * 4;

  c = in ? Redraw_Colour_PlinthGrey : Redraw_Colour_AlmostWhite;

  if (!size)
  {
    if (!savedraw_rectangle_fill(OTD(box->xmin),     OTD(box->ymin),     OTD(2),     OTD(h),     c)) return 0;
    if (!savedraw_rectangle_fill(OTD(box->xmin + 2), OTD(box->ymin + 2), OTD(2),     OTD(h - 2), c)) return 0;
    if (!savedraw_rectangle_fill(OTD(box->xmin + 4), OTD(box->ymax - 2), OTD(w - 4), OTD(2),     c)) return 0;
    if (!savedraw_rectangle_fill(OTD(box->xmin + 4), OTD(box->ymax - 4), OTD(w - 6), OTD(2),     c)) return 0;
  }

  tsize += DSIZE_FRECT * 4;

  c = in ? Redraw_Colour_AlmostWhite : Redraw_Colour_PlinthGrey;

  if (!size)
  {
    if (!savedraw_rectangle_fill(OTD(box->xmin + 2), OTD(box->ymin),     OTD(w - 2), OTD(2),     c)) return 0;
    if (!savedraw_rectangle_fill(OTD(box->xmin + 4), OTD(box->ymin + 2), OTD(w - 4), OTD(2),     c)) return 0;
    if (!savedraw_rectangle_fill(OTD(box->xmax - 4), OTD(box->ymin + 4), OTD(2),     OTD(h - 8), c)) return 0;
    if (!savedraw_rectangle_fill(OTD(box->xmax - 2), OTD(box->ymin + 4), OTD(2),     OTD(h - 6), c)) return 0;
  }

  return tsize;
}

/*************************************************/
/* savedraw_switch()                             */
/*                                               */
/* Output a switch (small sprite) at given       */
/* coordinates.                                  */
/*                                               */
/* Parameters: x position of the item in Draw    */
/*             coordinates;                      */
/*                                               */
/*             y position of the item in Draw    */
/*             coordinates;                      */
/*                                               */
/*             Pointer to the sprite's name;     */
/*                                               */
/*             Pointer to an int if wanting to   */
/*             know the size of the item only,   */
/*             else NULL (NB the int contents    */
/*             are not updated - the pointer is  */
/*             acting like a flag, basically).   */
/*************************************************/

static int savedraw_switch(int x, int y, char * spr, int * size)
{
  draw_spristrhdr    h;
  _kernel_swi_regs   r;
  sprite_header    * sh;
  int                asize = 0;
  int                wid, hei;

  h.tag = draw_OBJSPRITE;

  /* Find the sprite. Use _kernel_swi to set up */
  /* _kernel_last_oserror.                      */

  r.r[0] = 280;
  r.r[1] = (int) sprite_block;
  r.r[2] = (int) spr;

  if (_kernel_swi(OS_SpriteOp, &r, &r)) return 0;
  else sh = (sprite_header *) r.r[2];

  if (read_sprite_size(spr, &wid, &hei)) return 0;

  asize  = sh->next + sizeof(h);
  h.size = (int) WordAlign(asize);

  x = OTD(x);
  y = OTD(y);

  h.bbox.xmin = x;
  h.bbox.ymin = y;
  h.bbox.xmax = h.bbox.xmin + OTD(wid);
  h.bbox.ymax = h.bbox.ymin + OTD(hei);

  if (!size)
  {
    if (!savedraw_write_bytes((char *) &h, sizeof(h)))         return 0;
    if (!savedraw_write_bytes((char *) sh, asize - sizeof(h))) return 0;

    if (asize != h.size)
    {
      if (!savedraw_write_bytes("    ", h.size - asize)) return 0;
    }
  }

  return h.size;
}

/*************************************************/
/* savedraw_texthdr()                            */
/*                                               */
/* Outputs a header for a text item.             */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             for which the item is to be       */
/*             output;                           */
/*                                               */
/*             Pointer to an HStream struct      */
/*             from which the foreground colour  */
/*             and font to use is drawn (and     */
/*             nothing else);                    */
/*                                               */
/*             x position of the item in Draw    */
/*             coordinates;                      */
/*                                               */
/*             y position of the item in Draw    */
/*             coordinates;                      */
/*                                               */
/*             Item's width in Draw coordinates; */
/*                                               */
/*             Item's height in Draw             */
/*             coordinates;                      */
/*                                               */
/*             Baseline offset for the text      */
/*             (added to the y coordinate);      */
/*                                               */
/*             Length of the string that this    */
/*             header will be representing;      */
/*                                               */
/*             Background colour to use.         */
/*************************************************/

static int savedraw_texthdr(browser_data * b, HStream * tp, int x, int y,
                            int w, int h, int base, int length, int bgnd)
{
  draw_trfmtextstrhdr hdr;
  int                 number, size;

  if (w <= 0 || h <= 0)
  {
    #ifdef TRACE

      erb.errnum = Utils_Error_Custom_Normal;

      StrNCpy0(erb.errmess,
               "Width or height is zero in savedraw_texthdr, adjusting to 2 OS units to try and ensure output file is valid.");

      show_error_ret(&erb);

    #endif

    if (w <= 0) w = OTD(2);
    if (h <= 0) h = OTD(2);
  }

  /* Find info on the font */

  savedraw_font_info(tp, &number, &size);

  /* Fill in the header */

  hdr.tag                  = draw_OBJTEXTTRFM;
  hdr.size                 = sizeof(draw_trfmtextstrhdr) + (int) WordAlign(length + 1);
  hdr.bbox.xmin            = x;
  hdr.bbox.xmax            = x + w;
  hdr.bbox.ymin            = y;
  hdr.bbox.ymax            = y + h;
  hdr.matrix[0]            = 0x10000;
  hdr.matrix[1]            = 0;
  hdr.matrix[2]            = 0;
  hdr.matrix[3]            = 0x10000;
  hdr.matrix[4]            = 0;
  hdr.matrix[5]            = 0;
  hdr.fontflags            = SaveDraw_FontFlags_Kerned;
  hdr.textcolour           = redraw_token_colour(b, tp);
  hdr.background           = bgnd;
  hdr.textstyle.fontref    = fm_system_font() ? 0 : number;
  hdr.textstyle.reserved8  = 0;
  hdr.textstyle.reserved16 = 0;
  hdr.fsizey               = size * 40;

  if (number <= 8) hdr.fsizex = size * 40;
  else             hdr.fsizex = (size * 4 * choices.tt_aspect) / 10;

  if (fm_system_font()) hdr.fsizex /= 2;

  hdr.coord.x              = x;
  hdr.coord.y              = y + base;

  /* Exit through the save routines */

  return savedraw_write_bytes((char *) &hdr, sizeof(hdr));
}

/*************************************************/
/* savedraw_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; */
/*                                               */
/*             Pointer to an int if wanting to   */
/*             know the size of the item only,   */
/*             else NULL (NB the int contents    */
/*             are not updated - the pointer is  */
/*             acting like a flag, basically).   */
/*                                               */
/* Returns:    Size of item written out, or 0 if */
/*             it failed.                        */
/*************************************************/

static int savedraw_draw_placeholder(browser_data * b, WimpRedrawWindowBlock * r, BBox * holder, HStream * token, const char * text, int * size)
{
  int  tsize = 0;
  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)
  {
    tsize += 4 * DSIZE_FRECT;

    if (!size)
    {
      if (!savedraw_rectangle_fill(OTD(ph.xmin + 2),           OTD(ph.ymin + 2),           OTD(ph.xmax - 4), OTD(4),           Redraw_Colour_AlmostWhite)) return 0;
      if (!savedraw_rectangle_fill(OTD(ph.xmax + ph.xmin - 6), OTD(ph.ymin + 2),           OTD(4),           OTD(ph.ymax - 4), Redraw_Colour_AlmostWhite)) return 0;
      if (!savedraw_rectangle_fill(OTD(ph.xmin + 2),           OTD(ph.ymax + ph.ymin - 6), OTD(ph.xmax - 6), OTD(4),           Redraw_Colour_MidGrey))     return 0;
      if (!savedraw_rectangle_fill(OTD(ph.xmin + 2),           OTD(ph.ymin + 4),           OTD(4),           OTD(ph.ymax - 6), Redraw_Colour_MidGrey))     return 0;
    }
  }

  /* Otherwise a thin black frame */

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

    tsize += 4 * DSIZE_FRECT;

    if (!size)
    {
      if (!savedraw_rectangle_fill(OTD(ph.xmin),               OTD(ph.ymin),               OTD(ph.xmax), OTD(2),       Redraw_Colour_Black)) return 0;
      if (!savedraw_rectangle_fill(OTD(ph.xmin),               OTD(ph.ymin),               OTD(2),       OTD(ph.ymax), Redraw_Colour_Black)) return 0;
      if (!savedraw_rectangle_fill(OTD(ph.xmin + ph.xmax - 2), OTD(ph.ymin),               OTD(2),       OTD(ph.ymax), Redraw_Colour_Black)) return 0;
      if (!savedraw_rectangle_fill(OTD(ph.xmin),               OTD(ph.ymin + ph.ymax - 2), OTD(ph.xmax), OTD(2),       Redraw_Colour_Black)) return 0;
    }
  }

  /* Output any text that there is */

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

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

    fbox.xmin = fbox.ymin = 0;

    /* Claim the font */

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

    h = fm_find_font(b,
                     "sans",
                     fsize,
                     fsize,
                     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 work out the bounding BBox from the placeholder BBox,  */
    /* allowing for a small edge gap before the text gets dropped */
    /* out completely (we can't clip in a Draw file, so must just */
    /* not output the text if it won't fit).                      */

    fbox.xmin = ph.xmin + 8;
    fbox.ymin = ph.ymin;// + 8;
    fbox.xmax = ph.xmin + ph.xmax - 8; /* Since ph.xmax = placeholder width) */
    fbox.ymax = ph.ymin + ph.ymax;// - 8; /* Since ph.ymax = placeholder height) */

    /* Can only proceed if there's a > 0 size bounding box */

    if (fbox.xmin < fbox.xmax && fbox.ymin < fbox.ymax)
    {
      /* Find the intersection of the area of the image in which */
      /* we can plot ALT text and the 'redraw' rectangle         */

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

      /* If this box is smaller than that required for the ALT text, */
      /* don't proceed                                               */

      if (
           ibox                                    &&
           ibox->xmax - ibox->xmin >= stringwidth  &&
           ibox->ymax - ibox->ymin >= stringheight
         )
      {
        draw_trfmtextstrhdr hdr;
        int                 c      = redraw_token_colour(b, token);
        int                 length = strlen(text) + 1;
        char                null   = '\0';

        /* Construct the header */

        hdr.tag                  = draw_OBJTEXTTRFM;
        hdr.size                 = sizeof(draw_trfmtextstrhdr) + (int) WordAlign(length);
        hdr.bbox.xmin            = OTD(xpos);
        hdr.bbox.xmax            = OTD(xpos + stringwidth);
        hdr.bbox.ymin            = OTD(vcent);
        hdr.bbox.ymax            = OTD(vcent + stringheight);
        hdr.matrix[0]            = 0x10000;
        hdr.matrix[1]            = 0;
        hdr.matrix[2]            = 0;
        hdr.matrix[3]            = 0x10000;
        hdr.matrix[4]            = 0;
        hdr.matrix[5]            = 0;
        hdr.fontflags            = SaveDraw_FontFlags_Kerned;
        hdr.textcolour           = c;
        hdr.background           = redraw_background_colour(b, c);
        hdr.textstyle.fontref    = fm_system_font() ? 0 : 5;
        hdr.textstyle.reserved8  = 0;
        hdr.textstyle.reserved16 = 0;
        hdr.fsizex               = fsize * 40;
        hdr.fsizey               = fsize * 40;

        if (fm_system_font()) hdr.fsizex /= 2;

        hdr.coord.x              = OTD(xpos);
        hdr.coord.y              = OTD(vcent);

        tsize += sizeof(hdr) + (int) WordAlign(length);

        if (!size)
        {
          if (!savedraw_write_bytes((char *) &hdr, sizeof(hdr))) return 0;

          /* Write the text itself */

          if (!savedraw_write_bytes(text, length - 1)) return 0;
          if (!savedraw_write_bytes(&null, 1)) return 0;

          if (length != (int) WordAlign(length))
          {
            if (!savedraw_write_bytes("    ", (int) WordAlign(length) - length)) return 0;
          }
        }

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

        if (b->underline_links && ISLINK(token))
        {
          tsize += DSIZE_FRECT;

          if (!size && !savedraw_rectangle_fill(OTD(xpos), OTD(vcent - 7), OTD(stringwidth), OTD(2), c)) return 0;
        }
      }
    }
  }

  return tsize;
}

/*************************************************/
/* savedraw_textarea_redraw()                    */
/*                                               */
/* Outputs the text in a text area object.       */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the text area;        */
/*                                               */
/*             Pointer to the token representing */
/*             the text area;                    */
/*                                               */
/*             Pointer to a BBox holding the     */
/*             bounding box of the text area, in */
/*             screen coords (so OS units);      */
/*                                               */
/*             Pointer to a BBox holding the     */
/*             current graphics window (usually  */
/*             from a call to the Wimp to redraw */
/*             something - remember that this    */
/*             can't be read during a printing   */
/*             routine as VDU variables may not  */
/*             be read during printing);         */
/*                                               */
/*             A font handle for the font to use */
/*             for the redraw;                   */
/*                                               */
/*             1 if the text area has multiple   */
/*             lines, 0 to render all text on    */
/*             one line only;                    */
/*                                               */
/*             1 if the text area represents a   */
/*             password object (show the text as */
/*             a line of *s, instead of the      */
/*             actual chars), else 0;            */
/*                                               */
/*             Pointer to an int if wanting to   */
/*             know the size of the item only,   */
/*             else NULL (NB the int contents    */
/*             are not updated - the pointer is  */
/*             acting like a flag, basically).   */
/*                                               */
/* Returns:    Size of item written out (which   */
/*             may be zero), or -1 for failure - */
/*             note how this differs from most   */
/*             other functions in this source!   */
/*************************************************/

static int savedraw_textarea_redraw(browser_data * b, HStream * token, BBox *box, BBox *gw, int fh,
                                    int multiline, int password, int * size)
{
  int    nxmin, nymin, nxmax, nymax;
  int    lh, lb, length, used;
  int    xs, ys, y;
  int    tsize = 0;
  BBox   area, fontbox;
  char   c;
  char * p, * t;

  /* Set 'area' to hold the bounding box of the text area */

  area = *box;

  /* Bring the coordinates in to account for the text area's border */

  area.xmin += 8;
  area.ymin += 8;
  area.xmax -= 8;
  area.ymax -= 8;

  /* Get nxmin, nxmax, nymin anx nymax to hold the coordinates of */
  /* a BBox formed by the intersection of the text area and the   */
  /* current graphics rectangle.                                  */

  nxmin = area.xmin > gw->xmin ? area.xmin : gw->xmin;
  nxmax = area.xmax < gw->xmax ? area.xmax : gw->xmax;
  nymin = area.ymin > gw->ymin ? area.ymin : gw->ymin;
  nymax = area.ymax < gw->ymax ? area.ymax : gw->ymax;

  nxmin &= ~1;
  nymin &= ~1;
  nxmax &= ~1;
  nymax &= ~1;

  /* If the minimum coords are less than / equal to the maximums, */
  /* there is nothing to redraw.                                  */

  if (nxmin >= nxmax || nymin >= nymax) return 0;

  /* Get the height of an individual line, based on the height of */
  /* the font to be used plus vertical spacing considerations.    */

  fm_font_box(fh, &fontbox);

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

  /* Get the text for the text area */

  p = form_get_field_text(b, token);

  /* Only proceed if there's text to draw */

  if (p)
  {
    /* Assume a zero scroll offset (so the output */
    /* Draw file reflects a 'dead' unedited page, */
    /* in the same way that the keyboard selected */
    /* item, if present, isn't shown highlighted) */

    xs = ys = 0;

    y = area.ymax + ys;

    do
    {
      /* The text may be terminated by newlines or null bytes; if */
      /* a newline, this implies a split point for a text area.   */

      t         = strchr(p, '\n');
      if (!t) t = strchr(p, 0);

      /* Ensure for C's purposes (sigh) that there is a zero terminator */
      /* at the end of the chunk to plot.                               */

       c = *t;
      *t = 0;

      length = used = strlen(p);

      /* If the line is in the visible region, plot it. Note the use of */
      /* the FE_PassCode line of asterisks - if a password is being     */
      /* entered, as many of these as there are characters in the       */
      /* real string will be displayed instead of that string.          */

      if (length && y - lh < area.ymax)
      {
        int  tw;
        char null = '\0';

        length ++; /* (Initial condition) */

        do
        {
          length --;

          if (fm_get_string_width(fh,
                                  password ? FE_PassCode : p,
                                  0x1000000,
                                  length,
                                  -1,
                                  NULL,
                                  &tw)) return -1;

          convert_to_os(tw, &tw);
        }
        while (length && tw > nxmax - nxmin);

        if (length)
        {
          /* Output the header */

          tsize += sizeof(draw_trfmtextstrhdr);

          if (!savedraw_texthdr(b,
                                token,
                                OTD(area.xmin - xs),
                                OTD(y - lh + lb),
                                OTD(tw),
                                OTD(lh),
                                0,
                                length,
                                (token->tagno == TAG_SELECT) ? Redraw_Colour_BackGrey : Redraw_Colour_White)) return -1;

          /* The count should include a terminator at this point */

          length++;

          /* Output the text ifself */

          tsize += (int) WordAlign(length);

          if (!savedraw_write_bytes(password ? FE_PassCode : p, length - 1)) return -1;
          if (!savedraw_write_bytes(&null, 1)) return -1;

          if (length != (int) WordAlign(length))
          {
            if (!savedraw_write_bytes("    ", (int) WordAlign(length) - length)) return -1;
          }
        }
      }

      y -= lh;

      /* Restore the character that was altered to a null byte earlier */

      *t = c;

      /* Advance the string pointer past the text just plotted */

      p += used;

      /* If we're on a newline, there must be another chunk of string to */
      /* plot, so move the pointer past it.                              */

      if (*p == '\n') p++;

      /* Keep looping whilst in the visible area and there's data left to   */
      /* plot, provided that this was flagged as a multiline object redraw. */
    }
    while (y>area.ymin && multiline && *p);
  }

  return tsize;
}

/*************************************************/
/* savedraw_create()                             */
/*                                               */
/* Output a Draw file representing the contents  */
/* of a given browser, or find out how big the   */
/* file would be. The static 'outfile' local to  */
/* this file should hold a valid FILE * to       */
/* output to in the first case.                  */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             to represent in the Draw file;    */
/*                                               */
/*             1 to include any background       */
/*             image present on the browser's    */
/*             page, else 0;                     */
/*                                               */
/*             Pointer to an int to take the     */
/*             file size if that's all you want, */
/*             or NULL to actually output the    */
/*             file.                             */
/*************************************************/

static int savedraw_create(browser_data * b, int bgimages, int * size)
{
  WimpRedrawWindowBlock   r;
  _kernel_oserror       * e;
  HStream               * selected;
  int                     success;
  int                     tsize = 40; /* Draw file header */
  int                     height;

  /* Compile a pseudo-redraw block for the Draw file, */
  /* in screen coordinates.                           */

  height = reformat_return_extent(b, b->cell);
  if (height < b->display_height) height = b->display_height;

  r.visible_area.xmin = r.redraw_area.xmin = 0;
  r.visible_area.ymin = r.redraw_area.ymin = 0;
  r.visible_area.xmax = r.redraw_area.xmax = b->display_extent;
  r.visible_area.ymax = r.redraw_area.ymax = height;

  r.window_handle = b->window_handle;
  r.xscroll       = 0;
  r.yscroll       = 0;

  /* If required, write the Draw file header */

  tsize += DSIZE_FRECT; /* Background */

  if (size) *size = 0;
  else
  {
    draw_fileheader h;

    /* Draw file ID */

    strncpy(h.title, "Draw", sizeof(h.title));

    /* Draw file version */

    h.majorstamp = 201;
    h.minorstamp = 0;

    /* Program name - we do not rely on a field immediately */
    /* after 'progident' to take a string terminator here.  */

    strncpy(h.progident, program_name, sizeof(h.progident));

    if (strlen(program_name) < sizeof(h.progident))
    {
      int i = strlen(program_name);
      int j = sizeof(h.progident) - i;

      while (j) h.progident[i++] = ' ', j--;
    }

    /* Whole file's bounding box */

    h.bbox.xmin = DRAWMARGIN     + OTD(r.redraw_area.xmin);
    h.bbox.ymin = DRAWMARGIN     + OTD(r.redraw_area.ymin);
    h.bbox.xmax = DRAWMARGIN * 2 + OTD(r.redraw_area.xmax);
    h.bbox.ymax = DRAWMARGIN * 2 + OTD(r.redraw_area.ymax);

    if (!savedraw_write_bytes((char *) &h, sizeof(h))) return 0;
  }

  /* Unless we're in system font, list the fonts used */

  if (!fm_system_font())
  {
    int  f, i, b, n;
    char fontname[Limits_FontName];
    int  fsize;

    fsize = 8;

    for (f = 0; f < 12; f++) /* 12 faces */
    {
      n = f / 4;        /* Internal font number     */
      i = (f % 4) & 1;  /* Italic flag, lsb (bit 0) */
      b = (f % 4) >> 1; /* Bold flag, bit 1         */

      e = fm_write_name(n, fontname, i, b);
      if (e) show_error_ret(e);

      fsize += strlen(fontname) + 2;
    }

    tsize += (int) WordAlign(fsize);

    /* If required, write the header out */

    if (!size)
    {
      *(int*) fontname       = draw_OBJFONTLIST;
      *(int*) (fontname + 4) = (int) WordAlign(fsize);

      if (!savedraw_write_bytes(fontname, 8)) return 0;

      for (f = 0; f < 12; f++) /* 12 faces */
      {
        n = f / 4;        /* Internal font number     */
        i = (f % 4) & 1;  /* Italic flag, lsb (bit 0) */
        b = (f % 4) >> 1; /* Bold flag, bit 1         */

        e = fm_write_name(n, fontname + 1, i, b);
        if (e) show_error_ret(e);

        fontname[0] = f + 1;

        if (!savedraw_write_bytes(fontname, strlen(fontname + 1) + 2)) return 0;
      }

      /* Word align the file contents */

      if (fsize != (int) WordAlign(fsize))
      {
        if (!savedraw_write_bytes("    ", (int) WordAlign(fsize) - fsize)) return 0;
      }
    }
  }

  /* Now draw the main page (recursively, for tables); */
  /* note that 'b->selected' is cleared to avoid       */
  /* highlighted tokens coming out as SeleColour in    */
  /* the draw output.                                  */

  selected    = b->selected;
  b->selected = NULL;
  success     = savedraw_create_r(1, 0, 0, b, b->cell, 0, &r, bgimages, size, &tsize);
  b->selected = selected;

  /* Finished */

  return success;
}

/*************************************************/
/* savedraw_create_r()                           */
/*                                               */
/* Recursive back-end to savedraw_create. API is */
/* similar in many ways to redraw_draw_r.        */
/*                                               */
/* Parameters: 1 if being called externally, or  */
/*             0 if being called recursively;    */
/*                                               */
/*             x coord of offset from the top    */
/*             right to 'plot' at (in milli-     */
/*             points);                          */
/*                                               */
/*             y coord of offset from the top    */
/*             right to 'plot' at (in milli-     */
/*             points);                          */
/*                                               */
/*             Pointer to a browser_data struct  */
/*             to represent in the Draw file;    */
/*                                               */
/*             Pointer to the reformat_cell      */
/*             which is to be output;            */
/*                                               */
/*             1 to draw no backgrounds, else 0; */
/*                                               */
/*             A WimpRedrawWindowBlock pointer   */
/*             containing details on the area    */
/*             of the page to output;            */
/*                                               */
/*             1 to include any background       */
/*             image present on the browser's    */
/*             page, else 0;                     */
/*                                               */
/*             Pointer to an int to take the     */
/*             file size if that's all you want, */
/*             or NULL to actually output the    */
/*             file;                             */
/*                                               */
/*             Pointer to an int in which the    */
/*             file size of the current chunk    */
/*             / cell (which may not be the      */
/*             whole file) is written.           */
/*                                               */
/* Assumes:    Pointers must be valid and not    */
/*             NULL unless otherwise specified   */
/*             above.                            */
/*************************************************/

static int savedraw_create_r(int toplevel, int xorg, int yorg,
                             browser_data * b, reformat_cell * d, int noback,
                             WimpRedrawWindowBlock * r, int bgimages,
                             int * size, int * tsize)
{
  BBox wbox, sbox, fbox;
  int  page_bottom, page_height;
  int  osxorg, osyorg;
  int  l = 0;

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

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

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

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

  /* Now convert sbox to Draw coordinates. So, we have: */
  /*                                                    */
  /* sbox - The redraw area in Draw file coords;        */
  /* fbox - The redraw area in screen coords converted  */
  /*        to millipoints;                             */
  /* wbox - The redraw area in work area coords.        */

  sbox.xmin = DRAWMARGIN + OTD(sbox.xmin);
  sbox.ymin = DRAWMARGIN + OTD(sbox.ymin);
  sbox.xmax = DRAWMARGIN + OTD(sbox.xmax);
  sbox.ymax = DRAWMARGIN + OTD(sbox.ymax);

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

  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 ||
         (
           !bgimages ||
           !image_tile_to_draw(b,
                               r,
                               0,
                               -htop,
                               size)
         )
       )
    {
      if (!toplevel || redraw_backcol(b) != Redraw_Colour_White)
      {
        if (
             !size &&
             !savedraw_rectangle_fill(sbox.xmin,
                                      sbox.ymin,
                                      sbox.xmax - sbox.xmin,
                                      sbox.ymax - sbox.ymin - (toplevel ? OTD(htop) : 0),
                                      redraw_backcol(b))
           )
           return 0;

        *tsize += DSIZE_FRECT;
      }
    }
  }

  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 (toplevel)
      {
        _swix(Hourglass_Percentage,
              _IN(0),

              (((wbox.ymax - osyorg - d->ldata[l].y) * 100) / (wbox.ymax - wbox.ymin)) - 1);
      }

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

      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 all of the line chunks */

      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;

          /* 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; /* Redraw box - i.e. the cell BBox in window coordintes */
              BBox           * ibox; /* Used for intersection between cell BBox and current graphics rectangle */
              BBox             tbox; /* Used to plot the table outer border */

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

              int              swap;

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

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

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

                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)
                        {
                          int cx, cy, cw, ch;

                          cell = &cellarray[cellindex];

                          cx = x + cell->x;
                          cy = y + cell->y + oh - cell->cellheight;
                          cw = cell->cellwidth;
                          ch = cell->cellheight;

                          convert_pair_to_os(cx, cy, &cx, &cy);
                          convert_pair_to_os(cw, ch, &cw, &ch);

                          /* Update the table bounding box as required */

                          if (cx      < tbox.xmin) tbox.xmin = cx;
                          if (cy      < tbox.ymin) tbox.ymin = cy;
                          if (cx + cw > tbox.xmax) tbox.xmax = cx + cw;
                          if (cy + ch > tbox.ymax) tbox.ymax = cy + ch;

                        /* 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, deal with 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;

                  /* Output the background */

                  redraw_set_colour(TABLE_BGCOL(table));

                  if (!savedraw_rectangle_fill(OTD(tx), OTD(ty), OTD(tw), OTD(th), TABLE_BGCOL(table))) return 0;
                }

                /* Now output the table cells */

                row       = table->List;
                cellcount = 0;

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

                          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 to OS units */

                          convert_box_to_os(&rbox, &rbox);

                          /* 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 notional page redraw box intersects the overall redraw rectangle */

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

                          if (ibox)
                          {
                            BBox oldrect;

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

                            /* Recursive call to redraw the cell contents */

                            savedraw_create_r(0,
                                              keepx + cell->x,
                                              keepy + cell->y + oh,
                                              b,
                                              cell,
                                              t_noback,
                                              r,
                                              bgimages,
                                              size,
                                              tsize);

                            /* Restore the redraw rectangle */

                            r->redraw_area = oldrect;
                          }

                          /* 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 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;
                            ch = rbox.ymax - rbox.ymin;

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

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

                              *tsize += DSIZE_FRECT * 4;

                              if (
                                   choices.table_inner != Choices_TableInner_Always2D &&
                                   (
                                     table->cellspacing > max ||
                                     choices.table_inner == Choices_TableInner_Always3D
                                   )
                                 )
                              {
                                if (!savedraw_rectangle_fill(OTD(cx),      OTD(cy - 2),  OTD(cw),     OTD(2),      Redraw_Colour_AlmostWhite)) return 0;
                                if (!savedraw_rectangle_fill(OTD(cx + cw), OTD(cy - 2),  OTD(2),      OTD(ch + 2), Redraw_Colour_AlmostWhite)) return 0;
                                if (!savedraw_rectangle_fill(OTD(cx - 2),  OTD(cy - 2),  OTD(2),      OTD(ch + 4), Redraw_Colour_PlinthGrey))  return 0;
                                if (!savedraw_rectangle_fill(OTD(cx),      OTD(cy + ch), OTD(cw + 2), OTD(2),      Redraw_Colour_PlinthGrey))  return 0;
                              }

                              /* 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))
                              {
                                if (!savedraw_rectangle_fill(OTD(cx - 2),  OTD(cy - 2),  OTD(cw + 2), OTD(2),      Redraw_Colour_Black)) return 0;
                                if (!savedraw_rectangle_fill(OTD(cx + cw), OTD(cy - 2),  OTD(2),      OTD(ch + 2), Redraw_Colour_Black)) return 0;
                                if (!savedraw_rectangle_fill(OTD(cx - 2),  OTD(cy),      OTD(2),      OTD(ch + 2), Redraw_Colour_Black)) return 0;
                                if (!savedraw_rectangle_fill(OTD(cx),      OTD(cy + ch), OTD(cw + 2), OTD(2),      Redraw_Colour_Black)) return 0;
                              }
                              else
                              {
                                if (!savedraw_rectangle_fill(OTD(cx),          OTD(cy),          OTD(cw), OTD(2),  Redraw_Colour_Black)) return 0;
                                if (!savedraw_rectangle_fill(OTD(cx + cw - 2), OTD(cy),          OTD(2),  OTD(ch), Redraw_Colour_Black)) return 0;
                                if (!savedraw_rectangle_fill(OTD(cx),          OTD(cy),          OTD(2),  OTD(ch), Redraw_Colour_Black)) return 0;
                                if (!savedraw_rectangle_fill(OTD(cx),          OTD(cy + ch - 2), OTD(cw), OTD(2),  Redraw_Colour_Black)) return 0;
                              }
                            }
                          }

                        /* 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] = OTD(tx);
                    x[1] = OTD(tx + tb);
                    x[2] = OTD(tx + tw - tb);
                    x[3] = OTD(tx + tw);

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

                    /* 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. First,    */
                    /* the bottom edge.                                     */

                    if (!savedraw_triangle_fill(x[0], y[0], x[1], y[1], x[3], y[0], Redraw_Colour_PlinthGrey))  return 0; /* 0 -> 1 -> 2 */
                    if (!savedraw_triangle_fill(x[1], y[1], x[3], y[0], x[2], y[1], Redraw_Colour_PlinthGrey))  return 0; /* 1 -> 2 -> 3 */

                    /* Right hand edge */

                    if (!savedraw_triangle_fill(x[3], y[0], x[2], y[1], x[3], y[3], Redraw_Colour_PlinthGrey))  return 0; /* 2 -> 3 -> 4 */
                    if (!savedraw_triangle_fill(x[2], y[1], x[3], y[3], x[2], y[2], Redraw_Colour_PlinthGrey))  return 0; /* 3 -> 4 -> 5 */

                    /* Now the lighter section - the top edge first.*/

                    if (!savedraw_triangle_fill(x[3], y[3], x[2], y[2], x[0], y[3], Redraw_Colour_AlmostWhite)) return 0; /* 4 -> 5 -> 6 */
                    if (!savedraw_triangle_fill(x[2], y[2], x[0], y[3], x[1], y[2], Redraw_Colour_AlmostWhite)) return 0; /* 5 -> 6 -> 7 */

                    /* Finally, the left hand edge. */

                    if (!savedraw_triangle_fill(x[0], y[3], x[1], y[2], x[0], y[0], Redraw_Colour_AlmostWhite)) return 0; /* 6 -> 7 -> 0 */
                    if (!savedraw_triangle_fill(x[1], y[2], x[0], y[0], x[1], y[1], Redraw_Colour_AlmostWhite)) return 0; /* 7 -> 0 -> 1 */
                  }

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

                  else
                  {
                    if (!savedraw_rectangle_fill(OTD(tx),           OTD(ty),           OTD(tw), OTD(tb), Redraw_Colour_Black)) return 0;
                    if (!savedraw_rectangle_fill(OTD(tx),           OTD(ty + th - tb), OTD(tw), OTD(tb), Redraw_Colour_Black)) return 0;
                    if (!savedraw_rectangle_fill(OTD(tx),           OTD(ty),           OTD(tb), OTD(th), Redraw_Colour_Black)) return 0;
                    if (!savedraw_rectangle_fill(OTD(tx + tw - tb), OTD(ty),           OTD(tb), OTD(th), Redraw_Colour_Black)) return 0;
                  }
                }

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

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

              asize = savedraw_input_field(b,
                                           tp,
                                           &box,
                                           redraw_token_colour(b, tp),
                                           tp->tagno == TAG_SELECT,
                                           size);

              if (!asize) return 0;
              else *tsize += asize;

              /* Now the text inside the item */

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

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

                /* Work out the vertical offset */

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

                /* We've not adjusted width and height for the border for the */
                /* same reason as the '-2' below; in Redraw.c, this code will */
                /* give the icon itself a border and the icon contents are    */
                /* centred within the bounding box that takes account of this */
                /* - whereas here, we're just plotting the icon in a BBox     */
                /* which fits it closely at an absolute position.             */

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

                /* Output the icon */

                asize = savedraw_switch(icon.xmin, icon.ymin, "fgright", size);

                if (!asize) return 0;
                else *tsize += asize;

                /* If we're on a non-grey background, give it a border */

                if (redraw_backcol(b) != Redraw_Colour_BackGrey)
                {
                  *tsize += DSIZE_FRECT * 4;

                  if (!size)
                  {
                    int lw, lh;

                    lw = icon.xmax - icon.xmin;
                    lh = icon.ymax - icon.ymin;

                    if (!savedraw_rectangle_fill(OTD(icon.xmin - 2),  OTD(icon.ymin - 2),  OTD(lw + 4), OTD(2),      Redraw_Colour_Black)) return 0;
                    if (!savedraw_rectangle_fill(OTD(icon.xmin - 2),  OTD(icon.ymin - 2),  OTD(2),      OTD(lh + 4), Redraw_Colour_Black)) return 0;
                    if (!savedraw_rectangle_fill(OTD(icon.xmin + lw), OTD(icon.ymin - 2),  OTD(2),      OTD(lh + 4), Redraw_Colour_Black)) return 0;
                    if (!savedraw_rectangle_fill(OTD(icon.xmin - 2),  OTD(icon.ymin + lh), OTD(lw + 4), OTD(2),      Redraw_Colour_Black)) return 0;
                  }
                }
              }
            }
            else switch(HtmlINPUTtype(tp))
            {
              /* Graphics-based forms elements */

              case inputtype_CHECKBOX:
              {
                int ox, oy;

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

                asize = savedraw_switch(ox,
                                        oy - 8, /* See redraw_switch, which subtracts 8 from the Y value itself */
                                        form_get_field(b, d->cdata[cn].t) -> checked ? "fopton" : "foptoff",
                                        size);


                if (!asize) return 0;
                else *tsize += asize;
              }
              break;

              case inputtype_RADIO:
              {
                int ox, oy;

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

                asize = savedraw_switch(ox,
                                        oy - 8, /* See redraw_switch, which subtracts 8 from the Y value itself */
                                        form_get_field(b, d->cdata[cn].t) -> checked ? "fradioon" : "fradiooff",
                                        size);

                if (!asize) return 0;
                else *tsize += asize;
              }
              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, th, tw;
                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);

                th = box.ymax - 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;

                /* Draw the button's plinth */

                colour = redraw_token_colour(b, tp);

                asize = savedraw_button(b, tp, &box, 0, size);

                if (!asize) return 0;
                else *tsize += asize;

                /* Plot the text (which will always fit in a button), centred horizontally */

                if (p && *p)
                {
                  int  length, end, offset;
                  char null = '\0';

                  length = strlen(p);
                  end    = 0;

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

                  if (fm_get_string_width(fh,
                                          p,
                                          0x1000000,
                                          end - d->cdata[cn].o,
                                          -1,
                                          NULL,
                                          &tw)) return 0;

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

                  /* Output the header */

                  if (!savedraw_texthdr(b,
                                        tp,
                                        PTD(x + offset),
                                        PTD(y + base),
                                        PTD(tw),
                                        OTD(th),
                                        0,
                                        length,
                                        Redraw_Colour_BackGrey)) return 0;

                  else *tsize += sizeof(draw_trfmtextstrhdr);

                  /* (The count should include a terminator at this point) */

                  length++;

                  /* Output the text ifself */

                  *tsize += (int) WordAlign(length);

                  if (!savedraw_write_bytes(p, length - 1)) return 0;
                  if (!savedraw_write_bytes(&null, 1)) return 0;

                  if (length != (int) WordAlign(length))
                  {
                    if (!savedraw_write_bytes("    ", (int) WordAlign(length) - length)) return 0;
                  }
                }
              }
              break;
            }
          }

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

          else if (ISOBJECT(tp) || (tp->style & IMG))
          {
            _kernel_oserror * e;
            BBox              box;
            int               ox, oy, o;
            int               ok;
            int               isobj;
            int               objim;

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

            if (ISOBJECT(tp)) isobj = 1, objim = object_token_is_image(b, tp);
            else              isobj = 0, objim = 0;

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

            e = isobj ? reformat_get_object_size(b, tp, &box) : reformat_get_image_size(b, tp, &box);

            if (!e)
            {
              draw_spristrhdr h;
              int             img_size = 0;

              if (!isobj || objim)
              {
                img_size = image_draw_file_size(b, tp, 1);

                h.tag    = draw_OBJSPRITE;
                h.size   = image_draw_file_size(b, tp, 1) + sizeof(h);
              }

              /* Correct the coordinates for plotting */

              ox -= box.xmin;

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

              h.bbox.xmin = OTD(box.xmin);
              h.bbox.ymin = OTD(box.ymin);
              h.bbox.xmax = OTD(box.xmax);
              h.bbox.ymax = OTD(box.ymax);

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

              if (isobj)                o = HtmlOBJECTborder(tp);
              else if (tp->style & IMG) o = tp->maxlen * 2;
              else                      o = 0;

              /* If we have an image to plot, output it */

              if (img_size)
              {
                if (!savedraw_write_bytes((char *) &h, sizeof(h))) return 0;

                /* Output the image itself - do this before the */
                /* image border (if any) so if there's some     */
                /* rounding when the Draw file is viewed scaled */
                /* down, the border doesn't get hidden.         */

                ok = image_to_draw_file(b, r, tp, o + box.xmin, o + box.ymin, 1);

                if (!ok) return 0;
              }

              /* Otherwise, output a placeholder */

              else
              {
                BBox         pbox = box;
                const char * text;
                int          asize;

                pbox.xmin += o;
                pbox.ymin += o;

                pbox.xmax -= pbox.xmin + o;
                pbox.ymax -= pbox.ymin + o;

                pbox.xmin &= ~1;
                pbox.ymin &= ~1;

                /* Work out what text to plot */

                text = objim ? HtmlOBJECTstandby(tp) : tp->text;

                /* Output the placeholder */

                asize = savedraw_draw_placeholder(b,
                                                  r,
                                                  &pbox,
                                                  tp,
                                                  text,
                                                  NULL);

                if (!asize) return 0;
                else *tsize += asize;
              }

              /* Draw a border of tp->maxlen * 2 OS units width around an image. */
              /* This comes from the image's BORDER attribute.                   */

              if (o)
              {
                unsigned int c = redraw_token_colour(b, tp);

                *tsize += 4 * DSIZE_FRECT;

                if (!size)
                {
                  if (!savedraw_rectangle_fill(OTD(box.xmin),     OTD(box.ymin),     OTD(box.xmax - box.xmin), OTD(o),                   c)) return 0;
                  if (!savedraw_rectangle_fill(OTD(box.xmin),     OTD(box.ymin),     OTD(o),                   OTD(box.ymax - box.ymin), c)) return 0;
                  if (!savedraw_rectangle_fill(OTD(box.xmin),     OTD(box.ymax - o), OTD(box.xmax - box.xmin), OTD(o),                   c)) return 0;
                  if (!savedraw_rectangle_fill(OTD(box.xmax - o), OTD(box.ymin),     OTD(o),                   OTD(box.ymax - box.ymin), c)) return 0;
                }
              }
            }
          }

          /* Plot a horizontal rule */

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

            convert_to_os(y, &oy);

            lmarg = redraw_left_gap(b, d, tp);

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

            /* Change ox and oy to draw coordinates */

            ox = OTD(ox);
            oy = OTD(oy);

            /* 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)
            {
              *tsize += DSIZE_FRECT; /* 1 rectangle */

              if (!savedraw_rectangle_fill(ox, oy, OTD(w), OTD(h), 0)) return 0;
            }
            else
            {
              if (h == 4)
              {
                /* Simple 'groove' rule */

                h = h / 2;

                w = OTD(w);
                h = OTD(h);

                *tsize += 2 * DSIZE_FRECT; /* 2 rectangles */

                if (!savedraw_rectangle_fill(ox, oy,     w, h, Redraw_Colour_MidGrey))     return 0;
                if (!savedraw_rectangle_fill(ox, oy - h, w, h, Redraw_Colour_AlmostWhite)) return 0;
              }
              else
              {
                /* 3D 'box' rule */

                w = OTD(w);
                h = OTD(h);

                *tsize += 4 * DSIZE_FRECT; /* 4 rectangles */

                if (!savedraw_rectangle_fill(ox,              oy,              w,      OTD(2), Redraw_Colour_AlmostWhite)) return 0;
                if (!savedraw_rectangle_fill(ox + w - OTD(2), oy,              OTD(2), h,      Redraw_Colour_AlmostWhite)) return 0;
                if (!savedraw_rectangle_fill(ox,              oy + h - OTD(2), w,      OTD(2), Redraw_Colour_MidGrey))     return 0;
                if (!savedraw_rectangle_fill(ox,              oy,              OTD(2), h,      Redraw_Colour_MidGrey))     return 0;
              }
            }
          }

          /* Plot a bullet point */

          else if (ISBULLET(tp))
          {
            int  ox, oy;
            int  asize;
            char spr[32];

            sprintf(spr, "b%d\0", (tp->indent + bullets - 1) % bullets);

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

            asize = savedraw_switch(ox, oy, spr, size);

            if (!asize) return 0;
            else *tsize += asize;
          }

          /* Plot some text */

          else
          {
            dp = tp->text;

            if (dp)
            {
              BBox   size;
              int    c, yofs, height;
              int    drax, dray;
              int    length;
              char   null = '\0';
              char * start;
              char * end;

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

              /* Get the x and y coords in Draw coordinate units */

              drax = PTD(x);
              dray = PTD(yofs);

              length = d->cdata[cn].l + 1;

              /* Can't have control characters in the Draw file output */

              start = dp + d->cdata[cn].o;
              end   = start + length - 2;

              while (length > 1 && *start && *start < ' ') start ++, length --;
              while (length > 1 && *end   < ' ')           end   --, length --;

              if (length && d->cdata[cn].w && d->ldata[l].h)
              {
                /* Write the text item header */

                *tsize += sizeof(draw_trfmtextstrhdr);

                if (!savedraw_texthdr(b,
                                      tp,
                                      drax,
                                      dray,
                                      PTD(d->cdata[cn].w),
                                      OTD(d->ldata[l].h),
                                      0,
                                      length - 1,
                                      redraw_background_colour(b, c))) return 0;

                /* Write the text itself */

                *tsize += (int) WordAlign(length);

                if (!savedraw_write_bytes(start, length - 1)) return 0;
                if (!savedraw_write_bytes(&null, 1)) return 0;

                if (length != (int) WordAlign(length))
                {
                  if (!savedraw_write_bytes("    ", (int) WordAlign(length) - length)) return 0;
                }

                /* 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, w, c;

                  /* Find the colour */

                  c = redraw_token_colour(b, tp);

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

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

                  /* Draw the 'line' as a rectangle */

                  ox = OTD(ox);
                  oy = OTD(oy);

                  w = PTD(d->cdata[cn].w);

                  *tsize += DSIZE_FRECT;
                  if (!savedraw_rectangle_fill(ox, oy, w, OTD(2), c)) return 0;
                }

                /* 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, w, c;
                  int hs;

                  c = redraw_token_colour(b, tp);

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

                  ox = OTD(ox);
                  oy = OTD(oy);

                  w = PTD(d->cdata[cn].w);

                  *tsize += DSIZE_FRECT;
                  if (!savedraw_rectangle_fill(ox, oy, w, OTD(2), c)) return 0;
                }
              }
            }
          }

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

  if (size) *size = *tsize;

  return 1;
}

/*************************************************/
/* savedraw_save_draw()                          */
/*                                               */
/* Save a given browser page as a Draw file.     */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the page to save as   */
/*             a Draw file;                      */
/*                                               */
/*             Pointer to the pathname to save   */
/*             to;                               */
/*                                               */
/*             1 to include background image     */
/*             tiling if there is such an image, */
/*             else 0 (0 recommended!).          */
/*************************************************/

_kernel_oserror * savedraw_save_draw(browser_data * b, const char * pathname, int bgimages)
{
  int success = 0;

  /* If we seem to have left a file open, close it */

  if (outfile)
  {
    fclose(outfile);
    outfile = NULL;
  }

  /* Only proceed if we've got a pathname */

  if (pathname && *pathname)
  {
    _swix(Hourglass_On, 0);

    save_record_path(pathname);

    /* Open the file */

    outfile = fopen(pathname, "wb");

    if (!outfile) RetLastE;

    /* Create the file */

    success = savedraw_create(b, bgimages, NULL);

    fclose(outfile);
    outfile = NULL;

    _swix(Hourglass_Off, 0);

    if (!success) RetLastE;

    /* Set the filetype */

    return _swix(OS_File,
                 _INR(0,2) | _INR(4,5),

                 18,
                 pathname,
                 FileType_DRAW);
  }

  return NULL;
}

/*************************************************/
/* savedraw_draw_size()                          */
/*                                               */
/* Returns the size of file that would be        */
/* written by savedraw_save_draw for the given   */
/* browser.                                      */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the page to save as   */
/*             a Draw file, for which the file   */
/*             size is to be returned;           */
/*                                               */
/*             1 to include background image     */
/*             tiling in the count if there is   */
/*             such an image, else 0.            */
/*                                               */
/* Returns:    Size of file that would be        */
/*             written by savedraw_save_draw for */
/*             the given browser.                */
/*************************************************/

int savedraw_draw_size(browser_data * b, int bgimages)
{
  int size;

//  savedraw_create(b, bgimages, &size);
size = 4096;

  return size;
}