/* 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: Functions to handle page reformatting. */
/*                                                 */
/* 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" /* (Which itself includes URLstat.h) */
#include "FetchPage.h"
#include "FontManage.h"
#include "Forms.h"
#include "History.h"
#include "Images.h"
#include "Memory.h"
#include "Object.h"
#include "Redraw.h"
#include "Tables.h"
#include "Toolbars.h"

#include "Reformat.h"

/* Local constant definitions */

#define BULLET_GAP 12

/* Static function prototypes */

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_text_line_height (browser_data * b, HStream * tp, int * top, int * bot);
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);

/* Local compilation options */

#undef SUPPORT_NOBR

// 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)
{
//  if (b->suspend_format) Printf("reformat_formatting: %p suspend_format set\n",b);
//  else if (b->last_token)
//  {
//    if (!b->last_token->next) Printf("reformat_formatting: %p b->last_token->next = NULL\n",b);
//    else if (b->last_token->next && !(b->last_token->next->flags & HFlags_DealtWithToken)) Printf("reformat_formatting: %p last_token->next HFlags NULL\n",b);
//    else if (b->cell->nlines <= 0) Printf("reformat_formatting: %p will return b->final_token != NULL: %d\n",b,b->final_token != NULL);
//    else Printf("reformat_formatting: %p will return 1\n",b);
//  }
//  else if (b->cell->nlines <= 0) Printf("reformat_formatting: %p will return b->final_token != NULL: %d\n",b,b->final_token != NULL);
//  else Printf("reformat_formatting: %p will return 1\n",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).                                 */
    /*                                                            */
    /* The check for a 'next' token is done as final_token only   */
    /* records main token stream tokens dealt with by the         */
    /* preprocessor. It is possible for a precise set of          */
    /* circumstances to leave last_token and final_token          */
    /* equivalent due to table parsing, though there is more data */
    /* in the main token stream after it (this happened during    */
    /* testing, so it isn't all that rare).                       */

    if (!b->last_token->next) return 0;

    /* If the token after the last one dealt with by the   */
    /* fetcher had not been looked at by the preprocessor, */
    /* the reformatter must have exitted for that reason.  */

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

  /* If there are no lines, are there any HStream structures? */
  /* If so, the preprocessor has got stuff from the fetcher   */
  /* but the reformatter hasn't built anything with them yet. */

  if (b->cell->nlines <= 0) return (b->final_token != 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->tagno != TAG_TABLE          &&
           !ISOBJECT(tp)                   &&
           !
           (
             tp->tagno         == TAG_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);       */
/*                                               */
/*             A flags word, as ppassed to       */
/*             reformat_reformatter.             */
/*                                               */
/* Returns:    Fills in the structure's 'width'  */
/*             and 'bytes' fields with width and */
/*             byte count information.           */
/*************************************************/

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->tagno == TAG_TABLE)
  {
    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);

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

    #ifdef TRACE

      if (tl & (1u<<20))
      {
        Printf("reformat_token_width: Size of table is %d\n",size);
        if (!size) Printf("                      (Table has no cells...)\n");
      }

    #endif

    if (size) 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

    /* If the table has no cells, may need to clear an existing cell array */

    if (!size)
    {
      if (table->cells) HtmlFree(table->cells);

      table->ncells = 0;
    }
    else
    {
      /* If we've got an existing cell array and the number of cells has */
      /* not changed, don't need to do anything. Otherwise, (re)allocate */
      /* the cell array.                                                 */

      if (!table->cells || table->ncells != size)
      {
        reformat_cell * old_cells = table->cells;

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

        table->cells = 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) show_error_cont(make_no_table_memory_error(9));

        /* Otherwise, initialise the cell contents */

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

        /* Copy over any old cells ('ncells' still holds the old number) */

        if (table->ncells && old_cells)
        {
          memcpy(table->cells, old_cells, table->ncells * sizeof(reformat_cell));

          /* Free the old cell array */

          HtmlFree(old_cells);
        }

        /* Now update 'ncells' */

        table->ncells = size;
      }
    }

    cellarray = table->cells;

    if (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, flags);
      w->bytes = tables_height_table(toplevel, w->b, table,            cellarray);

      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->tagno == TAG_SELECT   ||
            w->tp->tagno == TAG_TEXTAREA ||
            w->tp->tagno == TAG_INPUT
          )
  {
    if (
         w->tp->tagno         == TAG_SELECT         ||
         w->tp->tagno         == TAG_TEXTAREA       ||
         HtmlINPUTtype(w->tp) == inputtype_PASSWORD ||
         HtmlINPUTtype(w->tp) == inputtype_TEXT
       )
    {
      int h;
      int done = 0;
      int length;

      if (w->tp->tagno == TAG_TEXTAREA)
      {
        /* Text areas */

        length = w->tp->cols;

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

        done = 0;
      }
      else if (w->tp->tagno == TAG_SELECT)
      {
        /* SELECT elements (have pop-up menus) */

        int    width, l;
        int    extra = 0;
        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);

        /* Need to find out the actual used width, or we end up */
        /* generally overestimating the required size.          */

        p      = (char *) HtmlSELECToptions(w->tp) + 8;
        h      = fm_find_token_font(w->b, w->tp, 0);
        l      = 0;
        width  = 0;
        length = 0; /* Not used, but stops compiler giving warnings */

        /* Some fields can hold the selNONE or selMANY text, as well */
        /* as the menu item texts.                                   */

        if (HtmlSELECTmultiple(w->tp) || *p == 0xff)
        {
          e = fm_get_string_width(h,
                                  lookup_token("selNONE:<None>",0,0),
                                  Reformat_AsWideAsPossible_MP,
                                  strlen(tokens),
                                  -1,
                                  NULL,
                                  &width);

          if (!e && width > l) l = width;
        }

        if (HtmlSELECTmultiple(w->tp))
        {
          e = fm_get_string_width(h,
                                  lookup_token("selMANY:<Many>",0,0),
                                  Reformat_AsWideAsPossible_MP,
                                  strlen(tokens),
                                  -1,
                                  NULL,
                                  &width);

          if (!e && width > l) l = width;
        }

        /* Look for the width of the widest OPTION field */

        while (*p != 0xff)
        {
          p++;

          e = fm_get_string_width(h,
                                  p,
                                  Reformat_AsWideAsPossible_MP,
                                  strlen(p),
                                  -1,
                                  NULL,
                                  &width);

          if (e)
          {
            erb   = *e, e = &erb;
            width = 16000; /* Arbitrary! Give it 40 OS units on error */
            break;
          }

          /* Record if this is the widest and skip to the next field */

          if (width > l) l = width;

          p += strlen(p) + 1;
          p += strlen(p) + 1;
        }

        /* Set the width and flag that we've done the calculation */
        /* already so code lower down doesn't try.                */

        w->width = l + extra;
        done     = 1;
      }
      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;
        }

        done = 0;
      }

      /* Some cases above may have worked out the width already. */
      /* For those that haven't, do it on the basis of 'length', */
      /* holding the maximum number of characters, and the       */
      /* width of the widest character possible in the item.     */

      if (!done)
      {
        BBox box;

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

        /* Finding the width based on font BBox multipled by the */
        /* field length usually is a significant overestimation, */
        /* so use a number instead - this is typically closer.   */

        h = fm_find_token_font(w->b, w->tp, 0);

        e = fm_char_box(h, '0', &box);

        if (e) erb = *e, e = &erb;
        else
        {
          w->width = ((box.xmax - box.xmin) * length) + 20;

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

      w->bytes = 0;

      return e;
    }
    else switch(HtmlINPUTtype(w->tp))
    {
      case inputtype_SUBMIT: /* No break - same as RESET */
      case inputtype_BUTTON: /* Again, no break          */
      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(w->b, w->tp, 0);
        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;

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

  /* Handle OBJECT, EMBED and APPLET tags */

  else if (ISOBJECT(w->tp))
  {
    RetError(reformat_get_object_size(w->b, w->tp, &box));

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

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

  /* Handle images */

  else if (w->tp->style & IMG)
  {
do_image: /* Used by switch statement above */

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

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

    RetError(reformat_get_image_size(w->b, w->tp, &box));

    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, unless the HR specifies a     */
    /* particular width directly.                                 */
    /*                                                            */
    /* 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 (!HR_HAS_WIDTH(w->tp) || HR_WIDTH_UNITS(w->tp) == UNITS_PERCENT)
    {
      if (
           !(flags & Reformatter_FindingWidest)   &&
           !(flags & Reformatter_FindingSmallest)
         )
      {
        if (HR_HAS_WIDTH(w->tp)) w->width = (w->maxwid * HR_WIDTH(w->tp)) / 100;
        else                     w->width = w->maxwid;
      }

      else w->width = 0;
    }
    else
    {
      w->width = HR_WIDTH(w->tp) * 2; /* 1 'web pixel' = 2 OS units */

      convert_to_points(w->width, &w->width);
    }
  }
  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;

    /* Can't do anything if there's no text */

    if (!w->data || !*w->data)
    {
      w->bytes = 0;
      w->width = 0;
    }
    else
    {
      /* 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 (w->data[end] && 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(w->b, w->tp, 0);

      /* 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 (w->data[end] && w->data[w->offset + w->bytes] == '\n') w->bytes++;
    }
  }

  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)
{
  if (ISHEAD(tp)) return 1;
  if (ISNULL(tp)) return 1;

  if (tp->flags & HFlags_IgnoreObject) return 1;

  if (
       ISBODY(tp)   &&
       !tp->tag     &&
       !tp->tagno   &&
       !tp->text    &&
       !tp->anchor  &&
       !tp->src     &&
       !tp->enctype &&
       !tp->name    &&
       !tp->value   &&
       !tp->target  &&
       (
         tp->style == 0x00000000 ||
         tp->style == 0x80000000
       )
     )
     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)
{
  /* It generally looks better if there's a line break for tables, */
  /* though this is theoretically not necessary. In practice you   */
  /* find various line spacing and redraw problems without the     */
  /* break - this may get sorted one day...!                       */

  if (current->tagno == TAG_TABLE || last->tagno == TAG_TABLE) return 9;

  /* 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 we have a paragraph break but the last item was a <li> tag, and we are */
  /* at the top of the line, then HTML like '<ul><li><p>Text' is being used -  */
  /* we should ignore the <p> tag. The position of this line of code relative  */
  /* to other checks in this function is obviously quite important (so be      */
  /* careful with it!)...                                                      */

  if (!offset && (current->style & P) && (last->style & LI)) return 0;

  /* 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 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)
{
  /* Is this a text item? */

  if (!reformat_istext(tp)) 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 */
/*             (inclusive);                      */
/*                                               */
/*             The line number at which to end   */
/*             (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;
  reformat_cell           * cell = b->cell;
  int                       line;
  WimpGetWindowStateBlock   s;

  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) line = start, start = end, end = line;

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

  /* Alter the lines' y-coordinates */

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

  /* Have to force a redraw of the entire page to ensure */
  /* that image positions are updated                    */

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

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

  return wimp_force_redraw(s.window_handle,
                           s.visible_area.xmin,
                           s.visible_area.ymin,
                           s.visible_area.xmax,
                           s.visible_area.ymax);
}

/*************************************************/
/* 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.                  */
/*                                               */
/* The defined way to ensure all line and chunk  */
/* data for a given browser is freed, including  */
/* that tied up in tables, is by calling with    */
/* the browser_data struct, -1, 1, and -1 (see   */
/* parameters list below).                       */
/*                                               */
/* 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? Yes if told to in the Choices,  */
  /* in the function parameters, or if we're showing an external */
  /* image (want to see that come in progressively, not wait for */
  /* a reformat which is in this particular case completely      */
  /* trivial anyway).                                            */

  if (
       !choices.refo_wait ||
       immediate          ||
       b->displayed == Display_External_Image
     )
  {
    reformat_format_from_now(b, lastline);
  }
  else
  {
    if (b->refo_time)
    {
      /* 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->refo_line) b->refo_line = 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->refo_time);

      if (e) return e;

      b->refo_line = 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            */
/* 'refo_time' 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->refo_time)
  {
    handle->refo_time = 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->refo_time > choices.refo_time)
  {
    /* reformat_format_from_now will deregister this handler */

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

  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->refo_time)
  {
    if (b->refo_line < lastline) lastline = b->refo_line;

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

    /* Update the status bar */

    toolbars_update_status(b, Toolbars_Status_Formatting);
  }

  /* Ensure the pointer shape is OK */

  browser_pointer_check(0, NULL, NULL, b);

  /* 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->refo_time) deregister_null_claimant(Wimp_ENull, (WimpEventHandler *) reformat_format_timer, b);

  b->refo_time = 0;
  b->refo_line = Reformat_UnrealisticallyHighLineNumber;
}

/*************************************************/
/* 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.     */
/*                                               */
/* 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;
  fm_face           h;
  BBox              hbox;
  int               height = 0;

  /* We can't easily find out how high text is surrounding */
  /* the image for align=top, align=middle or whatever,    */
  /* but we can do a reasonable guess based on whatever    */
  /* font the given token would use if it held text.       */

  h = fm_find_token_font(NULL, tp, 0);

  if (h)
  {
    if (!fm_font_box(h, &hbox)) height = hbox.ymax - hbox.xmin;

    fm_lose_font(NULL, h);
  }

  /* 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:
    {
      int hh = height / 3; /* Technically '/ 2', but '/ 3' looks, on average, better in practice */

      box->ymin -= box->ymax / 2;
      box->ymax /= 2;

      box->ymin += hh;
      box->ymax += hh;
    }
    break;

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

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

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

    b = tp->maxlen * 2;

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

  return NULL;
}

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

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

  /* Get the Object size from the Object library */

  RetError(object_get_token_object_size(b, tp, box));

  /* Deal with alignments */

  align = HtmlOBJECTalign(tp);

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

    // Sort this out, as with image handling

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

  /* Account for a border */

  if (HtmlOBJECTborder(tp))
  {
    int b;

    b = HtmlOBJECTborder(tp) * 2;

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

  return NULL;
}
/*************************************************/
/* reformat_get_placeholder_size()               */
/*                                               */
/* Gets a BBox that a placeholder would fill     */
/* based on given ALT text to plot inside it.    */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the placeholder;      */
/*                                               */
/*             Pointer to an HStream struct      */
/*             representing the placeholder;     */
/*                                               */
/*             Pointer to the ALT text (or NULL  */
/*             or a null string for no text);    */
/*                                               */
/*             Pointer to a BBox, in which the   */
/*             required bounding box for the     */
/*             placeholder is written.           */
/*                                               */
/* Assumes:    Pointer to the BBox may not be    */
/*             NULL.                             */
/*************************************************/

void reformat_get_placeholder_size(browser_data * b, HStream * tp, const char * text, BBox * box)
{
  int  h, temp, size;
  BBox fbox;

  /* Quick sanity check */

  if (!box) return;

  box->xmin = box->ymin = 0;

  if (!b || !tp || !text || !*text)
  {
    box->xmax = ImageDefaultOSSize_X;
    box->ymax = ImageDefaultOSSize_Y;

    return;
  }

  /* Claim the font */

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

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

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

  fm_get_string_width(h,
                      text,
                      Reformat_AsWideAsPossible_MP,
                      strlen(text),
                      -1,
                      &temp,
                      &box->xmax);

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

  /* Find the font height */

  fm_font_box(h, &fbox);

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

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

  convert_to_os(choices.font_size, &temp);

  if (temp < 20) temp = 20;

  box->ymax += temp;
  box->xmax += temp + temp / 2; /* Looks better to have extra horizontally */

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

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

  {
    int remain;

    convert_to_os(b->left_margin + b->right_margin, &remain);

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

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

  /* Finished */

  return;
}

/*************************************************/
/* 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[32];
  int  w;

  sprintf(spr, "b%d\0", (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[32];
  int  h;

  sprintf(spr, "b%d\0", (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;

  if (!controls.swap_bars) offset = toolbars_button_height(b) + toolbars_url_height(b);
  else                     offset = toolbars_status_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_text_line_height()                   */
/*                                               */
/* Works out how tall a line of text should be,  */
/* returning the height above and below the      */
/* notional text baseline in OS units.           */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the line;             */
/*                                               */
/*             Pointer to an HStream which is    */
/*             used to determine what font the   */
/*             line would use;                   */
/*                                               */
/*             Pointer to an int, in which the   */
/*             height above the baseline is      */
/*             written;                          */
/*                                               */
/*             Pointer to an int, in which the   */
/*             height below the baseline is      */
/*             written.                          */
/*                                               */
/* Assumes:    Either int pointer may be NULL.   */
/*             If the HStream pointer is NULL, a */
/*             value based on the base (normal)  */
/*             font size will be used.           */
/*************************************************/

static _kernel_oserror * reformat_text_line_height(browser_data * b, HStream * tp, int * top, int * bot)
{
  int  h;
  int  rettop, retbot;
  BBox box;

  if (tp) h = fm_find_token_font(b, tp, 1);
  else    h = fm_find_font(b, "serif", choices.font_size, choices.font_size, 0, 0);

//  {
//     int face, size, italic, bold, scale;

  RetError(fm_font_box(h, &box));

  /* We know the font is 'size' 16ths, so we can work */
  /* out the line spacing from this. Don't use the    */
  /* font metrics - otherwise, bold text (say) will   */
  /* force the line spacing higher than plain.        */

  rettop = box.ymax - ((b->page_is_text && !choices.system_font) ? b->leading * 2 : 0);
  retbot = -box.ymin + b->leading;

//     fm_token_font_info(tp, &face, &size, &italic, &bold);
//
//     bot = b->leading;
//
//     convert_to_points(1, &scale);
//     top += (size * 1000) / (16 * scale);

  if (top) *top = rettop;
  if (bot) *bot = retbot;

  return NULL;
}

/*************************************************/
/* reformat_check_height()                       */
/*                                               */
/* Looks at the contents of a reformat_line      */
/* structure and ensures that it's y coordinate, */
/* height and font baseline offset fields are    */
/* correctly filled in.                          */
/*                                               */
/* Parameters: 1 for all external callers, but   */
/*             will be 0 if 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 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;
  int               bot     = 0;
  int               setpara = 0; /* Flags that a paragraph break should be added later on */

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

  /* Some initial white space calculations. All these rely on the */
  /* item being at the start of a paragraph - 'offset' is zero.   */

  if (!offset)
  {
    /* If the token represents a line break, include a gap, unless */
    /* we're at the top of a page / cell or just after a LI tag.   */

    if (
         line > 0              &&
         !(tpLast->style & LI) &&
         (tp->style & P)
       )
       setpara = 1;

    /* Alternatively, if the indent has changed, insert some */
    /* space above the item (but don't do this for UL and DL */
    /* items, as these are handled more carefully later).    */

    else if (
              tpLast                       &&
              tpLast->indent != tp->indent &&
              !(tp->style & UL)            &&
              !(tp->style & DL)
            )
            setpara = 1;

    /* The next few require text before them to gain a gap, or if */
    /* the last tag was BR and the tag before that was text, the  */
    /* gaps can still be added as BR just breaks the line.        */

    else if (
              tpLast &&
              (
                (tpLast->style & PCDATA) ||
                (
                  (tpLast->tagno == TAG_BR)      &&
                  tpLast->prev                   &&
                  (tpLast->prev->style & PCDATA)
                )
              )
            )
    {
      /* Want a space above UL items */

      if (tp->style & UL) setpara = 1;

      /* If a header type has changed, and we are dealing with a */
      /* text token, insert a gap above the item to push it away */
      /* either from the text above or from the header above.    */

      else if (
                (tp->style & PCDATA) &&
                redraw_header(tp->style) != redraw_header(tpLast->style)
              )
              setpara = 1;

      /* If we're about to enter a definition list, the 'spaces if */
      /* indent changes' code above will make the spaces look odd  */
      /* unless we also give a space above the first item in the   */
      /* definition list (the DT elements are not indented, so no  */
      /* white space would have been inserted so far).             */

      else if (tp->style & DL) setpara = 1;
    }
  }

  /* If flagged to do so, set 'top' for an initial paragraph break */

  if (setpara)
  {
    int texttop, textbot;

    RetError(reformat_text_line_height(b,
                                       NULL,
                                       &texttop,
                                       &textbot));

    top = ((texttop + textbot) * 7) >> 3;
  }

  /* BRs - a single BR between bits of text will force a new line and be the     */
  /* first token in the new line, but will then have other tokens in that same   */
  /* line. It should not alter the line height itself under these circumstances. */
  /* However, two consecutive BRs should - that is, when the second is found, it */
  /* should force a P-style break, and so-on for all subsequent BRs. It is also  */
  /* the case that a BR at the top of a page / cell should similarly force a     */
  /* paragraph-like break.                                                       */

  {
    HStream * fol; /* First On Line */

    fol = d->cdata[d->ldata[line].chunks].t;

    if (
         (fol->tagno == TAG_BR) &&
         (
           (
             fol->prev &&
             (fol->prev->tagno == TAG_BR)
           )
           || line == 0
         )
       )
    {
      int texttop, textbot;

      RetError(reformat_text_line_height(b,
                                         tp,
                                         &texttop,
                                         &textbot));

      top = texttop + textbot;
    }
  }

  /* Find out the height of an image */

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

    RetError(reformat_get_image_size(b, tp, &box));

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

  /* Deal with OBJECT, APPLET and EMBED tags */

  else if (ISOBJECT(tp))
  {
    BBox box;

    RetError(reformat_get_object_size(b, tp, &box));

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

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

  else if (
            tp->tagno         == TAG_INPUT &&
            HtmlINPUTtype(tp) == inputtype_CHECKBOX
          )
  {
    int size;

    read_sprite_size("fopton", NULL, &size);
    top += size - 8;
    bot = 8;
  }

  else if (
            tp->tagno         == TAG_INPUT &&
            HtmlINPUTtype(tp) == inputtype_RADIO
          )
  {
    int size;

    read_sprite_size("fradioon", NULL, &size);
    top += size - 8;
    bot = 8;
  }

  /* Height of a bullet point */

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

  else if (tp->tagno == TAG_TABLE)
  {
    /* 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
  {
    /* Get the bounding box of the font that should be used for the token */
    /* if we've got some text, and we're either a visible forms element   */
    /* or the text consists of more than one single space.                */

    if (
         (
           (tp->style & FORM)        &&
           tp->tagno != TAG_FORM     &&
           tp->tagno != TAG_FORM_END &&
           HtmlINPUTtype(tp) != inputtype_HIDDEN
         )
         ||
         (
           (tp->text) &&
           (
             tp->text[0] &&
             !
             (
               tp->text[0] == ' '  &&
               tp->text[1] == '\0'
             )
           )
         )
       )
    {
      int texttop, textbot;

      RetError(reformat_text_line_height(b,
                                         tp,
                                         &texttop,
                                         &textbot));

      top += texttop;
      bot =  textbot;
    }
  }

  /* Work out height of various forms elements */

  if (tp->tagno == TAG_TEXTAREA)
  {
    /* Text areas, based on the number of rows */

    BBox box;
    int  h;
    int  r;
    int  lh, lb;

    h = fm_find_token_font(b, tp, 0);
    e = fm_font_box(h, &box);
    if (e) return e;

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

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

    bot += lh * (r - 1) + 8; /* + 8 for the border; r - 1 as bot is already below the first text line, so drop it by (rows - 1) more  */
    top += 8;
  }
  else if (tp->tagno == TAG_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->tagno == TAG_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_BUTTON: /* Again, no break          */
      case inputtype_RESET:
      {
        bot += 8;
        top += 12;
      }
      break;
    }
  }

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

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

  /* 'top' will correspond to the line height above the font baseline. */
  /* If it is greater than the current value, extend the top of the    */
  /* line by the difference. Move the line y coordinate down to make   */
  /* room for the extra height.                                        */

  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; /* (Since 'h' is the total height above and below the baseline) */
    d->ldata[line].b += diff;
    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)) 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)
{
  /* Nothing to do for small fetch windows */

  if (b->small_fetch) return NULL;

  /* Otherwise exit through the extent setting routines */

  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)
{
  WimpGetWindowStateBlock s;
  BBox                    new_extent;
  int                     hbot, width;

  #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;
  RetError(wimp_get_window_state(&s));

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

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

  y_extent -= (hbot + 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.                                   */

  width = s.visible_area.xmax - s.visible_area.xmin;

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

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

  /* Update height, too */

  {
    int h1, h2;

    h1 = toolbars_button_height(b) + toolbars_url_height(b);
    h2 = toolbars_status_height(b);

    if (h1) h1 += wimpt_dy();
    if (h2) h2 += wimpt_dy();

    b->display_height = s.visible_area.ymax - s.visible_area.ymin - h1 - h2;
  }

  /* Set the extent */

  RetError(window_set_extent(0,b->self_id,&new_extent));

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

    RetError(toolbox_get_parent(0, b->self_id, &po, &pc));

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

  /* The browser may have the input focus inside a forms */
  /* item, so after each loop of the reformatter ask the */
  /* forms library to ensure the caret position is OK.   */

  form_caret_may_need_moving(b);
}

/*************************************************/
/* 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. This   */
/*             includes the left hand margin /   */
/*             gap; however, if there is no      */
/*             stream to reformat, 0 is returned */
/*             rather than purely the left hand  */
/*             margin plus zero to indicate this */
/*             special case to the caller.       */
/*             Ideally the caller should never   */
/*             invoke the reformatter without a  */
/*             stream though, it's not very      */
/*             efficient...                      */
/*************************************************/

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, left_margin, right_margin;
  int                   toplevel, doall, fromstart, noalloc;
  int                   token_has_text;

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

    linewidth = redraw_left_gap(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)
      {
        /* In this case associated data will *almost* always be zero length, */
        /* in terms of text, but there are some rare circumstances when it   */
        /* won't be. Hence the reformat_datasize call.                       */

        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) token_has_text = tpCurr->text ? reformat_istext(tpCurr) : 0;
    else        token_has_text = 0;

    // Used to be 'if (!tpCurr || offset >= reformat_datasize(tpCurr))'
    // Really should check that the below never differs from it.

    if (!token_has_text || offset < 0 || !tpCurr->text[offset])
    {
      /* 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                                  &&
                (tpCurr->flags & HFlags_DealtWithToken) &&
                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) done = 1;
        else
        {
          if (!(tpCurr->flags & HFlags_DealtWithToken))
          {
            b->last_token = tpCurr->prev;
            tpCurr        = NULL;
            tnCurr        = NULL;
            done          = 1;
          }
        }

        /* 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_left_gap in Redraw.c (converted to points). */

      left_margin = redraw_left_gap(b, d, tpCurr);

      /* Can't have a margin greater than display width, but this consideration */
      /* only applies if we're not finding the smallest the stream can be.      */

      if (!(flags & Reformatter_FindingSmallest) && left_margin > displaywidth) left_margin = 0;

      /* The right hand gap value */

      right_margin = redraw_right_gap(b, d, tpCurr);

      /* Can't have margins greater than the display width */

      if (left_margin + right_margin > displaywidth) right_margin = 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 - right_margin) 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;

      /* Right, if newline is set, create a new line. */

      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 line height as zero, and baseline offset as zero. We */
            /* have no contents with which to do anything different yet.    */
            /* Changing 'b' will move the font baseline relative to the     */
            /* line base, and force the line height up. 'h' is the line     */
            /* height total including the baseline offset.                  */
            /*                                                              */
            /* When reformat_check_height is called, it will go through     */
            /* working out values called 'top' and 'bot'. The first is the  */
            /* amount by which the line height above the baseline (i.e.     */
            /* ...[...].h - ...[...].b) is to be extended; the second is    */
            /* the amount by which the space below the baseline is to be    */
            /* extended. These can thus be played about with to do vertical */
            /* alignment on images and so-forth.                            */

            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 = left_margin;

          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)
      {
        int available;

//        /* If the image has a known width and height, the reformatter is  */
//        /* about to deal 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).                                       */
//        /*                                                                */
//        /* Important to call this *before* trying to find the item height */
//        /* or width, as the image size routines will lie about the real   */
//        /* size if they think the item is not redrawable because it has   */
//        /* not be reformatted yet - chicken and egg...                    */
//
//        // Mmm, nice. And robust too. Or Your Money Back (TM).
//        //
//        // Really *must* do something about this (but probably won't...)
//
//        if (
//             (tpCurr->style & IMG) ||
//             (
//               tpCurr->tagno         == TAG_INPUT &&
//               HtmlINPUTtype(tpCurr) == inputtype_IMAGE
//             )
//           )
//           image_token_check_redrawable(b, tpCurr);

        /* If the image has a known width and height, the reformatter */
        /* has dealt with it; so both mark it as redrawable, and lock */
        /* its size down.                                             */

        image_token_reformatted(b, tpCurr);

        /* Fill in the reformat_width_data structure */

        wd.b      = b;
        wd.d      = d;
        wd.tp     = tpCurr;
        wd.data   = reformat_istext(tpCurr) ? tpCurr->text : NULL;
        wd.offset = offset;

        /* Tables, when specifying percentage widths, take up the whole */
        /* page - e.g. 100% when in an indented list will result in the */
        /* right hand edge of the table being off the page. Well, this  */
        /* is what MSIE / NN do anyway, despite it being stupid IMHO.   */

        if (tpCurr->tagno == TAG_TABLE && (TABLE_HAS_WIDTH((table_stream *) tpCurr)))
        {
          available = displaywidth - redraw_left_margin(b, d) - right_margin;
        }
        else
        {
          available = displaywidth - linewidth - right_margin;
        }

        /* The maximum width is 'very large' for preformatted text (effectively limitless).  */
        /* Similarly, for any text items in NOBR, the width in effect is without a limit.    */
        /* 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)
                      )

                      #ifdef SUPPORT_NOBR
                        ||
                        (
                          (tpCurr->style & PCDATA) &&
                          (tpCurr->style & NOBR)
                        )
                      #endif

                    )

                    ?

                    Reformat_AsWideAsPossible_MP : available;

        if (wd.maxwid < 0) wd.maxwid = 0;

        /* 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->tagno == TAG_TABLE)
        {
          if (!noalloc)
          {
            /* Take the height in millipoints (overloaded into wd.bytes) and use */
            /* this as the line height.                                          */

            convert_to_os(wd.bytes, &d->ldata[lnCurr].h);

            /* Correct the line's y coordinate given the above height. */

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

            /* P tags before tables get attached to the TABLE HStream itself. */

            if ((tpCurr->style & P) && (lnCurr > 0))
            {
              int texttop, textbot;

              e = reformat_text_line_height(b, NULL, &texttop, &textbot);

              if (!e) d->ldata[lnCurr].y -= texttop + textbot;
            }
          }

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

      /* A new chunk is forced by setting 'newchunk' if the previous item and this one    */
      /* should not be split up. For example, an LI bullet point and the text that        */
      /* follows it (if any) should appear on the same line.                              */

      if (!e && !newchunk)
      {
        if (
             !offset                    &&
             tpCurr->prev               &&
             !(tpCurr->style & LI)      &&
             (tpCurr->prev->style & LI)
           )
           newchunk = 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 an 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->tagno         == TAG_INPUT &&
                   HtmlINPUTtype(tpCurr) == inputtype_IMAGE
                 )
               )
               image_set_token_image_position(b, tpCurr, -1, -1);

            #ifdef TRACE
              if ((tl & (1u<<20)) && tpCurr->tagno == TAG_TABLE) 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;

