/*  document.c
* ===========================================================================
*
*                            PUBLIC DOMAIN NOTICE
*            National Center for Biotechnology Information (NCBI)
*
*  This software/database is a "United States Government Work" under the
*  terms of the United States Copyright Act.  It was written as part of
*  the author's official duties as a United States Government employee and
*  thus cannot be copyrighted.  This software/database is freely available
*  to the public for use. The National Library of Medicine and the U.S.
*  Government do not place any restriction on its use or reproduction.
*  We would, however, appreciate having the NCBI and the author cited in
*  any work or product based on this material
*
*  Although all reasonable efforts have been taken to ensure the accuracy
*  and reliability of the software and data, the NLM and the U.S.
*  Government do not and cannot warrant the performance or results that
*  may be obtained by using this software or data. The NLM and the U.S.
*  Government disclaim all warranties, express or implied, including
*  warranties of performance, merchantability or fitness for any particular
*  purpose.
*
* ===========================================================================
*
* File Name:  document.c
*
* Author:  Jonathan Kans
*   
* Version Creation Date: 4/12/93
*
* $Revision: 6.26 $
*
* File Description:  Converts fielded text into final report in a document
*
* Modifications:  
* --------------------------------------------------------------------------
* Date     Name        Description of modification
* -------  ----------  -----------------------------------------------------
*
* ==========================================================================
*/

#include <vibrant.h>
#include <document.h>
#include <ncbilang.h>

/*****************************************************************************
*
*   SYMBOL DEFINES
*
*****************************************************************************/

#define JUST_LEFT   0
#define JUST_CENTER 1
#define JUST_RIGHT  2

#define MAXLISTS  256
#define LISTSIZE  256
#define MAXFONTS   32

/*****************************************************************************
*
*   TYPE DEFINES
*
*****************************************************************************/

typedef struct colxdata {
  FonT          font;
  Int2          position;
  Int2          pixWidth;
  Int2          pixInset;
  Int2          charWidth;
  Int2          charInset;
  unsigned int  just      : 2;
  unsigned int  zeroWidth : 1;
  unsigned int  wrap      : 1;
  unsigned int  bar       : 1;
  unsigned int  underline : 1;
  unsigned int  left      : 1;
  unsigned int  last      : 1;
} ColXData, PNTR ColXPtr;

typedef struct itemdata {
  DocPrntProc   prtProc;
  Pointer       dataPtr;
  CharPtr       text;
  CharPtr       cached;
  FonT          font;
  ColXPtr       colFmt;
  VoidPtr       extra;
  Int4          startsAt;
  Int2          numRows;
  Int2          numCols;
  Int2          lineHeight;
  Int2          leadHeight;
  Int2          minLines;
  Int2          minHeight;
  unsigned int  openSpace    : 1;
  unsigned int  keepWithNext : 1;
  unsigned int  keepTogether : 1;
  unsigned int  newPage      : 1;
  unsigned int  tabStops     : 1;
  unsigned int  docOwnsData  : 1;
  unsigned int  notCached    : 1;
  unsigned int  neverCached  : 1;
} ItemData, PNTR ItemPtr;

typedef struct listdata {
  ItemData  items [LISTSIZE];
} ListData, PNTR ListDPtr;

typedef struct masterdata {
  ListDPtr  list [MAXLISTS];
} MasterData, PNTR MasterPtr;

typedef struct docdata {
  DoC           doc;
  MasterPtr     master;
  DocDrawProc   draw;
  DocCellProc   drawcell;
  DocPanProc    pan;
  DocShadeProc  gray;
  DocShadeProc  invert;
  DocShadeProc  color;
  DocPutProc    put;
  DocGetProc    get;
  DocUpdProc    upd;
  VoidPtr       data;
  DocFreeProc   cleanup;
  ValNodePtr    colFmts;
  Int2          numItems;
  Int4          numLines;
  Int4          barmax;
  Int2          pgUp;
  Int2          pgDn;
  Int2          tabCount;
  Boolean       autoAdjust;
  ParPtr        defaultParFmt;
  ColPtr        defaultColFmt;
  FonT          defaultFont;
  Int2          firstHighlightItem;
  Int2          lastHighlightItem;
  Boolean       useRowsNotItems;
  DocNotfyProc  notify;
} DocData, PNTR DocDataPtr;

/*****************************************************************************
*
*   justToChar
*       Converts justification parameter to character code
*
*****************************************************************************/

static Char  justToChar [4] = {'l', 'c', 'r', 'l'};

/*****************************************************************************
*
*   fontHeights
*       Cached list of fonts and font pixel heights
*
*****************************************************************************/

static struct fontheights {
  FonT  font;
  Int2  height;
} fontHeights [MAXFONTS];

/*****************************************************************************
*
*   GetItemPtr (ddatptr, item)
*       Returns an item pointer given an item number
*
*****************************************************************************/

static ItemPtr GetItemPtr (DocDataPtr ddatptr, Int2 item)

{
  Int2       index;
  Int2       list;
  ItemPtr    itemPtr;
  ListDPtr   listPtr;
  MasterPtr  masterPtr;

  itemPtr = NULL;
  if (ddatptr != NULL && ddatptr->master != NULL && item < ddatptr->numItems) {
    list = (item / LISTSIZE);
    if (list < MAXLISTS) {
      masterPtr = ddatptr->master;
      listPtr = masterPtr->list [list];
      if (listPtr != NULL) {
        index = (item % LISTSIZE);
        itemPtr = &(listPtr->items [index]);
      }
    }
  }
  return itemPtr;
}

/*****************************************************************************
*
*   GetItemNum (ddatptr, desired)
*       Returns the number of the item containing the desired line
*
*****************************************************************************/

static Int2 GetItemNum (DocDataPtr ddatptr, Int4 desired)

{
  Boolean  goOn;
  ItemPtr  itemPtr;
  Int4     left;
  Int4     mid;
  Int4     right;

  mid = 1;
  if (ddatptr != NULL) {
    left = 1;
    right = ddatptr->numItems;
    goOn = TRUE;
    while (left <= right && goOn) {
      mid = (left + right) / 2;
      itemPtr = GetItemPtr (ddatptr, mid - 1);
      if (itemPtr != NULL) {
        if (desired < itemPtr->startsAt + itemPtr->numRows) {
          right = mid - 1;
        }
        if (desired >= itemPtr->startsAt) {
          left = mid + 1;
        }
      } else {
        goOn = FALSE;
        mid = 1;
      }
    }
  }
  return mid - 1;
}

/*****************************************************************************
*
*   GetNextBlock (title, maxwid, byPixels, wordWrap, tabStops, tabCount)
*       Returns the number of characters in the next block, including
*       trailing spaces (which are trimmed back later)
*
*****************************************************************************/

static Int2 GetNextBlock (CharPtr title, Int2 maxwid, Boolean byPixels,
                          Boolean wordWrap, Boolean tabStops, Int2 tabCount)

{
  Char     ch;
  Int4     i;
  Boolean  isjapanese;
  Int4     j;
  Int4     wid;

  isjapanese = IsJapanese ();
  if (isjapanese) {
  i = 0;
  j = 0;
  wid = 0;
  if (title != NULL && maxwid > 0) {
    ch = title [i];
    while (ch != '\0' && ch != '\n' && ch != '\r' &&
           (ch != '\t' || tabStops) && wid <= maxwid) {
      if (wordWrap) {
        if (ch == '\t' && tabStops) {
          if (byPixels) {
            wid += CharWidth (' ') * tabCount;
          } else {
            wid += tabCount;
          }
        } else {
          if (byPixels) {
            wid += TextWidth(title + i, Nlm_LetterByte(title + i));
          } else {
            wid += Nlm_LetterByte(title + i);
          }
        }
      }
      i += Nlm_LetterByte(title + i);
      ch = title [i];
    }
    j = i;
/* sorry, 1 or 2 byte letters only. but isn't it enough ? */
#define BackIsMBLetter(str, i)	((i > 1) && (IsMBLetter(title + i - 2)))
#define BackStep(str, i)	(BackIsMBLetter(str, i) ? 2 : 1)
    if (wid > maxwid && wordWrap) {
      j -= BackStep(title, j);
      if (byPixels) {
        while (TextWidth (title, i) > maxwid) {
          i -= BackStep(title, i);
        }
      } else {
        while (i > maxwid) {
          i -= BackStep(title, i);
        }
      }
	  while (i > 0 && title[i - 1] != ' ' && title [i - 1] != '-' && 
										!(BackIsMBLetter(title, i))) {
        /* at least in Japanese, almost all of multibyte letters are
           good as a word wrap point. */
        i -= BackStep(title, i);
	  }
      while (title [i] == ' ') {
        i++;
      }
    }
  }
  } else {
  i = 0;
  j = 0;
  wid = 0;
  if (title != NULL && maxwid > 0) {
    ch = title [i];
    while (ch != '\0' && ch != '\n' && ch != '\r' &&
           (ch != '\t' || tabStops) && wid <= maxwid) {
      if (wordWrap) {
        if (ch == '\t' && tabStops) {
          if (byPixels) {
            wid += CharWidth (' ') * tabCount;
          } else {
            wid += tabCount;
          }
        } else {
          if (byPixels) {
            wid += CharWidth (ch);
          } else {
            wid++;
          }
        }
      }
      i++;
      ch = title [i];
    }
    j = i;
    if (wid > maxwid && wordWrap) {
      j--;
      if (byPixels) {
        while (TextWidth (title, i) > maxwid) {
          i--;
        }
      } else {
        while (i > maxwid) {
          i--;
        }
      }
      while (i > 0 && title [i - 1] != ' ' && title [i - 1] != '-') {
        i--;
      }
      while (title [i] == ' ') {
        i++;
      }
    }
  }
  }
  if (i > 0 && i < j) {
    return i;
  } else if (j > 0) {
    return j;
  } else {
    return 0;
  }
}

/*****************************************************************************
*
*   CELL STRUCTURE DEFINITIONS
*
*****************************************************************************/

#define CELLCHUNK 128

typedef struct celldata {
  Uint4  start;
  Int2   count;
} CellData, PNTR CellPtr;

typedef struct parsedata {
  Int4     numCells;
  CellPtr  cellPtr;
} ParseData, PNTR ParsePtr;

/*****************************************************************************
*
*   RecordCell (itemPtr, parsePtr, start, len, row, col)
*       Records the character position and length for a given cell
*
*****************************************************************************/

static void RecordCell (ItemPtr itemPtr, ParsePtr parsePtr,
                        Uint4 start, Int2 len, Int2 row, Int2 col)

{
  Int4     cell;
  CellPtr  cellPtr;
  Int4     newCells;
  Int4     numCells;

  if (itemPtr != NULL && parsePtr != NULL && col < itemPtr->numCols) {
    numCells = parsePtr->numCells;
    cellPtr = parsePtr->cellPtr;
    cell = (Int4) ((Int4) row * (Int4) (itemPtr->numCols) + (Int4) col);
    if (cell >= (Int4) numCells) {
      newCells = (cell / CELLCHUNK + 1) * CELLCHUNK;
      if (cellPtr != NULL) {
        cellPtr = (CellPtr) MemMore (cellPtr, sizeof (CellData) * newCells);
        if (cellPtr != NULL) {
          while (numCells < newCells) {
            cellPtr [numCells].start = 0;
            cellPtr [numCells].count = 0;
            numCells++;
          }
        }
      } else {
        cellPtr = (CellPtr) MemNew (sizeof (CellData) * newCells);
      }
      parsePtr->cellPtr = cellPtr;
      parsePtr->numCells = newCells;
    }
    if (cellPtr != NULL) {
      cellPtr [cell].start = start;
      cellPtr [cell].count = len;
    }
  }
}

/*****************************************************************************
*
*   ParseText (itemPtr, parsePtr, text, byPixels, tabCount)
*       Parses text that contains \t, \r and \n characters into a list of
*       cells organized by row and column
*
*****************************************************************************/

static void ParseText (ItemPtr itemPtr, ParsePtr parsePtr,
                       CharPtr text, Boolean byPixels, Int2 tabCount)

{
  Int2     blklen;
  Char     ch;
  Int2     col;
  ColXPtr  colFmt;
  FonT     curFont;
  FonT     fnt;
  Int2     inset;
  Int2     insetLeft;
  Int2     insetRight;
  Char     just;
  Int2     len;
  Int2     maxwid;
  Int2     numCols;
  Int2     numRows;
  Int2     returnRow;
  Int2     row;
  Uint4    start;
  Int2     tabRow;
  Boolean  tabStops;
  Int2     width;
  Boolean  wrap;

  if (itemPtr != NULL && parsePtr != NULL && text != NULL && *text != '\0') {
    curFont = NULL;
    colFmt = itemPtr->colFmt;
    if (colFmt != NULL) {
      fnt = itemPtr->font;
      if (byPixels) {
        if (fnt != NULL) {
          SelectFont (fnt);
          curFont = fnt;
        } else {
          SelectFont (systemFont);
          curFont = systemFont;
        }
      }
      start = 0;
      row = 0;
      tabRow = 0;
      returnRow = 1;
      col = 0;
      tabStops = itemPtr->tabStops;
      numRows = 0;
      numCols = itemPtr->numCols;
      while (text [start] != '\0') {
        len = 0;
        wrap = FALSE;
        ch = text [start];
        if (ch != '\0' && ch != '\n' && ch != '\r' && (ch != '\t' || tabStops)) {
          /* note - maxwid has to be less than INT2_MAX less the width of one character, or it will roll to negative */
          maxwid = INT2_MAX - 16;
          if (col < numCols) {
            if (byPixels) {
              width = colFmt [col].pixWidth;
              inset = colFmt [col].pixInset;
            } else {
              width = colFmt [col].charWidth;
              inset = colFmt [col].charInset;
            }
            just = justToChar [colFmt [col].just];
            wrap = colFmt [col].wrap;
            fnt = colFmt [col].font;
          } else {
            width = 0;
            inset = 0;
            just = 'l';
            wrap = FALSE;
            fnt = NULL;
          }
          if (just == 'c') {
            insetLeft = inset;
            insetRight = inset;
          } else if (just == 'l') {
            insetLeft = inset;
            insetRight = 0;
          } else if (just == 'r') {
            insetLeft = 0;
            insetRight = inset;
          } else {
            insetLeft = 0;
            insetRight = 0;
          }
          if (byPixels) {
            if (col < numCols && wrap && width > 0 &&
                width >= 2 + insetLeft + insetRight) {
              maxwid = width - 2 - insetLeft - insetRight;
            }
          } else {
            if (col < numCols && wrap && width > 0 &&
                width >= insetLeft + insetRight) {
              maxwid = width - insetLeft - insetRight;
            }
          }
          if (byPixels) {
            if (fnt != NULL) {
              if (fnt != curFont) {
                SelectFont (fnt);
                curFont = fnt;
              }
            } else {
              fnt = itemPtr->font;
              if (fnt != NULL) {
                if (fnt != curFont) {
                  SelectFont (fnt);
                  curFont = fnt;
                }
              }
            }
          }
          blklen = GetNextBlock (text + start, maxwid, byPixels,
                                 wrap, tabStops, tabCount);
          len = blklen;
          if (len > 0) {
            if (text [start + len] != '\0') {
              while (len > 0 && text [start + len - 1] == ' ') {
                len--;
              }
              if (len == 0) {
                len = blklen;
              }
            }
            RecordCell (itemPtr, parsePtr, start, len, row, col);
            if (len > 0 && col < numCols && colFmt [col].zeroWidth) {
              if (byPixels) {
                colFmt [col].pixWidth = TextWidth (text + start, len) +
                                        CharWidth (' ') + 2;
              } else {
                colFmt [col].charWidth = len + 1;
              }
            }
            start += blklen;
          } else {
            if (byPixels) {
              while (TextWidth (text + start, len) <= maxwid) {
                len++;
              }
            } else {
              while (len <= maxwid) {
                len++;
              }
            }
            len--;
            if (len > 0) {
              RecordCell (itemPtr, parsePtr, start, len, row, col);
            }
            ch = text [start];
            while (ch != '\0' && ch != '\n' && ch != '\r' && ch != '\t') {
              start++;
              ch = text [start];
            }
          }
        }
        ch = text [start];
        if (ch == '\n') {
          start++;
          row = returnRow;
          tabRow = row;
          returnRow++;
          col = 0;
        } else if (ch == '\r') {
          start++;
          row++;
          returnRow = MAX (returnRow, row + 1);
        } else if (ch == '\t') {
          start++;
          row = tabRow;
          col++;
          if (text [start] == '\0') {
            row = returnRow;
          }
        } else if (ch != '\0' && wrap) {
          if (len == 0) {
            start++;
          }
          row++;
          returnRow = MAX (returnRow, row + 1);
        } else if (ch != '\0') {
          start++;
        } else if (text [start - 1] != '\n' && text [start - 1] != '\r') {
          row = returnRow;
        }
        numRows = MAX (numRows, row);
      }
      itemPtr->numRows = MAX (numRows, itemPtr->minLines);
    }
    SelectFont (systemFont);
  } else if (itemPtr != NULL) {
    itemPtr->numRows = 0;
  }
}

/*****************************************************************************
*
*   RearrangeText (itemPtr, parsePtr, text)
*       Reconstructs text in row and column order so that the list of cells
*       does not need to persist
*
*****************************************************************************/

