/* 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   : Images.c                               */
/*                                                 */
/* Purpose: Image related functions.               */
/*                                                 */
/* Author : Merlyn Kline for Customer browser     */
/*          This source adapted by A.D.Hodgkinson  */
/*                                                 */
/* History: 28-Nov-96: Created with dummy function */
/*                     for temporary use in other  */
/*                     routines.                   */
/*          20-Jan-97: Filled in with most of the  */
/*                     functions present and       */
/*                     converted to the new data   */
/*                     structures where needed.    */
/*          15-Mar-97: Reorganised a bit, putting  */
/*                     functions in a more logical */
/*                     order to aid legibility.    */
/***************************************************/

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.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 "toolbox.h"

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

#include "Browser.h"
#include "Fetch.h" /* (Which itself includes URLstat.h) */
#include "FetchHTML.h"
#include "FetchPage.h"
#include "FontManage.h"
#include "PrintStyle.h"
#include "Redraw.h"
#include "Reformat.h"
#include "TokenUtils.h"
#include "Toolbars.h"
#include "URLveneer.h"

#include "Images.h"

/* Statics */

static _kernel_oserror * image_load_chunk             (browser_data * b, int image, char * buffer, int bytes);
static void              image_remove_data            (browser_data * b, int image);
static void              image_remove_all_data        (browser_data * b, int image);
static void              image_delay                  (browser_data * b, int image);
static void              image_abandon                (browser_data * b, int image);
static void              image_delete_image_entry     (int image);

static _kernel_oserror * image_update_area            (browser_data * b, int x, int y, BBox * ubox, int image, int redraw);
static _kernel_oserror * image_update_image           (browser_data * b, int image, BBox * box);

static int               image_data_offset            (browser_data * b, int image);
static int               image_count_fetches          (browser_data * b);

static int               image_get_token_image_xref   (browser_data * b, HStream * token);
static int               image_get_token_image_actual (browser_data * b, HStream * token);

static _kernel_oserror * image_get_image_size         (browser_data * b, int image, BBox * box);

/* Locals */

static image_info * idata       = NULL;
static char       * ddata       = NULL;
static int          nimages     = 0;
static int          lastimage   = 0;
static int          animhandler = 0;

/* For animated images, the filler function for the background of a   */
/* transparent image will get called only for the main image with the */
/* associated image data - not for any cross referenced images. These */
/* statics hold the browser and image being redrawn at any time, so   */
/* the filler function knows which cross referencing image is         */
/* actually being redrawn.                                            */

static int            image_redrawing   = -1;
static browser_data * browser_redrawing = NULL;

/* Local compilation options */

#undef TRACE_FETCH_STORE

// Unimplemented functions:

// /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
// static _kernel_oserror * image_savesprite_start_save1(image_info * ip,int * isize)
// {
//   int        size;
//
//   * isize=-1;
//   if(ip && ip->istore && ip->istore->width>0)
//   {
//     ERROUT((_kernel_oserror*)ip->istore->StartExport(ip->istore,&size));
//     size-=12; /* subtract the size of the sprite area header */
//   }
//   else
//   {
//     sprite_header * p;
//     sprite_area   * sa;
//     sprite_id     sid;
//
//     image_get_broken_sprite(&sa,&sid);
//     p=(sprite_header*)sid.s.addr;
//     size=WORDALIGN(p->next);
//   }
//   * isize=size;
//   return(NULL);
// }
//
// /* ----------------------------------------------------------------------*/
// _kernel_oserror * image_savesprite_start_save(browser_data * b,int token,int * isize)
// {
//   image_info * ip;
//
//   ip=image_get_token_image_xref(b,token);
//   return(image_savesprite_start_save1(ip,isize));
// }
//
//
// /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
// static int image_savesprite_save_image1(image_info * ip)
// {
//   sprite_header * p;
//   sprite_area   * sa;
//   sprite_id     sid;
//
//   if(ip && ip->istore && ip->istore->width>0)
//   {
//     int  len;
//     char buffer[2048];
//     int  skip=12; /* skip first 12 bytes (sprite area header) */
//
//     do
//     {
//       len=sizeof(buffer);
//       if(wimpt_complain((_kernel_oserror*)ip->istore->Export(ip->istore,buffer,&len))) return(0);
//       if(len-skip>0 && !save_write_bytes(buffer+skip,len-skip)) return(0);
//       skip-=len;
//       if(skip<0) skip=0;
//     } while(len);
//     return(1);
//   }
//   image_get_broken_sprite(&sa,&sid);
//   p=(sprite_header*)sid.s.addr;
//   return(save_write_bytes((void*)p,WORDALIGN(p->next)));
// }
//
// /* ----------------------------------------------------------------------*/
// int image_savesprite_save_image(browser_data * b,int token)
// {
//   image_info    * ip;
//
//   ip=image_get_token_image_xref(b,token);
//   return(image_savesprite_save_image1(ip));
// }
//
// /* ----------------------------------------------------------------------*/
// void image_savesprite_end_save(browser_data * b,int token)
// {
//   image_info * ip;
//
//   ip=image_get_token_image_xref(b,token);
//   if(ip && ip->istore && ip->istore->width>0) ip->istore->EndExport(ip->istore);
// }
//
// /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
// static int image_picture_saver(void * handle,char * pathname)
// {
//   browser       * b;
//   int           * si,token;
//   image_info    * ip;
//
//   pathname=pathname;
//   si=(int*)handle;
//   b=(browser*)si[0];
//   token=si[1];
//   ip=NULL;
//   if(token>0) ip=image_get_token_image_xref(b,token);
//   else if(b->backimage>0) ip=image_info_addr(b,b->backimage);
//   if(ip && ip->istore && ip->istore->width>0)
//   {
//     int  len;
//     char buffer[2048];
//
//     do
//     {
//       len=sizeof(buffer);
//       if(wimpt_complain((_kernel_oserror*)ip->istore->Export(ip->istore,buffer,&len))) return(0);
//       if(len && !save_write_bytes(buffer,len)) return(0);
//     } while(len);
//   }
//   else
//   {
//     sprite_area   area,* sa;
//     sprite_id     sid;
//     sprite_header * p;
//
//     image_get_broken_sprite(&sa,&sid);
//     p=(sprite_header*)sid.s.addr;
//     area.size=sizeof(sprite_area)+p->next;
//     area.number=1;
//     area.sproff=sizeof(sprite_area);
//     area.freeoff=area.size;
//     if(!save_write_bytes((void*)(&area.number),sizeof(area)-4)) return(0);
//     return(save_write_bytes((void*)p,p->next));
//   }
//   return(1);
// }
//
// /* ----------------------------------------------------------------------*/
// _kernel_oserror * image_save_picture(browser_data * b,int token)
// {
//   int        size,saveinfo[2];
//   char       pathname[256];
//   image_info * ip;
//
//   ip=NULL;
//   if(token && fetch_token_address(b,token)->style&IMG) ip=image_get_token_image_xref(b,token);
//   if(!ip && b->backimage>0)
//   {
//     ip=image_info_addr(b,b->backimage);
//     token=0;
//   }
//   if(ip && ip->istore && ip->istore->width>0) ip->istore->StartExport(ip->istore,&size);
//   else
//   {
//     sprite_area   * sa;
//     sprite_id     sid;
//     sprite_header * p;
//
//     image_get_broken_sprite(&sa,&sid);
//     p=(sprite_header*)sid.s.addr;
//     size=sizeof(sprite_area)+p->next-4;
//   }
//   saveinfo[0]=(int)b;
//   saveinfo[1]=token;
//   strcpy(pathname,msgs_lookup("sprfile:SpriteFile"));
//   save_saveas(FILETYPE_SPRITE,pathname,size,image_picture_saver,NULL,(void*)saveinfo);
//   if(ip && ip->istore && ip->istore->width>0) ip->istore->EndExport(ip->istore);
//   return(NULL);
// }

// /* ----------------------------------------------------------------------*/
// _kernel_oserror * image_saveback_start_save(browser_data * b,int * isize)
// {
//   image_info * ip;
//
//   ip=image_info_addr(b,b->backimage);
//   return(image_savesprite_start_save1(ip,isize));
// }
//
// /* ----------------------------------------------------------------------*/
// int image_saveback_save_image(browser_data * b)
// {
//   image_info    * ip;
//
//   ip=image_info_addr(b,b->backimage);
//   return(image_savesprite_save_image1(ip));
// }
//
// /* ----------------------------------------------------------------------*/
// void image_saveback_end_save(browser_data * b)
// {
//   image_info * ip;
//
//   ip=image_info_addr(b,b->backimage);
//   if(ip && idata[i].istore && idata[i].istore->width>0) idata[i].istore->EndExport(idata[i].istore);
// }

// Deprecated functions:

// /*************************************************/
// /* image_info_addr                               */
// /*                                               */
// /* Returns the address of the image_info struct  */
// /* for a given image number.                     */
// /*                                               */
// /* This function is deprecated. Use a direct     */
// /* array reference (i.e. idata[image]) in        */
// /* preference, as this is robust with respect to */
// /* flex block movements (see Assumes below).     */
// /*                                               */
// /* Parameters: Pointer to a browser_data struct  */
// /*             relevant to the image in          */
// /*             question;                         */
// /*                                               */
// /*             An image number, from 0 to        */
// /*             nimages - 1.                      */
// /*                                               */
// /* Returns:    Pointer to the image_info struct  */
// /*             for that image.                   */
// /*                                               */
// /* Assumes:    The pointer is in a flex block,   */
// /*             so it will be invalidated if the  */
// /*             block should move;                */
// /*                                               */
// /*             No limit checking is performed on */
// /*             the image number.                 */
// /*************************************************/
//
// static image_info * image_info_addr(browser_data * b, int image)
// {
//   #ifdef TRACE
//     if (tl & (1u<<15)) Printf("image_info_addr: Called for image %d, and exitting\n",image);
//   #endif
//
//   return &idata[image];
// }

// Deprecated fragments:

// No such thing as The Broken Sprite anymore...
//
// static void image_get_broken_sprite(sprite_area * * sa,sprite_id * sid)
// {
//   * sa=resspr_area();
//   sid->tag=sprite_id_name;
//   sid->s.name="missing";
//   plotspr_locate_sprite(* sa,sid);
// }

/*************************************************/
/* image_load_chunk()                            */
/*                                               */
/* Tell the image library to deal with some of   */
/* image data we've fetched for a given image.   */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the image;            */
/*                                               */
/*             The image number, from 0 to       */
/*             nimages - 1;                      */
/*                                               */
/*             Pointer to a buffer containing    */
/*             the data, as a char *;            */
/*                                               */
/*             The amount of data in that        */
/*             buffer.                           */
/*************************************************/

static _kernel_oserror * image_load_chunk(browser_data * b, int image, char * buffer, int bytes)
{
  BBox              box;
  _kernel_oserror * e = NULL;

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_load_chunk: Called for image %d\n",image);
  #endif

  /* If the library has filled in the istore field, tell it to */
  /* get some of the data we've fetched.                       */

  if (idata[image].istore) e = idata[image].istore -> Load(idata[image].istore,
                                                           buffer,
                                                           bytes,
                                                           &box);

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_load_chunk: Exitting with error\n");
  #endif

  /* Return errors for STRICT_PARSER builds only */

  #ifdef STRICT_PARSER
    if (e) return e;
  #else
    if (e) return NULL;
  #endif

  /* Update the image on the page to reflect the new data */

  image_update_image(b, image, &box);

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_load_chunk: Successful\n");
  #endif

  return NULL;
}

/*************************************************/
/* image_remove_data()                           */
/*                                               */
/* Discards the fetched image data associated    */
/* with an image, but leaves the URL information */
/* alone.                                        */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the image;            */
/*                                               */
/*             The image number, from 0 to       */
/*             nimages - 1.                      */
/*************************************************/

static void image_remove_data(browser_data * b, int image)
{
  int p, oldsize, remove;

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_remove_data: Called for image %d\n",image);
  #endif

  /* Get the current data size */

  oldsize = flex_size((flex_ptr) &ddata);

  /* The alloc field holds the total amount of data associated */
  /* with the image, and the ualloc field holds the amount     */
  /* that the URL uses. So subtract ualloc from alloc to get   */
  /* the amount that must be removed.                          */

  remove = idata[image].alloc - idata[image].ualloc;

  /* Need to shuffle the rest of the flex block */
  /* down and shrink the block, if this isn't   */
  /* the last image (i.e. p != oldsize).        */

  p = image_data_offset(b,image) + idata[image].alloc;

  if (p != oldsize) memmove(ddata + p - remove, ddata + p, oldsize - p);

  flex_extend((flex_ptr) &ddata, oldsize - remove);

  #ifdef TRACE
    flexcount -= remove;
    if (tl & (1u<<14)) Printf("**   flexcount: %d\n",flexcount);
  #endif

  /* Subtract the amount removed from the alloc field */
  /* and set isize to zero, i.e. we're not storing    */
  /* any image data anymore.                          */

  idata[image].alloc -= remove;
  idata[image].isize  = 0;

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_remove_data: Successful\n");
  #endif
}

/*************************************************/
/* image_remove_all_data()                       */
/*                                               */
/* Discards the fetched image data associated    */
/* with an image, including URL information.     */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the image;            */
/*                                               */
/*             The image number, from 0 to       */
/*             nimages - 1.                      */
/*************************************************/

