Logo Search packages:      
Sourcecode: saods9 version File versions

tkTextDisp.c

/* 
 * tkTextDisp.c --
 *
 *    This module provides facilities to display text widgets.  It is
 *    the only place where information is kept about the screen layout
 *    of text widgets.
 *
 * Copyright (c) 1992-1994 The Regents of the University of California.
 * Copyright (c) 1994-1997 Sun Microsystems, Inc.
 *
 * See the file "license.terms" for information on usage and redistribution
 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
 *
 * RCS: @(#) $Id: tkTextDisp.c,v 1.1.1.1 2004/04/02 22:35:07 joye Exp $
 */

#include "tkPort.h"
#include "tkInt.h"
#include "tkText.h"

#ifdef __WIN32__
#include "tkWinInt.h"
#endif

/*
 * The following structure describes how to display a range of characters.
 * The information is generated by scanning all of the tags associated
 * with the characters and combining that with default information for
 * the overall widget.  These structures form the hash keys for
 * dInfoPtr->styleTable.
 */

typedef struct StyleValues {
    Tk_3DBorder border;       /* Used for drawing background under text.
                         * NULL means use widget background. */
    int borderWidth;          /* Width of 3-D border for background. */
    int relief;               /* 3-D relief for background. */
    Pixmap bgStipple;         /* Stipple bitmap for background.  None
                         * means draw solid. */
    XColor *fgColor;          /* Foreground color for text. */
    Tk_Font tkfont;           /* Font for displaying text. */
    Pixmap fgStipple;         /* Stipple bitmap for text and other
                         * foreground stuff.   None means draw
                         * solid.*/
    int justify;        /* Justification style for text. */
    int lMargin1;       /* Left margin, in pixels, for first display
                         * line of each text line. */
    int lMargin2;       /* Left margin, in pixels, for second and
                         * later display lines of each text line. */
    int offset;               /* Offset in pixels of baseline, relative to
                         * baseline of line. */
    int overstrike;           /* Non-zero means draw overstrike through
                         * text. */
    int rMargin;        /* Right margin, in pixels. */
    int spacing1;       /* Spacing above first dline in text line. */
    int spacing2;       /* Spacing between lines of dline. */
    int spacing3;       /* Spacing below last dline in text line. */
    TkTextTabArray *tabArrayPtr;/* Locations and types of tab stops (may
                         * be NULL). */
    int underline;            /* Non-zero means draw underline underneath
                         * text. */
    int elide;                /* Non-zero means draw text */
    TkWrapMode wrapMode;      /* How to handle wrap-around for this tag.
                         * One of TEXT_WRAPMODE_CHAR,
                         * TEXT_WRAPMODE_NONE or TEXT_WRAPMODE_WORD.*/
} StyleValues;

/*
 * The following structure extends the StyleValues structure above with
 * graphics contexts used to actually draw the characters.  The entries
 * in dInfoPtr->styleTable point to structures of this type.
 */

typedef struct TextStyle {
    int refCount;       /* Number of times this structure is
                         * referenced in Chunks. */
    GC bgGC;                  /* Graphics context for background.  None
                         * means use widget background. */
    GC fgGC;                  /* Graphics context for foreground. */
    StyleValues *sValuePtr;   /* Raw information from which GCs were
                         * derived. */
    Tcl_HashEntry *hPtr;      /* Pointer to entry in styleTable.  Used
                         * to delete entry. */
} TextStyle;

/*
 * The following macro determines whether two styles have the same
 * background so that, for example, no beveled border should be drawn
 * between them.
 */

#define SAME_BACKGROUND(s1, s2) \
    (((s1)->sValuePtr->border == (s2)->sValuePtr->border) \
        && ((s1)->sValuePtr->borderWidth == (s2)->sValuePtr->borderWidth) \
        && ((s1)->sValuePtr->relief == (s2)->sValuePtr->relief) \
        && ((s1)->sValuePtr->bgStipple == (s2)->sValuePtr->bgStipple))

/*
 * The following macro is used to compare two floating-point numbers
 * to within a certain degree of scale.  Direct comparison fails on
 * processors where the processor and memory representations of FP
 * numbers of a particular precision is different (e.g. Intel)
 */

#define FP_EQUAL_SCALE(double1, double2, scaleFactor) \
    (fabs((double1)-(double2))*((scaleFactor)+1.0) < 0.3)

/*
 * The following structure describes one line of the display, which may
 * be either part or all of one line of the text.
 */

typedef struct DLine {
    TkTextIndex index;        /* Identifies first character in text
                         * that is displayed on this line. */
    int byteCount;            /* Number of bytes accounted for by this
                         * display line, including a trailing space
                         * or newline that isn't actually displayed. */
    int y;              /* Y-position at which line is supposed to
                         * be drawn (topmost pixel of rectangular
                         * area occupied by line). */
    int oldY;                 /* Y-position at which line currently
                         * appears on display.  -1 means line isn't
                         * currently visible on display and must be
                         * redrawn.  This is used to move lines by
                         * scrolling rather than re-drawing. */
    int height;               /* Height of line, in pixels. */
    int baseline;       /* Offset of text baseline from y, in
                         * pixels. */
    int spaceAbove;           /* How much extra space was added to the
                         * top of the line because of spacing
                         * options.  This is included in height
                         * and baseline. */
    int spaceBelow;           /* How much extra space was added to the
                         * bottom of the line because of spacing
                         * options.  This is included in height. */
    int length;               /* Total length of line, in pixels. */
    TkTextDispChunk *chunkPtr;      /* Pointer to first chunk in list of all
                         * of those that are displayed on this
                         * line of the screen. */
    struct DLine *nextPtr;    /* Next in list of all display lines for
                         * this window.   The list is sorted in
                         * order from top to bottom.  Note:  the
                         * next DLine doesn't always correspond
                         * to the next line of text:  (a) can have
                         * multiple DLines for one text line, and
                         * (b) can have gaps where DLine's have been
                         * deleted because they're out of date. */
    int flags;                /* Various flag bits:  see below for values. */
} DLine;

/*
 * Flag bits for DLine structures:
 *
 * HAS_3D_BORDER -            Non-zero means that at least one of the
 *                      chunks in this line has a 3D border, so
 *                      it potentially interacts with 3D borders
 *                      in neighboring lines (see
 *                      DisplayLineBackground).
 * NEW_LAYOUT -               Non-zero means that the line has been
 *                      re-layed out since the last time the
 *                      display was updated.
 * TOP_LINE -                 Non-zero means that this was the top line
 *                      in the window the last time that the window
 *                      was laid out.  This is important because
 *                      a line may be displayed differently if its
 *                      at the top or bottom than if it's in the
 *                      middle (e.g. beveled edges aren't displayed
 *                      for middle lines if the adjacent line has
 *                      a similar background).
 * BOTTOM_LINE -        Non-zero means that this was the bottom line
 *                      in the window the last time that the window
 *                      was laid out.
 * IS_DISABLED -        This Dline cannot be edited.
 */

#define HAS_3D_BORDER   1
#define NEW_LAYOUT      2
#define TOP_LINE  4
#define BOTTOM_LINE     8
#define IS_DISABLED    16

/*
 * Overall display information for a text widget:
 */

typedef struct TextDInfo {
    Tcl_HashTable styleTable; /* Hash table that maps from StyleValues
                         * to TextStyles for this widget. */
    DLine *dLinePtr;          /* First in list of all display lines for
                         * this widget, in order from top to bottom. */
    GC copyGC;                /* Graphics context for copying from off-
                         * screen pixmaps onto screen. */
    GC scrollGC;        /* Graphics context for copying from one place
                         * in the window to another (scrolling):
                         * differs from copyGC in that we need to get
                         * GraphicsExpose events. */
    int x;              /* First x-coordinate that may be used for
                         * actually displaying line information.
                         * Leaves space for border, etc. */
    int y;              /* First y-coordinate that may be used for
                         * actually displaying line information.
                         * Leaves space for border, etc. */
    int maxX;                 /* First x-coordinate to right of available
                         * space for displaying lines. */
    int maxY;                 /* First y-coordinate below available
                         * space for displaying lines. */
    int topOfEof;       /* Top-most pixel (lowest y-value) that has
                         * been drawn in the appropriate fashion for
                         * the portion of the window after the last
                         * line of the text.  This field is used to
                         * figure out when to redraw part or all of
                         * the eof field. */

    /*
     * Information used for scrolling:
     */

    int newByteOffset;        /* Desired x scroll position, measured as the
                         * number of average-size characters off-screen
                         * to the left for a line with no left
                         * margin. */
    int curPixelOffset;       /* Actual x scroll position, measured as the
                         * number of pixels off-screen to the left. */
    int maxLength;            /* Length in pixels of longest line that's
                         * visible in window (length may exceed window
                         * size).  If there's no wrapping, this will
                         * be zero. */
    double xScrollFirst, xScrollLast;
                        /* Most recent values reported to horizontal
                         * scrollbar;  used to eliminate unnecessary
                         * reports. */
    double yScrollFirst, yScrollLast;
                        /* Most recent values reported to vertical
                         * scrollbar;  used to eliminate unnecessary
                         * reports. */

    /*
     * The following information is used to implement scanning:
     */

    int scanMarkIndex;        /* Byte index of character that was at the
                         * left edge of the window when the scan
                         * started. */
    int scanMarkX;            /* X-position of mouse at time scan started. */
    int scanTotalScroll;      /* Total scrolling (in screen lines) that has
                         * occurred since scanMarkY was set. */
    int scanMarkY;            /* Y-position of mouse at time scan started. */

    /*
     * Miscellaneous information:
     */

    int dLinesInvalidated;    /* This value is set to 1 whenever something
                         * happens that invalidates information in
                         * DLine structures;  if a redisplay
                         * is in progress, it will see this and
                         * abort the redisplay.  This is needed
                         * because, for example, an embedded window
                         * could change its size when it is first
                         * displayed, invalidating the DLine that
                         * is currently being displayed.  If redisplay
                         * continues, it will use freed memory and
                         * could dump core. */
    int flags;                /* Various flag values:  see below for
                         * definitions. */
} TextDInfo;

/*
 * In TkTextDispChunk structures for character segments, the clientData
 * field points to one of the following structures:
 */

typedef struct CharInfo {
    int numBytes;       /* Number of bytes to display. */
    char chars[4];            /* UTF characters to display.  Actual size
                         * will be numBytes, not 4.  THIS MUST BE
                         * THE LAST FIELD IN THE STRUCTURE. */
} CharInfo;

/*
 * Flag values for TextDInfo structures:
 *
 * DINFO_OUT_OF_DATE:         Non-zero means that the DLine structures
 *                      for this window are partially or completely
 *                      out of date and need to be recomputed.
 * REDRAW_PENDING:            Means that a when-idle handler has been
 *                      scheduled to update the display.
 * REDRAW_BORDERS:            Means window border or pad area has
 *                      potentially been damaged and must be redrawn.
 * REPICK_NEEDED:       1 means that the widget has been modified
 *                      in a way that could change the current
 *                      character (a different character might be
 *                      under the mouse cursor now).  Need to
 *                      recompute the current character before
 *                      the next redisplay.
 */

#define DINFO_OUT_OF_DATE     1
#define REDRAW_PENDING        2
#define REDRAW_BORDERS        4
#define REPICK_NEEDED         8

/*
 * The following counters keep statistics about redisplay that can be
 * checked to see how clever this code is at reducing redisplays.
 */

static int numRedisplays;     /* Number of calls to DisplayText. */
static int linesRedrawn;      /* Number of calls to DisplayDLine. */
static int numCopies;         /* Number of calls to XCopyArea to copy part
                         * of the screen. */

/*
 * Forward declarations for procedures defined later in this file:
 */

static void       AdjustForTab _ANSI_ARGS_((TkText *textPtr,
                      TkTextTabArray *tabArrayPtr, int index,
                      TkTextDispChunk *chunkPtr));
static void       CharBboxProc _ANSI_ARGS_((TkTextDispChunk *chunkPtr,
                      int index, int y, int lineHeight, int baseline,
                      int *xPtr, int *yPtr, int *widthPtr,
                      int *heightPtr));
static void       CharDisplayProc _ANSI_ARGS_((TkTextDispChunk *chunkPtr,
                      int x, int y, int height, int baseline,
                      Display *display, Drawable dst, int screenY));
static int        CharMeasureProc _ANSI_ARGS_((TkTextDispChunk *chunkPtr,
                      int x));
static void       CharUndisplayProc _ANSI_ARGS_((TkText *textPtr,
                      TkTextDispChunk *chunkPtr));

/*
   Definitions of elided procs.
   Compiler can't inline these since we use pointers to these functions.
   ElideDisplayProc, ElideUndisplayProc special-cased for speed,
   as potentially many elided DLine chunks if large, tag toggle-filled
   elided region.
*/
static void       ElideBboxProc _ANSI_ARGS_((TkTextDispChunk *chunkPtr,
                      int index, int y, int lineHeight, int baseline,
                      int *xPtr, int *yPtr, int *widthPtr,
                      int *heightPtr));
static int        ElideMeasureProc _ANSI_ARGS_((TkTextDispChunk *chunkPtr,
                      int x));

static void       DisplayDLine _ANSI_ARGS_((TkText *textPtr,
                      DLine *dlPtr, DLine *prevPtr, Pixmap pixmap));
static void       DisplayLineBackground _ANSI_ARGS_((TkText *textPtr,
                      DLine *dlPtr, DLine *prevPtr, Pixmap pixmap));
static void       DisplayText _ANSI_ARGS_((ClientData clientData));
static DLine *          FindDLine _ANSI_ARGS_((DLine *dlPtr,
                      TkTextIndex *indexPtr));
static void       FreeDLines _ANSI_ARGS_((TkText *textPtr,
                      DLine *firstPtr, DLine *lastPtr, int unlink));
static void       FreeStyle _ANSI_ARGS_((TkText *textPtr,
                      TextStyle *stylePtr));
static TextStyle *      GetStyle _ANSI_ARGS_((TkText *textPtr,
                      TkTextIndex *indexPtr));
static void       GetXView _ANSI_ARGS_((Tcl_Interp *interp,
                      TkText *textPtr, int report));
static void       GetYView _ANSI_ARGS_((Tcl_Interp *interp,
                      TkText *textPtr, int report));
static DLine *          LayoutDLine _ANSI_ARGS_((TkText *textPtr,
                      TkTextIndex *indexPtr));
static int        MeasureChars _ANSI_ARGS_((Tk_Font tkfont,
                      CONST char *source, int maxBytes, int startX,
                      int maxX, int tabOrigin, int *nextXPtr));
static void       MeasureUp _ANSI_ARGS_((TkText *textPtr,
                      TkTextIndex *srcPtr, int distance,
                      TkTextIndex *dstPtr));
static int        NextTabStop _ANSI_ARGS_((Tk_Font tkfont, int x,
                      int tabOrigin));
static void       UpdateDisplayInfo _ANSI_ARGS_((TkText *textPtr));
static void       ScrollByLines _ANSI_ARGS_((TkText *textPtr,
                      int offset));
static int        SizeOfTab _ANSI_ARGS_((TkText *textPtr,
                      TkTextTabArray *tabArrayPtr, int index, int x,
                      int maxX));
static void       TextInvalidateRegion _ANSI_ARGS_((TkText *textPtr,
                      TkRegion region));


/*
 *----------------------------------------------------------------------
 *
 * TkTextCreateDInfo --
 *
 *    This procedure is called when a new text widget is created.
 *    Its job is to set up display-related information for the widget.
 *
 * Results:
 *    None.
 *
 * Side effects:
 *    A TextDInfo data structure is allocated and initialized and attached
 *    to textPtr.
 *
 *----------------------------------------------------------------------
 */

void
TkTextCreateDInfo(textPtr)
    TkText *textPtr;          /* Overall information for text widget. */
{
    register TextDInfo *dInfoPtr;
    XGCValues gcValues;

    dInfoPtr = (TextDInfo *) ckalloc(sizeof(TextDInfo));
    Tcl_InitHashTable(&dInfoPtr->styleTable, sizeof(StyleValues)/sizeof(int));
    dInfoPtr->dLinePtr = NULL;
    dInfoPtr->copyGC = None;
    gcValues.graphics_exposures = True;
    dInfoPtr->scrollGC = Tk_GetGC(textPtr->tkwin, GCGraphicsExposures,
          &gcValues);
    dInfoPtr->topOfEof = 0;
    dInfoPtr->newByteOffset = 0;
    dInfoPtr->curPixelOffset = 0;
    dInfoPtr->maxLength = 0;
    dInfoPtr->xScrollFirst = -1;
    dInfoPtr->xScrollLast = -1;
    dInfoPtr->yScrollFirst = -1;
    dInfoPtr->yScrollLast = -1;
    dInfoPtr->scanMarkIndex = 0;
    dInfoPtr->scanMarkX = 0;
    dInfoPtr->scanTotalScroll = 0;
    dInfoPtr->scanMarkY = 0;
    dInfoPtr->dLinesInvalidated = 0;
    dInfoPtr->flags = DINFO_OUT_OF_DATE;
    textPtr->dInfoPtr = dInfoPtr;
}

/*
 *----------------------------------------------------------------------
 *
 * TkTextFreeDInfo --
 *
 *    This procedure is called to free up all of the private display
 *    information kept by this file for a text widget.
 *
 * Results:
 *    None.
 *
 * Side effects:
 *    Lots of resources get freed.
 *
 *----------------------------------------------------------------------
 */

void
TkTextFreeDInfo(textPtr)
    TkText *textPtr;          /* Overall information for text widget. */
{
    register TextDInfo *dInfoPtr = textPtr->dInfoPtr;

    /*
     * Be careful to free up styleTable *after* freeing up all the
     * DLines, so that the hash table is still intact to free up the
     * style-related information from the lines.  Once the lines are
     * all free then styleTable will be empty.
     */

    FreeDLines(textPtr, dInfoPtr->dLinePtr, (DLine *) NULL, 1);
    Tcl_DeleteHashTable(&dInfoPtr->styleTable);
    if (dInfoPtr->copyGC != None) {
      Tk_FreeGC(textPtr->display, dInfoPtr->copyGC);
    }
    Tk_FreeGC(textPtr->display, dInfoPtr->scrollGC);
    if (dInfoPtr->flags & REDRAW_PENDING) {
      Tcl_CancelIdleCall(DisplayText, (ClientData) textPtr);
    }
    ckfree((char *) dInfoPtr);
}

/*
 *----------------------------------------------------------------------
 *
 * GetStyle --
 *
 *    This procedure creates all the information needed to display
 *    text at a particular location.
 *
 * Results:
 *    The return value is a pointer to a TextStyle structure that
 *    corresponds to *sValuePtr.
 *
 * Side effects:
 *    A new entry may be created in the style table for the widget.
 *
 *----------------------------------------------------------------------
 */