static void RearrangeText (ItemPtr itemPtr, ParsePtr parsePtr, CharPtr text)

{
  Int4     cell;
  CellPtr  cellPtr;
  Int2     col;
  ColXPtr  colFmt;
  CharPtr  dst;
  Int4     len;
  Int2     minLines;
  Int4     numCells;
  Int2     numCols;
  Int2     numRows;
  Int2     row;
  CharPtr  src;
  CharPtr  str;

  if (itemPtr != NULL && parsePtr != NULL && text != NULL && *text != '\0') {
    colFmt = itemPtr->colFmt;
    if (colFmt != NULL) {
      numRows = itemPtr->numRows;
      numCols = itemPtr->numCols;
      minLines = itemPtr->minLines;
      if (parsePtr->numCells > 0 && parsePtr->cellPtr != NULL) {
        cellPtr = parsePtr->cellPtr;
        numCells = parsePtr->numCells;
        len = (Int4) (numRows * numCols);
        for (row = 0; row < numRows; row++) {
          for (col = 0; col < numCols; col++) {
            cell = (Int4) ((Int4) row * (Int4) numCols + (Int4) col);
            if (cell < (Int4) numCells) {
              len += (Int4) (cellPtr [cell].count);
            }
          }
        }
        len += (Int4) minLines + (Int4) 4;
        str = NULL;
        if ((size_t)len < SIZE_MAX) {
          str = (CharPtr) MemNew ((size_t) (Uint4) len);
          if (str != NULL) {
            dst = str;
            for (row = 0; row < numRows; row++) {
              for (col = 0; col < numCols; col++) {
                cell = (Int4) ((Int4) row * (Int4) numCols + (Int4) col);
                if (cell < numCells) {
                  if (col > 0) {
                    *dst = '\t';
                    dst++;
                  }
                  src = text + cellPtr [cell].start;
                  len = (Int4) (cellPtr [cell].count);
                  while (len > 0) {
                    *dst = *src;
                    dst++;
                    src++;
                    len--;
                  }
                }
              }
              *dst = '\n';
              dst++;
            }
            while (row < minLines) {
              *dst = '\n';
              dst++;
              row++;
            }
            *dst = '\0';
          }
        }
        itemPtr->text = str;
      }
    }
  }
}

/*****************************************************************************
*
*   FormatText (ddatptr, item, itemPtr, byPixels, tabCount)
*       Calls the print function for the item, then parses and rearranges
*       the text so that it can be displayed, saved, or printed
*
*****************************************************************************/

static void FormatText (DocDataPtr ddatptr, Int2 item, ItemPtr itemPtr,
                        Boolean byPixels, Int2 tabCount)

{
  Boolean    needToParse;
  ParseData  parseData;
  CharPtr    text;

  if (itemPtr != NULL && itemPtr->text == NULL && itemPtr->prtProc != NULL) {
    needToParse = TRUE;
    text = NULL;
    if (ddatptr != NULL && ddatptr->get != NULL) {
      text = ddatptr->get (ddatptr->doc, item);
      if (text != NULL) {
        needToParse = FALSE;
      } else {
        text = itemPtr->prtProc (ddatptr->doc, item, itemPtr->dataPtr);
      }
    } else if (ddatptr != NULL) {
      text = itemPtr->prtProc (ddatptr->doc, item, itemPtr->dataPtr);
    }
    if (needToParse && text != NULL && *text != '\0') {
      parseData.numCells = 0;
      parseData.cellPtr = NULL;
      ParseText (itemPtr, &parseData, text, byPixels, tabCount);
      RearrangeText (itemPtr, &parseData, text);
      itemPtr->notCached = FALSE;
      itemPtr->neverCached = FALSE;
      MemFree (text);
      MemFree (parseData.cellPtr);
    } else {
      itemPtr->text = text;
    }
    text = itemPtr->text;
    if (text == NULL || *text == '\0') {
      MemFree (itemPtr->text);
      itemPtr->text = StringSave (" \n");
      itemPtr->numRows = 1;
      itemPtr->notCached = FALSE;
      itemPtr->neverCached = FALSE;
      needToParse = TRUE;
    }
    if (needToParse && ddatptr != NULL && ddatptr->put != NULL) {
      ddatptr->put (ddatptr->doc, item, itemPtr->text);
    }
  }
}

/*****************************************************************************
*
*   GetStartsAt (ddatptr, item)
*       Returns the first line of the item
*
*****************************************************************************/

static Int4 GetStartsAt (DocDataPtr ddatptr, Int2 item)

{
  Int4     startsAt;
  ItemPtr  itemPtr;

  startsAt = 0;
  if (ddatptr != NULL) {
    itemPtr = GetItemPtr (ddatptr, item);
    if (itemPtr != NULL) {
      startsAt = itemPtr->startsAt;
    }
  }
  return startsAt;
}

/*****************************************************************************
*
*   GetLineHeight (ddatptr, item)
*       Returns the pixel height of the item
*
*****************************************************************************/

static Int2 GetLineHeight (DocDataPtr ddatptr, Int2 item)

{
  ItemPtr  itemPtr;
  Int2     lineHeight;

  lineHeight = 0;
  if (ddatptr != NULL) {
    itemPtr = GetItemPtr (ddatptr, item);
    if (itemPtr != NULL) {
      lineHeight = itemPtr->lineHeight;
    }
  }
  return lineHeight;
}

/*****************************************************************************
*
*   SetTablePixFormat (colFmt, left, numCols)
*       Uses pixel widths to calculate the positions of columns for drawing
*
*****************************************************************************/

static void SetTablePixFormat (ColXPtr colFmt, Int2 left, Int2 numCols)

{
  Int2  i;

  if (colFmt != NULL) {
    colFmt [0].position = left;
    for (i = 1; i < numCols; i++) {
      if (colFmt [i].left) {
        colFmt [i].position = left;
      } else {
        colFmt [i].position = colFmt [i - 1].position + colFmt [i - 1].pixWidth;
      }
    }
  }
}

/*****************************************************************************
*
*   SetTableCharFormat (colFmt, numCols)
*       Uses character counts to calculate the positions of columns for saving
*
*****************************************************************************/

static void SetTableCharFormat (ColXPtr colFmt, Int2 numCols)

{
  Int2  i;

  if (colFmt != NULL) {
    colFmt [0].position = 0;
    for (i = 1; i < numCols; i++) {
      if (colFmt [i].left) {
        colFmt [i].position = 0;
      } else {
        colFmt [i].position = colFmt [i - 1].position + colFmt [i - 1].charWidth;
      }
    }
  }
}

/*****************************************************************************
*
*   UpdateLineStarts (ddatptr, frst)
*       Recalculates the first lines as a running sum of the number of rows
*
*****************************************************************************/

static Int4 UpdateLineStarts (DocDataPtr ddatptr, Int2 frst)

{
  Int2     i;
  ItemPtr  itemPtr;
  Int4     totalNumLines;

  totalNumLines = 0;
  if (ddatptr != NULL) {
    itemPtr = GetItemPtr (ddatptr, frst);
    if (itemPtr != NULL) {
      totalNumLines = itemPtr->startsAt;
    }
    for (i = frst; i < ddatptr->numItems; i++) {
      itemPtr = GetItemPtr (ddatptr, i);
      if (itemPtr != NULL) {
        itemPtr->startsAt = totalNumLines;
        totalNumLines += itemPtr->numRows;
      }
    }
  }
  return totalNumLines;
}

/*****************************************************************************
*
*   CollectRange (item, lowest, highest)
*       Records the range of items that were manipulated for the purpose of
*       updating line starts or uncaching formatted text on items that are
*       not currently visible
*
*****************************************************************************/

static void CollectRange (Int2 item, Int2Ptr lowest, Int2Ptr highest)

{
  if (lowest != NULL) {
    *lowest = MIN (*lowest, item);
  }
  if (highest != NULL) {
    *highest = MAX (*highest, item);
  }
}

/*****************************************************************************
*
*   CacheAndFormat (ddatptr, item, itemPtr, r, tabCount)
*       Formats text (for drawing) if the item text is not currently cached
*
*****************************************************************************/

static void CacheAndFormat (DocDataPtr ddatptr, Int2 item, ItemPtr itemPtr,
                            RectPtr r, Int2 tabCount)

{
  if (itemPtr != NULL && r != NULL && itemPtr->notCached) {
    SetTablePixFormat (itemPtr->colFmt, r->left, itemPtr->numCols);
    FormatText (ddatptr, item, itemPtr, TRUE, tabCount);
  }
}

/*****************************************************************************
*
*   CacheIfNever (ddatptr, item, itemPtr, r, tabCount)
*       Formats text (for obtaining the correct number of rows) if the item
*       text has never been cached
*
*****************************************************************************/

static void CacheIfNever (DocDataPtr ddatptr, Int2 item, ItemPtr itemPtr,
                          RectPtr r, Int2 tabCount)

{
  if (itemPtr != NULL && r != NULL && itemPtr->neverCached) {
    SetTablePixFormat (itemPtr->colFmt, r->left, itemPtr->numCols);
    FormatText (ddatptr, item, itemPtr, TRUE, tabCount);
  }
}

/*****************************************************************************
*
*   FreeCachedItem (ddatptr, item, lowest, highest)
*       Frees the formatted text string for an item that is not currently
*       visible
*
*****************************************************************************/

static void FreeCachedItem (DocDataPtr ddatptr, Int2 item,
                            Int2Ptr lowest, Int2Ptr highest)

{
  ItemPtr  itemPtr;

  if (ddatptr != NULL) {
    itemPtr = GetItemPtr (ddatptr, item);
    if (itemPtr != NULL) {
      itemPtr->text = (CharPtr) MemFree (itemPtr->text);
      itemPtr->notCached = TRUE;
    }
  }
}

/*****************************************************************************
*
*   VisLinesAbove (ddatptr, r, item, line, lowest, highest)
*       Returns the number of lines visible on the screen if the given line
*       in the given item is the last line visible
*
*****************************************************************************/

static Int2 VisLinesAbove (DocDataPtr ddatptr, RectPtr r, Int2 item,
                           Int2 line, Int2Ptr lowest, Int2Ptr highest)

{
  ItemPtr  itemPtr;
  Int2     pixels;
  Int2     vis;

  vis = 0;
  if (ddatptr != NULL && r != NULL && item < ddatptr->numItems) {
    pixels = r->bottom - r->top;
    while (pixels > 0 && item >= 0) {
      itemPtr = GetItemPtr (ddatptr, item);
      if (itemPtr != NULL) {
        CollectRange (item, lowest, highest);
        CacheIfNever (ddatptr, item + 1, itemPtr, r, ddatptr->tabCount);
        if (line < 0) {
          line = itemPtr->numRows - 1;
        }
        while (pixels > 0 && line >= 0 && line < itemPtr->numRows) {
          pixels -= itemPtr->lineHeight;
          if (pixels >= 0) {
            vis++;
          }
          line--;
        }
        pixels -= itemPtr->leadHeight;
      }
      item--;
    }
  }
  return vis;
}

/*****************************************************************************
*
*   VisLinesBelow (ddatptr, r, item, line, lowest, highest)
*       Returns the number of lines visible on the screen if the given line
*       in the given item is the first line visible
*
*****************************************************************************/

static Int2 VisLinesBelow (DocDataPtr ddatptr, RectPtr r, Int2 item,
                           Int2 line, Int2Ptr lowest, Int2Ptr highest)

{
  ItemPtr  itemPtr;
  Int2     pixels;
  Int2     vis;

  vis = 0;
  if (ddatptr != NULL && r != NULL && item < ddatptr->numItems && item >= 0) {
    pixels = r->bottom - r->top;
    while (pixels > 0 && item < ddatptr->numItems) {
      itemPtr = GetItemPtr (ddatptr, item);
      if (itemPtr != NULL) {
        CollectRange (item, lowest, highest);
        CacheIfNever (ddatptr, item + 1, itemPtr, r, ddatptr->tabCount);
        if (line  < 0) {
          line = 0;
          pixels -= itemPtr->leadHeight;
        }
        while (pixels > 0 && line >= 0 && line < itemPtr->numRows) {
          pixels -= itemPtr->lineHeight;
          if (pixels >= 0) {
            vis++;
          }
          line++;
        }
        line = -1;
      }
      item++;
    }
  }
  return vis;
}

/*****************************************************************************
*
*   PixelsBetween (ddatptr, r, firstLine, lastLine, lowest, highest)
*       Returns the number of pixels between two lines
*
*****************************************************************************/

static Int2 PixelsBetween (DocDataPtr ddatptr, RectPtr r, Int4 firstLine,
                           Int4 lastLine, Int2Ptr lowest, Int2Ptr highest)

{
  Int2     count;
  Int2     item;
  ItemPtr  itemPtr;
  Int4     line;
  Int4     numLines;
  Int2     onItem;
  Int2     pixels;

  pixels = 0;
  if (ddatptr != NULL && r != NULL) {
    count = ABS (lastLine - firstLine);
    item = GetItemNum (ddatptr, MIN (firstLine, lastLine));
    itemPtr = GetItemPtr (ddatptr, item);
    onItem = item;
    if (itemPtr != NULL && item >= 0) {
      CollectRange (item, lowest, highest);
      CacheIfNever (ddatptr, item + 1, itemPtr, r, ddatptr->tabCount);
      line = MIN (firstLine, lastLine) - itemPtr->startsAt;
      while (count > 0 && item < ddatptr->numItems) {
        if (item != onItem) {
          itemPtr = GetItemPtr (ddatptr, item);
          onItem = item;
        }
        numLines = 0;
        if (itemPtr != NULL) {
          CollectRange (item, lowest, highest);
          CacheIfNever (ddatptr, item + 1, itemPtr, r, ddatptr->tabCount);
          numLines = itemPtr->numRows;
          while (count > 0 && line >= 0 && line < numLines) {
            pixels += itemPtr->lineHeight;
            line++;
            count--;
          }
        }
        item++;
        if (line >= numLines) {
          itemPtr = GetItemPtr (ddatptr, item);
          onItem = item;
          if (itemPtr != NULL) {
            CollectRange (item, lowest, highest);
            CacheIfNever (ddatptr, item + 1, itemPtr, r, ddatptr->tabCount);
            pixels += itemPtr->leadHeight;
          }
        }
        line = 0;
      }
      if (lastLine < firstLine) {
        pixels = -pixels;
      }
    }
  }
  return pixels;
}

/*****************************************************************************
*
*   DrawTableItem (d, ddatptr, itemPtr, r, item, frst, grayProc,
                   invertProc, colorProc, force, tabCount)
*       Draws a given item on the screen, suppressing partial lines
*
*****************************************************************************/

static Boolean is_old_win = FALSE;
#ifdef OS_MSWIN
static Boolean old_win_set = FALSE;
#endif

static Int2 DrawTableItem (DoC d, DocDataPtr ddatptr, ItemPtr itemPtr, RectPtr r,
                          Int2 item, Int2 frst, DocShadeProc grayProc,
                          DocShadeProc invertProc, DocShadeProc colorProc,
                          Boolean force, Int2 tabCount)