static void image_remove_all_data(browser_data * b, int image)
{
  int p, oldsize, remove;

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_remove_all_data: Called for image %d\n",image);
  #endif

  /* Get the current data size */

  oldsize = flex_size((flex_ptr) &ddata);

  /* The alloc field holds the total amount of data associated */
  /* with the image.                                           */

  remove = idata[image].alloc;

  if (remove)
  {
    /* Need to shuffle the rest of the flex block */
    /* down and shrink the block, if this isn't   */
    /* the last image (i.e. p != oldsize).        */

    p = image_data_offset(b, image);

    if (p != oldsize) memmove(ddata + p, ddata + p + remove, oldsize - p - remove);

    flex_extend((flex_ptr) &ddata, oldsize - remove);

    #ifdef TRACE
      flexcount -= remove;
      if (tl & (1u<<14)) Printf("**   flexcount: %d\n",flexcount);
    #endif
  }

  /* Subtract the amount removed from the alloc field */
  /* and set isize to zero, i.e. we're not storing    */
  /* any image data anymore.                          */

  idata[image].alloc = 0;
  idata[image].isize = 0;

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_remove_all_data: Successful\n");
  #endif
}

/*************************************************/
/* image_delay()                                 */
/*                                               */
/* Stop fetching data for an image, even if it   */
/* hasn't all been fetched, but allow the fetch  */
/* to continue later on.                         */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the image;            */
/*                                               */
/*             The image number, from 0 to       */
/*             nimages - 1.                      */
/*************************************************/

static void image_delay(browser_data * b, int image)
{
  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_delay: Called for image %d\n",image);
  #endif

  /* Stop any fetching for the image */

  if (idata[image].handle) html_close(idata[image].handle);

  idata[image].handle  = 0;
  idata[image].delayed = 1;

  /* If this is a background, go back to the plain background */

  if (image == b->backimage) b->aacol = redraw_backcol(b);

  if (idata[image].istore) image_update_image(b, image, NULL);

  /* There may be a new overall fetch status, so ensure the */
  /* status bar is up to date                               */

  toolbars_cancel_status(b, Toolbars_Status_GetPics);

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_delay: Successful\n");
  #endif
}

/*************************************************/
/* image_abandon()                               */
/*                                               */
/* Stop fetching data for an image, even if it   */
/* hasn't all been fetched.                      */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the image;            */
/*                                               */
/*             The image number, from 0 to       */
/*             nimages - 1.                      */
/*************************************************/

static void image_abandon(browser_data * b, int image)
{
  BBox end;

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_abandon: Called for image %d\n",image);
  #endif

  /* Stop any fetching for the image */

  if (idata[image].handle) html_close(idata[image].handle);

  idata[image].handle  = 0;
  idata[image].fetched = 1;

  /* If this is a background, go back to the plain background */

  if (image == b->backimage) b->aacol = redraw_backcol(b);

  if (idata[image].istore)
  {
//    #ifdef STRICT_PARSER
//
//      /* Report any errors as Continue-only in strict parser mode */
//
//      _kernel_oserror * e;
//
//      e = idata[image].istore -> EndLoad(idata[image].istore);
//
//      if (e)
//      {
//        e->errnum = Utils_Error_Custom_Message;
//        show_error_ret(e);
//      }
//
//    #else
//
//      /* If not in Strict mode, ignore any errors */
//
    idata[image].istore -> EndLoad(idata[image].istore, &end);
//
//    #endif

    image_update_image(b, image, &end);
  }

  /* There may be a new overall fetch status, so ensure the */
  /* status bar is up to date                               */

  toolbars_cancel_status(b, Toolbars_Status_GetPics);

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_abandon: Successful\n");
  #endif
}

/*************************************************/
/* image_abort_fetches()                         */
/*                                               */
/* Stop fetching data for all images belonging   */
/* to a given browser, discarding all data for   */
/* those images. The fetches cannot be restarted */
/* after this!                                   */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the images.           */
/*************************************************/

void image_abort_fetches(browser_data * b)
{
  int i;

  /* Loop through the images */

  for (i = 0; i < nimages; i++)
  {
    /* If owned by this browser... */

    if (idata[i].owner == b)
    {
      /* Mark the image as fetched (so pending images don't then */
      /* start to fetch after fetching images stop).             */

      idata[i].fetched = 1;

      /* Stop any images which are fetching */

      if (idata[i].handle)
      {
        html_close(idata[i].handle);
        idata[i].handle = 0;

        /* If this is a background, go back to the plain background */

        if (i == b->backimage) b->aacol = redraw_backcol(b);

        if (idata[i].istore)
        {
          #ifdef STRICT_PARSER

            /* Report any errors as Continue-only in strict parser mode */

            _kernel_oserror * e;

            e = idata[i].istore -> Delete(idata[i].istore);

            if (e)
            {
              e->errnum = Utils_Error_Custom_Message;
              show_error_ret(e);
            }

          #else

            /* If not in Strict mode, ignore any errors */

            idata[i].istore -> Delete(idata[i].istore);

          #endif

          idata[i].istore = NULL;

          browser_update_token(b, idata[i].token, 0, 0);
        }
      }
    }
  }

  /* There may be a new overall fetch status, so ensure the */
  /* status bar is up to date                               */

  toolbars_cancel_status(b, Toolbars_Status_GetPics);
}

/*************************************************/
/* image_new_image()                             */
/*                                               */
/* Starts fetching a new image. Creates a record */
/* in the idata flex block and increments the    */
/* image counter. If an image is already being   */
/* fetched with the same URL then a cross        */
/* reference is set up to point to that, else a  */
/* fetch is initiated.                           */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the image to fetch;   */
/*                                               */
/*             Pointer to the URL to fetch;      */
/*                                               */
/*             The address of the token that led */
/*             to this function call, i.e. the   */
/*             token representing that image;    */
/*                                               */
/*             1 if the image is for the         */
/*             background (it will be turned     */
/*             into a sprite for fast plotting), */
/*             else 0. If 2, then the backimage  */
/*             field of the browser_data         */
/*             structure is filled in with the   */
/*             relevant image number and the     */
/*             image will again be turned into a */
/*             sprite for fast plotting.         */
/*************************************************/

_kernel_oserror * image_new_image(browser_data * b, const char * url, HStream * token, int background)
{
  int       size, ok, ulen, xref;
  HStream * tp;

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

  /* Can't proceed without the URL to fetch! */

  if (!url) url = ""; /* Point to *something*, even if it is blank */

  xref = -1;
  tp   = token;

  /* Need to make sure we don't already have an image with this URL */
  /* if there are already some images present.                      */

  if (nimages)
  {
    int i;

    for (i = 0; i < nimages; i++)
    {
      if (
           idata[i].alloc                               && /* The image has data allocated for it                 */
           !strcmp(url, ddata + image_data_offset(b,i)) && /* This holds a URL which matches that passed in       */
           idata[i].xref < 0                            && /* The image doesn't cross reference things itself     */
           idata[i].background == !!background             /* We're not trying to make a b/g an f/g or vice versa */
         )
      {
        xref = i;       /* If all of the above are satisfied, cross reference */
        i    = nimages; /* this image with the one it matches.                */
      }
    }
  }

  if (xref >= 0) ulen = 0; /* Don't need any extra data for a cross referenced image. */
  else
  {
    ulen = (strlen(url) + 1);
    ulen = (int) WordAlign(ulen);
  }

  size = ulen;

  /* Allocate space for the image_info structure */

  if (idata)
  {
    int oldsize;

    oldsize = flex_size  ((flex_ptr) &idata);
    ok      = flex_extend((flex_ptr) &idata, oldsize + sizeof(image_info));
  }
  else ok   = flex_alloc ((flex_ptr) &idata, sizeof(image_info));

  /* Allocate space for the image data */

  if (ok && ddata)
  {
    int oldsize;

    #ifdef TRACE
      flexcount += sizeof(image_info);
      if (tl & (1u<<14)) Printf("**   flexcount: %d\n",flexcount);
    #endif

    oldsize = flex_size  ((flex_ptr) &ddata);
    ok      = flex_extend((flex_ptr) &ddata, oldsize + size);
  }
  else ok   = flex_alloc ((flex_ptr) &ddata, size);

  /* Throw back an error if out of memory */

  if (!ok)
  {
    erb.errnum = Utils_Error_Custom_Message;

    StrNCpy0(erb.errmess,
             lookup_token("NoMemImg:There is not enough free memory for any new images (%0).",
                          0,
                          "1"));

    return &erb;
  }

  #ifdef TRACE
    flexcount += size;
    if (tl & (1u<<14)) Printf("**   flexcount: %d\n",flexcount);
  #endif

//  if (idata)
//  {
//    char * p;
//    int    olddatasize;
//
//    olddatasize = flex_size((flex_ptr) &idata);
//    size += olddatasize;
//    ok = flex_extend((flex_ptr) &idata,size);
//    p = (char*) idata;
//    p+=nimages* sizeof(image_info);
//    memmove(p+sizeof(image_info),p,olddatasize-nimages* sizeof(image_info));
//  }
//  else ok=mflex_alloc(&idata,size);
//
//  if(!ok) return(errorgen(2));

  /* Increment the image counter, zero the contents of the image_info structure */
  /* and fill in some of the fields where contents are known now.               */

  memset(&idata[nimages], 0, sizeof(image_info)); /* Precautionary catch-all */

  idata[nimages].owner      = b;
  idata[nimages].x          = -1;
  idata[nimages].y          = -1;

  idata[nimages].alloc      = size;
  idata[nimages].ualloc     = ulen;
  idata[nimages].isize      = 0;

  idata[nimages].token      = tp;
  idata[nimages].xref       = xref;

  idata[nimages].background = !!background;

  /* Set the 'delayed' flag if it's not a background image and */
  /* images are flagged as not being shown right now, or if it */
  /* is a backgrond image and backgrounds are flagged to be    */
  /* plain only.                                               */

  if (background)
  {
    idata[nimages].delayed   = b->plainback;
    idata[nimages].canredraw = 1;

    /* Set the background image number */

    if (background == 2) b->backimage = nimages;
  }
  else idata[nimages].delayed = !b->displayimages && b->displayed != Display_External_Image;

  // This works, but you've then got to alter the dataoffset fields
  // of higher images whenever you remove any data from one lower in
  // the flex block. That's not implemented yet, and the speed penalty
  // of this may offset the gains in image_data_offset.
  //
  // if (!nimages) idata[nimages].dataoffset = 0;
  // else idata[nimages].dataoffset = idata[nimages - 1].dataoffset + alloc;

  /* If the image is not a background one, need to update the */
  /* width and height fields according to any details in the  */
  /* HTML source.                                             */

  if (!background && tp->tagno != TAG_INPUT)
  {
    if (tp->cols > 0) idata[nimages].currw = tp->cols * 2;
    if (tp->rows > 0) idata[nimages].currh = tp->rows * 2;

    if (tp->cols > 0 && tp->rows > 0) idata[nimages].fixedsize = 1, idata[nimages].canredraw = 1;
  }

  if (!choices.refowait) idata[nimages].canredraw = 1; /* If not waiting, reformats are immediate so canredraw is always true */

  nimages++;

  /* If there's data for a URL, need to tell the image handling library */
  /* about the new image.                                               */

  if (ulen)
  {
    idata[nimages - 1].istore = NewImage(NULL, background ? IMAGE_FAST : 0);

    /* Fill in the URL */

    strcpy(ddata + image_data_offset(b, nimages - 1), url);
  }
  else
  {
    /* If this is a cross-referencing image, and it hasn't got  */
    /* its fixedsize flag set, then it has no size info yet.    */
    /* In that case, get it from the image it cross references. */

    if (!idata[nimages - 1].fixedsize)
    {
      int xref = idata[nimages - 1].xref;

      if (xref >= 0 && idata[xref].istore)
      {
        idata[nimages - 1].currw = idata[xref].istore->width_os;
        idata[nimages - 1].currh = idata[xref].istore->height_os;
      }
    }
  }

  /* Reflect any change of status */

  toolbars_update_status(b, Toolbars_Status_GetPics);
  toolbars_set_button_states(b);

  /* Finished. */

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_new_image: Successful\n");
  #endif

  return NULL;
}

/*************************************************/
/* image_process_null()                          */
/*                                               */
/* The main engine of the image loading system.  */
/* Allows up to choices.maximages images to be   */
/* simultaneously fetched. Handles redraw,       */
/* memory allocation, out-of-memory fetch stops, */
/* and so-forth.                                 */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the images.           */
/*************************************************/

