/*
** Routines for doing layout of HTML tables
**
** Copyright (C) 1997-2000 D. Richard Hipp
**
** This library is free software; you can redistribute it and/or
** modify it under the terms of the GNU Library General Public
** License as published by the Free Software Foundation; either
** version 2 of the License, or (at your option) any later version.
**
** This library is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
** Library General Public License for more details.
** 
** You should have received a copy of the GNU Library General Public
** License along with this library; if not, write to the
** Free Software Foundation, Inc., 59 Temple Place - Suite 330,
** Boston, MA  02111-1307, USA.
**
** Author contact information:
**   drh@acm.org
**   http://www.hwaci.com/drh/
*/
#include <tk.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <math.h>
#include "htmltable.h"

/*
** Default values for various table style parameters
*/
#define DFLT_BORDER             0
#define DFLT_CELLSPACING_3D     5
#define DFLT_CELLSPACING_FLAT   0
#define DFLT_CELLPADDING        2
#define DFLT_HSPACE             0
#define DFLT_VSPACE             0

#if INTERFACE
/*
** Set parameter A to the maximum of A and B.
*/
#define SETMAX(A,B)  if( (A)<(B) ){ (A) = (B); }
#define MAX(A,B)     ((A)<(B)?(B):(A))
#endif

/*
** Return the appropriate cell spacing for the given table.
*/
static int CellSpacing(HtmlWidget *htmlPtr, HtmlElement *pTable){
  char *z;
  int relief;
  int cellSpacing;

  z = HtmlMarkupArg(pTable, "cellspacing", 0);
  if( z==0 ){
     relief = htmlPtr->tableRelief;
     if( relief==TK_RELIEF_RAISED || relief==TK_RELIEF_SUNKEN ){
       cellSpacing = DFLT_CELLSPACING_3D;
     }else{
       cellSpacing = DFLT_CELLSPACING_FLAT;
     }
  }else{
    cellSpacing = atoi(z);
  }
  return cellSpacing;
}

/* Forward declaration */
static HtmlElement *MinMax(HtmlWidget*, HtmlElement *, int *, int *, int);