{
  Int2     barBottom;
  Int2     barTop;
  Char     ch;
  Int2     col;
  Boolean  color;
  ColXPtr  colFmt;
  FonT     curFont;
  Uint4    currentColor;
  Boolean  drawBars;
  RecT     drw;
  FonT     fnt;
  Boolean  gray;
  Int2     insetLeft;
  Int2     insetRight;
  Boolean  invert;
  Char     just;
  Int2     lineHeight;
  Int2     num;
  CharPtr  ptr;
  RecT     rct;
  Int2     row;
  Int2     rsult;
  CharPtr  text;
  CharPtr  tmp;

#ifdef OS_MSWIN
  if (! old_win_set) {
    text = GetOpSysString ();
    if (StringICmp (text, "MS WINDOWS 95/98/Me") == 0 ||
        StringICmp (text, "MS WINDOWS 3.1") == 0) {
      is_old_win = TRUE;
    }
    text = MemFree (text);
    old_win_set = TRUE;
  }
#endif

  rsult = 0;
  if (d != NULL && ddatptr != NULL && itemPtr != NULL && r != NULL) {
    text = itemPtr->text;
    colFmt = itemPtr->colFmt;
    if (text != NULL && *text != '\0' && colFmt != NULL) {
      fnt = itemPtr->font;
      if (fnt != NULL) {
        curFont = fnt;
      } else {
        curFont = systemFont;
      }
      SelectFont (curFont);
      lineHeight = itemPtr->lineHeight;
      rct.top = r->top;
      rct.bottom = rct.top;
      row = 0;
      col = 0;
      ptr = text;
      ch = *ptr;
      currentColor = 0;
      if (colorProc != NULL && (! force)) {
        currentColor = GetColor ();
      }
      drawBars = FALSE;
      barTop = INT2_MAX;
      barBottom = INT2_MIN;
      while (ch != '\0') {
        if (ch == '\n') {
          rct.top = r->top + (row - frst) * lineHeight;
          rct.bottom = rct.top + lineHeight;
          InvertMode ();
          for (col = 0; col < itemPtr->numCols; col++) {
            rct.left = colFmt [col].position;
            rct.right = rct.left + colFmt [col].pixWidth;
            if (colFmt [col].bar) {
              drawBars = TRUE;
              barTop = MIN (barTop, rct.top);
              barBottom = MAX (barBottom, rct.bottom);
            }
            if (colFmt [col].underline) {
              MoveTo (rct.left, rct.bottom - 1);
              LineTo (rct.right, rct.bottom - 1);
            }
          }
          CopyMode ();
          col = 0;
          row++;
          ptr++;
        } else if (ch == '\t' && (! (itemPtr->tabStops))) {
          col++;
          ptr++;
          ch = *ptr;
          if (ch == '\t' && (! (itemPtr->tabStops)) &&
              row >= frst && col < itemPtr->numCols) {
            rct.top = r->top + (row - frst) * lineHeight;
            rct.bottom = rct.top + lineHeight;
            rct.left = colFmt [col].position;
            rct.right = rct.left + colFmt [col].pixWidth;
            LoadRect (&drw, MAX (rct.left, r->left), rct.top,
                      MIN (rct.right, r->right), rct.bottom);
            if (RectInRect (&drw, r) &&
                (force || updateRgn == NULL || RectInRgn (&rct, updateRgn))) {
              invert = FALSE;
              if (invertProc != NULL && (! force)) {
                invert = invertProc (d, item + 1, row + 1, col + 1);
              } else if (ddatptr->useRowsNotItems) {
                invert = (Boolean) (ddatptr->firstHighlightItem <= row + 1 &&
                                    ddatptr->lastHighlightItem >= row + 1);
              } else {
                invert = (Boolean) (ddatptr->firstHighlightItem <= item + 1 &&
                                    ddatptr->lastHighlightItem >= item + 1);
              }
              if (invert) {
                InvertColors ();
                EraseRect (&rct);
                InvertColors ();
              }
            }
          }
        } else  {
          num = 0;
          while (ch != '\0' && (ch != '\t' || itemPtr->tabStops) && ch != '\n') {
            num++;
            ch = ptr [num];
          }
          if (row >= frst && col < itemPtr->numCols) {
            rct.top = r->top + (row - frst) * lineHeight;
            rct.bottom = rct.top + lineHeight;
            rct.left = colFmt [col].position;
            rct.right = rct.left + colFmt [col].pixWidth;
            LoadRect (&drw, MAX (rct.left, r->left), rct.top,
                      MIN (rct.right, r->right), rct.bottom);
            if (RectInRect (&drw, r) &&
                (force || updateRgn == NULL || RectInRgn (&rct, updateRgn))) {
              just = justToChar [colFmt [col].just];
              if (just == 'c') {
                insetLeft = colFmt [col].pixInset;
                insetRight = colFmt [col].pixInset;
              } else if (just == 'l') {
                insetLeft = colFmt [col].pixInset;
                insetRight = 0;
              } else if (just == 'r') {
                insetLeft = 0;
                insetRight = colFmt [col].pixInset;
              } else {
                insetLeft = 0;
                insetRight = 0;
              }
              fnt = colFmt [col].font;
              if (fnt != NULL) {
                if (fnt != curFont) {
                  curFont = fnt;
                  SelectFont (curFont);
                }
              } else {
                fnt = itemPtr->font;
                if (fnt != NULL) {
                  if (fnt != curFont) {
                    curFont = fnt;
                    SelectFont (curFont);
                  }
                }
              }
              color = FALSE;
              if (colorProc != NULL && (! force)) {
                color = colorProc (d, item + 1, row + 1, col + 1);
                if (! color) {
                  SetColor (currentColor);
                }
              }
              invert = FALSE;
              if (invertProc != NULL && (! force)) {
                invert = invertProc (d, item + 1, row + 1, col + 1);
              } else if (ddatptr->useRowsNotItems) {
                invert = (Boolean) (ddatptr->firstHighlightItem <= row + 1 &&
                                    ddatptr->lastHighlightItem >= row + 1);
              } else {
                invert = (Boolean) (ddatptr->firstHighlightItem <= item + 1 &&
                                    ddatptr->lastHighlightItem >= item + 1);
              }
              gray = FALSE;
              if (grayProc != NULL && (! force)) {
                gray = grayProc (d, item + 1, row + 1, col + 1);
              }
              SelectFont (curFont);
              if (invert) {
                InvertColors ();
                EraseRect (&rct);
              }
              rct.left += insetLeft;
              rct.right -= insetRight;
              drw = rct;
              if (num > 0) {
                while (num > 0 && *ptr == '\t') {
                  num--;
                  ptr++;
                  drw.left += CharWidth (' ') * tabCount;
                }
                if (ddatptr->drawcell != NULL) {
                  tmp = (CharPtr) MemNew((size_t)(num + 2));
                  if (tmp != NULL) {
                    MemCopy (tmp, ptr, num);
                    ddatptr->drawcell (d, item + 1, row + 1, col + 1, &drw, tmp, just, gray);
                    MemFree (tmp);
                  }
                } else {
                  DrawText (&drw, ptr, num, just, gray);
                }
              }
              if (invert) {
                InvertColors ();
              }
              if (colorProc != NULL && (! force)) {
                SetColor (currentColor);
              }
              rct.left -= insetLeft;
              rct.right += insetRight;
            }
          }
          ptr += num;
        }
        ch = *ptr;
      }
      if (drawBars) {
        InvertMode ();
        for (col = 0; col < itemPtr->numCols; col++) {
          rct.left = colFmt [col].position;
          rct.right = rct.left + colFmt [col].pixWidth;
          if (colFmt [col].bar) {
            drawBars = TRUE;
            MoveTo (rct.left, barTop);
/*
#ifdef WIN_MAC
            LineTo (rct.left, barBottom -1);
#else
            LineTo (rct.left, barBottom);
#endif
*/
            if (is_old_win) {
              LineTo (rct.left, barBottom);
            } else {
              LineTo (rct.left, barBottom - 1);
            }
          }
        }
        CopyMode ();
      }
      rct.top = rct.bottom;
      rsult = rct.top - r->top;
    }
    SelectFont (systemFont);
  }
  return rsult;
}

/*****************************************************************************
*
*   DrawDocument (d)
*       Panel callback that traverses the item list and draws visible items
*       Clipping is to the intersection of the updateRgn (the portion of the
*       panel exposed) and the inset area of the document, leaving a four-
*       pixel margin
*
*****************************************************************************/

static void DrawDocument (PaneL d)

{
  DocData  ddata;
  RegioN   dst;
  Int4     firstLine;
  Int2     highest;
  Int2     item;
  ItemPtr  itemPtr;
  Int2     lowest;
  Int2     numRows;
  Int4     off;
  Int2     pixels;
  RecT     r;
  RecT     rct;
  BaR      sb;
  RegioN   src;

  GetPanelExtra (d, &ddata);
  if (ddata.numItems > 0) {
    ObjectRect (d, &r);
    InsetRect (&r, 4, 4);
    sb = GetSlateVScrollBar ((SlatE) d);
    if (sb != NULL) {
      src = CreateRgn ();
      dst = CreateRgn ();
      LoadRectRgn (src, r.left, r.top, r.right, r.bottom);
      SectRgn (src, updateRgn, dst);
      ClipRgn (dst);
      DestroyRgn (src);
      DestroyRgn (dst);
      off = GetBarValue (sb);
      pixels = 0;
      lowest = INT2_MAX;
      highest = INT2_MIN;
      item = GetItemNum (&ddata, off);
      itemPtr = GetItemPtr (&ddata, item);
      if (itemPtr != NULL) {
        firstLine = off - itemPtr->startsAt;
        do {
          if (itemPtr != NULL) {
            if (itemPtr->neverCached) {
              CollectRange (item, &lowest, &highest);
            }
            CacheAndFormat (&ddata, item + 1, itemPtr, &r, ddata.tabCount);
            SetTablePixFormat (itemPtr->colFmt, r.left, itemPtr->numCols);
            if (pixels != 0) {
              r.top += itemPtr->leadHeight;
            }
            rct = r;
            numRows = MIN (itemPtr->numRows - firstLine,
                           MAX ((r.bottom - rct.top) / itemPtr->lineHeight, 0));
            rct.bottom = rct.top + itemPtr->lineHeight * (itemPtr->numRows - firstLine);
            if (updateRgn == NULL || RectInRgn (&rct, updateRgn)) {
              pixels = DrawTableItem ((DoC) d, &ddata, itemPtr, &r, item, firstLine,
                                      ddata.gray, ddata.invert, ddata.color,
                                      FALSE, ddata.tabCount);
              if (ddata.draw != NULL) {
                rct.bottom = rct.top + itemPtr->lineHeight * numRows;
                if (RectInRect (&rct, &r) &&
                    (updateRgn == NULL || RectInRgn (&rct, updateRgn))) {
                  ddata.draw ((DoC) d, &rct, item + 1, firstLine);
                }
                ResetDrawingTools ();
              }
            } else {
              pixels = rct.bottom - rct.top;
            }
            r.top += pixels;
          }
          item++;
          firstLine = 0;
          itemPtr = GetItemPtr (&ddata, item);
        } while (r.top < r.bottom && item < ddata.numItems && pixels > 0);
        if (lowest < INT2_MAX) {
          ddata.numLines = UpdateLineStarts (&ddata, lowest);
          SetPanelExtra (d, &ddata);
        }
      }
      ResetClip ();
    }
  }
  SelectFont (systemFont);
}

/*****************************************************************************
*
*   DocumentScrlProc (sb, s, newval, oldval)
*       Scroll bar callback that takes suppressed partial lines into account
*
*****************************************************************************/

static void DocumentScrlProc (BaR sb, SlatE s, Int4 newval, Int4 oldval)

{
  Int4     barmax;
  Int4     barval;
  DocData  ddata;
  Int2     firstShown;
  Boolean  goToEnd;
  Int2     height;
  Int2     highest;
  Int2     highFree;
  Int2     item;
  ItemPtr  itemPtr;
  Int4     last;
  Int2     limit;
  Int2     line;
  Int2     lineInto;
  Int2     lowest;
  Int2     lowFree;
  Int4     min;
  Int2     pixels;
  Int2     pgDn;
  Int2     pgUp;
  RecT     r;
  Int2     vis;

  if (s != NULL && oldval != newval) {
    if (Visible (s) && AllParentsVisible (s)) {
      GetPanelExtra ((PaneL) s, &ddata);
      ObjectRect (s, &r);
      InsetRect (&r, 4, 4);
      height = r.bottom - r.top;

      Select (s);
      if (ddata.pgUp < 1 || ddata.pgDn < 1) {
        InsetRect (&r, -1, -1);
        InvalRect (&r);
      } else if ((newval > oldval && newval <= oldval + ddata.pgDn) ||
          (newval < oldval && newval >= oldval - ddata.pgUp)) {
        pixels = PixelsBetween (&ddata, &r, newval, oldval, NULL, NULL);
        if (ABS (pixels) < height) {
          min = MIN (oldval, newval);
          item = GetItemNum (&ddata, min);
          line = (Int2) (min - GetStartsAt (&ddata, item));
          vis = VisLinesBelow (&ddata, &r, item, line, NULL, NULL);
          last = min + vis - 1;
          limit = PixelsBetween (&ddata, &r, newval, last, NULL, NULL);
          item = GetItemNum (&ddata, last);
          line = (Int2) (last - GetStartsAt (&ddata, item));
          limit += GetLineHeight (&ddata, item);
          ScrollRect (&r, 0, pixels);
          r.top += limit;
          InsetRect (&r, -1, -1);
          InvalRect (&r);
        } else {
          InsetRect (&r, -1, -1);
          InvalRect (&r);
        }
      } else {
        InsetRect (&r, -1, -1);
        InvalRect (&r);
      }

      min = MIN (oldval, newval);
      item = GetItemNum (&ddata, min);
      ddata.numLines = UpdateLineStarts (&ddata, item);
      SetPanelExtra ((PaneL) s, &ddata);

      item = GetItemNum (&ddata, newval);
      line = (Int2) (newval - GetStartsAt (&ddata, item));
      firstShown = item;
      lineInto = line;

      barval = GetStartsAt (&ddata, firstShown) + lineInto;
      if (barval > ddata.barmax) {
        CorrectBarMax (sb, barval);
        CorrectBarValue (sb, barval);
      } else {
        CorrectBarValue (sb, barval);
      }
      Update ();

      lowest = INT2_MAX;
      highest = INT2_MIN;
      ObjectRect (s, &r);
      InsetRect (&r, 4, 4);
      pgUp = VisLinesAbove (&ddata, &r, firstShown, lineInto, &lowest, &highest) - 1;
      pgDn = VisLinesBelow (&ddata, &r, firstShown, lineInto, &lowest, &highest) - 1;
      if (pgDn < 1) {
        pgDn = 1;
      }
      if (pgUp < 1) {
        pgUp = 1;
      }
      /*
      item = GetItemNum (&ddata, newval + pgDn);
      line = (Int2) (newval + (Int4) pgDn - GetStartsAt (&ddata, item));
      VisLinesBelow (&ddata, &r, item, line, &lowest, &highest);
      */
      if (lowest < INT2_MAX) {
        ddata.numLines = UpdateLineStarts (&ddata, lowest);
        SetPanelExtra ((PaneL) s, &ddata);
      }

      lowFree = INT2_MAX;
      highFree = INT2_MIN;
      for (item = 0; item < lowest; item++) {
        FreeCachedItem (&ddata, item, &lowFree, &highFree);
      }
      for (item = highest + 1; item < ddata.numItems; item++) {
        FreeCachedItem (&ddata, item, &lowFree, &highFree);
      }
      if (lowFree < INT2_MAX) {
        ddata.numLines = UpdateLineStarts (&ddata, lowFree);
        SetPanelExtra ((PaneL) s, &ddata);
      }

      barmax = 0;
      goToEnd = (Boolean) (newval == ddata.barmax);
      if (ddata.numLines > 0 && ddata.numItems > 0) {
        itemPtr = GetItemPtr (&ddata, ddata.numItems - 1);
        if (itemPtr != NULL) {
          ObjectRect (s, &r);
          InsetRect (&r, 4, 4);
          lowest = INT2_MAX;
          highest = INT2_MIN;
          vis = VisLinesAbove (&ddata, &r, ddata.numItems - 1,
                               itemPtr->numRows - 1, &lowest, &highest);
          if (lowest < INT2_MAX) {
            ddata.numLines = UpdateLineStarts (&ddata, lowest);
            SetPanelExtra ((PaneL) s, &ddata);
          }
          barmax = ddata.numLines - vis;
        }
      }
      barval = GetStartsAt (&ddata, firstShown) + lineInto;
      if (goToEnd) {
        barval = barmax;
      }
      if (barval > ddata.barmax) {
        CorrectBarMax (sb, barmax);
        CorrectBarValue (sb, barval);
      } else {
        CorrectBarValue (sb, barval);
        CorrectBarMax (sb, barmax);
      }
      CorrectBarPage (sb, pgUp, pgDn);
      ddata.barmax = barmax;
      ddata.pgUp = pgUp;
      ddata.pgDn = pgDn;
      SetPanelExtra ((PaneL) s, &ddata);
      Update ();
      if (ddata.pan != NULL) {
        Select (s);
        ddata.pan ((DoC) s);
      }
    }
  }
}

/*****************************************************************************
*
*   CreateItemPtr (d, item)
*       Adds an item slot to the item list
*
*****************************************************************************/

static ItemPtr CreateItemPtr (DoC d, Int2 item)

{
  DocData    ddata;
  Int2       index;
  Int2       list;
  ItemPtr    itemPtr;
  ListDPtr   listPtr;
  MasterPtr  masterPtr;

  itemPtr = NULL;
  if (d != NULL && item < 32767) {
    GetPanelExtra ((PaneL) d, &ddata);
    if (ddata.master == NULL) {
      ddata.master = (MasterPtr) MemNew (sizeof (MasterData));
      SetPanelExtra ((PaneL) d, &ddata);
    }
    list = (item / LISTSIZE);
    if (list < MAXLISTS && ddata.master != NULL) {
      masterPtr = ddata.master;
      listPtr = masterPtr->list [list];
      if (listPtr == NULL) {
        masterPtr->list [list] = (ListDPtr) MemNew(sizeof(ListData));
        listPtr = masterPtr->list [list];
      }
      if (listPtr != NULL) {
        index = (item % LISTSIZE);
        itemPtr = &(listPtr->items [index]);
      }
    }
  }
  return itemPtr;
}

/*****************************************************************************
*
*   GetFontHeight (font)
*       Returns the pixel height of a font, caching for quicker access
*
*****************************************************************************/

static Int2 GetFontHeight (FonT font)

{
  Int2  height;
  Int2  i;

  height = 0;
  i = 0;
  while (i < MAXFONTS && fontHeights [i].font != font && fontHeights [i].font != NULL) {
    i++;
  }
  if (fontHeights [i].font == font) {
    height = fontHeights [i].height;
  } else {
    SelectFont (font);
    height = LineHeight ();
    if (i < MAXFONTS) {
      fontHeights [i].font = font;
      fontHeights [i].height = height;
    }
  }
  return height;
}

/*****************************************************************************
*
*   UpdateItemHeights (itemPtr)
*       Returns the maximum height of all fonts used in an item
*
*****************************************************************************/