_kernel_oserror * image_process_null(browser_data * b)
{
  _kernel_oserror * e;

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

  /* If there are not maximages images being fetched and there are still  */
  /* images which have not started fetching, then start a fetch for one.  */
  /* Then do a 'get next chunk' on the next image with a fetch going      */
  /* on. Cause a screen update as appropriate.                            */

  if (nimages && b->fetch_status != BS_DATAFETCH)
  {
    int image, fetches;

    fetches = image_count_fetches(b);

    if (lastimage < 0 || lastimage >= nimages) lastimage = nimages - 1;

    image = lastimage;

    /* For all images up to and including the last one dealt with in a */
    /* null event, initiate fetches if they haven't already started.   */

    do
    {
      image ++;
      if (image >= nimages) image = 0;

      if (
           !idata[image].handle        &&
           !idata[image].fetched       &&
           !idata[image].delayed       &&
           idata[image].xref < 0       &&
           fetches < choices.maximages
         )
      {
        e = html_get(ddata + image_data_offset(b,image), /* Document to fetch                */
                     NULL,                               /* Pointer to any extra data        */
                     (int *) &idata[image].handle,       /* Place to return the fetch handle */
                     URL_Method_http_GET,                /* Fetch method                     */

                     #ifndef SINGLE_USER
                       user.name,                        /* User name for MailServ, if using */
                     #else                               /* a multiuser version              */
                       NULL,
                     #endif

                     0,                                  /* 0 = Don't parse fetched data     */
                     !b->reloading);                     /* 1 = Proxy can be used, 0 = can't */

        if (e)
        {
          show_error_ret(e);
          image_abandon(b, image);
        }
      }
    }
    while (image != lastimage && idata[image].handle == 0);

    lastimage = image;

    /* If the image we're now on has a fetch handle... */

    if (idata[image].handle)
    {
      char buffer[10240];

      /* ...And a fetch has already started... */

      if (idata[image].started)
      {
        int done, bytes;

        /* ...Then get the next chunk of data. If the fetch */
        /* call gives an error, abandon the image fetch and */
        /* exit.                                            */

        e = fetch_get_raw_data(NULL,                /* The browser_data structure */
                               idata[image].handle, /* Fetch handle */
                               buffer,              /* Buffer for fetched data */
                               sizeof(buffer),      /* Buffer's size */
                               &done,               /* 'done' = 1 for fetch completed, else 0 */
                               &bytes);             /* Number of bytes put into buffer */

        if (e)
        {
          image_abandon(b, image);
          return e;
        }

        idata[image].bytesgot += bytes;

        /* If the fetch_get_raw_data call resulted in data being fetched, */
        /* send that to the image library and reflect the fetched data in */
        /* the status bar.                                                */

        if (bytes)
        {
          e = image_load_chunk(b, image, buffer, bytes);

          if (e)
          {
            image_abandon(b, image);
            return e;
          }

          toolbars_update_progress(b);
        }

        /* If the fetch has finished, update everything as appropriate */

        if (done)
        {
          BBox end;

          html_close(idata[image].handle);

          idata[image].handle  = 0;
          idata[image].fetched = 1;
          idata[image].success = 1;

          if (idata[image].istore)
          {
            #ifdef STRICT_PARSER

              /* Report any errors as Continue-only in strict parser mode */

              e = idata[image].istore -> EndLoad(idata[image].istore, &end);

              if (e)
              {
                e->errnum = Utils_Error_Custom_Message;
                show_error_ret(e);
              }

            #else

              /* If not in Strict mode, ignore any errors */

              idata[image].istore -> EndLoad(idata[image].istore, &end);

            #endif
          }

          /* If the image is a background image and the library has */
          /* dealt with it (this is assumed to be the case if the   */
          /* library has assigned a width to the image) then get    */
          /* the background text colour from the library on the     */
          /* basis of the image's contents. Else, use the plain     */
          /* background colour.                                     */
          /*                                                        */
          /* Note that it is not until the call to BGCol that       */
          /* ImageLib actually turns the background image into a    */
          /* sprite in the current colour depth, so if this isn't   */
          /* called, the sprite will plot slowly resulting in       */
          /* (typically) painfully slow redraws.                    */

          if (image == idata[image].owner->backimage)
          {
            if (idata[image].istore->width > 0)
            {
              #ifdef STRICT_PARSER

                /* Report any errors as Continue-only in strict parser mode */

                e = idata[image].istore -> BGCol(idata[image].istore,
                                                 &b->aacol);

                if (e)
                {
                  e->errnum = Utils_Error_Custom_Message;
                  show_error_ret(e);
                }

              #else

                /* If not in Strict mode, ignore any errors */

                idata[image].istore -> BGCol(idata[image].istore,
                                             &b->aacol);

              #endif
            }
            else b->aacol = redraw_backcol(b);
          }

          image_update_image(b, image, &end);
        }
      }
      else
      {
        /* In this block of code, the current image has had a fetch */
        /* started for it, but hasn't fetched any data yet - so we  */
        /* haven't (for example) got a full header for the request. */

        int       remn, sofar, waiting;
        int       realsize;
        HStream * tptr;
        void    * store;

        /* Go in with no store; only interested in new data, as  */
        /* anything fetched so far goes into the ddata block. */

        store = NULL;

        /* At this stage, must put the data fetched so far back into the  */
        /* store. This is because headers may come in over several        */
        /* passes through the fetcher - if we give the fetcher an empty   */
        /* block each time, it won't be able to see the whole header      */
        /* block and the fetch fails (well, the image library keeps       */
        /* waiting for something to happen). At the time of writing this  */
        /* is demonstrated by the images at http://www.debenhams.co.uk/'. */

        if (idata[image].isize)
        {
          int p = image_data_offset(b, image) + idata[image].ualloc;

          /* Complain if the flex allocation fails, else copy the data across */

          if (!flex_alloc(&store, idata[image].isize))
          {
            image_abandon(b, image);

            return NULL;
          }
          else
          {
            #ifdef TRACE
              flexcount += idata[image].isize;
              if (tl & (1u<<14)) Printf("**   flexcount: %d\n",flexcount);
            #endif

            memcpy(store, ddata + p, idata[image].isize);
          }
        }

        #ifdef TRACE_FETCH_STORE

          if (store)
          {
            int  offset;
            int  size = flex_size(&store);
            char cha;
            int  nl   = 0;

            Printf("Image %d pre-fetch store:\n\n",image);

            for (offset = 0; offset < size; offset ++)
            {
              cha = ((char *) store)[offset];

              if (cha > 31) Printf("%c",cha);
              if (cha == 13 || cha == 10)
              {
                if (!nl)
                {
                  Printf("\n");
                  nl = 1;
                }
                else nl = 0;
              }
            }

            Printf("\nEnd of store\n\n");
          }

        #endif

        /* Call the fetch routine */

        e = html_get_next_token(b,
                                idata[image].handle,
                                &remn,
                                &sofar,
                                &tptr,
                                &waiting,
                                &store,
                                ddata + image_data_offset(b, image),
                                1);

        if (e)
        {
          /* If there's an error, free the store and ditch the image */

          if (store)
          {
            #ifdef TRACE
              flexcount -= flex_size(&store);
              if (tl & (1u<<14)) Printf("**   flexcount: %d\n",flexcount);
            #endif

            flex_free(&store);
          }

          image_abandon(b, image);

          #ifdef TRACE
            if (tl & (1u<<15)) Printf("image_process_null: Exitting with error\n");
          #endif

          return e;
        }

        #ifdef TRACE_FETCH_STORE

          if (store)
          {
            int  offset;
            int  size = flex_size(&store);
            char cha;
            int  nl   = 0;

            Printf("Image %d post-fetch store:\n\n",image);

            for (offset = 0; offset < size; offset ++)
            {
              cha = ((char *) store)[offset];

              if (cha > 31) Printf("%c",cha);
              if (cha == 13 || cha == 10)
              {
                if (!nl)
                {
                  Printf("\n");
                  nl = 1;
                }
                else nl = 0;
              }
            }

            Printf("\nEnd of store\n\n");
          }

        #endif

        /* How much data was added? */

        if (store) realsize = flex_size(&store) - idata[image].isize; /* We already had 'isize' bytes before the fetcher was entered */
        else       realsize = 0;

        /* If we've got data in the local store, copy it to the image store. */

        if (realsize >= 0)
        {
          int p, oldsize;

          /* oldsize holds the whole image data block size (for all images) */

          oldsize = flex_size((flex_ptr) &ddata);

          /* Allocate 'size' extra bytes, doing the relevant error handling */
          /* code if the allocation fails                                   */

          if (!flex_extend((flex_ptr) &ddata, oldsize + realsize))
          {
            #ifdef TRACE
              flexcount -= flex_size(&store);
              if (tl & (1u<<14)) Printf("**   flexcount: %d\n",flexcount);
            #endif

            flex_free(&store);

            image_abandon(b,image);

            erb.errnum = Utils_Error_Custom_Message;

            StrNCpy0(erb.errmess,
                     lookup_token("NoMemImg:There is not enough free memory for any new images (%0).",
                                  0,
                                  "2"));
            return &erb;
          }

          #ifdef TRACE

          else
          {
            flexcount += realsize;
            if (tl & (1u<<14)) Printf("**   flexcount: %d\n",flexcount);
          }

          #endif

          /* p points to the first byte after the data used by this image */

          p = image_data_offset(b, image) + idata[image].alloc;

          /* Move all data from p up to the end of the block (as was, hence */
          /* oldsize) along the block by size bytes, effectively creating   */
          /* a gap of that many bytes just after this image's data. If p    */
          /* = oldsize, this was the last image in the block and there's    */
          /* no data above it to move (so don't do the memmove).            */

          if (p != oldsize) memmove(ddata + p + realsize, ddata + p, oldsize - p);

          /* Copy the new data into that gap */

          memcpy(ddata + p, ((char *) store) + idata[image].isize, realsize);

          /* Update the image's image data and total data pointers */

          idata[image].alloc += realsize;
          idata[image].isize += realsize;
        }

        /* Act according to the fetch status */

        switch (waiting)
        {
          case 1: break; /* Nothing happened yet, or the HTTP response header is still coming in */
          case 2: break; /* Redirected, we can ignore and wait for the fetcher to cope */
          case 0: /* Not awaiting delivery (shouldn't happen in this context, but here 'just in case') */
          case 3: /* We have some data */
          {
            int i, n, n1;

            /* Flag that a fetch has started to happen */

            idata[image].started = 1;

            /* Set 'n' to the size of data that was fetched */

            if (store) n = flex_size(&store);
            else       n = 0;

            /* Increment the 'bytes so far' counter for the image */

            idata[image].bytesgot += n;

            /* Push the data into the library, in chunks defined by */
            /* the size of 'buffer' (allocated as a char[] above).  */

            i = 0;

            while (n)
            {
              n1 = n;

              if (n1 > sizeof(buffer)) n1 = sizeof(buffer);
              memcpy(buffer, (char *) store + i, n1);

              i += n1;
              n -= n1;

              e = image_load_chunk(b, image, buffer, n1);

              if (e)
              {
                /* If there's an error, free the store and ditch the image */

                if (store)
                {
                  #ifdef TRACE
                    flexcount -= flex_size(&store);
                    if (tl & (1u<<14)) Printf("**   flexcount: %d\n",flexcount);
                  #endif

                  flex_free(&store);
                }

                image_abandon(b, image);

                #ifdef TRACE
                  if (tl & (1u<<15)) Printf("image_process_null: Exitting with error\n");
                #endif

                return e;
              }
            }

            /* Get rid of any accumulated data and update the status bar */

            image_remove_data(b, image);
            toolbars_update_progress(b);
          }
          break;
        }

        /* Free the temporary store, if it was used */

        if (store)
        {
          #ifdef TRACE
            flexcount -= flex_size(&store);
            if (tl & (1u<<14)) Printf("**   flexcount: %d\n",flexcount);
          #endif

          flex_free(&store);
        }

        /* Closure of long 'if' that deals with either continuing  */
        /* to fetch data for an image, or getting the first chunk  */
        /* of data for a fetch. (The code immediately above is for */
        /* getting the first chunk).                               */
      }

      /* Closure of long 'if' that sees if a fetch has started */
      /* for an image. (The code immediately above excutes if  */
      /* it has, else a fetch is started).                     */
    }

    /* Closure of long 'if' that checks to see if there are */
    /* images to be fetched, and that the fetch status is   */
    /* not BS_DATAFETCH (i.e. other fetch activity has      */
    /* ceased).                                             */
  }

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_process_null: Successful\n");
  #endif

  return NULL;
}

/*************************************************/
/* image_delete_image_entry()                    */
/*                                               */
/* Deletes an image_info structure from the      */
/* array of structures. The cross reference      */
/* image numbers of subsequent images are        */
/* adjusted appropriately.                       */
/*                                               */
/* The idata flex block is not shrunk; this is   */
/* expected to be done externally, to avoid      */
/* possibly freeing many small blocks            */
/* consecutively (which can be slow with flex).  */
/*                                               */
/* Parameters: Image number to remove.           */
/*************************************************/

static void image_delete_image_entry(int image)
{
  browser_data * b;
  int            i;

  nimages--;

  if (lastimage == image) lastimage--;
  if (lastimage >= nimages) lastimage = nimages - 1;

  /* Only need to alter xref numbers or move flex data */
  /* around if this wasn't the last image in the block */

  if (image < nimages) /* (Not 'nimages - 1' as nimages has been decremented by this point) */
  {
    memmove(&idata[image], &idata[image + 1], sizeof(image_info) * (nimages - image));

    for (i = image; i < nimages; i++)
    {
      #ifdef TRACE
        if (idata[i].xref == image) Printf("JUST DELETED IMAGE %d WHICH WAS XREF'D AND SHOULDN'T HAVE BEEN\n");
      #endif

      if (idata[i].xref > image) idata[i].xref--;
    }
  }

  /* Need to also alter browser_data structures' background image */
  /* number fields.                                               */

  b = last_browser;

  while (b)
  {
    if (b->backimage > image) b->backimage--;

    b = b->previous;
  }
}

/*************************************************/
/* image_discard()                               */
/*                                               */
/* Discards all images for a given browser       */
/* window, freeing the memory and shutting down  */
/* any active fetches.                           */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the images.           */
/*************************************************/

