/* 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   : Reformat.c                             */
/* Purpose: Reformatting functions for the browser */
/* Author : A.D.Hodgkinson                         */
/* History: 03-Dec-96: Created                     */
/*          16-Apr-97: First merge with T.Cheal's  */
/*                     table code...               */
/*          22-May-97: Amazingly, *still* trying   */
/*                     to get this to work.        */
/*          18-Jun-97: Hpmh; works, but very slow. */
/*                     Will need to rewrite at     */
/*                     some stage; for the moment, */
/*                     moved a few bits over to    */
/*                     Tables.c as they fitted in  */
/*                     better over there.          */
/***************************************************/

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

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

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

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

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

#include "Browser.h"
#include "Fetch.h"
#include "FetchPage.h"
#include "FontManage.h"
#include "Forms.h"
#include "History.h"
#include "Images.h"
#include "Memory.h"
#include "Redraw.h"
#include "Tables.h"
#include "Toolbars.h"

#include "Reformat.h"

/* Local constant definitions */

#define BULLET_GAP 12

/* Statics */

static int               reformat_istext          (HStream * tp);
static int               reformat_useless_token   (HStream * tp);
static int               reformat_newline_check   (HStream * current, HStream * last, int offset);
static int               reformat_datasize        (HStream * p);
static _kernel_oserror * reformat_token_width     (reformat_width_data * w, unsigned int flags);
static void              reformat_check_visited   (browser_data * b, HStream * token);

static _kernel_oserror * reformat_add_line        (browser_data * b, reformat_cell * cell);
static _kernel_oserror * reformat_add_line_chunk  (browser_data * b, reformat_cell * cell);

static _kernel_oserror * reformat_format_from_now (browser_data * b, int lastline);

static _kernel_oserror * reformat_check_height    (int toplevel, browser_data * b, reformat_cell * d, int line, HStream * tp, HStream * tpLast, int offset);
static int               reformat_reformatter_r   (unsigned int flags, browser_data * b, reformat_cell * d, HStream * streambase);

// static void              reformat_strip_prespace (browser_data * b, int chunk, HStream * tp);

/*************************************************/
/* reformat_formatting()                         */
/*                                               */
/* Returns 0 if reformatting is not in progress, */
/* else non-zero.                                */
/*                                               */
/* Parameters: Pointer to the browser_data       */
/*             structure associated with the     */
/*             page which might be reformatting. */
/*                                               */
/* Returns:    0 if reformatting is not in       */
/*             progress, else it is.             */
/*************************************************/

int reformat_formatting(browser_data * b)
{
  /* Reformatting had been suspended due to an error */

  if (b->suspend_format) return 0;

  if (b->last_token)
  {
    /* If we've not overrun the tokens dealt with by the fetcher, */
    /* the fetcher could have exitted and this could be the last  */
    /* token (so we've finished).                                 */

    if (b->last_token == b->finaltoken) return 0;

    /* If the last token dealt with had not been looked at */
    /* by the fetcher, the reformatter must have exitted.  */

    if (!(b->last_token->flags & HFlags_DealtWithToken)) return 0;
  }

  /* If there are no lines, are there any HStream structures? */

  if (b->cell->nlines <= 0) return (b->finaltoken != NULL);

  /* If none of the above, we must still be formatting */

  return 1;
}

/*************************************************/
/* reformat_stop()                               */
/*                                               */
/* Suspends a reformat, flagging that this has   */
/* been done in the browser_data structure.      */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             with the details of the reformat  */
/*             process to stop inside it.        */
/*************************************************/

_kernel_oserror * reformat_stop(browser_data * b)
{
  b->suspend_format = 1;

  return toolbars_set_button_states(b);
}

/*************************************************/
/* reformat_istext()                             */
/*                                               */
/* Returns 1 if an HStream structure represents  */
/* neither a horizontal rule nor an image.       */
/*                                               */
/* Parameters: Pointer to the HStream structure. */
/*                                               */
/* Returns:    1 if the struct represents text,  */
/*             else 0.                           */
/*************************************************/

static int reformat_istext(HStream * tp)
{
  return (((tp->style) & (IMG | HR)) == 0 &&
          !((tp->style & INPUT) && HtmlINPUTtype(tp) == inputtype_IMAGE));
}

/*************************************************/
/* reformat_token_width()                        */
/*                                               */
/* Fills in the 'width' and 'bytes' fields of a  */
/* reformat_width_data structure according to    */
/* the contents of the token (HStream structure) */
/* that the reformat_width_data structure points */
/* to (this structure is defined in Reformat.h). */
/*                                               */
/* The idea is to fit the token into maxwidth    */
/* coordinates (in millipoints). The actual      */
/* width is returned (which may be greater than, */
/* or less than maxwidth) and the number of      */
/* bytes used to make that width is returned.    */
/*                                               */
/* Parameters: Pointer to a reformat_width_data  */
/*             structure (see Reformat.h)        */
/*                                               */
/* Returns: Fills in the structure's 'width' and */
/*          'bytes' fields with width and byte   */
/*          count information;                   */
/*                                               */
/*          Flags word, as passed to             */
/*          reformat_reformatter.                */
/*************************************************/

static _kernel_oserror * reformat_token_width(reformat_width_data * w, unsigned int flags)
{
  _kernel_oserror * e = NULL;
  BBox box;

  /* Deal with tables */

  if (w->tp->tag == TABLE && ISBODY(w->tp))
  {
    reformat_cell * cellarray;
    table_stream  * table = (table_stream *) w->tp;
    int             size;
    int             toplevel;

    #ifdef TRACE
      if (tl & (1u<<20)) Printf("reformat_token_width: Dealing with table, token %p\n",table);
    #endif

    tables_count_table(w->b, table);

    #ifdef TRACE
      if (tl & (1u<<20)) Printf("reformat_token_width: Tag is now 0x%x\n",w->tp->tag);
    #endif

    tables_position_table(w->b, table);

    #ifdef TRACE
      if (tl & (1u<<20)) Printf("reformat_token_width: Tag is now 0x%x\n",w->tp->tag);
    #endif

    size = table->ColSpan * table->RowSpan;

    #ifdef TRACE
      if (tl & (1u<<20)) Printf("reformat_token_width: Size of table is %d\n",size);
    #endif

    /* If reformatting, the table structure's 'cells' field will */
    /* already have information attached, so free this first.    */

    if (table->cells) HtmlFree(table->cells);

    /* Now allocate a new cell array and initialise the cell contents */

    table->cells = cellarray = HtmlMalloc(size * sizeof(reformat_cell), table);

    /* If the allocation fails, can't possibly continue so jump */
    /* back to poll loop after reporting the error.             */

    if (!table->cells)
    {
      make_no_table_memory_error(9);

      show_error_cont(&erb);
    }

    /* Otherwise, initialise the cell contents */

    tables_init_table(w->b, table, cellarray);

    /* Find the width and height of the cells and fix their positions */
    /* as millipoint offsets from the top left of the table.          */
    /*                                                                */
    /* If the reformatter flags say not to generate lines, then we    */
    /* must be doing a width finding session as part of a parent      */
    /* table, so toplevel is 0. Otherwise, this is the highest level  */
    /* and toplevel is therefore set to 1.                            */

    if (flags & Reformatter_Virtual) toplevel = 1;
    else                             toplevel = 0;

    w->width = tables_width_table (toplevel, w->b, table, w->maxwid, cellarray); // + 3200 * (table->ColSpan + 1); //3200 for edge grooves
    w->bytes = tables_height_table(toplevel, w->b, table,            cellarray); // + 3200 * (table->RowSpan + 1); //3200 for edge grooves

    tables_fix_table(w->b, table, cellarray);

    #ifdef TRACE
      if (tl & (1u<<20)) Printf("reformat_token_width: Tag is now 0x%x\n",w->tp->tag);
    #endif

    /* Return the cell array pointer for convenience (may prove useful */
    /* to have this in the reformatter some time...).                  */

    w->data = (char *) cellarray;

    return e;
  }

  /* Deal with forms: Text-based elements */

  else if (w->tp->style & (INPUT | TEXTAREA | SELECT))
  {
    if ( (w->tp->style & (TEXTAREA | SELECT) ||
         HtmlINPUTtype(w->tp) == inputtype_TEXT ||
         HtmlINPUTtype(w->tp) == inputtype_PASSWORD
         )
       )
    {
      int  h, length, extra = 0;

      if (w->tp->style & TEXTAREA)
      {
        /* Text areas */

        length = w->tp->cols;

        if (length == 1) length = 2;
        else if (length < 2) length = 30;

        extra = 0;
      }
      else if (w->tp->style & SELECT)
      {
        /* SELECT elements (have pop-up menus) */

        int    width, l;
        char * p;

        e = read_sprite_size("fgright", &width, NULL);
        if (e) return e;

        width += 32; /* Account for border and gap */
        convert_to_points(width, &extra);

        p = (char *) HtmlSELECToptions(w->tp) + 8;
        l = 8;

        while(*p != 0xff)
        {
          p++;
          if (strlen(p) > l) l = strlen(p);
          p += strlen(p) + 1;
          p += strlen(p) + 1;
        }
        length = l;
      }
      else
      {
        /* One line writable */

        length = HtmlINPUTsize(w->tp);

        if (length == 1) length = 2;
        else if (length < 2)
        {
          length = w->tp->maxlen;
          if (length > 40) length = 40;
        }
      }

      if (length == 1) length = 2;
      else if (length < 2) length = 20;

      /* Arbitrary adjustment for proportional fonts */

      if ((!choices.systemfont) && !(w->tp->style & (PRE | TT))) length /= 2;

      h = fm_find_token_font(NULL, w->tp);
      e = fm_font_box(h, &box);

      if (e) erb = *e, e = &erb;

      convert_box_to_points(&box, &box);

      w->width = (((box.xmax - box.xmin) * length)) + 16 * 400 + extra;

      fm_lose_font(NULL, h);
      w->bytes = 0;

      return e;
    }
    else switch(HtmlINPUTtype(w->tp))
    {
      case inputtype_SUBMIT: /*; no break - same as RESET */
      case inputtype_RESET:
      {
        const char * p;
        int    h, length, end;

        p = form_button_text(w->tp);
        length = strlen(p);
        end = 0;

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

        h = fm_find_token_font(NULL, w->tp);
        e = fm_get_string_width(h,
                                p,
                                Reformat_AsWideAsPossible_MP,
                                end - w->offset,
                                -1,
                                &w->bytes,
                                &w->width);

        if (e) erb = *e, e = &erb;

        w->width += 400 * 4 * 12; /* Account for border */
        w->bytes  = 0;

        fm_lose_font(NULL, h);

        return e;
      }
      break;

      case inputtype_CHECKBOX:
      {
        w->bytes = 0;

        read_sprite_size("fopton", &w->width, NULL);

        convert_to_points(w->width, &w->width);
      }
      break;

      case inputtype_RADIO:
      {
        w->bytes = 0;

        read_sprite_size("fradioon", &w->width, NULL);

        convert_to_points(w->width, &w->width);
      }
      break;

      case inputtype_HIDDEN:
      {
        w->width = w->bytes = 0;
      }
      break;

      case inputtype_IMAGE:
      {
        goto do_image; /* See below */
      }
      break;
    }
  }
  else if (w->tp->style & IMG)
  {

do_image: /* Used by switch statement above */

    w->bytes = 0;
    w->width = 0;

    /* If the image has a known width and height, the reformatter has */
    /* dealt with it - so the image library can mark it as redrawable */
    /* now (otherwise, for delayed reformats, some images which were  */
    /* cross referenced and did not have reformat sessions explicitly */
    /* started for them, may get stuck in a 'non-redrawable' state).  */

    image_token_check_redrawable(w->b, w->tp);

    /* Now get the size of the image for reformatting purposes */

    e = reformat_get_image_size(w->b, w->tp, &box);
    if (e) return e;

    w->width = box.xmax - box.xmin;

    convert_to_points(w->width, &w->width);
  }
  else if (w->tp->style & HR)
  {
    /* For a horizontal rule, there's no extra data to put in (so */
    /* the w->bytes field is zero) and the width is as wide as    */
    /* the specified maximum width.                               */
    /*                                                            */
    /* The special case of finding min/max widths is checked in   */
    /* the flags here, as otherwise any HR tag can force the      */
    /* width up to maxwid - which may be very large.              */

    w->bytes = 0;

    if (
         !(flags & Reformatter_FindingWidest)   &&
         !(flags & Reformatter_FindingSmallest)
       )
       w->width = w->maxwid;

    else w->width = 0;
  }
  else if (ISBULLET(w->tp)) /* ISBULLET is defined in Fetch.h */
  {
    /* For a bullet, there is again no extra data so bytes = 0, */
    /* and the width is taken from the sprite width of the      */
    /* bullet point.                                            */

    w->bytes = 0;

    convert_to_points(reformat_bullet_width(w->tp->indent), &w->width);
  }
  else
  {
    int h, end, split, length;

    /* If there is text associated with the token, set 'length' */
    /* to the string length of that text; else set it to 0.     */

    length = w->tp->text ? strlen(w->tp->text) : 0;

    /* Loop round until a newline is found, or the end of the  */
    /* string is reached, starting at the offset into the data */
    /* specified by the 'offset' field.                        */

    end = w->offset;
    while ((end < length) && (w->data[end] != '\n')) end++;

    /* If the text is preformatted, set a null split character. */
    /* Else specify splitting on spaces.                        */

    split = (w->tp->style & PRE) ? -1 : ' ';

    /* Get a font handle for rendering the token */

    h = fm_find_token_font(NULL, w->tp);

    /* If end > offset, the loop above must have gone through at least */
    /* one non-newline character in the string, or there was no string */
    /* to look through; find the width of the string (the call won't   */
    /* mind if there's no string, it'll just return 0)                 */

    if (end > w->offset) e = fm_get_string_width(h,
                                                 w->data + w->offset,
                                                 w->maxwid,
                                                 end - w->offset,
                                                 split,

                                                 &w->bytes,
                                                 &w->width);

    /* If finding the minimum or maximum width for tables, add a little to */
    /* the above width to account for italics etc. - the font manager will */
    /* not have returned the upper limit on either side.                   */

    if (
         (flags & Reformatter_FindingWidest)   ||
         (flags & Reformatter_FindingSmallest)
       )
       w->width += 3200;

    /* If the scan for a newline finished before the end of the string (so */
    /* a newline was found) and the chunk of data defined by the call to   */
    /* fm_get_string_width() above gave a string ending in a newline, add  */
    /* 1 to the bytes counter to ensure that the chunk includes it.        */

    if (end < length && w->data[w->offset + w->bytes] == '\n') w->bytes++;

    /* We don't need to keep the font claimed for just finding out a width */

    fm_lose_font(NULL, h);
  }

  return e;
}