static TextStyle *
GetStyle(textPtr, indexPtr)
    TkText *textPtr;          /* Overall information about text widget. */
    TkTextIndex *indexPtr;    /* The character in the text for which
                         * display information is wanted. */
{
    TkTextTag **tagPtrs;
    register TkTextTag *tagPtr;
    StyleValues styleValues;
    TextStyle *stylePtr;
    Tcl_HashEntry *hPtr;
    int numTags, new, i;
    XGCValues gcValues;
    unsigned long mask;

    /*
     * The variables below keep track of the highest-priority specification
     * that has occurred for each of the various fields of the StyleValues.
     */

    int borderPrio, borderWidthPrio, reliefPrio, bgStipplePrio;
    int fgPrio, fontPrio, fgStipplePrio;
    int underlinePrio, elidePrio, justifyPrio, offsetPrio;
    int lMargin1Prio, lMargin2Prio, rMarginPrio;
    int spacing1Prio, spacing2Prio, spacing3Prio;
    int overstrikePrio, tabPrio, wrapPrio;

    /*
     * Find out what tags are present for the character, then compute
     * a StyleValues structure corresponding to those tags (scan
     * through all of the tags, saving information for the highest-
     * priority tag).
     */

    tagPtrs = TkBTreeGetTags(indexPtr, &numTags);
    borderPrio = borderWidthPrio = reliefPrio = bgStipplePrio = -1;
    fgPrio = fontPrio = fgStipplePrio = -1;
    underlinePrio = elidePrio = justifyPrio = offsetPrio = -1;
    lMargin1Prio = lMargin2Prio = rMarginPrio = -1;
    spacing1Prio = spacing2Prio = spacing3Prio = -1;
    overstrikePrio = tabPrio = wrapPrio = -1;
    memset((VOID *) &styleValues, 0, sizeof(StyleValues));
    styleValues.relief = TK_RELIEF_FLAT;
    styleValues.fgColor = textPtr->fgColor;
    styleValues.tkfont = textPtr->tkfont;
    styleValues.justify = TK_JUSTIFY_LEFT;
    styleValues.spacing1 = textPtr->spacing1;
    styleValues.spacing2 = textPtr->spacing2;
    styleValues.spacing3 = textPtr->spacing3;
    styleValues.tabArrayPtr = textPtr->tabArrayPtr;
    styleValues.wrapMode = textPtr->wrapMode;
    styleValues.elide = 0;
    for (i = 0 ; i < numTags; i++) {
      tagPtr = tagPtrs[i];

      /*
       * On Windows and Mac, we need to skip the selection tag if
       * we don't have focus.
       */

#ifndef ALWAYS_SHOW_SELECTION
      if ((tagPtr == textPtr->selTagPtr) && !(textPtr->flags & GOT_FOCUS)) {
          continue;
      }
#endif

      if ((tagPtr->border != NULL) && (tagPtr->priority > borderPrio)) {
          styleValues.border = tagPtr->border;
          borderPrio = tagPtr->priority;
      }
      if ((tagPtr->bdString != NULL)
            && (tagPtr->priority > borderWidthPrio)) {
          styleValues.borderWidth = tagPtr->borderWidth;
          borderWidthPrio = tagPtr->priority;
      }
      if ((tagPtr->reliefString != NULL)
            && (tagPtr->priority > reliefPrio)) {
          if (styleValues.border == NULL) {
            styleValues.border = textPtr->border;
          }
          styleValues.relief = tagPtr->relief;
          reliefPrio = tagPtr->priority;
      }
      if ((tagPtr->bgStipple != None)
            && (tagPtr->priority > bgStipplePrio)) {
          styleValues.bgStipple = tagPtr->bgStipple;
          bgStipplePrio = tagPtr->priority;
      }
      if ((tagPtr->fgColor != None) && (tagPtr->priority > fgPrio)) {
          styleValues.fgColor = tagPtr->fgColor;
          fgPrio = tagPtr->priority;
      }
      if ((tagPtr->tkfont != None) && (tagPtr->priority > fontPrio)) {
          styleValues.tkfont = tagPtr->tkfont;
          fontPrio = tagPtr->priority;
      }
      if ((tagPtr->fgStipple != None)
            && (tagPtr->priority > fgStipplePrio)) {
          styleValues.fgStipple = tagPtr->fgStipple;
          fgStipplePrio = tagPtr->priority;
      }
      if ((tagPtr->justifyString != NULL)
            && (tagPtr->priority > justifyPrio)) {
          styleValues.justify = tagPtr->justify;
          justifyPrio = tagPtr->priority;
      }
      if ((tagPtr->lMargin1String != NULL)
            && (tagPtr->priority > lMargin1Prio)) {
          styleValues.lMargin1 = tagPtr->lMargin1;
          lMargin1Prio = tagPtr->priority;
      }
      if ((tagPtr->lMargin2String != NULL)
            && (tagPtr->priority > lMargin2Prio)) {
          styleValues.lMargin2 = tagPtr->lMargin2;
          lMargin2Prio = tagPtr->priority;
      }
      if ((tagPtr->offsetString != NULL)
            && (tagPtr->priority > offsetPrio)) {
          styleValues.offset = tagPtr->offset;
          offsetPrio = tagPtr->priority;
      }
      if ((tagPtr->overstrikeString != NULL)
            && (tagPtr->priority > overstrikePrio)) {
          styleValues.overstrike = tagPtr->overstrike;
          overstrikePrio = tagPtr->priority;
      }
      if ((tagPtr->rMarginString != NULL)
            && (tagPtr->priority > rMarginPrio)) {
          styleValues.rMargin = tagPtr->rMargin;
          rMarginPrio = tagPtr->priority;
      }
      if ((tagPtr->spacing1String != NULL)
            && (tagPtr->priority > spacing1Prio)) {
          styleValues.spacing1 = tagPtr->spacing1;
          spacing1Prio = tagPtr->priority;
      }
      if ((tagPtr->spacing2String != NULL)
            && (tagPtr->priority > spacing2Prio)) {
          styleValues.spacing2 = tagPtr->spacing2;
          spacing2Prio = tagPtr->priority;
      }
      if ((tagPtr->spacing3String != NULL)
            && (tagPtr->priority > spacing3Prio)) {
          styleValues.spacing3 = tagPtr->spacing3;
          spacing3Prio = tagPtr->priority;
      }
      if ((tagPtr->tabString != NULL)
            && (tagPtr->priority > tabPrio)) {
          styleValues.tabArrayPtr = tagPtr->tabArrayPtr;
          tabPrio = tagPtr->priority;
      }
      if ((tagPtr->underlineString != NULL)
            && (tagPtr->priority > underlinePrio)) {
          styleValues.underline = tagPtr->underline;
          underlinePrio = tagPtr->priority;
      }
      if ((tagPtr->elideString != NULL)
            && (tagPtr->priority > elidePrio)) {
          styleValues.elide = tagPtr->elide;
          elidePrio = tagPtr->priority;
      }
      if ((tagPtr->wrapMode != TEXT_WRAPMODE_NULL)
            && (tagPtr->priority > wrapPrio)) {
          styleValues.wrapMode = tagPtr->wrapMode;
          wrapPrio = tagPtr->priority;
      }
    }
    if (tagPtrs != NULL) {
      ckfree((char *) tagPtrs);
    }

    /*
     * Use an existing style if there's one around that matches.
     */

    hPtr = Tcl_CreateHashEntry(&textPtr->dInfoPtr->styleTable,
          (char *) &styleValues, &new);
    if (!new) {
      stylePtr = (TextStyle *) Tcl_GetHashValue(hPtr);
      stylePtr->refCount++;
      return stylePtr;
    }

    /*
     * No existing style matched.  Make a new one.
     */

    stylePtr = (TextStyle *) ckalloc(sizeof(TextStyle));
    stylePtr->refCount = 1;
    if (styleValues.border != NULL) {
      gcValues.foreground = Tk_3DBorderColor(styleValues.border)->pixel;
      mask = GCForeground;
      if (styleValues.bgStipple != None) {
          gcValues.stipple = styleValues.bgStipple;
          gcValues.fill_style = FillStippled;
          mask |= GCStipple|GCFillStyle;
      }
      stylePtr->bgGC = Tk_GetGC(textPtr->tkwin, mask, &gcValues);
    } else {
      stylePtr->bgGC = None;
    }
    mask = GCFont;
    gcValues.font = Tk_FontId(styleValues.tkfont);
    mask |= GCForeground;
    gcValues.foreground = styleValues.fgColor->pixel;
    if (styleValues.fgStipple != None) {
      gcValues.stipple = styleValues.fgStipple;
      gcValues.fill_style = FillStippled;
      mask |= GCStipple|GCFillStyle;
    }
    stylePtr->fgGC = Tk_GetGC(textPtr->tkwin, mask, &gcValues);
    stylePtr->sValuePtr = (StyleValues *)
          Tcl_GetHashKey(&textPtr->dInfoPtr->styleTable, hPtr);
    stylePtr->hPtr = hPtr;
    Tcl_SetHashValue(hPtr, stylePtr);
    return stylePtr;
}

/*
 *----------------------------------------------------------------------
 *
 * FreeStyle --
 *
 *    This procedure is called when a TextStyle structure is no longer
 *    needed.  It decrements the reference count and frees up the
 *    space for the style structure if the reference count is 0.
 *
 * Results:
 *    None.
 *
 * Side effects:
 *    The storage and other resources associated with the style
 *    are freed up if no-one's still using it.
 *
 *----------------------------------------------------------------------
 */

static void
FreeStyle(textPtr, stylePtr)
    TkText *textPtr;                /* Information about overall widget. */
    register TextStyle *stylePtr;   /* Information about style to free. */

{
    stylePtr->refCount--;
    if (stylePtr->refCount == 0) {
      if (stylePtr->bgGC != None) {
          Tk_FreeGC(textPtr->display, stylePtr->bgGC);
      }
      if (stylePtr->fgGC != None) {
          Tk_FreeGC(textPtr->display, stylePtr->fgGC);
      }
      Tcl_DeleteHashEntry(stylePtr->hPtr);
      ckfree((char *) stylePtr);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * LayoutDLine --
 *
 *    This procedure generates a single DLine structure for a display
 *    line whose leftmost character is given by indexPtr.
 *    
 * Results:
 *    The return value is a pointer to a DLine structure desribing the
 *    display line.  All fields are filled in and correct except for
 *    y and nextPtr.
 *
 * Side effects:
 *    Storage is allocated for the new DLine.
 *
 *----------------------------------------------------------------------
 */

static DLine *
LayoutDLine(textPtr, indexPtr)
    TkText *textPtr;          /* Overall information about text widget. */
    TkTextIndex *indexPtr;    /* Beginning of display line.  May not
                         * necessarily point to a character segment. */
{
    register DLine *dlPtr;          /* New display line. */
    TkTextSegment *segPtr;          /* Current segment in text. */
    TkTextDispChunk *lastChunkPtr;  /* Last chunk allocated so far
                               * for line. */
    TkTextDispChunk *chunkPtr;            /* Current chunk. */
    TkTextIndex curIndex;
    TkTextDispChunk *breakChunkPtr; /* Chunk containing best word break
                               * point, if any. */
    TkTextIndex breakIndex;         /* Index of first character in
                               * breakChunkPtr. */
    int breakByteOffset;            /* Byte offset of character within
                               * breakChunkPtr just to right of best
                               * break point. */
    int noCharsYet;                 /* Non-zero means that no characters
                               * have been placed on the line yet. */
    int justify;              /* How to justify line: taken from
                               * style for the first character in
                               * line. */
    int jIndent;              /* Additional indentation (beyond
                               * margins) due to justification. */
    int rMargin;              /* Right margin width for line. */
    TkWrapMode wrapMode;            /* Wrap mode to use for this line. */
    int x = 0, maxX = 0;            /* Initializations needed only to
                               * stop compiler warnings. */
    int wholeLine;                  /* Non-zero means this display line
                               * runs to the end of the text line. */
    int tabIndex;             /* Index of the current tab stop. */
    int gotTab;                     /* Non-zero means the current chunk
                               * contains a tab. */
    TkTextDispChunk *tabChunkPtr;   /* Pointer to the chunk containing
                               * the previous tab stop. */
    int maxBytes;             /* Maximum number of bytes to
                               * include in this chunk. */
    TkTextTabArray *tabArrayPtr;    /* Tab stops for line; taken from
                               * style for the first character on
                               * line. */
    int tabSize;              /* Number of pixels consumed by current
                               * tab stop. */
    TkTextDispChunk *lastCharChunkPtr;    /* Pointer to last chunk in display
                               * lines with numBytes > 0.  Used to
                               * drop 0-sized chunks from the end
                               * of the line. */
    int byteOffset, ascent, descent, code, elide, elidesize;
    StyleValues *sValuePtr;

    /*
     * Create and initialize a new DLine structure.
     */

    dlPtr = (DLine *) ckalloc(sizeof(DLine));
    dlPtr->index = *indexPtr;
    dlPtr->byteCount = 0;
    dlPtr->y = 0;
    dlPtr->oldY = -1;
    dlPtr->height = 0;
    dlPtr->baseline = 0;
    dlPtr->chunkPtr = NULL;
    dlPtr->nextPtr = NULL;
    dlPtr->flags = NEW_LAYOUT;

    /*
     * Special case entirely elide line as there may be 1000s or more
     */
    elide = TkTextIsElided(textPtr, indexPtr);        /* save a malloc */
    if (elide && indexPtr->byteIndex==0) {
      maxBytes = 0;
      for (segPtr = indexPtr->linePtr->segPtr;
           elide && (segPtr != NULL);
           segPtr = segPtr->nextPtr) {
          if ((elidesize = segPtr->size) > 0) {
            maxBytes += elidesize;
            /*
             * If have we have a tag toggle, there is a chance
             * that invisibility state changed, so bail out
             */
          } else if ((segPtr->typePtr == &tkTextToggleOffType)
                || (segPtr->typePtr == &tkTextToggleOnType)) {
            if (segPtr->body.toggle.tagPtr->elideString != NULL) {
                elide = (segPtr->typePtr == &tkTextToggleOffType)
                  ^ segPtr->body.toggle.tagPtr->elide;
            }
          }
      }

      if (elide) {
          dlPtr->byteCount = maxBytes;
          dlPtr->spaceAbove = dlPtr->spaceBelow = dlPtr->length = 0;
          return dlPtr;
      }
    }

    /*
     * Each iteration of the loop below creates one TkTextDispChunk for
     * the new display line.  The line will always have at least one
     * chunk (for the newline character at the end, if there's nothing
     * else available).
     */

    curIndex = *indexPtr;
    lastChunkPtr = NULL;
    chunkPtr = NULL;
    noCharsYet = 1;
    elide = 0;
    breakChunkPtr = NULL;
    breakByteOffset = 0;
    justify = TK_JUSTIFY_LEFT;
    tabIndex = -1;
    tabChunkPtr = NULL;
    tabArrayPtr = NULL;
    rMargin = 0;
    wrapMode = TEXT_WRAPMODE_CHAR;
    tabSize = 0;
    lastCharChunkPtr = NULL;

    /*
     * Find the first segment to consider for the line.  Can't call
     * TkTextIndexToSeg for this because it won't return a segment
     * with zero size (such as the insertion cursor's mark).
     */

    for (byteOffset = curIndex.byteIndex, segPtr = curIndex.linePtr->segPtr;
       (byteOffset > 0) && (byteOffset >= segPtr->size);
       byteOffset -= segPtr->size, segPtr = segPtr->nextPtr) {
      /* Empty loop body. */
    }

    while (segPtr != NULL) {
      /*
       * Every line still gets at least one chunk due to expectations
       * in the rest of the code, but we are able to skip elided portions
       * of the line quickly.
       * If current chunk is elided and last chunk was too, coalese
       */
      if (elide && (lastChunkPtr != NULL)
            && (lastChunkPtr->displayProc == NULL /*ElideDisplayProc*/)) {
          if ((elidesize = segPtr->size - byteOffset) > 0) {
            curIndex.byteIndex += elidesize;
            lastChunkPtr->numBytes += elidesize;
            breakByteOffset = lastChunkPtr->breakIndex = lastChunkPtr->numBytes;
            /*
             * If have we have a tag toggle, there is a chance
             * that invisibility state changed, so bail out
             */
          } else if ((segPtr->typePtr == &tkTextToggleOffType)
                || (segPtr->typePtr == &tkTextToggleOnType)) {
            if (segPtr->body.toggle.tagPtr->elideString != NULL) {
                elide = (segPtr->typePtr == &tkTextToggleOffType)
                  ^ segPtr->body.toggle.tagPtr->elide;
            }
          }

          byteOffset = 0;
          segPtr = segPtr->nextPtr;
          if (segPtr == NULL && chunkPtr != NULL) {
            ckfree((char *) chunkPtr);
          }
          continue;
      }

      if (segPtr->typePtr->layoutProc == NULL) {
          segPtr = segPtr->nextPtr;
          byteOffset = 0;
          continue;
      }
      if (chunkPtr == NULL) {
          chunkPtr = (TkTextDispChunk *) ckalloc(sizeof(TkTextDispChunk));
          chunkPtr->nextPtr = NULL;
      }
      chunkPtr->stylePtr = GetStyle(textPtr, &curIndex);
      elide = chunkPtr->stylePtr->sValuePtr->elide;

      /*
       * Save style information such as justification and indentation,
       * up until the first character is encountered, then retain that
       * information for the rest of the line.
       */

      if (noCharsYet) {
          tabArrayPtr = chunkPtr->stylePtr->sValuePtr->tabArrayPtr;
          justify = chunkPtr->stylePtr->sValuePtr->justify;
          rMargin = chunkPtr->stylePtr->sValuePtr->rMargin;
          wrapMode = chunkPtr->stylePtr->sValuePtr->wrapMode;
          x = ((curIndex.byteIndex == 0)
                ? chunkPtr->stylePtr->sValuePtr->lMargin1
                : chunkPtr->stylePtr->sValuePtr->lMargin2);
          if (wrapMode == TEXT_WRAPMODE_NONE) {
            maxX = -1;
          } else {
            maxX = textPtr->dInfoPtr->maxX - textPtr->dInfoPtr->x
                  - rMargin;
            if (maxX < x) {
                maxX = x;
            }
          }
      }

      /*
       * See if there is a tab in the current chunk; if so, only
       * layout characters up to (and including) the tab.
       */

      gotTab = 0;
      maxBytes = segPtr->size - byteOffset;
      if (!elide && justify == TK_JUSTIFY_LEFT) {
          if (segPtr->typePtr == &tkTextCharType) {
            char *p;

            for (p = segPtr->body.chars  + byteOffset; *p != 0; p++) {
                if (*p == '\t') {
                  maxBytes = (p + 1 - segPtr->body.chars) - byteOffset;
                  gotTab = 1;
                  break;
                }
            }
          }
      }
      chunkPtr->x = x;
      if (elide && maxBytes) {
          /* don't free style here, as other code expects to be able to do that */
          /*breakByteOffset =*/ chunkPtr->breakIndex = chunkPtr->numBytes = maxBytes;
          chunkPtr->width = 0;
          chunkPtr->minAscent = chunkPtr->minDescent = chunkPtr->minHeight = 0;

          /* would just like to point to canonical empty chunk */
          chunkPtr->displayProc = (Tk_ChunkDisplayProc *) NULL;
          chunkPtr->undisplayProc = (Tk_ChunkUndisplayProc *) NULL;
          chunkPtr->measureProc = ElideMeasureProc;
          chunkPtr->bboxProc = ElideBboxProc;

          code = 1;
      } else
      code = (*segPtr->typePtr->layoutProc)(textPtr, &curIndex, segPtr,
            byteOffset, maxX-tabSize, maxBytes, noCharsYet, wrapMode,
            chunkPtr);
      if (code <= 0) {
          FreeStyle(textPtr, chunkPtr->stylePtr);
          if (code < 0) {
            /*
             * This segment doesn't wish to display itself (e.g. most
             * marks).
             */

            segPtr = segPtr->nextPtr;
            byteOffset = 0;
            continue;
          }

          /*
           * No characters from this segment fit in the window: this
           * means we're at the end of the display line.
           */

          if (chunkPtr != NULL) {
            ckfree((char *) chunkPtr);
          }
          break;
      }
      if (chunkPtr->numBytes > 0) {
          noCharsYet = 0;
          lastCharChunkPtr = chunkPtr;
      }
      if (lastChunkPtr == NULL) {
          dlPtr->chunkPtr = chunkPtr;
      } else {
          lastChunkPtr->nextPtr = chunkPtr;
      }
      lastChunkPtr = chunkPtr;
      x += chunkPtr->width;
      if (chunkPtr->breakIndex > 0) {
          breakByteOffset = chunkPtr->breakIndex;
          breakIndex = curIndex;
          breakChunkPtr = chunkPtr;
      }
      if (chunkPtr->numBytes != maxBytes) {
          break;
      }

      /*
       * If we're at a new tab, adjust the layout for all the chunks
       * pertaining to the previous tab.  Also adjust the amount of
       * space left in the line to account for space that will be eaten
       * up by the tab.
       */

      if (gotTab) {
          if (tabIndex >= 0) {
            AdjustForTab(textPtr, tabArrayPtr, tabIndex, tabChunkPtr);
            x = chunkPtr->x + chunkPtr->width;
          }
          tabIndex++;
          tabChunkPtr = chunkPtr;
          tabSize = SizeOfTab(textPtr, tabArrayPtr, tabIndex, x, maxX);
          if ((maxX >= 0) && (tabSize >= maxX - x)) {
            break;
          }
      }
      curIndex.byteIndex += chunkPtr->numBytes;
      byteOffset += chunkPtr->numBytes;
      if (byteOffset >= segPtr->size) {
          byteOffset = 0;
          segPtr = segPtr->nextPtr;
      }

      chunkPtr = NULL;
    }
    if (noCharsYet) {
      panic("LayoutDLine couldn't place any characters on a line");
    }
    wholeLine = (segPtr == NULL);

    /*
     * We're at the end of the display line.  Throw away everything
     * after the most recent word break, if there is one;  this may
     * potentially require the last chunk to be layed out again.
     */

    if (breakChunkPtr == NULL) {
      /*
       * This code makes sure that we don't accidentally display
       * chunks with no characters at the end of the line (such as
       * the insertion cursor).  These chunks belong on the next
       * line.  So, throw away everything after the last chunk that
       * has characters in it.
       */

      breakChunkPtr = lastCharChunkPtr;
      breakByteOffset = breakChunkPtr->numBytes;
    }
    if ((breakChunkPtr != NULL) && ((lastChunkPtr != breakChunkPtr)
          || (breakByteOffset != lastChunkPtr->numBytes))) {
      while (1) {
          chunkPtr = breakChunkPtr->nextPtr;
          if (chunkPtr == NULL) {
            break;
          }
          FreeStyle(textPtr, chunkPtr->stylePtr);
          breakChunkPtr->nextPtr = chunkPtr->nextPtr;
          (*chunkPtr->undisplayProc)(textPtr, chunkPtr);
          ckfree((char *) chunkPtr);
      }
      if (breakByteOffset != breakChunkPtr->numBytes) {
          (*breakChunkPtr->undisplayProc)(textPtr, breakChunkPtr);
          segPtr = TkTextIndexToSeg(&breakIndex, &byteOffset);
          (*segPtr->typePtr->layoutProc)(textPtr, &breakIndex,
                segPtr, byteOffset, maxX, breakByteOffset, 0, 
                wrapMode, breakChunkPtr);
      }
      lastChunkPtr = breakChunkPtr;
      wholeLine = 0;
    }


    /*
     * Make tab adjustments for the last tab stop, if there is one.
     */

    if ((tabIndex >= 0) && (tabChunkPtr != NULL)) {
      AdjustForTab(textPtr, tabArrayPtr, tabIndex, tabChunkPtr);
    }

    /*
     * Make one more pass over the line to recompute various things
     * like its height, length, and total number of bytes.  Also
     * modify the x-locations of chunks to reflect justification.
     * If we're not wrapping, I'm not sure what is the best way to
     * handle left and center justification:  should the total length,
     * for purposes of justification, be (a) the window width, (b)
     * the length of the longest line in the window, or (c) the length
     * of the longest line in the text?  (c) isn't available, (b) seems
     * weird, since it can change with vertical scrolling, so (a) is
     * what is implemented below.
     */

    if (wrapMode == TEXT_WRAPMODE_NONE) {
      maxX = textPtr->dInfoPtr->maxX - textPtr->dInfoPtr->x - rMargin;
    }
    dlPtr->length = lastChunkPtr->x + lastChunkPtr->width;
    if (justify == TK_JUSTIFY_LEFT) {
      jIndent = 0;
    } else if (justify == TK_JUSTIFY_RIGHT) {
      jIndent = maxX - dlPtr->length;
    } else {
      jIndent = (maxX - dlPtr->length)/2;
    }
    ascent = descent = 0;
    for (chunkPtr = dlPtr->chunkPtr; chunkPtr != NULL;
          chunkPtr = chunkPtr->nextPtr) {
      chunkPtr->x += jIndent;
      dlPtr->byteCount += chunkPtr->numBytes;
      if (chunkPtr->minAscent > ascent) {
          ascent = chunkPtr->minAscent;
      }
      if (chunkPtr->minDescent > descent) {
          descent = chunkPtr->minDescent;
      }
      if (chunkPtr->minHeight > dlPtr->height) {
          dlPtr->height = chunkPtr->minHeight;
      }
      sValuePtr = chunkPtr->stylePtr->sValuePtr;
      if ((sValuePtr->borderWidth > 0)
            && (sValuePtr->relief != TK_RELIEF_FLAT)) {
          dlPtr->flags |= HAS_3D_BORDER;
      }
    }
    if (dlPtr->height < (ascent + descent)) {
      dlPtr->height = ascent + descent;
      dlPtr->baseline = ascent;
    } else {
      dlPtr->baseline = ascent + (dlPtr->height - ascent - descent)/2;
    }
    sValuePtr = dlPtr->chunkPtr->stylePtr->sValuePtr;
    if (dlPtr->index.byteIndex == 0) {
      dlPtr->spaceAbove = sValuePtr->spacing1;
    } else {
      dlPtr->spaceAbove = sValuePtr->spacing2 - sValuePtr->spacing2/2;
    }
    if (wholeLine) {
      dlPtr->spaceBelow = sValuePtr->spacing3;
    } else {
      dlPtr->spaceBelow = sValuePtr->spacing2/2;
    }
    dlPtr->height += dlPtr->spaceAbove + dlPtr->spaceBelow;
    dlPtr->baseline += dlPtr->spaceAbove;

    /*
     * Recompute line length:  may have changed because of justification.
     */

    dlPtr->length = lastChunkPtr->x + lastChunkPtr->width;
    return dlPtr;
}

/*
 *----------------------------------------------------------------------
 *
 * UpdateDisplayInfo --
 *
 *    This procedure is invoked to recompute some or all of the
 *    DLine structures for a text widget.  At the time it is called
 *    the DLine structures still left in the widget are guaranteed
 *    to be correct except that (a) the y-coordinates aren't
 *    necessarily correct, (b) there may be missing structures
 *    (the DLine structures get removed as soon as they are potentially
 *    out-of-date), and (c) DLine structures that don't start at the
 *    beginning of a line may be incorrect if previous information in
 *    the same line changed size in a way that moved a line boundary
 *    (DLines for any info that changed will have been deleted, but
 *    not DLines for unchanged info in the same text line).
 *
 * Results:
 *    None.
 *
 * Side effects:
 *    Upon return, the DLine information for textPtr correctly reflects
 *    the positions where characters will be displayed.  However, this
 *    procedure doesn't actually bring the display up-to-date.
 *
 *----------------------------------------------------------------------
 */

static void
UpdateDisplayInfo(textPtr)
    TkText *textPtr;                /* Text widget to update. */
{
    register TextDInfo *dInfoPtr = textPtr->dInfoPtr;
    register DLine *dlPtr, *prevPtr;
    TkTextIndex index;
    TkTextLine *lastLinePtr;
    int y, maxY, pixelOffset, maxOffset;

    if (!(dInfoPtr->flags & DINFO_OUT_OF_DATE)) {
      return;
    }
    dInfoPtr->flags &= ~DINFO_OUT_OF_DATE;

    /*
     * Delete any DLines that are now above the top of the window.
     */

    index = textPtr->topIndex;
    dlPtr = FindDLine(dInfoPtr->dLinePtr, &index);
    if ((dlPtr != NULL) && (dlPtr != dInfoPtr->dLinePtr)) {
      FreeDLines(textPtr, dInfoPtr->dLinePtr, dlPtr, 1);
    }

    /*
     *--------------------------------------------------------------
     * Scan through the contents of the window from top to bottom,
     * recomputing information for lines that are missing.
     *--------------------------------------------------------------
     */

    lastLinePtr = TkBTreeFindLine(textPtr->tree,
          TkBTreeNumLines(textPtr->tree));
    dlPtr = dInfoPtr->dLinePtr;
    prevPtr = NULL;
    y = dInfoPtr->y;
    maxY = dInfoPtr->maxY;
    while (1) {
      register DLine *newPtr;

      if (index.linePtr == lastLinePtr) {
          break;
      }

      /*
       * There are three possibilities right now:
       * (a) the next DLine (dlPtr) corresponds exactly to the next
       *     information we want to display: just use it as-is.
       * (b) the next DLine corresponds to a different line, or to
       *     a segment that will be coming later in the same line:
       *     leave this DLine alone in the hopes that we'll be able
       *     to use it later, then create a new DLine in front of
       *     it.
       * (c) the next DLine corresponds to a segment in the line we
       *     want, but it's a segment that has already been processed
       *     or will never be processed.  Delete the DLine and try
       *     again.
       *
       * One other twist on all this.  It's possible for 3D borders
       * to interact between lines (see DisplayLineBackground) so if
       * a line is relayed out and has styles with 3D borders, its
       * neighbors have to be redrawn if they have 3D borders too,
       * since the interactions could have changed (the neighbors
       * don't have to be relayed out, just redrawn).
       */

      if ((dlPtr == NULL) || (dlPtr->index.linePtr != index.linePtr)) {
          /*
           * Case (b) -- must make new DLine.
           */

          makeNewDLine:
          if (tkTextDebug) {
            char string[TK_POS_CHARS];

            /*
             * Debugging is enabled, so keep a log of all the lines
             * that were re-layed out.  The test suite uses this
             * information.
             */

            TkTextPrintIndex(&index, string);
            Tcl_SetVar2(textPtr->interp, "tk_textRelayout", (char *) NULL,
                  string,
                  TCL_GLOBAL_ONLY|TCL_APPEND_VALUE|TCL_LIST_ELEMENT);
          }
          newPtr = LayoutDLine(textPtr, &index);
          if (prevPtr == NULL) {
            dInfoPtr->dLinePtr = newPtr;
          } else {
            prevPtr->nextPtr = newPtr;
            if (prevPtr->flags & HAS_3D_BORDER) {
                prevPtr->oldY = -1;
            }
          }
          newPtr->nextPtr = dlPtr;
          dlPtr = newPtr;
      } else {
          /*
           * DlPtr refers to the line we want.  Next check the
           * index within the line.
           */

          if (index.byteIndex == dlPtr->index.byteIndex) {
            /*
             * Case (a) -- can use existing display line as-is.
             */

            if ((dlPtr->flags & HAS_3D_BORDER) && (prevPtr != NULL)
                  && (prevPtr->flags & (NEW_LAYOUT))) {
                dlPtr->oldY = -1;
            }
            goto lineOK;
          }
          if (index.byteIndex < dlPtr->index.byteIndex) {
            goto makeNewDLine;
          }

          /*
           * Case (c) -- dlPtr is useless.  Discard it and start
           * again with the next display line.
           */

          newPtr = dlPtr->nextPtr;
          FreeDLines(textPtr, dlPtr, newPtr, 0);
          dlPtr = newPtr;
          if (prevPtr != NULL) {
            prevPtr->nextPtr = newPtr;
          } else {
            dInfoPtr->dLinePtr = newPtr;
          }
          continue;
      }

      /*
       * Advance to the start of the next line.
       */

      lineOK:
      dlPtr->y = y;
      y += dlPtr->height;
      TkTextIndexForwBytes(&index, dlPtr->byteCount, &index);
      prevPtr = dlPtr;
      dlPtr = dlPtr->nextPtr;

      /*
       * If we switched text lines, delete any DLines left for the
       * old text line.
       */

      if (index.linePtr != prevPtr->index.linePtr) {
          register DLine *nextPtr;

          nextPtr = dlPtr;
          while ((nextPtr != NULL)
                && (nextPtr->index.linePtr == prevPtr->index.linePtr)) {
            nextPtr = nextPtr->nextPtr;
          }
          if (nextPtr != dlPtr) {
            FreeDLines(textPtr, dlPtr, nextPtr, 0);
            prevPtr->nextPtr = nextPtr;
            dlPtr = nextPtr;
          }
      }

      /*
       * It's important to have the following check here rather than in
       * the while statement for the loop, so that there's always at least
       * one DLine generated, regardless of how small the window is.  This
       * keeps a lot of other code from breaking.
       */

      if (y >= maxY) {
          break;
      }
    }

    /*
     * Delete any DLine structures that don't fit on the screen.
     */

    FreeDLines(textPtr, dlPtr, (DLine *) NULL, 1);

    /*
     *--------------------------------------------------------------
     * If there is extra space at the bottom of the window (because
     * we've hit the end of the text), then bring in more lines at
     * the top of the window, if there are any, to fill in the view.
     *--------------------------------------------------------------
     */

    if (y < maxY) {
      int lineNum, spaceLeft, bytesToCount;
      DLine *lowestPtr;

      /*
       * Layout an entire text line (potentially > 1 display line),
       * then link in as many display lines as fit without moving
       * the bottom line out of the window.  Repeat this until
       * all the extra space has been used up or we've reached the
       * beginning of the text.
       */

      spaceLeft = maxY - y;
      lineNum = TkBTreeLineIndex(dInfoPtr->dLinePtr->index.linePtr);
      bytesToCount = dInfoPtr->dLinePtr->index.byteIndex;
      if (bytesToCount == 0) {
          bytesToCount = INT_MAX;
          lineNum--;
      }
      for ( ; (lineNum >= 0) && (spaceLeft > 0); lineNum--) {
          index.linePtr = TkBTreeFindLine(textPtr->tree, lineNum);
          index.byteIndex = 0;
          lowestPtr = NULL;

          do {
            dlPtr = LayoutDLine(textPtr, &index);
            dlPtr->nextPtr = lowestPtr;
            lowestPtr = dlPtr;
            if (dlPtr->length == 0 && dlPtr->height == 0) { bytesToCount--; break; }      /* elide */
            TkTextIndexForwBytes(&index, dlPtr->byteCount, &index);
            bytesToCount -= dlPtr->byteCount;
          } while ((bytesToCount > 0)
                && (index.linePtr == lowestPtr->index.linePtr));

          /*
           * Scan through the display lines from the bottom one up to
           * the top one.
           */

          while (lowestPtr != NULL) {
            dlPtr = lowestPtr;
            spaceLeft -= dlPtr->height;
            if (spaceLeft < 0) {
                break;
            }
            lowestPtr = dlPtr->nextPtr;
            dlPtr->nextPtr = dInfoPtr->dLinePtr;
            dInfoPtr->dLinePtr = dlPtr;
            if (tkTextDebug) {
                char string[TK_POS_CHARS];

                TkTextPrintIndex(&dlPtr->index, string);
                Tcl_SetVar2(textPtr->interp, "tk_textRelayout",
                      (char *) NULL, string,
                      TCL_GLOBAL_ONLY|TCL_APPEND_VALUE|TCL_LIST_ELEMENT);
            }
          }
          FreeDLines(textPtr, lowestPtr, (DLine *) NULL, 0);
          bytesToCount = INT_MAX;
      }

      /*
       * Now we're all done except that the y-coordinates in all the
       * DLines are wrong and the top index for the text is wrong.
       * Update them.
       */

      textPtr->topIndex = dInfoPtr->dLinePtr->index;
      y = dInfoPtr->y;
      for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL;
            dlPtr = dlPtr->nextPtr) {
          if (y > dInfoPtr->maxY) {
            panic("Added too many new lines in UpdateDisplayInfo");
          }
          dlPtr->y = y;
          y += dlPtr->height; 
      }
    }

    /*
     *--------------------------------------------------------------
     * If the old top or bottom line has scrolled elsewhere on the
     * screen, we may not be able to re-use its old contents by
     * copying bits (e.g., a beveled edge that was drawn when it was
     * at the top or bottom won't be drawn when the line is in the
     * middle and its neighbor has a matching background).  Similarly,
     * if the new top or bottom line came from somewhere else on the
     * screen, we may not be able to copy the old bits.
     *--------------------------------------------------------------
     */

    dlPtr = dInfoPtr->dLinePtr;
    if ((dlPtr->flags & HAS_3D_BORDER) && !(dlPtr->flags & TOP_LINE)) {
      dlPtr->oldY = -1;
    }
    while (1) {
      if ((dlPtr->flags & TOP_LINE) && (dlPtr != dInfoPtr->dLinePtr)
            && (dlPtr->flags & HAS_3D_BORDER)) {
          dlPtr->oldY = -1;
      }
      if ((dlPtr->flags & BOTTOM_LINE) && (dlPtr->nextPtr != NULL)
            && (dlPtr->flags & HAS_3D_BORDER)) {
          dlPtr->oldY = -1;
      }
      if (dlPtr->nextPtr == NULL) {
          if ((dlPtr->flags & HAS_3D_BORDER)
                && !(dlPtr->flags & BOTTOM_LINE)) {
            dlPtr->oldY = -1;
          }
          dlPtr->flags &= ~TOP_LINE;
          dlPtr->flags |= BOTTOM_LINE;
          break;
      }
      dlPtr->flags &= ~(TOP_LINE|BOTTOM_LINE);
      dlPtr = dlPtr->nextPtr;
    }
    dInfoPtr->dLinePtr->flags |= TOP_LINE;

    /*
     * Arrange for scrollbars to be updated.
     */

    textPtr->flags |= UPDATE_SCROLLBARS;

    /*
     *--------------------------------------------------------------
     * Deal with horizontal scrolling:
     * 1. If there's empty space to the right of the longest line,
     *    shift the screen to the right to fill in the empty space.
     * 2. If the desired horizontal scroll position has changed,
     *    force a full redisplay of all the lines in the widget.
     * 3. If the wrap mode isn't "none" then re-scroll to the base
     *    position.
     *--------------------------------------------------------------
     */

    dInfoPtr->maxLength = 0;
    for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL;
          dlPtr = dlPtr->nextPtr) {
      if (dlPtr->length > dInfoPtr->maxLength) {
          dInfoPtr->maxLength = dlPtr->length;
      }
    }
    maxOffset = (dInfoPtr->maxLength - (dInfoPtr->maxX - dInfoPtr->x)
          + textPtr->charWidth - 1)/textPtr->charWidth;
    if (dInfoPtr->newByteOffset > maxOffset) {
      dInfoPtr->newByteOffset = maxOffset;
    }
    if (dInfoPtr->newByteOffset < 0) {
      dInfoPtr->newByteOffset = 0;
    }
    pixelOffset = dInfoPtr->newByteOffset * textPtr->charWidth;
    if (pixelOffset != dInfoPtr->curPixelOffset) {
      dInfoPtr->curPixelOffset = pixelOffset;
      for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL;
            dlPtr = dlPtr->nextPtr) {
          dlPtr->oldY = -1;
      }
    }
}