_kernel_oserror * image_discard(browser_data * b)
{
  int newsize, i;

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("\nimage_discard: Called for %p\n\n", b);
  #endif

  if (!nimages) return NULL;

  /* Discard all the images in this view, free the memory */
  /* and close the URL handles if still open.             */

  i = nimages - 1;

  /* First, get rid of any images owned by this browser which cross */
  /* reference others that point to the actual image data.          */

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_discard: Have %d images before first pass.\n\n",nimages);
  #endif

  _swix(Hourglass_Start, _IN(0), 1);

  while (i >= 0)
  {
    #ifdef TRACE
      if (tl & (1u<<15)) Printf("image_discard: First pass, check image %d\n", i);
    #endif

    _swix(Hourglass_Percentage, _IN(0), (100 * (nimages - i)) / nimages);

    if (idata[i].xref >= 0 && idata[i].owner == b)
    {
      #ifdef TRACE
        if (tl & (1u<<15)) Printf("image_discard: Deleting entry owned by %p that cross references image %d\n", b, idata[i].xref);
      #endif

      image_remove_all_data(b, i);
      image_delete_image_entry(i);
    }

    i--;
  }

  #ifdef TRACE
    if (tl & (1u<<15))
    {
      Printf("image_discard: First pass complete.\n\n");
      Printf("image_discard: Have %d images before second pass.\n\n",nimages);
    }
  #endif

  /* Now check all images that point to data and see if they're */
  /* cross referenced by anything. If so, they can't be deleted */
  /* yet. If not, and they're owned by the browser that is      */
  /* discarding images, they can all go. If they're not owned   */
  /* by the browser, check if they're owned by any other        */
  /* current browser; if not, then they can again be discarded. */

  i = nimages - 1;

  while (i >= 0)
  {
    #ifdef TRACE
      if (tl & (1u<<15)) Printf("image_discard: Second pass, check image %d\n", i);
    #endif

    _swix(Hourglass_Percentage, _IN(0), (100 * (nimages - i)) / nimages);

    /* Proceed if the image does not cross reference anything itself. */

    if (idata[i].xref < 0)
    {
      int i2, xref = -1;

      #ifdef TRACE
        if (tl & (1u<<15)) Printf("image_discard: This is a non-xref image\n");
      #endif

      /* Scan all images, looking for at least one reference to the current */
      /* image. Remember that image's number in xref, if there is one.      */

      for (i2 = 0; i2 < nimages; i2++)
      {
        if (idata[i2].xref == i)
        {
          xref = i2;
          break;
        }
      }

      #ifdef TRACE
        if (tl & (1u<<15))
        {
          if (xref >= 0) Printf("image_discard: %d cross references this\n",xref);
          else           Printf("image_discard: Nothing cross references this\n");
        }
      #endif

      if (xref < 0)
      {
        int deleteit = 0;

        if (idata[i].owner == b)
        {
          /* Delete the current image, as nothing cross references it and */
          /* it is owned by the browser that is discarding images.        */

          #ifdef TRACE
            if (tl & (1u<<15)) Printf("image_discard: The current browser %p owns this, so it can be deleted.\n",b);
          #endif

          deleteit = 1;
        }
        else
        {
          browser_data * check;
          int            found = 0;

          /* Nothing cross references the image, but it isn't owned by this  */
          /* browser. So we can't delete it, as it may be used by another    */
          /* window... Unless, of course, there is no browser_data structure */
          /* in the list of browsers that claims to own it. In that case,    */
          /* all windows using the image *must* be gone by now.              */

          #ifdef TRACE
            if (tl & (1u<<15)) Printf("image_discard: The current browser %p does not own this...\n", b);
          #endif

          check = last_browser;

          while (check)
          {
            if (idata[i].owner == check)
            {
              found = 1;
              break;
            }

            check = check->previous;
          }

          /* If found is zero, no current browser owns the image, so it can go */

          if (!found)
          {
            deleteit = 1;

            #ifdef TRACE
              if (tl & (1u<<15)) Printf("image_discard: ...and no current browser does either, so it can be deleted.\n");
            #endif
          }
          else
          {
            #ifdef TRACE
              if (tl & (1u<<15)) Printf("image_discard: ...but another browser does, so it must not be deleted.\n");
            #endif
          }
        }

        if (deleteit)
        {
          #ifdef TRACE
            if (tl & (1u<<15)) Printf("image_discard: Deleting entry %d\n", i);
          #endif

          /* If the image was being fetched, close that session */

          if (idata[i].handle)
          {
            html_close(idata[i].handle);
            idata[i].handle = 0;
          }

          if (idata[i].istore)
          {
            #ifdef STRICT_PARSER

              /* Report any errors as Continue-only in strict parser mode */

              _kernel_oserror * e;

              e = idata[i].istore -> Delete(idata[i].istore);

              if (e)
              {
                e->errnum = Utils_Error_Custom_Message;
                show_error_ret(e);
              }

            #else

              /* If not in Strict mode, ignore any errors */

              idata[i].istore -> Delete(idata[i].istore);

            #endif
          }

          image_remove_all_data(idata[i].owner, i);
          image_delete_image_entry(i);
        }
      }
    }

    i--;
  }

  #ifdef TRACE
    if (tl & (1u<<15))
    {
      Printf("image_discard: Second pass complete.\n\n");
      Printf("image_discard: Have %d images left.\n\n", nimages);
    }
  #endif

  /* If there is an animation handler present, but no animated */
  /* images remain, remove that handler.                       */

  if (animhandler)
  {
    int found = 0;

    for (i = 0; i < nimages; i++)
    {
      if (idata[i].istore && idata[i].istore->animated)
      {
        found = 1;
        break;
      }
    }

    if (!found)
    {
      deregister_null_claimant(Wimp_ENull, (WimpEventHandler *) image_animate_images, NULL);
      animhandler = 0;
    }
  }

  /* Ensure the image_info data block matches the size of the images now present */

  newsize = nimages * sizeof(image_info);

  #ifdef TRACE
    {
      int oldsize;

      oldsize    = flex_size((flex_ptr) &idata);
      flexcount -= (oldsize - newsize);

      if (tl & (1u<<14)) Printf("**   flexcount: %d\n",flexcount);
    }
  #endif

  if (idata)
  {
    if (newsize) flex_extend((flex_ptr) &idata, newsize);
    else         flex_free((flex_ptr) &idata);
  }

  _swix(Hourglass_Off, 0);

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_discard: Successful\n\n");
  #endif

  return NULL;
}

/*************************************************/
/* image_reload()                                */
/*                                               */
/* Reloads an image - well, in fact it just      */
/* redraws an existing image at present or will  */
/* start a fetch if the image was delayed.       */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the images;           */
/*                                               */
/*             Address of the token representing */
/*             the image.                        */
/*************************************************/

void image_reload(browser_data * b, HStream * token)
{
  if (nimages)
  {
    int i, image;

    /* First, start a fetch on the base image if necessary, */
    /* otherwise just set the priority flag and redraw it.  */

    image = image_get_token_image_xref(b, token);

    if (image < 0) return;

    if (
         (
           !idata[image].fetched &&
           idata[image].delayed
         )
         ||
         (
           idata[image].fetched  &&
           !idata[image].success &&
           !idata[image].istore
         )
       )
    {
      idata[image].success  = 0;
      idata[image].delayed  = 0;
      idata[image].fetched  = 0;
      idata[image].priority = 1;

      if (!b->fetch_handler) fetchpage_claim_nulls(b);
      toolbars_update_status(b, Toolbars_Status_GetPics);
    }
    else
    {
      idata[image].priority = 1;
      image_update_image(b, image, NULL);
    }

    /* Now set the priority flag on all images that cross */
    /* reference the base image.                          */

    for (i = 0; i < nimages; i++)
    {
      if (idata[i].xref == image)
      {
        idata[i].priority = 1;
        image_update_image(b, i, NULL);
      }
    }
  }
}

/*************************************************/
/* image_delay_fetches()                         */
/*                                               */
/* Suspends image loading temporarily.           */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the images.           */
/*************************************************/

void image_delay_fetches(browser_data * b)
{
  if (nimages)
  {
    int image;

    for (image = 0; image < nimages; image++)
    {
      if (!idata[image].handle) image_delay(b, image);
    }
  }
}

/*************************************************/
/* image_restart_fetches()                       */
/*                                               */
/* If image loading has been suspended for any   */
/* reason, it can be resumed by calling this     */
/* function.                                     */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the images;           */
/*                                               */
/*             1 to restart foreground fetches,  */
/*             else 0;                           */
/*                                               */
/*             1 to restart background fetches,  */
/*             else 0.                           */
/*************************************************/

void image_restart_fetches(browser_data * b, int foreground, int background)
{
  if (nimages)
  {
    int            image;
    int            xref;
    browser_data * xref_b;

    /* Loop round all the images */

    for (image = 0; image < nimages; image++)
    {
      /* If an image has not been fetched and is flagged as being */
      /* delayed, or an image has been fetched but has not been   */
      /* flagged as succesfully fetched and has not been dealt    */
      /* with by the image library (the istore field is NULL),    */
      /* then clear all the fetch flags for the image so that it  */
      /* will later be fetched.                                   */

      if (
           idata[image].owner == b &&
           (
             (
               !idata[image].fetched &&
               idata[image].delayed
             )
             ||
             (
               idata[image].fetched  &&
               !idata[image].success &&
               !idata[image].istore
             )
           )
         )
      {
        /* Only fetch foreground or background images if asked to */

        if (
             (idata[image].background  && background) ||
             (!idata[image].background && foreground)
           )
        {
          idata[image].success = 0;
          idata[image].delayed = 0;
          idata[image].fetched = 0;

          /* Deal with cross referenced images. If this browser cross */
          /* references another image, and that image isn't owned by  */
          /* this browser, then clear its fetching flags also and     */
          /* make sure that this other browser will fetch the data.   */

          xref = idata[image].xref;

          if (xref >= 0 && idata[xref].owner != b)
          {
            idata[xref].success = 0;
            idata[xref].delayed = 0;
            idata[xref].fetched = 0;

            xref_b = idata[xref].owner;

            if (!xref_b->fetch_handler) fetchpage_claim_nulls(xref_b);
            toolbars_update_status(xref_b, Toolbars_Status_GetPics);
          }
        }
      }
    }

    /* Finally, make sure fetching is started for the given browser */

    if (!b->fetch_handler) fetchpage_claim_nulls(b);
    toolbars_update_status(b, Toolbars_Status_GetPics);
  }
}

/*************************************************/
/* image_update_area()                           */
/*                                               */
/* Updates (redraws) part of an image, which     */
/* will reflect any new data fetched for it if   */
/* the whole image hasn't been fetched yet.      */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the image;            */
/*                                               */
/*             The left hand X window coordinate */
/*             in OS units for the image;        */
/*                                               */
/*             The bottom Y window coordinate in */
/*             OS units for the image;           */
/*                                               */
/*             A BBox defining a rectangle       */
/*             relative to X and Y which is the  */
/*             area to update;                   */
/*                                               */
/*             The image number, from 0 to       */
/*             nimages - 1;                      */
/*                                               */
/*             1 to force a redraw (i.e. clear   */
/*             any previously drawn data first), */
/*             zero to not clear the region.     */
/*************************************************/

static _kernel_oserror * image_update_area(browser_data * b, int x, int y, BBox * ubox,
                                           int image, int redraw)
{
  _kernel_oserror       * e;
  WimpRedrawWindowBlock   r;
  int                     more, sx, sy;
  int                     xref;

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_update_area: Called for image %d\n",image);
  #endif

  /* If the image cannot be redrawn yet, don't redraw it... */

  if (!idata[image].canredraw)
  {
    #ifdef TRACE
      if (tl & (1u<<15)) Printf("image_update_area: Image %d can't be redrawn yet (canredraw = 0)\n",image);
    #endif

    return NULL;
  }

  r.window_handle = b->window_handle;

  r.visible_area  = *ubox;

  r.visible_area.xmin += x;
  r.visible_area.ymin += y;
  r.visible_area.xmax += x;
  r.visible_area.ymax += (y + 4);

  /* Ensure that 'xref' holds the number of the given image, which may well */
  /* cross reference another, 'image', that has the actual render data.     */

  xref = image;
  if (idata[image].xref >= 0) image = idata[image].xref;

  /* If the image may acquire a mask during rendering, even if it is not    */
  /* initially transparent, need to force the area to redraw to clear first */

  if (
       idata[image].istore->mutable_mask ||
       redraw
     )
     wimp_force_redraw(r.window_handle,
                       r.visible_area.xmin,
                       r.visible_area.ymin,
                       r.visible_area.xmax,
                       r.visible_area.ymax);
  else
  {
    /* Otherwise, do a less flickery non-clearing update */

    wimp_update_window(&r, &more);

    sx = coords_x_toscreen(x, &r);
    sy = coords_y_toscreen(y, &r);

    while(more)
    {
      if (
           b->displayimages                       ||
           b->displayed == Display_External_Image ||
           idata[xref].priority
         )
      {
        browser_redrawing = b;
        image_redrawing   = xref;

        e = idata[image].istore -> Render(idata[image].istore,
                                          sx,
                                          sy,
                                          100,
                                          idata[xref].currw,
                                          idata[xref].currh);

        browser_redrawing = NULL;
        image_redrawing   = -1;

        #ifdef STRICT_PARSER

          if (e)
          {
            e->errnum = Utils_Error_Custom_Message;
            show_error_ret(e);
          }

        #endif
      }

      #ifdef ANTI_TWITTER

        if (!printing) anti_twitter(&r);

      #endif

      wimp_get_rectangle(&r,&more);
    }
  }

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_update_area: Successful\n");
  #endif

  return NULL;
}

/*************************************************/
/* image_update_image()                          */
/*                                               */
/* Update the specified image number on screen,  */
/* including all which cross reference it. If    */
/* the image has changed size on screen then     */
/* start a reformat from the first occurrence    */
/* (i.e. smallest token number), otherwise just  */
/* do an update on the relevant rectangle for    */
/* each occurrence.                              */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the images;           */
/*                                               */
/*             The image number, from 0 to       */
/*             nimages - 1;                      */
/*                                               */
/*             Pointer (which may be NULL) to a  */
/*             BBox into which the image's size  */
/*             will be returned.                 */
/*************************************************/

