/* 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   : Tables.c                               */
/*                                                 */
/* Purpose: Table handling functions.              */
/*                                                 */
/* Author : T.Cheal, adapted by A.D.Hodgkinson     */
/*                                                 */
/* History: 18-Mar-97: Datestamp on code received  */
/*                     from T.Cheal.               */
/*          12-Apr-97: Tidied up, more comments,   */
/*                     and from here on continued  */
/*                     development of the code     */
/*                     content.                    */
/*          18-Jun-97: Moved several functions     */
/*                     that were in the            */
/*                     reformatter here, as this   */
/*                     is a more appropriate       */
/*                     location for them.          */
/***************************************************/

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

#include "swis.h"

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

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

#include "toolbox.h"

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

#include "Memory.h"
#include "Redraw.h"
#include "Reformat.h"
#include "TokenUtils.h"
#include "Toolbars.h"

#include "Tables.h"

/* Local definitions */

#define Tables_Hourglass_WidthingTableDelay 100
#define Tables_Hourglass_FreeingTablesDelay 25

#define CorrectRowSpan(D,p) ((D)->RowSpan > (p)->RowSpan ? (p)->RowSpan : (D)->RowSpan)
#define CorrectColSpan(D,p) ((D)->ColSpan > (p)->ColSpan ? (p)->ColSpan : (D)->ColSpan)

/* Statics */

static int width_table_leds = 1;

/* Static function prototypes */

static void tables_align_contents(browser_data * b, HStream * streambase, table_stream * table, reformat_cell * cellarray, int Row, int Column);

/*************************************************/
/* tables_count_table()                          */
/*                                               */
/* Works out the number of rows and columns in a */
/* given table, and writes back into the given   */
/* table_stream structure these values plus      */
/* pointers to two allocated blocks of memory    */
/* that can hold one integer for each row and    */
/* column.                                       */
/*                                               */
/* Parameters: Pointer to a browser_data struct, */
/*             which is the parent of the table; */
/*                                               */
/*             Pointer to a table_stream struct  */
/*             relevant to the table.            */
/*                                               */
/* Returns:    Writes to the given table_stream  */
/*             struct, so this must not be in a  */
/*             read only area. The ColSpan and   */
/*             RowSpan fields are filled in with */
/*             the number of columns and rows    */
/*             respectively, and the ColOffs and */
/*             RowOffs fields are filled with    */
/*             the pointers to the arrays of     */
/*             Cols and Rows ints, respectively. */
/*************************************************/

void tables_count_table(browser_data * b, table_stream * p)
{
  table_row      * R;
  table_headdata * D;
  int              I;
  int            * RowSpill;
  int              ColCount, RowCount;
  int              Cols,     Rows;

  /* Avoid compiler warning - b may be used in later revisions of this code */

  b = b;

  #ifdef TRACE
    if (tl & (1u<<20)) Printf("\ntables_count_table entered: 0x%x\n",(int) P);
  #endif

  /* How many rows are there? 'Rows' will keep count of the current row */
  /* number according to the table structure, 'RowCount' will represent */
  /* a maximum value of the current row number, plus any defined        */
  /* rowspan value for the cell.                                        */

  Rows     = 0;
  RowCount = 0;
  R        = p->List;

  while (R)
  {
    D = R->List;

    if (!D)
    {
      if (Rows + 1 > RowCount) RowCount = Rows + 1;
    }
    else
    {
      while (D)
      {
        switch (D->Tag)
        {
          case TagTableData:
          case TagTableHead:
          {
            int this_count;

            if (D->RowSpan > 1) this_count = Rows + D->RowSpan;
            else                this_count = Rows + 1;

            if (this_count > RowCount) RowCount = this_count;
          }
          break;
        }

        D = D->Next;
      }
    }

    Rows ++;

    R = R->Next;
  }

  /* The RowSpill array is used to hold the number of columns */
  /* defined in other rows, when a cell 'spills' onto those   */
  /* other rows by having a rowspan value specified for it.   */
  /*                                                          */
  /* When scanning rows that a cell above has spilled into,   */
  /* the table structures themselves will skip over that      */
  /* column - as a cell above that row has already defined    */
  /* contents here. To be able to work out how many actual    */
  /* columns there are, then, need to fill this array with    */
  /* the number of columns any such cells span.               */
  /*                                                          */
  /* If not clear already, hopefully it'll become more so in  */
  /* the code later which actually deals with the array.      */

  RowSpill = (int *) memory_alloc_and_set(RowCount * (sizeof(int)), 0);

  /* If allocation fails, report the error and jump out to */
  /* polling - can't possibly continue here.               */

  if (!RowSpill) show_error_cont(make_no_table_memory_error(1));

  /* Now restart the process starting the */
  /* column count as the total of all row */
  /* spillage to date.                    */

  Rows = 0;
  Cols = 0;
  R    = p->List;

  /* Move through all rows */

  while (R)
  {
    #ifdef TRACE
      if (tl & (1u<<20)) Printf("tables_count_table row: 0x%x\n",(int) R);
    #endif

    ColCount = RowSpill[Rows];
    D        = R->List;

    /* Move through all columns in the current row */

    while (D)
    {
      #ifdef TRACE
        if (tl & (1u<<20)) Printf("tables_count_table headdata: 0x%x\n",(int) D);
      #endif

      switch (D->Tag)
      {
        /* Items which if present, define a column */

        case TagTableData:
        case TagTableHead:
        {
          if (D->ColSpan) ColCount += D->ColSpan;
          else            ColCount ++;

          /* If this column is meant to span several rows, */
          /* fill in the RowSpill arrays for all the rows  */
          /* it spans (except the current one) with the    */
          /* number of columns this tag defines. So when   */
          /* we get to those rows, they'll already have    */
          /* some columns counted in from previous columns */
          /* which had a rowspan specified.                */

          if (D->RowSpan > 1)
          {
            int index;

            for (I = 1; I < RowCount; I++)
            {
              index = Rows + I;

              if (index < RowCount) /* Stupidity check */
              {
                if (D->ColSpan) RowSpill[index] += D->ColSpan;
                else            RowSpill[index] ++;
              }
            }
          }
        }
        break;
      }

      D = D->Next;
    }

    /* Keep Cols up to date as the highest column count */
    /* encountered so far, and move to the next row.    */

    if (Cols < ColCount) Cols = ColCount;

    Rows ++;

    R = R->Next;
  }

  /* Free the temporary rowspill array */

  free(RowSpill);

  #ifdef TRACE
    if (tl & (1u<<20)) Printf("Rows, Cols: %d, %d\n", RowCount, Cols);
  #endif

  /* Fill in the table_stream structure with the number of rows */
  /* and columns, and pointers to two allocated blocks of Rows  */
  /* and Cols integers.                                         */
  /*                                                            */
  /* Note that if a table cell specifies colspans or rowspans   */
  /* that extend into rows or columns that would otherwise not  */
  /* be defined anywhere, these extra rows/cols will **NOT** be */
  /* included in the count here. This is why loops dealing with */
  /* such values *must* be very careful not to run off the end  */
  /* of arrays and suchlike!                                    */

  p->ColSpan = Cols;
  p->RowSpan = RowCount;

  /* May be reformatting the table, in which case there will already */
  /* be arrays attached to p.                                        */

  if (p->ColOffs)
  {
    HtmlFree(p->ColOffs);
    p->ColOffs = NULL;
  }

  if (p->RowOffs)
  {
    HtmlFree(p->RowOffs);
    p->RowOffs = NULL;
  }

  /* Now allocate the array, in the context of the HStream list.   */
  /* When the list is eventually discarded this will automatically */
  /* be discard too.                                               */

  if (p->ColSpan)
  {
    if (p->ColSpan) p->ColOffs = HtmlMalloc(p->ColSpan * sizeof(int), p);
    if (!p->ColOffs) show_error_cont(make_no_table_memory_error(10));
  }

  if (p->RowSpan)
  {
    p->RowOffs = HtmlMalloc(p->RowSpan * sizeof(int), p);
    if (!p->RowOffs) show_error_cont(make_no_table_memory_error(11));
  }

  return;
}

/*************************************************/
/* tables_position_table()                       */
/*                                               */
/* Fills in the RowOffs and ColOffs fields for   */
/* the given table and all cells in a given      */
/* table, taking account of multiple row and     */
/* column spanning. These are integers which,    */
/* for a given cell, give the number of rows and */
/* columns from the top left of the table        */
/* (starting at (0, 0)) that the cell is offset  */
/* by.                                           */
/*                                               */
/* Parameters: Pointer to a browser_data struct, */
/*             which is the parent of the table; */
/*                                               */
/*             Pointer to a table_stream struct  */
/*             relevant to the table.            */
/*************************************************/

void tables_position_table(browser_data * b, table_stream * p)
{
  table_row      * R;
  table_headdata * D;
  int              Cols, Rows, ColCount;
  int            * ColOffs;
  int            * RowOffs;
  unsigned char  * RowSpill;

  if (!p->ColSpan || !p->RowSpan) return;

  /* Avoid compiler warning - b may be used in later revisions of this code */

  b = b;

  #ifdef TRACE
    if (tl & (1u<<20)) Printf("tables_position_table entered: 0x%x\n",(int) P);
  #endif

  Rows     = 0;
  RowSpill = (unsigned char *) memory_alloc_and_set(p->ColSpan * p->RowSpan, 0); /* Enough for all rows & cols */
  ColCount = p->ColSpan;
  ColOffs  = p->ColOffs;
  RowOffs  = p->RowOffs;

  /* If allocation fails, report the error and jump out to */
  /* polling - can't possibly continue here.               */

  if (!RowSpill) show_error_cont(make_no_table_memory_error(2));

  R = p->List;

  /* Loop through all rows */

  while (R && Rows < p->RowSpan)
  {
    Cols = 0;

    /* Look through the RowSpill array for this row, skipping */
    /* any columns which have already been done - marked by a */
    /* non-zero value in the array.                           */

    while (RowSpill[Rows * ColCount + Cols]) Cols++;

    /* Cols now points at a column that hasn't been positioned yet. */

    if (R->Width)
    {
      if (ColOffs[Cols] < R->Width) ColOffs[Cols] = R->Width;
    }

    D = R->List;

    /* Loop through all columns pointed to by this row */

    while (D && Cols < ColCount)
    {
      int C, R, I, J;

      switch (D->Tag)
      {
        case TagTableData:
        case TagTableHead:
        {
          #ifdef TRACE
            if (tl & (1u<<20)) Printf("tables_position_table: In scan, D (0x%x) RowOffs = %d, ColOffs = %d\n", D, Rows, Cols);
          #endif

          D->ColOffs = Cols;
          D->RowOffs = Rows; /* This is where this cell is, honest */

          if (D->ColSpan) C = CorrectColSpan(D, p);
          else            C = 1;

          if (D->RowSpan) R = CorrectRowSpan(D, p);
          else            R = 1;

          /* Loops contain a stupidity check... */

          for (I = Cols; I <= Cols + C - 1; I++)
          {
            for (J = Rows; J <= Rows + R - 1; J++)
            {
              /* For all columns and rows that this cell covers, mark that these */
              /* have been dealt with in the RowSpill array.                     */

              if (I < p->ColSpan && J < p->RowSpan) RowSpill[J * ColCount + I] = 0xff;
            }
          }
          Cols += C;

          /* Ensure that Cols is advanced appropriately to get past */
          /* anything marked as dealt with by the above code.       */

          while (RowSpill[Rows * ColCount + Cols]) Cols++;
        }
        break;
      }

      D = D->Next;
    }

    Rows ++;

    R = R->Next;
  }

  /* Free up the temporary array and exit */

  free(RowSpill);
}