static void UpdateItemHeights (ItemPtr itemPtr)

{
  ColXPtr  colFmt;
  FonT     fnt;
  Int2     i;
  Int2     linhgt;
  Int2     maxhgt;

  if (itemPtr != NULL) {
    colFmt = itemPtr->colFmt;
    maxhgt = 0;
    if (colFmt != NULL) {
      i = itemPtr->numCols;
      while (i > 0) {
        i--;
        fnt = colFmt [i].font;
        if (fnt != NULL) {
          linhgt = GetFontHeight (fnt);
          if (linhgt > maxhgt) {
            maxhgt = linhgt;
          }
        } else if (itemPtr->font == NULL) {
          linhgt = stdLineHeight;
          if (linhgt > maxhgt) {
            maxhgt = linhgt;
          }
        }
      }
    }
    fnt = itemPtr->font;
    if (fnt != NULL) {
      linhgt = GetFontHeight (fnt);
      if (linhgt > maxhgt) {
        maxhgt = linhgt;
      }
    }
    if (maxhgt == 0) {
      maxhgt = stdLineHeight;
    }
    itemPtr->lineHeight = MAX (maxhgt, itemPtr->minHeight);
    if (itemPtr->openSpace) {
      itemPtr->leadHeight = maxhgt;
    } else {
      itemPtr->leadHeight = 0;
    }
  }
}

/*****************************************************************************
*
*   SetDocAutoAdjust (d, autoAdjust)
*       Sets the document automatic scroll adjust parameter
*
*****************************************************************************/

extern void SetDocAutoAdjust (DoC d, Boolean autoAdjust)

{
  DocData  ddata;

  if (d != NULL) {
    GetPanelExtra ((PaneL) d, &ddata);
    ddata.autoAdjust = autoAdjust;
    SetPanelExtra ((PaneL) d, &ddata);
  }
}

/* dgg here -- want to make use of tab settings other than from FancyFile call */

/*****************************************************************************
*
*   Nlm_SetDocTabstops (d, tabStops)
*       Sets the document tap stop interval
*
*****************************************************************************/

extern void Nlm_SetDocTabstops (DoC d, Int2 tabStops)
{
  DocData  ddata;

  if (d != NULL) {
    GetPanelExtra ((PaneL) d, &ddata);
    ddata.tabCount = tabStops;
    SetPanelExtra ((PaneL) d, &ddata);
  }
}

/*****************************************************************************
*
*   AdjustDocScroll (d)
*       Calculates an estimate for the scroll bar maximum
*
*****************************************************************************/

extern void AdjustDocScroll (DoC d)

{
  Int4     barmax;
  Int4     barval;
  DocData  ddata;
  Int2     firstShown;
  Int2     highest;
  Int2     item;
  ItemPtr  itemPtr;
  Int2     line;
  Int2     lineInto;
  Int2     lowest;
  Int2     pgDn;
  Int2     pgUp;
  RecT     r;
  BaR      sb;
  Int2     vis;

  if (d != NULL) {
    GetPanelExtra ((PaneL) d, &ddata);
    SelectFont (systemFont);
    sb = GetSlateVScrollBar ((SlatE) d);
    if (sb != NULL) {
      if (ddata.numLines > 0 && ddata.numItems > 0) {
        ObjectRect (d, &r);
        InsetRect (&r, 4, 4);
        barval = GetBarValue (sb);
        item = GetItemNum (&ddata, barval);
        line = (Int2) (barval - GetStartsAt (&ddata, item));
        firstShown = item;
        lineInto = line;
        lowest = INT2_MAX;
        highest = INT2_MIN;
        itemPtr = GetItemPtr (&ddata, ddata.numItems - 1);
        if (itemPtr != NULL) {
          CacheIfNever (&ddata, ddata.numItems, itemPtr, &r, ddata.tabCount);
          vis = VisLinesAbove (&ddata, &r, ddata.numItems - 1,
                               itemPtr->numRows - 1, &lowest, &highest);
        }
        pgUp = VisLinesAbove (&ddata, &r, firstShown, lineInto, &lowest, &highest) - 1;
        pgDn = VisLinesBelow (&ddata, &r, firstShown, lineInto, &lowest, &highest) - 1;
        if (pgDn < 1) {
          pgDn = 1;
        }
        if (pgUp < 1) {
          pgUp = 1;
        }
        /*
        item = GetItemNum (&ddata, barval + pgDn);
        line = (Int2) (barval + (Int4) pgDn - GetStartsAt (&ddata, item));
        VisLinesBelow (&ddata, &r, item, line, &lowest, &highest);
        */
        if (lowest < INT2_MAX) {
          ddata.numLines = UpdateLineStarts (&ddata, lowest);
          SetPanelExtra ((PaneL) d, &ddata);
        }
        itemPtr = GetItemPtr (&ddata, ddata.numItems - 1);
        if (itemPtr != NULL) {
          barmax = 0;
          ObjectRect (d, &r);
          InsetRect (&r, 4, 4);
          lowest = INT2_MAX;
          highest = INT2_MIN;
          vis = VisLinesAbove (&ddata, &r, ddata.numItems - 1,
                               itemPtr->numRows - 1, &lowest, &highest);
          if (lowest < INT2_MAX) {
            ddata.numLines = UpdateLineStarts (&ddata, lowest);
            SetPanelExtra ((PaneL) d, &ddata);
          }
          barmax = ddata.numLines - vis;
          barval = GetStartsAt (&ddata, firstShown) + lineInto;
          CorrectBarPage (sb, pgUp, pgDn);
          if (barval > ddata.barmax) {
            CorrectBarMax (sb, barmax);
            CorrectBarValue (sb, barval);
          } else {
            CorrectBarValue (sb, barval);
            CorrectBarMax (sb, barmax);
          }
          ddata.barmax = barmax;
          ddata.pgUp = pgUp;
          ddata.pgDn = pgDn;
          SetPanelExtra ((PaneL) d, &ddata);
        }
      } else {
        Reset (sb);
      }
    }
  }
}

/*****************************************************************************
*
*   ForceFormat (d, item)
*       Forces text format
*
*****************************************************************************/

extern void ForceFormat (DoC d, Int2 item)

{
  DocData  ddata;
  ItemPtr  itemPtr;
  RecT     r;

  if (d != NULL && item > 0) {
    GetPanelExtra ((PaneL) d, &ddata);
    itemPtr = GetItemPtr (&ddata, item - 1);
    if (itemPtr != NULL) {
      ObjectRect (d, &r);
      InsetRect (&r, 4, 4);
      itemPtr->text = (CharPtr) MemFree (itemPtr->text);
      itemPtr->notCached = TRUE;
      itemPtr->neverCached = TRUE;
      SetTablePixFormat (itemPtr->colFmt, r.left, itemPtr->numCols);
      FormatText (&ddata, item, itemPtr, TRUE, ddata.tabCount);
      UpdateItemHeights (itemPtr);
      ddata.numLines = UpdateLineStarts (&ddata, 0);
      SetPanelExtra ((PaneL) d, &ddata);
      if (ddata.autoAdjust) {
        AdjustDocScroll (d);
      }
    }
  }
}

/*****************************************************************************
*
*   defaultTable
*       Column format used if the colFmt parameter is NULL
*
*****************************************************************************/

static ColData defaultTable = {0, 0, 0, 0, NULL, 'l',
                               TRUE, FALSE, FALSE, FALSE, TRUE};

/*****************************************************************************
*
*   CacheColFmt (d, colFmt)
*       Caches the colFmt format structure
*
*****************************************************************************/

static ColXPtr CacheColFmt (DoC d, ColPtr colFmt)

{
  DocData     ddata;
  Boolean     found;
  Boolean     goOn;
  Int2        i;
  Int2        numCols;
  ColXPtr     rsult;
  ColXPtr     thisCol;
  ValNodePtr  vnp;

  rsult = NULL;
  if (d != NULL && colFmt != NULL) {
    i = 0;
    while (! colFmt [i].last) {
      i++;
    }
    i++;
    numCols = i;
    rsult = (ColXPtr) MemNew (numCols * sizeof (ColXData));
    if (rsult != NULL) {
      for (i = 0; i < numCols; i++) {
        rsult [i].position = 0;
        rsult [i].pixWidth = colFmt [i].pixWidth;
        rsult [i].pixInset = colFmt [i].pixInset;
        rsult [i].charWidth = colFmt [i].charWidth;
        rsult [i].charInset = colFmt [i].charInset;
        if (colFmt [i].just == 'l') {
          rsult [i].just = JUST_LEFT;
        } else if (colFmt [i].just == 'c') {
          rsult [i].just = JUST_CENTER;
        } else if (colFmt [i].just == 'r') {
          rsult [i].just = JUST_RIGHT;
        } else {
          rsult [i].just = JUST_LEFT;
        }
        rsult [i].font = colFmt [i].font;
        rsult [i].zeroWidth = (Boolean) (colFmt [i].pixWidth == 0);
        if (rsult [i].zeroWidth) {
          rsult [i].charWidth = 0;
        }
        rsult [i].wrap = colFmt [i].wrap;
        rsult [i].bar = colFmt [i].bar;
        rsult [i].underline = colFmt [i].underline;
        rsult [i].left = colFmt [i].left;
        rsult [i].last = colFmt [i].last;
      }
      GetPanelExtra ((PaneL) d, &ddata);
      vnp = ddata.colFmts;
      goOn = TRUE;
      while (vnp != NULL && goOn) {
        thisCol = (ColXPtr) vnp->data.ptrvalue;
        if (thisCol != NULL) {
          i = 0;
          while (! thisCol [i].last) {
            i++;
          }
          i++;
          if (i == numCols) {
            found = TRUE;
            for (i = 0; i < numCols; i++) {
              if (thisCol [i].zeroWidth) {
                thisCol [i].pixWidth = 0;
                thisCol [i].charWidth = 0;
              }
              if (thisCol [i].font != rsult [i].font ||
                  thisCol [i].pixWidth != rsult [i].pixWidth ||
                  thisCol [i].pixInset != rsult [i].pixInset ||
                  thisCol [i].charWidth != rsult [i].charWidth ||
                  thisCol [i].charInset != rsult [i].charInset ||
                  thisCol [i].just != rsult [i].just ||
                  thisCol [i].zeroWidth != rsult [i].zeroWidth ||
                  thisCol [i].wrap != rsult [i].wrap ||
                  thisCol [i].bar != rsult [i].bar ||
                  thisCol [i].underline != rsult [i].underline ||
                  thisCol [i].left != rsult [i].left ||
                  thisCol [i].last != rsult [i].last) {
                found = FALSE;
              }
            }
            if (found) {
              goOn = FALSE;
            }
          }
        }
        if (goOn) {
          vnp = vnp->next;
        }
      }
      if (goOn) {
        vnp = ValNodeNew (ddata.colFmts);
        if (vnp != NULL) {
          vnp->data.ptrvalue = (Pointer) rsult;
        }
        if (ddata.colFmts == NULL) {
          ddata.colFmts = vnp;
          SetPanelExtra ((PaneL) d, &ddata);
        }
      } else {
        MemFree (rsult);
        rsult = (ColXPtr) vnp->data.ptrvalue;
      }
    }
  }
  return rsult;
}

/*****************************************************************************
*
*   UpdateColFmt (d, *col)
*       Returns the maximum height of all fonts used in an item
*
*****************************************************************************/

extern void UpdateColFmt (DoC d, ColPtr col)

{
  DocData  dd;
  ItemPtr  itemPtr;
  Int2     item;

  GetPanelExtra ((PaneL) d, &dd);
  for (item = 0; item < dd.numItems; item++) {
    itemPtr = GetItemPtr (&dd, item);
    if (itemPtr != NULL) {
      itemPtr->colFmt = CacheColFmt (d, col);
    }
  }
  SetPanelExtra ((PaneL) d, &dd);
}

/*****************************************************************************
*
*   SetDocDefaults (d, defaultParFmt, defaultColFmt, defaultFont)
*       Sets default format preferences, so AppendItem, etc., can pass NULLs
*
*****************************************************************************/

extern void SetDocDefaults (DoC d, ParPtr defaultParFmt,
                            ColPtr defaultColFmt, FonT defaultFont)

{
  DocData  ddata;

  if (d != NULL) {
    GetPanelExtra ((PaneL) d, &ddata);
    ddata.defaultParFmt = defaultParFmt;
    ddata.defaultColFmt = defaultColFmt;
    ddata.defaultFont = defaultFont;
    SetPanelExtra ((PaneL) d, &ddata);
  }
}

/*****************************************************************************
*
*   SetDocSimpleMode (d, useRowsNotItems)
*       Sets default highlighting range
*
*****************************************************************************/

extern void SetDocSimpleMode (DoC d, Boolean useRowsNotItems)

{
  DocData  ddata;

  if (d != NULL) {
    GetPanelExtra ((PaneL) d, &ddata);
    ddata.useRowsNotItems = useRowsNotItems;
    SetPanelExtra ((PaneL) d, &ddata);
  }
}
/*****************************************************************************
*
*   SetDocHighlight (d, firstItem, lastItem)
*       Sets default highlighting range
*
*****************************************************************************/

extern void SetDocHighlight (DoC d, Int2 firstItem, Int2 lastItem)

{
  DocData  ddata;

  if (d != NULL) {
    GetPanelExtra ((PaneL) d, &ddata);
    ddata.firstHighlightItem = MIN (firstItem, lastItem);
    ddata.lastHighlightItem = MAX (firstItem, lastItem);
    SetPanelExtra ((PaneL) d, &ddata);
  }
}

/*****************************************************************************
*
*   GetDocHighlight (d, firstItem, lastItem)
*       Returns default highlighting range
*
*****************************************************************************/

extern void GetDocHighlight (DoC d, Int2Ptr firstItem, Int2Ptr lastItem)

{
  DocData  ddata;

  if (d != NULL) {
    GetPanelExtra ((PaneL) d, &ddata);
    if (firstItem != NULL) {
      *firstItem = ddata.firstHighlightItem;
    }
    if (lastItem != NULL) {
      *lastItem = ddata.lastHighlightItem;
    }
  }
}

/*****************************************************************************
*
*   SetDocNotify (d, notify)
*       Sets default click notification
*
*****************************************************************************/

static void DefaultNotifyClickProc (DoC d, PoinT pt)

{
  Int2     col;
  DocData  ddata;
  Int2     item;
  Int2     row;

  if (d != NULL) {
    GetPanelExtra ((PaneL) d, &ddata);
    if (ddata.notify != NULL) {
      MapDocPoint (d, pt, &item, &row, &col, NULL);
      ddata.notify (d, item, row, col, dblClick);
    }
  }
}

extern void SetDocNotify (DoC d, DocNotfyProc notify)

{
  DocData  ddata;

  if (d != NULL) {
    GetPanelExtra ((PaneL) d, &ddata);
    ddata.notify = notify;
    SetPanelExtra ((PaneL) d, &ddata);
    Nlm_SetPanelClick ((PaneL) d, (PnlClckProc) DefaultNotifyClickProc, NULL, NULL, NULL);
  }
}

/*****************************************************************************
*
*   SetupItem (d, ddatptr, itemPtr, proc, data, docOwnsData,
*              lines, parFmt, colFmt, font)
*       Processes user parameters into an item to append or insert into
*       the item list, or replace an item currently in the item list
*
*****************************************************************************/

static void SetupItem (DoC d, DocDataPtr ddatptr, ItemPtr itemPtr, DocPrntProc proc,
                       Pointer data, Boolean docOwnsData, Int2 lines,
                       ParPtr parFmt, ColPtr colFmt, FonT font)

{
  Int2  i;
  RecT  r;

  if (d != NULL && ddatptr != NULL && itemPtr != NULL && proc != NULL) {
    ObjectRect (d, &r);
    InsetRect (&r, 4, 4);
    itemPtr->prtProc = proc;
    itemPtr->dataPtr = data;
    itemPtr->text = NULL;
    if (font == NULL) {
      font = ddatptr->defaultFont;
    }
    itemPtr->font = font;
    if (colFmt == NULL) {
      colFmt = ddatptr->defaultColFmt;
    }
    if (colFmt == NULL) {
      defaultTable.pixWidth = r.right - r.left;
      defaultTable.charWidth = 80;
      colFmt = &defaultTable;
    }
    i = 0;
    while (! colFmt [i].last) {
      i++;
    }
    i++;
    itemPtr->numCols = i;
    itemPtr->numRows = MAX (lines, 1);
    itemPtr->colFmt = CacheColFmt (d, colFmt);
    itemPtr->extra = NULL;
    itemPtr->startsAt = 0;
    if (parFmt == NULL) {
      parFmt = ddatptr->defaultParFmt;
    }
    if (parFmt != NULL) {
      itemPtr->openSpace = parFmt->openSpace;
      itemPtr->keepWithNext = parFmt->keepWithNext;
      itemPtr->keepTogether = parFmt->keepTogether;
      itemPtr->newPage = parFmt->newPage;
      itemPtr->tabStops = parFmt->tabStops;
      itemPtr->minLines = MAX (parFmt->minLines, (Int2) 1);
      itemPtr->minHeight = parFmt->minHeight;
    } else {
      itemPtr->openSpace = TRUE;
      itemPtr->keepWithNext = FALSE;
      itemPtr->keepTogether = FALSE;
      itemPtr->newPage = FALSE;
      itemPtr->tabStops = FALSE;
      itemPtr->minLines = 0;
      itemPtr->minHeight = 0;
    }
    itemPtr->docOwnsData = docOwnsData;
    itemPtr->notCached = TRUE;
    itemPtr->neverCached = TRUE;
  }
}