/*
 *----------------------------------------------------------------------
 *
 * FreeDLines --
 *
 *    This procedure is called to free up all of the resources
 *    associated with one or more DLine structures.
 *
 * Results:
 *    None.
 *
 * Side effects:
 *    Memory gets freed and various other resources are released.
 *
 *----------------------------------------------------------------------
 */

static void
FreeDLines(textPtr, firstPtr, lastPtr, unlink)
    TkText *textPtr;                /* Information about overall text
                               * widget. */
    register DLine *firstPtr;       /* Pointer to first DLine to free up. */
    DLine *lastPtr;                 /* Pointer to DLine just after last
                               * one to free (NULL means everything
                               * starting with firstPtr). */
    int unlink;                     /* 1 means DLines are currently linked
                               * into the list rooted at
                               * textPtr->dInfoPtr->dLinePtr and
                               * they have to be unlinked.  0 means
                               * just free without unlinking. */
{
    register TkTextDispChunk *chunkPtr, *nextChunkPtr;
    register DLine *nextDLinePtr;

    if (unlink) {
      if (textPtr->dInfoPtr->dLinePtr == firstPtr) {
          textPtr->dInfoPtr->dLinePtr = lastPtr;
      } else {
          register DLine *prevPtr;
          for (prevPtr = textPtr->dInfoPtr->dLinePtr;
                prevPtr->nextPtr != firstPtr; prevPtr = prevPtr->nextPtr) {
            /* Empty loop body. */
          }
          prevPtr->nextPtr = lastPtr;
      }
    }
    while (firstPtr != lastPtr) {
      nextDLinePtr = firstPtr->nextPtr;
      for (chunkPtr = firstPtr->chunkPtr; chunkPtr != NULL;
            chunkPtr = nextChunkPtr) {
          if (chunkPtr->undisplayProc != NULL) {
            (*chunkPtr->undisplayProc)(textPtr, chunkPtr);
          }
          FreeStyle(textPtr, chunkPtr->stylePtr);
          nextChunkPtr = chunkPtr->nextPtr;
          ckfree((char *) chunkPtr);
      }
      ckfree((char *) firstPtr);
      firstPtr = nextDLinePtr;
    }
    textPtr->dInfoPtr->dLinesInvalidated = 1;
}

/*
 *----------------------------------------------------------------------
 *
 * DisplayDLine --
 *
 *    This procedure is invoked to draw a single line on the
 *    screen.
 *
 * Results:
 *    None.
 *
 * Side effects:
 *    The line given by dlPtr is drawn at its correct position in
 *    textPtr's window.  Note that this is one *display* line, not
 *    one *text* line.
 *
 *----------------------------------------------------------------------
 */

static void
DisplayDLine(textPtr, dlPtr, prevPtr, pixmap)
    TkText *textPtr;          /* Text widget in which to draw line. */
    register DLine *dlPtr;    /* Information about line to draw. */
    DLine *prevPtr;           /* Line just before one to draw, or NULL
                         * if dlPtr is the top line. */
    Pixmap pixmap;            /* Pixmap to use for double-buffering.
                         * Caller must make sure it's large enough
                         * to hold line. */
{
    register TkTextDispChunk *chunkPtr;
    TextDInfo *dInfoPtr = textPtr->dInfoPtr;
    Display *display;
    int height, x;

    if (dlPtr->chunkPtr == NULL) return;

    /*
     * First, clear the area of the line to the background color for the
     * text widget.
     */

    display = Tk_Display(textPtr->tkwin);
    Tk_Fill3DRectangle(textPtr->tkwin, pixmap, textPtr->border, 0, 0,
          Tk_Width(textPtr->tkwin), dlPtr->height, 0, TK_RELIEF_FLAT);

    /*
     * Next, draw background information for the whole line.
     */

    DisplayLineBackground(textPtr, dlPtr, prevPtr, pixmap);

    /*
     * Make another pass through all of the chunks to redraw the
     * insertion cursor, if it is visible on this line.  Must do
     * it here rather than in the foreground pass below because
     * otherwise a wide insertion cursor will obscure the character
     * to its left.
     */

    if (textPtr->state == TK_STATE_NORMAL) {
      for (chunkPtr = dlPtr->chunkPtr; (chunkPtr != NULL);
            chunkPtr = chunkPtr->nextPtr) {
          x = chunkPtr->x + dInfoPtr->x - dInfoPtr->curPixelOffset;
          if (chunkPtr->displayProc == TkTextInsertDisplayProc) {
            (*chunkPtr->displayProc)(chunkPtr, x, dlPtr->spaceAbove,
                  dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow,
                  dlPtr->baseline - dlPtr->spaceAbove, display, pixmap,
                  dlPtr->y + dlPtr->spaceAbove);
          }
      }
    }

    /*
     * Make yet another pass through all of the chunks to redraw all of
     * foreground information.  Note:  we have to call the displayProc
     * even for chunks that are off-screen.  This is needed, for
     * example, so that embedded windows can be unmapped in this case.
     * Conve
     */

    for (chunkPtr = dlPtr->chunkPtr; (chunkPtr != NULL);
          chunkPtr = chunkPtr->nextPtr) {
      if (chunkPtr->displayProc == TkTextInsertDisplayProc) {
          /*
           * Already displayed the insertion cursor above.  Don't
           * do it again here.
           */

          continue;
      }
      x = chunkPtr->x + dInfoPtr->x - dInfoPtr->curPixelOffset;
      if ((x + chunkPtr->width <= 0) || (x >= dInfoPtr->maxX)) {
          /*
           * Note:  we have to call the displayProc even for chunks
           * that are off-screen.  This is needed, for example, so
           * that embedded windows can be unmapped in this case.
           * Display the chunk at a coordinate that can be clearly
           * identified by the displayProc as being off-screen to
           * the left (the displayProc may not be able to tell if
           * something is off to the right).
           */

          if (chunkPtr->displayProc != NULL)
          (*chunkPtr->displayProc)(chunkPtr, -chunkPtr->width,
                dlPtr->spaceAbove,
                dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow,
                dlPtr->baseline - dlPtr->spaceAbove, display, pixmap,
                dlPtr->y + dlPtr->spaceAbove);
      } else {
          /* don't call if elide.  This tax ok since not very many visible DLine's in
              an area, but potentially many elide ones */
          if (chunkPtr->displayProc != NULL)
          (*chunkPtr->displayProc)(chunkPtr, x, dlPtr->spaceAbove,
                dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow,
                dlPtr->baseline - dlPtr->spaceAbove, display, pixmap,
                dlPtr->y + dlPtr->spaceAbove);
      }
      if (dInfoPtr->dLinesInvalidated) {
          return;
      }
    }

    /*
     * Copy the pixmap onto the screen.  If this is the last line on
     * the screen then copy a piece of the line, so that it doesn't
     * overflow into the border area.  Another special trick:  copy the
     * padding area to the left of the line;  this is because the
     * insertion cursor sometimes overflows onto that area and we want
     * to get as much of the cursor as possible.
     */

    height = dlPtr->height;
    if ((height + dlPtr->y) > dInfoPtr->maxY) {
      height = dInfoPtr->maxY - dlPtr->y;
    }
    XCopyArea(display, pixmap, Tk_WindowId(textPtr->tkwin), dInfoPtr->copyGC,
          dInfoPtr->x, 0, (unsigned) (dInfoPtr->maxX - dInfoPtr->x),
          (unsigned) height, dInfoPtr->x, dlPtr->y);
    linesRedrawn++;
}

/*
 *--------------------------------------------------------------
 *
 * DisplayLineBackground --
 *
 *    This procedure is called to fill in the background for
 *    a display line.  It draws 3D borders cleverly so that
 *    adjacent chunks with the same style (whether on the same
 *    line or different lines) have a single 3D border around
 *    the whole region.
 *
 * Results:
 *    There is no return value.  Pixmap is filled in with background
 *    information for dlPtr.
 *
 * Side effects:
 *    None.
 *
 *--------------------------------------------------------------
 */