static _kernel_oserror * image_update_image(browser_data * b, int image, BBox * box)
{
  /* Update the specified image number on the screen, including all       */
  /* which xref to it. If the image has changed size on screen then start */
  /* a reformat from the first occurrence (ie the smallest token number), */
  /* otherwise just do an update of the relevant rectangle for each       */
  /* occurrence.                                                          */

  int  i, actual;
  BBox whole, partial;

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_update_image: Called for image %d\n",image);
  #endif

  /* If a BBox is passed in here, then the image will be redrawn in that */
  /* specific region. Otherwise, the region is derived from the image's  */
  /* size. The BBox 'whole' is used for the latter case, so by setting   */
  /* 'box' to the address of this if a BBox was *not* given, we can then */
  /* transparently use this elsewhere and update either the requested    */
  /* area or the whole image without needing special case code.          */
  /*                                                                     */
  /* If the image's 'hadfirst' flag is clear, it hasn't been through     */
  /* this function yet. In that case, want to ensure that the whole      */
  /* image is redrawn to clear any placeholder that will be plotted in   */
  /* its place; otherwise, only the part of the placeholder that         */
  /* corresponds to the requested redraw box will be cleared.            */

  if (!box || !idata[image].hadfirst) box = &whole;

  /* Now ensure that 'image' is the image with the render information, as */
  /* the rest of the code has to deal with all xref images to this one    */
  /* anyway.                                                              */
  /*                                                                      */
  /* So 'actual' is now the image number given to image_update_image, and */
  /* 'image' is the number of the image with associated render data; this */
  /* may be the same as 'actual' or may be equal to the contents of its   */
  /* 'xref' field.                                                        */

  actual = image;
  if (idata[image].xref >= 0) image = idata[image].xref;

  /* If there appears to be no data for the image, or it is less than */
  /* one pixel wide (i.e. not displayable for whatever reason), exit. */

  if (!idata[image].istore || (idata[image].istore && idata[image].istore->width < 1)) return NULL;

  /* Check if the image has become animated */

  if (idata[image].istore->animated)
  {
    /* If the image is animated and we haven't got a null handler */
    /* for animations, install one now.                           */

    if (!animhandler)
    {
      register_null_claimant(Wimp_ENull, (WimpEventHandler *) image_animate_images, NULL);
      animhandler = 1;
    }

    #ifdef STRICT_PARSER

      {
        _kernel_oserror * e;

        /* Report any errors as Continue-only in strict parser mode */

        e = idata[image].istore -> RegisterFiller(idata[image].istore,
                                                  (FillerFunction *) image_fill_background,
                                                  b,
                                                  (int *) image);

        if (e)
        {
          e->errnum = Utils_Error_Custom_Message;
          show_error_ret(e);
        }
      }

    #else

      /* If not in Strict mode, ignore any errors */

      idata[image].istore -> RegisterFiller(idata[image].istore,
                                            (FillerFunction *) image_fill_background,
                                            b,
                                            (int *) image);
    #endif
  }

  /* When the image library gets some data for an image it will fill */
  /* in the width_os and height_os fields of the istore structure    */
  /* that 'image' points to. If the HTML source didn't specify a     */
  /* size for this or cross referencing images, fill in currw /      */
  /* currh with the new values.                                      */

  if (
       !idata[image].hadfirst              &&
       idata[image].istore->width_os  >= 0 &&
       idata[image].istore->height_os >= 0
     )
  {
    for (i = 0; i < nimages; i++)
    {
      if (
           (
             idata[i].xref == image ||
             i == image
           )
           && !idata[i].fixedsize
         )
      {
        idata[i].currw = idata[image].istore->width_os;
        idata[i].currh = idata[image].istore->height_os;
      }
    }
  }

  /* If the image is for the background, just refresh the window contents */
  /* with browser_update_window (remembering to update those windows that */
  /* cross reference the same background image, too).                     */

  {
    int            isbackground = 0;
    browser_data * check        = last_browser;

    /* First check if the image is the background of this browser. If */
    /* not then check all browsers.                                   */

    if (actual == b->backimage || image == b->backimage) isbackground = 1;
    else
    {
      while (check && !isbackground)
      {
        if (actual == check->backimage || image == check->backimage) isbackground = 1;

        check = check->previous;
      }
    }

    /* If this is a background, update all instances of the image */
    /* and exit the update routine here.                          */

    if (isbackground)
    {
      for (i = 0; i < nimages; i++)
      {
        if ((i == image || idata[i].xref == image) && idata[image].fetched)
        {
          browser_update_bottom(idata[i].owner, NULL);
        }
      }

      return NULL;
    }
  }

  /* Look through all images for cross references */

  whole.xmin = whole.ymin = 0;

  for (i = 0; i < nimages; i++)
  {
    /* If we're on the current image, or on one which  */
    /* references it, then proceed.                    */

    if (i == image || idata[i].xref == image)
    {
      int          l, x, y;
      int          chunk, o, depth, noplot = 0;
      token_path * path = NULL;
      HStream    * tp;
      BBox         ibox;

      /* Set the 'whole' BBox to the size of the base image in OS units */

      whole.xmax = idata[i].currw;
      whole.ymax = idata[i].currh;

      /* Ensure the redraw box returned by the image Load call */
      /* (or the BBox 'whole' from above) is scaled to fit the */
      /* actual image's dimensions.                            */
      /*                                                       */
      /* We don't scale if box = &whole, as in that case the   */
      /* bbox is already scaled, or if for some reason the     */
      /* width or height fields in the ImageLib image struct   */
      /* seem to be invalid.                                   */

      if (
           box != &whole                      &&
           idata[image].istore->width_os  > 0 &&
           idata[image].istore->height_os > 0
         )
      {
        partial.xmin = (box->xmin * idata[i].currw) / idata[image].istore->width_os;
        partial.ymin = (box->ymin * idata[i].currh) / idata[image].istore->height_os;
        partial.xmax = (box->xmax * idata[i].currw) / idata[image].istore->width_os;
        partial.ymax = (box->ymax * idata[i].currh) / idata[image].istore->height_os;
      }
      else partial = *box;

      /* If the image doesn't have position information, can't plot it */

      if (idata[i].x == -1 && idata[i].y == -1) noplot = 1;

      /* Only continue if the line containing a chunk */
      /* relating to the token which relates to this  */
      /* image (!) can be found.                      */

      depth = tokenutils_line_range(idata[i].owner, idata[i].token, &l, &chunk, NULL, NULL, &path);
      if (l < 0) noplot = 1;
      else
      {
        /* If a line was found and depth is non-zero, the line was inside */
        /* a table - want to find the parent line of the table, so that   */
        /* if a reformat is needed it will resize the table as required.  */

        if (depth) l = path[depth - 1].line;
        if (l < 0) noplot = 1;
      }
      if (path) free(path);

      /* May not be redrawing the image, but might still want to */
      /* reformat the page here if the size has changed.         */

      if (noplot)
      {
        if (!idata[i].hadfirst && !idata[i].fixedsize)
        {
          idata[i].hadfirst = 1;
          reformat_format_from(idata[i].owner, l - 1, 0, i);
        }
      }
      else
      {
        x = idata[i].x;
        y = idata[i].y;

        /* Get the address of the token representing the image */

        tp = idata[i].token;

        if (!reformat_get_image_size(idata[i].owner, tp, &ibox))
        {
          /* If the image size was obtained without error, */
          /* correct its X and Y coordinates appropriately */

          o = 0;
          if (ISLINK(tp) && (tp->style & IMG)) o = tp->maxlen * 2;
          x += o;
          y += ibox.ymin + o;

          if (!idata[i].hadfirst)
          {
            idata[i].hadfirst = 1;

            /* If the image size was specified in the HTML, just do a redraw  */
            /* (which clears the background first, see the very large comment */
            /* earlier on). Otherwise, the image size has changed so do a     */
            /* reformat here.                                                 */

            if (idata[i].fixedsize) image_update_area(idata[i].owner,
                                                      x,
                                                      y,
                                                      &partial,
                                                      i,
                                                      1);

            else reformat_format_from(idata[i].owner, l - 1, 0, i);

  // Haven't solved the cross referencing problem yet. What seems to happen
  // is that the function gets called when the actual image comes in, then
  // gets called separately with the cross references set up. So by that time,
  // the fact that the cross referenced images weren't sized either is not
  // known to the function and unless you've forced a reformat first time
  // round it'll go pear shaped later.

  //          {
  //            int width, old_h, old_y, old_b, top, bot;
  //
  //            /* Optimisation and complication... For images which lie alone on */
  //            /* a single line, don't have to reformat. Can just shift the page */
  //            /* below down, and redraw the line with the image in.             */
  //            /*                                                                */
  //            /* Later it should be possible to carry out a reformat between    */
  //            /* the image and the next line break point, but for now this at   */
  //            /* least gets rid of a few reformats.                             */
  //            /*                                                                */
  //            /* This is complicated somewhat by cross referencing. That is, if */
  //            /* several image tags refer to the same piece of data, normally   */
  //            /* the reformatter is called and so resizes them all in passing   */
  //            /* (see comments on the reformatter call below). This won't       */
  //            /* happen in the optimised case, so must to a scan right down the */
  //            /* line list until either all occurrences have been found or the  */
  //            /* reformatter gets called for some reason.                       */
  //
  //            if (b->ldata[l].n == 1)
  //            {
  //              Printf("Optimised page yshift for new, previously unsized image on line %d\n",l);
  //
  //              width  = ibox.xmax - ibox.xmin;
  //              convert_to_points(width, &width);
  //              b->cdata[chunk].w = width;
  //
  //              top = ibox.ymax;
  //              bot = ibox.ymin;
  //
  //              if (top & 3) top += 4 - (top & 3);
  //              if (bot & 3) bot += 4 - (bot & 3);
  //
  //              old_h = b->ldata[l].h;
  //              old_y = b->ldata[l].y;
  //              old_b = b->ldata[l].b;
  //
  //              /* lp->h - lp->b equates to the height of the line in OS units minus */
  //              /* the y offset of the font baseline from the bottom of the line. If */
  //              /* 'top' is greater than this the line needs to grow vertically.     */
  //
  //              if (top > (b->ldata[l].h - b->ldata[l].b))
  //              {
  //                int d;
  //
  //                d = top - (b->ldata[l].h - b->ldata[l].b);
  //                b->ldata[l].h += d;
  //                b->ldata[l].y -= d;
  //              }
  //
  //              /* Similarly, if bot is greater than the offset of the baseline from */
  //              /* the bottom of the line, account for the extra offset.             */
  //
  //              if (bot > b->ldata[l].b)
  //              {
  //                int d;
  //
  //                d = bot - b->ldata[l].b;
  //
  //                b->ldata[l].h += d;
  //                b->ldata[l].b += d; /* Was lp->b = bot; */
  //                b->ldata[l].y -= d;
  //              }
  //
  //              if (
  //                   b->ldata[l].h != old_h ||
  //                   b->ldata[l].y != old_y ||
  //                   b->ldata[l].b != old_b
  //                 )
  //              {
  //                int move;
  //
  //                move = b->ldata[l].h - old_h;
  //
  //                if (move)
  //                {
  //                  e = reformat_shift_vertically(b, l + 1, b->nlines + 1, -move);
  //                  if (e) return e;
  //                }
  //              }
  //
  //              /* If using complex backdrops, want to keep them in sync, so need */
  //              /* to refresh the whole area below the line just altered. Else,   */
  //              /* just redraw the line.                                          */
  //
  //              if (b->backimage != -1)
  //              {
  //                /* Redraw everything below the line */
  //
  //                int top;
  //
  //                if (b->ldata[l].y + b->ldata[l].h > old_y + old_h) top = b->ldata[l].y + b->ldata[l].h;
  //                else                                               top = old_y + old_h;
  //
  //                browser_update_bottom(b, top);
  //              }
  //              else
  //              {
  //                /* Redraw just the line that's changed */
  //
  //                WimpGetWindowStateBlock state;
  //                int                     top, bottom;
  //
  //                /* Work out the top and bottom points to redraw from, depending */
  //                /* on how the line moved and/or resized due to the above code.  */
  //
  //                if (b->ldata[l].y < old_y) bottom = b->ldata[l].y;
  //                else                       bottom = old_y;
  //
  //                if (b->ldata[l].y + b->ldata[l].h > old_y + old_h) top = b->ldata[l].y + b->ldata[l].h;
  //                else                                               top = old_y + old_h;
  //
  //                state.window_handle = b->window_handle;
  //                e = wimp_get_window_state(&state);
  //                if (e) return e;
  //
  //                coords_box_toworkarea(&state.visible_area, (WimpRedrawWindowBlock *) &state);
  //
  //                return wimp_force_redraw(state.window_handle,
  //                                         state.visible_area.xmin,
  //                                         bottom,
  //                                         state.visible_area.xmax,
  //                                         top);
  //              }
  //
  //              /* Closure of long 'if' checking if there was just one chunk on this */
  //              /* line - if so don't need to call the reformatter, can just shuffle */
  //              /* lines below (being very careful over the redraw...). The code     */
  //              /* above executes in this case. Else, reformat from this point.      */
  //            }
  //            else
  //            {
  //              /* Where several image tags in the document source refer to the same */
  //              /* actual piece of image data, this reformat call will ensure that   */
  //              /* they are all correctly sized and positioned. This is because it   */
  //              /* will always be called on the first of any such images and thus    */
  //              /* must go through all subsequent occurrences in passing.            */
  //
  //              return (reformat_format_from(b, l - 1, 0, i));
  //            }
  //          }
          }

          /* Otherwise, work out what region to redraw (may only require */
          /* a partial area update)                                      */

          else if (partial.ymax < partial.ymin)
          {
            BBox ubox = partial;

            /* If the y max coordinate is less than the y min coordinate, */
            /* redraw from ymin to the top of the image, and from ymax to */
            /* the bottom. That way can miss out a middle section.        */

            ubox.ymin = partial.ymin;
            ubox.ymax = idata[i].currh;

            image_update_area(idata[i].owner,
                              x,
                              y,
                              &ubox,
                              i,
                              0);

            ubox.ymax = partial.ymax;
            ubox.ymin = 0;

            image_update_area(idata[i].owner,
                              x,
                              y,
                              &ubox,
                              i,
                              0);
          }
          else if (partial.xmax < partial.xmin)
          {
            BBox ubox = partial;

            /* Similarly, if xmax is less than xmin, draw from xmin to the */
            /* right hand side of the image and from xmax to the left, so  */
            /* a vertical strip in the middle may be untouched.            */

            ubox.xmin = partial.xmin;
            ubox.xmax = idata[i].currw;

            image_update_area(idata[i].owner,
                              x,
                              y,
                              &ubox,
                              i,
                              0);

            ubox.xmax = partial.xmax;
            ubox.xmin = 0;

            image_update_area(idata[i].owner,
                              x,
                              y,
                              &ubox,
                              i,
                              0);
          }
          else
          {
            /* Otherwise, redraw the requested box */

            image_update_area(idata[i].owner,
                              x,
                              y,
                              &partial,
                              i,
                              0);
          }

         /* Closure of 'if (!reformat_get_image_size(idata[i].owner, tp, &ibox))' */
        }

      /* Closure of 'if (noplot)' - code immediately above is */
      /* executed in the 'else' section.                      */
      }

    /* Closure of 'if (i == image || idata[i].xref == image)' */
    }

  /* Closure of loop running round all the images */
  }

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_update_image: Successful\n");
  #endif

  return NULL;
}