//            if (!noalloc)
//            {
//              /* If a form had input focus, hopefully the forms library can now find */
//              /* the caret. This call will check if the token we're dealing with is  */
//              /* the currently editing one, and if so try to find its position and   */
//              /* move the caret as necessary. We do this only if 'noalloc' is unset, */
//              /* as if we're a 'virtual reformat loop', the token won't necessarily  */
//              /* have presence in any lines anyway. It might not be found if noalloc */
//              /* is unset anyway, but at least we increase the chances...            */
//
//              form_caret_may_be_moving(b, tnCurr);
//            }

            /* Advance through the token data */

            if (wd.bytes <= 0) offset = -1; /* Flag we need a new token */
            else
            {
              int has_text;

              offset += wd.bytes;

              /* Need a new line if we've not run off the end of the text - */
              /* there must have been a split point within it. This is not  */
              /* true, though, if NOBR is set.                              */

              if (tpCurr) has_text = tpCurr->text ? reformat_istext(tpCurr) : 0;
              else        has_text = 0;

              #ifdef SUPPORT_NOBR
                if (has_text && tpCurr->text[offset] && !(tpCurr->style & NOBR)) newline = 1;
              #else
                if (has_text && tpCurr->text[offset]) newline = 1;
              #endif
            }

            /* 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 - right_margin)
            {
              /* ...But shouldn't flag a new line if NOBR is set... */

              #ifdef SUPPORT_NOBR
                if (!(tpCurr->style & 0*NOBR)) newline = 1;
              #else
                newline = 1;
              #endif

              if (!d->table && w > b->display_extent) b->display_extent = w;
            }
          }
        }
      }
      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 = utils_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.keyboard_ctrl)
    {
      /* 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)
{
  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];

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

  if (c->width <= 400) c->width = 400; /* Give at least 1 OS unit to avoid possible problems in the reformatter */

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

                         b,
                         c,
                         streambase);

  /* Set the height to the line list extent, rounded down to an */
  /* integer number of pixels.                                  */

  dheight = reformat_return_extent(b, c) & ~(wimpt_dy() - 1);

  /* Convert to millipoints for storing in the cell */

  convert_to_points(dheight, &c->height);

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

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

  /* If we've already got values for this cell, return them now */

  if (c->minwid >= 0 && c->maxwid >= 0)
  {
    if (retmin) *retmin = c->minwid;
    if (retmax) *retmax = c->maxwid;

    return;
  }

  if (streambase)
  {
    /* 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
  }
  else minwidth = maxwidth = redraw_left_margin(b, c);

  /* Account for cellpadding */

  {
    int cellpadmp = table->cellpadding * 2;  /* 1 'web pixel' = 2 OS units, but only for right hand edge - redraw_left_margin takes care of the rest */

    convert_to_points(cellpadmp, &cellpadmp);

    minwidth += cellpadmp;
    maxwidth += cellpadmp;
  }

  /* Store the values */

  c->minwid = minwidth;
  c->maxwid = maxwidth;

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

  /* Finished */

  return;
}

/*************************************************/
/* 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)
{
// Note that this, then, does nothing right now - fix this function up some time!

  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->last_char == ' '
           || b->last_char == '('
           || b->last_char == 148  /* Opening double quote */
           || b->last_char == '\"')

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

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

      b->last_char = *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->tagno         == TAG_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      */
    /* last_char 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 ++;
    }
  }
}