/*************************************************/
/* reformat_useless_token()                      */
/*                                               */
/* Checks to see if a token (HStream struct) is  */
/* of any use to the reformatter.                */
/*                                               */
/* Parameters: Pointer to the HStream struct.    */
/*                                               */
/* Returns:    1 = the token is useless, else 0. */
/*************************************************/

static int reformat_useless_token(HStream * tp)
{
  /* Say the token is useless if it is part of the document */
  /* header. Otherwise say it is useful. The ISHEAD macro   */
  /* is defined in Fetch.h.                                 */

  if (ISHEAD(tp)) return 1;
  if (ISNULL(tp)) return 1;

  return 0;
}

/*************************************************/
/* reformat_newline_check()                      */
/*                                               */
/* Works out whether or not there should be a    */
/* line break in the page, according to the      */
/* token (HStream structure) that is currently   */
/* being considered and the token that was last  */
/* considered, and the offset into the data of   */
/* the tokens.                                   */
/*                                               */
/* Parameters: Pointer to the current token (the */
/*             HStream structure that is being   */
/*             dealt with by the reformatter,    */
/*             say, at the moment);              */
/*             Pointer to the last token that    */
/*             was dealt with;                   */
/*             Data offset into the tokens.      */
/*                                               */
/* Returns:    0 if there is no line break       */
/*             needed, or a value between 1 and  */
/*             8 that says 'yes, line break      */
/*             needed' and also holds details of */
/*             the conditions that were met to   */
/*             determine the line break was      */
/*             needed.                           */
/*************************************************/

static int reformat_newline_check(HStream * current, HStream * last, int offset)
{
  /* If the current token represents a horizontal rule and the last token also */
  /* represented one, with no offset indicating extra data in the tokens (and  */
  /* provided that there is actually a last token!), then return 1.            */

  if (((current->style) & HR) || ((!offset) && (last) && ((last->style) & HR))) return 1;

  /* If the tag itself indicates that a linebreak is needed, and we are at the */
  /* start of this line (offset into it is zero), return 2. This may be due to */
  /* <P>, <BR> and the like, in the document source.                           */

  if ((!offset) && ((current->style) & LINEBREAK)) return 2;

  /* If, again, this is the start of a line, and the pointer to the previous */
  /* token dealt with is not null, proceed with other line break checks.     */

  if ((!offset) && (last))
  {
    /* If the indentation has changed since the last token (e.g. a new list has */
    /* been started or an old one closed between the two tokens) then return 3. */

    if (current->indent != last->indent) return 3;

    /* If the header type changed between the two tokens, then provided the     */
    /* last token wasn't a list item (in which case a line break will already   */
    /* have been put in) return 4.                                              */

    if (((current->style & H_MASK) != (last->style & H_MASK)) && !(last->style & LI)) return 4;

    /* When certain tags are turned on or off, we want a linebreak. For example */
    /* it looks tidier for preformatted text to have a gap above and below it,  */
    /* rather than having it touch the previous and following text. The macro   */
    /* LINEBREAKSW (defined in Reformat.h) has a list of these tags ORed        */
    /* together to form a mask - if this mask changes between the tokens, one   */
    /* of the listed tags must have turned on or off. So we want a line break;  */
    /* hence, return 5.                                                         */

    if ((current->style & LINEBREAKSW) != (last->style & LINEBREAKSW)) return 5;

    /* CENTRED is defined in Reformat.h, and is 1 if the token holds data that  */
    /* should be displayed centred in the page. If switching from centred to    */
    /* uncentred text or vice versa, there must be a line break. So return 6.   */

    if (CENTRED(current) != CENTRED(last)) return 6;

    /* Some tags need a line break when they turn on, for example at the start  */
    /* of certain special kinds of list. The LINEBREAKON macro is defined in    */
    /* reformat.h and contains an ORed together list of such tags (just like    */
    /* the LINEBREAKSW macro used above) - so if the current token has one of   */
    /* these turned on, but the last token had it turned off, we want another   */
    /* line break; return 7.                                                    */

    if ((current->style & LINEBREAKON) && !(last->style & LINEBREAKON)) return 7;

    /* Similarly, some tags need a line break when they turn off; for example,  */
    /* headers look better if they have a gap underneath them. So again, there  */
    /* is a macro (LINEBREAKOFF) in Reformat.h which has a list of these tags   */
    /* and a value of 8 is returned if we go from one of these being turned     */
    /* on to one being turned off.                                              */

    if (!(current->style & LINEBREAKOFF) && (last->style & LINEBREAKOFF)) return 8;
  }

  /* If none of the above conditions are met, we need no line break - return 0. */

  return 0;
}

/*************************************************/
/* reformat_newline()                            */
/*                                               */
/* Returns 1 if a line break should be inserted  */
/* onto the page, or 0 if not. Does this by      */
/* calling reformat_newline_check() (see above)  */
/* and is only interested in if the value the    */
/* call returned was zero or not.                */
/*                                               */
/* Parameters: As for reformat_newline_check.    */
/*                                               */
/* Returns:    1 = line break required, else 0.  */
/*************************************************/

int reformat_newline(HStream * current, HStream * last, int offset)
{
  return (reformat_newline_check(current, last, offset) != 0);
}

/*************************************************/
/* reformat_datasize()                           */
/*                                               */
/* Returns the size of standard data pointed to  */
/* by an HStream (e.g. the length of any text    */
/* string it points to).                         */
/*                                               */
/* Parameters: Pointer to the HStream struct     */
/*                                               */
/* Returns: Size of associated data, in bytes    */
/*************************************************/

static int reformat_datasize(HStream * tp)
{
  /* Horizontal Rule and Image tags have no relevant extra */
  /* data. HR and IMG are defined in HTMLLib:tags.h.       */

  if ((tp->style) & HR) return 0;
  if ((tp->style) & IMG) return 0;

  /* If the HStream has a pointer to some text, return the */
  /* length of that text, else return zero.                */

  return (tp->text ? strlen(tp->text) : 0);
}