/* pStart points to a <table>.  Compute the number of columns, the
** minimum and maximum size for each column and the overall minimum
** and maximum size for this table and store these value in the
** pStart structure.  Return a pointer to the </table> element, 
** or to NULL if there is no </table>.
**
** The min and max size for column N (where the leftmost column has
** N==1) is pStart->minW[1] and pStart->maxW[1].  The pStart->minW[0]
** and pStart->maxW[0] entries contain the minimum and maximum widths
** of the whole table, including any cell padding, cell spacing,
** border width and "hspace".  The values of pStart->minW[I] for I>=1
** do not contain any cell padding, cell spacing or border width.
** Only pStart->minW[0] contains these extra spaces.
**
** The back references from </table>, </tr>, </td> and </th> back to
** the <table> markup are also filled in.  And for each <td> and <th>
** markup, the pTable and pEnd fields are set to their proper values.
*/
static HtmlElement *TableDimensions(
  HtmlWidget *htmlPtr,               /* The HTML widget */
  HtmlElement *pStart,               /* The <table> markup */
  int lineWidth                      /* Total widget available to the table */
){
  HtmlElement *p;                    /* Element being processed */
  HtmlElement *pNext;                /* Next element to process */
  int iCol = 0;                      /* Current column number.  1..N */
  int iRow = 0;                      /* Current row number */
  int inRow = 0;                     /* True if in between <TR> and </TR> */
  int i, j;                          /* Loop counters */
  int n;                             /* Number of columns */
  int minW, maxW, requestedW;        /* min, max, requested width for a cell */
  int noWrap;                        /* true for NOWRAP cells */
  int colspan;                       /* Column span for the current cell */
  int rowspan;                       /* Row span for the current cell */
  char *z;                           /* Value of a <table> parameter */
  int cellSpacing;                   /* Value of CELLSPACING parameter */
  int cellPadding;                   /* Value of CELLPADDING parameter */
  int tbw;                           /* Width of border around whole table */
  int cbw;                           /* Width of border around one cell */
  int hspace;                        /* Value of HSPACE parameter */
  int separation;                    /* Space between columns */
  int margin;                        /* Space between left margin and 1st col */
  int availWidth;                    /* Part of lineWidth still available */
  int maxTableWidth;                 /* Amount of lineWidth available to table*/
  int fromAbove[HTML_MAX_COLUMNS+1]; /* Cell above extends thru this row */
  int min0span[HTML_MAX_COLUMNS+1];  /* Min for colspan=0 cells */
  int max0span[HTML_MAX_COLUMNS+1];  /* Max for colspan=0 cells */
  int reqW[HTML_MAX_COLUMNS+1];      /* Requested width for each column */

  /* colMin[A][B] is the absolute minimum width of all columns between
  ** A+1 and B+1.  colMin[B][A] is the requested width of columns between
  ** A+1 and B+1.  This information is used to add in the constraints imposed
  ** by <TD COLSPAN=N> markup where N>=2.
  */
  int colMin[HTML_MAX_COLUMNS+1][HTML_MAX_COLUMNS+1];
# define ColMin(A,B) colMin[(A)-1][(B)-1]
# define ColReq(A,B) colMin[(B)-1][(A)-1]
  
  if( pStart==0 || pStart->base.type!=Html_TABLE ){ 
    TestPoint(0); 
    return pStart;
  }
  TRACE_PUSH(HtmlTrace_Table1);
  TRACE(HtmlTrace_Table1, ("Starting TableDimensions..\n"));
  pStart->table.nCol = 0;
  pStart->table.nRow = 0;
  z = HtmlMarkupArg(pStart, "border", 0);
  if( z && *z==0 ) z = "2";
  tbw = pStart->table.borderWidth = z ? atoi(z) : DFLT_BORDER;
  cbw = tbw>0;
  z = HtmlMarkupArg(pStart, "cellpadding", 0);
  cellPadding = z ? atoi(z) : DFLT_CELLPADDING;
  cellSpacing = CellSpacing(htmlPtr, pStart);
#ifdef DEBUG
  /* The HtmlTrace_Table4 flag causes tables to be draw with borders
  ** of 2, cellPadding of 5 and cell spacing of 2.  This makes the
  ** table clearly visible.  Useful for debugging. */
  if( HtmlTraceMask & HtmlTrace_Table4 ){
    tbw = pStart->table.borderWidth = 2;
    cbw = 1;
    cellPadding = 5;
    cellSpacing = 2;
    pStart->base.style.bgcolor = COLOR_Background;
  }
#endif
  separation = cellSpacing + 2*(cellPadding + cbw);
  margin = tbw + cellSpacing + cbw + cellPadding;
  z = HtmlMarkupArg(pStart, "hspace", 0);
  hspace = z ? atoi(z) : DFLT_HSPACE;

  for(p=pStart->pNext; p && p->base.type!=Html_EndTABLE; p=pNext){
    pNext = p->pNext;
    switch( p->base.type ){
      case Html_EndTD:
      case Html_EndTH:
      case Html_EndTABLE:
        p->ref.pOther = pStart;
        TestPoint(0);
        break;
      case Html_EndTR:
        p->ref.pOther = pStart;
        inRow = 0;
        TestPoint(0);
        break;
      case Html_TR:
        p->ref.pOther = pStart;
        iRow++;
        pStart->table.nRow++;
        iCol = 0;
        inRow = 1;
        maxTableWidth = availWidth = lineWidth - 2*margin;
        TestPoint(0);
        break;
      case Html_CAPTION:
        while( p && p->base.type!=Html_EndTABLE 
               && p->base.type!=Html_EndCAPTION ){
          p = p->pNext;
          TestPoint(0);
        }
        break;
      case Html_TD:
      case Html_TH:
        if( !inRow ){
          /* If the <TR> markup is omitted, insert it. */
          HtmlElement *pNew = HtmlAlloc( sizeof(HtmlRef) );
          if( pNew==0 ) break;
          memset(pNew, 0, sizeof(HtmlRef));
          pNew->base = p->base;
          pNew->base.pNext = p;
          pNew->base.type = Html_TR;
          pNew->base.count = 0;
          p->base.pPrev->base.pNext = pNew;
          p->base.pPrev = pNew;
          pNext = pNew;
          break;
        }
        do{
          iCol++;
        }while( iCol <= pStart->table.nCol && fromAbove[iCol] > iRow );
        p->cell.pTable = pStart;
        colspan = p->cell.colspan;
        if( colspan==0 ){
          colspan = 1;
        }
        if( iCol + colspan - 1 > pStart->table.nCol ){
          int nCol = iCol + colspan - 1;
          if( nCol > HTML_MAX_COLUMNS ){
            nCol = HTML_MAX_COLUMNS;
          }
          for(i=pStart->table.nCol+1; i<=nCol; i++){
            fromAbove[i] = 0;
            pStart->table.minW[i] = 0;
            pStart->table.maxW[i] = 0;
            min0span[i] = 0;
            max0span[i] = 0;
            reqW[i] = 0;
            for(j=1; j<i; j++){
              ColMin(j,i) = 0;
              ColReq(j,i) = 0;
            }
          }
          pStart->table.nCol = nCol;
        }
        noWrap = HtmlMarkupArg(p, "nowrap", 0)!=0;
        pNext = MinMax(htmlPtr, p, &minW, &maxW, availWidth);
        p->cell.pEnd = pNext;
        if( (z = HtmlMarkupArg(p, "width", 0))!=0 ){
          for(i=0; isdigit(z[i]); i++){}
          if( strcmp(z,"*")==0 ){
            requestedW = availWidth;
          }else if( z[i]==0 ){
            requestedW = atoi(z);
          }else if( z[i]=='%' ){
            /* requestedW = (atoi(z)*availWidth + 99)/100; */
            requestedW = (atoi(z)*maxTableWidth + 99)/100;
          }
        }else{
          requestedW = 0;
        }
        TRACE(HtmlTrace_Table1,
          ("Row %d Column %d: min=%d max=%d req=%d stop at %s\n",
            iRow,iCol,minW,maxW,requestedW, HtmlTokenName(p->cell.pEnd)));
        if( noWrap ){
          minW = maxW;
        }
        if( iCol + p->cell.colspan <= HTML_MAX_COLUMNS ){
          int min = 0;
          if( p->cell.colspan==0 ){
            SETMAX( min0span[iCol], minW );
            SETMAX( max0span[iCol], maxW );
            min = min0span[iCol] + separation;
          }else if( colspan==1 ){
            SETMAX( pStart->table.minW[iCol], minW );
            SETMAX( pStart->table.maxW[iCol], maxW );
            SETMAX( reqW[iCol], requestedW );     
            min = pStart->table.minW[iCol] + separation;
          }else{
            int n = p->cell.colspan;
            SETMAX( ColMin(iCol,iCol+n-1), minW);
            SETMAX( ColReq(iCol,iCol+n-1), requestedW);
            min = minW + separation;
#if 0
            maxW = (maxW + (n - 1)*(1-separation))/n;
            for(i=iCol; i<iCol + n && i<HTML_MAX_COLUMNS; i++){
              SETMAX( pStart->table.maxW[i], maxW );
            }
#endif
          }
          availWidth -= min;
        }
        rowspan = p->cell.rowspan;
        if( rowspan==0 ){
          rowspan = LARGE_NUMBER;
        }
        if( rowspan>1 ){
          for(i=iCol; i<iCol + p->cell.colspan && i<HTML_MAX_COLUMNS; i++){
            fromAbove[i] = iRow + rowspan;
          }
        }
        if( p->cell.colspan > 1 ){
          iCol += p->cell.colspan - 1;
        }else if( p->cell.colspan==0 ){
          iCol = HTML_MAX_COLUMNS + 1;
        }
        break;
    }
  }

#ifdef DEBUG
  if( HtmlTraceMask & HtmlTrace_Table6 ){
    char *zSpace = "";
    TRACE_INDENT;
    for(i=1; i<=pStart->table.nCol; i++){
      printf("%s%d:%d..%d",zSpace,i,
         pStart->table.minW[i],pStart->table.maxW[i]);
      if( reqW[i]>0 ){
        printf("(w=%d)",reqW[i]);
      }
      zSpace = "  ";
    }
    printf("\n");
    for(i=1; i<pStart->table.nCol; i++){
      for(j=i+1; j<=pStart->table.nCol; j++){
        if( ColMin(i,j)>0 ){
          TRACE_INDENT;
          printf("ColMin(%d,%d) = %d\n", i, j, ColMin(i,j));
        }
        if( ColReq(i,j)>0 ){
          TRACE_INDENT;
          printf("ColReq(%d,%d) = %d\n", i, j, ColReq(i,j));
        }
      } 
    }
  }
#endif

  /* Compute the min and max width of each column
  */
  for(i=1; i<=pStart->table.nCol; i++){
    int sumMin, sumReq, sumMax;

    /* Reduce the max[] field to N for columns that have "width=N" */
    if( reqW[i]>0 ){
      pStart->table.maxW[i] = MAX(pStart->table.minW[i],reqW[i]);
    }

    /* Expand the width of columns marked with "colspan=0". 
    */
    if( min0span[i]>0 || max0span[i]>0 ){
      int n = pStart->table.nCol - i + 1;
      minW = (min0span[i] + (n - 1)*(1-separation))/n;
      maxW = (max0span[i] + (n - 1)*(1-separation))/n;
      for(j=i; j<=pStart->table.nCol; j++){
        SETMAX( pStart->table.minW[j], minW );
        SETMAX( pStart->table.maxW[j], maxW );
      }
    }

    /* Expand the minW[] of columns to accomodate "colspan=N" constraints.
    ** The minW[] is expanded up to the maxW[] first.  Then all the maxW[]s
    ** are expanded in proportion to their sizes.  The same thing occurs
    ** for reqW[]s.
    */
    sumReq = reqW[i];
    sumMin = pStart->table.minW[i];
    sumMax = pStart->table.maxW[i];
    for(j=i-1; j>=1; j--){
      int cmin, creq;
      sumMin += pStart->table.minW[j];
      sumMax += pStart->table.maxW[j];
      sumReq += reqW[i];
      cmin = ColMin(j,i);
      if( cmin>sumMin ){
        int k;
        double scale;
        int *tminW = pStart->table.minW;
        int *tmaxW = pStart->table.maxW;
        if( sumMin<sumMax ){
          scale = (double)(cmin - sumMin)/(double)(sumMax - sumMin);
          for(k=j; k<=i; k++){
            sumMin -= tminW[k];
            tminW[k] = (tmaxW[k] - tminW[k])*scale + tminW[k];
            sumMin += tminW[k];
          }
        }else if( sumMin>0 ){
          scale = (double)cmin/(double)sumMin;
          for(k=j; k<=i; k++){
            sumMin -= tminW[k];
            tminW[k] = tmaxW[k] = tminW[k]*scale;
            sumMin += tminW[k];
          }
        }else{
          int unit = cmin/(i-j+1);
          for(k=j; k<=i; k++){
            tminW[k] = tmaxW[k] = unit;
            sumMin += tminW[k];
          }
        }
      }
      creq = ColReq(j,i);
      if( creq>sumReq ){
        int k;
        double scale;
        int *tmaxW = pStart->table.maxW;
        if( sumReq<sumMax ){
          scale = (double)(creq - sumReq)/(double)(sumMax - sumReq);
          for(k=j; k<=i; k++){
            sumReq -= reqW[k];
            reqW[k] = (tmaxW[k] - reqW[k])*scale + reqW[k];
            sumReq += reqW[k];
          }
        }else if( sumReq>0 ){
          scale = (double)creq/(double)sumReq;
          for(k=j; k<=i; k++){
            sumReq -= reqW[k];
            reqW[k] = reqW[k]*scale;
            sumReq += reqW[k];
          }
        }else{
          int unit = creq/(i-j+1);
          for(k=j; k<=i; k++){
            reqW[k] = unit;
            sumReq += reqW[k];
          }
        }
      }
    }
  }

#ifdef DEBUG
  if( HtmlTraceMask & HtmlTrace_Table6 ){
    char *zSpace = "";
    TRACE_INDENT;
    for(i=1; i<=pStart->table.nCol; i++){
      printf("%s%d:%d..%d",zSpace,i,
         pStart->table.minW[i],pStart->table.maxW[i]);
      if( reqW[i]>0 ){
        printf("(w=%d)",reqW[i]);
      }
      zSpace = "  ";
    }
    printf("\n");
  }
#endif

  /* Compute the min and max width of the whole table
  */
  n = pStart->table.nCol;
  requestedW = tbw*2 + (n+1)*cellSpacing + n*2*(cellPadding + cbw);
  pStart->table.minW[0] = requestedW;
  pStart->table.maxW[0] = requestedW;
  for(i=1; i<=pStart->table.nCol; i++){
    pStart->table.minW[0] += pStart->table.minW[i];
    pStart->table.maxW[0] += pStart->table.maxW[i];
    requestedW += MAX(reqW[i], pStart->table.minW[i]);
  }

  /* Figure out how wide to draw the table */
  z = HtmlMarkupArg(pStart, "width", 0);
  if( z ){
    int len = strlen(z);
    int totalWidth;
    if( len>0 && z[len-1]=='%' ){
      totalWidth = (atoi(z) * lineWidth)/100;
    }else{
      totalWidth = atoi(z);
    }
    SETMAX( requestedW, totalWidth );
  }
  if( lineWidth && (requestedW > lineWidth) ){
    TRACE(HtmlTrace_Table5,("RequestedW reduced to lineWidth: %d -> %d\n", 
       requestedW, lineWidth));
    requestedW = lineWidth;
  }
  if( requestedW > pStart->table.minW[0] ){
    float scale;
    int *tminW = pStart->table.minW;
    int *tmaxW = pStart->table.maxW;
    TRACE(HtmlTrace_Table5,
        ("Expanding table minW from %d to %d.  (reqW=%d width=%s)\n",
          tminW[0], requestedW, requestedW, z));
    if( tmaxW[0] > tminW[0] ){
      scale = (double)(requestedW - tminW[0]) / (double)(tmaxW[0] - tminW[0]);
      for(i=1; i<=pStart->table.nCol; i++){
        tminW[i] += (tmaxW[i] - tminW[i]) * scale;
        SETMAX(tmaxW[i], tminW[i]);
      }
    }else if( tminW[0]>0 ){
      scale = requestedW/(double)tminW[0];
      for(i=1; i<=pStart->table.nCol; i++){
        tminW[i] *= scale;
        tmaxW[i] *= scale;
      }
    }else if( pStart->table.nCol>0 ){
      int unit = (requestedW - margin)/pStart->table.nCol - separation;
      if( unit<0 ) unit = 0;
      for(i=1; i<=pStart->table.nCol; i++){
        tminW[i] = tmaxW[i] = unit;
      }
    }else{
      tminW[0] = tmaxW[0] = requestedW;
    }
    pStart->table.minW[0] = requestedW;
    SETMAX( pStart->table.maxW[0], requestedW );
  }

#ifdef DEBUG
  if( HtmlTraceMask & HtmlTrace_Table5 ){
    TRACE_INDENT;
    printf("Start with %s and ", HtmlTokenName(pStart));
    printf("end with %s\n", HtmlTokenName(p));
    TRACE_INDENT;
    printf("nCol=%d minWidth=%d maxWidth=%d\n",
      pStart->table.nCol, pStart->table.minW[0], pStart->table.maxW[0]);
    for(i=1; i<=pStart->table.nCol; i++){
      TRACE_INDENT;
      printf("Column %d minWidth=%d maxWidth=%d\n",
         i, pStart->table.minW[i], pStart->table.maxW[i]);
    }
  }
#endif

  TRACE(HtmlTrace_Table1,
     ("Result of TableDimensions: min=%d max=%d nCol=%d\n",
     pStart->table.minW[0], pStart->table.maxW[0], pStart->table.nCol));
  TRACE_POP(HtmlTrace_Table1);
  return p;
}