static void
DisplayLineBackground(textPtr, dlPtr, prevPtr, pixmap)
    TkText *textPtr;          /* Text widget containing line. */
    register DLine *dlPtr;    /* Information about line to draw. */
    DLine *prevPtr;           /* Line just above dlPtr, or NULL if dlPtr
                         * is the top-most line in the window. */
    Pixmap pixmap;            /* Pixmap to use for double-buffering.
                         * Caller must make sure it's large enough
                         * to hold line.  Caller must also have
                         * filled it with the background color for
                         * the widget. */
{
    TextDInfo *dInfoPtr = textPtr->dInfoPtr;
    TkTextDispChunk *chunkPtr;  /* Pointer to chunk in the current line. */
    TkTextDispChunk *chunkPtr2;     /* Pointer to chunk in the line above or
                         * below the current one.  NULL if we're to
                         * the left of or to the right of the chunks
                         * in the line. */
    TkTextDispChunk *nextPtr2;      /* Next chunk after chunkPtr2 (it's not the
                         * same as chunkPtr2->nextPtr in the case
                         * where chunkPtr2 is NULL because the line
                         * is indented). */
    int leftX;                /* The left edge of the region we're
                         * currently working on. */
    int leftXIn;        /* 1 means beveled edge at leftX slopes right
                         * as it goes down, 0 means it slopes left
                         * as it goes down. */
    int rightX;               /* Right edge of chunkPtr. */
    int rightX2;        /* Right edge of chunkPtr2. */
    int matchLeft;            /* Does the style of this line match that
                         * of its neighbor just to the left of
                         * the current x coordinate? */
    int matchRight;           /* Does line's style match its neighbor
                         * just to the right of the current x-coord? */
    int minX, maxX, xOffset;
    StyleValues *sValuePtr;
    Display *display;


    /*
     * Pass 1: scan through dlPtr from left to right.  For each range of
     * chunks with the same style, draw the main background for the style
     * plus the vertical parts of the 3D borders (the left and right
     * edges).
     */

    display = Tk_Display(textPtr->tkwin);
    minX = dInfoPtr->curPixelOffset;
    xOffset = dInfoPtr->x - minX;
    maxX = minX + dInfoPtr->maxX - dInfoPtr->x;
    chunkPtr = dlPtr->chunkPtr;

    /*
     * Note A: in the following statement, and a few others later in
     * this file marked with "See Note A above", the right side of the
     * assignment was replaced with 0 on 6/18/97.  This has the effect
     * of highlighting the empty space to the left of a line whenever
     * the leftmost character of the line is highlighted.  This way,
     * multi-line highlights always line up along their left edges. 
     * However, this may look funny in the case where a single word is
     * highlighted. To undo the change, replace "leftX = 0" with "leftX
     * = chunkPtr->x" and "rightX2 = 0" with "rightX2 = nextPtr2->x"
     * here and at all the marked points below.  This restores the old
     * behavior where empty space to the left of a line is not
     * highlighted, leaving a ragged left edge for multi-line
     * highlights.
     */

    leftX = 0;
    for (; leftX < maxX; chunkPtr = chunkPtr->nextPtr) {
      if ((chunkPtr->nextPtr != NULL)
            && SAME_BACKGROUND(chunkPtr->nextPtr->stylePtr,
            chunkPtr->stylePtr)) {
          continue;
      }
      sValuePtr = chunkPtr->stylePtr->sValuePtr;
      rightX = chunkPtr->x + chunkPtr->width;
      if ((chunkPtr->nextPtr == NULL) && (rightX < maxX)) {
          rightX = maxX;
      }
      if (chunkPtr->stylePtr->bgGC != None) {
          /* Not visible - bail out now */
          if (rightX + xOffset <= 0) {
              leftX = rightX;
            continue;
          }

          /*
           * Trim the start position for drawing to be no further away than
           * -borderWidth. The reason is that on many X servers drawing from
           * -32768 (or less) to +something simply does not display
           * correctly. [Patch #541999]
           */
          if ((leftX + xOffset) < -(sValuePtr->borderWidth)) {
              leftX = -sValuePtr->borderWidth - xOffset;
          }
          if ((rightX - leftX) > 32767) {
              rightX = leftX + 32767;
          }

          XFillRectangle(display, pixmap, chunkPtr->stylePtr->bgGC,
                leftX + xOffset, 0, (unsigned int) (rightX - leftX),
                (unsigned int) dlPtr->height);
          if (sValuePtr->relief != TK_RELIEF_FLAT) {
            Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border,
                  leftX + xOffset, 0, sValuePtr->borderWidth,
                  dlPtr->height, 1, sValuePtr->relief);
            Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border,
                  rightX - sValuePtr->borderWidth + xOffset,
                  0, sValuePtr->borderWidth, dlPtr->height, 0,
                  sValuePtr->relief);
          }
      }
      leftX = rightX;
    }

    /*
     * Pass 2: draw the horizontal bevels along the top of the line.  To
     * do this, scan through dlPtr from left to right while simultaneously
     * scanning through the line just above dlPtr.  ChunkPtr2 and nextPtr2
     * refer to two adjacent chunks in the line above.
     */

    chunkPtr = dlPtr->chunkPtr;
    leftX = 0;                      /* See Note A above. */
    leftXIn = 1;
    rightX = chunkPtr->x + chunkPtr->width;
    if ((chunkPtr->nextPtr == NULL) && (rightX < maxX)) {
      rightX = maxX;
    }
    chunkPtr2 = NULL;
    if (prevPtr != NULL && prevPtr->chunkPtr != NULL) {
      /*
       * Find the chunk in the previous line that covers leftX.
       */

      nextPtr2 = prevPtr->chunkPtr;
      rightX2 = 0;                  /* See Note A above. */
      while (rightX2 <= leftX) {
          chunkPtr2 = nextPtr2;
          if (chunkPtr2 == NULL) {
            break;
          }
          nextPtr2 = chunkPtr2->nextPtr;
          rightX2 = chunkPtr2->x + chunkPtr2->width;
          if (nextPtr2 == NULL) {
            rightX2 = INT_MAX;
          }
      }
    } else {
      nextPtr2 = NULL;
      rightX2 = INT_MAX;
    }

    while (leftX < maxX) {
      matchLeft = (chunkPtr2 != NULL)
            && SAME_BACKGROUND(chunkPtr2->stylePtr, chunkPtr->stylePtr);
      sValuePtr = chunkPtr->stylePtr->sValuePtr;
      if (rightX <= rightX2) {
          /*
           * The chunk in our line is about to end.  If its style
           * changes then draw the bevel for the current style.
           */

          if ((chunkPtr->nextPtr == NULL)
                || !SAME_BACKGROUND(chunkPtr->stylePtr,
                chunkPtr->nextPtr->stylePtr)) {
            if (!matchLeft && (sValuePtr->relief != TK_RELIEF_FLAT)) {
                Tk_3DHorizontalBevel(textPtr->tkwin, pixmap,
                      sValuePtr->border, leftX + xOffset, 0,
                      rightX - leftX, sValuePtr->borderWidth, leftXIn,
                      1, 1, sValuePtr->relief);
            }
            leftX = rightX;
            leftXIn = 1;

            /*
             * If the chunk in the line above is also ending at
             * the same point then advance to the next chunk in
             * that line.
             */

            if ((rightX == rightX2) && (chunkPtr2 != NULL)) {
                goto nextChunk2;
            }
          }
          chunkPtr = chunkPtr->nextPtr;
          if (chunkPtr == NULL) {
            break;
          }
          rightX = chunkPtr->x + chunkPtr->width;
          if ((chunkPtr->nextPtr == NULL) && (rightX < maxX)) {
            rightX = maxX;
          }
          continue;
      }

      /*
       * The chunk in the line above is ending at an x-position where
       * there is no change in the style of the current line.  If the
       * style above matches the current line on one side of the change
       * but not on the other, we have to draw an L-shaped piece of
       * bevel.
       */

      matchRight = (nextPtr2 != NULL)
            && SAME_BACKGROUND(nextPtr2->stylePtr, chunkPtr->stylePtr);
      if (matchLeft && !matchRight) {
          if (sValuePtr->relief != TK_RELIEF_FLAT) {
            Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border,
                  rightX2 - sValuePtr->borderWidth + xOffset, 0,
                  sValuePtr->borderWidth, sValuePtr->borderWidth, 0,
                  sValuePtr->relief);
          }
          leftX = rightX2 - sValuePtr->borderWidth;
          leftXIn = 0;
      } else if (!matchLeft && matchRight
            && (sValuePtr->relief != TK_RELIEF_FLAT)) {
          Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border,
                rightX2 + xOffset, 0, sValuePtr->borderWidth,
                sValuePtr->borderWidth, 1, sValuePtr->relief);
          Tk_3DHorizontalBevel(textPtr->tkwin, pixmap, sValuePtr->border,
                leftX + xOffset, 0, rightX2 + sValuePtr->borderWidth -leftX,
                sValuePtr->borderWidth, leftXIn, 0, 1,
                sValuePtr->relief);
      }

      nextChunk2:
      chunkPtr2 = nextPtr2;
      if (chunkPtr2 == NULL) {
          rightX2 = INT_MAX;
      } else {
          nextPtr2 = chunkPtr2->nextPtr;
          rightX2 = chunkPtr2->x + chunkPtr2->width;
          if (nextPtr2 == NULL) {
            rightX2 = INT_MAX;
          }
      }
    }
    /*
     * Pass 3: draw the horizontal bevels along the bottom of the line.
     * This uses the same approach as pass 2.
     */

    chunkPtr = dlPtr->chunkPtr;
    leftX = 0;                      /* See Note A above. */
    leftXIn = 0;
    rightX = chunkPtr->x + chunkPtr->width;
    if ((chunkPtr->nextPtr == NULL) && (rightX < maxX)) {
      rightX = maxX;
    }
    chunkPtr2 = NULL;
    if (dlPtr->nextPtr != NULL && dlPtr->nextPtr->chunkPtr != NULL) {
      /*
       * Find the chunk in the previous line that covers leftX.
       */

      nextPtr2 = dlPtr->nextPtr->chunkPtr;
      rightX2 = 0;                  /* See Note A above. */
      while (rightX2 <= leftX) {
          chunkPtr2 = nextPtr2;
          if (chunkPtr2 == NULL) {
            break;
          }
          nextPtr2 = chunkPtr2->nextPtr;
          rightX2 = chunkPtr2->x + chunkPtr2->width;
          if (nextPtr2 == NULL) {
            rightX2 = INT_MAX;
          }
      }
    } else {
      nextPtr2 = NULL;
      rightX2 = INT_MAX;
    }

    while (leftX < maxX) {
      matchLeft = (chunkPtr2 != NULL)
            && SAME_BACKGROUND(chunkPtr2->stylePtr, chunkPtr->stylePtr);
      sValuePtr = chunkPtr->stylePtr->sValuePtr;
      if (rightX <= rightX2) {
          if ((chunkPtr->nextPtr == NULL)
                || !SAME_BACKGROUND(chunkPtr->stylePtr,
                chunkPtr->nextPtr->stylePtr)) {
            if (!matchLeft && (sValuePtr->relief != TK_RELIEF_FLAT)) {
                Tk_3DHorizontalBevel(textPtr->tkwin, pixmap,
                      sValuePtr->border, leftX + xOffset,
                      dlPtr->height - sValuePtr->borderWidth,
                      rightX - leftX, sValuePtr->borderWidth, leftXIn,
                      0, 0, sValuePtr->relief);
            }
            leftX = rightX;
            leftXIn = 0;
            if ((rightX == rightX2) && (chunkPtr2 != NULL)) {
                goto nextChunk2b;
            }
          }
          chunkPtr = chunkPtr->nextPtr;
          if (chunkPtr == NULL) {
            break;
          }
          rightX = chunkPtr->x + chunkPtr->width;
          if ((chunkPtr->nextPtr == NULL) && (rightX < maxX)) {
            rightX = maxX;
          }
          continue;
      }

      matchRight = (nextPtr2 != NULL)
            && SAME_BACKGROUND(nextPtr2->stylePtr, chunkPtr->stylePtr);
      if (matchLeft && !matchRight) {
          if (sValuePtr->relief != TK_RELIEF_FLAT) {
            Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border,
                  rightX2 - sValuePtr->borderWidth + xOffset,
                  dlPtr->height - sValuePtr->borderWidth,
                  sValuePtr->borderWidth, sValuePtr->borderWidth, 0,
                  sValuePtr->relief);
          }
          leftX = rightX2 - sValuePtr->borderWidth;
          leftXIn = 1;
      } else if (!matchLeft && matchRight
            && (sValuePtr->relief != TK_RELIEF_FLAT)) {
          Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border,
                rightX2 + xOffset, dlPtr->height - sValuePtr->borderWidth,
                sValuePtr->borderWidth, sValuePtr->borderWidth,
                1, sValuePtr->relief);
          Tk_3DHorizontalBevel(textPtr->tkwin, pixmap, sValuePtr->border,
                leftX + xOffset, dlPtr->height - sValuePtr->borderWidth,
                rightX2 + sValuePtr->borderWidth - leftX,
                sValuePtr->borderWidth, leftXIn, 1, 0, sValuePtr->relief);
      }

      nextChunk2b:
      chunkPtr2 = nextPtr2;
      if (chunkPtr2 == NULL) {
          rightX2 = INT_MAX;
      } else {
          nextPtr2 = chunkPtr2->nextPtr;
          rightX2 = chunkPtr2->x + chunkPtr2->width;
          if (nextPtr2 == NULL) {
            rightX2 = INT_MAX;
          }
      }
    }
}

/*
 *----------------------------------------------------------------------
 *
 * DisplayText --
 *
 *    This procedure is invoked as a when-idle handler to update the
 *    display.  It only redisplays the parts of the text widget that
 *    are out of date.
 *
 * Results:
 *    None.
 *
 * Side effects:
 *    Information is redrawn on the screen.
 *
 *----------------------------------------------------------------------
 */

static void
DisplayText(clientData)
    ClientData clientData;    /* Information about widget. */
{
    register TkText *textPtr = (TkText *) clientData;
    TextDInfo *dInfoPtr = textPtr->dInfoPtr;
    Tk_Window tkwin;
    register DLine *dlPtr;
    DLine *prevPtr;
    Pixmap pixmap;
    int maxHeight, borders;
    int bottomY = 0;          /* Initialization needed only to stop
                         * compiler warnings. */
    Tcl_Interp *interp;

    if (textPtr->tkwin == NULL) {

      /*
       * The widget has been deleted.  Don't do anything.
       */

      return;
    }

    interp = textPtr->interp;
    Tcl_Preserve((ClientData) interp);

    if (tkTextDebug) {
      Tcl_SetVar2(interp, "tk_textRelayout", (char *) NULL, "",
                TCL_GLOBAL_ONLY);
    }

    if (textPtr->tkwin == NULL) {

      /*
       * The widget has been deleted.  Don't do anything.
       */

        goto end;
    }

    if (!Tk_IsMapped(textPtr->tkwin) || (dInfoPtr->maxX <= dInfoPtr->x)
          || (dInfoPtr->maxY <= dInfoPtr->y)) {
      UpdateDisplayInfo(textPtr);
      dInfoPtr->flags &= ~REDRAW_PENDING;
      goto doScrollbars;
    }
    numRedisplays++;
    if (tkTextDebug) {
      Tcl_SetVar2(interp, "tk_textRedraw", (char *) NULL, "",
                TCL_GLOBAL_ONLY);
    }

    if (textPtr->tkwin == NULL) {

      /*
       * The widget has been deleted.  Don't do anything.
       */

      goto end;
    }

    /*
     * Choose a new current item if that is needed (this could cause
     * event handlers to be invoked, hence the preserve/release calls
     * and the loop, since the handlers could conceivably necessitate
     * yet another current item calculation).  The tkwin check is because
     * the whole window could go away in the Tcl_Release call.
     */

    while (dInfoPtr->flags & REPICK_NEEDED) {
      Tcl_Preserve((ClientData) textPtr);
      dInfoPtr->flags &= ~REPICK_NEEDED;
      TkTextPickCurrent(textPtr, &textPtr->pickEvent);
      tkwin = textPtr->tkwin;
      Tcl_Release((ClientData) textPtr);
      if (tkwin == NULL) {
          goto end;
      }
    }

    /*
     * First recompute what's supposed to be displayed.
     */

    UpdateDisplayInfo(textPtr);
    dInfoPtr->dLinesInvalidated = 0;

    /*
     * See if it's possible to bring some parts of the screen up-to-date
     * by scrolling (copying from other parts of the screen).
     */

    for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL; dlPtr = dlPtr->nextPtr) {
      register DLine *dlPtr2;
      int offset, height, y, oldY;
      TkRegion damageRgn;

      if ((dlPtr->oldY == -1) || (dlPtr->y == dlPtr->oldY)
            || ((dlPtr->oldY + dlPtr->height) > dInfoPtr->maxY)) {
          continue;
      }

      /*
       * This line is already drawn somewhere in the window so it only
       * needs to be copied to its new location.  See if there's a group
       * of lines that can all be copied together.
       */

      offset = dlPtr->y - dlPtr->oldY;
      height = dlPtr->height;
      y = dlPtr->y;
      for (dlPtr2 = dlPtr->nextPtr; dlPtr2 != NULL;
            dlPtr2 = dlPtr2->nextPtr) {
          if ((dlPtr2->oldY == -1)
                || ((dlPtr2->oldY + offset) != dlPtr2->y)
                || ((dlPtr2->oldY + dlPtr2->height) > dInfoPtr->maxY)) {
            break;
          }
          height += dlPtr2->height;
      }

      /*
       * Reduce the height of the area being copied if necessary to
       * avoid overwriting the border area.
       */

      if ((y + height) > dInfoPtr->maxY) {
          height = dInfoPtr->maxY -y;
      }
      oldY = dlPtr->oldY;

      /*
       * Update the lines we are going to scroll to show that they
       * have been copied.
       */

      while (1) {
          dlPtr->oldY = dlPtr->y;
          if (dlPtr->nextPtr == dlPtr2) {
            break;
          }
          dlPtr = dlPtr->nextPtr;
      }

      /*
       * Scan through the lines following the copied ones to see if
       * we are going to overwrite them with the copy operation.
       * If so, mark them for redisplay.
       */

      for ( ; dlPtr2 != NULL; dlPtr2 = dlPtr2->nextPtr) {
          if ((dlPtr2->oldY != -1)
                && ((dlPtr2->oldY + dlPtr2->height) > y)
                && (dlPtr2->oldY < (y + height))) {
            dlPtr2->oldY = -1;
          }
      }

      /*
       * Now scroll the lines.  This may generate damage which we
       * handle by calling TextInvalidateRegion to mark the display
       * blocks as stale.
       */

      damageRgn = TkCreateRegion();
      if (TkScrollWindow(textPtr->tkwin, dInfoPtr->scrollGC,
            dInfoPtr->x, oldY,
            (dInfoPtr->maxX - dInfoPtr->x), height,
            0, y - oldY, damageRgn)) {
          TextInvalidateRegion(textPtr, damageRgn);
      }
      numCopies++;
      TkDestroyRegion(damageRgn);
    }

    /*
     * Clear the REDRAW_PENDING flag here.  This is actually pretty
     * tricky.  We want to wait until *after* doing the scrolling,
     * since that could generate more areas to redraw and don't
     * want to reschedule a redisplay for them.  On the other hand,
     * we can't wait until after all the redisplaying, because the
     * act of redisplaying could actually generate more redisplays
     * (e.g. in the case of a nested window with event bindings triggered
     * by redisplay).
     */

    dInfoPtr->flags &= ~REDRAW_PENDING;

    /*
     * Redraw the borders if that's needed.
     */

    if (dInfoPtr->flags & REDRAW_BORDERS) {
      if (tkTextDebug) {
          Tcl_SetVar2(interp, "tk_textRedraw", (char *) NULL, "borders",
                TCL_GLOBAL_ONLY|TCL_APPEND_VALUE|TCL_LIST_ELEMENT);
      }

        if (textPtr->tkwin == NULL) {

          /*
             * The widget has been deleted.  Don't do anything.
             */

            goto end;
        }

      Tk_Draw3DRectangle(textPtr->tkwin, Tk_WindowId(textPtr->tkwin),
            textPtr->border, textPtr->highlightWidth,
            textPtr->highlightWidth,
            Tk_Width(textPtr->tkwin) - 2*textPtr->highlightWidth,
            Tk_Height(textPtr->tkwin) - 2*textPtr->highlightWidth,
            textPtr->borderWidth, textPtr->relief);
      if (textPtr->highlightWidth != 0) {
          GC fgGC, bgGC;
    
          bgGC = Tk_GCForColor(textPtr->highlightBgColorPtr,
                  Tk_WindowId(textPtr->tkwin));
          if (textPtr->flags & GOT_FOCUS) {
            fgGC = Tk_GCForColor(textPtr->highlightColorPtr,
                  Tk_WindowId(textPtr->tkwin));
              TkpDrawHighlightBorder(textPtr->tkwin, fgGC, bgGC, 
                    textPtr->highlightWidth, Tk_WindowId(textPtr->tkwin));
          } else {
              TkpDrawHighlightBorder(textPtr->tkwin, bgGC, bgGC, 
                    textPtr->highlightWidth, Tk_WindowId(textPtr->tkwin));
          }
      }
      borders = textPtr->borderWidth + textPtr->highlightWidth;
      if (textPtr->padY > 0) {
          Tk_Fill3DRectangle(textPtr->tkwin, Tk_WindowId(textPtr->tkwin),
                textPtr->border, borders, borders,
                Tk_Width(textPtr->tkwin) - 2*borders, textPtr->padY,
                0, TK_RELIEF_FLAT);
          Tk_Fill3DRectangle(textPtr->tkwin, Tk_WindowId(textPtr->tkwin),
                textPtr->border, borders,
                Tk_Height(textPtr->tkwin) - borders - textPtr->padY,
                Tk_Width(textPtr->tkwin) - 2*borders,
                textPtr->padY, 0, TK_RELIEF_FLAT);
      }
      if (textPtr->padX > 0) {
          Tk_Fill3DRectangle(textPtr->tkwin, Tk_WindowId(textPtr->tkwin),
                textPtr->border, borders, borders + textPtr->padY,
                textPtr->padX,
                Tk_Height(textPtr->tkwin) - 2*borders -2*textPtr->padY,
                0, TK_RELIEF_FLAT);
          Tk_Fill3DRectangle(textPtr->tkwin, Tk_WindowId(textPtr->tkwin),
                textPtr->border,
                Tk_Width(textPtr->tkwin) - borders - textPtr->padX,
                borders + textPtr->padY, textPtr->padX,
                Tk_Height(textPtr->tkwin) - 2*borders -2*textPtr->padY,
                0, TK_RELIEF_FLAT);
      }
      dInfoPtr->flags &= ~REDRAW_BORDERS;
    }

    /*
     * Now we have to redraw the lines that couldn't be updated by
     * scrolling.  First, compute the height of the largest line and
     * allocate an off-screen pixmap to use for double-buffered
     * displays.
     */

    maxHeight = -1;
    for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL;
          dlPtr = dlPtr->nextPtr) {
      if ((dlPtr->height > maxHeight) && (dlPtr->oldY != dlPtr->y)) {
          maxHeight = dlPtr->height;
      }
      bottomY = dlPtr->y + dlPtr->height;
    }
    if (maxHeight > dInfoPtr->maxY) {
      maxHeight = dInfoPtr->maxY;
    }
    if (maxHeight > 0) {
      pixmap = Tk_GetPixmap(Tk_Display(textPtr->tkwin),
            Tk_WindowId(textPtr->tkwin), Tk_Width(textPtr->tkwin),
            maxHeight, Tk_Depth(textPtr->tkwin));
      for (prevPtr = NULL, dlPtr = textPtr->dInfoPtr->dLinePtr;
            (dlPtr != NULL) && (dlPtr->y < dInfoPtr->maxY);
            prevPtr = dlPtr, dlPtr = dlPtr->nextPtr) {
          if (dlPtr->chunkPtr == NULL) continue;
          if (dlPtr->oldY != dlPtr->y) {
            if (tkTextDebug) {
                char string[TK_POS_CHARS];
                TkTextPrintIndex(&dlPtr->index, string);
                Tcl_SetVar2(textPtr->interp, "tk_textRedraw",
                      (char *) NULL, string,
                      TCL_GLOBAL_ONLY|TCL_APPEND_VALUE|TCL_LIST_ELEMENT);
            }
            DisplayDLine(textPtr, dlPtr, prevPtr, pixmap);
            if (dInfoPtr->dLinesInvalidated) {
                Tk_FreePixmap(Tk_Display(textPtr->tkwin), pixmap);
                return;
            }
            dlPtr->oldY = dlPtr->y;
            dlPtr->flags &= ~NEW_LAYOUT;
          }
          /*prevPtr = dlPtr;*/
      }
      Tk_FreePixmap(Tk_Display(textPtr->tkwin), pixmap);
    }

    /*
     * See if we need to refresh the part of the window below the
     * last line of text (if there is any such area).  Refresh the
     * padding area on the left too, since the insertion cursor might
     * have been displayed there previously).
     */

    if (dInfoPtr->topOfEof > dInfoPtr->maxY) {
      dInfoPtr->topOfEof = dInfoPtr->maxY;
    }
    if (bottomY < dInfoPtr->topOfEof) {
      if (tkTextDebug) {
          Tcl_SetVar2(textPtr->interp, "tk_textRedraw",
                (char *) NULL, "eof",
                TCL_GLOBAL_ONLY|TCL_APPEND_VALUE|TCL_LIST_ELEMENT);
      }

        if (textPtr->tkwin == NULL) {

          /*
             * The widget has been deleted.  Don't do anything.
             */

            goto end;
        }

      Tk_Fill3DRectangle(textPtr->tkwin, Tk_WindowId(textPtr->tkwin),
            textPtr->border, dInfoPtr->x - textPtr->padX, bottomY,
            dInfoPtr->maxX - (dInfoPtr->x - textPtr->padX),
            dInfoPtr->topOfEof-bottomY, 0, TK_RELIEF_FLAT);
    }
    dInfoPtr->topOfEof = bottomY;

    doScrollbars:

    /*
     * Update the vertical scrollbar, if there is one.  Note:  it's
     * important to clear REDRAW_PENDING here, just in case the
     * scroll procedure does something that requires redisplay.
     */
    
    if (textPtr->flags & UPDATE_SCROLLBARS) {
      textPtr->flags &= ~UPDATE_SCROLLBARS;
      if (textPtr->yScrollCmd != NULL) {
          GetYView(textPtr->interp, textPtr, 1);
      }

        if (textPtr->tkwin == NULL) {

          /*
             * The widget has been deleted.  Don't do anything.
             */

            goto end;
        }

      /*
       * Update the horizontal scrollbar, if any.
       */

      if (textPtr->xScrollCmd != NULL) {
          GetXView(textPtr->interp, textPtr, 1);
      }
    }