/*************************************************/
/* reformat_shift_vertically()                   */
/*                                               */
/* Shifts all lines between two given line       */
/* numbers (inclusive) by a given y coordinate,  */
/* in OS units. A redraw is generated with       */
/* Wimp_BlockCopy if moved lines lie inside the  */
/* visible area of the browser window.           */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             holding information on the lines  */
/*             The line number at which to start */
/*             The line number at which to end   */
/*                         (these are inclusive) */
/*             The value to shift the lines by,  */
/*             in OS units. Since the Y coords   */
/*             of the lines are for a window,    */
/*             a negative value would move the   */
/*             lines down the window and vice    */
/*             versa.                            */
/*************************************************/

_kernel_oserror * reformat_shift_vertically(browser_data * b, int start, int end, int y_shift)
{
  _kernel_oserror * e;
  int               temp, topline, bottomline;
  reformat_cell   * cell = b->cell;

  if (!y_shift) return NULL;

  /* Limit check start and end, and exit if there appear */
  /* to be no lines to move                              */

  if (start > cell->nlines - 1) return NULL;
  if (start < 0) start = 0;
  if (end > cell->nlines - 1) end = cell->nlines - 1;
  if (end < 0) end = 0;
  if (start > end) temp = start, start = end, end = temp;

  if ((end < 0) || (start < 0) || (!cell->ldata)) return NULL;

  topline    = cell->ldata[start].y + cell->ldata[start].h;
  bottomline = cell->ldata[end].y;

  /* Alter the lines' y-coordinates */

  for (temp = start; temp <= end; temp ++) cell->ldata[temp].y += y_shift;

  /* Handle redraw / shifting of the page with Wimp_BlockCopy */

  {
    WimpGetWindowStateBlock s;
    BBox                    work;

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

    coords_box_toworkarea(&s.visible_area,(WimpRedrawWindowBlock *) &s);

    work = s.visible_area;

    coords_box_toworkarea(&work,(WimpRedrawWindowBlock *) &s);

    e = _swix(Wimp_BlockCopy,
              _INR(0,6),

              s.window_handle,
              work.xmin,
              bottomline,
              work.xmax,
              topline,
              work.xmin,
              bottomline + y_shift);

    if (e) return e;
  }

  return reformat_check_extent(b);
}

/*************************************************/
/* reformat_format_from()                        */
/*                                               */
/* Starts a new reformat session, possibly       */
/* starting after a given last line (i.e. from   */
/* a certain point in the page, rather than the  */
/* whole page).                                  */
/*                                               */
/* The reformat session may be postponed until   */
/* later according to the RefoTime and RefoWait  */
/* entires in the Choices file.                  */
/*                                               */
/* Parameters: A pointer to a browser_data       */
/*             structure for which the reformat  */
/*             is to take place;                 */
/*                                               */
/*             The number of the last valid      */
/*             reformat_line structure;          */
/*                                               */
/*             1 to format now, else the request */
/*             may be delayed for some time;     */
/*                                               */
/*             For requests generated by the     */
/*             resizing of images, pass the      */
/*             image number, else pass -1.       */
/*************************************************/

_kernel_oserror * reformat_format_from(browser_data * b, int lastline, int immediate, int image)
{
  _kernel_oserror * e;

  /* Do we delay these requests? */

  if (!choices.refowait || immediate) reformat_format_from_now(b, lastline);
  else
  {
    if (b->refotime)
    {
      /* If there's already a pending request, is the line number */
      /* specified for this one�lower than the one already there? */
      /* If so, then use that higher line, else stick with the    */
      /* old one. Nothing else to do - the null handler takes     */
      /* care of it all.                                          */

      if (lastline < b->refoline) b->refoline = lastline;
    }
    else
    {
      /* This is the first reformat request; so set the timer and */
      /* install a null handler for it.                           */

      e = _swix(OS_ReadMonotonicTime,
                _OUT(0),

                &b->refotime);

      if (e) return e;

      b->refoline = lastline;

      register_null_claimant(Wimp_ENull, (WimpEventHandler *) reformat_format_timer, b);
    }
  }

  return NULL;
}

/*************************************************/
/* reformat_format_timer()                       */
/*                                               */
/* Calls the reformatter after a delay given by  */
/* the Choices file entry 'RefoTime', according  */
/* to the start time specified by the 'refotime' */
/* field of the browser_data structure.          */
/*                                               */
/* Parameters are as standard for a Wimp event   */
/* handler.                                      */
/*************************************************/

int reformat_format_timer(int eventcode, WimpPollBlock * b, IdBlock * idb, browser_data * handle)
{
  int timenow;

  /* If there appears to be no initiation time specified in the      */
  /* browser_data structure, dereigster and exit (being cautions...) */

  if (!handle->refotime)
  {
    handle->refotime = 1; /* So that reformat_stop_pending will deregister this handler */

    reformat_stop_pending(handle);

    return 0;
  }

  /* Otherwise, get the current time into 'timenow' */

  if (_swix(OS_ReadMonotonicTime,
            _OUT(0),

            &timenow)) return 0;

  /* Do we need to reformat yet? */

  if (timenow - handle->refotime > choices.refotime)
  {
    /* reformat_format_from_now will deregister this handler */

    ChkError(reformat_format_from_now(handle, handle->refoline));
  }

  return 0;
}

/*************************************************/
/* reformat_format_from_now()                    */
/*                                               */
/* Starts a new reformat session, possibly       */
/* starting after a given last line (i.e. from   */
/* a certain point in the page, rather than the  */
/* whole page).                                  */
/*                                               */
/* The reformat session will be stared           */
/* immediately - it will not be delayed. Don't   */
/* call this directly - go through               */
/* reformat_format_from instead.                 */
/*                                               */
/* Parameters: A pointer to a browser_data       */
/*             structure for which the reformat  */
/*             is to take place;                 */
/*                                               */
/*             The number of the last valid      */
/*             reformat_line structure.          */
/*************************************************/

static _kernel_oserror * reformat_format_from_now(browser_data * b, int lastline)
{
  int             bottom = 0;
  reformat_cell * cell   = b->cell;

  /* If this browser had any queued reformats, cancel them */

  if (b->refotime)
  {
    if (b->refoline < lastline) lastline = b->refoline;

    b->refotime = 0;
    b->refoline = 0x1000000;
    deregister_null_claimant(Wimp_ENull, (WimpEventHandler *) reformat_format_timer, b);
  }

  /* Line numbers less than -1 are invalid, and can't end */
  /* on a line greater than the number of lines present!  */

  if (lastline < -1) lastline = -1;
  if (lastline >= cell->nlines) lastline = cell->nlines - 1;

  /* Clear the flag that says formatting is suspended due to error */

  b->suspend_format = 0;

  /* Clear the field holding the last token number for which */
  /* reformatting was definitely completed                   */

  b->last_token = NULL;

  /* If lastline holds a valid line number, set bottom to the */
  /* bottom y coordinate of the last line. Otherwise leave    */
  /* bottom set at 0 (we're reformatting to no lines).        */

  if (lastline >= 0) bottom = cell->ldata[lastline].y;

  /* If there's existing line data, need to ensure that all  */
  /* memory allocated in table cells is freed before getting */
  /* rid of the lines after lastline.                        */

  tables_free_memory(1, b, cell, lastline + 1);

  /* If we're reformatting to less than the current number of */
  /* lines, 'chop off' those that are left over.              */

  if ((lastline + 1) < cell->nlines)
  {
    int size;//, topline;

//    /* Before anything else, mark the token currently displayed at the */
//    /* top of the page so it can be shown after the reformat.          */
//
//    topline = browser_top_line(b, cell, 0) - 1;
//
//    if (topline >= 0 && (lastline + 1) < topline)
//    {
//      b->display_request = cell->cdata[cell->ldata[topline].chunks].t;
//      b->display_offset  = cell->cdata[cell->ldata[topline].chunks].o;
//      b->display_vscroll = 0;
//    }

    /* Size = offset of line chunks for the last line, plus  */
    /*        the number of line chunks times the size of a  */
    /*        chunk. I.e. the offset for the end of the line */
    /*        chunks for the last line - so size = size of   */
    /*        data that is needed for all the line chunks    */
    /*        with lastline chunks present.                  */

    if (lastline < 0) size = 0;
    else
    {
      /* If it turns out this line has no chunks, need to use the chunk address */
      /* and number of chunks for the previous line; unless, of course, this is */
      /* line 0, in which case there are no previous lines to refer to.         */

      if (!cell->ldata[lastline].n)
      {
        if (lastline == 0) size = 0;
        else size = (cell->ldata[lastline - 1].chunks + /* Base array offset into chunks                                    */
                    cell->ldata[lastline - 1].n)      * /* Add number of chunks for this line to get total number of chunks */
                    sizeof(reformat_line_chunk);        /* Multiply by size of a reformat_line_chunk to get total size      */
      }

      /* Otherwise, we're OK; the last line has line chunks. */

      else size = (cell->ldata[lastline].chunks +
                  cell->ldata[lastline].n)      *
                  sizeof(reformat_line_chunk);
    }

    /* Clip the number of lines to the new value */

    cell->nlines = lastline + 1;

    /* Make sure that the right amount of memory is allocated */

    #ifdef TRACE
      if (tl & (1u<<12)) Printf("reformat_format_from: Chunk CK_LINE set to %d\n",(lastline + 1) * sizeof(reformat_line));
    #endif

    memory_set_chunk_size(b, cell, CK_LINE, (lastline + 1) * sizeof(reformat_line));

    #ifdef TRACE
      if (tl & (1u<<12)) Printf("reformat_format_from: Chunk CK_LDAT set to %d\n",size);
    #endif

    memory_set_chunk_size(b, cell, CK_LDAT, size);
  }

  if (!printing)
  {
    /* Ensure null events are claimed for the rest of the reformat */

    if (!b->fetch_handler) fetchpage_claim_nulls(b);

    /* Make sure the forms library knows where the caret should be */

    form_check_caret(b);

    /* Update the status bar */

    toolbars_update_status(b, Toolbars_Status_Formatting);
  }

  /* Redraw the bottom line of the window if not printing */

  return (!printing ? browser_update_bottom(b, bottom) : NULL);
}

/*************************************************/
/* reformat_stop_pending()                       */
/*                                               */
/* Stops any pending reformats from happening,   */
/* deregistering handlers etc. as needed.        */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the queue.            */
/*************************************************/

void reformat_stop_pending(browser_data * b)
{
  if (b->refotime) deregister_null_claimant(Wimp_ENull, (WimpEventHandler *) reformat_format_timer, b);

  b->refotime = 0;
  b->refoline = 0x1000000;
}