/*****************************************************************************
*
*   AppendItem (d, proc, data, docOwnsData, lines,
*               parFmt, colFmt, font)
*       Processes user parameters and appends the item to the end of the
*       item list
*
*****************************************************************************/

extern void AppendItem (DoC d, DocPrntProc proc, Pointer data,
                        Boolean docOwnsData, Int2 lines,
                        ParPtr parFmt, ColPtr colFmt, FonT font)

{
  DocData   ddata;
  ItemData  itemData;
  ItemPtr   itemPtr;

  if (d != NULL && proc != NULL) {
    GetPanelExtra ((PaneL) d, &ddata);
    MemFill (&itemData, 0, sizeof (ItemData));
    SetupItem (d, &ddata, &itemData, proc, data, docOwnsData,
               lines, parFmt, colFmt, font);
    if (ddata.upd != NULL) {
      ddata.upd (d, ddata.numItems, 0);
    }
    itemPtr = CreateItemPtr (d, ddata.numItems);
    GetPanelExtra ((PaneL) d, &ddata);
    if (itemPtr != NULL) {
      *itemPtr = *(&itemData);
      itemPtr->startsAt = ddata.numLines;
      UpdateItemHeights (itemPtr);
      ddata.numLines += itemPtr->numRows;
      (ddata.numItems)++;
      SetPanelExtra ((PaneL) d, &ddata);
      if (ddata.autoAdjust) {
        AdjustDocScroll (d);
      }
    }
  }
}

/*****************************************************************************
*
*   ReplaceItem (d, item, proc, data, docOwnsData,
*                lines, parFmt, colFmt, font)
*       Replaces an existing item in a document
*
*****************************************************************************/

extern void ReplaceItem (DoC d, Int2 item, DocPrntProc proc,
                         Pointer data, Boolean docOwnsData, Int2 lines,
                         ParPtr parFmt, ColPtr colFmt, FonT font)

{
  DocData  ddata;
  ItemPtr  itemPtr;
  Int4     startsAt;

  if (d != NULL && proc != NULL) {
    GetPanelExtra ((PaneL) d, &ddata);
    if (item > 0 && item <= ddata.numItems) {
      item--;
      if (ddata.upd != NULL) {
        ddata.upd (d, item, item);
      }
      itemPtr = GetItemPtr (&ddata, item);
      if (itemPtr != NULL) {
        startsAt = itemPtr->startsAt;
        itemPtr->text = (CharPtr) MemFree (itemPtr->text);
        if (itemPtr->docOwnsData) {
          itemPtr->dataPtr = (Pointer) MemFree (itemPtr->dataPtr);
        } else {
          itemPtr->dataPtr = NULL;
        }
        itemPtr->extra = MemFree (itemPtr->extra);
        MemFill (itemPtr, 0, sizeof (ItemData));
        SetupItem (d, &ddata, itemPtr, proc, data, docOwnsData,
                   lines, parFmt, colFmt, font);
        itemPtr->startsAt = startsAt;
        UpdateItemHeights (itemPtr);
        ddata.numLines = UpdateLineStarts (&ddata, item);
        SetPanelExtra ((PaneL) d, &ddata);
        if (ddata.autoAdjust) {
          AdjustDocScroll (d);
        }
      }
    }
  }
}

/*****************************************************************************
*
*   InsertItem (d, item, proc, data, docOwnsData,
*               lines, parFmt, colFmt, font)
*       Inserts a new item into a document
*
*****************************************************************************/

extern void InsertItem (DoC d, Int2 item, DocPrntProc proc,
                        Pointer data, Boolean docOwnsData, Int2 lines,
                        ParPtr parFmt, ColPtr colFmt, FonT font)

{
  DocData  ddata;
  ItemPtr  fromItem;
  ItemPtr  itemPtr;
  Int2     j;
  Int4     startsAt;
  ItemPtr  toItem;

  if (d != NULL && proc != NULL) {
    GetPanelExtra ((PaneL) d, &ddata);
    if (item > 0 && item <= ddata.numItems) {
      item--;
      if (ddata.upd != NULL) {
        ddata.upd (d, item, 0);
      }
      itemPtr = CreateItemPtr (d, ddata.numItems);
      if (itemPtr != NULL) {
        GetPanelExtra ((PaneL) d, &ddata);
        (ddata.numItems)++;
        SetPanelExtra ((PaneL) d, &ddata);
        for (j = ddata.numItems - 1; j > item; j--) {
          fromItem = GetItemPtr (&ddata, j - 1);
          toItem = GetItemPtr (&ddata, j);
          if (fromItem != NULL && toItem != NULL) {
            *toItem = *fromItem;
          }
        }
        itemPtr = GetItemPtr (&ddata, item);
        if (itemPtr != NULL) {
          startsAt = itemPtr->startsAt;
          MemFill (itemPtr, 0, sizeof (ItemData));
          SetupItem (d, &ddata, itemPtr, proc, data, docOwnsData,
                     lines, parFmt, colFmt, font);
          itemPtr->startsAt = startsAt;
          UpdateItemHeights (itemPtr);
          ddata.numLines = UpdateLineStarts (&ddata, item);
          SetPanelExtra ((PaneL) d, &ddata);
          if (ddata.autoAdjust) {
            AdjustDocScroll (d);
          }
        }
      }
    }
  }
}

/*****************************************************************************
*
*   CharPrtProc (ptr)
*       Standard print function used with AppendText
*
*****************************************************************************/

static CharPtr CharPrtProc (DoC d, Int2 item, Pointer ptr)

{
  if (ptr != NULL) {
    return StringSave( (CharPtr)ptr );
  } else {
    return NULL;
  }
}

/*****************************************************************************
*
*   SkipPastNewLine (text, cnt)
*       Finds the end of a line of text, up to a maximum number of characters
*
*****************************************************************************/

static Int2 SkipPastNewLine (CharPtr text, Int2 cnt)

{
  Char  ch;

  ch = *(text + cnt);
  while (ch != '\0' && ch != '\n' && cnt < 24380) {
    cnt++;
    ch = *(text + cnt);
  }
  while ((ch == '\n' || ch == '\r') && cnt < 24380) {
    cnt++;
    ch = *(text + cnt);
  }
  return cnt;
}

/*****************************************************************************
*
*   AppendText (d, text, parFmt, colFmt, font)
*       Special case of AppendItem that takes formatted text as its data
*
*****************************************************************************/

extern void AppendText (DoC d, CharPtr text, ParPtr parFmt,
                        ColPtr colFmt, FonT font)

{
  Int2     cnt;
  Int2     cntr;
  Int2     start;
  Pointer  txt;

  if (d != NULL) {
    if (text != NULL && *text != '\0') {
      start = 0;
      cntr = StringLen (text);
      cnt = MIN (cntr, 24000);
      cnt = SkipPastNewLine (text + start, cnt);
      while (cnt > 0) {
        txt = MemNew (cnt + 1);
        MemCopy (txt, text + start, cnt);
        ProgMon ("AppendText");
        AppendItem (d, CharPrtProc, txt, TRUE, (cnt / 50) + 1,
                    parFmt, colFmt, font);
        start += cnt;
        cntr -= cnt;
        cnt = MIN (cntr, 24000);
        cnt = SkipPastNewLine (text + start, cnt);
      }
    } else {
      ProgMon ("AppendText");
      AppendItem (d, CharPrtProc, "", FALSE, 1,
                  parFmt, colFmt, font);
    }
  }
}

/*****************************************************************************
*
*   ReplaceText (d, item, text, parFmt, colFmt, font)
*       Special case of ReplaceItem that takes formatted text as its data
*
*****************************************************************************/

extern void ReplaceText (DoC d, Int2 item, CharPtr text,
                         ParPtr parFmt, ColPtr colFmt, FonT font)

{
  Int2     cnt;
  Int2     cntr;
  Int2     start;
  Pointer  txt;

  if (d != NULL) {
    if (text != NULL && *text != '\0') {
      start = 0;
      cntr = StringLen (text);
      cnt = MIN (cntr, 24000);
      cnt = SkipPastNewLine (text + start, cnt);
      while (cnt > 0) {
        txt = MemNew (cnt + 1);
        MemCopy (txt, text + start, cnt);
        ReplaceItem (d, item, CharPrtProc, txt, TRUE, (cnt / 50) + 1,
                     parFmt, colFmt, font);
        start += cnt;
        cntr -= cnt;
        cnt = MIN (cntr, 24000);
        cnt = SkipPastNewLine (text + start, cnt);
        item++;
      }
    } else {
      ReplaceItem (d, item, CharPrtProc, "", FALSE, 1,
                   parFmt, colFmt, font);
    }
  }
}

/*****************************************************************************
*
*   InsertText (d, item, text, parFmt, colFmt, font)
*       Special case of InsertItem that takes formatted text as its data
*
*****************************************************************************/

extern void InsertText (DoC d, Int2 item, CharPtr text,
                        ParPtr parFmt, ColPtr colFmt, FonT font)

{
  Int2     cnt;
  Int2     cntr;
  Int2     start;
  Pointer  txt;

  if (d != NULL) {
    if (text != NULL && *text != '\0') {
      start = 0;
      cntr = StringLen (text);
      cnt = MIN (cntr, 24000);
      cnt = SkipPastNewLine (text + start, cnt);
      while (cnt > 0) {
        txt = MemNew (cnt + 1);
        MemCopy (txt, text + start, cnt);
        InsertItem (d, item, CharPrtProc, txt, TRUE, (cnt / 50) + 1,
                    parFmt, colFmt, font);
        start += cnt;
        cntr -= cnt;
        cnt = MIN (cntr, 24000);
        cnt = SkipPastNewLine (text + start, cnt);
        item++;
      }
    } else {
      InsertItem (d, item, CharPrtProc, "", FALSE, 1,
                  parFmt, colFmt, font);
    }
  }
}

/*****************************************************************************
*
*   DeleteItem (d, item)
*       Deletes an item from a document
*
*****************************************************************************/

extern void DeleteItem (DoC d, Int2 item)

{
  DocData  ddata;
  ItemPtr  fromItem;
  ItemPtr  itemPtr;
  Int2     j;
  Int4     startsAt;
  ItemPtr  toItem;

  if (d != NULL) {
    GetPanelExtra ((PaneL) d, &ddata);
    if (item > 0 && item <= ddata.numItems) {
      item--;
      itemPtr = GetItemPtr (&ddata, item);
      if (itemPtr != NULL) {
        startsAt = itemPtr->startsAt;
        itemPtr->text = (CharPtr) MemFree (itemPtr->text);
        if (itemPtr->docOwnsData) {
          itemPtr->dataPtr = (Pointer) MemFree (itemPtr->dataPtr);
        } else {
          itemPtr->dataPtr = NULL;
        }
        itemPtr->extra = MemFree (itemPtr->extra);
        for (j = item; j < ddata.numItems - 1; j++) {
          fromItem = GetItemPtr (&ddata, j + 1);
          toItem = GetItemPtr (&ddata, j);
          if (fromItem != NULL && toItem != NULL) {
            *toItem = *fromItem;
          }
        }
        itemPtr->startsAt = startsAt;
        (ddata.numItems)--;
        if(item < 1)
            ddata.numLines = UpdateLineStarts(&ddata, 0);
        else
        if(item == ddata.numItems)
            ddata.numLines = UpdateLineStarts (&ddata, item - 1);
        else
            ddata.numLines = UpdateLineStarts(&ddata, item);
        SetPanelExtra((PaneL)d, &ddata);
        if (ddata.autoAdjust) {
          AdjustDocScroll (d);
        }
      }
    }
  }
}

/*****************************************************************************
*
*   BulkAppendItem (d, numItems, proc, estLines, parFmt, colFmt, font)
*       Populates a document in one step, assuming that the print function
*       doesn't need individual data
*
*****************************************************************************/

extern void BulkAppendItem (DoC d, Int2 numItems, DocPrntProc proc,
                            Int2 estLines, ParPtr parFmt,
                            ColPtr colFmt, FonT font)

{
  ColXPtr    colXPtr;
  DocData    ddata;
  Int2       i;
  Int2       index;
  Int2       item;
  ItemPtr    itemPtr;
  Int2       leadHeight = 0;
  Int2       lineHeight = stdLineHeight;
  Int2       list;
  ListDPtr   listPtr;
  MasterPtr  masterPtr;
  Int2       numCols;
  Int2       numLists;
  ParData    parData;
  RecT       r;

  if (d != NULL && /* numItems <= 32767 && */ numItems > 0) {
    GetPanelExtra ((PaneL) d, &ddata);
    if (ddata.master == NULL) {
      ddata.master = (MasterPtr) MemNew (sizeof (MasterData));
      SetPanelExtra ((PaneL) d, &ddata);
    }
    numLists = (numItems / LISTSIZE) + 1;
    ObjectRect (d, &r);
    InsetRect (&r, 4, 4);
    if (numLists <= MAXLISTS && ddata.master != NULL) {
      masterPtr = ddata.master;
      for (list = 0; list < numLists; list++) {
        listPtr = masterPtr->list [list];
        if (listPtr == NULL) {
          masterPtr->list [list] = (ListDPtr) MemNew(sizeof(ListData));
          listPtr = masterPtr->list [list];
        }
      }
      if (colFmt == NULL) {
        colFmt = ddata.defaultColFmt;
      }
      if (colFmt == NULL) {
        defaultTable.pixWidth = r.right - r.left;
        defaultTable.charWidth = 80;
        colFmt = &defaultTable;
      }
      i = 0;
      while (! colFmt [i].last) {
        i++;
      }
      i++;
      numCols = i;
      colXPtr = CacheColFmt (d, colFmt);
      if (parFmt == NULL) {
        parFmt = ddata.defaultParFmt;
      }
      if (parFmt != NULL) {
        parData.openSpace = parFmt->openSpace;
        parData.keepWithNext = parFmt->keepWithNext;
        parData.keepTogether = parFmt->keepTogether;
        parData.newPage = parFmt->newPage;
        parData.tabStops = parFmt->tabStops;
        parData.minLines = MAX (parFmt->minLines, (Int2) 1);
        parData.minHeight = parFmt->minHeight;
      } else {
        parData.openSpace = TRUE;
        parData.keepWithNext = FALSE;
        parData.keepTogether = FALSE;
        parData.newPage = FALSE;
        parData.tabStops = FALSE;
        parData.minLines = 0;
        parData.minHeight = 0;
      }
      if (font == NULL) {
        font = ddata.defaultFont;
      }
      for (item = 0; item < numItems; item++) {
        list = (item / LISTSIZE);
        listPtr = masterPtr->list [list];
        if (listPtr != NULL) {
          index = (item % LISTSIZE);
          itemPtr = &(listPtr->items [index]);
          if (itemPtr != NULL) {
            itemPtr->prtProc = proc;
            itemPtr->dataPtr = NULL;
            itemPtr->text = NULL;
            itemPtr->cached = NULL;
            itemPtr->font = font;
            itemPtr->numRows = MAX (estLines, 1);
            itemPtr->numCols = numCols;
            itemPtr->colFmt = colXPtr;
            itemPtr->extra = NULL;
            itemPtr->startsAt = 0;
            itemPtr->openSpace = parData.openSpace;
            itemPtr->keepWithNext = parData.keepWithNext;
            itemPtr->keepTogether = parData.keepTogether;
            itemPtr->newPage = parData.newPage;
            itemPtr->tabStops = parData.tabStops;
            itemPtr->minLines = parData.minLines;
            itemPtr->minHeight = parData.minHeight;
            itemPtr->docOwnsData = FALSE;
            itemPtr->notCached = TRUE;
            itemPtr->neverCached = TRUE;
            if (item == 0) {
              UpdateItemHeights (itemPtr);
              lineHeight = itemPtr->lineHeight;
              leadHeight = itemPtr->leadHeight;
            }
            itemPtr->lineHeight = lineHeight;
            itemPtr->leadHeight = leadHeight;
          }
        }
      }
      ddata.numItems = numItems;
      ddata.numLines = UpdateLineStarts (&ddata, 0);
      SetPanelExtra ((PaneL) d, &ddata);
      if (ddata.upd != NULL) {
        ddata.upd ((DoC) d, 0, 0);
      }
      if (ddata.autoAdjust) {
        AdjustDocScroll (d);
      }
    }
  }
}

/*****************************************************************************
*
*   StdCache functions
*       Simple binary tree cache
*
*****************************************************************************/

extern void StdPutDocCache (DoC d, Int2 item, CharPtr text)

{
  DocData  ddata;
  ItemPtr  itemPtr;

  if (d != NULL && item > 0) {
    GetPanelExtra ((PaneL) d, &ddata);
    itemPtr = GetItemPtr (&ddata, item - 1);
    if (itemPtr != NULL) {
      itemPtr->cached = MemFree (itemPtr->cached);
      itemPtr->cached = StringSave (text);
    }
  }
}

extern CharPtr StdGetDocCache (DoC d, Int2 item)