end:
    Tcl_Release((ClientData) interp);
}

/*
 *----------------------------------------------------------------------
 *
 * TkTextEventuallyRepick --
 *
 *    This procedure is invoked whenever something happens that
 *    could change the current character or the tags associated
 *    with it.
 *
 * Results:
 *    None.
 *
 * Side effects:
 *    A repick is scheduled as an idle handler.
 *
 *----------------------------------------------------------------------
 */

      /* ARGSUSED */
void
TkTextEventuallyRepick(textPtr)
    TkText *textPtr;          /* Widget record for text widget. */
{
    TextDInfo *dInfoPtr = textPtr->dInfoPtr;

    dInfoPtr->flags |= REPICK_NEEDED;
    if (!(dInfoPtr->flags & REDRAW_PENDING)) {
      dInfoPtr->flags |= REDRAW_PENDING;
      Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * TkTextRedrawRegion --
 *
 *    This procedure is invoked to schedule a redisplay for a given
 *    region of a text widget.  The redisplay itself may not occur
 *    immediately:  it's scheduled as a when-idle handler.
 *
 * Results:
 *    None.
 *
 * Side effects:
 *    Information will eventually be redrawn on the screen.
 *
 *----------------------------------------------------------------------
 */

      /* ARGSUSED */
void
TkTextRedrawRegion(textPtr, x, y, width, height)
    TkText *textPtr;          /* Widget record for text widget. */
    int x, y;                 /* Coordinates of upper-left corner of area
                         * to be redrawn, in pixels relative to
                         * textPtr's window. */
    int width, height;        /* Width and height of area to be redrawn. */
{
    TextDInfo *dInfoPtr = textPtr->dInfoPtr;
    TkRegion damageRgn = TkCreateRegion();
    XRectangle rect;

    rect.x = x;
    rect.y = y;
    rect.width = width;
    rect.height = height;
    TkUnionRectWithRegion(&rect, damageRgn, damageRgn);

    TextInvalidateRegion(textPtr, damageRgn);

    if (!(dInfoPtr->flags & REDRAW_PENDING)) {
      dInfoPtr->flags |= REDRAW_PENDING;
      Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr);
    }
    TkDestroyRegion(damageRgn);
}

/*
 *----------------------------------------------------------------------
 *
 * TextInvalidateRegion --
 *
 *    Mark a region of text as invalid.
 *
 * Results:
 *    None.
 *
 * Side effects:
 *    Updates the display information for the text widget.
 *
 *----------------------------------------------------------------------
 */

static void
TextInvalidateRegion(textPtr, region)
    TkText *textPtr;          /* Widget record for text widget. */
    TkRegion region;          /* Region of area to redraw. */
{
    register DLine *dlPtr;
    TextDInfo *dInfoPtr = textPtr->dInfoPtr;
    int maxY, inset;
    XRectangle rect;

    /*
     * Find all lines that overlap the given region and mark them for
     * redisplay.
     */

    TkClipBox(region, &rect);
    maxY = rect.y + rect.height;
    for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL;
          dlPtr = dlPtr->nextPtr) {
      if ((dlPtr->oldY != -1) && (TkRectInRegion(region, rect.x, dlPtr->y,
            rect.width, (unsigned int) dlPtr->height) != RectangleOut)) {
          dlPtr->oldY = -1;
      }
    }
    if (dInfoPtr->topOfEof < maxY) {
      dInfoPtr->topOfEof = maxY;
    }

    /*
     * Schedule the redisplay operation if there isn't one already
     * scheduled.
     */

    inset = textPtr->borderWidth + textPtr->highlightWidth;
    if ((rect.x < (inset + textPtr->padX))
          || (rect.y < (inset + textPtr->padY))
          || ((int) (rect.x + rect.width) > (Tk_Width(textPtr->tkwin)
                - inset - textPtr->padX))
          || (maxY > (Tk_Height(textPtr->tkwin) - inset - textPtr->padY))) {
      dInfoPtr->flags |= REDRAW_BORDERS;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * TkTextChanged --
 *
 *    This procedure is invoked when info in a text widget is about
 *    to be modified in a way that changes how it is displayed (e.g.
 *    characters were inserted or deleted, or tag information was
 *    changed).  This procedure must be called *before* a change is
 *    made, so that indexes in the display information are still
 *    valid.
 *
 * Results:
 *    None.
 *
 * Side effects:
 *    The range of character between index1Ptr (inclusive) and
 *    index2Ptr (exclusive) will be redisplayed at some point in the
 *    future (the actual redisplay is scheduled as a when-idle handler).
 *
 *----------------------------------------------------------------------
 */

void
TkTextChanged(textPtr, index1Ptr, index2Ptr)
    TkText *textPtr;          /* Widget record for text widget. */
    TkTextIndex *index1Ptr;   /* Index of first character to redisplay. */
    TkTextIndex *index2Ptr;   /* Index of character just after last one
                         * to redisplay. */
{
    TextDInfo *dInfoPtr = textPtr->dInfoPtr;
    DLine *firstPtr, *lastPtr;
    TkTextIndex rounded;

    /*
     * Schedule both a redisplay and a recomputation of display information.
     * It's done here rather than the end of the procedure for two reasons:
     *
     * 1. If there are no display lines to update we'll want to return
     *    immediately, well before the end of the procedure.
     * 2. It's important to arrange for the redisplay BEFORE calling
     *    FreeDLines.  The reason for this is subtle and has to do with
     *    embedded windows.  The chunk delete procedure for an embedded
     *    window will schedule an idle handler to unmap the window.
     *    However, we want the idle handler for redisplay to be called
     *    first, so that it can put the embedded window back on the screen
     *    again (if appropriate).  This will prevent the window from ever
     *    being unmapped, and thereby avoid flashing.
     */

    if (!(dInfoPtr->flags & REDRAW_PENDING)) {
      Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr);
    }
    dInfoPtr->flags |= REDRAW_PENDING|DINFO_OUT_OF_DATE|REPICK_NEEDED;

    /*
     * Find the DLines corresponding to index1Ptr and index2Ptr.  There
     * is one tricky thing here, which is that we have to relayout in
     * units of whole text lines:  round index1Ptr back to the beginning
     * of its text line, and include all the display lines after index2,
     * up to the end of its text line.  This is necessary because the
     * indices stored in the display lines will no longer be valid.  It's
     * also needed because any edit could change the way lines wrap.
     */

    rounded = *index1Ptr;
    rounded.byteIndex = 0;
    firstPtr = FindDLine(dInfoPtr->dLinePtr, &rounded);
    if (firstPtr == NULL) {
      return;
    }
    lastPtr = FindDLine(dInfoPtr->dLinePtr, index2Ptr);
    while ((lastPtr != NULL)
          && (lastPtr->index.linePtr == index2Ptr->linePtr)) {
      lastPtr = lastPtr->nextPtr;
    }

    /*
     * Delete all the DLines from firstPtr up to but not including lastPtr.
     */

    FreeDLines(textPtr, firstPtr, lastPtr, 1);
}

/*
 *----------------------------------------------------------------------
 *
 * TkTextRedrawTag --
 *
 *    This procedure is invoked to request a redraw of all characters
 *    in a given range that have a particular tag on or off.  It's
 *    called, for example, when tag options change.
 *
 * Results:
 *    None.
 *
 * Side effects:
 *    Information on the screen may be redrawn, and the layout of
 *    the screen may change.
 *
 *----------------------------------------------------------------------
 */

void
TkTextRedrawTag(textPtr, index1Ptr, index2Ptr, tagPtr, withTag)
    TkText *textPtr;          /* Widget record for text widget. */
    TkTextIndex *index1Ptr;   /* First character in range to consider
                         * for redisplay.  NULL means start at
                         * beginning of text. */
    TkTextIndex *index2Ptr;   /* Character just after last one to consider
                         * for redisplay.  NULL means process all
                         * the characters in the text. */
    TkTextTag *tagPtr;        /* Information about tag. */
    int withTag;        /* 1 means redraw characters that have the
                         * tag, 0 means redraw those without. */
{
    register DLine *dlPtr;
    DLine *endPtr;
    int tagOn;
    TkTextSearch search;
    TextDInfo *dInfoPtr = textPtr->dInfoPtr;
    TkTextIndex *curIndexPtr;
    TkTextIndex endOfText, *endIndexPtr;

    /*
     * Round up the starting position if it's before the first line
     * visible on the screen (we only care about what's on the screen).
     */

    dlPtr = dInfoPtr->dLinePtr;
    if (dlPtr == NULL) {
      return;
    }
    if ((index1Ptr == NULL) || (TkTextIndexCmp(&dlPtr->index, index1Ptr) > 0)) {
      index1Ptr = &dlPtr->index;
    }

    /*
     * Set the stopping position if it wasn't specified.
     */

    if (index2Ptr == NULL) {
      index2Ptr = TkTextMakeByteIndex(textPtr->tree,
            TkBTreeNumLines(textPtr->tree), 0, &endOfText);
    }

    /* 
     * Initialize a search through all transitions on the tag, starting
     * with the first transition where the tag's current state is different
     * from what it will eventually be.
     */

    TkBTreeStartSearch(index1Ptr, index2Ptr, tagPtr, &search);
    /*
     * Make our own curIndex because at this point search.curIndex
     * may not equal index1Ptr->curIndex in the case the first tag toggle
     * comes after index1Ptr (See the use of FindTagStart in TkBTreeStartSearch)
     */
    curIndexPtr = index1Ptr;
    tagOn = TkBTreeCharTagged(index1Ptr, tagPtr);
    if (tagOn != withTag) {
      if (!TkBTreeNextTag(&search)) {
          return;
      }
      curIndexPtr = &search.curIndex;
    }

    /*
     * Schedule a redisplay and layout recalculation if they aren't
     * already pending.  This has to be done before calling FreeDLines,
     * for the reason given in TkTextChanged.
     */

    if (!(dInfoPtr->flags & REDRAW_PENDING)) {
      Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr);
    }
    dInfoPtr->flags |= REDRAW_PENDING|DINFO_OUT_OF_DATE|REPICK_NEEDED;

    /*
     * Each loop through the loop below is for one range of characters
     * where the tag's current state is different than its eventual
     * state.  At the top of the loop, search contains information about
     * the first character in the range.
     */

    while (1) {
      /*
       * Find the first DLine structure in the range.  Note: if the
       * desired character isn't the first in its text line, then look
       * for the character just before it instead.  This is needed to
       * handle the case where the first character of a wrapped
       * display line just got smaller, so that it now fits on the
       * line before:  need to relayout the line containing the
       * previous character.
       */

      if (curIndexPtr->byteIndex == 0) {
          dlPtr = FindDLine(dlPtr, curIndexPtr);
      } else {
          TkTextIndex tmp;

          tmp = *curIndexPtr;
          tmp.byteIndex -= 1;
          dlPtr = FindDLine(dlPtr, &tmp);
      }
      if (dlPtr == NULL) {
          break;
      }

      /*
       * Find the first DLine structure that's past the end of the range.
       */

      if (!TkBTreeNextTag(&search)) {
          endIndexPtr = index2Ptr;
      } else {
          curIndexPtr = &search.curIndex;
          endIndexPtr = curIndexPtr;
      }
      endPtr = FindDLine(dlPtr, endIndexPtr);
      if ((endPtr != NULL) && (endPtr->index.linePtr == endIndexPtr->linePtr)
            && (endPtr->index.byteIndex < endIndexPtr->byteIndex)) {
          endPtr = endPtr->nextPtr;
      }

      /*
       * Delete all of the display lines in the range, so that they'll
       * be re-layed out and redrawn.
       */

      FreeDLines(textPtr, dlPtr, endPtr, 1);
      dlPtr = endPtr;

      /*
       * Find the first text line in the next range.
       */

      if (!TkBTreeNextTag(&search)) {
          break;
      }
    }
}

/*
 *----------------------------------------------------------------------
 *
 * TkTextRelayoutWindow --
 *
 *    This procedure is called when something has happened that
 *    invalidates the whole layout of characters on the screen, such
 *    as a change in a configuration option for the overall text
 *    widget or a change in the window size.  It causes all display
 *    information to be recomputed and the window to be redrawn.
 *
 * Results:
 *    None.
 *
 * Side effects:
 *    All the display information will be recomputed for the window
 *    and the window will be redrawn.
 *
 *----------------------------------------------------------------------
 */

void
TkTextRelayoutWindow(textPtr)
    TkText *textPtr;          /* Widget record for text widget. */
{
    TextDInfo *dInfoPtr = textPtr->dInfoPtr;
    GC new;
    XGCValues gcValues;

    /*
     * Schedule the window redisplay.  See TkTextChanged for the
     * reason why this has to be done before any calls to FreeDLines.
     */

    if (!(dInfoPtr->flags & REDRAW_PENDING)) {
      Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr);
    }
    dInfoPtr->flags |= REDRAW_PENDING|REDRAW_BORDERS|DINFO_OUT_OF_DATE
          |REPICK_NEEDED;

    /*
     * (Re-)create the graphics context for drawing the traversal
     * highlight.
     */

    gcValues.graphics_exposures = False;
    new = Tk_GetGC(textPtr->tkwin, GCGraphicsExposures, &gcValues);
    if (dInfoPtr->copyGC != None) {
      Tk_FreeGC(textPtr->display, dInfoPtr->copyGC);
    }
    dInfoPtr->copyGC = new;

    /*
     * Throw away all the current layout information.
     */

    FreeDLines(textPtr, dInfoPtr->dLinePtr, (DLine *) NULL, 1);
    dInfoPtr->dLinePtr = NULL;

    /*
     * Recompute some overall things for the layout.  Even if the
     * window gets very small, pretend that there's at least one
     * pixel of drawing space in it.
     */

    if (textPtr->highlightWidth < 0) {
      textPtr->highlightWidth = 0;
    }
    dInfoPtr->x = textPtr->highlightWidth + textPtr->borderWidth
          + textPtr->padX;
    dInfoPtr->y = textPtr->highlightWidth + textPtr->borderWidth
          + textPtr->padY;
    dInfoPtr->maxX = Tk_Width(textPtr->tkwin) - textPtr->highlightWidth
          - textPtr->borderWidth - textPtr->padX;
    if (dInfoPtr->maxX <= dInfoPtr->x) {
      dInfoPtr->maxX = dInfoPtr->x + 1;
    }
    dInfoPtr->maxY = Tk_Height(textPtr->tkwin) - textPtr->highlightWidth
          - textPtr->borderWidth - textPtr->padY;
    if (dInfoPtr->maxY <= dInfoPtr->y) {
      dInfoPtr->maxY = dInfoPtr->y + 1;
    }
    dInfoPtr->topOfEof = dInfoPtr->maxY;

    /*
     * If the upper-left character isn't the first in a line, recompute
     * it.  This is necessary because a change in the window's size
     * or options could change the way lines wrap.
     */

    if (textPtr->topIndex.byteIndex != 0) {
      MeasureUp(textPtr, &textPtr->topIndex, 0, &textPtr->topIndex);
    }

    /*
     * Invalidate cached scrollbar positions, so that scrollbars
     * sliders will be udpated.
     */

    dInfoPtr->xScrollFirst = dInfoPtr->xScrollLast = -1;
    dInfoPtr->yScrollFirst = dInfoPtr->yScrollLast = -1;
}

/*
 *----------------------------------------------------------------------
 *
 * TkTextSetYView --
 *
 *    This procedure is called to specify what lines are to be
 *    displayed in a text widget.
 *
 * Results:
 *    None.
 *
 * Side effects:
 *    The display will (eventually) be updated so that the position
 *    given by "indexPtr" is visible on the screen at the position
 *    determined by "pickPlace".
 *
 *----------------------------------------------------------------------
 */

void
TkTextSetYView(textPtr, indexPtr, pickPlace)
    TkText *textPtr;          /* Widget record for text widget. */
    TkTextIndex *indexPtr;    /* Position that is to appear somewhere
                         * in the view. */
    int pickPlace;            /* 0 means topLine must appear at top of
                         * screen.  1 means we get to pick where it
                         * appears:  minimize screen motion or else
                         * display line at center of screen. */
{
    TextDInfo *dInfoPtr = textPtr->dInfoPtr;
    register DLine *dlPtr;
    int bottomY, close, lineIndex;
    TkTextIndex tmpIndex, rounded;
    Tk_FontMetrics fm;

    /*
     * If the specified position is the extra line at the end of the
     * text, round it back to the last real line.
     */

    lineIndex = TkBTreeLineIndex(indexPtr->linePtr);
    if (lineIndex == TkBTreeNumLines(indexPtr->tree)) {
      TkTextIndexBackChars(indexPtr, 1, &rounded);
      indexPtr = &rounded;
    }

    if (!pickPlace) {
      /*
       * The specified position must go at the top of the screen.
       * Just leave all the DLine's alone: we may be able to reuse
       * some of the information that's currently on the screen
       * without redisplaying it all.
       */

      if (indexPtr->byteIndex == 0) {
          textPtr->topIndex = *indexPtr;
      } else {
          MeasureUp(textPtr, indexPtr, 0, &textPtr->topIndex);
      }
      goto scheduleUpdate;
    }

    /*
     * We have to pick where to display the index.  First, bring
     * the display information up to date and see if the index will be
     * completely visible in the current screen configuration.  If so
     * then there's nothing to do.
     */

    if (dInfoPtr->flags & DINFO_OUT_OF_DATE) {
      UpdateDisplayInfo(textPtr);
    }
    dlPtr = FindDLine(dInfoPtr->dLinePtr, indexPtr);
    if (dlPtr != NULL) {
      if ((dlPtr->y + dlPtr->height) > dInfoPtr->maxY) {
          /*
           * Part of the line hangs off the bottom of the screen;
           * pretend the whole line is off-screen.
           */

          dlPtr = NULL;
      } else if ((dlPtr->index.linePtr == indexPtr->linePtr)
            && (dlPtr->index.byteIndex <= indexPtr->byteIndex)) {
          return;
      }
    }

    /*
     * The desired line isn't already on-screen.  Figure out what
     * it means to be "close" to the top or bottom of the screen.
     * Close means within 1/3 of the screen height or within three
     * lines, whichever is greater.  Add one extra line also, to
     * account for the way MeasureUp rounds.
     */

    Tk_GetFontMetrics(textPtr->tkfont, &fm);
    bottomY = (dInfoPtr->y + dInfoPtr->maxY + fm.linespace)/2;
    close = (dInfoPtr->maxY - dInfoPtr->y)/3;
    if (close < 3*fm.linespace) {
      close = 3*fm.linespace;
    }
    close += fm.linespace;
    if (dlPtr != NULL) {
      /*
       * The desired line is above the top of screen.  If it is
       * "close" to the top of the window then make it the top
       * line on the screen.
       */

      MeasureUp(textPtr, &textPtr->topIndex, close, &tmpIndex);
      if (TkTextIndexCmp(&tmpIndex, indexPtr) <= 0) {
          MeasureUp(textPtr, indexPtr, 0, &textPtr->topIndex);
          goto scheduleUpdate;
      }
    } else {
      /*
       * The desired line is below the bottom of the screen.  If it is
       * "close" to the bottom of the screen then position it at the
       * bottom of the screen.
       */

      MeasureUp(textPtr, indexPtr, close, &tmpIndex);
      if (FindDLine(dInfoPtr->dLinePtr, &tmpIndex) != NULL) {
          bottomY = dInfoPtr->maxY - dInfoPtr->y;
      }
    }

    /*
     * Our job now is to arrange the display so that indexPtr appears
     * as low on the screen as possible but with its bottom no lower
     * than bottomY.  BottomY is the bottom of the window if the
     * desired line is just below the current screen, otherwise it
     * is a half-line lower than the center of the window.
     */

    MeasureUp(textPtr, indexPtr, bottomY, &textPtr->topIndex);

    scheduleUpdate:
    if (!(dInfoPtr->flags & REDRAW_PENDING)) {
      Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr);
    }
    dInfoPtr->flags |= REDRAW_PENDING|DINFO_OUT_OF_DATE|REPICK_NEEDED;
}