/*
** Given a list of elements, compute the minimum and maximum width needed
** to render the list.  Stop the search at the first element seen that is
** in the following set:
**
**       <tr>  <td>  <th>  </tr>  </td>  </th>  </table>
**
** Return a pointer to the element that stopped the search, or to NULL
** if we ran out of data.
**
** Sometimes the value returned for both min and max will be larger than
** the true minimum and maximum.  This is rare, and only occurs if the
** element string contains figures with flow-around text.
*/
static HtmlElement *MinMax(
  HtmlWidget *htmlPtr,     /* The Html widget */
  HtmlElement *p,          /* Start the search here */
  int *pMin,               /* Return the minimum width here */
  int *pMax,               /* Return the maximum width here */
  int lineWidth            /* Total width available */
){
  int min = 0;             /* Minimum width so far */
  int max = 0;             /* Maximum width so far */
  int indent = 0;          /* Amount of indentation (minimum) */
  int obstacle = 0;        /* Possible obstacles in the margin */
  int x1 = 0;              /* Length of current line assuming maximum length */
  int x2 = 0;              /* Length of current line assuming minimum length */
  int go = 1;              /* Change to 0 to stop the loop */
  HtmlElement *pNext;      /* Next element in the list */

  for(p=p->pNext; go && p; p = pNext){
    pNext = p->pNext;
    switch( p->base.type ){
      case Html_Text:
        x1 += p->text.w;
        x2 += p->text.w;
        if( p->base.style.flags & STY_Preformatted ){
          SETMAX( min, x1 );
          SETMAX( max, x1 );
        }else{
          SETMAX( min, x2 );
          SETMAX( max, x1 );
        }
        break;
      case Html_Space:
        if( p->base.style.flags & STY_Preformatted ){
          if( p->base.flags & HTML_NewLine ){
            x1 = x2 = indent;
          }else{
            x1 += p->space.w * p->base.count;
            x2 += p->space.w * p->base.count;
          }
        }else if( p->base.style.flags & STY_NoBreak ){
          if( x1>indent ){ x1 += p->space.w; TestPoint(0);}
          if( x2>indent ){ x2 += p->space.w; TestPoint(0);}
        }else{
          if( x1>indent ){ x1 += p->space.w; TestPoint(0);}
          x2 = indent;
        }
        break;
      case Html_IMG:
        switch( p->image.align ){
          case IMAGE_ALIGN_Left:
          case IMAGE_ALIGN_Right:
            obstacle += p->image.w;
            x1 = obstacle + indent;
            x2 = indent;
            SETMAX( min, x2 );
            SETMAX( min, p->image.w );
            SETMAX( max, x1 );
            break;
          default:
            x1 += p->image.w;
            x2 += p->image.w;
            if( p->base.style.flags & STY_Preformatted ){
              SETMAX( min, x1 );
              SETMAX( max, x1 );
            }else{
              SETMAX( min, x2 );
              SETMAX( max, x1 );
            }
            break;
        }
        break;
      case Html_TABLE:
        /* pNext = TableDimensions(htmlPtr, p, lineWidth-indent); */
        pNext = TableDimensions(htmlPtr, p, 0);
        x1 = p->table.maxW[0] + indent + obstacle;
        x2 = p->table.minW[0] + indent;
        SETMAX( max, x1 );
        SETMAX( min, x2 );	
        x1 = indent + obstacle;
        x2 = indent;
        if( pNext && pNext->base.type==Html_EndTABLE ){
          pNext = pNext->pNext;
        }
        break;
      case Html_UL:
      case Html_OL:
        indent += HTML_INDENT;
        x1 = indent + obstacle;
        x2 = indent;
        break;
      case Html_EndUL:
      case Html_EndOL:
        indent -= HTML_INDENT;
        if( indent < 0 ){ indent = 0; }
        x1 = indent + obstacle;
        x2 = indent;
        break;
      case Html_BLOCKQUOTE:
        indent += 2*HTML_INDENT;
        x1 = indent + obstacle;
        x2 = indent;
        break;
      case Html_EndBLOCKQUOTE:
        indent -= 2*HTML_INDENT;
        if( indent < 0 ){ indent = 0; }
        x1 = indent + obstacle;
        x2 = indent;
        break;
      case Html_APPLET:
      case Html_INPUT:
      case Html_SELECT:
      case Html_EMBED:
      case Html_TEXTAREA:
        x1 += p->input.w + p->input.padLeft;
        if( p->base.style.flags & STY_Preformatted ){
          SETMAX( min, x1 );
          SETMAX( max, x1 );
          x2 += p->input.w + p->input.padLeft;
        }else{
          SETMAX( min, indent + p->input.w );
          SETMAX( max, x1 );
          x2 = indent;
        }
        break;
      case Html_BR:
      case Html_P:
      case Html_EndP:
      case Html_DIV:
      case Html_EndDIV:
      case Html_H1:
      case Html_EndH1:
      case Html_H2:
      case Html_EndH2:
      case Html_H3:
      case Html_EndH3:
      case Html_H4:
      case Html_EndH4:
      case Html_H5:
      case Html_H6:
        x1 = indent + obstacle;
        x2 = indent;
        break;
      case Html_EndTD:
      case Html_EndTH:
      case Html_CAPTION:
      case Html_EndTABLE:
      case Html_TD:
      case Html_TR:
      case Html_TH:
      case Html_EndTR:
        go = 0;
        break;
      default:
        break;
    }
    if( !go ){ break; }
  }
  *pMin = min;
  *pMax = max;
  return p;
}