{
  DocData  ddata;
  ItemPtr  itemPtr;
  CharPtr  text = NULL;

  if (d != NULL && item > 0) {
    GetPanelExtra ((PaneL) d, &ddata);
    itemPtr = GetItemPtr (&ddata, item - 1);
    if (itemPtr != NULL) {
      text = StringSave (itemPtr->cached);
    }
  }
  return text;
}

extern void StdResetDocCache (DoC d, Int2 from, Int2 to)

{
  DocData  ddata;
  Int2     i;
  ItemPtr  itemPtr;

  GetPanelExtra ((PaneL) d, &ddata);
  for (i = 0; i < ddata.numItems; i++) {
    itemPtr = GetItemPtr (&ddata, i);
    if (itemPtr != NULL) {
      itemPtr->cached = MemFree (itemPtr->cached);
    }
  }
  SetPanelExtra ((PaneL) d, &ddata);
}

/*****************************************************************************
*
*   ResetDocument (d)
*       Performs garbage collection on document data
*
*****************************************************************************/

static void ResetDocument (PaneL d)

{
  DocData    ddata;
  Int2       i;
  ItemPtr    itemPtr;
  MasterPtr  masterPtr;
  RecT       r;

  GetPanelExtra (d, &ddata);
  if (ddata.data != NULL && ddata.cleanup != NULL) {
    ddata.cleanup ((DoC) d, ddata.data);
  }
  if (ddata.upd != NULL) {
    ddata.upd ((DoC) d, 0, 0);
  }
  ObjectRect (d, &r);
  InsetRect (&r, 4, 4);
  for (i = 0; i < ddata.numItems; i++) {
    itemPtr = GetItemPtr (&ddata, i);
    if (itemPtr != NULL) {
      itemPtr->text = (CharPtr) MemFree (itemPtr->text);
      if (itemPtr->docOwnsData) {
        itemPtr->dataPtr = (Pointer) MemFree (itemPtr->dataPtr);
      } else {
        itemPtr->dataPtr = NULL;
      }
      itemPtr->extra = MemFree (itemPtr->extra);
      itemPtr->cached = MemFree (itemPtr->cached);
    }
  }
  if (ddata.master != NULL) {
    masterPtr = ddata.master;
    for (i = 0; i < MAXLISTS; i++) {
      masterPtr->list [i] = (ListDPtr) MemFree(masterPtr->list [i]);
    }
  }
  ddata.colFmts = ValNodeFreeData (ddata.colFmts);
  ddata.numItems = 0;
  ddata.numLines = 0;
  ddata.barmax = 0;
  ddata.pgUp = 0;
  ddata.pgDn = 0;
  ddata.master = (MasterPtr) MemFree (ddata.master);
  for (i = 0; i < MAXFONTS; i++) {
    fontHeights [i].font = NULL;
    fontHeights [i].height = 0;
  }
  ddata.data = NULL;
  ddata.cleanup = NULL;
  ddata.firstHighlightItem = 0;
  ddata.lastHighlightItem = 0;
  SetPanelExtra ((PaneL) d, &ddata);
}

/*****************************************************************************
*
*   NewDocument (d)
*       Initialized document panel extra data
*
*****************************************************************************/

static void NewDocument (DoC d)

{
  DocData  ddata;
  Int2     i;

  ddata.numItems = 0;
  ddata.numLines = 0;
  ddata.barmax = 0;
  ddata.pgUp = 0;
  ddata.pgDn = 0;
  ddata.tabCount = 4;
  ddata.autoAdjust = TRUE;
  ddata.doc = d;
  ddata.master = NULL;
  ddata.draw = NULL;
  ddata.drawcell = NULL;
  ddata.pan = NULL;
  ddata.gray = NULL;
  ddata.invert = NULL;
  ddata.color = NULL;
  ddata.put = NULL;
  ddata.get = NULL;
  ddata.upd = NULL;
  ddata.data = NULL;
  ddata.cleanup = NULL;
  ddata.colFmts = NULL;
  for (i = 0; i < MAXFONTS; i++) {
    fontHeights [i].font = NULL;
    fontHeights [i].height = 0;
  }
  ddata.defaultParFmt = NULL;
  ddata.defaultColFmt = NULL;
  ddata.defaultFont = NULL;
  ddata.firstHighlightItem = 0;
  ddata.lastHighlightItem = 0;
  ddata.useRowsNotItems = FALSE;
  ddata.notify = NULL;
  SetPanelExtra ((PaneL) d, &ddata);
}

/*****************************************************************************
*
*   DocumentPanel (prnt, pixwidth, pixheight)
*       Creates an empty document object
*
*****************************************************************************/

extern DoC DocumentPanel (GrouP prnt, Int2 pixwidth, Int2 pixheight)

{
  DoC  d;
  WindoW    tempPort;

  d = NULL;
  if (prnt != NULL) {
    tempPort = SavePort (prnt);
    d = (DoC) AutonomousPanel4 (prnt, pixwidth, pixheight, DrawDocument,
                                DocumentScrlProc, NULL, sizeof (DocData),
                                ResetDocument, NULL);
    if (d != NULL) {
      NewDocument (d);
    }
    RestorePort (tempPort);
  }
  return d;
}

/*****************************************************************************
*
*   SetDocProcs (d, click, drag, release, pan)
*       Sets mouse callbacks for a document object
*
*****************************************************************************/

extern void SetDocProcs (DoC d, DocClckProc click, DocClckProc drag,
                         DocClckProc release, DocPanProc pan)

{
  DocData  ddata;

  if (d != NULL) {
    Nlm_SetPanelClick ((PaneL) d, (PnlClckProc) click, (PnlClckProc) drag,
                       NULL, (PnlClckProc) release);
    GetPanelExtra ((PaneL) d, &ddata);
    ddata.pan = pan;
    SetPanelExtra ((PaneL) d, &ddata);
  }
}

/*****************************************************************************
*
*   SetDocShade (d, draw, gray, invert, color)
*       Sets draw callbacks for a document object
*
*****************************************************************************/

extern void SetDocShade (DoC d, DocDrawProc draw, DocShadeProc gray,
                         DocShadeProc invert, DocShadeProc color)

{
  DocData  ddata;

  if (d != NULL) {
    GetPanelExtra ((PaneL) d, &ddata);
    ddata.draw = draw;
    ddata.gray = gray;
    ddata.invert = invert;
    ddata.color = color;
    SetPanelExtra ((PaneL) d, &ddata);
  }
}

/*****************************************************************************
*
*   SetDocExtra (d, drawcell)
*       Sets drawcell callback for a document object
*
*****************************************************************************/

extern void SetDocExtra (DoC d, DocCellProc drawcell)

{
  DocData  ddata;

  if (d != NULL) {
    GetPanelExtra ((PaneL) d, &ddata);
    ddata.drawcell = drawcell;
    SetPanelExtra ((PaneL) d, &ddata);
  }
}

/*****************************************************************************
*
*   SetDocCache (d, put, get, upd)
*       Sets cache callbacks for a document object
*
*****************************************************************************/

extern void SetDocCache (DoC d, DocPutProc put, DocGetProc get, DocUpdProc upd)

{
  DocData  ddata;

  if (d != NULL) {
    GetPanelExtra ((PaneL) d, &ddata);
    ddata.put = put;
    ddata.get = get;
    ddata.upd = upd;
    SetPanelExtra ((PaneL) d, &ddata);
  }
}

/*****************************************************************************
*
*   SetDocData (d, data, cleanup)
*       Attaches instance data to a document object
*
*****************************************************************************/

void SetDocData (DoC d, VoidPtr data, DocFreeProc cleanup)

{
  DocData  ddata;

  if (d != NULL) {
    GetPanelExtra ((PaneL) d, &ddata);
    ddata.data = data;
    ddata.cleanup = cleanup;
    SetPanelExtra ((PaneL) d, &ddata);
  }
}

/*****************************************************************************
*
*   GetDocData (d)
*       Returns the instance data attached to a document object
*
*****************************************************************************/

VoidPtr GetDocData (DoC d)

{
  DocData  ddata;

  if (d != NULL) {
    GetPanelExtra ((PaneL) d, &ddata);
    return (ddata.data);
  } else {
    return NULL;
  }
}

/*****************************************************************************
*
*   GetDocParams (d, numItems, numLines)
*       Returns document-specific information
*
*****************************************************************************/

void GetDocParams4 (DoC d, Int2Ptr numItems, Int4Ptr numLines)

{
  DocData  ddata;
  Int2     items;
  Int4     lines;

  items = 0;
  lines = 0;
  if (d != NULL) {
    GetPanelExtra ((PaneL) d, &ddata);
    items = ddata.numItems;
    lines = ddata.numLines;
  }
  if (numItems != NULL) {
    *numItems = items;
  }
  if (numLines != NULL) {
    *numLines = lines;
  }
}

void GetDocParams (DoC d, Int2Ptr numItems, Int2Ptr numLines)

{
  Int4  lines;

  GetDocParams4 (d, numItems, &lines);
  if (numLines != NULL) {
    *numLines = (Int2) lines;
  }
}

/*****************************************************************************
*
*   GetItemParams (d, item, startsAt, numRows, numCols, lineHeight, data)
*       Returns item-specific information
*
*****************************************************************************/

void GetItemParams4 (DoC d, Int2 item, Int4Ptr startsAt, Int2Ptr numRows,
                     Int2Ptr numCols, Int2Ptr lineHeight, Pointer PNTR data)

{
  Int2     cols;
  Int4     first;
  VoidPtr  dataPtr;
  DocData  ddata;
  Int2     height;
  ItemPtr  itemPtr;
  Int2     rows;

  cols = 0;
  first = 0;
  height = 0;
  rows = 0;
  dataPtr = NULL;
  if (d != NULL && item > 0) {
    GetPanelExtra ((PaneL) d, &ddata);
    itemPtr = GetItemPtr (&ddata, item - 1);
    if (itemPtr != NULL) {
      first = itemPtr->startsAt;
      rows = itemPtr->numRows;
      cols = itemPtr->numCols;
      height = itemPtr->lineHeight;
      dataPtr = itemPtr->dataPtr;
    }
  }
  if (startsAt != NULL) {
    *startsAt = first;
  }
  if (numRows != NULL) {
    *numRows = rows;
  }
  if (numCols != NULL) {
    *numCols = cols;
  }
  if (lineHeight != NULL) {
    *lineHeight = height;
  }
  if (data != NULL) {
    *data = dataPtr;
  }
}

void GetItemParams (DoC d, Int2 item, Int2Ptr startsAt, Int2Ptr numRows,
                    Int2Ptr numCols, Int2Ptr lineHeight, Pointer PNTR data)

{
  Int4  first;

  GetItemParams4 (d, item, &first, numRows, numCols, lineHeight, data);
  if (startsAt != NULL) {
    *startsAt = (Int2) first;
  }
}

/*****************************************************************************
*
*   GetColParams (d, item, col, pixPos, pixWidth, pixInset, just)
*       Returns column-specific information
*
*****************************************************************************/

void GetColParams (DoC d, Int2 item, Int2 col, Int2Ptr pixPos,
                   Int2Ptr pixWidth, Int2Ptr pixInset, CharPtr just)

{
  ColXPtr  colFmt;
  DocData  ddata;
  Int2     inset;
  ItemPtr  itemPtr;
  Char     jst;
  Int2     position;
  RecT     r;
  Int2     width;

  inset = 0;
  position = 0;
  width = 0;
  jst = 'l';
  if (d != NULL && item > 0 && col > 0) {
    GetPanelExtra ((PaneL) d, &ddata);
    ObjectRect (d, &r);
    InsetRect (&r, 4, 4);
    itemPtr = GetItemPtr (&ddata, item - 1);
    if (itemPtr != NULL && col <itemPtr->numCols + 1) {
      colFmt = itemPtr->colFmt;
      if (colFmt != NULL) {
        SetTablePixFormat (colFmt, r.left, itemPtr->numCols);
        position = colFmt [col - 1].position;
        width = colFmt [col - 1].pixWidth;
        inset = colFmt [col - 1].pixInset;
        jst = justToChar [colFmt [col - 1].just];
      }
    }
  }
  if (pixPos != NULL) {
    *pixPos = position;
  }
  if (pixWidth != NULL) {
    *pixWidth = width;
  }
  if (pixInset != NULL) {
    *pixInset = inset;
  }
  if (just != NULL) {
    *just = jst;
  }
}

/*****************************************************************************
*
*   MapDocPoint (d, pt, item, row, col, rct)
*       Converts a mouse point to a item, row, and column positions
*       within a document
*
*****************************************************************************/

extern void MapDocPointEx (DoC d, PoinT pt, Int2Ptr item, Int2Ptr row, Int2Ptr col, RectPtr rct, Boolean prefer_first_match_col)

{
  Int2     cl;
  ColXPtr  colFmt;
  DocData  ddata;
  Int2     firstItem;
  Int2     firstLine;
  Boolean  goOn, found;
  Int2     i;
  Int2     itemNum;
  ItemPtr  itemPtr;
  Int2     itm;
  Int2     leadHeight;
  Int2     lineHeight;
  Int2     numCols;
  Int2     numRows;
  Int4     off;
  RecT     q;
  RecT     r;
  RecT     rc;
  Int2     rw;
  BaR      sb;

  itm = 0;
  rw = 0;
  cl = 0;
  LoadRect (&rc, 0, 0, 0, 0);
  if (d != NULL) {
    GetPanelExtra ((PaneL) d, &ddata);
    ObjectRect (d, &r);
    InsetRect (&r, 4, 4);
    if (PtInRect (pt, &r)) {
      sb = GetSlateVScrollBar ((SlatE) d);
      if (sb != NULL) {
        goOn = TRUE;
        LoadRect (&q, r.left, r.top, r.right, r.top);
        off = GetBarValue (sb);
        itemNum = GetItemNum (&ddata, off);
        itemPtr = GetItemPtr (&ddata, itemNum);
        if (itemPtr != NULL) {
          firstLine = (Int2) (off - itemPtr->startsAt);
          firstItem = itemNum;
          while (goOn && itemNum < ddata.numItems) {
            itemPtr = GetItemPtr (&ddata, itemNum);
            if (itemPtr != NULL) {
              CacheIfNever (&ddata, itemNum + 1, itemPtr, &r, ddata.tabCount);
              SetTablePixFormat (itemPtr->colFmt, r.left, itemPtr->numCols);
              if (itemNum > firstItem) {
                leadHeight = itemPtr->leadHeight;
              } else {
                leadHeight = 0;
              }
              lineHeight = itemPtr->lineHeight;
              numRows = MIN (itemPtr->numRows - firstLine,
                             MAX ((r.bottom - q.top - leadHeight) / lineHeight, 0));
              numCols = itemPtr->numCols;
              colFmt = itemPtr->colFmt;
              q.bottom = q.top + leadHeight + lineHeight * numRows;
              if (PtInRect (pt, &q) && numRows > 0) {
                goOn = FALSE;
                itm = itemNum + 1;
                if (pt.y <= q.top + leadHeight) {
                  rw = 0;
                } else {
                  rw = firstLine + ((pt.y - q.top - leadHeight) / lineHeight) + 1;
                }
                if (numCols > 0 && colFmt != NULL) {
                  found = FALSE;
                  for (i = 0; i < numCols && !found; i++) {
                    if (pt.x >= colFmt [i].position &&
                        pt.x < colFmt [i].position + colFmt [i].pixWidth) {
                      cl = i + 1;
                      if (rw > 0) {
                        rc.left = colFmt [i].position;
                        rc.top = q.top + leadHeight + lineHeight * (rw - firstLine - 1);
                        rc.right = rc.left + colFmt [i].pixWidth;
                        rc.bottom = rc.top + lineHeight;
                      }
                      if (prefer_first_match_col) {
                        found = TRUE;
                      }
                    }
                  }
                }
              } else {
                q.top = q.bottom;
              }
              if (numRows < itemPtr->numRows - firstLine) {
                goOn = FALSE;
              }
            }
            itemNum++;
            firstLine = 0;
          }
        }
      }
    }
  }
  if (item != NULL) {
    *item = itm;
  }
  if (row != NULL) {
    *row = rw;
  }
  if (col != NULL) {
    *col = cl;
  }
  if (rct != NULL) {
    *rct = rc;
  }
}

extern void MapDocPoint (DoC d, PoinT pt, Int2Ptr item, Int2Ptr row, Int2Ptr col, RectPtr rct)
{
  MapDocPointEx (d, pt, item, row, col, rct, FALSE);
}

/*****************************************************************************
*
*   GetDocText (d, item, row, col)
*       Returns a string with text from a document.  If item, row, or col
*       parameters are 0, text is taken from any item, row, or column,
*       respectively.  The string must be freed by the application
*
*****************************************************************************/

extern CharPtr GetDocText (DoC d, Int2 item, Int2 row, Int2 col)