/*
 *--------------------------------------------------------------
 *
 * MeasureUp --
 *
 *    Given one index, find the index of the first character
 *    on the highest display line that would be displayed no more
 *    than "distance" pixels above the given index.
 *
 * Results:
 *    *dstPtr is filled in with the index of the first character
 *    on a display line.  The display line is found by measuring
 *    up "distance" pixels above the pixel just below an imaginary
 *    display line that contains srcPtr.  If the display line
 *    that covers this coordinate actually extends above the 
 *    coordinate, then return the index of the next lower line
 *    instead (i.e. the returned index will be completely visible
 *    at or below the given y-coordinate).
 *
 * Side effects:
 *    None.
 *
 *--------------------------------------------------------------
 */

static void
MeasureUp(textPtr, srcPtr, distance, dstPtr)
    TkText *textPtr;          /* Text widget in which to measure. */
    TkTextIndex *srcPtr;      /* Index of character from which to start
                         * measuring. */
    int distance;       /* Vertical distance in pixels measured
                         * from the pixel just below the lowest
                         * one in srcPtr's line. */
    TkTextIndex *dstPtr;      /* Index to fill in with result. */
{
    int lineNum;        /* Number of current line. */
    int bytesToCount;         /* Maximum number of bytes to measure in
                         * current line. */
    TkTextIndex bestIndex;    /* Best candidate seen so far for result. */
    TkTextIndex index;
    DLine *dlPtr, *lowestPtr;
    int noBestYet;            /* 1 means bestIndex hasn't been set. */

    noBestYet = 1;
    bytesToCount = srcPtr->byteIndex + 1;
    index.tree = srcPtr->tree;
    for (lineNum = TkBTreeLineIndex(srcPtr->linePtr); lineNum >= 0;
          lineNum--) {
      /*
       * Layout an entire text line (potentially > 1 display line).
       * For the first line, which contains srcPtr, only layout the
       * part up through srcPtr (bytesToCount is non-infinite to
       * accomplish this).  Make a list of all the display lines
       * in backwards order (the lowest DLine on the screen is first
       * in the list).
       */

      index.linePtr = TkBTreeFindLine(srcPtr->tree, lineNum);
      index.byteIndex = 0;
      lowestPtr = NULL;
      do {
          dlPtr = LayoutDLine(textPtr, &index);
          dlPtr->nextPtr = lowestPtr;
          lowestPtr = dlPtr;
          TkTextIndexForwBytes(&index, dlPtr->byteCount, &index);
          bytesToCount -= dlPtr->byteCount;
      } while ((bytesToCount > 0) && (index.linePtr == dlPtr->index.linePtr));

      /*
       * Scan through the display lines to see if we've covered enough
       * vertical distance.  If so, save the starting index for the
       * line at the desired location.
       */

      for (dlPtr = lowestPtr; dlPtr != NULL; dlPtr = dlPtr->nextPtr) {
          distance -= dlPtr->height;
          if (distance < 0) {
            *dstPtr = (noBestYet) ? dlPtr->index : bestIndex;
            break;
          }
          bestIndex = dlPtr->index;
          noBestYet = 0;
      }

      /*
       * Discard the display lines, then either return or prepare
       * for the next display line to lay out.
       */

      FreeDLines(textPtr, lowestPtr, (DLine *) NULL, 0);
      if (distance < 0) {
          return;
      }
      bytesToCount = INT_MAX;       /* Consider all chars. in next line. */
    }

    /*
     * Ran off the beginning of the text.  Return the first character
     * in the text.
     */

    TkTextMakeByteIndex(textPtr->tree, 0, 0, dstPtr);
}

/*
 *--------------------------------------------------------------
 *
 * TkTextSeeCmd --
 *
 *    This procedure is invoked to process the "see" option for
 *    the widget command for text widgets. See the user documentation
 *    for details on what it does.
 *
 * Results:
 *    A standard Tcl result.
 *
 * Side effects:
 *    See the user documentation.
 *
 *--------------------------------------------------------------
 */

int
TkTextSeeCmd(textPtr, interp, argc, argv)
    TkText *textPtr;          /* Information about text widget. */
    Tcl_Interp *interp;       /* Current interpreter. */
    int argc;                 /* Number of arguments. */
    CONST char **argv;        /* Argument strings.  Someone else has already
                         * parsed this command enough to know that
                         * argv[1] is "see". */
{
    TextDInfo *dInfoPtr = textPtr->dInfoPtr;
    TkTextIndex index;
    int x, y, width, height, lineWidth, byteCount, oneThird, delta;
    DLine *dlPtr;
    TkTextDispChunk *chunkPtr;

    if (argc != 3) {
      Tcl_AppendResult(interp, "wrong # args: should be \"",
            argv[0], " see index\"", (char *) NULL);
      return TCL_ERROR;
    }
    if (TkTextGetIndex(interp, textPtr, argv[2], &index) != TCL_OK) {
      return TCL_ERROR;
    }

    /*
     * If the specified position is the extra line at the end of the
     * text, round it back to the last real line.
     */

    if (TkBTreeLineIndex(index.linePtr) == TkBTreeNumLines(index.tree)) {
      TkTextIndexBackChars(&index, 1, &index);
    }

    /*
     * First get the desired position into the vertical range of the window.
     */

    TkTextSetYView(textPtr, &index, 1);

    /*
     * Now make sure that the character is in view horizontally.
     */

    if (dInfoPtr->flags & DINFO_OUT_OF_DATE) {
      UpdateDisplayInfo(textPtr);
    }
    lineWidth = dInfoPtr->maxX - dInfoPtr->x;
    if (dInfoPtr->maxLength < lineWidth) {
      return TCL_OK;
    }

    /*
     * Find the chunk that contains the desired index.
     * dlPtr may be NULL if the widget is not mapped. [Bug #641778]
     */

    dlPtr = FindDLine(dInfoPtr->dLinePtr, &index);
    if (dlPtr == NULL) {
      return TCL_OK;
    }

    byteCount = index.byteIndex - dlPtr->index.byteIndex;
    for (chunkPtr = dlPtr->chunkPtr; chunkPtr != NULL ;
       chunkPtr = chunkPtr->nextPtr) {
      if (byteCount < chunkPtr->numBytes) {
          break;
      }
      byteCount -= chunkPtr->numBytes;
    }

    /*
     * Call a chunk-specific procedure to find the horizontal range of
     * the character within the chunk.
     * chunkPtr is NULL if trying to see in elided region.
     */

    if (chunkPtr != NULL) {
      (*chunkPtr->bboxProc)(chunkPtr, byteCount,
            dlPtr->y + dlPtr->spaceAbove,
            dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow,
            dlPtr->baseline - dlPtr->spaceAbove, &x, &y, &width,
            &height);
      delta = x - dInfoPtr->curPixelOffset;
      oneThird = lineWidth/3;
      if (delta < 0) {
          if (delta < -oneThird) {
            dInfoPtr->newByteOffset = (x - lineWidth/2)
                / textPtr->charWidth;
          } else {
            dInfoPtr->newByteOffset -= ((-delta) + textPtr->charWidth - 1)
                / textPtr->charWidth;
          }
      } else {
          delta -= (lineWidth - width);
          if (delta > 0) {
            if (delta > oneThird) {
                dInfoPtr->newByteOffset = (x - lineWidth/2)
                  / textPtr->charWidth;
            } else {
                dInfoPtr->newByteOffset += (delta + textPtr->charWidth - 1)
                  / textPtr->charWidth;
            }
          } else {
            return TCL_OK;
          }
      }
    }
    dInfoPtr->flags |= DINFO_OUT_OF_DATE;
    if (!(dInfoPtr->flags & REDRAW_PENDING)) {
      dInfoPtr->flags |= REDRAW_PENDING;
      Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr);
    }
    return TCL_OK;
}

/*
 *--------------------------------------------------------------
 *
 * TkTextXviewCmd --
 *
 *    This procedure is invoked to process the "xview" option for
 *    the widget command for text widgets. See the user documentation
 *    for details on what it does.
 *
 * Results:
 *    A standard Tcl result.
 *
 * Side effects:
 *    See the user documentation.
 *
 *--------------------------------------------------------------
 */

int
TkTextXviewCmd(textPtr, interp, argc, argv)
    TkText *textPtr;          /* Information about text widget. */
    Tcl_Interp *interp;       /* Current interpreter. */
    int argc;                 /* Number of arguments. */
    CONST char **argv;        /* Argument strings.  Someone else has already
                         * parsed this command enough to know that
                         * argv[1] is "xview". */
{
    TextDInfo *dInfoPtr = textPtr->dInfoPtr;
    int type, charsPerPage, count, newOffset;
    double fraction;

    if (dInfoPtr->flags & DINFO_OUT_OF_DATE) {
      UpdateDisplayInfo(textPtr);
    }

    if (argc == 2) {
      GetXView(interp, textPtr, 0);
      return TCL_OK;
    }

    newOffset = dInfoPtr->newByteOffset;
    type = Tk_GetScrollInfo(interp, argc, argv, &fraction, &count);
    switch (type) {
      case TK_SCROLL_ERROR:
          return TCL_ERROR;
      case TK_SCROLL_MOVETO:
          if (fraction > 1.0) {
            fraction = 1.0;
          }
          if (fraction < 0) {
            fraction = 0;
          }
          newOffset = (int) (((fraction * dInfoPtr->maxLength) / textPtr->charWidth)
                + 0.5);
          break;
      case TK_SCROLL_PAGES:
          charsPerPage = ((dInfoPtr->maxX - dInfoPtr->x) / textPtr->charWidth)
                - 2;
          if (charsPerPage < 1) {
            charsPerPage = 1;
          }
          newOffset += charsPerPage * count;
          break;
      case TK_SCROLL_UNITS:
          newOffset += count;
          break;
    }

    dInfoPtr->newByteOffset = newOffset;
    dInfoPtr->flags |= DINFO_OUT_OF_DATE;
    if (!(dInfoPtr->flags & REDRAW_PENDING)) {
      dInfoPtr->flags |= REDRAW_PENDING;
      Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr);
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * ScrollByLines --
 *
 *    This procedure is called to scroll a text widget up or down
 *    by a given number of lines.
 *
 * Results:
 *    None.
 *
 * Side effects:
 *    The view in textPtr's window changes to reflect the value
 *    of "offset".
 *
 *----------------------------------------------------------------------
 */

static void
ScrollByLines(textPtr, offset)
    TkText *textPtr;          /* Widget to scroll. */
    int offset;               /* Amount by which to scroll, in *screen*
                         * lines.  Positive means that information
                         * later in text becomes visible, negative
                         * means that information earlier in the
                         * text becomes visible. */
{
    int i, bytesToCount, lineNum;
    TkTextIndex new, index;
    TkTextLine *lastLinePtr;
    TextDInfo *dInfoPtr = textPtr->dInfoPtr;
    DLine *dlPtr, *lowestPtr;

    if (offset < 0) {
      /*
       * Must scroll up (to show earlier information in the text).
       * The code below is similar to that in MeasureUp, except that
       * it counts lines instead of pixels.
       */

      bytesToCount = textPtr->topIndex.byteIndex + 1;
      index.tree = textPtr->tree;
      offset--;               /* Skip line containing topIndex. */
      for (lineNum = TkBTreeLineIndex(textPtr->topIndex.linePtr);
            lineNum >= 0; lineNum--) {
          index.linePtr = TkBTreeFindLine(textPtr->tree, lineNum);
          index.byteIndex = 0;
          lowestPtr = NULL;
          do {
            dlPtr = LayoutDLine(textPtr, &index);
            dlPtr->nextPtr = lowestPtr;
            lowestPtr = dlPtr;
            TkTextIndexForwBytes(&index, dlPtr->byteCount, &index);
            bytesToCount -= dlPtr->byteCount;
          } while ((bytesToCount > 0)
                && (index.linePtr == dlPtr->index.linePtr));

          for (dlPtr = lowestPtr; dlPtr != NULL; dlPtr = dlPtr->nextPtr) {
            offset++;
            if (offset == 0) {
                textPtr->topIndex = dlPtr->index;
                break;
            }
          }

          /*
           * Discard the display lines, then either return or prepare
           * for the next display line to lay out.
           */
    
          FreeDLines(textPtr, lowestPtr, (DLine *) NULL, 0);
          if (offset >= 0) {
            goto scheduleUpdate;
          }
          bytesToCount = INT_MAX;
      }
    
      /*
       * Ran off the beginning of the text.  Return the first character
       * in the text.
       */

      TkTextMakeByteIndex(textPtr->tree, 0, 0, &textPtr->topIndex);
    } else {
      /*
       * Scrolling down, to show later information in the text.
       * Just count lines from the current top of the window.
       */

      lastLinePtr = TkBTreeFindLine(textPtr->tree,
            TkBTreeNumLines(textPtr->tree));
      for (i = 0; i < offset; i++) {
          dlPtr = LayoutDLine(textPtr, &textPtr->topIndex);
          if (dlPtr->length == 0 && dlPtr->height == 0) offset++;
          dlPtr->nextPtr = NULL;
          TkTextIndexForwBytes(&textPtr->topIndex, dlPtr->byteCount, &new);
          FreeDLines(textPtr, dlPtr, (DLine *) NULL, 0);
          if (new.linePtr == lastLinePtr) {
            break;
          }
          textPtr->topIndex = new;
      }
    }

    scheduleUpdate:
    if (!(dInfoPtr->flags & REDRAW_PENDING)) {
      Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr);
    }
    dInfoPtr->flags |= REDRAW_PENDING|DINFO_OUT_OF_DATE|REPICK_NEEDED;
}

/*
 *--------------------------------------------------------------
 *
 * TkTextYviewCmd --
 *
 *    This procedure is invoked to process the "yview" option for
 *    the widget command for text widgets. See the user documentation
 *    for details on what it does.
 *
 * Results:
 *    A standard Tcl result.
 *
 * Side effects:
 *    See the user documentation.
 *
 *--------------------------------------------------------------
 */

int
TkTextYviewCmd(textPtr, interp, argc, argv)
    TkText *textPtr;          /* Information about text widget. */
    Tcl_Interp *interp;       /* Current interpreter. */
    int argc;                 /* Number of arguments. */
    CONST char **argv;        /* Argument strings.  Someone else has already
                         * parsed this command enough to know that
                         * argv[1] is "yview". */
{
    TextDInfo *dInfoPtr = textPtr->dInfoPtr;
    int pickPlace, lineNum, type, bytesInLine;
    Tk_FontMetrics fm;
    int pixels, count;
    size_t switchLength;
    double fraction;
    TkTextIndex index, new;
    TkTextLine *lastLinePtr;
    DLine *dlPtr;

    if (dInfoPtr->flags & DINFO_OUT_OF_DATE) {
      UpdateDisplayInfo(textPtr);
    }

    if (argc == 2) {
      GetYView(interp, textPtr, 0);
      return TCL_OK;
    }

    /*
     * Next, handle the old syntax: "pathName yview ?-pickplace? where"
     */

    pickPlace = 0;
    if (argv[2][0] == '-') {
      switchLength = strlen(argv[2]);
      if ((switchLength >= 2)
            && (strncmp(argv[2], "-pickplace", switchLength) == 0)) {
          pickPlace = 1;
          if (argc != 4) {
            Tcl_AppendResult(interp, "wrong # args: should be \"",
                  argv[0], " yview -pickplace lineNum|index\"",
                  (char *) NULL);
            return TCL_ERROR;
          }
      }
    }
    if ((argc == 3) || pickPlace) {
      if (Tcl_GetInt(interp, argv[2+pickPlace], &lineNum) == TCL_OK) {
          TkTextMakeByteIndex(textPtr->tree, lineNum, 0, &index);
          TkTextSetYView(textPtr, &index, 0);
          return TCL_OK;
      }
    
      /*
       * The argument must be a regular text index.
       */
    
      Tcl_ResetResult(interp);
      if (TkTextGetIndex(interp, textPtr, argv[2+pickPlace],
            &index) != TCL_OK) {
          return TCL_ERROR;
      }
      TkTextSetYView(textPtr, &index, pickPlace);
      return TCL_OK;
    }

    /*
     * New syntax: dispatch based on argv[2].
     */

    type = Tk_GetScrollInfo(interp, argc, argv, &fraction, &count);
    switch (type) {
      case TK_SCROLL_ERROR:
          return TCL_ERROR;
      case TK_SCROLL_MOVETO:
          if (fraction > 1.0) {
            fraction = 1.0;
          }
          if (fraction < 0) {
            fraction = 0;
          }
          fraction *= TkBTreeNumLines(textPtr->tree);
          lineNum = (int) fraction;
          TkTextMakeByteIndex(textPtr->tree, lineNum, 0, &index);
          bytesInLine = TkBTreeBytesInLine(index.linePtr);
          index.byteIndex = (int)((bytesInLine * (fraction-lineNum)) + 0.5);
          if (index.byteIndex >= bytesInLine) {
            TkTextMakeByteIndex(textPtr->tree, lineNum + 1, 0, &index);
          }
          TkTextSetYView(textPtr, &index, 0);
          break;
      case TK_SCROLL_PAGES:
          /*
           * Scroll up or down by screenfuls.  Actually, use the
           * window height minus two lines, so that there's some
           * overlap between adjacent pages.
           */

          Tk_GetFontMetrics(textPtr->tkfont, &fm);
          if (count < 0) {
            pixels = (dInfoPtr->maxY - 2*fm.linespace - dInfoPtr->y)*(-count)
                  + fm.linespace;
            MeasureUp(textPtr, &textPtr->topIndex, pixels, &new);
            if (TkTextIndexCmp(&textPtr->topIndex, &new) == 0) {
                /*
                 * A page of scrolling ended up being less than one line.
                 * Scroll one line anyway.
                 */

                count = -1;
                goto scrollByLines;
            }
            textPtr->topIndex = new;
          } else {
            /*
             * Scrolling down by pages.  Layout lines starting at the
             * top index and count through the desired vertical distance.
             */

            pixels = (dInfoPtr->maxY - 2*fm.linespace - dInfoPtr->y)*count;
            lastLinePtr = TkBTreeFindLine(textPtr->tree,
                  TkBTreeNumLines(textPtr->tree));
            do {
                dlPtr = LayoutDLine(textPtr, &textPtr->topIndex);
                dlPtr->nextPtr = NULL;
                TkTextIndexForwBytes(&textPtr->topIndex, dlPtr->byteCount,
                      &new);
                pixels -= dlPtr->height;
                FreeDLines(textPtr, dlPtr, (DLine *) NULL, 0);
                if (new.linePtr == lastLinePtr) {
                  break;
                }
                textPtr->topIndex = new;
            } while (pixels > 0);
          }
          if (!(dInfoPtr->flags & REDRAW_PENDING)) {
            Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr);
          }
          dInfoPtr->flags |= REDRAW_PENDING|DINFO_OUT_OF_DATE|REPICK_NEEDED;
          break;
      case TK_SCROLL_UNITS:
          scrollByLines:
          ScrollByLines(textPtr, count);
          break;
    }
    return TCL_OK;
}

/*
 *--------------------------------------------------------------
 *
 * TkTextScanCmd --
 *
 *    This procedure is invoked to process the "scan" option for
 *    the widget command for text widgets. See the user documentation
 *    for details on what it does.
 *
 * Results:
 *    A standard Tcl result.
 *
 * Side effects:
 *    See the user documentation.
 *
 *--------------------------------------------------------------
 */