/*************************************************/
/* tables_init_table()                           */
/*                                               */
/* For a new, uninitialised array of             */
/* reformat_cell structures, fills in any        */
/* initial information prior to sizing and       */
/* positioning (as in, coordinate origins) of    */
/* the cells.                                    */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the tables;           */
/*                                               */
/*             Pointer to the table_stream       */
/*             struct representing the table;    */
/*                                               */
/*             Pointer to the cell array.        */
/*                                               */
/* Assumes:    At least tables_count_table must  */
/*             have been called to work out how  */
/*             many rows and columns there are.  */
/*************************************************/

void tables_init_table(browser_data * b, table_stream * table, reformat_cell * cellarray)
{
  int i, size;

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

  if (!size) return;

  memset(&cellarray[0], 0, sizeof(reformat_cell) * size);

  for (i = 0; i < size; i++)
  {
    cellarray[i].table  = table;
    cellarray[i].minwid = -1;
    cellarray[i].maxwid = -1;
  }

  return;
}

/*************************************************/
/* tables_width_table()                          */
/*                                               */
/* Works out the widths in millipoints for all   */
/* cells in a given table. This is called by the */
/* reformatter routines as they try to find the  */
/* width of various page elements. To find out   */
/* the width of, and finally format a particular */
/* cell, this function then itself calls the     */
/* reformatter. This can of course then call     */
/* back here for nested tables - i.e. this is a  */
/* recursive function, though it's not           */
/* immediately obvious as the callers and        */
/* callees are spread over a wide area of code.  */
/*                                               */
/* Because of the way tables define column       */
/* widths, for each cell three reformats take    */
/* place - minimum width find, maximum width     */
/* find, and final decided width.                */
/*                                               */
/* Coupled with all the juggling of those maxs   */
/* and mins - taking into account anything that  */
/* was specified in the HTML - this is something */
/* of a monster function and it is certainly not */
/* efficient at present. Hopefully this will     */
/* improve in a future interation of the tables  */
/* code...                                       */
/*                                               */
/* Parameters: 1 for a top level call, else 0 if */
/*             being called as part of a nested  */
/*             tables reformat session;          */
/*                                               */
/*             Pointer to a browser_data struct, */
/*             which is the parent of the table; */
/*                                               */
/*             Pointer to a table_stream struct  */
/*             relevant to the table;            */
/*                                               */
/*             The available width for the table */
/*             in millipoints;                   */
/*                                               */
/*             Pointer to an array of points to  */
/*             reformat_cell structures which    */
/*             will hold the final table layout; */
/*                                               */
/*             Flags as for reformat_token_width */
/*             - needed as the widthing routines */
/*             need to know if the widest a      */
/*             table can be is being found or    */
/*             not (for nested tables - avoids   */
/*             nested tables saying they're      */
/*             100% wide, say, ending up huge).  */
/*                                               */
/* Returns:    The table width, in millipoints.  */
/*************************************************/