/* Vertical alignments: 
*/
#define VAlign_Unknown    0
#define VAlign_Top        1
#define VAlign_Bottom     2
#define VAlign_Center     3
#define VAlign_Baseline   4

/*
** Return the vertical alignment specified by the given element.
*/
static int GetVerticalAlignment(HtmlElement *p, int dflt){
  char *z;
  int rc;
  if( p==0 ) return dflt;
  z = HtmlMarkupArg(p, "valign", 0);
  if( z==0 ){
    rc = dflt;
    TestPoint(0);
  }else if( stricmp(z,"top")==0 ){
    rc = VAlign_Top;
    TestPoint(0);
  }else if( stricmp(z,"bottom")==0 ){
    rc = VAlign_Bottom;
    TestPoint(0);
  }else if( stricmp(z,"center")==0 ){
    rc = VAlign_Center;
    TestPoint(0);
  }else if( stricmp(z,"baseline")==0 ){
    rc = VAlign_Baseline;
    TestPoint(0);
  }else{
    rc = dflt;
    TestPoint(0);
  }
  return rc;
}

/* Do all layout for a single table.  Return the </table> element or
** NULL if the table is unterminated.
*/
HtmlElement *HtmlTableLayout(
  HtmlLayoutContext *pLC,      /* The layout context */
  HtmlElement *pTable          /* The <table> element */
){
  HtmlElement *pEnd;      /* The </table> element */
  HtmlElement *p;         /* For looping thru elements of the table */
  HtmlElement *pNext;     /* Next element in the loop */
  HtmlElement *pCaption;  /* Start of the caption text.  The <caption> */
  HtmlElement *pEndCaption; /* End of the caption.  The </caption> */
  int width;              /* Width of the table as drawn */
  int cellSpacing;        /* Value of cellspacing= parameter to <table> */
  int cellPadding;        /* Value of cellpadding= parameter to <table> */
  int tbw;                /* Width of the 3D border around the whole table */
  int cbw;                /* Width of the 3D border around a cell */
  int pad;                /* cellPadding + borderwidth */
  char *z;                /* A string */
  int leftMargin;         /* The left edge of space available for drawing */
  int lineWidth;          /* Total horizontal space available for drawing */
  int separation;         /* Distance between content of columns (or rows) */
  int i;                  /* Loop counter */
  int n;                  /* Number of columns */
  int btm;                /* Bottom edge of previous row */
  int iRow;               /* Current row number */
  int iCol;               /* Current column number */
  int colspan;            /* Number of columns spanned by current cell */
  int vspace;             /* Value of the vspace= parameter to <table> */
  int hspace;             /* Value of the hspace= parameter to <table> */
  int rowBottom;          /* Bottom edge of content in the current row */
  int defaultVAlign;      /* Default vertical alignment for the current row */
  char *zAlign;           /* Value of the ALIGN= attribute of the <TABLE> */
#define N HTML_MAX_COLUMNS+1
  int y[N];               /* Top edge of each cell's content */
  int x[N];               /* Left edge of each cell's content */
  int w[N];               /* Width of each cell's content */
  int ymax[N];            /* Bottom edge of cell's content if valign=top */
  HtmlElement *apElem[N]; /* The <td> or <th> for each cell in a row */
  int firstRow[N];        /* First row on which a cell appears */
  int lastRow[N];         /* Row to which each cell span's */
  int valign[N];          /* Vertical alignment for each cell */
  HtmlLayoutContext savedContext;  /* Saved copy of the original pLC */
  HtmlLayoutContext cellContext;   /* Used to render a single cell */
#ifdef TABLE_TRIM_BLANK
	extern int HtmlLineWasBlank;
#endif /* TABLE_TRIM_BLANK */

  if( pTable==0 || pTable->base.type!=Html_TABLE ){ 
    TestPoint(0);
    return pTable;
  }
  TRACE_PUSH(HtmlTrace_Table2);
  TRACE(HtmlTrace_Table2, ("Starting TableLayout() at %s\n", 
                          HtmlTokenName(pTable)));

  /* Figure how much horizontal space is available for rendering 
  ** this table.  Store the answer in lineWidth.  leftMargin is
  ** the left-most X coordinate of the table.  btm stores the top-most
  ** Y coordinate.
  */
  HtmlComputeMargins(pLC, &leftMargin, &btm, &lineWidth);
  TRACE(HtmlTrace_Table2, ("...btm=%d left=%d width=%d\n",
                           btm, leftMargin, lineWidth));

  /* figure out how much space the table wants for each column,
  ** and in total.. */
  pEnd = TableDimensions(pLC->htmlPtr, pTable, lineWidth);

  /* If we don't have enough horizontal space to accomodate the minimum table
  ** width, then try to move down past some obstruction (such as an
  ** <IMG ALIGN=LEFT>) to give us more room.
  */
  if( lineWidth < pTable->table.minW[0] ){
    HtmlWidenLine(pLC, pTable->table.minW[0], &leftMargin, &btm, &lineWidth);
    TRACE(HtmlTrace_Table2, ("Widen to btm=%d left=%d width=%d\n", 
                             btm, leftMargin, lineWidth));
  }
  savedContext = *pLC;
  
  /* Figure out how wide to draw the table 
  */
  if( lineWidth < pTable->table.minW[0] ){
    width = pTable->table.minW[0];
  }else if( lineWidth <= pTable->table.maxW[0] ){
    width = lineWidth;
  }else{
    width = pTable->table.maxW[0];
  }


  /* Compute the width and left edge position of every column in
  ** the table */
  z = HtmlMarkupArg(pTable, "cellpadding", 0);
  cellPadding = z ? atoi(z) : DFLT_CELLPADDING;
  cellSpacing = CellSpacing(pLC->htmlPtr, pTable);
  z = HtmlMarkupArg(pTable, "vspace", 0);
  vspace = z ? atoi(z) : DFLT_VSPACE;
  z = HtmlMarkupArg(pTable, "hspace", 0);
  hspace = z ? atoi(z) : DFLT_HSPACE;
#ifdef DEBUG
  if( HtmlTraceMask & HtmlTrace_Table4 ){
    cellPadding = 5;
    cellSpacing = 2;
    if( vspace<2 ) vspace = 2;
    if( hspace<2 ) hspace = 2;
  }
#endif
  tbw = pTable->table.borderWidth;
  cbw = (tbw>0);
  pad = cellPadding + cbw;
  separation = cellSpacing + 2*pad;
  x[1] = leftMargin + tbw + cellSpacing + pad;
  n = pTable->table.nCol;
  if( n<=0 || pTable->table.maxW[0]<=0 ){
    /* Abort if the table has no columns at all or if the total width
    ** of the table is zero or less. */
    return pEnd;
  }
  zAlign = HtmlMarkupArg(pTable, "align", "");
  if( width < lineWidth ){
    int align = pTable->base.style.align;
    if( align==ALIGN_Right || stricmp(zAlign,"right")==0 ){
      x[1] += lineWidth - width;
    }else if( align==ALIGN_Center && stricmp(zAlign,"left")!=0 ){
      x[1] += (lineWidth - width)/2;
    }
  }
  if( width==pTable->table.maxW[0] ){
    w[1] = pTable->table.maxW[1];
    for(i=2; i<=n; i++){
      w[i] = pTable->table.maxW[i];
      x[i] = x[i-1] + w[i-1] + separation;
      TestPoint(0);
    }
  }else if( width > pTable->table.maxW[0] ){
    int *tmaxW = pTable->table.maxW;
    double scale = ((double)width)/ (double)tmaxW[0];
    w[1] = tmaxW[1] * scale;
    for(i=2; i<=n; i++){
      w[i] = tmaxW[i] * scale;
      x[i] = x[i-1] + w[i-1] + separation;
      TestPoint(0);
    }
  }else if( width > pTable->table.minW[0] ){
    float scale;
    int *tminW = pTable->table.minW;
    int *tmaxW = pTable->table.maxW;
    scale = (double)(width - tminW[0]) / (double)(tmaxW[0] - tminW[0]);
    w[1] = tminW[1] + (tmaxW[1] - tminW[1]) * scale;
    for(i=2; i<=n; i++){
      w[i] = tminW[i] + (tmaxW[i] - tminW[i]) * scale;
      x[i] = x[i-1] + w[i-1] + separation;
      TestPoint(0);
    }
  }else{
    w[1] = pTable->table.minW[1];
    for(i=2; i<=n; i++){
      w[i] = pTable->table.minW[i];
      x[i] = x[i-1] + w[i-1] + separation;
      TestPoint(0);
    }
  }
  w[n] = width - ((x[n] - x[1]) + 2*(tbw + pad + cellSpacing));

  /* Add notation to the pTable structure so that we will know where
  ** to draw the outer box around the outside of the table.
  */
  btm += vspace;
  pTable->table.y = btm;
  pTable->table.x = x[1] - (tbw + cellSpacing + pad);
  pTable->table.w = width;
  SETMAX(pLC->maxX, pTable->table.x + pTable->table.w);
  btm += tbw + cellSpacing;

  /* Begin rendering rows of the table */
  for(i=1; i<=n; i++){
    firstRow[i] = 0;
    lastRow[i] = 0;
    apElem[i] = 0;
  }
  p = pTable->pNext;
  rowBottom = btm;
  for(iRow=1; iRow<=pTable->table.nRow; iRow++){
    TRACE(HtmlTrace_Table2, ("Row %d: btm=%d\n",iRow,btm));
    /* Find the start of the next row.  Keep an eye out for the caption
    ** while we search */
    while( p && p->base.type!=Html_TR ){ 
      if( p->base.type==Html_CAPTION ){
        pCaption = p;
        while( p && p!=pEnd && p->base.type!=Html_EndCAPTION ){
          p = p->pNext;
        }
        pEndCaption = p;
      }
      TRACE(HtmlTrace_Table3, ("Skipping token %s\n", HtmlTokenName(p)));
      p = p->pNext; 
    }
    if( p==0 ){ TestPoint(0); break; }

    /* Record default vertical alignment flag for this row */
    defaultVAlign = GetVerticalAlignment(p, VAlign_Center);

    /* Find every new cell on this row */
    for(iCol=1; iCol<=pTable->table.nCol && iCol<HTML_MAX_COLUMNS; iCol++){
      if( lastRow[iCol]<iRow ) ymax[iCol] = 0;
    }
    iCol = 0;
    for(p=p->pNext; p && p->base.type!=Html_TR && p!=pEnd; p=pNext){
      pNext = p->pNext;
      TRACE(HtmlTrace_Table3, ("Processing token %s\n", HtmlTokenName(p)));
      switch( p->base.type ){
        case Html_TD:
        case Html_TH:
          /* Find the column number for this cell.  Be careful to skip
          ** columns which extend down to this row from prior rows */
          do{
            iCol++;
          }while( iCol <= HTML_MAX_COLUMNS && lastRow[iCol] >= iRow );
          TRACE(HtmlTrace_Table2,
            ("Column %d: x=%d w=%d\n",iCol,x[iCol],w[iCol]));
          /* Process the new cell.  (Cells beyond the maximum number of
          ** cells are simply ignored.) */
          if( iCol <= HTML_MAX_COLUMNS ){
            apElem[iCol] = p;
            pNext = p->cell.pEnd;
            if( p->cell.rowspan==0 ){
              lastRow[iCol] = pTable->table.nRow;
            }else{
              lastRow[iCol] = iRow + p->cell.rowspan - 1;
            }
            firstRow[iCol] = iRow;

            /* Set vertical alignment flag for this cell */
            valign[iCol] = GetVerticalAlignment(p, defaultVAlign);

            /* Render cell contents and record the height */
            y[iCol] = btm + pad;
            cellContext.htmlPtr = pLC->htmlPtr;
            cellContext.pStart = p->pNext;
            cellContext.pEnd = pNext;
            cellContext.headRoom = 0;
            cellContext.top = y[iCol];
            cellContext.bottom = y[iCol];
            cellContext.left = x[iCol];
            cellContext.right = 0;
            cellContext.pageWidth = x[iCol]+w[iCol];
            colspan = p->cell.colspan;
            if( colspan==0 ){
              for(i=iCol+1; i<=pTable->table.nCol; i++){
                cellContext.pageWidth += w[i] + separation;
                lastRow[i] = lastRow[iCol];
              }
            }else if( colspan>1 ){
              for(i=iCol+1; i<iCol+colspan; i++){
                cellContext.pageWidth += w[i] + separation;
                lastRow[i] = lastRow[iCol];
              }
            }
            cellContext.maxX = 0;
            cellContext.maxY = 0;
            cellContext.leftMargin = 0;
            cellContext.rightMargin = 0;
            HtmlLock(cellContext.htmlPtr);
            HtmlLayoutBlock(&cellContext);
            if( HtmlUnlock(cellContext.htmlPtr) ) return 0;
#ifdef TABLE_TRIM_BLANK
			/*
			 * Cancel any trailing vertical whitespace caused
			 * by break markup
			 */
			if (HtmlLineWasBlank)
				cellContext.maxY -= cellContext.headRoom;
#endif /* TABLE_TRIM_BLANK */
            ymax[iCol] = cellContext.maxY;
            SETMAX(ymax[iCol], y[iCol]);
            HtmlClearMarginStack(&cellContext.leftMargin);
            HtmlClearMarginStack(&cellContext.rightMargin);

            /* Set coordinates of the cell border */
            p->cell.x = x[iCol] - pad;
            p->cell.y = btm;
            p->cell.w = cellContext.pageWidth + 2*pad - x[iCol];
            TRACE(HtmlTrace_Table2,
              ("Column %d top=%d bottom=%d h=%d left=%d w=%d\n",
              iCol, y[iCol], ymax[iCol], ymax[iCol]-y[iCol], 
              p->cell.x, p->cell.w));

            /* Advance the column counter for cells spaning multiple columns */
            if( colspan > 1 ){
              iCol += colspan - 1;
            }else if( colspan==0 ){
              iCol = HTML_MAX_COLUMNS + 1;
            }
          }
          break;

        case Html_CAPTION:
          /* Gotta remember where the caption is so we can render it
          ** at the end */
          pCaption = p;
          while( pNext && pNext!=pEnd && pNext->base.type!=Html_EndCAPTION ){
            pNext = pNext->pNext;
          }
          pEndCaption = pNext;
          break;
      }
    }

    /* Figure out how high to make this row. */
    for(iCol=1; iCol<=pTable->table.nCol; iCol++){
      if( lastRow[iCol] == iRow || iRow==pTable->table.nRow ){
        SETMAX( rowBottom, ymax[iCol] );
      }
    }
    TRACE(HtmlTrace_Table2, ("Total row height: %d..%d -> %d\n",
                             btm,rowBottom,rowBottom-btm));

    /* Position every cell whose bottom edge ends on this row */
    for(iCol=1; iCol<=pTable->table.nCol; iCol++){
      int dy;    /* Extra space at top of cell used for vertical alignment */

      /* Skip any unused cells or cells that extend down thru 
      ** subsequent rows */
      if( apElem[iCol]==0 
      || (iRow!=pTable->table.nRow && lastRow[iCol]>iRow) ){  continue; }

      /* Align the contents of the cell vertically. */
      switch( valign[iCol] ){
        case VAlign_Unknown:
        case VAlign_Center:
          dy = (rowBottom - ymax[iCol])/2;
          break;
        case VAlign_Top:
        case VAlign_Baseline:
          dy = 0;
          break;
        case VAlign_Bottom:
          dy = rowBottom - ymax[iCol];
          break;
      }
      if( dy ){
        HtmlElement *pLast = apElem[iCol]->cell.pEnd;
        TRACE(HtmlTrace_Table3, ("Delta column %d by %d\n",iCol,dy));
        HtmlMoveVertically(apElem[iCol]->pNext, pLast, dy);
      }

      /* Record the height of the cell so that the border can be drawn */
      apElem[iCol]->cell.h = rowBottom + pad - apElem[iCol]->cell.y;
      apElem[iCol] = 0;
    }

    /* Update btm to the height of the row we just finished setting */
    btm = rowBottom + pad + cellSpacing;
  }

  btm += tbw;
  pTable->table.h = btm - pTable->table.y;
  SETMAX( pLC->maxY, btm );
  pLC->bottom = btm + vspace;

  /* Render the caption, if there is one */
  if( pCaption ){
  }

  /* Whenever we do any table layout, we need to recompute all the 
  ** HtmlBlocks.  The following statement forces this. */
  pLC->htmlPtr->firstBlock = pLC->htmlPtr->lastBlock = 0;

  /* Adjust the context for text that wraps around the table, if
  ** requested by an ALIGN=RIGHT or ALIGN=LEFT attribute.
  */
  if( stricmp(zAlign,"left")==0 ){
    savedContext.maxX = pLC->maxX;
    savedContext.maxY = pLC->maxY;
    *pLC = savedContext;
    HtmlPushMargin(&pLC->leftMargin, pTable->table.w + 2, 
        pTable->table.y + pTable->table.h + 2, 0);
  }else if( stricmp(zAlign,"right")==0 ){
    savedContext.maxX = pLC->maxX;
    savedContext.maxY = pLC->maxY;
    *pLC = savedContext;
    HtmlPushMargin(&pLC->rightMargin, pTable->table.w + 2, 
        pTable->table.y + pTable->table.h + 2, 0);
  }

  /* All done */
  TRACE(HtmlTrace_Table2, (
     "Done with TableLayout().  x=%d y=%d w=%d h=%d Return %s\n",
     pTable->table.x, pTable->table.y, pTable->table.w, pTable->table.h,
     HtmlTokenName(pEnd)));
  TRACE_POP(HtmlTrace_Table2);
  return pEnd;
}


/*
** Move all elements in the given list vertically by the amount dy
*/
void HtmlMoveVertically(
  HtmlElement *p,         /* First element to move */
  HtmlElement *pLast,     /* Last element.  Do move this one */
  int dy                  /* Amount by which to move */
){
  if( dy==0 ){ TestPoint(0); return; }
  while( p && p!=pLast ){
    switch( p->base.type ){
      case Html_A:
        p->anchor.y += dy;
        break;
      case Html_Text:
        p->text.y += dy;
        break;
      case Html_LI:
        p->li.y += dy;
        break;
      case Html_TD:
      case Html_TH:
        p->cell.y += dy;
        break;
      case Html_TABLE:
        p->table.y += dy;
        break;
      case Html_IMG:
        p->image.y += dy;
        break;
      case Html_INPUT:
      case Html_SELECT:
      case Html_APPLET:
      case Html_EMBED:
      case Html_TEXTAREA:
        p->input.y += dy;
        break;
      default:
        break;
    }
    p = p->pNext;
  }
}