int
TkTextScanCmd(textPtr, interp, argc, argv)
    register TkText *textPtr; /* Information about text widget. */
    Tcl_Interp *interp;       /* Current interpreter. */
    int argc;                 /* Number of arguments. */
    CONST char **argv;        /* Argument strings.  Someone else has already
                         * parsed this command enough to know that
                         * argv[1] is "scan". */
{
    TextDInfo *dInfoPtr = textPtr->dInfoPtr;
    TkTextIndex index;
    int c, x, y, totalScroll, newByte, maxByte, gain=10;
    Tk_FontMetrics fm;
    size_t length;

    if ((argc != 5) && (argc != 6)) {
      Tcl_AppendResult(interp, "wrong # args: should be \"",
            argv[0], " scan mark x y\" or \"",
            argv[0], " scan dragto x y ?gain?\"", (char *) NULL);
      return TCL_ERROR;
    }
    if (Tcl_GetInt(interp, argv[3], &x) != TCL_OK) {
      return TCL_ERROR;
    }
    if (Tcl_GetInt(interp, argv[4], &y) != TCL_OK) {
      return TCL_ERROR;
    }
    if ((argc == 6) && (Tcl_GetInt(interp, argv[5], &gain) != TCL_OK))
      return TCL_ERROR;
    c = argv[2][0];
    length = strlen(argv[2]);
    if ((c == 'd') && (strncmp(argv[2], "dragto", length) == 0)) {
      /*
       * Amplify the difference between the current position and the
       * mark position to compute how much the view should shift, then
       * update the mark position to correspond to the new view.  If we
       * run off the edge of the text, reset the mark point so that the
       * current position continues to correspond to the edge of the
       * window.  This means that the picture will start dragging as
       * soon as the mouse reverses direction (without this reset, might
       * have to slide mouse a long ways back before the picture starts
       * moving again).
       */

      newByte = dInfoPtr->scanMarkIndex + (gain*(dInfoPtr->scanMarkX - x))
            / (textPtr->charWidth);
      maxByte = 1 + (dInfoPtr->maxLength - (dInfoPtr->maxX - dInfoPtr->x)
            + textPtr->charWidth - 1)/textPtr->charWidth;
      if (newByte < 0) {
          newByte = 0;
          dInfoPtr->scanMarkIndex = 0;
          dInfoPtr->scanMarkX = x;
      } else if (newByte > maxByte) {
          newByte = maxByte;
          dInfoPtr->scanMarkIndex = maxByte;
          dInfoPtr->scanMarkX = x;
      }
      dInfoPtr->newByteOffset = newByte;

      Tk_GetFontMetrics(textPtr->tkfont, &fm);
      totalScroll = (gain*(dInfoPtr->scanMarkY - y)) / fm.linespace;
      if (totalScroll != dInfoPtr->scanTotalScroll) {
          index = textPtr->topIndex;
          ScrollByLines(textPtr, totalScroll-dInfoPtr->scanTotalScroll);
          dInfoPtr->scanTotalScroll = totalScroll;
          if ((index.linePtr == textPtr->topIndex.linePtr) &&
                (index.byteIndex == textPtr->topIndex.byteIndex)) {
            dInfoPtr->scanTotalScroll = 0;
            dInfoPtr->scanMarkY = y;
          }
      }
    } else if ((c == 'm') && (strncmp(argv[2], "mark", length) == 0)) {
      dInfoPtr->scanMarkIndex = dInfoPtr->newByteOffset;
      dInfoPtr->scanMarkX = x;
      dInfoPtr->scanTotalScroll = 0;
      dInfoPtr->scanMarkY = y;
    } else {
      Tcl_AppendResult(interp, "bad scan option \"", argv[2],
            "\": must be mark or dragto", (char *) NULL);
      return TCL_ERROR;
    }
    dInfoPtr->flags |= DINFO_OUT_OF_DATE;
    if (!(dInfoPtr->flags & REDRAW_PENDING)) {
      dInfoPtr->flags |= REDRAW_PENDING;
      Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr);
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * GetXView --
 *
 *    This procedure computes the fractions that indicate what's
 *    visible in a text window and, optionally, evaluates a
 *    Tcl script to report them to the text's associated scrollbar.
 *
 * Results:
 *    If report is zero, then the interp's result is filled in with
 *    two real numbers separated by a space, giving the position of
 *    the left and right edges of the window as fractions from 0 to
 *    1, where 0 means the left edge of the text and 1 means the right
 *    edge.  If report is non-zero, then the interp's result isn't modified
 *    directly, but instead a script is evaluated in interp to report
 *    the new horizontal scroll position to the scrollbar (if the scroll
 *    position hasn't changed then no script is invoked).
 *
 * Side effects:
 *    None.
 *
 *----------------------------------------------------------------------
 */

static void
GetXView(interp, textPtr, report)
    Tcl_Interp *interp;             /* If "report" is FALSE, string
                               * describing visible range gets
                               * stored in the interp's result. */
    TkText *textPtr;                /* Information about text widget. */
    int report;                     /* Non-zero means report info to
                               * scrollbar if it has changed. */
{
    TextDInfo *dInfoPtr = textPtr->dInfoPtr;
    char buffer[TCL_DOUBLE_SPACE * 2 + 1];
    double first, last;
    int code;

    if (dInfoPtr->maxLength > 0) {
      first = ((double) dInfoPtr->curPixelOffset)
            / dInfoPtr->maxLength;
      last = first + ((double) (dInfoPtr->maxX - dInfoPtr->x))
            / dInfoPtr->maxLength;
      if (last > 1.0) {
          last = 1.0;
      }
    } else {
      first = 0;
      last = 1.0;
    }
    if (!report) {
      sprintf(buffer, "%g %g", first, last);
      Tcl_SetResult(interp, buffer, TCL_VOLATILE);
      return;
    }
    if (FP_EQUAL_SCALE(first, dInfoPtr->xScrollFirst, dInfoPtr->maxLength) &&
      FP_EQUAL_SCALE(last,  dInfoPtr->xScrollLast,  dInfoPtr->maxLength)) {
      return;
    }
    dInfoPtr->xScrollFirst = first;
    dInfoPtr->xScrollLast = last;
    sprintf(buffer, " %g %g", first, last);
    code = Tcl_VarEval(interp, textPtr->xScrollCmd,
          buffer, (char *) NULL);
    if (code != TCL_OK) {
      Tcl_AddErrorInfo(interp,
            "\n    (horizontal scrolling command executed by text)");
      Tcl_BackgroundError(interp);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * GetYView --
 *
 *    This procedure computes the fractions that indicate what's
 *    visible in a text window and, optionally, evaluates a
 *    Tcl script to report them to the text's associated scrollbar.
 *
 * Results:
 *    If report is zero, then the interp's result is filled in with
 *    two real numbers separated by a space, giving the position of
 *    the top and bottom of the window as fractions from 0 to 1, where
 *    0 means the beginning of the text and 1 means the end.  If
 *    report is non-zero, then the interp's result isn't modified directly,
 *    but a script is evaluated in interp to report the new scroll
 *    position to the scrollbar (if the scroll position hasn't changed
 *    then no script is invoked).
 *
 * Side effects:
 *    None.
 *
 *----------------------------------------------------------------------
 */

static void
GetYView(interp, textPtr, report)
    Tcl_Interp *interp;             /* If "report" is FALSE, string
                               * describing visible range gets
                               * stored in the interp's result. */
    TkText *textPtr;                /* Information about text widget. */
    int report;                     /* Non-zero means report info to
                               * scrollbar if it has changed. */
{
    TextDInfo *dInfoPtr = textPtr->dInfoPtr;
    char buffer[TCL_DOUBLE_SPACE * 2 + 1];
    double first, last;
    DLine *dlPtr;
    int totalLines, code, count;

    dlPtr = dInfoPtr->dLinePtr;
    totalLines = TkBTreeNumLines(textPtr->tree);
    first = (double) TkBTreeLineIndex(dlPtr->index.linePtr)
          + (double) dlPtr->index.byteIndex
                / TkBTreeBytesInLine(dlPtr->index.linePtr);
    first /= totalLines;
    while (1) {
      if ((dlPtr->y + dlPtr->height) > dInfoPtr->maxY) {
          /*
           * The last line is only partially visible, so don't
           * count its characters in what's visible.
           */
          count = 0;
          break;
      }
      if (dlPtr->nextPtr == NULL) {
          count = dlPtr->byteCount;
          break;
      }
      dlPtr = dlPtr->nextPtr;
    }
    last = ((double) TkBTreeLineIndex(dlPtr->index.linePtr))
          + ((double) (dlPtr->index.byteIndex + count))
                / (TkBTreeBytesInLine(dlPtr->index.linePtr));
    last /= totalLines;
    if (!report) {
      sprintf(buffer, "%g %g", first, last);
      Tcl_SetResult(interp, buffer, TCL_VOLATILE);
      return;
    }
    if (FP_EQUAL_SCALE(first, dInfoPtr->yScrollFirst, totalLines) &&
      FP_EQUAL_SCALE(last,  dInfoPtr->yScrollLast,  totalLines)) {
      return;
    }
    dInfoPtr->yScrollFirst = first;
    dInfoPtr->yScrollLast = last;
    sprintf(buffer, " %g %g", first, last);
    code = Tcl_VarEval(interp, textPtr->yScrollCmd, buffer, (char *) NULL);
    if (code != TCL_OK) {
      Tcl_AddErrorInfo(interp,
            "\n    (vertical scrolling command executed by text)");
      Tcl_BackgroundError(interp);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * FindDLine --
 *
 *    This procedure is called to find the DLine corresponding to a
 *    given text index.
 *
 * Results:
 *    The return value is a pointer to the first DLine found in the
 *    list headed by dlPtr that displays information at or after the
 *    specified position.  If there is no such line in the list then
 *    NULL is returned.
 *
 * Side effects:
 *    None.
 *
 *----------------------------------------------------------------------
 */

static DLine *
FindDLine(dlPtr, indexPtr)
    register DLine *dlPtr;    /* Pointer to first in list of DLines
                         * to search. */
    TkTextIndex *indexPtr;    /* Index of desired character. */
{
    TkTextLine *linePtr;

    if (dlPtr == NULL) {
      return NULL;
    }
    if (TkBTreeLineIndex(indexPtr->linePtr)
          < TkBTreeLineIndex(dlPtr->index.linePtr)) {
      /*
       * The first display line is already past the desired line.
       */
      return dlPtr;
    }

    /*
     * Find the first display line that covers the desired text line.
     */

    linePtr = dlPtr->index.linePtr;
    while (linePtr != indexPtr->linePtr) {
      while (dlPtr->index.linePtr == linePtr) {
          dlPtr = dlPtr->nextPtr;
          if (dlPtr == NULL) {
            return NULL;
          }
      }
      linePtr = TkBTreeNextLine(linePtr);
      if (linePtr == NULL) {
          panic("FindDLine reached end of text");
      }
    }
    if (indexPtr->linePtr != dlPtr->index.linePtr) {
      return dlPtr;
    }

    /*
     * Now get to the right position within the text line.
     */

    while (indexPtr->byteIndex >= (dlPtr->index.byteIndex + dlPtr->byteCount)) {
      dlPtr = dlPtr->nextPtr;
      if ((dlPtr == NULL) || (dlPtr->index.linePtr != indexPtr->linePtr)) {
          break;
      }
    }
    return dlPtr;
}

/*
 *----------------------------------------------------------------------
 *
 * TkTextPixelIndex --
 *
 *    Given an (x,y) coordinate on the screen, find the location of
 *    the character closest to that location.
 *
 * Results:
 *    The index at *indexPtr is modified to refer to the character
 *    on the display that is closest to (x,y).
 *
 * Side effects:
 *    None.
 *
 *----------------------------------------------------------------------
 */

void
TkTextPixelIndex(textPtr, x, y, indexPtr)
    TkText *textPtr;          /* Widget record for text widget. */
    int x, y;                 /* Pixel coordinates of point in widget's
                         * window. */
    TkTextIndex *indexPtr;    /* This index gets filled in with the
                         * index of the character nearest to (x,y). */
{
    TextDInfo *dInfoPtr = textPtr->dInfoPtr;
    register DLine *dlPtr, *validdlPtr;
    register TkTextDispChunk *chunkPtr;

    /*
     * Make sure that all of the layout information about what's
     * displayed where on the screen is up-to-date.
     */

    if (dInfoPtr->flags & DINFO_OUT_OF_DATE) {
      UpdateDisplayInfo(textPtr);
    }

    /*
     * If the coordinates are above the top of the window, then adjust
     * them to refer to the upper-right corner of the window.  If they're
     * off to one side or the other, then adjust to the closest side.
     */

    if (y < dInfoPtr->y) {
      y = dInfoPtr->y;
      x = dInfoPtr->x;
    }
    if (x >= dInfoPtr->maxX) {
      x = dInfoPtr->maxX - 1;
    }
    if (x < dInfoPtr->x) {
      x = dInfoPtr->x;
    }

    /*
     * Find the display line containing the desired y-coordinate.
     */

    for (dlPtr = validdlPtr = dInfoPtr->dLinePtr; y >= (dlPtr->y + dlPtr->height);
          dlPtr = dlPtr->nextPtr) {
      if (dlPtr->chunkPtr !=NULL) validdlPtr = dlPtr;
      if (dlPtr->nextPtr == NULL) {
          /*
           * Y-coordinate is off the bottom of the displayed text.
           * Use the last character on the last line.
           */

          x = dInfoPtr->maxX - 1;
          break;
      }
    }
    if (dlPtr->chunkPtr == NULL) dlPtr = validdlPtr;


    /*
     * Scan through the line's chunks to find the one that contains
     * the desired x-coordinate.  Before doing this, translate the
     * x-coordinate from the coordinate system of the window to the
     * coordinate system of the line (to take account of x-scrolling).
     */

    *indexPtr = dlPtr->index;
    x = x - dInfoPtr->x + dInfoPtr->curPixelOffset;
    for (chunkPtr = dlPtr->chunkPtr; x >= (chunkPtr->x + chunkPtr->width);
          indexPtr->byteIndex += chunkPtr->numBytes,
          chunkPtr = chunkPtr->nextPtr) {
      if (chunkPtr->nextPtr == NULL) {
          indexPtr->byteIndex += chunkPtr->numBytes;
          TkTextIndexBackChars(indexPtr, 1, indexPtr);
          return;
      }
    }

    /*
     * If the chunk has more than one byte in it, ask it which
     * character is at the desired location.
     */

    if (chunkPtr->numBytes > 1) {
      indexPtr->byteIndex += (*chunkPtr->measureProc)(chunkPtr, x);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * TkTextCharBbox --
 *
 *    Given an index, find the bounding box of the screen area
 *    occupied by that character.
 *
 * Results:
 *    Zero is returned if the character is on the screen.  -1
 *    means the character isn't on the screen.  If the return value
 *    is 0, then the bounding box of the part of the character that's
 *    visible on the screen is returned to *xPtr, *yPtr, *widthPtr,
 *    and *heightPtr.
 *
 * Side effects:
 *    None.
 *
 *----------------------------------------------------------------------
 */

int
TkTextCharBbox(textPtr, indexPtr, xPtr, yPtr, widthPtr, heightPtr)
    TkText *textPtr;          /* Widget record for text widget. */
    TkTextIndex *indexPtr;    /* Index of character whose bounding
                         * box is desired. */
    int *xPtr, *yPtr;         /* Filled with character's upper-left
                         * coordinate. */
    int *widthPtr, *heightPtr;      /* Filled in with character's dimensions. */
{
    TextDInfo *dInfoPtr = textPtr->dInfoPtr;
    DLine *dlPtr;
    register TkTextDispChunk *chunkPtr;
    int byteIndex;

    /*
     * Make sure that all of the screen layout information is up to date.
     */

    if (dInfoPtr->flags & DINFO_OUT_OF_DATE) {
      UpdateDisplayInfo(textPtr);
    }

    /*
     * Find the display line containing the desired index.
     */

    dlPtr = FindDLine(dInfoPtr->dLinePtr, indexPtr);
    if ((dlPtr == NULL) || (TkTextIndexCmp(&dlPtr->index, indexPtr) > 0)) {
      return -1;
    }

    /*
     * Find the chunk within the line that contains the desired
     * index.
     */

    byteIndex = indexPtr->byteIndex - dlPtr->index.byteIndex;
    for (chunkPtr = dlPtr->chunkPtr; ; chunkPtr = chunkPtr->nextPtr) {
      if (chunkPtr == NULL) {
          return -1;
      }
      if (byteIndex < chunkPtr->numBytes) {
          break;
      }
      byteIndex -= chunkPtr->numBytes;
    }

    /*
     * Call a chunk-specific procedure to find the horizontal range of
     * the character within the chunk, then fill in the vertical range.
     * The x-coordinate returned by bboxProc is a coordinate within a
     * line, not a coordinate on the screen.  Translate it to reflect
     * horizontal scrolling.
     */

    (*chunkPtr->bboxProc)(chunkPtr, byteIndex, dlPtr->y + dlPtr->spaceAbove,
          dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow,
          dlPtr->baseline - dlPtr->spaceAbove, xPtr, yPtr, widthPtr,
          heightPtr);
    *xPtr = *xPtr + dInfoPtr->x - dInfoPtr->curPixelOffset;
    if ((byteIndex == (chunkPtr->numBytes - 1)) && (chunkPtr->nextPtr == NULL)) {
      /*
       * Last character in display line.  Give it all the space up to
       * the line.
       */

      if (*xPtr > dInfoPtr->maxX) {
          *xPtr = dInfoPtr->maxX;
      }
      *widthPtr = dInfoPtr->maxX - *xPtr;
    }
    if ((*xPtr + *widthPtr) <= dInfoPtr->x) {
      return -1;
    }
    if ((*xPtr + *widthPtr) > dInfoPtr->maxX) {
      *widthPtr = dInfoPtr->maxX - *xPtr;
      if (*widthPtr <= 0) {
          return -1;
      }
    }
    if ((*yPtr + *heightPtr) > dInfoPtr->maxY) {
      *heightPtr = dInfoPtr->maxY - *yPtr;
      if (*heightPtr <= 0) {
          return -1;
      }
    }
    return 0;
}

/*
 *----------------------------------------------------------------------
 *
 * TkTextDLineInfo --
 *
 *    Given an index, return information about the display line
 *    containing that character.
 *
 * Results:
 *    Zero is returned if the character is on the screen.  -1
 *    means the character isn't on the screen.  If the return value
 *    is 0, then information is returned in the variables pointed
 *    to by xPtr, yPtr, widthPtr, heightPtr, and basePtr.
 *
 * Side effects:
 *    None.
 *
 *----------------------------------------------------------------------
 */

int
TkTextDLineInfo(textPtr, indexPtr, xPtr, yPtr, widthPtr, heightPtr, basePtr)
    TkText *textPtr;          /* Widget record for text widget. */
    TkTextIndex *indexPtr;    /* Index of character whose bounding
                         * box is desired. */
    int *xPtr, *yPtr;         /* Filled with line's upper-left
                         * coordinate. */
    int *widthPtr, *heightPtr;      /* Filled in with line's dimensions. */
    int *basePtr;       /* Filled in with the baseline position,
                         * measured as an offset down from *yPtr. */
{
    TextDInfo *dInfoPtr = textPtr->dInfoPtr;
    DLine *dlPtr;
    int dlx;

    /*
     * Make sure that all of the screen layout information is up to date.
     */

    if (dInfoPtr->flags & DINFO_OUT_OF_DATE) {
      UpdateDisplayInfo(textPtr);
    }

    /*
     * Find the display line containing the desired index.
     */

    dlPtr = FindDLine(dInfoPtr->dLinePtr, indexPtr);
    if ((dlPtr == NULL) || (TkTextIndexCmp(&dlPtr->index, indexPtr) > 0)) {
      return -1;
    }

    dlx = (dlPtr->chunkPtr != NULL? dlPtr->chunkPtr->x: 0);
    *xPtr = dInfoPtr->x - dInfoPtr->curPixelOffset + dlx;
    *widthPtr = dlPtr->length - dlx;
    *yPtr = dlPtr->y;
    if ((dlPtr->y + dlPtr->height) > dInfoPtr->maxY) {
      *heightPtr = dInfoPtr->maxY - dlPtr->y;
    } else {
      *heightPtr = dlPtr->height;
    }
    *basePtr = dlPtr->baseline;
    return 0;
}

static void
ElideBboxProc(chunkPtr, index, y, lineHeight, baseline, xPtr, yPtr,
      widthPtr, heightPtr)
    TkTextDispChunk *chunkPtr;            /* Chunk containing desired char. */
    int index;                      /* Index of desired character within
                               * the chunk. */
    int y;                    /* Topmost pixel in area allocated
                               * for this line. */
    int lineHeight;                 /* Height of line, in pixels. */
    int baseline;             /* Location of line's baseline, in
                               * pixels measured down from y. */
    int *xPtr, *yPtr;               /* Gets filled in with coords of
                               * character's upper-left pixel. 
                               * X-coord is in same coordinate
                               * system as chunkPtr->x. */
    int *widthPtr;                  /* Gets filled in with width of
                               * character, in pixels. */
    int *heightPtr;                 /* Gets filled in with height of
                               * character, in pixels. */
{
    *xPtr = chunkPtr->x;
    *yPtr = y;
    *widthPtr = *heightPtr = 0;
}


static int
ElideMeasureProc(chunkPtr, x)
    TkTextDispChunk *chunkPtr;            /* Chunk containing desired coord. */
    int x;                    /* X-coordinate, in same coordinate
                               * system as chunkPtr->x. */
{
    return 0 /*chunkPtr->numBytes - 1*/;
}

/*
 *--------------------------------------------------------------
 *
 * TkTextCharLayoutProc --
 *
 *    This procedure is the "layoutProc" for character segments.
 *
 * Results:
 *    If there is something to display for the chunk then a
 *    non-zero value is returned and the fields of chunkPtr
 *    will be filled in (see the declaration of TkTextDispChunk
 *    in tkText.h for details).  If zero is returned it means
 *    that no characters from this chunk fit in the window.
 *    If -1 is returned it means that this segment just doesn't
 *    need to be displayed (never happens for text).
 *
 * Side effects:
 *    Memory is allocated to hold additional information about
 *    the chunk.
 *
 *--------------------------------------------------------------
 */

int
TkTextCharLayoutProc(textPtr, indexPtr, segPtr, byteOffset, maxX, maxBytes,
      noCharsYet, wrapMode, chunkPtr)
    TkText *textPtr;          /* Text widget being layed out. */
    TkTextIndex *indexPtr;    /* Index of first character to lay out
                         * (corresponds to segPtr and offset). */
    TkTextSegment *segPtr;    /* Segment being layed out. */
    int byteOffset;           /* Byte offset within segment of first
                         * character to consider. */
    int maxX;                 /* Chunk must not occupy pixels at this
                         * position or higher. */
    int maxBytes;       /* Chunk must not include more than this
                         * many characters. */
    int noCharsYet;           /* Non-zero means no characters have been
                         * assigned to this display line yet. */
    TkWrapMode wrapMode;      /* How to handle line wrapping: TEXT_WRAPMODE_CHAR,
                         * TEXT_WRAPMODE_NONE, or TEXT_WRAPMODE_WORD. */
    register TkTextDispChunk *chunkPtr;
                        /* Structure to fill in with information
                         * about this chunk.  The x field has already
                         * been set by the caller. */
{
    Tk_Font tkfont;
    int nextX, bytesThatFit, count;
    CharInfo *ciPtr;
    char *p;
    TkTextSegment *nextPtr;
    Tk_FontMetrics fm;

    /*
     * Figure out how many characters will fit in the space we've got.
     * Include the next character, even though it won't fit completely,
     * if any of the following is true:
     *   (a) the chunk contains no characters and the display line contains
     *           no characters yet (i.e. the line isn't wide enough to hold
     *           even a single character).
     *   (b) at least one pixel of the character is visible, we haven't
     *           already exceeded the character limit, and the next character
     *           is a white space character.
     */

    p = segPtr->body.chars + byteOffset;
    tkfont = chunkPtr->stylePtr->sValuePtr->tkfont;
    bytesThatFit = MeasureChars(tkfont, p, maxBytes, chunkPtr->x, maxX, 0,
          &nextX);
    if (bytesThatFit < maxBytes) {
      if ((bytesThatFit == 0) && noCharsYet) {
          Tcl_UniChar ch;
          
          bytesThatFit = MeasureChars(tkfont, p, Tcl_UtfToUniChar(p, &ch),
                chunkPtr->x, -1, 0, &nextX);
      }
      if ((nextX < maxX) && ((p[bytesThatFit] == ' ')
            || (p[bytesThatFit] == '\t'))) {
          /*
           * Space characters are funny, in that they are considered
           * to fit if there is at least one pixel of space left on the
           * line.  Just give the space character whatever space is left.
           */

          nextX = maxX;
          bytesThatFit++;
      }
      if (p[bytesThatFit] == '\n') {
          /*
           * A newline character takes up no space, so if the previous
           * character fits then so does the newline.
           */

          bytesThatFit++;
      }
      if (bytesThatFit == 0) {
          return 0;
      }
    }
      
    Tk_GetFontMetrics(tkfont, &fm);

    /*
     * Fill in the chunk structure and allocate and initialize a
     * CharInfo structure.  If the last character is a newline
     * then don't bother to display it.
     */

    chunkPtr->displayProc = CharDisplayProc;
    chunkPtr->undisplayProc = CharUndisplayProc;
    chunkPtr->measureProc = CharMeasureProc;
    chunkPtr->bboxProc = CharBboxProc;
    chunkPtr->numBytes = bytesThatFit;
    chunkPtr->minAscent = fm.ascent + chunkPtr->stylePtr->sValuePtr->offset;
    chunkPtr->minDescent = fm.descent - chunkPtr->stylePtr->sValuePtr->offset;
    chunkPtr->minHeight = 0;
    chunkPtr->width = nextX - chunkPtr->x;
    chunkPtr->breakIndex = -1;
    ciPtr = (CharInfo *) ckalloc((unsigned)
          (sizeof(CharInfo) - 3 + bytesThatFit));
    chunkPtr->clientData = (ClientData) ciPtr;
    ciPtr->numBytes = bytesThatFit;
    strncpy(ciPtr->chars, p, (size_t) bytesThatFit);
    if (p[bytesThatFit - 1] == '\n') {
      ciPtr->numBytes--;
    }

    /*
     * Compute a break location.  If we're in word wrap mode, a
     * break can occur after any space character, or at the end of
     * the chunk if the next segment (ignoring those with zero size)
     * is not a character segment.
     */

    if (wrapMode != TEXT_WRAPMODE_WORD) {
      chunkPtr->breakIndex = chunkPtr->numBytes;
    } else {
      for (count = bytesThatFit, p += bytesThatFit - 1; count > 0;
            count--, p--) {
          if (isspace(UCHAR(*p))) {
            chunkPtr->breakIndex = count;
            break;
          }
      }
      if ((bytesThatFit + byteOffset) == segPtr->size) {
          for (nextPtr = segPtr->nextPtr; nextPtr != NULL;
                nextPtr = nextPtr->nextPtr) {
            if (nextPtr->size != 0) {
                if (nextPtr->typePtr != &tkTextCharType) {
                  chunkPtr->breakIndex = chunkPtr->numBytes;
                }
                break;
            }
          }
      }
    }
    return 1;
}

/*
 *--------------------------------------------------------------
 *
 * CharDisplayProc --
 *
 *    This procedure is called to display a character chunk on
 *    the screen or in an off-screen pixmap.
 *
 * Results:
 *    None.
 *
 * Side effects:
 *    Graphics are drawn.
 *
 *--------------------------------------------------------------
 */

static void
CharDisplayProc(chunkPtr, x, y, height, baseline, display, dst, screenY)
    TkTextDispChunk *chunkPtr;            /* Chunk that is to be drawn. */
    int x;                    /* X-position in dst at which to
                               * draw this chunk (may differ from
                               * the x-position in the chunk because
                               * of scrolling). */
    int y;                    /* Y-position at which to draw this
                               * chunk in dst. */
    int height;                     /* Total height of line. */
    int baseline;             /* Offset of baseline from y. */
    Display *display;               /* Display to use for drawing. */
    Drawable dst;             /* Pixmap or window in which to draw
                               * chunk. */
    int screenY;              /* Y-coordinate in text window that
                               * corresponds to y. */
{
    CharInfo *ciPtr = (CharInfo *) chunkPtr->clientData;
    TextStyle *stylePtr;
    StyleValues *sValuePtr;
    int offsetBytes, offsetX;

    if ((x + chunkPtr->width) <= 0) {
      /*
       * The chunk is off-screen.
       */

      return;
    }

    stylePtr = chunkPtr->stylePtr;
    sValuePtr = stylePtr->sValuePtr;

    /*
     * If the text sticks out way to the left of the window, skip
     * over the characters that aren't in the visible part of the
     * window.  This is essential if x is very negative (such as
     * less than 32K);  otherwise overflow problems will occur
     * in servers that use 16-bit arithmetic, like X.
     */

    offsetX = x;
    offsetBytes = 0;
    if (x < 0) {
      offsetBytes = MeasureChars(sValuePtr->tkfont, ciPtr->chars,
          ciPtr->numBytes, x, 0, x - chunkPtr->x, &offsetX);
    }

    /*
     * Draw the text, underline, and overstrike for this chunk.
     */

    if (!sValuePtr->elide && (ciPtr->numBytes > offsetBytes) && (stylePtr->fgGC != None)) {
      int numBytes = ciPtr->numBytes - offsetBytes;
      char *string = ciPtr->chars + offsetBytes;

      if ((numBytes > 0) && (string[numBytes - 1] == '\t')) {
          numBytes--;
      }
      Tk_DrawChars(display, dst, stylePtr->fgGC, sValuePtr->tkfont, string,
            numBytes, offsetX, y + baseline - sValuePtr->offset);
      if (sValuePtr->underline) {
          Tk_UnderlineChars(display, dst, stylePtr->fgGC, sValuePtr->tkfont,
                ciPtr->chars + offsetBytes, offsetX,
                y + baseline - sValuePtr->offset, 0, numBytes);

      }
      if (sValuePtr->overstrike) {
          Tk_FontMetrics fm;
          
          Tk_GetFontMetrics(sValuePtr->tkfont, &fm);
          Tk_UnderlineChars(display, dst, stylePtr->fgGC, sValuePtr->tkfont,
                ciPtr->chars + offsetBytes, offsetX,
                y + baseline - sValuePtr->offset
                      - fm.descent - (fm.ascent * 3) / 10,
                0, numBytes);
      }
    }
}

/*
 *--------------------------------------------------------------
 *
 * CharUndisplayProc --
 *
 *    This procedure is called when a character chunk is no
 *    longer going to be displayed.  It frees up resources
 *    that were allocated to display the chunk.
 *
 * Results:
 *    None.
 *
 * Side effects:
 *    Memory and other resources get freed.
 *
 *--------------------------------------------------------------
 */

static void
CharUndisplayProc(textPtr, chunkPtr)
    TkText *textPtr;                /* Overall information about text
                               * widget. */
    TkTextDispChunk *chunkPtr;            /* Chunk that is about to be freed. */
{
    CharInfo *ciPtr = (CharInfo *) chunkPtr->clientData;

    ckfree((char *) ciPtr);
}

/*
 *--------------------------------------------------------------
 *
 * CharMeasureProc --
 *
 *    This procedure is called to determine which character in
 *    a character chunk lies over a given x-coordinate.
 *
 * Results:
 *    The return value is the index *within the chunk* of the
 *    character that covers the position given by "x".
 *
 * Side effects:
 *    None.
 *
 *--------------------------------------------------------------
 */

static int
CharMeasureProc(chunkPtr, x)
    TkTextDispChunk *chunkPtr;            /* Chunk containing desired coord. */
    int x;                    /* X-coordinate, in same coordinate
                               * system as chunkPtr->x. */
{
    CharInfo *ciPtr = (CharInfo *) chunkPtr->clientData;
    int endX;

    return MeasureChars(chunkPtr->stylePtr->sValuePtr->tkfont, ciPtr->chars,
          chunkPtr->numBytes - 1, chunkPtr->x, x, 0, &endX);
                                    /* CHAR OFFSET */
}

/*
 *--------------------------------------------------------------
 *
 * CharBboxProc --
 *
 *    This procedure is called to compute the bounding box of
 *    the area occupied by a single character.
 *
 * Results:
 *    There is no return value.  *xPtr and *yPtr are filled in
 *    with the coordinates of the upper left corner of the
 *    character, and *widthPtr and *heightPtr are filled in with
 *    the dimensions of the character in pixels.  Note:  not all
 *    of the returned bbox is necessarily visible on the screen
 *    (the rightmost part might be off-screen to the right,
 *    and the bottommost part might be off-screen to the bottom).
 *
 * Side effects:
 *    None.
 *
 *--------------------------------------------------------------
 */

static void
CharBboxProc(chunkPtr, byteIndex, y, lineHeight, baseline, xPtr, yPtr,
      widthPtr, heightPtr)
    TkTextDispChunk *chunkPtr;            /* Chunk containing desired char. */
    int byteIndex;                        /* Byte offset of desired character
                               * within the chunk. */
    int y;                    /* Topmost pixel in area allocated
                               * for this line. */
    int lineHeight;                 /* Height of line, in pixels. */
    int baseline;             /* Location of line's baseline, in
                               * pixels measured down from y. */
    int *xPtr, *yPtr;               /* Gets filled in with coords of
                               * character's upper-left pixel. 
                               * X-coord is in same coordinate
                               * system as chunkPtr->x. */
    int *widthPtr;                  /* Gets filled in with width of
                               * character, in pixels. */
    int *heightPtr;                 /* Gets filled in with height of
                               * character, in pixels. */
{
    CharInfo *ciPtr = (CharInfo *) chunkPtr->clientData;
    int maxX;

    maxX = chunkPtr->width + chunkPtr->x;
    MeasureChars(chunkPtr->stylePtr->sValuePtr->tkfont, ciPtr->chars,
          byteIndex, chunkPtr->x, -1, 0, xPtr);

    if (byteIndex == ciPtr->numBytes) {
      /*
       * This situation only happens if the last character in a line
       * is a space character, in which case it absorbs all of the
       * extra space in the line (see TkTextCharLayoutProc).
       */

      *widthPtr = maxX - *xPtr;
    } else if ((ciPtr->chars[byteIndex] == '\t')
          && (byteIndex == ciPtr->numBytes - 1)) {
      /*
       * The desired character is a tab character that terminates a
       * chunk;  give it all the space left in the chunk.
       */

      *widthPtr = maxX - *xPtr;
    } else {
      MeasureChars(chunkPtr->stylePtr->sValuePtr->tkfont, 
            ciPtr->chars + byteIndex, 1, *xPtr, -1, 0, widthPtr);
      if (*widthPtr > maxX) {
          *widthPtr = maxX - *xPtr;
      } else {
          *widthPtr -= *xPtr;
      }
    }
    *yPtr = y + baseline - chunkPtr->minAscent;
    *heightPtr = chunkPtr->minAscent + chunkPtr->minDescent;
}

/*
 *----------------------------------------------------------------------
 *
 * AdjustForTab --
 *
 *    This procedure is called to move a series of chunks right
 *    in order to align them with a tab stop.
 *
 * Results:
 *    None.
 *
 * Side effects:
 *    The width of chunkPtr gets adjusted so that it absorbs the
 *    extra space due to the tab.  The x locations in all the chunks
 *    after chunkPtr are adjusted rightward to align with the tab
 *    stop given by tabArrayPtr and index.
 *
 *----------------------------------------------------------------------
 */

static void
AdjustForTab(textPtr, tabArrayPtr, index, chunkPtr)
    TkText *textPtr;                /* Information about the text widget as
                               * a whole. */
    TkTextTabArray *tabArrayPtr;    /* Information about the tab stops
                               * that apply to this line.  May be
                               * NULL to indicate default tabbing
                               * (every 8 chars). */
    int index;                      /* Index of current tab stop. */
    TkTextDispChunk *chunkPtr;            /* Chunk whose last character is
                               * the tab;  the following chunks
                               * contain information to be shifted
                               * right. */

{
    int x, desired, delta, width, decimal, i, gotDigit;
    TkTextDispChunk *chunkPtr2, *decimalChunkPtr;
    CharInfo *ciPtr;
    int tabX, prev, spaceWidth;
    char *p;
    TkTextTabAlign alignment;

    if (chunkPtr->nextPtr == NULL) {
      /*
       * Nothing after the actual tab;  just return.
       */

      return;
    }

    /*
     * If no tab information has been given, do the usual thing:
     * round up to the next boundary of 8 average-sized characters.
     */

    x = chunkPtr->nextPtr->x;
    if ((tabArrayPtr == NULL) || (tabArrayPtr->numTabs == 0)) {
      /*
       * No tab information has been given, so use the default
       * interpretation of tabs.
       */

      desired = NextTabStop(textPtr->tkfont, x, 0);
      goto update;
    }

    if (index < tabArrayPtr->numTabs) {
      alignment = tabArrayPtr->tabs[index].alignment;
      tabX = tabArrayPtr->tabs[index].location;
    } else {
      /*
       * Ran out of tab stops;  compute a tab position by extrapolating
       * from the last two tab positions.
       */

      if (tabArrayPtr->numTabs > 1) {
          prev = tabArrayPtr->tabs[tabArrayPtr->numTabs-2].location;
      } else {
          prev = 0;
      }
      alignment = tabArrayPtr->tabs[tabArrayPtr->numTabs-1].alignment;
      tabX = tabArrayPtr->tabs[tabArrayPtr->numTabs-1].location
            + (index + 1 - tabArrayPtr->numTabs)
            * (tabArrayPtr->tabs[tabArrayPtr->numTabs-1].location - prev);
    }

    if (alignment == LEFT) {
      desired = tabX;
      goto update;
    }

    if ((alignment == CENTER) || (alignment == RIGHT)) {
      /*
       * Compute the width of all the information in the tab group,
       * then use it to pick a desired location.
       */

      width = 0;
      for (chunkPtr2 = chunkPtr->nextPtr; chunkPtr2 != NULL;
            chunkPtr2 = chunkPtr2->nextPtr) {
          width += chunkPtr2->width;
      }
      if (alignment == CENTER) {
          desired = tabX - width/2;
      } else {
          desired = tabX - width;
      }
      goto update;
    }

    /*
     * Must be numeric alignment.  Search through the text to be
     * tabbed, looking for the last , or . before the first character
     * that isn't a number, comma, period, or sign.
     */

    decimalChunkPtr = NULL;
    decimal = gotDigit = 0;
    for (chunkPtr2 = chunkPtr->nextPtr; chunkPtr2 != NULL;
          chunkPtr2 = chunkPtr2->nextPtr) {
      if (chunkPtr2->displayProc != CharDisplayProc) {
          continue;
      }
      ciPtr = (CharInfo *) chunkPtr2->clientData;
      for (p = ciPtr->chars, i = 0; i < ciPtr->numBytes; p++, i++) {
          if (isdigit(UCHAR(*p))) {
            gotDigit = 1;
          } else if ((*p == '.') || (*p == ',')) {
            decimal = p-ciPtr->chars;
            decimalChunkPtr = chunkPtr2;
          } else if (gotDigit) {
            if (decimalChunkPtr == NULL) {
                decimal = p-ciPtr->chars;
                decimalChunkPtr = chunkPtr2;
            }
            goto endOfNumber;
          }
      }
    }
    endOfNumber:
    if (decimalChunkPtr != NULL) {
      int curX;

      ciPtr = (CharInfo *) decimalChunkPtr->clientData;
      MeasureChars(decimalChunkPtr->stylePtr->sValuePtr->tkfont,
            ciPtr->chars, decimal, decimalChunkPtr->x, -1, 0, &curX);
      desired = tabX - (curX - x);
      goto update;
    } else {
      /*
       * There wasn't a decimal point.  Right justify the text.
       */
    
      width = 0;
      for (chunkPtr2 = chunkPtr->nextPtr; chunkPtr2 != NULL;
            chunkPtr2 = chunkPtr2->nextPtr) {
          width += chunkPtr2->width;
      }
      desired = tabX - width;
    }

    /*
     * Shift all of the chunks to the right so that the left edge is
     * at the desired location, then expand the chunk containing the
     * tab.  Be sure that the tab occupies at least the width of a
     * space character.
     */

    update:
    delta = desired - x;
    MeasureChars(textPtr->tkfont, " ", 1, 0, -1, 0, &spaceWidth);
    if (delta < spaceWidth) {
      delta = spaceWidth;
    }
    for (chunkPtr2 = chunkPtr->nextPtr; chunkPtr2 != NULL;
          chunkPtr2 = chunkPtr2->nextPtr) {
      chunkPtr2->x += delta;
    }
    chunkPtr->width += delta;
}

/*
 *----------------------------------------------------------------------
 *
 * SizeOfTab --
 *
 *    This returns an estimate of the amount of white space that will
 *    be consumed by a tab.
 *
 * Results:
 *    The return value is the minimum number of pixels that will
 *    be occupied by the index'th tab of tabArrayPtr, assuming that
 *    the current position on the line is x and the end of the
 *    line is maxX.  For numeric tabs, this is a conservative
 *    estimate.  The return value is always >= 0.
 *
 * Side effects:
 *    None.
 *
 *----------------------------------------------------------------------
 */

static int
SizeOfTab(textPtr, tabArrayPtr, index, x, maxX)
    TkText *textPtr;                /* Information about the text widget as
                               * a whole. */
    TkTextTabArray *tabArrayPtr;    /* Information about the tab stops
                               * that apply to this line.  NULL
                               * means use default tabbing (every
                               * 8 chars.) */
    int index;                      /* Index of current tab stop. */
    int x;                    /* Current x-location in line. Only
                               * used if tabArrayPtr == NULL. */
    int maxX;                       /* X-location of pixel just past the
                               * right edge of the line. */
{
    int tabX, prev, result, spaceWidth;
    TkTextTabAlign alignment;

    if ((tabArrayPtr == NULL) || (tabArrayPtr->numTabs == 0)) {
      tabX = NextTabStop(textPtr->tkfont, x, 0);
      return tabX - x;
    }
    if (index < tabArrayPtr->numTabs) {
      tabX = tabArrayPtr->tabs[index].location;
      alignment = tabArrayPtr->tabs[index].alignment;
    } else {
      /*
       * Ran out of tab stops;  compute a tab position by extrapolating
       * from the last two tab positions.
       */

      if (tabArrayPtr->numTabs > 1) {
          prev = tabArrayPtr->tabs[tabArrayPtr->numTabs-2].location;
      } else {
          prev = 0;
      }
      tabX = tabArrayPtr->tabs[tabArrayPtr->numTabs-1].location
            + (index + 1 - tabArrayPtr->numTabs)
            * (tabArrayPtr->tabs[tabArrayPtr->numTabs-1].location - prev);
      alignment = tabArrayPtr->tabs[tabArrayPtr->numTabs-1].alignment;
    }
    if (alignment == CENTER) {
      /*
       * Be very careful in the arithmetic below, because maxX may
       * be the largest positive number:  watch out for integer
       * overflow.
       */

      if ((maxX-tabX) < (tabX - x)) {
          result = (maxX - x) - 2*(maxX - tabX);
      } else {
          result = 0;
      }
      goto done;
    }
    if (alignment == RIGHT) {
      result = 0;
      goto done;
    }

    /*
     * Note: this treats NUMERIC alignment the same as LEFT
     * alignment, which is somewhat conservative.  However, it's
     * pretty tricky at this point to figure out exactly where
     * the damn decimal point will be.
     */

    if (tabX > x) {
      result = tabX - x;
    } else {
      result = 0;
    }

    done:
    MeasureChars(textPtr->tkfont, " ", 1, 0, -1, 0, &spaceWidth);
    if (result < spaceWidth) {
      result = spaceWidth;
    }
    return result;
}

/*
 *---------------------------------------------------------------------------
 *
 * NextTabStop --
 *
 *    Given the current position, determine where the next default
 *    tab stop would be located.  This procedure is called when the
 *    current chunk in the text has no tabs defined and so the default
 *    tab spacing for the font should be used.
 *
 * Results:
 *    The location in pixels of the next tab stop.
 *
 * Side effects:
 *    None.
 *
 *---------------------------------------------------------------------------
 */

static int
NextTabStop(tkfont, x, tabOrigin)
    Tk_Font tkfont;           /* Font in which chunk that contains tab
                         * stop will be drawn. */
    int x;              /* X-position in pixels where last
                         * character was drawn.  The next tab stop
                         * occurs somewhere after this location. */
    int tabOrigin;            /* The origin for tab stops.  May be
                         * non-zero if text has been scrolled. */
{
    int tabWidth, rem;
    
    tabWidth = Tk_TextWidth(tkfont, "0", 1) * 8;
    if (tabWidth == 0) {
      tabWidth = 1;
    }

    x += tabWidth;
    rem = (x - tabOrigin) % tabWidth;
    if (rem < 0) {
      rem += tabWidth;
    }
    x -= rem;
    return x;
}

/*
 *---------------------------------------------------------------------------
 *
 *  MeasureChars --
 *
 *    Determine the number of characters from the string that will fit
 *    in the given horizontal span.  The measurement is done under the
 *    assumption that Tk_DrawTextLayout will be used to actually display
 *    the characters.
 *
 *    If tabs are encountered in the string, they will be expanded
 *    to the next tab stop, unless the TK_IGNORE_TABS flag is specified.
 *
 *    If a newline is encountered in the string, the line will be
 *    broken at that point, unless the TK_NEWSLINES_NOT_SPECIAL flag
 *    is specified.  
 *
 * Results:
 *    The return value is the number of bytes from source
 *    that fit in the span given by startX and maxX.  *nextXPtr
 *    is filled in with the x-coordinate at which the first
 *    character that didn't fit would be drawn, if it were to
 *    be drawn.
 *
 * Side effects:
 *    None.
 *
 *--------------------------------------------------------------
 */

static int
MeasureChars(tkfont, source, maxBytes, startX, maxX, tabOrigin, nextXPtr)
    Tk_Font tkfont;           /* Font in which to draw characters. */
    CONST char *source;       /* Characters to be displayed.  Need not
                         * be NULL-terminated. */
    int maxBytes;       /* Maximum # of bytes to consider from
                         * source. */
    int startX;               /* X-position at which first character will
                         * be drawn. */
    int maxX;                 /* Don't consider any character that would
                         * cross this x-position. */
    int tabOrigin;            /* X-location that serves as "origin" for
                         * tab stops. */
    int *nextXPtr;            /* Return x-position of terminating
                         * character here. */
{
    int curX, width, ch;
    CONST char *special, *end, *start;

    ch = 0;             /* lint. */
    curX = startX;
    special = source;
    end = source + maxBytes;
    for (start = source; start < end; ) {
      if (start >= special) {
          /*
           * Find the next special character in the string.
           */

          for (special = start; special < end; special++) {
            ch = *special;
            if ((ch == '\t') || (ch == '\n')) {
                break;
            }
          }
      }

      /*
       * Special points at the next special character (or the end of the
       * string).  Process characters between start and special.
       */

      if ((maxX >= 0) && (curX >= maxX)) {
          break;
      }
      start += Tk_MeasureChars(tkfont, start, special - start, maxX - curX,
            0, &width);
      curX += width;
      if (start < special) {
          /*
           * No more chars fit in line.
           */

          break;
      }
      if (special < end) {
          if (ch == '\t') {
            start++;
          } else {
            break;
          }
      }
    }

    *nextXPtr = curX;
    return start - source;
}

Generated by  Doxygen 1.6.0   Back to index