/*************************************************/
/* image_animate_images()                        */
/*                                               */
/* Checks all images to see if they need         */
/* updating. Designed to be called on null       */
/* events.                                       */
/*                                               */
/* Parameters are as standard for a Wimp event   */
/* handler.                                      */
/*************************************************/

int image_animate_images(int eventcode, WimpPollBlock * block, IdBlock * idb, browser_data * handle)
{
  int i, image, redraw;

  /* Scan round all images */

  for (i = 0; i < nimages; i++)
  {
    /* Find the image with associated render data */

    if (idata[i].xref >= 0) image = idata[i].xref;
    else                    image = i;

    /* Skip this image if it as already been dealt with */

    if (image >= i)
    {
      /* If this image does have such data, is flagged as */
      /* animated, and has a pointer to a NeedRedraw      */
      /* function, proceed.                               */

      if (
           idata[image].istore             &&
           idata[image].istore->animated   &&
           idata[image].istore->NeedRedraw
         )
      {
        BBox extent; // Not actually used yet...!

        #ifdef STRICT_PARSER

          /* Report any errors as Continue-only in strict parser mode */

          _kernel_oserror * e;

          e = idata[image].istore -> NeedRedraw(idata[image].istore, &redraw, &extent);

          if (e)
          {
            e->errnum = Utils_Error_Custom_Message;
            show_error_ret(e);
          }

        #else

          /* If not in Strict mode, ignore any errors */

          idata[image].istore -> NeedRedraw(idata[image].istore, &redraw, &extent);

        #endif

        /* Redraw the image, if required */

        if (redraw) image_update_image(idata[i].owner, i, NULL);
      }
    }
  }

  return 0;
}

/*************************************************/
/* image_fill_background()                       */
/*                                               */
/* Called as a FillerFunction for ImageLib,      */
/* this redraws the background image. Output     */
/* will probably have been redirected by         */
/* ImageLib at this point.                       */
/*************************************************/

_kernel_oserror * image_fill_background(void * handle, int * i)
{
  WimpGetWindowStateBlock   state;
  _kernel_oserror         * e      = NULL;
  browser_data            * b      = (browser_data *) handle;
  int                       ximage = (int) i;
  int                       noplot = 0;
  int                       image;
  int                       bimage;
  int                       x, y, xorigin, yorigin;
  int                       w, h;

  state.window_handle = b->window_handle;
  e = wimp_get_window_state(&state);
  if (e) return e;

  /* Should never happen, but you never know... */

  if (idata[ximage].xref >= 0) ximage = idata[ximage].xref;

  /* See the top of the file for details on browser_redrawing */
  /* and image_redrawing                                      */

  if (browser_redrawing) b = browser_redrawing;

  if (image_redrawing >= -1) image = image_redrawing;
  else                       image = ximage;

  /* Can't plot a background image if there's no such image or */
  /* it's not fully fetched.                                   */

  if (b->backimage >= 0)
  {
    bimage = b->backimage;
    if (idata[bimage].xref >= 0) bimage = idata[bimage].xref;
  }
  else bimage = -1, noplot = 1;

  if (
       !noplot &&
       (
         !idata[bimage].fetched ||
         !idata[bimage].istore
       )
     )
     noplot = 1;

  if (
       !noplot              &&
       idata[bimage].istore &&
       (
         idata[bimage].istore->width_os  <= 0 ||
         idata[bimage].istore->height_os <= 0
       )
     )
     noplot = 1;

  xorigin = -idata[image].x;
  yorigin = -idata[image].y;

  /* Ensure dithered background colours have a dithering */
  /* (ECF) pattern that starts in the right place        */

  e = _swix(OS_SetECFOrigin,
            _INR(0,1),

            -xorigin,
            -yorigin);

  if (e) return e;

  /* Do a blank rectangle if the image is transparent (has a mask) */
  /* or the image itself can't be plotted, or the browser window   */
  /* set up not to plot background images, or we're printing and   */
  /* shouldn't plot backgrounds (we must plot something at this    */
  /* stage!).                                                      */

  if (
       noplot                            ||
       idata[bimage].istore->transparent ||
       b->plainback                      ||
       (
         printing &&
         !printstyle_show_all()
       )
     )
  {
    redraw_set_colour(redraw_backcol(b));

    bbc_rectanglefill(0,
                      0,
                      idata[ximage].istore -> width_os,
                      idata[ximage].istore -> height_os);
  }

  /* If there's no background image that can be plotted, exit here */

  if (
       b->plainback ||
       noplot       ||
       (
         printing &&
         !printstyle_show_all()
       )
     )
     return NULL;

  /* Find the scale for the background plotting; if the foreground */
  /* image is scaled, then plotting the background at 1:1 will     */
  /* make it look scaled by the same amount, and things won't      */
  /* match up. We could either prescale the foreground image and   */
  /* then plot into that prescaled image, but this takes a lot     */
  /* of memory, or we could try to stick with a post-scaled image. */
  /* In that case, the background can either be ignored or we can  */
  /* try to scale it down so though it will look blocky and low    */
  /* resolution, it will at least roughly line up with the things  */
  /* around the foreground. This code does the last of the three.  */
  /*                                                               */
  /* Note the faster, simpler routine for scalings of 1:1 - avoids */
  /* floating point.                                               */

  if (
       idata[image].currw != idata[ximage].istore->width_os  ||
       idata[image].currh != idata[ximage].istore->height_os
     )
  {
    BBox  redraw;
    float scalex, scaley;
    float tempx,  tempy;
    float tempw,  temph;
    int   htop;

    tempx = (float) idata[ximage].istore->width_os, tempy = (float) idata[ximage].istore->height_os;
    tempw = (float) idata[image].currw,             temph = (float) idata[image].currh;

    scalex = tempx / tempw;
    scaley = tempy / temph;

    /* Round the background image's width and height down to pixel boundaries */

    tempw = idata[bimage].istore->width_os;
    temph = idata[bimage].istore->height_os;

    w = ((int) (tempw * scalex + (float) 0.5)) & ~(wimpt_dx() - 1);
    h = ((int) (temph * scaley + (float) 0.5)) & ~(wimpt_dy() - 1);

    /* Work out the coordinates over which to tile the image */

    tempx = - (float) (idata[image].x);

    if (!fixed.swapbars) htop = toolbars_button_height(b) + toolbars_url_height(b);
    else                 htop = toolbars_status_height(b);

    if (htop) htop += wimpt_dy();

    tempy = - (float) idata[image].y
            - (float) htop;

    xorigin = (int) (tempx * scalex + (float) 0.5) + 1;
    yorigin = (int) (tempy * scaley + (float) 0.5) + 1;

    /* Exit if the background size appears to be too small */

    if (w < 1 || h < 1) return NULL;

    redraw.xmin = xorigin;
    redraw.ymin = -idata[ximage].istore->height_os;
    redraw.xmax = idata[ximage].istore->width_os;
    redraw.ymax = yorigin;

    /* Render the image over the redraw region, with an unscaled plot */

    for (y = redraw.ymax; y >= redraw.ymin - h; y -= h)
    {
      for (x = redraw.xmin; x <= redraw.xmax + w; x += w)
      {
        e = (idata[bimage].istore -> Render(idata[bimage].istore,
                                           x,
                                           y,
                                           100,
                                           w,
                                           h));
        if (e) break;
      }

      if (e) break;
    }
  }
  else
  {
    BBox redraw;
    int  htop;

    w = (idata[bimage].istore->width_os)  & ~(wimpt_dx() - 1);
    h = (idata[bimage].istore->height_os) & ~(wimpt_dy() - 1);

    xorigin = -idata[image].x;

    if (!fixed.swapbars) htop = toolbars_button_height(b) + toolbars_url_height(b);
    else                 htop = toolbars_status_height(b);

    if (htop) htop += wimpt_dy();

    yorigin = -idata[image].y - htop;

    /* Exit if the background size appears to be too small */

    if (w < 1 || h < 1) return NULL;

    redraw.xmin = xorigin;
    redraw.ymin = -idata[ximage].istore->height_os;
    redraw.xmax = idata[ximage].istore->width_os;
    redraw.ymax = yorigin;

    /* Render the image over the redraw region, with an unscaled plot */

    for (y = redraw.ymax; y >= redraw.ymin - h; y -= h)
    {
      for (x = redraw.xmin; x <= redraw.xmax + w; x += w)
      {
        e = (idata[bimage].istore -> Render(idata[bimage].istore,
                                           x,
                                           y,
                                           100,
                                           -1,
                                           -1));
        if (e) break;
      }

      if (e) break;
    }
  }

  return e;
}

/*************************************************/
/* image_redraw()                                */
/*                                               */
/* Does a high level redraw of an image, with    */
/* an outline to show where the image should be  */
/* if it isn't all plotted, or a slabbed box to  */
/* display broken or unfetched images.           */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the image;            */
/*                                               */
/*             Pointer to a RedrawWindowBlock    */
/*             struct which holds information    */
/*             about the current redraw session; */
/*                                               */
/*             Address of the token representing */
/*             the image;                        */
/*                                               */
/*             The X offset in window coords (so */
/*             OS units) of the left hand edge   */
/*             of the image;                     */
/*                                               */
/*             The Y offset in window coords (so */
/*             OS units) of the bottom edge of   */
/*             the image.                        */
/*************************************************/