{
  ByteStorePtr  bsp;
  Char          ch;
  Int2          cl;
  DocData       ddata;
  Int2          i;
  ItemData      itemData;
  ItemPtr       itemPtr;
  Int2          k;
  Int2          num;
  CharPtr       ptr;
  RecT          r;
  Int2          rw;
  Int2          start;
  Int2          stop;
  CharPtr       text;

  text = NULL;
  if (d != NULL && item >= 0 && row >= 0 && col >= 0) {
    ObjectRect (d, &r);
    InsetRect (&r, 4, 4);
    GetPanelExtra ((PaneL) d, &ddata);
    if (ddata.numItems > 0) {
      bsp = BSNew (0);
      if (bsp != NULL) {
        if (item == 0) {
          start = 0;
          stop = ddata.numItems;
        } else if (item <= ddata.numItems) {
          start = item - 1;
          stop = item;
        } else {
          start = 0;
          stop = 0;
        }
        for (i = start; i < stop; i++) {
          itemPtr = GetItemPtr (&ddata, i);
          if (itemPtr != NULL) {
            itemData.text = NULL;
            itemData.prtProc = itemPtr->prtProc;
            itemData.dataPtr = itemPtr->dataPtr;
            itemData.font = itemPtr->font;
            itemData.extra = itemPtr->extra;
            itemData.openSpace = itemPtr->openSpace;
            itemData.tabStops = itemPtr->tabStops;
            itemData.numRows = itemPtr->numRows;
            itemData.numCols = itemPtr->numCols;
            itemData.minLines = itemPtr->minLines;
            itemData.minHeight = itemPtr->minHeight;
            itemData.colFmt = itemPtr->colFmt;
            SetTablePixFormat (itemData.colFmt, r.left, itemData.numCols);
            if (itemData.prtProc != NULL) {
              FormatText (&ddata, i + 1, &itemData, TRUE, ddata.tabCount);
              itemPtr->numRows = itemData.numRows;
              itemPtr->neverCached = itemData.neverCached;
              SetTablePixFormat (itemData.colFmt, r.left, itemData.numCols);
              text = itemData.text;
              if (text != NULL && *text != '\0' && itemData.colFmt != NULL) {
                if (i > start && itemData.openSpace > 0 && item == 0) {
                  BSPutByte (bsp, (Int2) '\n');
                }
                rw = 0;
                cl = 0;
                ptr = text;
                ch = *ptr;
                while (ch != '\0') {
                  if (ch == '\n') {
                    if ((rw + 1 == row && col == 0) || row == 0) {
                      BSPutByte (bsp, (Int2) '\n');
                    }
                    cl = 0;
                    rw++;
                    ptr++;
                  } else if (ch == '\t') {
                    if ((rw + 1 == row || row == 0) && col == 0) {
                      BSPutByte (bsp, (Int2) '\t');
                    }
                    cl++;
                    ptr++;
                  } else {
                    num = 0;
                    while (ch != '\0' && ch != '\t' && ch != '\n') {
                      num++;
                      ch = ptr [num];
                    }
                    if (num > 0 && (cl +1 == col || col == 0) &&
                        (rw + 1 == row || row == 0)) {
                      for (k = 0; k < num; k++) {
                        BSPutByte (bsp, (Int2) ptr [k]);
                      }
                    }
                    ptr += num;
                  }
                  ch = *ptr;
                }
              }
            }
            MemFree (itemData.text);
          }
        }
        text = (CharPtr) BSMerge(bsp, NULL);
        BSFree (bsp);
        ddata.numLines = UpdateLineStarts (&ddata, start);
        SetPanelExtra ((PaneL) d, &ddata);
      }
    }
  }
  return text;
}

/*****************************************************************************
*
*   ItemIsVisible (d, item, top, bottom, firstLine)
*       Finds the visible region of an item in a document
*
*****************************************************************************/

extern Boolean ItemIsVisible (DoC d, Int2 item, Int2Ptr top,
                              Int2Ptr bottom, Int2Ptr firstLine)

{
  DocData  ddata;
  Int2     firstItem;
  Int2     fstLine;
  Boolean  goOn;
  Int2     itemNum;
  ItemPtr  itemPtr;
  Int2     leadHeight;
  Int2     lineHeight;
  Int2     numRows;
  Int4     off;
  RecT     r;
  RecT     rct;
  Boolean  rsult;
  BaR      sb;

  rsult = FALSE;
  if (top != NULL) {
    *top = 0;
  }
  if (bottom != NULL) {
    *bottom = 0;
  }
  if (firstLine != NULL) {
   *firstLine = 0;
  }
  if (d != NULL) {
    ObjectRect (d, &r);
    InsetRect (&r, 4, 4);
    GetPanelExtra ((PaneL) d, &ddata);
    sb = GetSlateVScrollBar ((SlatE) d);
    if (item > 0 && item <= ddata.numItems && sb != NULL) {
      item--;
      goOn = TRUE;
      LoadRect (&rct, r.left, r.top, r.right, r.top);
      off = GetBarValue (sb);
      itemNum = GetItemNum (&ddata, off);
      itemPtr = GetItemPtr (&ddata, itemNum);
      if (itemPtr != NULL) {
        fstLine = (Int2) (off - itemPtr->startsAt);
        firstItem = itemNum;
        lineHeight = 0;
        leadHeight = 0;
        numRows = 0;
        while (goOn && itemNum < ddata.numItems && itemNum <= item) {
          itemPtr = GetItemPtr (&ddata, itemNum);
          if (itemPtr != NULL) {
            CacheIfNever (&ddata, itemNum + 1, itemPtr, &r, ddata.tabCount);
            SetTablePixFormat (itemPtr->colFmt, r.left, itemPtr->numCols);
            if (itemNum > firstItem) {
              leadHeight = itemPtr->leadHeight;
            } else {
              leadHeight = 0;
            }
            lineHeight = itemPtr->lineHeight;
            rct.top += leadHeight;
            numRows = MIN (itemPtr->numRows - fstLine,
                           MAX ((r.bottom - rct.top) / lineHeight, 0));
            rct.bottom = rct.top + lineHeight * numRows;
            if (itemNum == item) {
              goOn = FALSE;
              if (numRows > 0) {
                if (top != NULL) {
                  *top = rct.top;
                }
                if (bottom != NULL) {
                  *bottom = rct.bottom;
                }
                if (firstLine != NULL) {
                  *firstLine = fstLine;
                }
                rsult = TRUE;
              }
            } else {
              rct.top = rct.bottom;
            }
            if (numRows < itemPtr->numRows - fstLine) {
              goOn = FALSE;
            }
          }
          itemNum++;
          fstLine = 0;
        }
      }
    }
  }
  return rsult;
}

/*****************************************************************************
*
*   GetScrlParams (d, offset, firstShown, firstLine)
*       Returns scroll-specific information
*
*****************************************************************************/

extern Boolean GetScrlParams4 (DoC d, Int4Ptr offset,
                               Int2Ptr firstShown, Int2Ptr firstLine)

{
  DocData  ddata;
  Int2     item;
  Int4     line;
  Int4     off;
  Boolean  rsult;
  BaR      sb;

  rsult = FALSE;
  off = 0;
  item = 0;
  line = 0;
  if (d != NULL) {
    GetPanelExtra ((PaneL) d, &ddata);
    sb = GetSlateVScrollBar ((SlatE) d);
    if (sb != NULL) {
      off = GetBarValue (sb);
      item = GetItemNum (&ddata, off);
      line = off - GetStartsAt (&ddata, item);
      rsult = TRUE;
    }
  }
  if (offset != NULL) {
    *offset = off;
  }
  if (firstShown != NULL) {
    *firstShown = item + 1;
  }
  if (firstLine != NULL) {
    *firstLine = (Int2) line;
  }
  return rsult;
}

extern void SetScrlParams4 (DoC d, Int4 offset)

{
  BaR      sb;

  if (d != NULL) {
    sb = GetSlateVScrollBar ((SlatE) d);
    if (sb != NULL) {
      SetBarValue (sb, offset);
    }
  }
}


extern Boolean GetScrlParams (DoC d, Int2Ptr offset,
                              Int2Ptr firstShown, Int2Ptr firstLine)

{
  Int4     off;
  Boolean  rsult;

  rsult = GetScrlParams4 (d, &off, firstShown, firstLine);
  if (offset != NULL) {
    *offset = (Int2) off;
  }
  return rsult;
}

/*****************************************************************************
*
*   RowIsVisible (d, item, row, top, bottom)
*       Finds the visible region of a row in an item of a document
*
*****************************************************************************/

extern Boolean RowIsVisible (DoC d, Int2 item, Int2 row,
                             Int2Ptr top, Int2Ptr bottom)

{
  Int2     bt;
  Int2     first;
  Int2     from;
  Int2     lineHeight;
  Int2     numRows;
  RecT     r;
  Boolean  rsult;
  Int2     tmp;
  Int2     to;
  Int2     tp;

  rsult = FALSE;
  if (top != NULL) {
    *top = 0;
  }
  if (bottom != NULL) {
    *bottom = 0;
  }
  if (d != NULL && item > 0) {
    if (Enabled (d) && AllParentsEnabled (d) &&
        Visible (d) && AllParentsVisible (d)) {
      if (ItemIsVisible (d, item, &tp, &bt, &first)) {
        ObjectRect (d, &r);
        InsetRect (&r, 4, 4);
        GetItemParams (d, item, NULL, &numRows, NULL, &lineHeight, NULL);
        from = MIN (row, numRows);
        to = MIN (row, numRows);
        first++;
        if (from > to) {
          tmp = from;
          from = to;
          to = tmp;
        }
        if (to >= first) {
          while (from > first) {
            tp += lineHeight;
            first++;
          }
          bt = tp;
          while (to >= from) {
            bt += lineHeight;
            from++;
          }
          if (tp >= r.top && tp <= r.bottom &&
              bt >= r.top && bt <= r.bottom) {
            if (top != NULL) {
              *top = tp;
            }
            if (bottom != NULL) {
              *bottom = bt;
            }
            rsult = TRUE;
          }
        }
      }
    }
  }
  return rsult;
}

/*****************************************************************************
*
*   InvalDocRows (d, item, from, to)
*       Invalidates rows in a document
*
*****************************************************************************/

extern void InvalDocRows (DoC d, Int2 item, Int2 from, Int2 to)

{
  Int2    bottom;
  Int2    first;
  Int2    lineHeight;
  Int2    numRows;
  RecT    r;
  WindoW  tempPort;
  Int2    tmp;
  Int2    top;

  if (d != NULL) {
    if (Enabled (d) && AllParentsEnabled (d) &&
        Visible (d) && AllParentsVisible (d)) {
      if (item > 0 && from >= 0 && to >= 0) {
        if (ItemIsVisible (d, item, &top, &bottom, &first)) {
          ObjectRect (d, &r);
          InsetRect (&r, 4, 4);
          GetItemParams (d, item, NULL, &numRows, NULL, &lineHeight, NULL);
          from = MIN (from, numRows);
          to = MIN (to, numRows);
          first++;
          if (from > to) {
            tmp = from;
            from = to;
            to = tmp;
          }
          if (to >= first) {
            while (from > first) {
              top += lineHeight;
              first++;
            }
            bottom = top;
            while (to >= from) {
              bottom += lineHeight;
              from++;
            }
            if (top >= r.top && top <= r.bottom &&
                bottom >= r.top && bottom <= r.bottom) {
              r.top = top;
              r.bottom = bottom;
              tempPort = SavePort (d);
              Select (d);
              InsetRect (&r, -1, -1);
              InvalRect (&r);
              RestorePort (tempPort);
            }
          }
        }
      } else if (item == 0) {
        tempPort = SavePort (d);
        Select (d);
        ObjectRect (d, &r);
        InsetRect (&r, 3, 3);
        InvalRect (&r);
        RestorePort (tempPort);
      }
    }
  }
}

/*****************************************************************************
*
*   InvalDocCols (d, item, from, to)
*       Invalidates columns in a document
*
*****************************************************************************/

extern void InvalDocCols (DoC d, Int2 item, Int2 from, Int2 to)

{
  Int2     bottom;
  ColXPtr  colFmt;
  DocData  ddata;
  ItemPtr  itemPtr;
  Int2     numCols;
  RecT     r;
  WindoW   tempPort;
  Int2     tmp;
  Int2     top;

  if (d != NULL) {
    if (Enabled (d) && AllParentsEnabled (d) &&
        Visible (d) && AllParentsVisible (d)) {
      if (item > 0 && from >= 0 && to >= 0) {
        if (ItemIsVisible (d, item, &top, &bottom, NULL)) {
          GetPanelExtra ((PaneL) d, &ddata);
          itemPtr = GetItemPtr (&ddata, item - 1);
          if (itemPtr == NULL) return;
          colFmt = itemPtr->colFmt;
          if (colFmt == NULL) return;
          ObjectRect (d, &r);
          InsetRect (&r, 4, 4);
          numCols = itemPtr->numCols;
          from = MIN (from, numCols);
          to = MIN (to, numCols);
          if (from > to) {
            tmp = from;
            from = to;
            to = tmp;
          }
          if (from > 0) {
            from--;
          }
          if (to > 0 && to <= numCols) {
            to--;
          }
          if (from >= 0 && from < numCols && to >= 0 && to < numCols) {
            r.left = colFmt [from].position;
            r.right = colFmt [to].position + colFmt [to].pixWidth;
            if (top >= r.top && top <= r.bottom &&
                bottom >= r.top && bottom <= r.bottom) {
              r.top = top;
              r.bottom = bottom;
              tempPort = SavePort (d);
              Select (d);
              InsetRect (&r, -1, -1);
              InvalRect (&r);
              RestorePort (tempPort);
            }
          }
        }
      } else if (item == 0) {
        tempPort = SavePort (d);
        Select (d);
        ObjectRect (d, &r);
        InsetRect (&r, 3, 3);
        InvalRect (&r);
        RestorePort (tempPort);
      }
    }
  }
}

/*****************************************************************************
*
*   InvalDocument (d)
*       Invalidates visible area of a document
*
*****************************************************************************/

extern void InvalDocument (DoC d)

{
  InvalDocRows (d, 0, 0, 0);
}

/*****************************************************************************
*
*   UpdateDocument (d, from, to)
*       Invalidates and updates items in a document
*
*****************************************************************************/

extern void UpdateDocument (DoC d, Int2 from, Int2 to)

{
  Int2     bottom;
  Boolean  botvis;
  DocData  ddata;
  Int2     item;
  ItemPtr  itemPtr;
  Int2     last;
  Int4     line;
  Int4     off;
  RecT     r;
  RecT     s;
  BaR      sb;
  Int2     start;
  Int2     stop;
  Int2     swap;
  WindoW   tempPort;
  Int2     top;
  Boolean  topvis;
  Int2     vis;

  if (d != NULL && from >= 0 && to >= 0) {
    GetPanelExtra ((PaneL) d, &ddata);
    if (from == 0 || from > ddata.numItems) {
      start = 0;
      from = 0;
    } else {
      start = from - 1;
    }
    if (to == 0 || to > ddata.numItems) {
      stop = ddata.numItems;
      to = ddata.numItems;
    } else {
      stop = to;
    }
    for (item = start; item < stop; item++) {
      itemPtr = GetItemPtr (&ddata,item);
      if (itemPtr != NULL) {
        itemPtr->text = (CharPtr) MemFree (itemPtr->text);
        itemPtr->notCached = TRUE;
        itemPtr->neverCached = TRUE;
        itemPtr->cached = (CharPtr) MemFree (itemPtr->cached);
      }
    }
    if (ddata.upd != NULL) {
      ddata.upd (d, MIN (from, to), MAX (from, to));
    }
    if (Enabled (d) && AllParentsEnabled (d) &&
        Visible (d) && AllParentsVisible (d)) {
      tempPort = SavePort (d);
      ObjectRect (d, &r);
      InsetRect (&r, 4, 4);
      Select (d);
      if (from > to && from != 0 && to != ddata.numItems) {
        swap = from;
        from = to;
        to = swap;
      }
      topvis = FALSE;
      botvis = FALSE;
      if (from > 0 && from <= ddata.numItems) {
        topvis = ItemIsVisible (d, from, &top, NULL, NULL);
        if (topvis) {
          r.top = top;
        }
      } else {
        topvis = TRUE;
      }
      if (to > 0 && to <= ddata.numItems) {
        botvis = ItemIsVisible (d, to, NULL, &bottom, NULL);
        if (botvis) {
          r.bottom = bottom;
        }
      } else {
        botvis = TRUE;
      }
      if (topvis || botvis) {
        InsetRect (&r, -1, -1);
        InvalRect (&r);
      } else {
        ObjectRect (d, &s);
        InsetRect (&s, 4, 4);
        sb = GetSlateVScrollBar ((SlatE) d);
        off = GetBarValue (sb);
        item = GetItemNum (&ddata, off);
        line = (Int2) (off - GetStartsAt (&ddata, item));
        vis = VisLinesBelow (&ddata, &s, item, line, NULL, NULL);
        last = GetItemNum (&ddata, off + vis - 1);
        if (item >= stop || last < start) {
        } else {
          InsetRect (&r, -1, -1);
          InvalRect (&r);
        }
      }
      RestorePort (tempPort);
    }
    AdjustDocScroll (d);
  }
}

/*****************************************************************************
*
*   SaveTableItem (itemPtr, f, tabStops, tabCount)
*       Reformats and saves an item to a file
*
*****************************************************************************/

static void SaveTableItem (ItemPtr itemPtr, FILE *f,
                           Boolean tabStops, Int2 tabCount)