/*************************************************/
/* reformat_get_image_size()                     */
/*                                               */
/* Gets a BBox for a specified image in OS       */
/* coordinates relative to the font base line    */
/* and left hand edge.                           */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the image;            */
/*                                               */
/*             A token address for the image;    */
/*             Pointer to a BBox in which the    */
/*             relevant coords are returned.     */
/*                                               */
/* Returns:    See parameters list.              */
/*                                               */
/* Assumes:    Pointer to the BBox may not be    */
/*             NULL.                             */
/*************************************************/

_kernel_oserror * reformat_get_image_size(browser_data * b, HStream * tp, BBox * box)
{
  _kernel_oserror * e;
  imgalign          align;

  /* Get the image size from the image library */

  e = image_get_token_image_size(b, tp, box);
  if (e) return e;

  /* Deal with alignments */

  if (tp->style & IMG) /* It'll either be an IMG or an INPUT TYPE=IMAGE item */
  {
    if      ((tp->type & TYPE_ALIGN_MASK) == TYPE_MIDDLE) align = imgalign_MIDDLE;
    else if ((tp->type & TYPE_ALIGN_MASK) == TYPE_TOP)    align = imgalign_TOP;
    else                                                  align = imgalign_NONE;
  }
  else align = HtmlINPUTalign(tp);

  switch (align)
  {
    case imgalign_MIDDLE:
    {
      box->ymin -= box->ymax / 2;
      box->ymax /= 2;
    }
    break;

    case imgalign_TOP:
    {
      box->ymin =- box->ymax;
      box->ymax = 0;
    }
    break;
  }

  /* Deal with links - need to account for a border */
  /* of maxlen * 2 pixels width. ISLINK is defined  */
  /* in Fetch.h.                                    */

  if (ISLINK(tp) && (tp->style & IMG))
  {
    int b;

    b = tp->maxlen * 2;

    box->xmax += b;
    box->ymax += b;
    box->xmin -= b;
    box->ymin -= b;
  }

  return NULL;
}

/*************************************************/
/* reformat_bullet_width()                       */
/*                                               */
/* Returns the width of a given bullet in OS     */
/* units.                                        */
/*                                               */
/* Parameters: The bullet number (in the Sprites */
/*             file, bullets are named b1, b2,   */
/*             ...bn).                           */
/*************************************************/

int reformat_bullet_width(int bullet)
{
  char spr[20];
  int  w;

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

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

  /* See top of the file for BULLET_GAP */

  return w + BULLET_GAP;
}

/*************************************************/
/* reformat_bullet_height()                      */
/*                                               */
/* Returns the height of a given bullet in OS    */
/* units.                                        */
/*                                               */
/* Parameters: The bullet number (in the Sprites */
/*             file, bullets are named b1, b2,   */
/*             ...bn).                           */
/*************************************************/

int reformat_bullet_height(int bullet)
{
  char spr[20];
  int  h;

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

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

  return h;
}

/*************************************************/
/* reformat_y_offset()                           */
/*                                               */
/* Returns the y offset from the top of a page,  */
/* in OS units, for all lines on that page.      */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             representing the page.            */
/*                                               */
/* Returns:    y offset for all the lines on the */
/*             page, in OS units, from the top.  */
/*************************************************/

int reformat_y_offset (browser_data * b)
{
  int offset = toolbars_button_height(b) + toolbars_url_height(b);

  /* The '4' accounts for the bottom window frame of the toolbars */

  if (offset) offset += wimpt_dy();

  if (!b->ancestor) offset += b->leading; /* Only put a gap at the top for base browsers, not for frames */

  return -offset;
}

/*************************************************/
/* reformat_check_height()                       */
/*                                               */
/* Looks at the contents of a reformat_line      */
/* structure and ensures that it's y coordinate, */
/* height and font baselines offset fields are   */
/* correctly filled in.                          */
/*                                               */
/* Parameters: 1 for all external callers, but   */
/*             will be 0 when it calls itself    */
/*             internally;                       */
/*                                               */
/*             Pointer to a browser_data struct  */
/*             relevant to the line;             */
/*                                               */
/*             Pointer to a reformat_cell struct */
/*             that the line lies in;            */
/*                                               */
/*             Number of the line in to alter;   */
/*                                               */
/*             Pointer to the token that may     */
/*             cause the line height to alter;   */
/*                                               */
/*             Pointer to the last token dealt   */
/*             with by the reformatter;          */
/*                                               */
/*             Offset into the current token's   */
/*             data.                             */
/*                                               */
/* Returns:    Fills in bits of the line struct, */
/*             as mentioned above.               */
/*************************************************/