_kernel_oserror * image_redraw(browser_data * b, WimpRedrawWindowBlock * r, HStream * token, int x, int y)
{
  int image, actual;
  int plotted = 0;

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_redraw: Called with token %d\n", token);
  #endif

  /* Get the image number for the given token; 'actual' holds the actual */
  /* image for the given token, which may cross reference 'image', which */
  /* holds the actual render data.                                       */

  image = image_get_token_image_actual(b, token);

  if (image < 0) return NULL;

  actual = image;
  if (idata[actual].xref >= 0) image = idata[actual].xref;

  /* If the image has a fetched width and height, */
  /* and the browser choices specify that images  */
  /* should be shown (or the image itself has a   */
  /* high priority show bit set), display part or */
  /* all of the image.                            */

  if (
       image  >= 0 &&
       actual >= 0 &&
       (
         b->displayimages                       ||
         b->displayed == Display_External_Image ||
         idata[actual].priority
       )
       && idata[actual].canredraw
       && idata[image].istore             /* Must check istore here, as if width / height are <= 0, */
       && idata[image].istore->width  > 0 /* the image library has no data for the image and will   */
       && idata[image].istore->height > 0 /* not have filled in (e.g.) the Render field. So you'd   */
     )                                    /* get a rather nasty abort trying to call it!            */
  {
    _kernel_oserror * e;

    browser_redrawing = b;
    image_redrawing   = actual;

    e = idata[image].istore -> Render(idata[image].istore,
                                      x,
                                      y,
                                      100,
                                      idata[actual].currw,
                                      idata[actual].currh);

    browser_redrawing = NULL;
    image_redrawing   = -1;

    #ifdef STRICT_PARSER

      if (e)
      {
        e->errnum = Utils_Error_Custom_Message;
        show_error_ret(e);
      }

    #endif

    if (!e) plotted = 1;
  }

  if (!plotted)
  {
    /* If the image was not plotted, either due to error or because there's */
    /* no data yet, plot a placeholder and any present ALT text instead.    */

    BBox box;

    image_get_image_size(b, actual, &box);

    box.xmin += x;
    box.ymin += y;
    box.xmax += x - box.xmin;
    box.ymax += y - box.ymin;

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

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

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

      redraw_set_colour(Redraw_Colour_MidGrey);
      bbc_rectanglefill(box.xmin + 2,
                        box.ymax + box.ymin - 6,
                        box.xmax - 7,
                        3);
      bbc_rectanglefill(box.xmin + 2,
                        box.ymin + 4,
                        3,
                        box.ymax - 7);
    }
    else
    {
      if (box.xmax < 2) box.xmax = 2;
      if (box.ymax < 2) box.ymax = 2;

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

    /* Plot any ALT text that there might be, if we have a */
    /* rational image number.                              */

    if (image >= 0)
    {
      HStream * tp;

      tp = idata[actual].token;

      if (tp && tp->text && *tp->text)
      {
        /* Have got some ALT text. First find out the bounding box */
        /* needed to contain that text.                            */

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

        fbox.xmin = fbox.ymin = 0;

        /* Claim the font */

        size = fm_size(tp->fontsize);

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

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

        fm_get_string_width(h,
                            tp->text,
                            0x1000000,
                            strlen(tp->text),
                            -1,
                            &dummy,
                            &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 = (box.ymax - stringheight) / 2 - fbox.ymin;
        if (vcent <= 0) vcent = 10;
        vcent += box.ymin;

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

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

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

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

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

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

          if (ibox)
          {
            int colour;

            colour = redraw_token_colour(b, tp);

            fm_set_font_colour(h,
                               colour,
                               redraw_backcol(b));
            fm_puts(h,
                    xpos,
                    vcent,
                    tp->text,
                    1,
                    b->backimage >= 0 && !b->plainback);

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

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

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

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

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_redraw: Successful\n");
  #endif

  return NULL;
}

/*************************************************/
/* image_tile_window()                           */
/*                                               */
/* For a given redraw rectangle, tiles the       */
/* background image within that rectangle.       */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the window and image; */
/*                                               */
/*             Pointer to a WimpRedrawWindow-    */
/*             Block structure defining the area */
/*             over which to tile the image;     */
/*                                               */
/*             The X and Y coordinates to take   */
/*             as the tile origin, in OS units.  */
/*                                               */
/* Returns:    1 for success, 0 for failure.     */
/*************************************************/

int image_tile_window(browser_data * b, WimpRedrawWindowBlock * r, int xorigin, int yorigin)
{
  _kernel_oserror * e;
  int               x, y, w, h;
  int               xmin, ymax;
  int               image;

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_tile_window: Called, backimage = %d\n",b->backimage);
  #endif

  /* Can only tile if there's a background image defined and fully fetched */

  if (b->backimage < 0) return 0;

  image = b->backimage;
  if (idata[image].xref >= 0) image = idata[image].xref;

  if (!idata[image].fetched || !idata[image].istore) return 0;

  /* Round the width and height to pixel boundaries */

  w = idata[image].istore->width_os  & ~(wimpt_dx() - 1);
  h = idata[image].istore->height_os & ~(wimpt_dy() - 1);

  if (w < 1 || h < 1) return 0;

  /* Work out the coordinates over which to tile the image */

  xmin = coords_x_toworkarea(r->redraw_area.xmin,r);
  ymax = coords_y_toworkarea(r->redraw_area.ymax,r);

  xmin -= ((xmin - xorigin) % w);
  ymax -= ((ymax - yorigin) % h) + 1;

  xmin = coords_x_toscreen(xmin, r);
  ymax = coords_y_toscreen(ymax, r);

  /* Do a blank rectangle if the image is transparent (has a mask) */

  if (idata[image].istore->transparent)
  {
    redraw_set_colour(redraw_backcol(b));

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

  /* Render the image over the redraw region */

  for (y = ymax; y >= r->redraw_area.ymin - h; y -= h)
  {
    for (x = xmin; x <= r->redraw_area.xmax; x += w)
    {
      browser_redrawing = b;
      image_redrawing   = image;

      e = idata[image].istore -> Render(idata[image].istore,
                                        x,
                                        y,
                                        100,
                                        w,
                                        h);

      browser_redrawing = NULL;
      image_redrawing   = -1;

      #ifdef STRICT_PARSER

        if (e)
        {
          e->errnum = Utils_Error_Custom_Message;
          show_error_ret(e);

          return 0;
        }

      #else

        if (e) return 0;

      #endif
    }
  }

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_tile_window: Successful\n");
  #endif

  return 1;
}

/*************************************************/
/* image_mark_as_redrawable()                    */
/*                                               */
/* Flags that an image may now be redrawn (this  */
/* is generally called because an unsized image  */
/* has found its size, asked for a reformat, and */
/* that reformat is about to take place).        */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the image;            */
/*                                               */
/*             The image number.                 */
/*************************************************/

void image_mark_as_redrawable(browser_data * b, int image)
{
  /* Mark the image as redrawable */

  if (idata[image].owner == b) idata[image].canredraw = 1;

  return;
}

/*************************************************/
/* image_token_check_redrawable()                */
/*                                               */
/* Checks to see if an image has a known width   */
/* and height, and if so, marks it as            */
/* redrawable. Used for delayed reformatting.    */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the image;            */
/*                                               */
/*             Pointer to an HStream struct      */
/*             representing the image.           */
/*************************************************/

void image_token_check_redrawable(browser_data * b, HStream * token)
{
  int image;

  /* Get the image number */

  image = image_get_token_image_actual(b, token);

  if (image < 0 || image > nimages) return;

  /* Exit if the image has no known size yet */

  if (idata[image].currw <= 0 || idata[image].currh <= 0) return;

  /* Otherwise mark it as redrawable */

  image_mark_as_redrawable(b, image);

  return;
}

/*************************************************/
/* image_mode_change()                           */
/*                                               */
/* Ensure images are up to date following a mode */
/* change.                                       */
/*************************************************/

_kernel_oserror * image_mode_change(void)
{
  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_mode_change: Called\n");
  #endif

  if (nimages)
  {
    int i;

    /* For each image, call the image library's mode change handling function */

    for (i = 0; i < nimages; i++)
    {
      if (idata[i].istore && idata[i].istore->width > 0)
      {
        #ifdef STRICT_PARSER

          /* Report any errors as Continue-only in strict parser mode */

          _kernel_oserror * e;

          e = idata[i].istore -> ModeChange(idata[i].istore);

          #ifdef TRACE
            if (tl & (1u<<15)) Printf("image_mode_change: Exitting with error\n");
          #endif

          if (e) return e;

        #else

          /* If not in Strict mode, ignore any errors */

          idata[i].istore -> ModeChange(idata[i].istore);

        #endif
      }
    }
  }

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_mode_change: Successful\n");
  #endif

  return NULL;
}

/*************************************************/
/* image_data_offset()                           */
/*                                               */
/* Returns the offset into the ddata block of    */
/* the data associated with a given image.       */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the image in          */
/*             question;                         */
/*                                               */
/*             An image number, from 0 to        */
/*             nimages - 1.                      */
/*                                               */
/* Returns:    Pointer to the data associated    */
/*             with that image (as a char *).    */
/*************************************************/

static int image_data_offset(browser_data * b, int image)
{
  int i, count = 0;

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_data_offset: Called for image %d, and exitting\n",image);
  #endif

  if (image <= 0) return 0; /* Image 0's data offset will be zero... */

  for (i = 0; i < image; count += idata[i].alloc, i++);

  return count;
}

/*************************************************/
/* image_count_fetches()                         */
/*                                               */
/* Counts how many fetches are being performed   */
/* for a given web page.                         */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the page and images   */
/*             being fetched.                    */
/*                                               */
/* Returns:    The number of image fetches that  */
/*             are currently in progress (this   */
/*             may be zero, of course).          */
/*************************************************/

static int image_count_fetches(browser_data * b)
{
  int i, c = 0;

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

  /* there is a fetch handle in their image_info structs. */

  for (i = 0; i < nimages; i++) if (idata[i].handle && idata[i].owner == b) c++;

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_count_fetches: Exitting with %d\n",c);
  #endif

  return c;
}

/*************************************************/
/* image_fetching()                              */
/*                                               */
/* Returns 1 if there are any image fetches      */
/* going on at all, else 0 (faster than using    */
/* image_count_fetches above if you don't care   */
/* how many fetches there are).                  */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the inquiry.          */
/*                                               */
/* Returns:    1 if there are image fetches in   */
/*             progress, else 0.                 */
/*************************************************/

int image_fetching(browser_data * b)
{
  int i;

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

  for (i = 0; i < nimages; i++)
  {
    if (idata[i].handle && idata[i].owner == b)
    {
      #ifdef TRACE
        if (tl & (1u<<15)) Printf("image_fetching: Exitting, there is at least 1 fetch in progress\n");
      #endif

      return 1;
    }
  }

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_fetching: Exitting, no fetches in progress\n");
  #endif

  return 0;
}

/*************************************************/
/* image_fetched()                               */
/*                                               */
/* Determines if an image has been completely    */
/* fetched or not.                               */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the image;            */
/*                                               */
/*             The image number, from 0 to       */
/*             nimages - 1.                      */
/*                                               */
/* Returns:    1 if the image has been fetched   */
/*             completely, else 0.               */
/*************************************************/

int image_fetched(browser_data * b, int image)
{
  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_fetched: Called for image %d\n",image);
  #endif

  if (image >= 0) return (idata[image].fetched);

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_fetched: Exitting with 0 (failed)\n");
  #endif

  return 0;
}

/*************************************************/
/* image_token_fetched()                         */
/*                                               */
/* Determines if an image has been completely    */
/* fetched or not, based on a token representing */
/* the image.                                    */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the image;            */
/*                                               */
/*             Pointer to a token representing   */
/*             the image.                        */
/*                                               */
/* Returns:    1 if the image has been fetched   */
/*             completely, else 0.               */
/*************************************************/

int image_token_fetched(browser_data * b, HStream * token)
{
  int image;

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_token_fetched: Called for token %p\n",token);
  #endif

  image = image_get_token_image_xref(b, token);

  #ifdef TRACE
    if (tl & (1u<<15))
    {
      Printf("image_token_fetched: Represents image %d\n",image);
      Printf("image_token_fetched: Exitting through image_fetched\n");
    }
  #endif

  return image_fetched(b, image);
}

/*************************************************/
/* image_total_bytes_fetched()                   */
/*                                               */
/* Works out the total number of bytes fetched   */
/* in images so far.                             */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the images.           */
/*                                               */
/* Returns:    The number of bytes fetched in    */
/*             image data at the moment of       */
/*             calling the function.             */
/*************************************************/

int image_total_bytes_fetched(browser_data * b)
{
  int count = 0;

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

  /* Sum the fetched size of all images currently present, */
  /* if there are any to count.                            */

  if (nimages)
  {
    int image;

    for (image = 0; image < nimages; image++)
    {
      if (idata[image].owner == b) count += idata[image].bytesgot;
    }
  }

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_total_bytes_fetched: Exitting with %d\n",count);
  #endif

  return count;
}

/*************************************************/
/* image_count_pending()                         */
/*                                               */
/* Counts how many images are waiting to be      */
/* fetched, but have been delayed for whatever   */
/* reason.                                       */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the images. Any       */
/*             images in child windows are also  */
/*             included in the count.            */
/*                                               */
/* Returns:    The number of images waiting to   */
/*             be fetched, counting any images   */
/*             in any child windows (if present) */
/*************************************************/

int image_count_pending(browser_data * b)
{
  int i, c = 0;

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_count_pending: Called for %p\n",b);
  #endif

  if (b->nchildren)
  {
    for (i = 0; i < b->nchildren; i++)
    {
      c += image_count_pending(b->children[i]);
    }
  }
  else
  {
    for (i = 0; i < nimages; i++)
    {
      if (
           !idata[i].fetched   &&
           !idata[i].delayed   &&
           idata[i].xref < 0   &&
           idata[i].owner == b
         )
         c++;
    }
  }

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_count_pending: Exitting with %d for %p\n",c,b);
  #endif

  return c;
}

/*************************************************/
/* image_count_specific_pending()                */
/*                                               */
/* Counts how many images are waiting to be      */
/* fetched, but have been delayed for whatever   */
/* reason, for just the specified browser (not   */
/* including any child windows).                 */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the images.           */
/*                                               */
/* Returns:    The number of images waiting to   */
/*             be fetched in the given browser.  */
/*************************************************/

int image_count_specific_pending(browser_data * b)
{
  int i, c = 0;

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_count_specific_pending: Called for %p\n",b);
  #endif

  for (i = 0; i < nimages; i++)
  {
    if (
         !idata[i].fetched   &&
         !idata[i].delayed   &&
         idata[i].xref < 0   &&
         idata[i].owner == b
       )
       c++;
  }

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_count_specific_pending: Exitting with %d for %p\n",c,b);
  #endif

  return c;
}

/*************************************************/
/* image_count_delayed()                         */
/*                                               */
/* Returns the number of images which have been  */
/* delayed (could be fetched, but have been set  */
/* up to not fetch yet).                         */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the images.           */
/*************************************************/

int image_count_delayed(browser_data * b)
{
  int count = 0;

  if (nimages)
  {
    int  i;

    for(i = 0; i < nimages; i++)
    {
      if (
           (
             !idata[i].fetched &&
             idata[i].delayed
           )
           ||
           (
             idata[i].fetched  &&
             !idata[i].success &&
             !idata[i].istore
           )
         )
         count++;
    }
  }

  return count;
}

/*************************************************/
/* image_plot_started()                          */
/*                                               */
/* Returns 1 if a given image in a given browser */
/* has had some plotting done for it (so the     */
/* placeholder box and possibly ALT text will no */
/* longer be visible for it).                    */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the image;            */
/*                                               */
/*             The number of the image.          */
/*                                               */
/* Returns:    1 if the image has been partially */
/*             or completely plotted, else 0.    */
/*************************************************/

int image_plot_started(browser_data * b, int image)
{
  if (image < 0) return 0;

  if (!idata[image].istore) return 0;

  if (!idata[image].canredraw) return 0;

  if (idata[image].istore->width > 0) return 1;
  else                                return 0;
}

/*************************************************/
/* image_token_plot_started()                    */
/*                                               */
/* Returns 1 if a given image in a given browser */
/* has had some plotting done for it (so the     */
/* placeholder box and possibly ALT text will no */
/* longer be visible for it).                    */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the image;            */
/*                                               */
/*             Pointer to a token representing   */
/*             the image.                        */
/*                                               */
/* Returns:    1 if the image has been partially */
/*             or completely plotted, else 0.    */
/*************************************************/

int image_token_plot_started(browser_data * b, HStream * token)
{
  int image;

  image = image_get_token_image_xref(b, token);

  return image_plot_started(b, image);
}

/*************************************************/
/* image_get_token_image_xref()                  */
/*                                               */
/* Finds the image number associated with a      */
/* given token. The number will be the image     */
/* that actually has the associated image data,  */
/* not just one that cross references this data. */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the image;            */
/*                                               */
/*             The token address.                */
/*                                               */
/* Returns:    The image number which references */
/*             that token, or -1 if none found.  */
/*************************************************/

static int image_get_token_image_xref(browser_data * b, HStream * token)
{
  int i, found = -1;

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

  /* If there are any images to deal with... */

  if (nimages)
  {
    /* ...then look through them all, seeing if the */
    /* image is referred to by the given token.     */

    for (i = 0; i < nimages; i++)
    {

      if (idata[i].token == token && idata[i].owner == b)
      {
        found = i;
        break;
      }
    }

    /* If an appropriate image was found, ensure it */
    /* is the one with render data attached, not    */
    /* just a cross reference image.                */

    if (found >= 0)
    {
      if (idata[found].xref >= 0) found = idata[found].xref;
    }
  }

  /* Return the image number, or -1 for failure */

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_get_token_image_xref: Exitting with %d\n", found);
  #endif

  return found;
}

/*************************************************/
/* image_get_token_image_actual()                */
/*                                               */
/* As image_get_token_image, but returns the     */
/* exact image number represented by the given   */
/* token rather than the cross referenced one    */
/* with the pointers to the image data.          */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the image;            */
/*                                               */
/*             The token address.                */
/*                                               */
/* Returns:    The image number which references */
/*             that token, or -1 if none found.  */
/*************************************************/

static int image_get_token_image_actual(browser_data * b, HStream * token)
{
  int i, found = -1;

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

  /* If there are any images to deal with... */

  if (nimages)
  {
    /* ...then look through them all, seeing if the */
    /* image is referred to by the given token.     */

    for (i = 0; i < nimages; i++)
    {

      if (idata[i].token == token && idata[i].owner == b)
      {
        found = i;
        break;
      }
    }
  }

  /* Return the image number, or -1 for failure */

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_get_token_image_actual: Exitting with %d\n", found);
  #endif

  return found;
}

/*************************************************/
/* image_get_image_size()                        */
/*                                               */
/* Returns the size of an image in OS units.     */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the image;            */
/*                                               */
/*             The image number, from 0 to       */
/*             nimages - 1, which may just be a  */
/*             cross referencing image (i.e. has */
/*             no directly associated data);     */
/*                                               */
/*             Pointer to a BBox into which the  */
/*             size information is placed.       */
/*                                               */
/* Assumes:    The BBox pointer is not NULL.     */
/*************************************************/

static _kernel_oserror * image_get_image_size(browser_data * b, int image, BBox * box)
{
  #ifdef TRACE

    if (tl & (1u<<15)) Printf("image_get_image_size: Called for image %d\n",image);

    if (!box)
    {
      erb.errnum = Utils_Error_Custom_Normal;
      strcpy(erb.errmess,"Null pointer to BBox given to image_get_image_size");
      return &erb;
    }

    if (image < 0)
    {
      erb.errnum = Utils_Error_Custom_Normal;
      sprintf(erb.errmess,"Invalid image number %d passed to image_get_image_size (nimages = %d)", image, nimages);
      return &erb;
    }
    if (image > nimages)
    {
      erb.errnum = Utils_Error_Custom_Normal;
      sprintf(erb.errmess,"Out of range image number %d passed to image_get_image_size (nimages = %d)", image, nimages);
      return &erb;
    }

  #else

    if (image < 0 || image >= nimages) return NULL;

  #endif

  /* Zero contents of the BBox */

  memset(box, 0, sizeof(BBox));

  /* Find the image width/height in pixels. For images which can't be   */
  /* redrawn yet, ensure that the default size is returned, even if the */
  /* real size is known - this is so that ALT text placeholder redraws  */
  /* and (for example) keyboard navigation won't fail.                  */

  box->xmax = idata[image].canredraw ? idata[image].currw & ~(wimpt_dx() - 1) : -1;
  box->ymax = idata[image].canredraw ? idata[image].currh & ~(wimpt_dy() - 1) : -1;

  if (box->xmax < 1)
  {
    /* Don't have precise image dimensions, so work out the ALT text */
    /* requirements and use these instead, if there *is* ALT text!   */

    HStream * tp = NULL;

    if (image >= 0) tp = idata[image].token;

    if (!tp || !tp->text || *tp->text == 0)
    {
      box->xmax = ImageDefaultOSSize_X;
      box->ymax = ImageDefaultOSSize_Y;
    }
    else
    {
      int  h, temp, size;
      BBox fbox;

      /* Claim the font */

      size = fm_size(tp->fontsize);

      h = fm_find_font(idata[image].owner,
                       "sans",
                       size,
                       size,
                       0,
                       0);

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

      fm_get_string_width(h,
                          tp->text,
                          0x1000000,
                          strlen(tp->text),
                          -1,
                          &temp,
                          &box->xmax);

      convert_to_os(box->xmax, &box->xmax);

      /* Find the font height */

      fm_font_box(h, &fbox);

      /* As well as subtracting ymin (the y minimum coordinate */
      /* of the font bbox) from ymax, need to also add some    */
      /* height to give a gap between the text and slabbed box */
      /* that's drawn to mark the image's position.            */

      box->ymax = fbox.ymax - fbox.ymin;

      convert_to_os(choices.fontsize, &temp);

      if (temp < 16) temp = 16;

      box->ymax += temp;
      box->xmax += temp * 2;

      /* Don't want to force the page width up just because of */
      /* ALT text in images, especially in narrow items such   */
      /* as navigation frames, so limit check xmax.            */

      // Currently this is done by an absolute hard coded upper
      // limit, but ultimately it would ideally be limited e.g.
      // by cell width. Just as soon as I work out a nice way
      // of doing that... (Remember, you may not know the cell
      // width at times when this is being called to try and
      // determine it; yet you must return consistent and
      // appropriate values subsequently for redraw purposes).

      {
        int remain;

        convert_to_os(b->leftmargin + b->rightmargin, &remain);

        remain = b->display_width - remain;
        if (remain > 320) remain = 320;

        if (box->xmax > remain) box->xmax = remain;
      }
    }
  }

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_get_image_size: Successful\n");
  #endif

  return NULL;
}

/*************************************************/
/* image_get_token_image_size()                  */
/*                                               */
/* As image_get_image_size above, but takes a    */
/* token address rather than an image number.    */
/* The default 'unknown' image size will be      */
/* returned if no image could be associated with */
/* the token (no error will be raised).          */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the image;            */
/*                                               */
/*             A token address;                  */
/*                                               */
/*             Pointer to a BBox into which the  */
/*             size information is placed.       */
/*                                               */
/* Assumes:    The BBox pointer is not NULL.     */
/*************************************************/

_kernel_oserror * image_get_token_image_size(browser_data * b, HStream * token, BBox * box)
{
  int image;

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

  image = image_get_token_image_actual(b, token);

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_get_token_image_size: Exitting through image_get_image_size\n");
  #endif

  return image_get_image_size(b, image, box);
}

/*************************************************/
/* image_get_back_image_size()                   */
/*                                               */
/* Finds out the size of the background image in */
/* OS units.                                     */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the background image; */
/*             Pointer to a BBox into which the  */
/*             size information is placed.       */
/*                                               */
/* Assumes:    That neither pointer is NULL.     */
/*************************************************/

_kernel_oserror * image_get_back_image_size(browser_data * b, BBox * box)
{
  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_get_back_image_size: Called, exitting through image_get_image_size\n");
  #endif

  return (image_get_image_size(b, b->backimage, box));
}

/*************************************************/
/* image_get_token_image_position()              */
/*                                               */
/* Returns the x and y fields of the image_info  */
/* structure for a given image.                  */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the image;            */
/*                                               */
/*             Pointer to the token representing */
/*             the image;                        */
/*                                               */
/*             Pointer to an int, in which the   */
/*             X coordinate is returned;         */
/*                                               */
/*             Pointer to an int, in which the   */
/*             Y coordinate is returned.         */
/*                                               */
/* Returns:    1 if the image could not be found */
/*             from the given token, or 0 for    */
/*             success.                          */
/*                                               */
/* Assumes:    Neither pointer is NULL.          */
/*************************************************/

int image_get_token_image_position(browser_data * b, HStream * t, int * x, int * y)
{
  int image = image_get_token_image_actual(b, t);

  if (image < 0) return 1;

  *x = idata[image].x;
  *y = idata[image].y;

  return 0;
}

/*************************************************/
/* image_set_token_image_position()              */
/*                                               */
/* Sets the x and y fields of the image_info     */
/* structure for a given image, so that it may   */
/* be [partially] plotted during a fetch.        */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the image;            */
/*                                               */
/*             Pointer to the token representing */
/*             the image;                        */
/*                                               */
/*             X coordinate (window coords);     */
/*                                               */
/*             Y coordinate (window coords).     */
/*                                               */
/* Returns:    Number of the image that was      */
/*             changed, or -1 if none could be   */
/*             found for the given token.        */
/*************************************************/

int image_set_token_image_position(browser_data * b, HStream * t, int x, int y)
{
  int image = image_get_token_image_actual(b, t);

  /* If an image was found, set the x and y coordinates */

  if (image >= 0)
  {
    idata[image].x = x;
    idata[image].y = y;
  }

  return image;
}

/*************************************************/
/* image_convert_to_pixels()                     */
/*                                               */
/* Converts a given OS unit X and Y size to      */
/* pixel size, for a given image.                */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the image;            */
/*                                               */
/*             Pointer to the token representing */
/*             the image;                        */
/*                                               */
/*             Pointer to an int which already   */
/*             contains the X size in OS units,  */
/*             which will have the pixel size    */
/*             returned into it;                 */
/*                                               */
/*             A similar pointer for the Y size. */
/*                                               */
/* Assumes:    That no pointer is NULL.          */
/*************************************************/

void image_convert_to_pixels(browser_data * b, HStream * token, int * x, int * y)
{
  int image;

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

  image = image_get_token_image_xref(b, token);

  /* If the library has got enough information to fill in the */
  /* width of the image, proceed with the conversion          */

  if (image >= 0 && idata[image].istore->width > 0)
  {
    *x = *x * idata[image].istore->width  / idata[image].istore->width_os;
    *y = *y * idata[image].istore->height / idata[image].istore->height_os;
  }
  else
  {
    /* Can't find image - maybe it's not loaded... Assume 2 OS units per pixel. */

    *x = *x / 2;
    *y = *y / 2;
  }

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_convert_to_pixels: Successful\n");
  #endif
}

/*************************************************/
/* image_return_click_offset()                   */
/*                                               */
/* Calculate the coordinates of a click on an    */
/* image in pixels from the top left corner, as  */
/* required by image maps and image input        */
/* fields.                                       */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the image;            */
/*                                               */
/*             Pointer to a token representing   */
/*             the image;                        */
/*                                               */
/*             A WimpGetPointerInfo block        */
/*             pointer describing the mouse      */
/*             pointer's position;               */
/*                                               */
/*             Pointer to an int, in which the   */
/*             x offset is returned;             */
/*                                               */
/*             Pointer to an int, in which the   */
/*             y offset is returned.             */
/*                                               */
/* Assumes:    That the int pointers are not     */
/*             NULL.                             */
/*************************************************/

_kernel_oserror * image_return_click_offset(browser_data * b, HStream * t, WimpGetPointerInfoBlock * i, int * x, int * y)
{
  _kernel_oserror         * e;
  WimpGetWindowStateBlock   s;
  BBox                      box;

  /* Get the image's size and position on screen */

  s.window_handle = b->window_handle;

  e = wimp_get_window_state(&s);
  if (e) return e;

  e = image_get_token_image_size(b, t, &box);
  if (e) return e;

  if (image_get_token_image_position(b, t, x, y)) return NULL;

  *x = coords_x_toscreen(*x, (WimpRedrawWindowBlock *) &s);
  *y = coords_y_toscreen(*y, (WimpRedrawWindowBlock *) &s);

  /* Get the offset of the pointer position from the top left */
  /* of the image in ox and oy                                */

  *x = i->x - *x;
  *y = *y + (box.ymax - box.ymin) - i->y;

  image_convert_to_pixels(b, t, x, y);

  return NULL;
}

/*************************************************/
/* image_export_sprite()                         */
/*                                               */
/* Saves an image represented by the given token */
/* as a sprite at the given path.                */
/*                                               */
/* Parameters: Pointer to the path to save to;   */
/*                                               */
/*             Pointer to a browser_data struct  */
/*             that owns the image;              */
/*                                               */
/*             Pointer to the token representing */
/*             the image, or NULL for the page's */
/*             background image.                 */
/*************************************************/

_kernel_oserror * image_export_sprite(char * path, browser_data * b, HStream * image)
{
  int i;

  /* Find the image number */

  if (!image) i = b->backimage;
  else        i = image_get_token_image_xref(b, image);

  /* Complain if we can't in TRACE builds */

  if (i < 0)
  {
    #ifdef TRACE

      erb.errnum = Utils_Error_Custom_Normal;

      sprintf(erb.errmess,
              "In image_export_foreground_sprite, can't find an image that is represented by the token %p for the browser %p.",
              image,
              b);

      return &erb;

    #endif

    return NULL;
  }

  /* Must have image data...! */

  if (!idata[i].istore || !idata[i].istore->DumpSprite)
  {
    #ifdef TRACE

      erb.errnum = Utils_Error_Custom_Normal;

      sprintf(erb.errmess,
              "There is no data for this image in image_export_foreground_sprite.");

      return &erb;

    #endif

    return NULL;
  }

  /* Export the image */

  return idata[i].istore -> DumpSprite(idata[i].istore,
                                       path,
                                       -1);
}

/*************************************************/
/* image_sprite_size()                           */
/*                                               */
/* Returns the size that the given image would   */
/* be as a sprite on disc.                       */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             that owns the image;              */
/*                                               */
/*             Pointer to the token representing */
/*             the image, or NULL for the page's */
/*             background image.                 */
/*                                               */
/* Returns:    The size the image would be as a  */
/*             sprite file saved to disc, in     */
/*             bytes.                            */
/*************************************************/

int image_sprite_size(browser_data * b, HStream * image)
{
  /* ...And for now, we don't know. */

  return -1;
}