{
  Char     ch;
  Int2     col;
  ColXPtr  colFmt;
  Int2     i;
  Int2     insetLeft;
  Int2     insetRight;
  Char     just;
  Int2     next;
  Int2     num;
  Int2     pos;
  CharPtr  ptr;
  Int2     row;
  CharPtr  text;

  if (itemPtr != NULL && f != NULL) {
    text = itemPtr->text;
    colFmt = itemPtr->colFmt;
    if (text != NULL && *text != '\0' && colFmt != NULL) {
      pos = 0;
      row = 0;
      col = 0;
      ptr = text;
      ch = *ptr;
      while (ch != '\0') {
        if (ch == '\n') {
          col = 0;
          row++;
          ptr++;
          pos = 0;
          fputc ('\n', f);
        } else if (ch == '\t' && (! tabStops)) {
          col++;
          ptr++;
        } else  {
          num = 0;
          while (ch != '\0' && (ch != '\t' || tabStops) && ch != '\n') {
            num++;
            ch = ptr [num];
          }
          if (num > 0 && col < itemPtr->numCols) {
            just = justToChar [colFmt [col].just];
            if (just == 'c') {
              insetLeft = colFmt [col].charInset;
              insetRight = colFmt [col].charInset;
            } else if (just == 'l') {
              insetLeft = colFmt [col].charInset;
              insetRight = 0;
            } else if (just == 'r') {
              insetLeft = 0;
              insetRight = colFmt [col].charInset;
            } else {
              insetLeft = 0;
              insetRight = 0;
            }
            next = colFmt [col].position + insetLeft;
            if (tabCount > 0) {
              while (num > 0 && *ptr == '\t') {
                num--;
                ptr++;
                next += tabCount;
              }
            }
            while (pos < next) {
              fputc (' ', f);
              pos++;
            }
            if (just == 'r') {
              next = colFmt [col].position + colFmt [col].charWidth -
                     insetRight - num;
              while (pos < next) {
                fputc (' ', f);
                pos++;
              }
            } else if (just == 'c') {
              next = colFmt [col].position + (colFmt [col].charWidth -
                     insetRight - num) / 2;
              while (pos < next) {
                fputc (' ', f);
                pos++;
              }
            }
            for (i = 0; i < num; i++) {
              fputc (ptr [i], f);
              pos++;
            }
          }
          ptr += num;
        }
        ch = *ptr;
      }
    }
  }
}

/*****************************************************************************
*
*   SaveDocument (d, f)
*       Saves all document items to a file
*
*****************************************************************************/

extern void SaveDocument (DoC d, FILE *f)

{
  DocData   ddata;
  Int2      i;
  ItemData  itemData;
  ItemPtr   itemPtr;

  if (d != NULL && f != NULL) {
    GetPanelExtra ((PaneL) d, &ddata);
    ddata.put = NULL;
    ddata.get = NULL;
    ddata.upd = NULL;
    for (i = 0; i < ddata.numItems; i++) {
      itemPtr = GetItemPtr (&ddata, i);
      if (itemPtr != NULL) {
        itemData.text = NULL;
        itemData.cached = NULL;
        itemData.prtProc = itemPtr->prtProc;
        itemData.dataPtr = itemPtr->dataPtr;
        itemData.font = itemPtr->font;
        itemData.extra = itemPtr->extra;
        itemData.openSpace = itemPtr->openSpace;
        itemData.tabStops = itemPtr->tabStops;
        itemData.numRows = itemPtr->numRows;
        itemData.numCols = itemPtr->numCols;
        itemData.minLines = itemPtr->minLines;
        itemData.minHeight = itemPtr->minHeight;
        itemData.colFmt = itemPtr->colFmt;
        SetTableCharFormat (itemData.colFmt, itemData.numCols);
        if (itemData.prtProc != NULL) {
          FormatText (&ddata, i + 1, &itemData, FALSE, ddata.tabCount);
          SetTableCharFormat (itemData.colFmt, itemData.numCols);
          if (i > 0 && itemData.openSpace > 0) {
            fputc ('\n', f);
          }
          SaveTableItem (&itemData, f,
                         (Boolean) (unsigned int) itemData.tabStops,
                         ddata.tabCount);
          MemFree (itemData.text);
        }
      }
    }
  }
}

/*****************************************************************************
*
*   SaveDocumentItem (d, f, item)
*       Saves one document item to a file
*
*****************************************************************************/

extern void SaveDocumentItem (DoC d, FILE *f, Int2 item)

{
  DocData   ddata;
  ItemData  itemData;
  ItemPtr   itemPtr;

  if (d != NULL && f != NULL) {
    GetPanelExtra ((PaneL) d, &ddata);
    ddata.put = NULL;
    ddata.get = NULL;
    ddata.upd = NULL;
    if (item > 0 && item <= ddata.numItems) {
      itemPtr = GetItemPtr (&ddata, item - 1);
      if (itemPtr != NULL) {
        itemData.text = NULL;
        itemData.cached = NULL;
        itemData.prtProc = itemPtr->prtProc;
        itemData.dataPtr = itemPtr->dataPtr;
        itemData.font = itemPtr->font;
        itemData.extra = itemPtr->extra;
        itemData.openSpace = itemPtr->openSpace;
        itemData.tabStops = itemPtr->tabStops;
        itemData.numRows = itemPtr->numRows;
        itemData.numCols = itemPtr->numCols;
        itemData.minLines = itemPtr->minLines;
        itemData.minHeight = itemPtr->minHeight;
        itemData.colFmt = itemPtr->colFmt;
        SetTableCharFormat (itemData.colFmt, itemData.numCols);
        if (itemData.prtProc != NULL) {
          FormatText (&ddata, item, &itemData, FALSE, ddata.tabCount);
          SetTableCharFormat (itemData.colFmt, itemData.numCols);
          /*
          if (item > 1 && itemData.openSpace > 0) {
            fputc ('\n', f);
          }
          */
          SaveTableItem (&itemData, f,
                         (Boolean) (unsigned int) itemData.tabStops,
                         ddata.tabCount);
          MemFree (itemData.text);
        }
      }
    }
  }
}

/*****************************************************************************
*
*   PrintDocument (d)
*       Reformats and sends a document to the printer
*
*****************************************************************************/

#ifdef WIN_MOTIF
extern CharPtr Nlm_XrmGetResource (const Char PNTR _resource);
#endif

extern void PrintDocument (DoC d)

{
#ifndef WIN_MOTIF
  DocData   ddata;
  Boolean   goOn;
  Int2      i;
  Int2      item;
  ItemPtr   itemPtr;
  ItemData  itemData;
  Int2      line;
  Boolean   newPage;
  Int2      pixels;
  RecT      r;
  RecT      rct;
  FloatHi   scale;
  Int2      visBelow;
  WindoW    w;

  if (d != NULL) {
    GetPanelExtra ((PaneL) d, &ddata);
    ddata.put = NULL;
    ddata.get = NULL;
    ddata.upd = NULL;
    if (ddata.numItems > 0) {
      ObjectRect (d, &rct);
      InsetRect (&rct, 4, 4);
      w = StartPrinting ();
      if (w != NULL) {
        for (i = 0; i < MAXFONTS; i++) {
          fontHeights [i].font = NULL;
          fontHeights [i].height = 0;
        }
        goOn = TRUE;
        pixels = 0;
        item = 0;
        newPage = TRUE;
        while (item < ddata.numItems && goOn) {
          if (newPage) {
            goOn = StartPage ();
            newPage = FALSE;
            PrintingRect (&r);
          }
          if (goOn) {
            itemPtr = GetItemPtr (&ddata, item);
            if (itemPtr != NULL) {
              itemData.text = NULL;
              itemData.cached = NULL;
              itemData.prtProc = itemPtr->prtProc;
              itemData.dataPtr = itemPtr->dataPtr;
              itemData.font = itemPtr->font;
              itemData.extra = itemPtr->extra;
              itemData.openSpace = itemPtr->openSpace;
              itemData.keepWithNext = itemPtr->keepWithNext;
              itemData.keepTogether = itemPtr->keepTogether;
              itemData.newPage = itemPtr->newPage;
              itemData.tabStops = itemPtr->tabStops;
              itemData.numRows = itemPtr->numRows;
              itemData.numCols = itemPtr->numCols;
              itemData.minLines = itemPtr->minLines;
              itemData.minHeight = itemPtr->minHeight;
              itemData.colFmt = (ColXPtr) MemNew (itemData.numCols * sizeof (ColXData));
              if (itemData.colFmt != NULL) {
                MemCopy (itemData.colFmt, itemPtr->colFmt,
                         itemData.numCols * sizeof (ColXData));
                for (i = 0; i < itemData.numCols; i++) {
                  if (itemData.colFmt [i].zeroWidth) {
                    itemData.colFmt [i].pixWidth = 0;
                  }
                }
              }
              UpdateItemHeights (&itemData);
              scale = (FloatHi) (r.right - r.left) / (FloatHi) (rct.right - rct.left);
              for (i = 0; i < itemData.numCols; i++) {
                itemData.colFmt [i].pixWidth = (Int2) (scale *
                                               (FloatHi) itemData.colFmt [i].pixWidth);
              }
              SetTablePixFormat (itemData.colFmt, r.left, itemData.numCols);
              if (itemData.prtProc != NULL) {
                FormatText (&ddata, item + 1, &itemData, TRUE, ddata.tabCount);
                SetTablePixFormat (itemData.colFmt, r.left, itemData.numCols);
                if (pixels != 0) {
                  r.top += itemData.leadHeight;
                }
                visBelow = (r.bottom - r.top) / itemData.lineHeight;
                if ((itemData.keepTogether && visBelow < itemData.numRows) ||
                    itemData.newPage) {
                  goOn = EndPage ();
                  if (goOn) {
                    goOn = StartPage ();
                  }
                  newPage = FALSE;
                  PrintingRect (&r);
                  visBelow = (r.bottom - r.top) / itemData.lineHeight;
                }
                line = 0;
                while (visBelow + line < itemData.numRows && goOn) {
                  pixels = DrawTableItem (d, &ddata, &itemData, &r, item, line, NULL,
                                          NULL, NULL, TRUE, ddata.tabCount);
                  r.top += pixels;
                  line += visBelow;
                  goOn = EndPage ();
                  if (goOn) {
                    StartPage ();
                  }
                  newPage = FALSE;
                  PrintingRect (&r);
                  visBelow = (r.bottom - r.top) / itemData.lineHeight;
                }
                if (visBelow > 0 && goOn) {
                  pixels = DrawTableItem (d, &ddata, &itemData, &r, item, line, NULL,
                                          NULL, NULL, TRUE, ddata.tabCount);
                  r.top += pixels;
                }
              }
              MemFree (itemData.text);
              MemFree (itemData.colFmt);
            }
          }
          item++;
          if (r.top >= r.bottom) {
            goOn = EndPage ();
            newPage = TRUE;
          }
        }
        if ((! newPage) && goOn) {
          goOn = EndPage ();
        }
        EndPrinting (w);
        for (i = 0; i < MAXFONTS; i++) {
          fontHeights [i].font = NULL;
          fontHeights [i].height = 0;
        }
      }
    }
  }
#endif
#ifdef WIN_MOTIF
  Char     cmmd [256];
  FILE     *f;
  Int2     len;
  CharPtr  printCmd;
  Char     str [PATH_MAX];

  if (d != NULL) {
    TmpNam (str);
    f = FileOpen (str, "w");
    if (f != NULL) {
      SaveDocument (d, f);
      FileClose (f);
      printCmd = Nlm_XrmGetResource ("printCommand");
      if (printCmd != NULL) {
        StringNCpy_0 (cmmd, printCmd, sizeof (cmmd) - 1);
      } else {
        StringCpy (cmmd, "lp -c");
      }
      MemFree (printCmd);
      len = (Int2) StringLen (cmmd);
      while (len > 0 && cmmd [len] == ' ') {
        cmmd [len] = '\0';
        len--;
      }
      StringCat (cmmd, " ");
      StringCat (cmmd, str);
      StringCat (cmmd, "; rm ");
      StringCat (cmmd, str);
      system (cmmd);
    } else {
      StringCpy (cmmd, "rm ");
      StringCat (cmmd, str);
      system (cmmd);
    }
  }
#endif
}

/*****************************************************************************
*
*   displayTable
*       Default column table for file display functions
*
*****************************************************************************/

static ColData displayTable = {0, 0, 80, 0, NULL, 'l',
                               TRUE, FALSE, FALSE, FALSE, TRUE};

/*****************************************************************************
*
*   GetChar (fp)
*       Gets a single character from a file, returning '\0' on end of file
*
*****************************************************************************/

static Char GetChar (FILE *fp)

{
#if (defined(OS_DOS) || defined (OS_NT))
  Int2  actual;
  Char  buf [32];
  Char  ch;

  ch = '\0';
  actual = (Int2) FileRead (buf, 1, 1, fp);
  if (actual > 0) {
    ch = buf [0];
  }
  return ch;
#else
  int  ch;

  ch = fgetc (fp);
  if (ch == EOF) {
    ch = '\0';
  }
  return (Char) ch;
#endif
}

/*****************************************************************************
*
*   DisplayFancy (d, file, parFmt, colFmt, font, tabStops)
*       Uses a document to display a file, allowing paragraph and column
*       formatting to be specified
*
*****************************************************************************/

extern void DisplayFancy (DoC d, CharPtr file, ParPtr parFmt,
                          ColPtr colFmt, FonT font, Int2 tabStops)

{
  Int2     actual;
  Char     ch;
  Int2     cnt;
  Int8     cntr;
  DocData  ddata;
  FILE     *fp;
  Int2     leftOver;
  ParData  para;
  RecT     r;
  WindoW   tempPort;
  CharPtr  text;
  CharPtr  txt;
#ifdef WIN_MAC
  CharPtr  p;
  CharPtr  q;
#endif
#if (defined(OS_DOS) || defined (OS_NT))
  CharPtr  p;
  CharPtr  q;
#endif

  if (d != NULL && file != NULL && file [0] != '\0') {
    GetPanelExtra ((PaneL) d, &ddata);
    ddata.tabCount = tabStops;
    SetPanelExtra ((PaneL) d, &ddata);
    if (parFmt == NULL) {
      parFmt = &para;
    }
    if (colFmt == NULL) {
      colFmt = &displayTable;
    }
    Reset (d);
    tempPort = SavePort (d);
    ObjectRect (d, &r);
    InsetRect (&r, 4, 4);
    displayTable.pixWidth = r.right - r.left;
    if (font == NULL) {
      font = systemFont;
    }
    text = (CharPtr) MemNew (24000);
    if (text != NULL) {
      fp = FileOpen (file, "r");
      if (fp != NULL) {
        leftOver = 0;
        cntr = FileLength (file);
        cnt = (Int2) MIN (cntr, 15000L);
        para.openSpace = FALSE;
        para.keepWithNext = FALSE;
        para.keepTogether = FALSE;
        para.newPage = FALSE;
        para.tabStops = TRUE;
        para.minLines = 0;
        para.minHeight = 0;
        while (cnt > 0 && cntr > 0) {
          txt = text + leftOver;
          actual = (Int2) FileRead (txt, 1, cnt, fp);
          if (actual > 0) {
            cnt = actual;
            txt [cnt] = '\0';
            ch = GetChar (fp);
            while (ch != '\0' && ch != '\n' && cnt < 15900) {
              txt [cnt] = ch;
              cnt++;
              ch = GetChar (fp);
            }
            while ((ch == '\n' || ch == '\r') && cnt < 15900) {
              txt [cnt] = ch;
              cnt++;
              ch = GetChar (fp);
            }
            txt [cnt] = '\0';
#if (defined(OS_DOS) || defined (OS_NT))
            p = text;
            q = text;
            while (*p) {
              if (*p == '\r') {
                p++;
              } else {
                *q = *p;
                p++;
                q++;
              }
            }
            *q = '\0';
#endif
#ifdef WIN_MAC
            p = text;
            q = text;
            while (*p) {
              if (*p == '\n') {
                *q = '\n';
                p++;
                q++;
                if (*p == '\r') {
                  p++;
                }
              } else if (*p == '\r') {
                *q = '\n';
                p++;
                q++;
                if (*p == '\n') {
                  p++;
                }
              } else {
                *q = *p;
                p++;
                q++;
              }
            }
            *q = '\0';
#endif
            AppendText (d, text, parFmt, colFmt, font);
            leftOver = 1;
            text [0] = ch;
            cntr -= cnt;
            cnt = (Int2) MIN (cntr, 15000L);
          } else {
            cnt = 0;
            cntr = 0;
          }
        }
        FileClose (fp);
      }
      text = (CharPtr) MemFree (text);
    }
    if (Enabled (d) && AllParentsEnabled (d) &&
        Visible (d) && AllParentsVisible (d)) {
      ObjectRect (d, &r);
      InsetRect (&r, 3, 3);
      Select (d);
      InvalRect (&r);
    }
    if (! ddata.autoAdjust) {
      AdjustDocScroll (d);
    }
    RestorePort (tempPort);
  }
}

/*****************************************************************************
*
*   DisplayFile (d, file, font)
*       Simple function to display a file in a document
*
*****************************************************************************/

extern void DisplayFile (DoC d, CharPtr file, FonT font)

{
  DisplayFancy (d, file, NULL, NULL, font, 4);
}