static _kernel_oserror * reformat_check_height(int toplevel, browser_data * b, reformat_cell * d, int line, HStream * tp, HStream * tpLast, int offset)
{
  _kernel_oserror * e;
  int               top = 0, bot = 0;

  /* Find out the height of an image */

  if (
       (tp->style & IMG) ||
       (
         (tp->style & INPUT) &&
         HtmlINPUTtype(tp) == inputtype_IMAGE
       )
     )
  {
    BBox box;

    e = reformat_get_image_size(b, tp, &box);
    if (e) return e;

    top =  box.ymax;
    bot = -box.ymin;
  }

  /* Size of a horizontal rule; the rule is plotted */
  /* centred vertically within its bounding box so  */
  /* there is no need to set both bot and top to    */
  /* the same value. With top = 0, setting bot is   */
  /* enough.                                        */

  else if (tp->style & HR)
  {
    int size;

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

    if (HR_HAS_SIZE(tp))
    {
      /* (Only recognise pixels at present) */

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

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

    if (size < 24) size = 24;

    bot           = size + 4;
    b->lastspace += bot;
  }

  /* A few easy to work out forms elements */

  else if ((tp->style & INPUT) && HtmlINPUTtype(tp) == inputtype_CHECKBOX)
  {
    read_sprite_size("fopton", NULL, &top);
    top -= 8;
    bot = 8;
  }

  else if ((tp->style & INPUT) && HtmlINPUTtype(tp) == inputtype_RADIO)
  {
    read_sprite_size("fradioon", NULL, &top);
    top -= 8;
    bot = 8;
  }

  /* Height of a bullet point */

  else if (ISBULLET(tp))
  {
    top = reformat_bullet_height(tp->indent);
  }

  else if (tp->tag == TABLE && ISBODY(tp))
  {
    /* Don't do anything! h is already correct */

    tp = tp; /* Make sure the compiler gets the if..else if.. etc. right */
  }

  /* If the above matches aren't found, use a more general routine */

  else
  {
    int           h, spaced = 0, fontsize = choices.fontsize >> 4;
    unsigned char add;
    BBox          box;

    /* Get the bounding box of the font that should be used for the token */

//    if (tp->text && *tp->text)
    {
      h = fm_find_token_font(b, tp);
      e = fm_font_box(h, &box);
      if (e) return e;

      top = box.ymax;

//    /* Account for leading. */
// // /* Don't do for bold/italic due to font manager bug... */
//
//    if (!(tp->style & (ITALIC | BOLD))) bot += b->leading;

      bot = -box.ymin + b->leading;

      /* Ensure the bottom line is not negative */

      if (bot < 0) bot = 0;
    }
//    else top = bot = box.xmin = box.xmax = box.ymin = box.ymax = 0;

    /* The amount to add to 'top' to give line break effects */

    add = (unsigned char) 3 * fontsize;

    /* If the token represents a line break but there is no extra  */
    /* data (so this is right at the top of the paragraph) include */
    /* a gap for the break.                                        */

    if ((!offset) && (tp->style & (P | UL)))
    {
      b->lastspace += add, spaced = 1;
      // if (b->lastspace <= add) top += add;
      top += add;
    }

    /* Leave a gap above any DT elements */

    if (!offset && (tp->style & DT))
    {
      b->lastspace += add, spaced = 1;
      // if (b->lastspace <= add) top += add;
      top += add;
    }

    if ((tpLast) && !(tp->style & (DD | DT)) && (tpLast->style & DD))
    {
      b->lastspace += top, spaced = 1;
      // if (b->lastspace <= add) top += add;
      top += add;
    }

// Debug section, useful to keep around in case things go wrong!
//
//    Printf("H_MASK: %d\n",(tp->style & H_MASK));
//    if (tp->text) Printf("Text  : '%s'\n",tp->text);
//    else Printf("(No text)\n");
//    Printf("Last  : %p\n",(void *) tpLast);
//    Printf("H_MASK: %d\n",(tpLast->style & H_MASK));
//    if (tpLast) Printf("Logic : %d\n\n",((tp->style & H_MASK) && !(tpLast->style & H_MASK)));
//    else Printf("(No last)\n\n");

    /* Gap if the indentation level changes */

    if ((tpLast) && (tp->indent != tpLast->indent))
    {
      b->lastspace += add, spaced = 1;
      // if (b->lastspace <= add) top += add;
      top += add;
    }

    /* Gaps for headers; first, if a header is turned on, */
    /* then, if it is turned off.                         */

    if ((tpLast) && ((tp->style & H_MASK) && !(tpLast->style & H_MASK)))
    {
      b->lastspace += add, spaced = 1;
      // if (b->lastspace <= add) top += add;
      top += add;
    }

    if ((tpLast) && ((tp->style & H_MASK) && !(tpLast->style & H_MASK)))
    {
      b->lastspace += add, spaced = 1;
      // if (b->lastspace <= add) bot += add;
      top += add;
    }

    /* Similarly give extra gaps around header entries */

    switch (redraw_header(tp->style))
    {
      case 1: bot += 16, top += 20;
      break;
      case 2: bot += 16, top += (64 & (!offset));
      break;
      case 3: bot += 16, top += (64 & (!offset));
      break;
      case 4: bot += 16, top += (32 & (!offset));
      break;
      case 5: top += (32 & (!offset));
      break;
      case 6: top += (4 & (!offset));
      break;

      /* Give a gap for block quotations as they switch on or off (i.e. the flag saying */
      /* if the tag is part of a block quote has changed between this tag and the last  */
      /* one).                                                                          */

      default: if (tpLast && ((tp->style & BLOCKQUOTE) != (tpLast->style & BLOCKQUOTE)) && !offset && !add)
               {
                 b->lastspace += top, spaced = 1;
                 // if (b->lastspace <= add) top += top;
               }
    }

    if (!spaced) b->lastspace = 0;
  }

  /* Round up top and bot to a multiple of 4 */

  if (top & 3) top += 4 - (top & 3);
  if (bot & 3) bot += 4 - (bot & 3);

  /* Work out height of various forms elements */

  if (tp->style & TEXTAREA)
  {
    /* Text areas, based on the number of rows */

    int r;

    r = tp->rows;
    if (r < 2) r = 2;
    top = (top + bot) * r + 12 - bot;
    bot += 16;
  }
  else if (tp->style & SELECT)
  {
    /* Selection lists - a pop-up menu icon */

    int h;

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

    bot += 8;
    top += 8;
    if (top + bot < h) top += h - top - bot;
  }
  else if (tp->style & INPUT)
  {
    /* General input types */

    switch(HtmlINPUTtype(tp))
    {
      case inputtype_TEXT: /* No break - same as PASSWORD */
      case inputtype_PASSWORD:
        bot += 8;
        top += 8;
      break;
      case inputtype_SUBMIT: /* No break - same as RESET */
      case inputtype_RESET:
        bot += 8;
        top += 12;
      break;
    }
  }

  /* 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 > (d->ldata[line].h - d->ldata[line].b))
  {
    int diff;

    diff = top - (d->ldata[line].h - d->ldata[line].b);
    d->ldata[line].h += diff;
    d->ldata[line].y -= diff;
  }

  /* Similarly, if bot is greater than the offset of the baseline from */
  /* the bottom of the line, account for the extra offset.             */

  if (bot > d->ldata[line].b)
  {
    int diff;

    diff = bot - d->ldata[line].b;

    d->ldata[line].h += diff;
    d->ldata[line].b += diff; /* Was lp->b = bot; */
    d->ldata[line].y -= diff;
  }

  return NULL;
}

/*************************************************/
/* reformat_check_visited()                      */
/*                                               */
/* Looks at a given token and compares it to the */
/* global history, setting a bit in the flags    */
/* word if a link it represents has been visited */
/* before.                                       */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the token;            */
/*                                               */
/*             Pointer to the token.             */
/*************************************************/

static void reformat_check_visited(browser_data * b, HStream * token)
{
  if (ISLINK(token) && history_visited(token->anchor, 0) >= 0) token->flags |= HFlags_LinkVisited;
}

/*************************************************/
/* reformat_check_extent()                       */
/*                                               */
/* Sets the browser window vertical extent to    */
/* match the page, by looking at the last line   */
/* in the line list.                             */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             holding details of the window to  */
/*             alter.                            */
/*************************************************/

_kernel_oserror * reformat_check_extent(browser_data * b)
{
  return reformat_set_extent(b, -reformat_return_extent(b, NULL));
}

/*************************************************/
/* reformat_return_extent()                      */
/*                                               */
/* Returns the height of a page in OS units,     */
/* according to its current formatted state.     */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             holding details of the page;      */
/*                                               */
/*             Pointer to a reformat_cell struct */
/*             holding redraw information.       */
/*************************************************/

int reformat_return_extent(browser_data * b, reformat_cell * cell)
{
  int extent = 0;

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

  if (cell->ldata) extent = -cell->ldata[cell->nlines - 1].y;

  return extent;
}

/*************************************************/
/* reformat_set_extent()                         */
/*                                               */
/* Sets the browser window vertical extent. The  */
/* extent can only ever grow.                    */
/*                                               */
/* Parameters: A pointer to a browser_data       */
/*             structure containing details of   */
/*             the window to alter;              */
/*             A new vertical extent in OS units */
/*             - this is a *negative* number,    */
/*             expressed as a downward offset    */
/*             from the top of the window.       */
/*************************************************/

_kernel_oserror * reformat_set_extent(browser_data * b, int y_extent)
{
  _kernel_oserror           * e;
  WimpGetWindowStateBlock     s;
  BBox                        new_extent;

  #ifdef TRACE
    if (tl & (1u<<8)) Printf("\nreformat_set_extent: Called, -y_extent = %d\n",-y_extent);
  #endif

  /* For windows with frames, don't want to mess around with the extent */

  if (b->nchildren) return NULL;

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

  /* Y extent is set to the requested value plus an amount for the toolbars */
  /* and an extra amount for aesthetics.                                    */

  y_extent -= (toolbars_status_height(b) + b->leading);

  /* Ensure that the extent is at least as great as the minimum height */

  if ((-y_extent) < b->min_height) y_extent = -(b->min_height);

  /* For the height, don't want to resize below the current */
  /* visible height. Things are a bit messy due to the      */
  /* negative signs on extent, etc.                         */

  if ((-y_extent) < (s.visible_area.ymax - s.visible_area.ymin)) y_extent = -(s.visible_area.ymax - s.visible_area.ymin);

  new_extent.ymax = 0;
  new_extent.ymin = y_extent;

  /* x extent must only match or be larger than the visible */
  /* area, never smaller.                                   */

  if (b->display_extent < b->display_width) b->display_extent = b->display_width;

  new_extent.xmin = 0;
  new_extent.xmax = b->display_extent;

  /* Set the extent */

  e = window_set_extent(0,b->self_id,&new_extent);
  if (e) return e;

  /* Can't set extent so visible area is now outside the work area; */
  /* call wimp_open_window to make sure scroll positions are OK.    */

  {
    ObjectId    po;
    ComponentId pc;

    e = toolbox_get_parent(0, b->self_id, &po, &pc);
    if (e) return e;

    return toolbox_show_object(0, b->self_id, 1, (void *) &s.visible_area, po, pc);
  }
}

/*************************************************/
/* reformat_add_line()                           */
/*                                               */
/* Adds a line to the array of line structures.  */
/* The contents are NOT initialised.             */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the page;             */
/*                                               */
/*             Pointer to the reformat_cell      */
/*             structure to add the chunks to.   */
/*************************************************/

static _kernel_oserror * reformat_add_line(browser_data * b, reformat_cell * cell)
{
  _kernel_oserror * e;

  #ifdef TRACE
    if (tl & (1u<<8)) Printf("reformat_add_line: Called with cell %p, cell->nlines = %d\n",cell,cell->nlines);
  #endif

  /* Allocate memory for the new number of lines */

  #ifdef TRACE
    if (tl & (1u<<12)) Printf("reformat_add_line: Chunk CK_LINE set to %d\n",(cell->nlines + 1) * sizeof(reformat_line));
  #endif

  e = memory_set_chunk_size(b,
                            cell,
                            !cell->table ? CK_LINE : CK_LINV, /* Variable granularity allocation for table cells */
                            (cell->nlines + 1) * sizeof(reformat_line));
  if (e) return e;

  /* Increment the line counter in the browser_data structure */

  cell->ldata[cell->nlines].x = cell->ldata[cell->nlines].y = 0;

  cell->nlines++;

//  memset( - something! - ,0,sizeof(reformat_line));

  #ifdef TRACE
    if (tl & (1u<<8)) Printf("reformat_add_line: Successful with cell->nlines = %d\n",cell->nlines);
  #endif

  return NULL;
}

/*************************************************/
/* reformat_add_line_chunk()                     */
/*                                               */
/* Adds a line chunk to the array of line chunks */
/* associated with a particular line structure.  */
/* This is complicated slightly by the variable  */
/* length of the arrays of line chunks. The new  */
/* chunk's contents are NOT initialised.         */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the page;             */
/*                                               */
/*             Pointer to the reformat_cell      */
/*             structure to add the chunks to.   */
/*************************************************/

static _kernel_oserror * reformat_add_line_chunk(browser_data * b, reformat_cell * cell)
{
  int               size, cline = cell->nlines - 1;
  _kernel_oserror * e;

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

  /* The function can only ever add a chunk to the end of the list. */
  /* So the chunk must belong to the last line in the list, and     */
  /* if it is the first chunk for the line structure that line      */
  /* structure's pointer to the chunks will be filled in. The chunk */
  /* counter for that line is incremented.                          */

  #ifdef TRACE
    /* If there are no lines, we can't proceed - something has gone wrong */

    if (!cell->nlines)
    {
      erb.errnum = 0;

      strcpy(erb.errmess,
             "Serious internal error - There are no line structures defined in reformat_add_line_chunk(); must exit immediately.");
      show_error(&erb);
    }
  #endif

  /* If there is no chunk data already, this is the first   */
  /* chunk allocation so the block size we want to move to  */
  /* is easy to work out.                                   */

  if (!cell->cdata) size = sizeof(reformat_line_chunk);
  else
  {
    /* We want to find the current size of data and put it into size. To do this, */
    /* get the last line's offset to the chunks and add the number of chunks it   */
    /* claims to have multiplied by the size of one chunk. The complication is    */
    /* that the last line(s) may not have any chunks, so we need to go back to    */
    /* a line that does, if that is the case.                                     */

    while (!cell->ldata[cline].n && cline >= 0) cline --;

    #ifdef TRACE
      if (cline < 0)
      {
        erb.errnum = 0;

        strcpy(erb.errmess,
               "Serious internal error - No lines have associated chunks defined in reformat_add_line_chunk(); must exit immediately.");
        show_error(&erb);
      }
    #endif

    size = (cell->ldata[cline].chunks +
            cell->ldata[cline].n + 1) *
            sizeof(reformat_line_chunk);
  }

  // if (cell->cdata) Printf("!! Consistency check - Current block size for chunks  = %p\n",(void *) flex_size((flex_ptr) &cell->cdata));
  // else Printf("!! Consistency check - Current block size for chunks  = none allocated\n");
  // Printf("!!                     New block size to be allocated = %p\n",(void *) size);

  /* Now allocate the memory */

  #ifdef TRACE
    if (tl & (1u<<12)) Printf("reformat_add_line_chunk: Chunk CK_LDAT set to %d\n",size);
  #endif

  e = memory_set_chunk_size(b,
                            cell,
                            !cell->table ? CK_LDAT : CK_LDAV, /* Variable granularity allocation for table cells */
                            size);
  if (e) return e;

  /* Update the chunk counter on the last line and the base chunk */
  /* array number, if it wasn't already set (when the line is     */
  /* created, the chunks field is filled with -1. When the first  */
  /* chunk is added for the line the chunks field is filled in,   */
  /* and it is left alone subsequently - which is exactly what we */
  /* want, as the chunks field holds the base array index of the  */
  /* chunks for the line, not the last chunk array index.         */

  cline = cell->nlines - 1;
  cell->ldata[cline].n++;
  if (cell->ldata[cline].chunks < 0) cell->ldata[cline].chunks = (int) size / sizeof(reformat_line_chunk) - 1;

  /* Success */

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

  return NULL;
}

/*************************************************/
/* reformat_reformatter()                        */
/*                                               */
/* The actual working end of the reformat        */
/* routines - takes the list of HStream structs  */
/* (or tokens) and turns them into reformat_line */
/* structures for the redraw routines etc.       */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             containing all the info on the    */
/*             fetch for which reformatting is   */
/*             to take place.                    */
/*************************************************/

void reformat_reformatter(browser_data * b)
{
  reformat_reformatter_r(0, b, b->cell, b->stream);
}

/*************************************************/
/* reformat_reformatter_r()                      */
/*                                               */
/* Recursive back-end to reformat_reformatter.   */
/*                                               */
/* Parameters: Flags (see Reformat.h);           */
/*                                               */
/*             Pointer to a browser_data struct  */
/*             relevant to the reformat session; */
/*                                               */
/*             Pointer to the reformat_cell      */
/*             struct that is to be acted upon;  */
/*                                               */
/*             Pointer to the first token in the */
/*             list of HStreams to use if there  */
/*             is no evidence of a previous      */
/*             incomplete reformat session in    */
/*             the browser_data structure.       */
/*                                               */
/* Returns:    Width of the widest line that was */
/*             generated, in millipoints.        */
/*************************************************/

static int reformat_reformatter_r(unsigned int flags, browser_data * b, reformat_cell * d, HStream * streambase)
{
  int                   lnCurr        = -1;
  int                   cnCurr        = -1;
  int                   cnLast        = 0;
  _kernel_oserror     * e             = NULL;
  HStream             * tpCurr        = NULL, * tpLast   = NULL;
  HStream             * tnCurr        = NULL, * tnLast   = NULL;
  int                   bottom        = 0,      extent   = 0;
  int                   newlines      = 0,      newline  = 0;
  int                   newchunks     = 0,      newchunk = 0;
  int                   done          = 0;
  int                   linewidth     = 0;
  int                   widest        = 0;
  int                   offset        = 0;
  reformat_width_data   wd;
  int                   displaywidth, leftmargin, rightmargin;
  int                   toplevel, doall, fromstart, noalloc;

  /* Read various flags */

  toplevel  = !(flags & Reformatter_KeepGoingToEnd); /* (This may get more complex, hence 'doall' is not just '!toplevel') */
  doall     = (flags & Reformatter_KeepGoingToEnd);
  fromstart = (flags & Reformatter_FromStreamStart);

  if (doall) noalloc = (flags & Reformatter_Virtual);
  else       noalloc = 0;

  /* If there is more than one line, find the y coordinate */
  /* of the last one and put it into 'bottom'              */

  if      (d->nlines > 2)  bottom = d->ldata[d->nlines - 2].y;
  else if (d->nlines == 1) bottom = d->ldata[0].y;

  /* This y coordinate is for the last line, so it also gives the vertical extent */

  extent = bottom;

  /* Work out the current width to format to, in millipoints. */
  /* The width is the size of the visible area, but not less  */
  /* than min_width.                                          */

  if      (flags & Reformatter_FindingWidest)   displaywidth = Reformat_AsWideAsPossible_MP;
  else if (flags & Reformatter_FindingSmallest) displaywidth = 1600;
  else convert_to_points(redraw_display_width(b, d), &displaywidth);

  if (d->nlines && !noalloc)
  {
    /* If there are some lines, set up the local variables */
    /* according to the last line's contents.              */

    int i;

    /* Set lnCurr to the last line, and set cnLast to the first chunk */
    /* number on that line. Note that if the line has no chunks, the  */
    /* routine steps back one. The function will exit if it turns out */
    /* that the first line has no chunks (after all, there's nothing  */
    /* to format if this is the case!)                                */

    lnCurr = d->nlines - 1;

    while (!d->ldata[lnCurr].n && lnCurr >= 0) lnCurr --;
    if (lnCurr < 0) return 0;

    cnLast = d->ldata[lnCurr].chunks;

    /* Get the address of the HStream associated with the */
    /* first chunk of the last line into tpLast.          */

    tpLast = d->cdata[cnLast].t;

//    /* Since the current tag is be at the start of this line,   */
//    /* strip out any preceeding spaces in it (the function      */
//    /* deals with the special case of preformatted text, etc.). */
//
//    reformat_strip_prespace(b, cnCurr, tpCurr);

    /* Set 'linewidth' to the indentation requried at this line, */
    /* and add the widths of the line chunks to this.            */

    if (tpLast->style & HR) linewidth = 0;
    else                    linewidth = redraw_margin(b, d, tpLast);

    for (
          i = 0;
          i < d->ldata[lnCurr].n;
          i++, cnLast++
        )
        linewidth += d->cdata[cnLast].w;

    if (linewidth > widest) widest = linewidth;

    /* If the current line has at least 1 chunk... */

    if (d->ldata[lnCurr].n)
    {
      /* Set cnLast to now hold the number of the last chunk for the */
      /* last line, and tpCurr and tpLast to hold the address of the */
      /* token associated with the chunk.                            */

      cnLast = d->ldata[lnCurr].n + d->ldata[lnCurr].chunks - 1;
      tnCurr = tnLast = tpCurr = tpLast = d->cdata[cnLast].t;

      /* Set 'offset' to the offset into the token associated with the last */
      /* chunk in the last line, plus the amount of data to take from that  */
      /* token - i.e. it points to the first unused byte in that token,     */
      /* as far as the last chunk of the last line is concerned.            */

      offset = d->cdata[cnLast].o + d->cdata[cnLast].l;

      /* If the number of bytes into the token is <=0, then find out the */
      /* total size of data normally associated with the token (e.g. the */
      /* string length of any text it points to) and put this in offset. */

      if (d->cdata[cnLast].l <= 0) offset = reformat_datasize(tpLast);

      /* If the tag represents text, points to a text string, and that text */
      /* string terminates in a new line character, then need to start on a */
      /* new line.                                                          */

      if (
           offset > 0                       &&
           reformat_istext(tpCurr)          &&
           tpCurr->text                     &&
           tpCurr->text[offset - 1] == '\n'
         )
         newline = 1;
    }
    else newchunk = 1; /* Otherwise, flag that a chunk needs adding */
  }

  /* There are no line structures currently present; we aren't, then, */
  /* going to add to an existing line and need to flag that a new     */
  /* line must be created before chunks can be added.                 */

  else newline = 1;

  /* Loop round the reformatter whilst there is no error, the reformat */
  /* hasn't completed (we are still reformatting and the 'done' flag   */
  /* isn't set), and the number of new lines dealt with is less than   */
  /* 10 (don't want to do too much in one go, or the Desktop will      */
  /* feel very jerky) or we haven't flagged a new line is needed - the */
  /* reformat will only keep going for a certain number of lines, but  */
  /* won't stop mid-line.                                              */

  while (
          !e    &&
          !done &&
          (
            doall         ||
            newlines < 10 ||
            !newline
          )
          && reformat_formatting(b)
        )
  {
    /* tpCurr points the the token we're currently dealing with. If */
    /* this is NULL, or the offset into that token seems to point   */
    /* past its associated data, we've finished with the token. So  */
    /* move onto another, or if there are no more, set 'done' to 1. */

    if (!tpCurr || offset >= reformat_datasize(tpCurr))
    {
      /* Record the last token that was dealt with in the browser_data structure */

      if (!fromstart) b->last_token = tnLast;

      /* Advance to the next token after that last one */

      if (tpLast) tnCurr = tnLast->next;
      else        tnCurr = streambase;

      /* Proceed if we haven't just run out of tokens */

      if (tnCurr && (tnCurr->flags & HFlags_DealtWithToken))
      {
        tpCurr = tnCurr;

        /* If this token isn't of any use to the reformatter (it */
        /* might correspond to header information rather than    */
        /* body text, say), keep getting new tokens until they   */
        /* are useful or we run out. If we run out, set 'done'   */
        /* again to show that the tokens have all been dealt     */
        /* with.                                                 */

        while (tpCurr && reformat_useless_token(tpCurr))
        {
          if (!fromstart) b->last_token = tpCurr; /* Update the record of the last token dealt with */
          tnCurr = tpCurr = tpCurr->next;         /* Get the pointer to the new token               */
        }

        /* If we've ended up off the end of the token list (tpCurr is */
        /* NULL) or on a token that the fetcher hasn't dealt with yet */
        /* (the HFlags_DealtWithToken bit in the flags word is unset) */
        /* then signal that the reformatter should exit by setting    */
        /* 'done' to 1.                                               */

//        if (
//             !tpCurr ||
//             (
//               tpCurr && !(tpCurr->flags & HFlags_DealtWithToken)
//             )
//           )
//
//           done = 1;
if (!tpCurr) done = 1;

        /* Otherwise, can proceed with processing this token. */

//        // If a tag consists of a single space and nothing more,
//        // and is a link, clear it's link status - this gets
//        // around the underscores before some images, and is a
//        // temporary fix until the HTML library spacing rules
//        // get sorted out.
//
//        if (tpCurr && reformat_istext(tpCurr) && tpCurr->text)
//        {
//          if (*tpCurr->text == 32 && !*(tpCurr->text + 1) && ISLINK(tpCurr)) tpCurr->style &= ~A;
//        }

        /* Offset is set to 0 to show this is a new token, and we haven't */
        /* dealt with any of the data in it yet.                          */

        offset = 0;
      }

      /* The current token number was greater than the number of tokens - */
      /* we've run out of tokens, so flag that this in 'done'.            */

      else done = 1;
    }

    /* Continue with the reformat if it hasn't been flagged as finished through 'done'. */

    if (!done)
    {
      /* The left hand margin - zero for horizontal rules, which can span the whole   */
      /* display (their actual visible extent is determined by the redraw routine);   */
      /* else equal to the result of redraw_margin in Redraw.c (converted to points). */

      if (tpCurr->style & HR) leftmargin = 0;
      else                    leftmargin = redraw_margin(b, d, tpCurr);

      /* The right hand margin - zero for horizontal rules, which can span the whole   */
      /* display (their actual visible extent is determined by the redraw routine);    */
      /* same as the left margin for block quotations; else the right margin quantity. */

      if (!(tpCurr->style & HR))
      {
        if (tpCurr->style & BLOCKQUOTE) rightmargin = leftmargin;
        else                            rightmargin = redraw_right_margin(b, d);
      }
      else rightmargin = 0;

      /* If the text is not preformatted and the line width has gone over */
      /* over the available display width, flag that a new line is needed */

      if (!newline && !(tpCurr->style & PRE) && linewidth > displaywidth - rightmargin) newline = 1;

      /* It generally looks better if there's a line break for tables, */
      /* though this is strictly not necessary.                        */

      if (!newline && (tpCurr->tag == TABLE && ISBODY(tpCurr))) newline = 1;

      /* If the difference between the current and last tags say we should */
      /* put in a line break, flag this in newline.                        */

      if (!newline && reformat_newline(tpCurr,tpLast,offset)) newline = 1;

      if (newline)
      {
        int y = 0;

        newline  = 0;
        newchunk = 1;
        newlines ++;

        if (!noalloc)
        {
          /* Set y to hold the y coordinate the line is to be placed */
          /* at. This will either be determined by the previous      */
          /* line's y coordinate or if there are no lines, the       */
          /* toolbars and aesthetic lead-in considerations. The line */
          /* at this stage is of zero height; as it's height grows,  */
          /* the y value worked out here has the height subtracted   */
          /* from it to get the actual line position.                */

          if (lnCurr >= 0) y = d->ldata[lnCurr].y;

          /* (The 4 is to allow for the window frame of the toolbars) */

          else y = toplevel ? reformat_y_offset(b) : 0;

          /* If there aren't any new chunks, the bottom line hasn't */
          /* changed so set bottom to y; this stops the unchanged   */
          /* line being redrawn.                                    */

          if (!newchunks) bottom = y;

          /* Add the new line structure */

          e = reformat_add_line(b, d);
        }
        else e = NULL;

        /* If the addition was successful, fill in various details */

        if (!e)
        {
          tpCurr = tnCurr;
          tpLast = tnLast;

          if (!noalloc)
          {
            /* Advance the current line number in lnCurr to this new line */

            lnCurr = d->nlines - 1;

//            /* Set the new line to have a default minimum height of 3 OS units, */
//            /* and its bottom y coordinate must therefore be the bottom of the  */
//            /* page as worked out so far (in 'y') minus this height.            */
//
//            d->ldata[lnCurr].h = 3;
//            d->ldata[lnCurr].y = y - 3;
//
//            /* The 'b' field holds the y offset of the font baseline from the */
//            /* bottom of the line - this is filled in somewhat arbitrarily.   */
//
//            d->ldata[lnCurr].b = 1;

            d->ldata[lnCurr].h = 0;
            d->ldata[lnCurr].y = y;
            d->ldata[lnCurr].b = 0;

            /* The line has no chunks associated with it yet. */

            d->ldata[lnCurr].n      = 0;
            d->ldata[lnCurr].chunks = -1;
          }

          /* In light of this new line, set linewidth back to just the left margin,  */
          /* as there are no previous chunks to consider in working out chunk widths */
          /* in the add chunk code below.                                            */

          linewidth = leftmargin;

          if (linewidth > widest) widest = linewidth;
        }

      /* End of newline check; if newline != 0, a new line is added to the line structure array. */
      }

      /* If there's no error, continue with the reformat procedure. */

      if (!e)
      {
        /* Fill in the reformat_width_data structure */

        wd.b      = b;
        wd.tp     = tpCurr;
        wd.data   = fetch_token_data_address(b, tpCurr);
        wd.offset = offset;

        /* The maximum width is 'very large' for preformatted text (effectively limitless).  */
        /* For other tokens, the left hand edge is at an indent equal to the left hand       */
        /* margin plus the summed widths of preceeding chunks (this is kept in 'linewidth'), */
        /* so the width is the available display width minus this left hand value.           */

        wd.maxwid = (
                      (
                        (tpCurr->style & PRE) &&
                        reformat_istext(tpCurr)
                      )
                    )

                    ?

                    Reformat_AsWideAsPossible_MP : displaywidth - linewidth;

        wd.maxwid -= rightmargin;

        /* Set the (returned) bytes and width fields to zero initially */

        wd.bytes = wd.width = 0;

        /* Find out the width */

        e = reformat_token_width(&wd, flags);

        /* Adjust the line height for tables based on */
        /* the data from the above call               */

        if (tpCurr->tag == TABLE && ISBODY(tpCurr))
        {
          if (!noalloc)
          {
            convert_to_os(wd.bytes, &d->ldata[lnCurr].h);

            d->ldata[lnCurr].y -= d->ldata[lnCurr].h;
          }

          wd.bytes = 0;
        }

        /* If the chunk width'd and defined by the above call ends in a newline character, */
        /* flag a line break is needed through newline = 1.                                */

        else if (reformat_istext(tpCurr) && wd.bytes && wd.data[wd.offset + wd.bytes - 1] == '\n') newline = 1;
      }

      /* Proceed if there is no error, to add a chunk. If the current line has no chunks, */
      /* then one will always be added. If there are already chunks present, another is   */
      /* added only if it will fit in the display (wd.width is less than wd.maxwid), or   */
      /* if the text is preformatted (in which case you keep adding chunks until there's  */
      /* a line break in the source).                                                     */

      if (
           !e &&
           (
             newchunk              || /* Will be set if current line has no chunks yet */
             wd.width <= wd.maxwid ||
             (tpCurr->style & PRE)
           )
         )
      {
        newchunk = 0;
        newchunks ++;

        if (!noalloc) e = reformat_add_line_chunk(b, d);
        else          e = NULL;

        /* Proceed if the above didn't return an error */

        if (!e)
        {
          tpCurr = tnCurr;
          tpLast = tnLast;

          if (!noalloc)
          {
            /* Set cnCurr to the array index of the last (i.e new) chunk; */
            /* we know that if a new chunk has been added the last line   */
            /* must have at least the one chunk now, so no need for all   */
            /* the checking for various cases of lines not having chunks  */
            /* that has gone on elsewhere.                                */

            cnCurr = d->ldata[d->nlines - 1].chunks + d->ldata[d->nlines - 1].n - 1;

            /* Fill in the new line chunk */

            d->cdata[cnCurr].t = tpCurr;
            d->cdata[cnCurr].w = wd.width;
            d->cdata[cnCurr].o = offset;
            d->cdata[cnCurr].l = wd.bytes;

            /* If this holds and image, need to invalidate the x and y position */
            /* information that the redraw routines set up, as it may have      */
            /* moved. The new position will be set when the reformatted         */
            /* region is next redrawn.                                          */

            if (tpCurr->style & IMG ||
                ((tpCurr->style & INPUT) && HtmlINPUTtype(tpCurr)==inputtype_IMAGE))
              image_set_token_image_position(b, tpCurr, -1, -1);

            /* If the image has a known width and height, the reformatter has */
            /* dealt with it - so the image library can mark it as redrawable */
            /* now (otherwise, for delayed reformats, some images which were  */
            /* cross referenced and did not have reformat sessions explicitly */
            /* started for them, may get stuck in a 'non-redrawable' state).  */

            image_token_check_redrawable(b, tpCurr);
            #ifdef TRACE
              if ((tl & (1u<<20)) && tpCurr->tag == TABLE && ISBODY(tpCurr)) Printf("reformat_reformatter_r: Added a table\n");
            #endif

//            if (d->ldata[d->nlines - 1].n == 1)
//            {
//              /* Since the current chunk will be at the start of a new line, */
//              /* strip out any preceeding spaces in it (the function deals   */
//              /* with the special case of preformatted text, etc.).          */
//
//              reformat_strip_prespace(b, cnCurr, tpCurr);
//            }

//            if (d->ldata[d->nlines - 1].n == 1)
//            {
//              if (tpCurr->text && !(tpCurr->style & PRE))
//              {
//                while (*(tpCurr->text + d->cdata[cnCurr].o) == ' ' && d->cdata[cnCurr].l) d->cdata[cnCurr].o++, d->cdata[cnCurr].l--, offset++;
//              }
//            }


            /* Set the Visited flag if this token is a link and is in the global History */

            reformat_check_visited(b, tpCurr);

            /* Ensure the line's height details are updated in light of the new chunk */

            e = reformat_check_height(toplevel, b, d, lnCurr, tpCurr, tpLast, offset);
          }
          else e = NULL;

          if (!e)
          {
            int w;

            /* Advance through the token data */

            if (wd.bytes <= 0) offset = reformat_datasize(tpCurr) + 1;
            else offset += wd.bytes;

            /* Make the last poken pointer is now updated */

            tnLast = tnCurr;
            tpLast = tpCurr;

            /* Add the chunk's width to the line width total */

            linewidth += wd.width;

            if (linewidth > widest) widest = linewidth;

            /* Convert the width to OS units and compare to the minimum width for */
            /* the browser so far. If the width as gone up, i.e. there is an      */
            /* enforced minimum for whatever reason (preformatted text, say) then */
            /* update the minimum width field appropriately.                      */

            convert_to_os(linewidth, &w);

            if (linewidth >= displaywidth - rightmargin)
            {
              newline = 1;
              if (!d->table && w > b->display_extent) b->display_extent = w;
            }

            /* If offset is still within the token data, there must have been a */
            /* line split point in there so we need another chunk.              */

            if (offset < reformat_datasize(tpCurr)) newline = 1;
          }
        }
      }
      else newline = 1;
    }
  }

  /* If there has been an error, stop the reformat and report it */

  if (e)
  {
    reformat_stop(b);
    show_error_ret(e); /* This call returns to this point rather than jumping to the poll loop */
  }
  else
  {
    browser_data * ancestor = b->ancestor;

    if (!ancestor) ancestor = b;

    /* For keyboard control, try to find something to select. */
    /* Browser windows that have children can't have visible  */
    /* tokens, so don't bother for them, and for anything     */
    /* with a selected item, check that it has't acquired     */
    /* children over the reformat - if it has, move the       */
    /* selection out of there.                                */

    if (choices.keyboardctl)
    {
      /* Clear the selection if the owner has acquired children */

      if (ancestor->selected)
      {
        browser_data * owner = ancestor->selected_owner;

        if (owner && owner->nchildren) browser_clear_selection(owner, 0);
      }

      /* If there is no selection (whether this is due to the */
      /* above code or not), and the current browser has no   */
      /* children, try to select something in it.             */

      if (!ancestor->selected && !b->nchildren)
      {
        ancestor->selected = browser_find_first_selectable(b, NULL, 0);

        if (ancestor->selected) ancestor->selected_owner = b;
      }
    }
  }

  /* Make sure the window is the right size and ensure */
  /* that the altered regions are redrawn, unless      */
  /* we are printing (so the reformat doesn't actually */
  /* correspond to any real window).                   */

  if (toplevel && !printing)
  {
    reformat_check_extent(b);
    browser_update_bottom(b, bottom + 4);
  }

  /* Return the width of the widest line generated in this session, in millipoints */

  return widest;
}

/*************************************************/
/* reformat_format_cell()                        */
/*                                               */
/* Reformats a specific table cell to a given    */
/* width.                                        */
/*                                               */
/* Parameters: 1 for a top level call, 0 if      */
/*             being called as part of a nested  */
/*             table parse;                      */
/*                                               */
/*             Pointer to a browser_data struct  */
/*             relevant to the table;            */
/*                                               */
/*             Pointer to the first HStream      */
/*             structure in the stream that will */
/*             be formatted into the cell;       */
/*                                               */
/*             Pointer to the table_stream       */
/*             struct representing the table;    */
/*                                               */
/*             Pointer to the table's array of   */
/*             reformat_cell structures;         */
/*                                               */
/*             Allowed cell (column) width, in   */
/*             millipoints;                      */
/*                                               */
/*             Row number of the cell;           */
/*                                               */
/*             Column number of the cell.        */
/*                                               */
/* Returns:    Actual final cell width in        */
/*             millipoints.                      */
/*************************************************/

int reformat_format_cell(int toplevel, browser_data * b, HStream * streambase, table_stream * table,
                         reformat_cell * cellarray, int ColWidth, int Row, int Column)
{
  /* in actual fact table_stream * is a synonym for HStream * */
  /* when we hit a table -                                    */
  /* we call tables_count_table(),tables_position_table()     */
  /* then tables_width_table()                                */
  /* this will call back here to find some answers...         */
  /* in practice this now means that we need to do a reformat */
  /* of the HStream structure using a new nlines,ldata,cdata  */
  /* oh joy                                                   */
  /* stuff therefore is an array of nlines,ldata,cdata        */

  int             dheight;
  int             cellindex = Row * table->ColSpan + Column;
  reformat_cell * c;

  /* Can't do anything if the cell index is out of range */

  if (cellindex >= table->RowSpan * table->ColSpan) return 1600;
  else c = &cellarray[cellindex];

//  /* Try to run the tokens through the fetcher so images etc. */
//  /* can work.                                                */
//
//  {
//    HStream * list = streambase;
//
//    while (list)
//    {
//      // !! THIS WON'T HANDLE FORMS CORRECTLY !! //
//
//      if (!(list->flags & HFlags_DealtWithToken)) fetch_preprocess_token(b, list);
//      list = list->next;
//    }
//  }

  #ifdef TRACE
    if (tl & (1u<<20))
    {
      Printf("tables_width_cell: %p %d %d %d %d\n",streambase,ColWidth,table->ColSpan,Row,Column);
      Printf("tables_width_cell in: %d\n", ColWidth / 400);
    }
  #endif

  /* Format the cell to the specified width. If 'toplevel' is 0, this is */
  /* being called as part of a format for a parent table, so don't       */
  /* generate lines (flag Virtual in the reformatter). Otherwise, allow  */
  /* line generation (don't flag Virtual).                               */

  c->width = c->cellwidth = ColWidth;

  reformat_reformatter_r(Reformatter_KeepGoingToEnd            |
                         Reformatter_FromStreamStart           |
                         (toplevel ? Reformatter_Virtual : 0),

                         b,
                         c,
                         streambase);

  convert_to_points(reformat_return_extent(b, c), &dheight);
  c->height = dheight;

  #ifdef TRACE
    if (tl & (1u<<20))
    {
      Printf("width found is %d\n", c->width);
      Printf("height found is %d\n",c->height);
    }
  #endif

  return c->width;
}

/*************************************************/
/* reformat_find_cell_limits()                   */
/*                                               */
/* Works out the narrowest and widest widths a   */
/* given table cell could be, in millipoints.    */
/*                                               */
/* Parameters: 1 for a top level call, 0 if      */
/*             being called as part of a nested  */
/*             table parse;                      */
/*                                               */
/*             Pointer to a browser_data struct  */
/*             relevant to the table;            */
/*                                               */
/*             Pointer to the first HStream      */
/*             structure in the stream that the  */
/*             cell is to contain;               */
/*                                               */
/*             Pointer to the table_stream       */
/*             struct representing the table;    */
/*                                               */
/*             Pointer to the table's array of   */
/*             reformat_cell structures;         */
/*                                               */
/*             Row number of the cell;           */
/*                                               */
/*             Column number of the cell;        */
/*                                               */
/*             Pointer to an int, in which the   */
/*             minimum width is returned;        */
/*                                               */
/*             Pointer to an int, in which the   */
/*             maximum width is returned;        */
/*                                               */
/* Returns:    See parameters list.              */
/*************************************************/

void reformat_find_cell_limits(int toplevel, browser_data * b, HStream * streambase, table_stream * table,
                               reformat_cell * cellarray, int Row, int Column, int * retmin, int * retmax)
{
  int             maxwidth  = 0;
  int             minwidth  = 0;
  int             cellindex = Row * table->ColSpan + Column;
  reformat_cell * c;

  /* Can't find the limits of something outside the cell range */
  /* of the table...                                           */

  if (cellindex >= table->RowSpan * table->ColSpan)
  {
    if (retmin) *retmin = 1600;
    if (retmax) *retmax = 1600;

    return;
  }
  else c = &cellarray[cellindex];

//  /* Try to run the tokens through the fetcher so images etc. */
//  /* can work.                                                */
//
//  {
//    HStream * list = streambase;
//
//    while (list)
//    {
//      // !! THIS WON'T HANDLE FORMS CORRECTLY !! //
//
//      if (!(list->flags & HFlags_DealtWithToken)) fetch_preprocess_token(b, list);
//      list = list->next;
//    }
//  }

  /* Find the maximum width used by the cell; first, reformat */
  /* to a 'large width' (effectively, no line breaks).        */

  maxwidth = reformat_reformatter_r(Reformatter_KeepGoingToEnd  |
                                    Reformatter_FindingWidest   |
                                    Reformatter_FromStreamStart |
                                    Reformatter_Virtual,

                                    b,
                                    c,
                                    streambase);

  #ifdef TRACE
    if (c->nlines || c->ldata || c->cdata)
    {
      erb.errnum = Utils_Error_Custom_Normal;

      strcpy(erb.errmess,"Line or chunk data allocated inside reformat_find_cell_limits for maxwidth check");

      show_error_ret(&erb);
    }
  #endif

  /* Find the minimum width */

  minwidth = reformat_reformatter_r(Reformatter_KeepGoingToEnd  |
                                    Reformatter_FindingSmallest |
                                    Reformatter_FromStreamStart |
                                    Reformatter_Virtual,

                                    b,
                                    c,
                                    streambase);

  #ifdef TRACE
    if (c->nlines || c->ldata || c->cdata)
    {
      erb.errnum = Utils_Error_Custom_Normal;

      strcpy(erb.errmess,"Line or chunk data allocated inside reformat_find_cell_limits for minwidth check");

      show_error_ret(&erb);
    }
  #endif

  // Should do the CELLPADDING stuff here, but where is the actual table
  // definition HStream? The TableStream passed in is just the head of
  // the data stream, not the definition.

  // This would be an 'else' to having no cellpadding info.

//  {
//    /* Default 8 OS unit padding for aesthetics */
//
//    minwidth += 3200;
//    maxwidth += 3200;
//  }

  if (retmin) *retmin = minwidth;
  if (retmax) *retmax = maxwidth;
}

/*************************************************/
/* reformat_change_text()                        */
/*                                               */
/* Used to alter text in a tag, to provide (for  */
/* example) smart quotes handling.               */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             with information on the fetch     */
/*             Pointer to a token to alter.      */
/*                                               */
/* Assumes:    The browser_data struct may not   */
/*             be NULL, but the token pointer    */
/*             can be and the token does not     */
/*             have to contain text.             */
/*************************************************/

void reformat_change_text(browser_data * b, HStream * tp)
{
  return;

  if (tp && tp->text && !(tp->style & TT) && (reformat_istext(tp)))
  {
    char * curr = tp->text;

    while (*curr)
    {
      if (*curr == '`') *curr = 148; /* Always make this quote an opening quote */

      if (*curr == '\'') /* Dumb single quote */
      {
        if   (b->lastchar == ' '
           || b->lastchar == '('
           || b->lastchar == 148  /* Opening double quote */
           || b->lastchar == '\"')

             *curr = 144; /* Opening single quote */
        else *curr = 145; /* Closing single quote */
      }
      else if (*curr == '\"') /* Dumb double quote */
      {
        if   (b->lastchar == ' '
           || b->lastchar == '('
           || b->lastchar == 144  /* Opening single quote */
           || b->lastchar == '\''
           || b->lastchar == '`')

             *curr = 148; /* Opening double quote */
        else *curr = 149; /* Closing double quote */
      }
      else if (*curr == '-' && b->lastchar == ' ') *curr = 151; /* 'en' dash */
//      else if (*curr == '-' && b->lastchar == 151) memmove - something!

      b->lastchar = *curr;
      curr ++;
    }
  }

  /* If we have ALT text for an image, strip off any preceeding */
  /* spaces or [s, and any trailing spaces or ]s.               */

  else if (tp && tp->text && ((tp->style & IMG) || ((tp->style & INPUT) && HtmlINPUTtype(tp) == inputtype_IMAGE)))
  {
    char * start, * end;
    char   last;
    int    len;

    len = strlen(tp->text);

    /* Get rid of preceeding characters */

    start = tp->text;
    end   = tp->text + len - 1;

    while (*start == ' ' || *start == '[') start ++;

    /* If there's anything left... */

    if (*start)
    {
      /* Get rid of trailing characters */

      while (*end == ' ' || *end == ']')
      {
        *end = '\0';
        end --;
      }
    }

    /* If there's still something left, move the */
    /* string contents down so tp->text points   */
    /* past the stripped preceeding chracters.   */

    if (start <= end) memmove(tp->text, start, strlen(start) + 1); /* 'strlen + 1' to get the string terminator */
    else
    {
      /* If there was nothing left, did we originally have     */
      /* enough to put '[]' to mark that there's no text left? */

      if (len > 1) strcpy(tp->text,"[]");
    }

    /* Now do smart quotes substitution. Need to do this */
    /* separately from the main text routines as the     */
    /* lastchar variable must not be changed by ALT text */
    /* - it stands alone for each image.                 */

    last = ' ';
    start = tp->text;

    while (*start)
    {
      if (*start == '\'' || *start == '`') /* Dumb single quote */
      {
        if   (last == ' '
           || last == '('
           || last == 148  /* Opening double quote */
           || last == '\"')

             *start = 144; /* Opening single quote */
        else *start = 145; /* Closing single quote */
      }
      else if (*start == '\"') /* Dumb double quote */
      {
        if   (last == ' '
           || last == '('
           || last == 144  /* Opening single quote */
           || last == '\''
           || last == '`')

             *start = 148; /* Opening double quote */
        else *start = 149; /* Closing double quote */
      }
      else if (*start == '-' && last == ' ') *start = 151; /* 'en' dash */

      last = *start;
      start ++;
    }
  }
}

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