int tables_width_table(int toplevel, browser_data * b, table_stream * p,
                       int available_width, reformat_cell * cellarray, unsigned int flags)
{
  table_row       * R;
  table_headdata  * D;
  int               I; // S, T; // For code commented out below
  int             * ColOffs;
  int               finding_widest;

  int               cellmax  = p->ColSpan * p->RowSpan;
  int               rowcount = 0;

  int               real_width,      new_width;
  int               min_table_width, max_table_width;
  int               display_width;

  int             * mins,       * maxs;
  int             * overmins,   * overmaxs;
  int             * pcwidths;
  table_headdata ** usedcells;

  int               have_colspans = 0;
  int               have_percents = 0;
  int               have_pixels   = 0;

  int               have_checked  = 0;

  int               cellspacingos = p->cellspacing * 2; /* 1 'web pixel' = 2 OS units */
  int               cellspacingmp;

  int               tbordmp;

  // Trying to work out why the final 'are we underwidth' pass is needed,
  // but due to code freeze (29/01/1998) have to put this on hold... :-(
  //
  // Printf("Table %p, aw %d, flags %x\n",p,available_width/400,flags);

  #ifdef TRACE
    if (tl & (1u<<20)) Printf("tables_width_table entered: 0x%x %d (%d OS) 0x%x\n", (int) P, available_width, available_width / 400, cellarray);
  #endif

  if (!cellmax) return 1600;

  convert_to_points(cellspacingos, &cellspacingmp);
  convert_to_points(TABLE_BORDER(p) * 2, &tbordmp); /* 1 'web pixel' = 2 OS units */

  /* We may need the browser page width available in millipoints quite */
  /* frequently, so do the conversion just the once for speed.         */

  convert_to_points(b->display_width, &display_width);

  /* If we've been given a very large value for available_width, we're */
  /* trying to find the widest a table can be.                         */
  /*                                                                   */
  /* If 'finding_widest' is 2, the available_width field is valid even */
  /* though we're finding the greatest width because the table gave an */
  /* absolute width in pixels.                                         */

  if (flags & Reformatter_FindingWidest) finding_widest = 1;
  else                                   finding_widest = 0;

  /* Deal with specific non-zero table widths */

  if (TABLE_HAS_WIDTH(p) && TABLE_WIDTH(p))
  {
    int width = 0, dx;

    switch (TABLE_WIDTH_UNITS(p))
    {
      default:
      case UNITS_PIXELS:
      {
        convert_to_points(wimpt_dx(), &dx);
        width = TABLE_WIDTH(p) * dx;

        if (finding_widest) finding_widest = 2;
      }
      break;

      case UNITS_PERCENT:
      {
        /* We may be finding the widest the table can be, and if so we shouldn't */
        /* try to calculate the percentage as this will give a coordinate        */
        /* overflow.                                                             */

        if (!finding_widest) width = (TABLE_WIDTH(p) * available_width) / 100;
      }
      break;
    }

    if (width) available_width = width;
  }

  /* Allocate various arrays. */

  /* First, two arrays of ints to hold the minimum and */
  /* maximum widths of the table's cells.              */

  maxs = (int *) memory_alloc_and_set(sizeof(int) * cellmax, 0);

  if (!maxs)
  {
    show_error_ret(make_no_table_memory_error(3));

    return 0;
  }

  mins = (int *) memory_alloc_and_set(sizeof(int) * cellmax, 0);

  if (!mins)
  {
    free(maxs);

    show_error_ret(make_no_table_memory_error(4));

    return 0;
  }

  /* Allocate arrays to hold min and max widths of columns */
  /* worked out overall.                                   */

  overmaxs = (int *) memory_alloc_and_set(sizeof(int) * p->ColSpan, 0);

  if (!overmaxs)
  {
    free(mins);
    free(maxs);

    show_error_ret(make_no_table_memory_error(5));

    return 0;
  }

  overmins = (int *) memory_alloc_and_set(sizeof(int) * p->ColSpan, 0);

  if (!overmins)
  {
    free(overmaxs);
    free(mins);
    free(maxs);

    show_error_ret(make_no_table_memory_error(6));

    return 0;
  }

  /* Array to hold the stated or inferred percentage widths of cells */

  pcwidths = (int *) memory_alloc_and_set(sizeof(int) * cellmax, 0);

  if (!pcwidths)
  {
    free(overmins);
    free(overmaxs);
    free(mins);
    free(maxs);

    show_error_ret(make_no_table_memory_error(7));

    return 0;
  }

  /* Array to hold addresses of the cells used to decide the overall */
  /* column widths - the attributes the HTML uses to define these    */
  /* cells can alter the final stage table widthing. The first 'row' */
  /* of the array is used for the addresses of min values, the       */
  /* second for addresses of max values.                             */

  usedcells = (table_headdata **) memory_alloc_and_set(sizeof(table_headdata *) * p->ColSpan * 2, 0);

  if (!usedcells)
  {
    free(pcwidths);
    free(overmins);
    free(overmaxs);
    free(mins);
    free(maxs);

    show_error_ret(make_no_table_memory_error(7));

    return 0;
  }

  if (finding_widest != 1)
  {
    /* Decrease available width according to cell spacing constraints */

    if (p->ColSpan) available_width -= cellspacingmp * (p->ColSpan + 1);

    /* Decrease available width according to overall table border */

    available_width -= tbordmp * 2;
  }

  /* This can get s l o w... */

  _swix(Hourglass_Start,
        _IN(0),

        Tables_Hourglass_WidthingTableDelay);

  /* Treat the p->ColOffs field as an array of millipoint offsets */
  /* from the left of the table for each column.                  */

  ColOffs = p->ColOffs;

  /* First pass - find min and max widths of the columns. */

  R = p->List; /* p is a table_stream, so R now points to the first table_row */

  while (R && rowcount < p->RowSpan)
  {
    /* Preliminaries */

    int row_pc_total;

    #ifdef TRACE
      if (tl & (1u<<20)) Printf("tables_width_table: Row struct 0x%x\n",(int) R);
    #endif

    /* This is used to keep track of the total of */
    /* width specifiers in the row                */

    row_pc_total  = 0;
    have_percents = 0;
    have_pixels   = 0;

    /* Flick the LEDs around a bit to show progress. Not much point */
    /* trying to do %s because of nested tables.                    */

    _swix(Hourglass_LEDs,
          _INR(0,1),

          3,
          width_table_leds);

    width_table_leds ^= 3;

    /* R is a table_row, so D now points to a table_headdata */

    D = R->List;

    while (D && D->ColOffs < p->ColSpan)
    {
      #ifdef TRACE
        if (tl & (1u<<20)) Printf("tables_width_table: Head struct, tag 0x%x 0x%x\n",(int) D,(int) D->Tag);
      #endif

      switch (D->Tag)
      {
        case TagTableData:
        case TagTableHead:
        {
          int width      = -1;
          int cellnumber = D->RowOffs * p->ColSpan + D->ColOffs;

          if (cellnumber < cellmax)
          {
            /* First stage of dealing with specific cell widths, and find cell size limits from reformatter */

            #ifdef TRACE
              if (tl & (1u<<20)) Printf("tables_width_table: Dealing with column tag 0x%x\n",(int) D->Tag);
            #endif

            /* Need a fair bit of extra work if there are any */
            /* column spanning cells, but can avoid another   */
            /* scan of the table if we flag whether or not    */
            /* there are any present.                         */

            if (D->ColSpan > 1) have_colspans = 1;

            /* This cell starts with no actual or implied percentage */
            /* sizespecifier                                         */

            pcwidths[cellnumber] = -1;

            /* Does the cell specify a width? */

            if (TD_HAS_WIDTH(D))
            {
              switch (TD_WIDTH_UNITS(D))
              {
                default:
                case UNITS_PIXELS:
                {
                  int dx;

                  have_pixels ++;

                  convert_to_points(wimpt_dx(), &dx); /* Millipoints per pixel in dx */

                  width = TD_WIDTH(D) * dx;
                }
                break;

                /* Don't allow percentage specifiers on a row to exceed 100 */

                case UNITS_PERCENT:
                {
                  have_percents ++;

                  if (row_pc_total == 100)                   pcwidths[cellnumber] = 0;
                  else if (TD_WIDTH(D) + row_pc_total > 100) pcwidths[cellnumber] = 100 - row_pc_total, row_pc_total  = 100;
                  else                                       pcwidths[cellnumber] = TD_WIDTH(D),        row_pc_total += TD_WIDTH(D);
                }
                break;
              }

              if (finding_widest != 1)
              {
                /* Deal with garbage like 'TD WIDTH="2000"' - if a cell */
                /* wants to be wider than the table can be, ignore the  */
                /* width specifier completely.                          */

                if (width > available_width) width = -1;
              }
              else
              {
                /* Because this is a scan to find the widest limit of a table, */
                /* we can't use available_width to limit the item; so instead  */
                /* use the browser's display width as a second best option.    */

                if (width > display_width) width = -1;
              }
            }

            /* Unfortunately, still need to find the minimum size. So bang */
            /* goes any possible speed up from knowing it in advance. If   */
            /* only web page designers could count...                      */

            reformat_find_cell_limits(toplevel,
                                      b,
                                      (HStream *) D->List,
                                      p,
                                      cellarray,
                                      D->RowOffs,
                                      D->ColOffs,
                                      &mins[cellnumber],
                                      &maxs[cellnumber]);

            /* If the table cell specified a particular width, and it's */
            /* greater than the minimum width size, then the maximum    */
            /* cell size should be considered as the given width value. */
            /* Otherwise, the maximum size should be as close as        */
            /* possible - i.e., the same as the minimum size.           */
            /*                                                          */
            /* This is so that table cells containing, say, a column of */
            /* images, work - if there are no BRs between the images,   */
            /* then the max width is large. But we shouldn't let the    */
            /* cell get wider than its specified width (if it has one), */
            /* or images that are meant to be in a column could appear  */
            /* side by side.                                            */

            if (width >= 0)
            {
              if (width > mins[cellnumber]) maxs[cellnumber] = width;
              else                          maxs[cellnumber] = mins[cellnumber]; /* (Keep it is small as possible) */
            }

          /* Closure of 'if (cellnumber < cellmax)' */
          }
        }
        break;

      /* Closure of 'switch (D->Tag)' */
      }

      D = D->Next;

    /* Closure of 'while (D && ...)' */
    }

    /* Right, we've done that row, marking out any cells which had */
    /* percentage specifiers (and cropping values where the total  */
    /* exceeds 100%). If there were any % specifiers on the row,   */
    /* we need to width them and deal with any effects this has on */
    /* other cells in the row.                                     */

    if (have_percents > 0) /* have_percents holds the number of % specified cells */
    {
      int min_pc       = 100;
      int explicit_pc  = 0;
      int pc_size      = 0;
      int max_row      = 0;
      int min_row      = 0;
      int do_inference = 0;

      /* If the whole row contains % cells, MSIE / NN seem to find the smallest */
      /* non-zero % specifier in the row. If there is more than one cell with   */
      /* this specifier, find the one with the greatest max size value. The     */
      /* width of the chosen cell, coupled with the percentage width specifier  */
      /* on the cell, gives you the width of 1% by division. The other cells    */
      /* can thus be widthed.                                                   */
      /*                                                                        */
      /* If the row has some unspecified cells, these aquire inferred           */
      /* percentage widths based on 100% minus the total of all percentage      */
      /* specified cells on that row, divided by the number of unspecified      */
      /* cells on the row.                                                      */
      /*                                                                        */
      /* If there are any pixel specified values, it gets complex. First, if    */
      /* the total of explicit % specified cells is < 100, then infer % sizes   */
      /* on the pixel specified cells. You *don't* use this for widthing - you  */
      /* use their pixel values - but you *do* use it as part of the 'what is   */
      /* 1%' algorithm as described above. However, if the total of explicit %  */
      /* specified cells is greater than 100%, then the meaning of 1% will now  */
      /* change. It now becomes 1/100th of the total available width for the    */
      /* row, after all the pixel specified cell widths have been subtracted.   */
      /*                                                                        */
      /* First, do we have any unspecified cells to infer percentages on? We    */
      /* can look for this at the same time as we look for the smallest         */
      /* percentage specifier.                                                  */

      D = R->List;

      while (D && D->ColOffs < p->ColSpan)
      {
        switch (D->Tag)
        {
          case TagTableData:
          case TagTableHead:
          {
            int cellnumber = D->RowOffs * p->ColSpan + D->ColOffs;

            if (cellnumber < cellmax)
            {
              if (pcwidths[cellnumber] == -1)
              {
                /* If everything is either %s of pixels, count the */
                /* pixels (pcwidths[] = -1, so this doesn't have a */
                /* percentage specifier)                           */

                if (p->ColSpan == have_percents + have_pixels)
                {
                  max_row += maxs[cellnumber];
                  min_row += mins[cellnumber];
                }

                /* Otherwise, there is a mixture of %s, pixels (maybe) */
                /* and unspecified cells - only count the latter.      */

                else if (!TD_HAS_WIDTH(D))
                {
                  max_row += maxs[cellnumber];
                  min_row += mins[cellnumber];
                }
              }
            }
          }
          break;
        }

        D = D->Next;
      }

      D = R->List;

      while (D && D->ColOffs < p->ColSpan)
      {
        switch (D->Tag)
        {
          case TagTableData:
          case TagTableHead:
          {
            int cellnumber = D->RowOffs * p->ColSpan + D->ColOffs;

            if (cellnumber < cellmax)
            {
              int infer = pcwidths[cellnumber];

              /* If we have any cells with no percentage specifiers, then there are two */
              /* cases; the other cells all have pixel specifiers on them, or only some */
              /* of the cells do. In the former case, infer percentage specifiers on    */
              /* all of the other cells. In the latter, only infer percentages on the   */
              /* cells that don't specify a width in any other way.                     */
              /*                                                                        */
              /* We need to find the lowest % specifier, and of those in the row, the   */
              /* one with the greatest maximum width (if there are several with the     */
              /* same minimum % value). For this, the cells we infer %s onto get a      */
              /* distribution of (100% - explicitly specifieds) / (number of cells we   */
              /* are inferring sizes on); i.e. a linear distribution. So this is used   */
              /* to work out what 1% means. However, for later calculation of the sizes */
              /* based on this value of 1%, we infer %s in proportion to the sizes of   */
              /* the columns in question. That's why the 'infer' variable is used at    */
              /* first, but then there's a second bit of code to fill in pcwidths[] for */
              /* an inferred cell with a proportionally scaled, rather than linearly    */
              /* scaled value.                                                          */

              if (infer == -1)
              {
                if (p->ColSpan == have_percents + have_pixels)
                {
                  int div = p->ColSpan - have_percents;

                  if (!div) div = 1;

                  infer = (100 - row_pc_total) / div;
                }
                else if (!TD_HAS_WIDTH(D))
                {
                  int div = p->ColSpan - (have_percents + have_pixels);

                  if (!div) div = 1;

                  infer = (100 - row_pc_total) / div;
                }
              }
              else
              {
                explicit_pc += infer;
              }

              /* Remember the smallest non-zero value */

              if (
                   infer > 0 &&
                   infer <= min_pc
                 )
              {
                if (min_pc == infer)
                {
                  if (maxs[cellnumber] > pc_size)
                  {
                    pc_size = maxs[cellnumber];

                    if (pcwidths[cellnumber] == -1) do_inference = 0;
                    else                            do_inference = 1;
                  }
                }
                else
                {
                  min_pc  = infer;
                  pc_size = maxs[cellnumber];

                  if (pcwidths[cellnumber] == -1) do_inference = 0;
                  else                            do_inference = 1;
                }
              }
            }
          }
          break;
        }

        D = D->Next;
      }

      if (do_inference)
      {
        D = R->List;

        while (D && D->ColOffs < p->ColSpan)
        {
          switch (D->Tag)
          {
            case TagTableData:
            case TagTableHead:
            {
              int cellnumber = D->RowOffs * p->ColSpan + D->ColOffs;

              if (cellnumber < cellmax)
              {
                /* The *actual* inferred value is based on the cell size */

                if (pcwidths[cellnumber] == -1)
                {
                  if (p->ColSpan == have_percents + have_pixels)
                  {
                    pcwidths[cellnumber] = ((100 - row_pc_total) * maxs[cellnumber]) / (max_row ? max_row : 1);
                  }
                  else if (!TD_HAS_WIDTH(D))
                  {
                    pcwidths[cellnumber] = ((100 - row_pc_total) * maxs[cellnumber]) / (max_row ? max_row : 1);
                  }
                }
              }
            }
            break;
          }

          D = D->Next;
        }
      }

      /* If we have less than 100% explicitly specified total in the row, */
      /* find the smallest explicit or inferred non-zero % specifier cell */
      /* (and if there are several with this value, find the one with the */
      /* greatest maximum content width), and use this to find what 1%    */
      /* should be.                                                       */

      if (explicit_pc < 100)
      {
        /* We now know what min_pc percent is in OS units (pc_size), and thus */
        /* what 1 percent is in OS units. We can thus assign actual widths to */
        /* the columns and store them in overmaxs array if larger than the    */
        /* existing value.                                                    */

        D = R->List;

        while (D && D->ColOffs < p->ColSpan)
        {
          switch (D->Tag)
          {
            case TagTableData:
            case TagTableHead:
            {
              int cellnumber = D->RowOffs * p->ColSpan + D->ColOffs;
              int pc_based_width;

              if (cellnumber < cellmax)
              {
                if (!TD_HAS_WIDTH(D) || TD_WIDTH_UNITS(D) != UNITS_PIXELS)
                {
                  pc_based_width = (pcwidths[cellnumber] * pc_size) / (min_pc ? min_pc : 1);

                  if (pc_based_width > maxs[cellnumber]) maxs[cellnumber] = pc_based_width;
                }
              }
            }
            break;
          }

          D = D->Next;
        }
      }

      /* If, instead, we do have pixel specifiers, 1% comes from the width */
      /* left over after the pixel specifier widths have been subtracted   */
      /* from the whole width available to the row.                        */
      /*                                                                   */
      /* If we're finding the widest a table can be, just leave the full   */
      /* width from the reformatter for all cells - 1% of 'very very wide' */
      /* doens't make much sense...                                        */

      else if (finding_widest != 1)
      {
        int remaining = available_width;

        /* Find how much is left for the percentage cells */

        D = R->List;

        while (D && D->ColOffs < p->ColSpan)
        {
          switch (D->Tag)
          {
            case TagTableData:
            case TagTableHead:
            {
              int cellnumber = D->RowOffs * p->ColSpan + D->ColOffs;

              if (cellnumber < cellmax)
              {
                /* If the cell has no specified or inferred percentage width, */
                /* subtract the cell's *minimum* size from the remaining      */
                /* available width - the remainder thus represents a maximum  */
                /* value.                                                     */

                if (pcwidths[cellnumber] < 0) remaining -= mins[cellnumber];

                if (remaining < 0) remaining = 0;
              }
            }
            break;
          }

          D = D->Next;
        }

        /* Now calculate all of the percentage specified widths based on this */

        D = R->List;

        while (D && D->ColOffs < p->ColSpan)
        {
          switch (D->Tag)
          {
            case TagTableData:
            case TagTableHead:
            {
              int cellnumber = D->RowOffs * p->ColSpan + D->ColOffs;
              int pc_based_width;

              if (cellnumber < cellmax)
              {
                if (TD_HAS_WIDTH(D) && TD_WIDTH_UNITS(D) == UNITS_PERCENT)
                {
                  pc_based_width = (remaining * pcwidths[cellnumber]) / 100;

                  if (pc_based_width > maxs[cellnumber]) maxs[cellnumber] = pc_based_width;
                }
              }
            }
            break;
          }

          D = D->Next;
        }
      }
    }

    /* Carry on for the other rows. */

    rowcount ++;
    R = R->Next;

  /* Closure of 'while (R && ...)' */
  }

  /* Debug output to show mins and maxs */

  #ifdef TRACE

    if (tl & (1u<<20))
    {
      int rowc,colc;

      Printf("\nTable %p, size before COLSPAN/ROWSPAN: mins then maxs\n", p);

      for(rowc=0;rowc<p->RowSpan;rowc++)
      {
        Printf("%d: ",rowc);

        for(colc=0;colc<p->ColSpan;colc++)
        {
          Printf("%d (%d) ",mins[rowc*p->ColSpan+colc],mins[rowc*p->ColSpan+colc]/400);
        }
        Printf("\n");
      }

      Printf("\n");

      for(rowc=0;rowc<p->RowSpan;rowc++)
      {
        Printf("%d: ",rowc);

        for(colc=0;colc<p->ColSpan;colc++)
        {
          Printf("%d (%d) ",maxs[rowc*p->ColSpan+colc],maxs[rowc*p->ColSpan+colc]/400);
        }
        Printf("\n");
      }
      Printf("\n");
    }

  #endif

  /* mins and maxs arrays consequently now hold the min and max */
  /* sizes of all cells (not accounting for row or column       */
  /* spanning, though).                                         */
  /*                                                            */
  /* We now need to work out the overall greatest minimum and   */
  /* maximum width for each column. Any cells specifying a      */
  /* colspan > 1 are ignored in this, as they will have a large */
  /* width (they span several cells, after all). We're only     */
  /* interested in single cells for now.                        */
  /*                                                            */
  /* We follow the table structure to do this as we must record */
  /* the addresses of cells used in the final width, and want   */
  /* to be able to skip those cells that specify a colspan.     */

  R = p->List;

  while (R)
  {
    int col;

    D = R->List;

    while (D && D->ColOffs < p->ColSpan)
    {
      col = D->ColOffs;

      switch (D->Tag)
      {
        case TagTableData:
        case TagTableHead:
        {
          int cellnumber = D->RowOffs * p->ColSpan + D->ColOffs;

          if (cellnumber < cellmax && col < p->ColSpan)
          {
            /* If the cell doesn't span more than 1 column and has a min or max */
            /* value wider than the cells in the same column above it, use the  */
            /* min or max values of this cell instead. Record the cell in the   */
            /* usedcells array accordingly.                                     */

            if (D->ColSpan < 2)
            {
              if (mins[cellnumber] > overmins[col])
              {
                overmins [col] = mins[cellnumber];
                usedcells[col] = D;
              }

              if (maxs[cellnumber] > overmaxs[col])
              {
                table_headdata * lc = usedcells[p->ColSpan + col];

                /* Only use the maximum value if the cell isn't 'locked' by one */
                /* already in use that specifies a pixel size                   */

                if (TD_HAS_WIDTH(lc) && TD_WIDTH_UNITS(lc) == UNITS_PIXELS)
                {
                  /* May be forced to use this cell if the *minimum* value */
                  /* ends up greater than the maximum!                     */

                  if (mins[cellnumber] > overmaxs[col])
                  {
                    overmaxs [col]              = mins     [cellnumber];
                    usedcells[p->ColSpan + col] = usedcells[col];
                  }
                }
                else
                {
                  overmaxs [col]              = maxs[cellnumber];
                  usedcells[p->ColSpan + col] = D;
                }
              }
            }
          }
        }
        break;
      }

      D = D->Next;
    }

    R = R->Next;
  }

  /* Debug output to show the results so far... */

  #ifdef TRACE

    if (tl & (1u<<20))
    {
      int colc;

      Printf("\nTable %p, overall 1: mins / maxs\n", p);

      for(colc=0;colc<p->ColSpan;colc++)
      {
        Printf("%d (%d) ",overmins[colc],overmins[colc]/400);
      }

      Printf("\n");

      for(colc=0;colc<p->ColSpan;colc++)
      {
        Printf("%d (%d) ",overmaxs[colc],overmaxs[colc]/400);
      }
      Printf("\n\n");
    }

  #endif

  /* Now deal with cells with colspans > 1. For each case, add up   */
  /* the width of the spanned cells from the array just calculated. */
  /* If these work out more than the width calculated for that      */
  /* cell, then nothing needs to be done; else distribute the       */
  /* difference over the spanned cells.                             */

  if (have_colspans)
  {
    R        = p->List;
    rowcount = 0;

    while (R && rowcount < p->RowSpan)
    {
      D = R->List;

      while (D && D->ColOffs < p->ColSpan)
      {
        switch (D->Tag)
        {
          case TagTableData:
          case TagTableHead:
          {
            if (D->ColSpan > 1)
            {
              int currcol;
              int cellnumber         = D->RowOffs * p->ColSpan + D->ColOffs;
              int spanned_total_mins = 0;
              int spanned_total_maxs = 0;
              int px_specifiers      = 0;
              int pc_specifiers      = 0;

              if (cellnumber < cellmax)
              {
                /* Count the spanned columns' minimum and maximum widths, and  */
                /* gather some statistics on the width specifiers on the cells */
                /* used to arrive at those widths.                             */

                for (
                      currcol = D->ColOffs;
                      currcol < D->ColOffs + D->ColSpan && currcol < p->ColSpan;
                      currcol ++
                    )
                {
                  spanned_total_mins += overmins[currcol];
                  spanned_total_maxs += overmaxs[currcol];

                  if (usedcells[currcol + p->ColSpan])
                  {
                    table_headdata * head = usedcells[currcol + p->ColSpan];

                    if (TD_HAS_WIDTH(head))
                    {
                      if (TD_WIDTH_UNITS(head) == UNITS_PERCENT) pc_specifiers ++;
                      else                                       px_specifiers ++;
                    }
                  }
                }

                #ifdef TRACE

                  if (tl & (1u<<20)) Printf("spanned total maxs, mins: %d, %d (%d, %d)\n",
                                            spanned_total_mins,
                                            spanned_total_maxs,
                                            spanned_total_mins/400,
                                            spanned_total_maxs/400);
                #endif

                /* If not big enough, distribute the difference over the columns. */
                /* We can't start altering the minimum values here as when the    */
                /* distribution over the table width (if any) takes place, the    */
                /* total of the spanned columns may end up large enough. So we    */
                /* only increase the potential maximum values, and then must      */
                /* check after the 'final decision' on the column widths that all */
                /* column spanning cells got allocated at least as much as they   */
                /* require.                                                       */

                if (spanned_total_maxs < maxs[cellnumber] && D->ColSpan)
                {
                  int max_diff = maxs[cellnumber] - spanned_total_maxs;
                  int basic;
                  int remainder;
                  int distribute_over;

                  /* If there are any pixel specified columns spanned, but they aren't */
                  /* all pixel specified, then distribute the width over the others    */
                  /* instead.                                                          */

                  distribute_over = D->ColSpan - px_specifiers;

                  basic     = max_diff / (distribute_over ? distribute_over : D->ColSpan);
                  remainder = max_diff - (basic * (distribute_over ? distribute_over : D->ColSpan));

                  for (
                        currcol = D->ColOffs;
                        currcol < D->ColOffs + D->ColSpan && currcol < p->ColSpan;
                        currcol ++
                      )
                  {
                    if (
                         !distribute_over                                                ||
                         !usedcells[currcol + p->ColSpan]                                ||
                         !TD_HAS_WIDTH(usedcells[currcol + p->ColSpan])                  ||
                         TD_WIDTH_UNITS(usedcells[currcol + p->ColSpan]) != UNITS_PIXELS
                       )
                    {
                      if (remainder < 0)
                      {
                        overmaxs[currcol] += basic - 1;
                        remainder ++;
                      }
                      else if (remainder > 0)
                      {
                        overmaxs[currcol] += basic + 1;
                        remainder --;
                      }
                      else overmaxs[currcol] += basic;
                    }

                    /* Make sure the minimum value is less than the maximum! They */
                    /* may have swapped when we added to overmins - but of course */
                    /* can't do this check until afterwards, or overmaxs will get */
                    /* set to overmins, and then added to *again* by the above    */
                    /* loop.                                                      */

                    if (overmaxs[currcol] < overmins[currcol]) overmaxs[currcol] = overmins[currcol];
                  }
                }

              /* Closure of 'if (cellnumber < cellmax)' */
              }
            }
          }
          break;

        /* Closure of 'switch (D->Tag)' */
        }

        D = D->Next;

      /* Closure of 'while (D && ...)' */
      }

      rowcount ++;
      R = R->Next;

    /* Closure of 'while (R && ...)' */
    }

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

tables_width_table_assign_finals: /* (The colspan code below this section may jump back here) */

  /* RFC 1942-like autolayout algorithm - if the *minimum* widths exceed */
  /* the width we're trying to fit the table into, assign the min sizes  */
  /* anyway and (elsewhere) allow horizontal scrolling.                  */
  /*                                                                     */
  /* NB, note the 'usedcells' array needs to have its two 'rows' of      */
  /* cells used for min and max values rationalised at the same time as  */
  /* the final widths are chosen. We'll put the final values in the      */
  /* first 'row' of usedcells.                                           */

  min_table_width = max_table_width = 0;

  for (I = 0; I < p->ColSpan; I++) min_table_width += overmins[I], max_table_width += overmaxs[I];

  if (finding_widest != 1 && min_table_width > available_width)
  {
    /* Assign minimum widths. All of the cells used for minimum values */
    /* are already stored in the first 'row' of usedcells, so we have  */
    /* no more work to do there in this case.                          */

    #ifdef TRACE
      if (tl & (1u<<20)) Printf("\ntables_width_table: Forced to assign minimum widths:\n\n");
    #endif

    for (I = 0; I < p->ColSpan; I++)
    {
      ColOffs[I] = overmins[I];

      #ifdef TRACE
        if (tl & (1u<<20)) Printf("%d: %d OS units\n", I, overmins[I] / 400);
      #endif
    }

    #ifdef TRACE
      if (tl & (1u<<20)) Printf("\n");
    #endif
  }

  /* Otherwise, do the maximum widths fit in? If so, assign */
  /* those maximum widths.                                  */

  else if (finding_widest == 1 || max_table_width <= available_width)
  {
    /* Assign maximum widths */

    #ifdef TRACE
      if (tl & (1u<<20)) Printf("\ntables_width_table: Table fits, assigning maximum widths:\n\n");
    #endif

    for (I = 0; I < p->ColSpan; I++)
    {
      ColOffs[I] = overmaxs[I];

      /* Move all of the second 'row' of usedcells to the first row, as we're */
      /* using the maximum value for all cells.                               */

      usedcells[I] = usedcells[p->ColSpan + I];

      #ifdef TRACE
        if (tl & (1u<<20)) Printf("%d: %d OS units\n", I, overmaxs[I] / 400);
      #endif
    }

    #ifdef TRACE
      if (tl & (1u<<20)) Printf("\n");
    #endif
  }

  /* Otherwise the maximum width of the table is greater than the available */
  /* space, but the minimum width is less than it.                          */
  /*                                                                        */
  /* According to RFC 1942, the following widthing algorithm is best.       */

  else
  {
    float d, W, D; /* Must be floats as ints will overflow, hmph. */

    #ifdef TRACE
      if (tl & (1u<<20)) Printf("\ntables_width_table: Assigning variable column widths:\n\n");
    #endif

    W = (float) available_width - (float) min_table_width; /* Difference between minimum table width and available width */
    D = (float) max_table_width - (float) min_table_width; /* Difference between minimum and maximum table width */

    if (D > 0)
    {
      for (I = 0; I < p->ColSpan; I++)
      {
        d = (float) overmaxs[I] - (float) overmins[I]; /* Difference between minimum and maximum column width */
        if (d < 0) d = 0;

        /* The following makes columns with large differences between their minimum and maximum */
        /* widths wider than columns with small differences.                                    */

        ColOffs[I] = overmins[I] + (int) (d * W / D);

        /* Hmm, hard to know what to do with 'usedcells' here. For now, move all */
        /* of the second 'row' of usedcells to the first row, as if we're using  */
        /* the max values.                                                       */

        usedcells[I] = usedcells[p->ColSpan + I];

        #ifdef TRACE
          if (tl & (1u<<20)) Printf("%d: %d OS units\n", I, ColOffs[I] / 400);
        #endif
      }
    }

    #ifdef TRACE

      else
      {
        if (tl & (1u<<20)) Printf("...can't, max_table_width = min_table_width\n");
      }

      if (tl & (1u<<20)) Printf("\n");

    #endif
  }

  /* If we have any colspan cells, we must ensure that the columns they span */
  /* give at least as much space as the spanning cell requires. If not,      */
  /* distribute the space over the minimum size values of the spanned        */
  /* columns and rewidth the table overall.                                  */

  if (have_colspans && !have_checked)
  {
    int must_rewidth = 0;

    have_checked = 1;
    R            = p->List;
    rowcount     = 0;

    while (R && rowcount < p->RowSpan)
    {
      D = R->List;

      while (D && D->ColOffs < p->ColSpan)
      {
        switch (D->Tag)
        {
          case TagTableData:
          case TagTableHead:
          {
            if (D->ColSpan > 1)
            {
              int currcol;
              int cellnumber    = D->RowOffs * p->ColSpan + D->ColOffs;
              int spanned_total = 0;
              int mins_total    = 0;

              if (cellnumber < cellmax)
              {
                /* Count the spanned columns */

                for (
                      currcol = D->ColOffs;
                      currcol < D->ColOffs + D->ColSpan && currcol < p->ColSpan;
                      currcol ++
                    )
                    spanned_total += ColOffs[currcol],
                    mins_total    += overmins[currcol];

                /* If not big enough, distribute the difference between the minimum */
                /* size of the colspan cell and the sum of the minimum sizes of the */
                /* columns it spans. Using minimums ensures that when rewidthing    */
                /* the table, we won't need to check colspan cells again in case    */
                /* they are still undersize...!                                     */

                if (spanned_total < mins[cellnumber])
                {
                  int min_diff  = mins[cellnumber] - mins_total;
                  int basic     = min_diff / (D->ColSpan ? D->ColSpan : 1);
                  int remainder = min_diff - (basic * D->ColSpan);

                  must_rewidth = 1;

                  /* Distribute over the minimums, handling rounding carefully */

                  for (
                        currcol = D->ColOffs;
                        currcol < D->ColOffs + D->ColSpan && currcol < p->ColSpan;
                        currcol ++
                      )
                  {
                    if (remainder < 0)
                    {
                      overmins[currcol] += basic - 1;
                      remainder ++;
                    }
                    else if (remainder > 0)
                    {
                      overmins[currcol] += basic + 1;
                      remainder --;
                    }
                    else overmins[currcol] += basic;

                    if (overmaxs[currcol] < overmins[currcol]) overmaxs[currcol] = overmins[currcol];
                  }
                }

              /* Closure of 'if (cellnumber < cellmax)' */
              }
            }
          }
          break;

        /* Closure of 'switch (D->Tag)' */
        }

        D = D->Next;

      /* Closure of 'while (D && ...)' */
      }

      rowcount ++;
      R = R->Next;

    /* Closure of 'while (R && ...)' */
    }

    if (must_rewidth)
    {
      /* Now need to rewidth the table on the basis that the */
      /* minimum widths in overmins[] changed.               */

      #ifdef TRACE
        if (tl & (1u<<20)) Printf("\ntables_width_table: Second table widthing pass due to colspan cells:\n");
      #endif

      goto tables_width_table_assign_finals;

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

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

//  #ifdef TRACE

    /* Final sanity check - the algorithms above should ensure that */
    /* by this stage, no cell whether spanning multiple columns or  */
    /* not should be below its minimum size. In TRACE builds, this  */
    /* check warns if this is not the case (this indicates a bug in */
    /* the widthing code above).                                    */

    R        = p->List;
    rowcount = 0;

    while (R && rowcount < p->RowSpan)
    {
      D = R->List;

      while (D && D->ColOffs < p->ColSpan)
      {
        switch (D->Tag)
        {
          case TagTableData:
          case TagTableHead:
          {
            int currcol    = D->ColOffs;
            int cellnumber = D->RowOffs * p->ColSpan + D->ColOffs;

            if (cellnumber < cellmax)
            {
              if (
                   D->ColSpan <= 1 &&
                   ColOffs[currcol] < mins[cellnumber]
                 )
              {
                #ifdef TRACE

                  erb.errnum = Utils_Error_Custom_Normal;

                  sprintf(erb.errmess,
                          "Non-colspan cell %d would have ended up at %d OS units, below minimum width of %d",
                          cellnumber,
                          ColOffs[currcol] / 400,
                          mins[cellnumber] / 400);

                  show_error_ret(&erb);

                #endif

                ColOffs[currcol] = mins[cellnumber];
              }
            }
          }
          break;
        }

        D = D->Next;
      }

      rowcount ++;
      R = R->Next;
    }

    /* Now deal with cells with colspans > 1. For each case, add up   */
    /* the width of the spanned cells from the array just calculated. */
    /* If these work out more than the width calculated for that      */
    /* cell, then nothing needs to be done; else distribute the       */
    /* difference over the spanned cells.                             */

    if (have_colspans)
    {
      R        = p->List;
      rowcount = 0;

      while (R && rowcount < p->RowSpan)
      {
        D = R->List;

        while (D && D->ColOffs < p->ColSpan)
        {
          switch (D->Tag)
          {
            case TagTableData:
            case TagTableHead:
            {
              if (D->ColSpan > 1)
              {
                int currcol;
                int cellnumber    = D->RowOffs * p->ColSpan + D->ColOffs;
                int spanned_total = 0;

                if (cellnumber < cellmax)
                {
                  /* Count the spanned columns */

                  for (
                        currcol = D->ColOffs;
                        currcol < D->ColOffs + D->ColSpan && currcol < p->ColSpan;
                        currcol ++
                      )
                      spanned_total += ColOffs[currcol];

                  /* If not big enough, distribute the difference over the columns. */
                  /* We can't start altering the minimum values here as when the    */
                  /* distribution over the table width (if any) takes place, the    */
                  /* total of the spanned columns may end up large enough.          */

                  if (spanned_total < mins[cellnumber])
                  {
                    int min_diff  = mins[cellnumber] - spanned_total;
                    int basic     = min_diff / (D->ColSpan ? D->ColSpan : 1);
                    int remainder = min_diff - (basic * D->ColSpan);

                    #ifdef TRACE

                      erb.errnum = Utils_Error_Custom_Normal;

                      sprintf(erb.errmess,
                              "Colspan cell %d would have ended up at %d OS units, below minimum width of %d",
                              cellnumber,
                              spanned_total / 400,
                              mins[cellnumber] / 400);

                      show_error_ret(&erb);

                    #endif

                    /* Distribute for minimums, handling rounding carefully */

                    for (
                          currcol = D->ColOffs;
                          currcol < D->ColOffs + D->ColSpan && currcol < p->ColSpan;
                          currcol ++
                        )
                    {
                      if (remainder < 0)
                      {
                        ColOffs[currcol] += basic - 1;
                        remainder ++;
                      }
                      else if (remainder > 0)
                      {
                        ColOffs[currcol] += basic + 1;
                        remainder --;
                      }
                      else ColOffs[currcol] += basic;
                    }
                  }

                /* Closure of 'if (cellnumber < cellmax)' */
                }
              }
            }
            break;

          /* Closure of 'switch (D->Tag)' */
          }

          D = D->Next;

        /* Closure of 'while (D && ...)' */
        }

        rowcount ++;
        R = R->Next;

      /* Closure of 'while (R && ...)' */
      }

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

//  #endif

  /* Add up the widths */

  real_width = 0;

  for (I = 0; I < p->ColSpan; I++) real_width += ColOffs[I];

  /* Did the table specify a non-zero width to fit to? */

  if (TABLE_HAS_WIDTH(p) && TABLE_WIDTH(p))
  {
    /* If the width is less than that available, distribute */
    /* the extra width over the table to make it fit.       */

    #ifdef TRACE
      if (tl & (1u<<20)) Printf("\nTable specifies a width - should columns be extended to fit?\n");
    #endif

    if (finding_widest != 1 && real_width && real_width < available_width)
    {
      float W,  add = 0;

      int   rw, aw;

      int   pix = 0;
      int   per = 0;
      int   usp = 0;

      rw = real_width;
      aw = available_width;

      /* If we have any pixel specified cells, only distribute over */
      /* non-pixel specified cells. For some reason, MSIE only does */
      /* this if there are some unspecified cells on the row. If    */
      /* there are only pixel or percentage specified cells, the    */
      /* distribution occurs across all cells.                      */
      /*                                                            */
      /* Of course, by this stage we're working on an amalgamation  */
      /* of individual rows into overall columns, but the principle */
      /* is the same... ;-)                                         */

      for (I = 0; I < p->ColSpan; I++)
      {
        if (usedcells[I])
        {
          if      (!TD_HAS_WIDTH(usedcells[I]))                   usp += ColOffs[I];
          else if (TD_WIDTH_UNITS(usedcells[I]) == UNITS_PIXELS)  pix += ColOffs[I];
          else if (TD_WIDTH_UNITS(usedcells[I]) == UNITS_PERCENT) per += ColOffs[I];
          else                                                    usp += ColOffs[I];
        }
      }

      if (usp)
      {
        /* If we have pixel specifiers and also some other types of cell, */
        /* reduce the apparent table width by the width of the pixel      */
        /* specified cells. The distribution routine below will then skip */
        /* these cells.                                                   */

        if (pix && pix != rw) rw -= pix, aw -= pix;
        else pix = 0;
      }
      else pix = 0;

      #ifdef TRACE
        if (tl & (1u<<20)) Printf("\ntables_width_table: Resizing all columns to fit space:\n\n");
      #endif

      W = (float) aw - (float) rw;

      for (I = 0; I < p->ColSpan; I++)
      {
        if (
             !pix ||
             (
               pix &&
               (
                 !usedcells[I] ||
                 !TD_HAS_WIDTH(usedcells[I]) ||
                 TD_WIDTH_UNITS(usedcells[I]) != UNITS_PIXELS
               )
             )
           )
        {
          add = W * ((float) ColOffs[I] / (float) (rw ? rw : 1));

          ColOffs[I] += (int) add;

          #ifdef TRACE
            if (tl & (1u<<20)) Printf("%d: %d OS units\n", I, ColOffs[I] / 400);
          #endif
        }
      }
    }
    #ifdef TRACE

      else
      {
        if (tl & (1u<<20)) Printf("...can't, real_width = %d\n", real_width);
      }

      if (tl & (1u<<20)) Printf("\n");

    #endif
  }

  /* We've finished with the temporary arrays */

  free(maxs);
  free(mins);
  free(overmaxs);
  free(overmins);
  free(pcwidths);
  free(usedcells);

  /* Now we need to go back and reformat the cell contents to the known width */

  rowcount = 0;
  R        = p->List;

  while (R && rowcount < p->RowSpan)
  {
    D = R->List;

    while (D && D->ColOffs < p->ColSpan)
    {
      switch (D->Tag)
      {
        case TagTableData:
        case TagTableHead:
        {
          /* Use the tag's number of columns offset from the left  */
          /* of the table as an index into the array of millipoint */
          /* offsets.                                              */

          new_width = abs(ColOffs[D->ColOffs]);

          /* If the tag spans more than one column, add in the widths */
          /* of the extra columns it covers.                          */

          if (D->ColSpan > 1)
          {
            int index;

            for(I = 1; I < D->ColSpan; I++)
            {
              index = D->ColOffs + I;

              if (index < p->ColSpan) new_width += abs(ColOffs[index]);
            }
          }

          /* Reformat the cell, recursing for inner tables */

          real_width = tables_width_cell(toplevel,
                                         b,
                                         (HStream *) D->List,
                                         p,
                                         cellarray,
                                         new_width,
                                         D->RowOffs,
                                         D->ColOffs);
        }
        break;
      }

      D = D->Next;
    }

    rowcount ++;
    R = R->Next;
  }

  /* Finally, add up all the widths of the columns and return it */

  max_table_width = 0;

  for (I = 0; I < p->ColSpan; I++) max_table_width += abs(ColOffs[I]);

  #ifdef TRACE
    if (tl & (1u<<20)) Printf("tables_width_table: Finished, returning %d (%d OS)\n", max_table_width, max_table_width / 400);
  #endif

  _swix(Hourglass_Off, 0);

  /* Must of course add in whatever we subtracted from available */
  /* width to allow for cellspacing to the returned table width, */
  /* and allow for the outer table border.                       */

  if (p->ColSpan) max_table_width += cellspacingmp * (p->ColSpan + 1) + tbordmp * 2;

  return max_table_width;
}

/*************************************************/
/* tables_width_cell()                           */
/*                                               */
/* Returns the actual width of a given table     */
/* cell after attempting to reformat it 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 the  */
/*             cell is to contain;               */
/*                                               */
/*             Pointer to the table_stream       */
/*             struct representing the table;    */
/*                                               */
/*             Pointer to the table's array of   */
/*             reformat_cell structures;         */
/*                                               */
/*             Allowed column width (millipts);  */
/*                                               */
/*             Row number of the cell;           */
/*                                               */
/*             Column number of the cell.        */
/*                                               */
/* Returns:    Actual width after formatting,    */
/*             in millipoints.                   */
/*************************************************/

int tables_width_cell(int toplevel, browser_data * b, HStream * streambase, table_stream * table,
                      reformat_cell * cellarray, int ColWidth, int Row, int Column)
{
  return reformat_format_cell(toplevel,
                              b,
                              streambase,
                              table,
                              cellarray,
                              ColWidth,
                              Row,
                              Column);
}

/*************************************************/
/* tables_height_table()                         */
/*                                               */
/* Works out the heights in millipoints for all  */
/* cells in a given table. This may be called    */
/* from recursively called functions, but does   */
/* not enter into recursion itself as the height */
/* information for particular cells is stored    */
/* when finding the width - there's no need to   */
/* call the reformatter again.                   */
/*                                               */
/* Parameters: 1 for a top level call, else 0 if */
/*             being called as part of a nested  */
/*             tables reformat session;          */
/*                                               */
/*             Pointer to a browser_data struct, */
/*             which is the parent of the table; */
/*                                               */
/*             Pointer to a table_stream struct  */
/*             relevant to the table;            */
/*                                               */
/*             Pointer to an array of points to  */
/*             reformat_cell structures which    */
/*             will hold the final table layout. */
/*                                               */
/* Returns:    Height of the whole table, in     */
/*             millipoints.                      */
/*************************************************/

int tables_height_table(int toplevel, browser_data * b, table_stream * p, reformat_cell * cellarray)
{
  int              ReturnHeight = 0, Height, NewWidth;
  int            * RowOffs;
  int            * ColOffs;
  int              Row;
  table_row      * R;
  table_headdata * D;
  int              I, C;
  int              scale_height  = 0;
  int              cellmax       = p->ColSpan * p->RowSpan;
  int              cellspacingos = p->cellspacing * 2; /* 1 'web pixel' = 2 OS units */
  int              cellspacingmp;
  int              tbordmp;

  if (!cellmax) return 1600;

  convert_to_points(cellspacingos, &cellspacingmp);
  convert_to_points(TABLE_BORDER(p) * 2, &tbordmp); /* 1 'web pixel' = 2 OS units */

  #ifdef TRACE
    if (tl & (1u<<20)) Printf("tables_height_table entered: 0x%x 0x%x\n",(int) P,(int) cellarray);
  #endif

  /* To work out height, must know the width (so that the */
  /* reformatter knows what width to wrap to, etc.). So   */
  /* must only call this *after* tables_width_table.      */

  RowOffs = p->RowOffs;
  ColOffs = p->ColOffs;

  Row = 0;
  R   = p->List;

  /* Deal with specific table heights - we may need */
  /* to scale everything up to these afterwards.    */

  if (TABLE_HAS_HEIGHT(p))
  {
    int height = 0, dy;

    switch (TABLE_HEIGHT_UNITS(p))
    {
      default:
      case UNITS_PIXELS:
      {
        convert_to_points(wimpt_dy(), &dy);
        height = TABLE_HEIGHT(p) * dy;
      }
      break;

      case UNITS_PERCENT:
      {
        convert_to_points(redraw_display_height(b, NULL), &height);

        height = (TABLE_HEIGHT(p) * height) / 100;
      }
      break;
    }

    scale_height = height;
  }

  /* Now find the collective row heights for all of the cells */

  while (R)
  {
    D = R->List;

    /* Scan the cells */

    while (D)
    {
      switch (D->Tag)
      {
        case TagTableData:
        case TagTableHead:
        {
          /* Work out the width that this cell uses, for ROWSPAN = 1 cells only */

          if (D->ColOffs < p->ColSpan && D->RowOffs < p->RowSpan && D->RowSpan <= 1)
          {
            NewWidth = abs(ColOffs[D->ColOffs]);

            if (D->ColSpan > 1)
            {
              int index;

              for (I = 1; I < D->ColSpan; I++)
              {
                index = D->ColOffs + I;

                if (index < p->ColSpan) NewWidth += abs(ColOffs[index]);
              }
            }

            /* Find the required cell height (this is not a    */
            /* recursive call - the height is stored in the    */
            /* relevant place by tables_width_cell called from */
            /* tables_width_table above, which is where the    */
            /* recursion is handled).                          */

            Height = tables_height_cell(toplevel,
                                        b,
                                        (HStream *) D->List,
                                        p,
                                        cellarray,
                                        NewWidth,
                                        D->RowOffs,
                                        D->ColOffs);

            /* Deal with direct height specifiers */

            if (TD_HAS_HEIGHT(D))
            {
              int hc = 0;

              /* Percentages come from the whole visible area size */

              if (TD_HEIGHT_UNITS(D) == UNITS_PERCENT)
              {
                hc = (b->display_height * TD_HEIGHT(D)) / 100;
              }

              /* Pixel specifiers are easier */

              else
              {
                hc = TD_HEIGHT(D) * 2; /* 2 'web pixels' = 1 OS unit */
              }

              convert_to_points(hc, &hc);

              if (hc > Height) Height = hc;
            }

            /* If this is the greatest height so far for the row, remember it */

            if (Height > RowOffs[Row]) RowOffs[Row] = Height;
          }
        }
        break;
      }

      D = D->Next;
    }

    #ifdef TRACE
      if (tl & (1u<<20)) Printf("tables_height_table: First pass, row %d, height %d (%d OS)\n", Row, RowOffs[Row], RowOffs[Row] / 400);
    #endif

    Row ++;

    R = R->Next;
  }

  /* Another scan, for ROWSPAN this time */

  Row = 0;
  R   = p->List;

  while (R)
  {
    D = R->List;

    while (D)
    {
      switch (D->Tag)
      {
        case TagTableData:
        case TagTableHead:
        {
          /* Work out the width that this cell uses, for ROWSPAN = 1 cells only */

          if (D->ColOffs < p->ColSpan && D->RowOffs < p->RowSpan && D->RowSpan > 1)
          {
            NewWidth = abs(ColOffs[D->ColOffs]);

            if (D->ColSpan > 1)
            {
              int index;

              for (I = 1; I < D->ColSpan; I++)
              {
                index = D->ColOffs + I;

                if (index < p->ColSpan) NewWidth += abs(ColOffs[index]);
              }
            }

            /* Find the required cell height (this is not a    */
            /* recursive call - the height is stored in the    */
            /* relevant place by tables_width_cell called from */
            /* tables_width_table above, which is where the    */
            /* recursion is handled).                          */

            Height = tables_height_cell(toplevel,
                                        b,
                                        (HStream *) D->List,
                                        p,
                                        cellarray,
                                        NewWidth,
                                        D->RowOffs,
                                        D->ColOffs);

            /* Deal with direct height specifiers */

            if (TD_HAS_HEIGHT(D))
            {
              /* Percentages come from the whole visible area size */

              if (TD_HEIGHT_UNITS(D) == UNITS_PERCENT)
              {
                int hc = (b->display_height * TD_HEIGHT(D)) / 100;

                if (hc > Height) Height = hc;
              }

              /* Pixel specifiers are easier */

              else if (TD_HEIGHT_UNITS(D) == UNITS_PIXELS)
              {
                int hc = TD_HEIGHT(D) * 2; /* 2 'web pixels' = 1 OS unit */

                if (hc > Height) Height = hc;
              }
            }

            /* The cell spans several rows, so we must count up the height */
            /* already present in the rows it spans. Any extra that this   */
            /* cell requires is added to the last row it touches.          */

            C = D->RowSpan;
            if (C + Row >= p->RowSpan) C = p->RowSpan - Row;

            if (C > 0)
            {
              int TotalHeight = 0;

              for (I = Row; I < Row + C && I < p->RowSpan; I++)
              {
                TotalHeight += RowOffs[I];
              }

              /* If the total amount the cell spans is less than this cell */
              /* requires, add in the extra to the last cell.              */

              if (TotalHeight < Height) RowOffs[Row + C - 1] += Height - TotalHeight;
            }
          }
        }
        break;
      }

      D = D->Next;
    }

    #ifdef TRACE
      if (tl & (1u<<20)) Printf("tables_height_table: Second pass, row %d, height %d (%d OS)\n", Row, RowOffs[Row], RowOffs[Row] / 400);
    #endif

    Row ++;

    R = R->Next;
  }

  /* Calculate the table height */

  for (Row = 0; Row < p->RowSpan; Row ++) ReturnHeight += RowOffs[Row];

  /* If this is below the table scale height, do a *linear* scale up */

  if (ReturnHeight < scale_height)
  {
    int difference = scale_height - ReturnHeight;
    int basic;
    int remainder;

    basic     = difference / (p->RowSpan ? p->RowSpan : 1);
    remainder = difference - (basic * p->RowSpan);

    for (
          Row = 0;
          Row < p->RowSpan;
          Row ++
        )
    {
      if (remainder < 0)
      {
        RowOffs[Row] += basic - 1;
        remainder ++;
      }
      else if (remainder > 0)
      {
        RowOffs[Row] += basic + 1;
        remainder --;
      }
      else RowOffs[Row] += basic;
    }

    /* Recalculate height (may not be dead on scale_height because */
    /* of rounding errors, though it won't be far out because of   */
    /* the above code)                                             */

    ReturnHeight = 0;

    for (Row = 0; Row < p->RowSpan; Row ++) ReturnHeight += RowOffs[Row];
  }

  /* Add in cell spacings and the table outer border */

  if (p->RowSpan) ReturnHeight += cellspacingmp * (p->RowSpan + 1) + tbordmp * 2;

  /* Return the height */

  #ifdef TRACE
    if (tl & (1u<<20)) Printf("tables_height_table: Finished, returning %d (%d OS)\n\n", ReturnHeight, ReturnHeight / 400);
  #endif

  return ReturnHeight;
}

/*************************************************/
/* tables_height_cell()                          */
/*                                               */
/* Returns the height of a cell widthed by a     */
/* previous call to tables_width_cell.           */
/*                                               */
/* 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;         */
/*                                               */
/*             Allowed column width (millipts -  */
/*             this is not used at present);     */
/*                                               */
/*             Row number of the cell;           */
/*                                               */
/*             Column number of the cell.        */
/*                                               */
/* Returns:    Cell height in millipoints.       */
/*************************************************/

int tables_height_cell(int toplevel, browser_data * b, HStream * streambase, table_stream * table,
                       reformat_cell * cellarray, int ColWidth, int Row, int Column)
{
  int             cellindex = Row * table->ColSpan + Column;
  int             cellpadmp;
  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];

  /* OK, so not used for now, but may be - this is to avoid a compiler warning */

  b = b;

  #ifdef TRACE
    if (tl & (1u<<20))
    {
      Printf("tables_height_cell: %p %p %d %d %d\n",streambase,table,ColWidth,Row,Column);
      Printf("tables_height_cell returning %d\n",c->height);
    }
  #endif

  /* Add in the cell padding values to the top and bottom */

  cellpadmp = table->cellpadding * 4; /* 1 'web pixel' = 2 OS units, times two for top and bottom */
  convert_to_points(cellpadmp, &cellpadmp);

  c->height += cellpadmp;

  return c->height;
}

/*************************************************/
/* tables_fix_table()                            */
/*                                               */
/* Having found the width and height of the      */
/* cells in a given table, and their row/column  */
/* offset from the top left, this fixes the      */
/* cell positions as x and y coordinates         */
/* in millipoints from the top left.             */
/*                                               */
/* Parameters: Pointer to a browser_data struct, */
/*             which is the parent of the table; */
/*                                               */
/*             Pointer to a table_stream struct  */
/*             relevant to the table;            */
/*                                               */
/*             Pointer to an array of points to  */
/*             reformat_cell structures which    */
/*             will hold the final table layout. */
/*************************************************/

void tables_fix_table(browser_data * b, table_stream * p, reformat_cell * cellarray)
{
  table_row      * R;
  table_headdata * D;
  int              I;
  int              Width, Height;
  int            * ColOffs;
  int            * RowOffs;
  int              CurX, CurY;
  int              cellcount     = 0;
  int              cellmax       = p->ColSpan * p->RowSpan;
  int              cellspacingos = p->cellspacing * 2; /* 1 'web pixel' = 2 OS units */
  int              cellspacingmp;
  int              tbordmp;

  convert_to_points(cellspacingos, &cellspacingmp);
  convert_to_points(TABLE_BORDER(p) * 2, &tbordmp); /* 1 'web pixel' = 2 OS units */

  /* Have ColOffs and RowOffs as the arrays holding millipoint */
  /* offsets from the top left of the table.                   */

  R       = p->List;
  ColOffs = p->ColOffs;
  RowOffs = p->RowOffs;

  while (R && cellcount < cellmax)
  {
    D = R->List;

    /* In here, D->RowOffs or D->ColOffs will be the number of */
    /* rows / columns from the top left that this cell is      */
    /* offset by.                                              */

    while (D && cellcount < cellmax)
    {
      if (D->ColOffs < p->ColSpan && D->RowOffs < p->RowSpan)
      {
        /* If the cell is offset horizontally, add up the widths of */
        /* all the columns before it into CurX.                     */

        CurX = cellspacingmp + tbordmp;

        if (D->ColOffs)
        {
          for (I = 0; I < D->ColOffs; I++)
          {
            CurX += abs(ColOffs[I]) + cellspacingmp;
          }
        }

        CurY = -cellspacingmp - tbordmp;

        /* Similarly, add up the row heights */

        if (D->RowOffs)
        {
          for (I = 0; I < D->RowOffs; I++)
          {
            CurY -= abs(RowOffs[I]) + cellspacingmp;
          }
        }

        /* Set Width to the width of this cell, */
        /* taking account of column spanning.   */

        Width = abs(ColOffs[D->ColOffs]);

        if (D->ColSpan > 1)
        {
          for (I = 1; I < D->ColSpan; I++)
          {
            Width += abs(ColOffs[D->ColOffs + I]);
          }

          /* Account for cell spacing */

          Width += cellspacingmp * (D->ColSpan - 1);
        }

        /* Do the same for the height */

        Height = abs(RowOffs[D->RowOffs]);

        if (D->RowSpan > 1)
        {
          for (I = 1; I < CorrectRowSpan(D, p); I++)
          {
            Height += abs(RowOffs[D->RowOffs + I]);
          }

          /* Account for cell spacing */

          Height += cellspacingmp * (D->RowSpan - 1);
        }

        /* We now know the X and Y millipoint offsets of the cell and */
        /* its width and height (again, in millipoints). Now call     */
        /* tables_fix_cell to fix the positions of the cell inside    */
        /* the relevant data structure components.                    */

        switch (D->Tag)
        {
          case TagTableData: tables_fix_cell(b, (HStream *) D->List, p, cellarray, CurX, CurY, Width, Height, D->RowOffs, D->ColOffs); break;
          case TagTableHead: tables_fix_cell(b, (HStream *) D->List, p, cellarray, CurX, CurY, Width, Height, D->RowOffs, D->ColOffs); break;
        }
      }

      cellcount++;

      D = D->Next;
    }

    R = R->Next;
  }
}

/*************************************************/
/* tables_fix_cell()                             */
/*                                               */
/* Fixes the position of a given table cell as   */
/* an offset from the top left of the table in   */
/* millipoints.                                  */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             owning 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;         */
/*                                               */
/*             X offset from top left, in        */
/*             millipoints;                      */
/*                                               */
/*             Yoffset from top left, in         */
/*             millipoints;                      */
/*                                               */
/*             Total cell width (millipoints);   */
/*                                               */
/*             Total cell height (millipoints);  */
/*                                               */
/*             Row number of the cell;           */
/*                                               */
/*             Column number of the cell.        */
/*************************************************/

void tables_fix_cell(browser_data * b, HStream * streambase, table_stream * table, reformat_cell * cellarray,
                     int x, int y, int Width, int Height, int Row, int Column)
{
  int              cellindex = Row * table->ColSpan + Column;
  reformat_cell  * c;

  #ifdef TRACE
    if (tl & (1u<<20)) Printf("tables_fix_cell: %p %p %d %d\n",streambase,table,Row,Column);
  #endif

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

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

  //the 3200's in here should be replaced by border sizes in due course

  c->x = x + Column;// * 3200 + 1600;
  c->y = y - Row;// * 3200 - 1600;

  c->cellwidth  = Width;
  c->cellheight = Height;

  /* Post-correct Y for VALIGN and cellpadding considerations */

  tables_align_contents(b,
                        streambase,
                        table,
                        cellarray,
                        Row,
                        Column);

  /* Finished */

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

/*************************************************/
/* tables_align_contents()                       */
/*                                               */
/* Once the cellheight field of a reformat_cell  */
/* struct is filled in, it is possible to look   */
/* at the VALIGN specification on the table      */
/* cell tag represented by the reformat_cell     */
/* struct and align the cell contents in an      */
/* appropriate fashion. This is done by          */
/* increasing the height of the first line as    */
/* required, shifting the y coordinate of all    */
/* subsequent lines as needed, and increasing    */
/* the used height value in the cell to match.   */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             owning 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.        */
/*************************************************/

static void tables_align_contents(browser_data * b, HStream * streambase, table_stream * table, reformat_cell * cellarray, int Row, int Column)
{
  int              cellindex = Row * table->ColSpan + Column;
  int              cellpadmp = table->cellpadding * 2; /* 1 'web pixel' = 2 OS units */
  int              valign_offset_mp, valign_offset_os;
  table_headdata * head;
  reformat_cell  * c;

  #ifdef TRACE
    if (tl & (1u<<20)) Printf("tables_align_contents: %p %p %d %d\n",streambase,table,Row,Column);
  #endif

  convert_to_points(cellpadmp, &cellpadmp);

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

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

  /* If the cell has no lines, again, can't do anything */

  if (!c->nlines) return;

  /* OK, so not used for now, but may be - this is to avoid a compiler warning */

  b = b;

  /* Work out the vertical alignment offset value in OS units and millipoints */

  head = streambase ? (table_headdata *) streambase->parent : NULL;

  if (head)
  {
    if      (TD_VALIGN(head) == ALIGN_BOTTOM) valign_offset_mp = c->cellheight - c->height;
    else if (TD_VALIGN(head) == ALIGN_MIDDLE) valign_offset_mp = (c->cellheight - c->height) / 2;
    else                                      valign_offset_mp = 0;

    valign_offset_mp += cellpadmp;

    convert_to_os(valign_offset_mp, &valign_offset_os);

    /* Only proceed if there's a change to be made */

    if (valign_offset_mp && valign_offset_os)
    {
      int l;

      /* Shift the y coordinates of the lines */

      for (l = 0; l < c->nlines; l++) c->ldata[l].y -= valign_offset_os;

      /* Increase the used height value */

      c->height += valign_offset_mp;
    }
  }

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

/*************************************************/
/* tables_free_memory()                          */
/*                                               */
/* Goes through the lines of a reformat_cell     */
/* structure, freeing all allocated memory       */
/* relating to any tables in that list; that is, */
/* the lines and chunks allocated in any assoc-  */
/* iated cells. Does not free the given cell's   */
/* line or chunk arrays, or any HTMLLib          */
/* allocated data such as the cell arrays        */
/* allocated through HTMLLib or the HStream      */
/* lists (HtmlStreamFree should be used for      */
/* those).                                       */
/*                                               */
/* Parameters: 1 for top level call, 0 for a     */
/*             recursive one (only used for the  */
/*             hourglass);                       */
/*                                               */
/*             Pointer to a browser_data struct  */
/*             relevant to the tables;           */
/*                                               */
/*             Pointer to a reformat_cell struct */
/*             holding the line array;           */
/*                                               */
/*             Line number in that line array    */
/*             from which freeing is to start.   */
/*************************************************/

void tables_free_memory(int toplevel, browser_data * b, reformat_cell * d, int line)
{
  int l, c;

  if (!d->nlines || line >= d->nlines) return;
  if (line < 0) line = 0;

  if (toplevel) _swix(Hourglass_Start,
                      _IN(0),

                      Tables_Hourglass_FreeingTablesDelay);

  /* Scan through the lines from last line to the requested one */
  /* (this is so that flex thrashage will hopefully be reduced, */
  /* as other cells in lines of higher numbers will on average  */
  /* appear in memory above cells in lines of lower numbers).   */

  for (l = d->nlines - 1; l >= line; l--)
  {
    /* Scan through all chunks on the line */

    for (c = d->ldata[l].chunks + d->ldata[l].n - 1; c >= d->ldata[l].chunks; c--)
    {
      /* Does the chunk represent a table? */

      if (d->cdata[c].t->tagno == TAG_TABLE)
      {
        table_stream   * table     = (table_stream *) d->cdata[c].t;
        table_row      * row       = NULL;
        table_headdata * head      = NULL;
        reformat_cell  * cellarray = table->cells;
        reformat_cell  * cell;
        int              cellindex;
        int              cellcount = 0;
        int              cellmax   = table->ColSpan * table->RowSpan;

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

          while (row && cellcount < cellmax)
          {
            head = row->List;

            while (head && cellcount < cellmax)
            {
              switch (head->Tag)
              {
                case TagTableData:
                case TagTableHead:
                {
                  cellindex = head->RowOffs * table->ColSpan + head->ColOffs;

                  if (cellindex < cellmax)
                  {
                    cell = &cellarray[cellindex];

                    /* Recursive call to free any nested tables inside this cell */

                    tables_free_memory(0, b, cell, 0);

                    /* Free the line and chunk lists for the cell */

                    if (cell->ldata) flex_free((flex_ptr) &cell->ldata);
                    if (cell->cdata) flex_free((flex_ptr) &cell->cdata);

                    cell->nlines = 0;

                    /* Invalidate the min and max width information */

                    cell->minwid = -1;
                    cell->maxwid = -1;
                  }
                }
                break;

              /* Closure of 'switch (head->Tag)' */
              }

              cellcount++;

              head = head->Next;

            /* Closure of 'while (head && ...)' */
            }

            row = row->Next;

          /* Closure of 'while (row && ...)' */
          }

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

      /* Closure of 'if (d->cdata[c].t->tagno == TAG_TABLE)' */
      }

    /* Closure of loop scanning this line's chunks */
    }

    if (toplevel && d->nlines) _swix(Hourglass_Percentage, _IN(0), (100 * (d->nlines - l - 1)) / d->nlines);

  /* Closure of loop scanning the cell's lines */
  }

  if (toplevel) _swix(Hourglass_Off, 0);
}