/* 
 * 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.  (Well, strictly, each TkTextLine caches its
 *	last observed pixel height, but that information is
 *	calculated here).
 *
 * 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.43 2004/06/08 20:28:19 dgp Exp $
 */

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

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

/*
 * "Calculations of line pixel heights and the size of the vertical
 * scrollbar."
 * 
 * Given that tag, font and elide changes can happen to large numbers of
 * diverse chunks in a text widget containing megabytes of text, it is
 * not possible to recalculate all affected height information
 * immediately any such change takes place and maintain a responsive
 * user-experience.  Yet, for an accurate vertical scrollbar to be
 * drawn, we must know the total number of vertical pixels shown on
 * display versus the number available to be displayed.
 * 
 * The way the text widget solves this problem is by maintaining cached
 * line pixel heights (in the BTree for each logical line), and having
 * asynchronous timer callbacks (i) to iterate through the logical
 * lines recalculating their heights, and (ii) to recalculate the
 * vertical scrollbar's position and size.
 * 
 * Typically this works well but there are some situations where the
 * overall functional design of this file causes some problems.  These
 * problems can only arise because the calculations used to display
 * lines on screen are not connected to those in the iterating-line-
 * recalculation-process.
 * 
 * The reason for this disconnect is that the display calculations
 * operate in display lines, and the iteration and cache operates in
 * logical lines.  Given that the display calculations both need not
 * contain complete logical lines (at top or bottom of display), and
 * that they do not actually keep track of logical lines (for simplicity
 * of code and historical design), this means a line may be known and
 * drawn with a different pixel height to that which is cached in the
 * BTree, and this might cause some temporary undesirable mismatch
 * between display and the vertical scrollbar.
 * 
 * All such mismatches should be temporary, however, since the
 * asynchronous height calculations will always catch up eventually.
 * 
 * For further details see the comments before and within the following
 * functions below: LayoutDLine, AsyncUpdateLineMetrics, GetYView,
 * GetYPixelCount, TkTextUpdateOneLine, TkTextUpdateLineMetrics.
 *
 * For details of the way in which the BTree keeps track of pixel
 * heights, see tkTextBTree.c.  Basically the BTree maintains two
 * pieces of information: the logical line indices and the pixel
 * height cache.
 */

/*
 * 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;			/* Zero means draw text, otherwise not */
    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.  This
				 * is used to move lines by scrolling
				 * rather than re-drawing.  If 'flags'
				 * have the OLD_Y_INVALID bit set, then
				 * we will never examine this field
				 * (which means line isn't currently
				 * visible on display and must be
				 * redrawn).  */
    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.
 * OLD_Y_INVALID -              The value of oldY in the structure is
 *                              not valid or useful and should not be 
 *                              examined.  'oldY' is only useful when the
 *                              DLine is currently displayed at a
 *                              different position and we wish to
 *                              re-display it via scrolling, so this
 *                              means the DLine needs redrawing.
 */

#define HAS_3D_BORDER	1
#define NEW_LAYOUT	2
#define TOP_LINE	4
#define BOTTOM_LINE	8
#define OLD_Y_INVALID  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. */
    int topPixelOffset; 	/* Identifies first pixel in top display
				 * line to display in window. */
    int newTopPixelOffset; 	/* Desired first pixel in top display
				 * line to display in window. */
    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 newXPixelOffset;	/* Desired x scroll position, measured as the
				 * number of pixels off-screen to the left 
				 * for a line with no left margin. */
    int curXPixelOffset;	/* 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 scanMarkXPixel;		/* Pixel index of left edge of the window 
                       		 * when the scan started. */
    int scanMarkX;		/* X-position of mouse at time scan started. */
    int scanTotalYScroll;	/* Total scrolling (in screen pixels) 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. */
    /*
     * Information used to handle the asynchronous updating of the
     * y-scrollbar and the vertical height calculations:
     */

    int lineMetricUpdateEpoch;  /* Stores a number which is incremented
				 * each time the text widget changes in
				 * a significant way (e.g. resizing or 
				 * geometry-influencing tag changes). */
    int currentMetricUpdateLine;/* Stores a counter which is used to 
				 * iterate over the logical lines
				 * contained in the widget and update
				 * their geometry calculations, if they
				 * are out of date.  */
    TkTextIndex metricIndex;    /* If the current metric update line
                                 * wraps into very many display lines, then
                                 * this is used to keep track of what
                                 * index we've got to so far... */
    int metricPixelHeight;      /* ...and this is for the height
                                 * calculation so far...*/
    int metricEpoch;            /* ...and this for the epoch of the
                                 * partial calculation so it can be
                                 * cancelled if things change once more. */
    int lastMetricUpdateLine;   /* When the current update line reaches
				 * this line, we are done and should
				 * stop the asychronous callback
				 * mechanism. */
    Tcl_TimerToken lineUpdateTimer; /* A token pointing to the current
				     * line metric update callback. */
    Tcl_TimerToken scrollbarTimer;  /* A token pointing to the current
				     * scrollbar update callback. */
} 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

/*
 * Action values for FreeDLines:
 *
 * DLINE_FREE:		Free the lines, but no need to unlink them
 *                      from the current list of actual display lines.
 * DLINE_UNLINK:	Free and unlink from current display.
 * DLINE_FREE_TEMP:	Free, but don't unlink, and also don't
 *                      set 'dLinesInvalidated'.
 */

#define DLINE_FREE        0
#define DLINE_UNLINK      1
#define DLINE_FREE_TEMP   2

/*
 * 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.  */
static int lineHeightsRecalculated; /* Number of line layouts purely
                                     * for height calculation purposes.*/
/*
 * 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,
			    CONST TkTextIndex *indexPtr));
static void		FreeDLines _ANSI_ARGS_((TkText *textPtr,
			    DLine *firstPtr, DLine *lastPtr, int action));
static void		FreeStyle _ANSI_ARGS_((TkText *textPtr,
			    TextStyle *stylePtr));
static TextStyle *	GetStyle _ANSI_ARGS_((TkText *textPtr,
			    CONST 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 int              GetYPixelCount _ANSI_ARGS_((TkText *textPtr, 
			    DLine *dlPtr));
static DLine *		LayoutDLine _ANSI_ARGS_((TkText *textPtr,
			    CONST TkTextIndex *indexPtr));
static int		MeasureChars _ANSI_ARGS_((Tk_Font tkfont,
			    CONST char *source, int maxBytes, int startX,
			    int maxX, int *nextXPtr));
static void		MeasureUp _ANSI_ARGS_((TkText *textPtr,
			    CONST TkTextIndex *srcPtr, int distance,
			    TkTextIndex *dstPtr, int *overlap));
static int		NextTabStop _ANSI_ARGS_((Tk_Font tkfont, int x,
			    int tabOrigin));
static void		UpdateDisplayInfo _ANSI_ARGS_((TkText *textPtr));
static void		YScrollByLines _ANSI_ARGS_((TkText *textPtr,
			    int offset));
static void		YScrollByPixels _ANSI_ARGS_((TkText *textPtr,
			    int offset));
static int		SizeOfTab _ANSI_ARGS_((TkText *textPtr,
			    TkTextTabArray *tabArrayPtr, int *indexPtr, 
			    int x, int maxX));
static void		TextInvalidateRegion _ANSI_ARGS_((TkText *textPtr,
			    TkRegion region));
static int              CalculateDisplayLineHeight _ANSI_ARGS_((
			    TkText *textPtr, CONST TkTextIndex *indexPtr, 
			    int *byteCountPtr));
static void             DlineIndexOfX _ANSI_ARGS_((TkText *textPtr, 
			    DLine *dlPtr, int x, TkTextIndex *indexPtr));
static int              DlineXOfIndex _ANSI_ARGS_((TkText *textPtr, 
			    DLine *dlPtr, int byteIndex));
static int              TextGetScrollInfoObj _ANSI_ARGS_((Tcl_Interp *interp, 
			    TkText *textPtr, int objc, 
			    Tcl_Obj *CONST objv[], double *dblPtr,
			    int *intPtr));
static void	        AsyncUpdateLineMetrics _ANSI_ARGS_((ClientData 
			    clientData));
static void	        AsyncUpdateYScrollbar _ANSI_ARGS_((ClientData 
			    clientData));

/*
 * Result values returned by TextGetScrollInfo:
 */

#define TKTEXT_SCROLL_MOVETO    	1
#define TKTEXT_SCROLL_PAGES		2
#define TKTEXT_SCROLL_UNITS		3
#define TKTEXT_SCROLL_ERROR		4
#define TKTEXT_SCROLL_PIXELS		5


/*
 *----------------------------------------------------------------------
 *
 * 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->newXPixelOffset = 0;
    dInfoPtr->curXPixelOffset = 0;
    dInfoPtr->maxLength = 0;
    dInfoPtr->xScrollFirst = -1;
    dInfoPtr->xScrollLast = -1;
    dInfoPtr->yScrollFirst = -1;
    dInfoPtr->yScrollLast = -1;
    dInfoPtr->scanMarkXPixel = 0;
    dInfoPtr->scanMarkX = 0;
    dInfoPtr->scanTotalYScroll = 0;
    dInfoPtr->scanMarkY = 0;
    dInfoPtr->dLinesInvalidated = 0;
    dInfoPtr->flags = DINFO_OUT_OF_DATE;
    dInfoPtr->topPixelOffset = 0;
    dInfoPtr->newTopPixelOffset = 0;
    dInfoPtr->currentMetricUpdateLine = -1;
    dInfoPtr->lastMetricUpdateLine = -1;
    dInfoPtr->lineMetricUpdateEpoch = 1;
    dInfoPtr->metricEpoch = -1;
    dInfoPtr->metricIndex.textPtr = NULL;
    dInfoPtr->metricIndex.linePtr = NULL;
    
    /* Add a refCount for each of the idle call-backs */
    textPtr->refCount++;
    dInfoPtr->lineUpdateTimer = Tcl_CreateTimerHandler(0, 
	    AsyncUpdateLineMetrics, (ClientData) textPtr);
    textPtr->refCount++;
    dInfoPtr->scrollbarTimer = Tcl_CreateTimerHandler(200, 
	    AsyncUpdateYScrollbar, (ClientData) textPtr);

    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, DLINE_UNLINK);
    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);
    }
    if (dInfoPtr->lineUpdateTimer != NULL) {
	Tcl_DeleteTimerHandler(dInfoPtr->lineUpdateTimer);
	textPtr->refCount--;
	dInfoPtr->lineUpdateTimer = NULL;
    }
    if (dInfoPtr->scrollbarTimer != NULL) {
	Tcl_DeleteTimerHandler(dInfoPtr->scrollbarTimer);
	textPtr->refCount--;
	dInfoPtr->scrollbarTimer = NULL;
    }
    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. */
    CONST 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->borderWidthPtr != NULL) 
	        && (Tcl_GetString(tagPtr->borderWidthPtr)[0] != '\0')
		&& (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->tabStringPtr != 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.
 *	
 *	See the comments in 'GetYView' for some thoughts on what the
 *	side-effects of this call (or its callers) should be -- the
 *	synchronisation of TkTextLine->pixelHeight with the sum of 
 *	the results of this function operating on all display lines
 *	within each logical line.  Ideally the code should be
 *	refactored to ensure the cached pixel height is never behind
 *	what is known when this function is called elsewhere.
 *	
 *	Unfortunately, this function is currently called from many
 *	different places, not just to layout a display line for actual
 *	display, but also simply to calculate some metric or other of one
 *	or more display lines (typically the height).  It would be a good
 *	idea to do some profiling of typical text widget usage and the
 *	way in which this is called and see if some optimization could or
 *	should be done.
 *
 *----------------------------------------------------------------------
 */

static DLine *
LayoutDLine(textPtr, indexPtr)
    TkText *textPtr;		/* Overall information about text widget. */
    CONST 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;
    TkTextElideInfo info;               /* Keep track of elide state */
    
    /*
     * Create and initialize a new DLine structure.
     */

    dlPtr = (DLine *) ckalloc(sizeof(DLine));
    dlPtr->index = *indexPtr;
    dlPtr->byteCount = 0;
    dlPtr->y = 0;
    dlPtr->oldY = 0;  /* Only setting this to avoid compiler warnings */
    dlPtr->height = 0;
    dlPtr->baseline = 0;
    dlPtr->chunkPtr = NULL;
    dlPtr->nextPtr = NULL;
    dlPtr->flags = NEW_LAYOUT | OLD_Y_INVALID;

    /*
     * Special case entirely elide line as there may be 1000s or more
     */
    elide = TkTextIsElided(textPtr, indexPtr, &info);
    if (elide && indexPtr->byteIndex==0) {
	maxBytes = 0;
	for (segPtr = info.segPtr; segPtr != NULL; segPtr = segPtr->nextPtr) {
	    if (segPtr->size > 0) {
		if (elide == 0) {
		    /*
		     * We toggled a tag and the elide state changed to
		     * visible, and we have something of non-zero 
		     * size.  Therefore we must bail out.
		     */
		    break;
		}
		maxBytes += segPtr->size;
		/* 
		 * Reset tag elide priority, since we're on a new
		 * character.
		 */
	    } else if ((segPtr->typePtr == &tkTextToggleOffType)
		       || (segPtr->typePtr == &tkTextToggleOnType)) {
		TkTextTag *tagPtr = segPtr->body.toggle.tagPtr;
		/*
		 * The elide state only changes if this tag is
		 * either the current highest priority tag
		 * (and is therefore being toggled off), or it's
		 * a new tag with higher priority.
		 */
		if (tagPtr->elideString != NULL) {
		    info.tagCnts[tagPtr->priority]++;
		    if (info.tagCnts[tagPtr->priority] & 1) {
			info.tagPtrs[tagPtr->priority] = tagPtr;
		    }
		    if (tagPtr->priority >= info.elidePriority) {
			if (segPtr->typePtr == &tkTextToggleOffType) {
			    /* 
			     * If it is being toggled off, and it has
			     * an elide string, it must actually be the
			     * current highest priority tag, so this
			     * check is redundant:
			     */
			    if (tagPtr->priority != info.elidePriority) {
				Tcl_Panic("Bad tag priority being toggled off");
			    }
			    
			    /* 
			     * Find previous elide tag, if any (if not
			     * then elide will be zero, of course).
			     */
			    elide = 0;
			    while (--info.elidePriority > 0) {
				if (info.tagCnts[info.elidePriority] & 1) {
				    elide = info.tagPtrs[info.elidePriority]
								     ->elide;
				    break;
				}
			    }
			} else {
			    elide = tagPtr->elide;
			    info.elidePriority = tagPtr->priority;
			}
		    }
		}
	    }
	}

	if (elide) {
	    dlPtr->byteCount = maxBytes;
	    dlPtr->spaceAbove = dlPtr->spaceBelow = dlPtr->length = 0;
	    if (dlPtr->index.byteIndex == 0) {
		/* 
		 * Elided state goes from beginning to end of 
		 * an entire logical line.  This means we can
		 * update the line's pixel height, and bring
		 * its pixel calculation up to date.
		 */
		dlPtr->index.linePtr->pixelCalculationEpoch 
		  = textPtr->dInfoPtr->lineMetricUpdateEpoch;

		if (dlPtr->index.linePtr->pixelHeight != 0) {
		    TkBTreeAdjustPixelHeight(dlPtr->index.linePtr, 0);
		}
	    }
	    TkTextFreeElideInfo(&info);
	    return dlPtr;
	}
    }
    TkTextFreeElideInfo(&info);
    
    /*
     * 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 logical 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.
	 * 
	 * This also means that each logical line which is entirely
	 * elided still gets laid out into a DLine, but with zero height.
	 * This isn't particularly a problem, but it does seem somewhat
	 * unnecessary.  If/when we fix [Tk Bug 443848] (see below)
	 * then we will probably have to remove such zero height DLines
	 * too. 
	 */
	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 (!elide && 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;
	    }
	    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;
	    if (elide && segPtr == NULL) {
		/* 
		 * An elided section started on this line, and carries on
		 * until the newline.  Currently this forces a new line
		 * anyway (i.e. even though the newline is elided it
		 * still takes effect).  This is because the code 
		 * currently doesn't allow two or more logical lines to
		 * appear on the same display line. [Tk Bug #443848]
		 */
	    }
	}

	chunkPtr = NULL;
    }
    if (noCharsYet) {
	Tcl_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, xPixelOffset, maxOffset, lineHeight;

    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, DLINE_UNLINK);
    }
    if (index.byteIndex == 0) {
	lineHeight = 0;
    } else {
	lineHeight = -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 - dInfoPtr->newTopPixelOffset;
    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->flags |= OLD_Y_INVALID;
		}
	    }
	    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->flags |= OLD_Y_INVALID;
		}
		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, DLINE_FREE);
	    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;
	if (lineHeight != -1) {
	    lineHeight += 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, DLINE_FREE);
		prevPtr->nextPtr = nextPtr;
		dlPtr = nextPtr;
	    }
	    
	    if ((lineHeight != -1) 
	      && (lineHeight > prevPtr->index.linePtr->pixelHeight)) {
		/* 
		 * The logical line height we just calculated is actually
		 * larger than the currently cached height of the
		 * text line.  That is fine (the text line heights
		 * are only calculated asynchronously), but we must
		 * update the cached height so that any counts made
		 * with DLine pointers do not exceed counts made
		 * through the BTree.  
		 */
		TkBTreeAdjustPixelHeight(prevPtr->index.linePtr, lineHeight);
		/* 
		 * I believe we can be 100% sure that we started at the
		 * beginning of the logical line, so we can also adjust
		 * the 'pixelCalculationEpoch' to mark it as being up to
		 * date.  There is a slight concern that we might not
		 * have got this right for the first line in the
		 * re-display.
		 */
		prevPtr->index.linePtr->pixelCalculationEpoch = 
			dInfoPtr->lineMetricUpdateEpoch;
	    }
	    lineHeight = 0;
	}

	/*
	 * 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, DLINE_UNLINK);

    /*
     *--------------------------------------------------------------
     * 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.
     * 
     * Since the top line may only be partially visible, we try first
     * to simply show more pixels from that line (newTopPixelOffset).
     * If that isn't enough, we have to layout more lines.
     *--------------------------------------------------------------
     */

    if (y < maxY) {
	int spaceLeft = maxY - y;
	
	if (spaceLeft <= dInfoPtr->newTopPixelOffset) {
	    /* 
	     * We can full up all the needed space just by showing
	     * more of the current top line
	     */
	    dInfoPtr->newTopPixelOffset -= spaceLeft;
	    y += spaceLeft;
	    spaceLeft = 0;
	} else {
	    int lineNum, bytesToCount;
	    DLine *lowestPtr;
    
	    /* 
	     * Add in all of the current top line, which won't
	     * be enough to bring y up to maxY (if it was we
	     * would be in the 'if' block above).
	     */
	    y += dInfoPtr->newTopPixelOffset;
	    dInfoPtr->newTopPixelOffset = 0;

	    /*
	     * 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--) {
		int pixelHeight = 0;
		
		index.linePtr = TkBTreeFindLine(textPtr->tree, lineNum);
		index.byteIndex = 0;
		lowestPtr = NULL;

		do {
		    dlPtr = LayoutDLine(textPtr, &index);
		    pixelHeight += dlPtr->height;
		    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));
		
		/* 
		 * We may not have examined the entire line (depending
		 * on the value of 'bytesToCount', so we only want
		 * to set this if it is genuinely bigger).  
		 */
		if (pixelHeight > lowestPtr->index.linePtr->pixelHeight) {
		    TkBTreeAdjustPixelHeight(lowestPtr->index.linePtr, 
			    pixelHeight);
		    if (index.linePtr != lowestPtr->index.linePtr) {
			/* 
			 * We examined the entire line, so can update
			 * the epoch.
			 */
			lowestPtr->index.linePtr->pixelCalculationEpoch = 
				dInfoPtr->lineMetricUpdateEpoch;
		    }
		}

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

		while (lowestPtr != NULL) {
		    dlPtr = lowestPtr;
		    spaceLeft -= dlPtr->height;
		    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);
		    }
		    if (spaceLeft <= 0) {
			break;
		    }
		}
		FreeDLines(textPtr, lowestPtr, (DLine *) NULL, DLINE_FREE);
		bytesToCount = INT_MAX;
	    }
	    /*
	     * We've filled in the space we wanted to, and we
	     * need to store any extra overlap we've just
	     * created for the top line.
	     */
	    if (lineNum >= 0) {
		dInfoPtr->newTopPixelOffset = -spaceLeft;
		if (spaceLeft > 0 ||
			dInfoPtr->newTopPixelOffset >= dInfoPtr->dLinePtr->height) {
		    /* Bad situation */
		    Tcl_Panic("Pixel height problem while laying out text widget");
		}
	    }
	}

	/*
	 * 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 - dInfoPtr->newTopPixelOffset;
	for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL;
		dlPtr = dlPtr->nextPtr) {
	    if (y > dInfoPtr->maxY) {
		Tcl_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->flags |= OLD_Y_INVALID;
    }
    while (1) {
	if ((dlPtr->flags & TOP_LINE) && (dlPtr != dInfoPtr->dLinePtr)
		&& (dlPtr->flags & HAS_3D_BORDER)) {
	    dlPtr->flags |= OLD_Y_INVALID;
	}
	/*
	 * If the old top-line was not completely showing (i.e. the
	 * pixelOffset is non-zero) and is no longer the top-line, then
	 * we must re-draw it.
	 */
	if ((dlPtr->flags & TOP_LINE) 
		&& dInfoPtr->topPixelOffset!=0 && dlPtr!=dInfoPtr->dLinePtr) {
	    dlPtr->flags |= OLD_Y_INVALID;
	}
	if ((dlPtr->flags & BOTTOM_LINE) && (dlPtr->nextPtr != NULL)
		&& (dlPtr->flags & HAS_3D_BORDER)) {
	    dlPtr->flags |= OLD_Y_INVALID;
	}
	if (dlPtr->nextPtr == NULL) {
	    if ((dlPtr->flags & HAS_3D_BORDER)
		    && !(dlPtr->flags & BOTTOM_LINE)) {
		dlPtr->flags |= OLD_Y_INVALID;
	    }
	    dlPtr->flags &= ~TOP_LINE;
	    dlPtr->flags |= BOTTOM_LINE;
	    break;
	}
	dlPtr->flags &= ~(TOP_LINE|BOTTOM_LINE);
	dlPtr = dlPtr->nextPtr;
    }
    dInfoPtr->dLinePtr->flags |= TOP_LINE;
    dInfoPtr->topPixelOffset = dInfoPtr->newTopPixelOffset;

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

    xPixelOffset = dInfoPtr->newXPixelOffset;
    if (xPixelOffset > maxOffset) {
	xPixelOffset = maxOffset;
    }
    if (xPixelOffset < 0) {
	xPixelOffset = 0;
    }
    
    /* 
     * Here's a problem: see the tests textDisp-29.2.1-4
     * 
     * If the widget is being created, but has not yet been configured
     * it will have a maxY of 1 above, and we we won't have examined
     * all the lines (just the first line, in fact), and so maxOffset
     * will not be a true reflection of the widget's lines.  Therefore
     * we must not overwrite the original newXPixelOffset in this case.
     */
    if (!(((Tk_FakeWin *) (textPtr->tkwin))->flags & TK_NEED_CONFIG_NOTIFY)) {
	dInfoPtr->newXPixelOffset = xPixelOffset;
    }
    
    if (xPixelOffset != dInfoPtr->curXPixelOffset) {
	dInfoPtr->curXPixelOffset = xPixelOffset;
	for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL;
		dlPtr = dlPtr->nextPtr) {
	    dlPtr->flags |= OLD_Y_INVALID;
	}
    }
}

/*
 *----------------------------------------------------------------------
 *
 * 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, action)
    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 action;                 /* DLINE_UNLINK means DLines are
				 * currently linked into the list
				 * rooted at
				 * textPtr->dInfoPtr->dLinePtr and
				 * they have to be unlinked.
				 * DLINE_FREE means just free without
				 * unlinking.  DLINE_FREE_TEMP means
				 * the DLine given is just a
				 * temporary one and we shouldn't
				 * invalidate anything for the
				 * overall widget. */
{
    register TkTextDispChunk *chunkPtr, *nextChunkPtr;
    register DLine *nextDLinePtr;

    if (action == DLINE_FREE_TEMP) {
	lineHeightsRecalculated++;
	if (tkTextDebug) {
	    char string[TK_POS_CHARS];

	    /*
	     * Debugging is enabled, so keep a log of all the lines
	     * whose height was recalculated.  The test suite uses this
	     * information.
	     */

	    TkTextPrintIndex(&firstPtr->index, string);
	    Tcl_SetVar2(textPtr->interp, "tk_textHeightCalc", (char *) NULL,
		string, TCL_GLOBAL_ONLY|TCL_APPEND_VALUE|TCL_LIST_ELEMENT);
	}
    } else if (action == DLINE_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;
    }
    if (action != DLINE_FREE_TEMP) {
        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, y_off;

    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_TEXT_STATE_NORMAL) {
	for (chunkPtr = dlPtr->chunkPtr; (chunkPtr != NULL);
		chunkPtr = chunkPtr->nextPtr) {
	    if (chunkPtr->displayProc == TkTextInsertDisplayProc) {
		int x = chunkPtr->x + dInfoPtr->x - dInfoPtr->curXPixelOffset;
		(*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.
     */

    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;
	}
	/* 
	 * 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) {
	    int x = chunkPtr->x + dInfoPtr->x - dInfoPtr->curXPixelOffset;

	    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).
		 */
		x = -chunkPtr->width;
	    }
	    (*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 first or 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;
    }
    if (dlPtr->y < dInfoPtr->y) {
	y_off = dInfoPtr->y - dlPtr->y;
	height -= y_off;
    } else {
	y_off = 0;
    }
    XCopyArea(display, pixmap, Tk_WindowId(textPtr->tkwin), dInfoPtr->copyGC,
	    dInfoPtr->x, y_off, (unsigned) (dInfoPtr->maxX - dInfoPtr->x),
	    (unsigned) height, dInfoPtr->x, dlPtr->y + y_off);
    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->curXPixelOffset;
    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;
	    }
	}
    }
}

/*
 *----------------------------------------------------------------------
 *
 * AsyncUpdateLineMetrics --
 *
 *	This procedure is invoked as a background handler to update the
 *	pixel-height calculations of individual lines in an
 *	asychronous manner.
 *	
 *	Currently a timer-handler is used for this purpose, which
 *	continuously reschedules itself.  It may well be better to
 *	use some other approach (e.g. a background thread).  We can't
 *	use an idle-callback because of a known bug in Tcl/Tk in
 *	which idle callbacks are not allowed to re-schedule
 *	themselves.  This just causes an effective infinite loop.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Line heights may be recalculated.
 *
 *----------------------------------------------------------------------
 */

static void
AsyncUpdateLineMetrics(clientData)
    ClientData clientData;	/* Information about widget. */
{
    register TkText *textPtr = (TkText *) clientData;
    TextDInfo *dInfoPtr = textPtr->dInfoPtr;
    int lineNum;
    
    dInfoPtr->lineUpdateTimer = NULL;

    if ((textPtr->tkwin == NULL) || (textPtr->flags & DESTROYED)) {

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

	if (--textPtr->refCount == 0) {
	    ckfree((char *) textPtr);
	}
	return;
    }

    if (dInfoPtr->flags & REDRAW_PENDING) {
	dInfoPtr->lineUpdateTimer = Tcl_CreateTimerHandler(1, 
		AsyncUpdateLineMetrics, clientData);
	return;
    }

    lineNum = dInfoPtr->currentMetricUpdateLine;
    if (lineNum == -1) {
	dInfoPtr->lastMetricUpdateLine = 0;
    }
    /*
     * Update the lines in blocks of about 24 recalculations,
     * or 250+ lines examined, so we pass in 256 for 'doThisMuch'.
     */
    lineNum = TkTextUpdateLineMetrics(textPtr, lineNum, 
	    dInfoPtr->lastMetricUpdateLine, 256);

    if (tkTextDebug) {
	char buffer[2 * TCL_INTEGER_SPACE + 1];

	sprintf(buffer, "%d %d", lineNum, dInfoPtr->lastMetricUpdateLine);
	Tcl_SetVar2(textPtr->interp, "tk_textInvalidateLine", (char *) NULL, 
		    buffer, TCL_GLOBAL_ONLY|TCL_APPEND_VALUE|TCL_LIST_ELEMENT);
    }
    
    if (lineNum == dInfoPtr->lastMetricUpdateLine) {
	/*
	 * We have looped over all lines, so we're done.  We must
	 * release our refCount on the widget (the timer token
	 * was already set to NULL above).
	 */
	textPtr->refCount--;
	if (textPtr->refCount == 0) {
	    ckfree((char *) textPtr);
	}
	return;
    }
    dInfoPtr->currentMetricUpdateLine = lineNum;

    /* 
     * Re-arm the timer.  We already have a refCount on the text widget
     * so no need to adjust that.
     */
    dInfoPtr->lineUpdateTimer = Tcl_CreateTimerHandler(1, 
	    AsyncUpdateLineMetrics, (ClientData) textPtr);
}

/*
 *----------------------------------------------------------------------
 *
 * TkTextUpdateLineMetrics --
 *
 *	This procedure updates the pixel height calculations of a
 *	range of lines in the widget.  The range is from lineNum to
 *	endLine, but, if doThisMuch is positive, then the procedure
 *	may return earlier, once a certain number of lines has been
 *	examined.  The line counts are from 0.
 *	
 *	If doThisMuch is -1, then all lines in the range will be
 *	updated.  This will potentially take quite some time for
 *	a large text widget.
 *	
 *	Note: with bad input for lineNum and endLine, this procedure can
 *	loop indefinitely.
 *
 * Results:
 *	The index of the last line examined (or -1 if we are about to
 *	wrap around from end to beginning of the widget, and the next
 *	line will be the first line).
 *
 * Side effects:
 *	Line heights may be recalculated.
 *
 *----------------------------------------------------------------------
 */

int
TkTextUpdateLineMetrics(textPtr, lineNum, endLine, doThisMuch)
    TkText *textPtr;	/* Information about widget. */
    int lineNum;        /* Start at this line */
    int endLine;        /* Go no further than this line */
    int doThisMuch;     /* How many lines to check, or how many 10s of
                         * lines to recalculate.  If '-1' then do
                         * everything in the range (which may take a
                         * while). */
{
    TkTextLine *linePtr = NULL;
    int count = 0;
    int totalLines = TkBTreeNumLines(textPtr->tree);
    
    while (1) {
	/* Get a suitable line */
	if (lineNum == -1 && linePtr == NULL) {
	    lineNum = 0;
	    linePtr = TkBTreeFindLine(textPtr->tree, lineNum);
	} else {
	    if (lineNum == -1 || linePtr == NULL) {
		if (lineNum == -1) {
		    lineNum = 0;
		}
		linePtr = TkBTreeFindLine(textPtr->tree, lineNum);
	    } else {
		lineNum++;
		linePtr = TkBTreeNextLine(linePtr);
	    }
	    if (lineNum == endLine) {
		/* 
		 * We have looped over all lines, so we're done.
		 */ 
		break;
	    }
	}

	if (lineNum < totalLines) {
	    if (tkTextDebug) {
		char buffer[4 * TCL_INTEGER_SPACE + 3];

		sprintf(buffer, "%d %d %d %d", lineNum, endLine, 
			totalLines, count);
		Tcl_SetVar2(textPtr->interp, "tk_textInvalidateLine", 
			    (char *) NULL, buffer, 
			    TCL_GLOBAL_ONLY|TCL_APPEND_VALUE|TCL_LIST_ELEMENT);
	    }
	    
	    /* Now update the line's metrics if necessary */
	    if (linePtr->pixelCalculationEpoch 
		!= textPtr->dInfoPtr->lineMetricUpdateEpoch) {
		if (doThisMuch == -1) {
		    count += 8 * TkTextUpdateOneLine(textPtr, linePtr, 
						     0, NULL);
		} else {
		    TkTextIndex index;
		    TkTextIndex *indexPtr;
		    int pixelHeight;
		    
		    /* 
		     * If the metric epoch is the same as the widget's 
		     * epoch, then we know that indexPtrs are still
		     * valid, and if the cached metricIndex (if any) is
		     * for the same line as we wish to examine, then
		     * we are looking at a long line wrapped many
		     * times, which we will examine in pieces.
		     */
		    if (textPtr->dInfoPtr->metricEpoch == textPtr->stateEpoch 
		      && textPtr->dInfoPtr->metricIndex.linePtr == linePtr) {
			indexPtr = &textPtr->dInfoPtr->metricIndex;
			pixelHeight = textPtr->dInfoPtr->metricPixelHeight;
		    } else {
			/* 
			 * We must reset the partial line height
			 * calculation data here, so we don't use
			 * it when it is out of date.
			 */
			textPtr->dInfoPtr->metricEpoch = -1;
			index.tree = textPtr->tree;
			index.linePtr = linePtr;
			index.byteIndex = 0;
			index.textPtr = NULL;
			indexPtr = &index;
			pixelHeight = 0;
		    }
		    /* 
		     * Update the line and update the counter, counting
		     * 8 for each display line we actually re-layout.
		     */
		    count += 8 * TkTextUpdateOneLine(textPtr, linePtr, 
						     pixelHeight, indexPtr);
		    
		    if (indexPtr->linePtr == linePtr) {
			/* 
			 * We didn't complete the logical line, because it
			 * produced very many display lines -- it must be a
			 * long line wrapped many times.  So we must
			 * cache as far as we got for next time around.
			 */
			if (pixelHeight == 0) {
			    textPtr->dInfoPtr->metricIndex = index;
			    textPtr->dInfoPtr->metricEpoch = textPtr->stateEpoch;
			}
			textPtr->dInfoPtr->metricPixelHeight = linePtr->pixelHeight;
			break;
		    }
		}
	    } else {
		/* 
		 * This line is already up to date.   That means there's
		 * nothing to do here.
		 */
	    }
	} else {
	    /* 
	     * We must never recalculate the height of the
	     * last artificial line.  It must stay at zero, and
	     * if we recalculate it, it will change.
	     */
	    
	    if (endLine >= totalLines) {
		lineNum = endLine;
		break;
	    }
	    /* Set things up for the next loop through */
	    lineNum = -1;
	}
	count++;
	
	if (doThisMuch != -1 && count >= doThisMuch) {
	    break;
	}
    }
    if (doThisMuch == -1) {
	/* 
	 * If we were requested to provide a full update,
	 * then also update the scrollbar.
	 */
	GetYView(textPtr->interp, textPtr, 1);
    }
    return lineNum;
}

/*
 *----------------------------------------------------------------------
 *
 * TkTextInvalidateLineMetrics --
 *
 *	Mark a number of text lines as having invalid line metric
 *	calculations.  Never call this with linePtr as the last
 *	(artificial) line in the text.  Depending on 'action' which
 *	indicates whether the given lines are simply invalid or have
 *	been inserted or deleted, the pre-existing asynchronous line
 *	update range may need to be adjusted.
 *	
 *	If linePtr is NULL then 'lineCount' and 'action' are ignored and
 *	all lines are invalidated.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	May schedule an asychronous callback. 
 *
 *----------------------------------------------------------------------
 */

void
TkTextInvalidateLineMetrics(textPtr, linePtr, lineCount, action)
    TkText *textPtr;		/* Widget record for text widget. */
    TkTextLine *linePtr;        /* Invalidation starts from this line. */
    int lineCount;              /* And includes this many following 
                                 * lines. */
    int action;                 /* Indicates what type of invalidation
                                 * occurred (insert, delete, or
                                 * simple). */ 
{
    int fromLine;
    TextDInfo *dInfoPtr = textPtr->dInfoPtr;

    if (linePtr != NULL) {
	int counter = lineCount;
	
	fromLine = TkBTreeLineIndex(linePtr);

	/* 
	 * Invalidate the height calculations of each line in the
	 * given range.
	 */
	linePtr->pixelCalculationEpoch = 0;
	while (counter > 0 && linePtr != 0) {
	    linePtr = TkBTreeNextLine(linePtr);
	    if (linePtr != NULL) {
		linePtr->pixelCalculationEpoch = 0;
	    }
	    counter--;
	}
	/* 
	 * Now schedule an examination of each line in the union of the
	 * old and new update ranges, including the (possibly empty)
	 * range in between.  If that between range is not-empty, then we
	 * are examining more lines than is strictly necessary (but the
	 * examination of the extra lines should be quick, since their
	 * pixelCalculationEpoch will be up to date).  However, to keep
	 * track of that would require more complex record-keeping that
	 * what we have.
	 */
	if (dInfoPtr->lineUpdateTimer == NULL) {
	    dInfoPtr->currentMetricUpdateLine = fromLine;
	    if (action == TK_TEXT_INVALIDATE_DELETE) {
		lineCount = 0;
	    }
	    dInfoPtr->lastMetricUpdateLine = fromLine + lineCount + 1;
	} else {
	    int toLine = fromLine + lineCount + 1;
	    
	    if (action == TK_TEXT_INVALIDATE_DELETE) {
		if (toLine <= dInfoPtr->currentMetricUpdateLine) {
		    dInfoPtr->currentMetricUpdateLine = fromLine;
		    if (dInfoPtr->lastMetricUpdateLine != -1) {
			dInfoPtr->lastMetricUpdateLine -= lineCount;
		    }
		} else if (fromLine <= dInfoPtr->currentMetricUpdateLine) {
		    dInfoPtr->currentMetricUpdateLine = fromLine;
		    if (toLine <= dInfoPtr->lastMetricUpdateLine) {
			dInfoPtr->lastMetricUpdateLine -= lineCount;
		    }
		} else {
		    if (dInfoPtr->lastMetricUpdateLine != -1) {
			dInfoPtr->lastMetricUpdateLine = toLine;
		    }
		}
	    } else if (action == TK_TEXT_INVALIDATE_INSERT) {
		if (toLine <= dInfoPtr->currentMetricUpdateLine) {
		    dInfoPtr->currentMetricUpdateLine = fromLine;
		    if (dInfoPtr->lastMetricUpdateLine != -1) {
			dInfoPtr->lastMetricUpdateLine += lineCount;
		    }
		} else if (fromLine <= dInfoPtr->currentMetricUpdateLine) {
		    dInfoPtr->currentMetricUpdateLine = fromLine;
		    if (toLine <= dInfoPtr->lastMetricUpdateLine) {
			dInfoPtr->lastMetricUpdateLine += lineCount;
		    }
		    if (toLine > dInfoPtr->lastMetricUpdateLine) {
			dInfoPtr->lastMetricUpdateLine = toLine;
		    }
		} else {
		    if (dInfoPtr->lastMetricUpdateLine != -1) {
			dInfoPtr->lastMetricUpdateLine = toLine;
		    }
		}
	    } else {
		if (fromLine < dInfoPtr->currentMetricUpdateLine) {
		    dInfoPtr->currentMetricUpdateLine = fromLine;
		}
		if (dInfoPtr->lastMetricUpdateLine != -1
		  && toLine > dInfoPtr->lastMetricUpdateLine) {
		    dInfoPtr->lastMetricUpdateLine = toLine;
		}
	    }
	}
    } else {
	/*
	 * This invalidates the height of all lines in the widget.
	 */
	if ((++dInfoPtr->lineMetricUpdateEpoch) == 0) {
	    dInfoPtr->lineMetricUpdateEpoch++;
	}
	/* 
	 * This has the effect of forcing an entire new loop
	 * of update checks on all lines in the widget.
	 */
	if (dInfoPtr->lineUpdateTimer == NULL) {
	    dInfoPtr->currentMetricUpdateLine = -1;
	}
	dInfoPtr->lastMetricUpdateLine = dInfoPtr->currentMetricUpdateLine;
    }
    
    /* 
     * Now re-set the current update calculations 
     */
    if (dInfoPtr->lineUpdateTimer == NULL) {
	textPtr->refCount++;
	dInfoPtr->lineUpdateTimer = Tcl_CreateTimerHandler(1, 
		AsyncUpdateLineMetrics, (ClientData) textPtr);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * TkTextFindDisplayLineEnd --
 *
 *	This procedure is invoked to find the index of the beginning or
 *	end of the particular display line on which the given index sits,
 *	whether that line is displayed or not.
 *	
 *	If 'end' is zero, we look for the start, and if 'end' is one 
 *	we look for the end.
 *	
 *	If the beginning of the current display line is elided, and we
 *	are looking for the start of the line, then the returned index
 *	will be the first elided index on the display line. 
 *	
 *	Similarly if the end of the current display line is elided
 *	and we are looking for the end, then the returned index will
 *	be the last elided index on the display line.  (NB. This also
 *	highlights a current bug in the text widget that we cannot
 *	place two logical lines on a single display line -- even
 *	though the newline in this case is elided, it still causes
 *	a line break to be shown).
 *	
 * Results:
 *	Modifies indexPtr to point to the given end.
 *	
 *	If xOffset is non-NULL, it is set to the x-pixel offset of the
 *	given original index within the given display line.
 *
 * Side effects:
 *	The combination of 'LayoutDLine' and 'FreeDLines' seems
 *	like a rather time-consuming way of gathering the information
 *	we need, so this would be a good place to look to speed up
 *	the calculations.  In particular these calls will map and
 *	unmap embedded windows respectively, which I would hope isn't
 *	exactly necessary!
 *
 *----------------------------------------------------------------------
 */

void
TkTextFindDisplayLineEnd(textPtr, indexPtr, end, xOffset)
    TkText *textPtr;         /* Widget record for text widget. */
    TkTextIndex *indexPtr;   /* Index we will adjust to the display
                              * line start or end. */
    int end;                 /* 0 = start, 1 = end. */
    int *xOffset;            /* NULL, or used to store the x-pixel
                              * offset of the original index within
                              * its display line. */
{
    if (!end && indexPtr->byteIndex == 0) {
	/* Nothing to do */
	if (xOffset != NULL) {
	    *xOffset = 0;
	}
        return;
    } else {
	TkTextIndex index = *indexPtr;
	index.byteIndex = 0;
	index.textPtr = NULL;
	
	while (1) {
	    DLine *dlPtr;
	    int byteCount;
	    
	    dlPtr = LayoutDLine(textPtr, &index);
	    byteCount = dlPtr->byteCount;

	    /* 
	     * 'byteCount' goes up to the beginning of the next line,
	     * so equality here says we need one more line
	     */
	    if (index.byteIndex + byteCount > indexPtr->byteIndex) {
		/* It's on this display line */
		if (xOffset != NULL) {
		    /* 
		     * This call takes a byte index relative to the
		     * start of the current _display_ line, not
		     * logical line.  We are about to overwrite
		     * indexPtr->byteIndex, so we must do this now.
		     */
		    *xOffset = DlineXOfIndex(textPtr, dlPtr, 
				indexPtr->byteIndex - dlPtr->index.byteIndex);
		}
		indexPtr->byteIndex = index.byteIndex;
		if (end) {
		    /* 
		     * The index we want is one less than the number
		     * of bytes in the display line.
		     */
		    indexPtr->byteIndex += byteCount - sizeof(char);
		}
		FreeDLines(textPtr, dlPtr, NULL, DLINE_FREE_TEMP);
		return;
	    }
	    FreeDLines(textPtr, dlPtr, NULL, DLINE_FREE_TEMP);
	    TkTextIndexForwBytes(&index, byteCount, &index);
	}
    }
}

/*
 *----------------------------------------------------------------------
 *
 * CalculateDisplayLineHeight --
 *
 *	This procedure is invoked to recalculate the height of the
 *	particular display line which starts with the given index, 
 *	whether that line is displayed or not.
 *	
 *	This procedure does not, in itself, update any cached
 *	information about line heights.  That should be done, where
 *	necessary, by its callers.
 *	
 *	The behaviour of this procedure is _undefined_ if indexPtr
 *	is not currently at the beginning of a display line.
 *	
 * Results:
 *	The number of vertical pixels used by the display line.
 *	
 *	If 'byteCountPtr' is non-NULL, then returns in that pointer
 *	the number of byte indices on the given display line (which
 *	can be used to update indexPtr in a loop).
 *
 * Side effects:
 *	The combination of 'LayoutDLine' and 'FreeDLines' seems
 *	like a rather time-consuming way of gathering the information
 *	we need, so this would be a good place to look to speed up
 *	the calculations.  In particular these calls will map and
 *	unmap embedded windows respectively, which I would hope isn't
 *	exactly necessary!
 *
 *----------------------------------------------------------------------
 */

static int
CalculateDisplayLineHeight(textPtr, indexPtr, byteCountPtr)
    TkText *textPtr;             /* Widget record for text widget. */
    CONST TkTextIndex *indexPtr; /* The index at the beginning of the
				  * display line of interest. */
    int *byteCountPtr;           /* NULL or used to return the number of
				  * byte indices on the given display
				  * line. */
{
    DLine *dlPtr;
    int pixelHeight;
    
    /* 
     * Layout, find the information we need and then free the
     * display-line we laid-out.  We must use 'FreeDLines' because it
     * will actually call the relevant code to unmap any embedded windows
     * which were mapped in the LayoutDLine call!
     */
    dlPtr = LayoutDLine(textPtr, indexPtr);
    pixelHeight = dlPtr->height;
    if (byteCountPtr != NULL) {
        *byteCountPtr = dlPtr->byteCount;
    }
    FreeDLines(textPtr, dlPtr, NULL, DLINE_FREE_TEMP);

    return pixelHeight;
}

/*
 *----------------------------------------------------------------------
 *
 * TkTextIndexYPixels --
 *
 *	This procedure is invoked to calculate the number of vertical
 *	pixels between the first index of the text widget and the given
 *	index.  The range from first logical line to given logical
 *	line is determined using the cached values, and the range
 *	inside the given logical line is calculated on the fly.
 *	
 * Results:
 *	The pixel distance between first pixel in the widget and the
 *	top of the index's current display line (could be zero).
 *
 * Side effects:
 *	Just those of 'CalculateDisplayLineHeight'.
 *
 *----------------------------------------------------------------------
 */

int
TkTextIndexYPixels(textPtr, indexPtr)
    TkText *textPtr;              /* Widget record for text widget. */
    CONST TkTextIndex *indexPtr;  /* The index of which we want the pixel
				   * distance from top of logical line to
				   * top of index. */
{
    int pixelHeight;
    TkTextIndex index;
    
    pixelHeight = TkBTreePixels(indexPtr->linePtr);
    
    /* 
     * Iterate through all display-lines corresponding to the single
     * logical line belonging to indexPtr, adding up the pixel height of
     * each such display line as we go along, until we go past
     * 'indexPtr'.
     */

    if (indexPtr->byteIndex == 0) {
        return pixelHeight;
    }
    
    index.tree = textPtr->tree;
    index.linePtr = indexPtr->linePtr;
    index.byteIndex = 0;
    index.textPtr = NULL;
    
    while (1) {
	int bytes, height;
	
	/* 
	 * Currently this call doesn't have many side-effects. 
	 * However, if in the future we change the code so there
	 * are side-effects (such as adjusting linePtr->pixelHeight),
	 * then the code might not quite work as intended,
	 * specifically the 'linePtr->pixelHeight == pixelHeight' test 
	 * below this while loop.
	 */
	height = CalculateDisplayLineHeight(textPtr, &index, &bytes);
	
	index.byteIndex += bytes;
	
	if (index.byteIndex > indexPtr->byteIndex) {
	    return pixelHeight;
	}
	
	if (height > 0) {
	    pixelHeight += height;
	}

	if (index.byteIndex == indexPtr->byteIndex) {
	    return pixelHeight;
	}
    }
}

/*
 *----------------------------------------------------------------------
 *
 * TkTextUpdateOneLine --
 *
 *	This procedure is invoked to recalculate the height of a
 *	particular logical line, whether that line is displayed or not.
 *	
 *	It must NEVER be called for the artificial last TkTextLine
 *	which is used internally for administrative purposes only.
 *	That line must retain its initial height of 0 otherwise
 *	the pixel height calculation maintained by the B-tree will
 *	be wrong.
 *	
 * Results:
 *	The number of display lines in the logical line.  This could
 *	be zero if the line is totally elided.
 *
 * Side effects:
 *	Line heights may be recalculated, and a timer to update
 *	the scrollbar may be installed.  Also see the called 
 *	function 'CalculateDisplayLineHeight' for its side
 *	effects.
 *
 *----------------------------------------------------------------------
 */

int
TkTextUpdateOneLine(textPtr, linePtr, pixelHeight, indexPtr)
    TkText *textPtr;        /* Widget record for text widget. */
    TkTextLine *linePtr;    /* The line of which to calculate the
                             * height. */
    int pixelHeight;        /* If indexPtr is non-NULL, then this
                             * is the number of pixels in the logical
                             * line linePtr, up to the index which
                             * has been given. */
    TkTextIndex *indexPtr;  /* Either NULL or an index at the start of
                             * a display line belonging to linePtr,
                             * up to which we have already calculated. */
{
    TkTextIndex index;
    int displayLines, partialCalc;
    
    if (indexPtr == NULL) {
	index.tree = textPtr->tree;
	index.linePtr = linePtr;
	index.byteIndex = 0;
	index.textPtr = NULL;
	indexPtr = &index;
	pixelHeight = 0;
	partialCalc = 0; 
    } else {
	partialCalc = 1; 
    }
    
    /* 
     * Iterate through all display-lines corresponding to the
     * single logical line 'linePtr', adding up the pixel height
     * of each such display line as we go along.  The final
     * total is, therefore, the height of the logical line.
     */

    displayLines = 0;
    
    while (1) {
	int bytes, height;
	
	/* 
	 * Currently this call doesn't have many side-effects. 
	 * However, if in the future we change the code so there
	 * are side-effects (such as adjusting linePtr->pixelHeight),
	 * then the code might not quite work as intended,
	 * specifically the 'linePtr->pixelHeight == pixelHeight' test 
	 * below this while loop.
	 */
	height = CalculateDisplayLineHeight(textPtr, indexPtr, &bytes);
	
	if (height > 0) {
	    pixelHeight += height;
	    displayLines++;
	}
	
	if (TkTextIndexForwBytes(indexPtr, bytes, indexPtr)) {
            break;
        }

	if (indexPtr->linePtr != linePtr) {
	    /* 
	     * If we reached the end of the logical line, then
	     * either way we don't have a partial calculation.
	     */
	    partialCalc = 0;
	    break;
	}
	if (partialCalc && displayLines > 50) {
	    /* 
	     * Only calculate 50 display lines at a time, to 
	     * avoid huge delays.  In any case it is very rare
	     * that a single line wraps 50 times!
	     */
	    break;
	}
    }
    
    if (!partialCalc) {
	/* 
	 * Mark the logical line as being up to date (caution: it isn't
	 * yet up to date, that will happen in TkBTreeAdjustPixelHeight
	 * just below).
	 */
	linePtr->pixelCalculationEpoch = textPtr->dInfoPtr->lineMetricUpdateEpoch;
	/* 
	 * Also cancel any partial line height calculation state.
	 */
	textPtr->dInfoPtr->metricEpoch = -1;

	if (linePtr->pixelHeight == pixelHeight) {
	    return displayLines;
	}
    }
    
    /* 
     * We set the line's height, but the return value is now the height
     * of the entire widget, which may be used just below for
     * reporting/debugging purposes.
     */
    pixelHeight = TkBTreeAdjustPixelHeight(linePtr, pixelHeight);
    
    if (tkTextDebug) {
	char buffer[2 * TCL_INTEGER_SPACE + 1];

	if (TkBTreeNextLine(linePtr) == NULL) {
	    Tcl_Panic("Mustn't ever update line height of last artificial line");
	}

	sprintf(buffer, "%d %d", TkBTreeLineIndex(linePtr), pixelHeight);
	Tcl_SetVar2(textPtr->interp, "tk_textNumPixels", (char *) NULL, 
		    buffer, TCL_GLOBAL_ONLY|TCL_APPEND_VALUE|TCL_LIST_ELEMENT);
    }
    if (textPtr->dInfoPtr->scrollbarTimer == NULL) {
	textPtr->refCount++;
        textPtr->dInfoPtr->scrollbarTimer = Tcl_CreateTimerHandler(200, 
		AsyncUpdateYScrollbar, (ClientData) textPtr);
    }
    return displayLines;
}

/*
 *----------------------------------------------------------------------
 *
 * 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;
    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) || (textPtr->flags & DESTROYED)) {

	/*
	 * 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) || (textPtr->flags & DESTROYED)) {

	/*
	 * 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) || (textPtr->flags & DESTROYED)) {

	/*
	 * 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) {
	textPtr->refCount++;
	dInfoPtr->flags &= ~REPICK_NEEDED;
	TkTextPickCurrent(textPtr, &textPtr->pickEvent);
	if (--textPtr->refCount == 0) {
	    ckfree((char *) textPtr);
	    goto end;
	}
	if ((textPtr->tkwin == NULL) || (textPtr->flags & DESTROYED)) {
	    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).  We have
     * to be particularly careful with the top and bottom lines of the
     * display, since these may only be partially visible and therefore
     * not helpful for some scrolling purposes.
     */

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

	/* 
	 * These tests are, in order:
	 * 
	 * 1. If the line is already marked as invalid
	 * 2. If the line hasn't moved
	 * 3. If the line overlaps the bottom of the window and we
	 * are scrolling up
	 * 4. If the line overlaps the top of the window and we are
	 * scrolling down
	 * 
	 * If any of these tests are true, then we can't scroll this
	 * line's part of the display.
	 * 
	 * Note that even if tests 3 or 4 aren't true, we may be
	 * able to scroll the line, but we still need to be sure
	 * to call embedded window display procs on top and bottom
	 * lines if they have any portion non-visible (see below).
	 */
	if ((dlPtr->flags & OLD_Y_INVALID) 
	    || (dlPtr->y == dlPtr->oldY)
	    || (((dlPtr->oldY + dlPtr->height) > dInfoPtr->maxY) 
		&& (dlPtr->y < dlPtr->oldY))
	    || ((dlPtr->oldY < dInfoPtr->y) 
		&& (dlPtr->y > dlPtr->oldY))) {
	    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->flags & OLD_Y_INVALID)
		    || ((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;
	if (y < dInfoPtr->y) {
	    /* 
	     * Adjust if the area being copied is going to overwrite
	     * the top border of the window (so the top line is only
	     * half onscreen).
	     */
	    int y_off = dInfoPtr->y - dlPtr->y;
	    height -= y_off;
	    oldY += y_off;
	    y = dInfoPtr->y;
	}
	
	/*
	 * Update the lines we are going to scroll to show that they
	 * have been copied.
	 */

	while (1) {
	    /* The DLine already has OLD_Y_INVALID cleared */
	    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->flags & OLD_Y_INVALID))
		    && ((dlPtr2->oldY + dlPtr2->height) > y)
		    && (dlPtr2->oldY < (y + height))) {
		dlPtr2->flags |= OLD_Y_INVALID;
	    }
	}

	/*
	 * 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->flags & OLD_Y_INVALID) || (dlPtr->oldY != dlPtr->y))) {
	    maxHeight = dlPtr->height;
	}
	bottomY = dlPtr->y + dlPtr->height;
    }
    /* 
     * There used to be a line here which restricted 'maxHeight' to be no
     * larger than 'dInfoPtr->maxY', but this is incorrect for the case
     * where individual lines may be taller than the widget _and_ we have
     * smooth scrolling.  What we can do is restrict maxHeight to be
     * no larger than 'dInfoPtr->maxY + dInfoPtr->topPixelOffset'.
     */
    if (maxHeight > (dInfoPtr->maxY + dInfoPtr->topPixelOffset)) {
	maxHeight = (dInfoPtr->maxY + dInfoPtr->topPixelOffset);
    }
    
    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->flags & OLD_Y_INVALID) || 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 | OLD_Y_INVALID);
	    } else if (dlPtr->chunkPtr != NULL && ((dlPtr->y < 0) 
		       || (dlPtr->y + dlPtr->height > dInfoPtr->maxY))) {
		register TkTextDispChunk *chunkPtr;

		/* 
		 * It's the first or last DLine which are also
		 * overlapping the top or bottom of the window, but we
		 * decided above it wasn't necessary to display them (we
		 * were able to update them by scrolling).  This is fine,
		 * except that if the lines contain any embedded windows,
		 * we must still call the display proc on them because
		 * they might need to be unmapped or they might need to
		 * be moved to reflect their new position.  Otherwise,
		 * everything else moves, but the embedded window
		 * doesn't!
		 * 
		 * So, we loop through all the chunks, calling the
		 * display proc of embedded windows only.
		 */
		for (chunkPtr = dlPtr->chunkPtr; (chunkPtr != NULL);
			chunkPtr = chunkPtr->nextPtr) {
		    int x;
		    if (chunkPtr->displayProc != TkTextEmbWinDisplayProc) {
			continue;
		    }
		    x = chunkPtr->x + dInfoPtr->x - dInfoPtr->curXPixelOffset;
		    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).
			 */
			x = -chunkPtr->width;
		    }
		    TkTextEmbWinDisplayProc(chunkPtr, x, dlPtr->spaceAbove,
			dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow,
			dlPtr->baseline - dlPtr->spaceAbove, (Display *) NULL, 
			(Drawable) None, dlPtr->y + dlPtr->spaceAbove);
		}
 	       
	    }
	}
	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) || (textPtr->flags & DESTROYED)) {

	    /*
             * 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) || (textPtr->flags & DESTROYED)) {

	    /*
             * 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->flags & OLD_Y_INVALID)) 
	    && (TkRectInRegion(region, rect.x, dlPtr->y, 
			       rect.width, (unsigned int) dlPtr->height) 
		!= RectangleOut)) {
	    dlPtr->flags |= OLD_Y_INVALID;
	}
    }
    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.
 *	
 *	Note: if the range of indices may change geometry as well
 *	as simply requiring redisplay, then the caller should also
 *	call TkTextInvalidateLineMetrics.
 *
 * 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. */
    CONST TkTextIndex *index1Ptr; /* Index of first character to 
                                   * redisplay. */
    CONST 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, DLINE_UNLINK);
}

/*
 *----------------------------------------------------------------------
 *
 * 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;

    /* 
     * Invalidate the pixel calculation of all lines in the given range.
     * This may be a bit over-aggressive, so we could consider more
     * subtle techniques here in the future.  In particular, when we
     * create a tag for the first time with '.t tag configure foo -font
     * "Arial 20"', say, even though that obviously can't apply to
     * anything at all (the tag didn't exist a moment ago), we invalidate
     * every single line in the widget.
     */
    if (tagPtr->affectsDisplayGeometry) {
        TkTextLine *startLine, *endLine;
	int lineCount;
	if (index2Ptr == NULL) {
	    endLine = NULL;
	    lineCount = TkBTreeNumLines(textPtr->tree);
	} else {
	    endLine = index2Ptr->linePtr;
	    lineCount = TkBTreeLineIndex(endLine);
	}
	if (index1Ptr == NULL) {
	    startLine = NULL;
	} else {
	    startLine = index1Ptr->linePtr;
	    lineCount -= TkBTreeLineIndex(startLine);
	}
	TkTextInvalidateLineMetrics(textPtr, startLine, lineCount, 
				    TK_TEXT_INVALIDATE_ONLY);
    }

    /*
     * 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, DLINE_UNLINK);
	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, mask)
    TkText *textPtr;		/* Widget record for text widget. */
    int mask;                   /* OR'd collection of bits showing what
                                 * has changed */
{
    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, DLINE_UNLINK);
    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;
    }
    /* 
     * This is the only place where dInfoPtr->maxY is set.
     */
    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) {
	TkTextFindDisplayLineEnd(textPtr, &textPtr->topIndex, 0, NULL);
    }

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

    dInfoPtr->xScrollFirst = dInfoPtr->xScrollLast = -1;
    dInfoPtr->yScrollFirst = dInfoPtr->yScrollLast = -1;
    
    if (mask & TK_TEXT_LINE_GEOMETRY) {
        /* 
	 * Set up line metric recalculation.
	 * 
	 * Avoid the special zero value, since that is used to
	 * mark individual lines as being out of date.
	 */
	if ((++dInfoPtr->lineMetricUpdateEpoch) == 0) {
	    dInfoPtr->lineMetricUpdateEpoch++;
	}

	dInfoPtr->currentMetricUpdateLine = -1;
	
	if (dInfoPtr->lineUpdateTimer == NULL) {
	    textPtr->refCount++;
	    dInfoPtr->lineUpdateTimer = Tcl_CreateTimerHandler(1, 
		    AsyncUpdateLineMetrics, (ClientData) textPtr);
	}
    }
}

/*
 *----------------------------------------------------------------------
 *
 * 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 the given index must appear
                  		 * exactly at the top of the screen.
                  		 * TK_TEXT_PICKPLACE (-1) means we get to
                  		 * pick where it appears: minimize screen
                  		 * motion or else display line at center
                  		 * of screen.  TK_TEXT_NOPIXELADJUST (-2)
                  		 * indicates to make the given index the
                  		 * top line, but if it is already the top
                  		 * line, don't nudge it up or down by a
                  		 * few pixels just to make sure it is
                  		 * entirely displayed.  Positive numbers
                  		 * indicate the number of pixels of the
                  		 * index's line which are to be off the
                  		 * top of the screen.  */ 
{
    TextDInfo *dInfoPtr = textPtr->dInfoPtr;
    register DLine *dlPtr;
    int bottomY, close, lineIndex;
    TkTextIndex tmpIndex, rounded;
    int lineHeight;
    
    /*
     * 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(NULL,indexPtr, 1, &rounded, COUNT_INDICES);
	indexPtr = &rounded;
    }

    if (pickPlace == TK_TEXT_NOPIXELADJUST) {
	if (textPtr->topIndex.linePtr == indexPtr->linePtr 
	    && textPtr->topIndex.byteIndex == indexPtr->byteIndex) {
	    pickPlace = dInfoPtr->topPixelOffset;
	} else {
	    pickPlace = 0;
	}
    }
    
    if (pickPlace != TK_TEXT_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.
	 */

	textPtr->topIndex = *indexPtr;
	if (indexPtr->byteIndex != 0) {
	    TkTextFindDisplayLineEnd(textPtr, &textPtr->topIndex, 0, NULL);
	}
	dInfoPtr->newTopPixelOffset = pickPlace;
	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)) {
	    if (dInfoPtr->dLinePtr == dlPtr 
	      && dInfoPtr->topPixelOffset != 0) {
		/* 
		 * It is on the top line, but that line is hanging
		 * off the top of the screen.  Change the top
		 * overlap to zero and update.
		 */
		dInfoPtr->newTopPixelOffset = 0;
		goto scheduleUpdate;
	    }
	    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. 
     * 
     * If the line is not close, place it in the center of the
     * window.  
     */

    lineHeight = CalculateDisplayLineHeight(textPtr, indexPtr, NULL);
    /* 
     * It would be better if 'bottomY' were calculated using the
     * actual height of the given line, not 'textPtr->charHeight'.
     */
    bottomY = (dInfoPtr->y + dInfoPtr->maxY + lineHeight)/2;
    close = (dInfoPtr->maxY - dInfoPtr->y)/3;
    if (close < 3*textPtr->charHeight) {
	close = 3*textPtr->charHeight;
    }
    if (dlPtr != NULL) {
	int overlap;
	/*
	 * 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 counts from the bottom
	 * of the given index upwards, so we add an extra half line
	 * to be sure we count far enough.
	 */

	MeasureUp(textPtr, &textPtr->topIndex, close + textPtr->charHeight/2, 
		&tmpIndex, &overlap);
	if (TkTextIndexCmp(&tmpIndex, indexPtr) <= 0) {
	    textPtr->topIndex = *indexPtr;
	    TkTextFindDisplayLineEnd(textPtr, &textPtr->topIndex, 0, NULL);
	    dInfoPtr->newTopPixelOffset = 0;
	    goto scheduleUpdate;
	}
    } else {
	int overlap;
	/*
	 * 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 + lineHeight 
		- textPtr->charHeight/2, &tmpIndex, &overlap);
	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, 
	    &dInfoPtr->newTopPixelOffset);

    scheduleUpdate:
    if (!(dInfoPtr->flags & REDRAW_PENDING)) {
	Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr);
    }
    dInfoPtr->flags |= REDRAW_PENDING|DINFO_OUT_OF_DATE|REPICK_NEEDED;
}

/*
 *--------------------------------------------------------------
 *
 * TkTextMeasureDown --
 *
 *	Given one index, find the index of the first character
 *	on the highest display line that would be displayed no more
 *	than "distance" pixels below the top of the given index.
 *
 * Results:
 *	The srcPtr is manipulated in place to reflect the new
 *	position.  We return the number of pixels by which 'distance'
 *	overlaps the srcPtr.
 *
 * Side effects:
 *	None.
 *
 *--------------------------------------------------------------
 */

int
TkTextMeasureDown(textPtr, srcPtr, distance)
    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 top pixel in srcPtr's 
                                   logical line. */
{
    TkTextLine *lastLinePtr;
    DLine *dlPtr;
    TkTextIndex loop;

    lastLinePtr =
	    TkBTreeFindLine(textPtr->tree, TkBTreeNumLines(textPtr->tree));

    do {
	dlPtr = LayoutDLine(textPtr, srcPtr);
	dlPtr->nextPtr = NULL;

	if (distance < dlPtr->height) {
	    FreeDLines(textPtr, dlPtr, (DLine *) NULL, DLINE_FREE_TEMP);
	    break;
	}
	distance -= dlPtr->height;
	TkTextIndexForwBytes(srcPtr, dlPtr->byteCount, &loop);
	FreeDLines(textPtr, dlPtr, (DLine *) NULL, DLINE_FREE_TEMP);
	if (loop.linePtr == lastLinePtr) {
	    break;
	}
	*srcPtr = loop;
    } while (distance > 0);

    return distance;
}

/*
 *--------------------------------------------------------------
 *
 * 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.
 *	
 *	If this function is called with distance=0, it simply finds the
 *	first index on the same display line as srcPtr.  However, there
 *	is a another function TkTextFindDisplayLineEnd designed just for
 *	that task which is probably better to use.
 *
 * 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 any excess pixels in *overlap, if
 *	that is non-NULL.
 *
 * Side effects:
 *	None.
 *
 *--------------------------------------------------------------
 */

static void
MeasureUp(textPtr, srcPtr, distance, dstPtr, overlap)
    TkText *textPtr;		/* Text widget in which to measure. */
    CONST 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 *overlap;               /* Used to store how much of the final
                                 * index returned was not covered by
                                 * 'distance'. */
{
    int lineNum;		/* Number of current line. */
    int bytesToCount;		/* Maximum number of bytes to measure in
				 * current line. */
    TkTextIndex index;
    DLine *dlPtr, *lowestPtr;

    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.  If distance was zero to start
	 * with then we simply get the first index on the same display
	 * line as the original index.
	 */

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

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

	FreeDLines(textPtr, lowestPtr, (DLine *) NULL, DLINE_FREE);
	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);
    if (overlap != NULL) {
        *overlap = 0;
    }
}

/*
 *--------------------------------------------------------------
 *
 * 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, objc, objv)
    TkText *textPtr;		/* Information about text widget. */
    Tcl_Interp *interp;		/* Current interpreter. */
    int objc;			/* Number of arguments. */
    Tcl_Obj *CONST objv[];	/* Argument objects. Someone else has already
				 * parsed this command enough to know that
				 * objv[1] is "see". */
{
    TextDInfo *dInfoPtr = textPtr->dInfoPtr;
    TkTextIndex index;
    int x, y, width, height, lineWidth, byteCount, oneThird, delta;
    DLine *dlPtr;
    TkTextDispChunk *chunkPtr;

    if (objc != 3) {
	Tcl_WrongNumArgs(interp, 2, objv, "index");
	return TCL_ERROR;
    }
    if (TkTextGetObjIndex(interp, textPtr, objv[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(NULL,&index, 1, &index, COUNT_INDICES);
    }

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

    TkTextSetYView(textPtr, &index, TK_TEXT_PICKPLACE);

    /*
     * 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->curXPixelOffset;
	oneThird = lineWidth/3;
	if (delta < 0) {
	    if (delta < -oneThird) {
		dInfoPtr->newXPixelOffset = (x - lineWidth/2);
	    } else {
		dInfoPtr->newXPixelOffset -= ((-delta) );
	    }
	} else {
	    delta -= (lineWidth - width);
	    if (delta > 0) {
		if (delta > oneThird) {
		    dInfoPtr->newXPixelOffset = (x - lineWidth/2);
		} else {
		    dInfoPtr->newXPixelOffset += (delta );
		}
	    } 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, objc, objv)
    TkText *textPtr;		/* Information about text widget. */
    Tcl_Interp *interp;		/* Current interpreter. */
    int objc;			/* Number of arguments. */
    Tcl_Obj *CONST objv[];	/* Argument objects. Someone else has already
				 * parsed this command enough to know that
				 * objv[1] is "xview". */
{
    TextDInfo *dInfoPtr = textPtr->dInfoPtr;
    int type, count;
    double fraction;

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

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

    type = TextGetScrollInfoObj(interp, textPtr, objc, objv, 
				&fraction, &count);
    switch (type) {
	case TKTEXT_SCROLL_ERROR:
	    return TCL_ERROR;
	case TKTEXT_SCROLL_MOVETO:
	    if (fraction > 1.0) {
		fraction = 1.0;
	    }
	    if (fraction < 0) {
		fraction = 0;
	    }
	    dInfoPtr->newXPixelOffset = (int) (fraction 
					       * dInfoPtr->maxLength + 0.5);
	    break;
	case TKTEXT_SCROLL_PAGES: {
	    int pixelsPerPage;
	    pixelsPerPage = (dInfoPtr->maxX-dInfoPtr->x) - 2*textPtr->charWidth;
	    if (pixelsPerPage < 1) {
		pixelsPerPage = 1;
	    }
	    dInfoPtr->newXPixelOffset += pixelsPerPage * count;
	    break;
	}
	case TKTEXT_SCROLL_UNITS:
	    dInfoPtr->newXPixelOffset += count * textPtr->charWidth;
	    break;
	case TKTEXT_SCROLL_PIXELS:
	    dInfoPtr->newXPixelOffset += count;
	    break;
    }

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

static void
YScrollByPixels(textPtr, offset)
    TkText *textPtr;		/* Widget to scroll. */
    int offset;			/* Amount by which to scroll, in 
               			 * pixels. Positive means that information
				 * later in text becomes visible, negative
				 * means that information earlier in the
				 * text becomes visible. */
{
    TextDInfo *dInfoPtr = textPtr->dInfoPtr;

    if (offset < 0) {
	/* 
	 * Now we want to measure up this number of pixels
	 * from the top of the screen.  But the top line may
	 * not be totally visible.  Note that 'count' is 
	 * negative here.
	 */
	offset -= CalculateDisplayLineHeight(textPtr, 
		&textPtr->topIndex, NULL) - dInfoPtr->topPixelOffset;
	MeasureUp(textPtr, &textPtr->topIndex, -offset, 
		&textPtr->topIndex, &dInfoPtr->newTopPixelOffset);
    } else if (offset > 0) {
	DLine *dlPtr;
	TkTextLine *lastLinePtr;
	TkTextIndex new;
	/*
	 * Scrolling down by pixels.  Layout lines starting at
	 * the top index and count through the desired vertical
	 * distance.
	 */

	lastLinePtr = TkBTreeFindLine(textPtr->tree,
		TkBTreeNumLines(textPtr->tree));
	offset += dInfoPtr->topPixelOffset;
	dInfoPtr->newTopPixelOffset = 0;
	while (offset > 0) {
	    dlPtr = LayoutDLine(textPtr, &textPtr->topIndex);
	    dlPtr->nextPtr = NULL;
	    TkTextIndexForwBytes(&textPtr->topIndex, dlPtr->byteCount, &new);
	    if (offset <= dlPtr->height) {
		/* Adjust the top overlap accordingly */
		dInfoPtr->newTopPixelOffset = offset;
	    }
	    offset -= dlPtr->height;
	    FreeDLines(textPtr, dlPtr, (DLine *) NULL, DLINE_FREE_TEMP);
	    if (new.linePtr == lastLinePtr || offset <= 0) {
		break;
	    }
	    textPtr->topIndex = new;
	}
    } else {
	/* offset = 0, so no scrolling required */
	return;
    }
    if (!(dInfoPtr->flags & REDRAW_PENDING)) {
	Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr);
    }
    dInfoPtr->flags |= REDRAW_PENDING|DINFO_OUT_OF_DATE|REPICK_NEEDED;
}

/*
 *----------------------------------------------------------------------
 *
 * YScrollByLines --
 *
 *	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
YScrollByLines(textPtr, offset)
    TkText *textPtr;		/* Widget to scroll. */
    int offset;			/* Amount by which to scroll, in display 
				 * 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, DLINE_FREE);
	    if (offset >= 0) {
		goto scheduleUpdate;
	    }
	    bytesToCount = INT_MAX;
	}
    
	/*
	 * Ran off the beginning of the text.  Return the first character
	 * in the text, and make sure we haven't left anything
	 * overlapping the top window border.
	 */

	TkTextMakeByteIndex(textPtr->tree, 0, 0, &textPtr->topIndex);
	dInfoPtr->newTopPixelOffset = 0;
    } 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, DLINE_FREE);
	    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, objc, objv)
    TkText *textPtr;		/* Information about text widget. */
    Tcl_Interp *interp;		/* Current interpreter. */
    int objc;			/* Number of arguments. */
    Tcl_Obj *CONST objv[];	/* Argument objects. Someone else has already
				 * parsed this command enough to know that
				 * objv[1] is "yview". */
{
    TextDInfo *dInfoPtr = textPtr->dInfoPtr;
    int pickPlace, type;
    int pixels, count;
    size_t switchLength;
    double fraction;
    TkTextIndex index;

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

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

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

    pickPlace = 0;
    if (Tcl_GetString(objv[2])[0] == '-') {
	switchLength = strlen(Tcl_GetString(objv[2]));
	if ((switchLength >= 2)
		&& (strncmp(Tcl_GetString(objv[2]), 
			"-pickplace", switchLength) == 0)) {
	    pickPlace = 1;
	    if (objc != 4) {
		Tcl_WrongNumArgs(interp, 3, objv, "lineNum|index");
		return TCL_ERROR;
	    }
	}
    }
    if ((objc == 3) || pickPlace) {
	int lineNum;
	if (Tcl_GetIntFromObj(interp, objv[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 (TkTextGetObjIndex(interp, textPtr, objv[2+pickPlace],
		&index) != TCL_OK) {
	    return TCL_ERROR;
	}
	TkTextSetYView(textPtr, &index, (pickPlace ? TK_TEXT_PICKPLACE : 0));
	return TCL_OK;
    }

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

    type = TextGetScrollInfoObj(interp, textPtr, objc,objv, &fraction, &count);
    switch (type) {
	case TKTEXT_SCROLL_ERROR:
	    return TCL_ERROR;
	case TKTEXT_SCROLL_MOVETO: {
	    int numPixels = TkBTreeNumPixels(textPtr->tree);
	    int topMostPixel;
	    if (numPixels == 0) {
		/* 
		 * If the window is totally empty no scrolling is
		 * needed, and the TkTextMakePixelIndex call
		 * below will fail.
		 */
		break;
	    }
	    if (fraction > 1.0) {
		fraction = 1.0;
	    }
	    if (fraction < 0) {
		fraction = 0;
	    }
	    /* 
	     * Calculate the pixel count for the new topmost pixel
	     * in the topmost line of the window.  Note that the
	     * interpretation of 'fraction' is that it counts from
	     * 0 (top pixel in buffer) to 1.0 (one pixel past the
	     * last pixel in buffer).
	     */
	    topMostPixel = (int) (0.5 + fraction * numPixels);
	    if (topMostPixel >= numPixels) {
		topMostPixel = numPixels -1;
	    }
	    /*
	     * This function returns the number of pixels by which the
	     * given line should overlap the top of the visible screen.
	     * 
	     * This is then used to provide smooth scrolling.
	     */
	    pixels = TkTextMakePixelIndex(textPtr, topMostPixel, &index);
	    TkTextSetYView(textPtr, &index, pixels);
	    break;
	}
	case TKTEXT_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.
	     */
	    int height = dInfoPtr->maxY - dInfoPtr->y;
	    if (textPtr->charHeight * 4 >= height) {
		/* 
		 * A single line is more than a quarter of the
		 * display.  We choose to scroll by 3/4 of the
		 * height instead.
		 */
		pixels = 3*height/4;
		if (pixels < textPtr->charHeight) {
		    /* 
		     * But, if 3/4 of the height is actually less than a
		     * single typical character height, then scroll by
		     * the minimum of the linespace or the total height.
		     */
		    if (textPtr->charHeight < height) {
			pixels = textPtr->charHeight;
		    } else {
			pixels = height;
		    }
		}
		pixels *= count;
	    } else {
		pixels = (height - 2*textPtr->charHeight)*count;
	    }
	    YScrollByPixels(textPtr, pixels);
	    break;
	}
	case TKTEXT_SCROLL_PIXELS:
	    YScrollByPixels(textPtr, count);
	    break;
	case TKTEXT_SCROLL_UNITS:
	    YScrollByLines(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, objc, objv)
    register TkText *textPtr;	/* Information about text widget. */
    Tcl_Interp *interp;		/* Current interpreter. */
    int objc;			/* Number of arguments. */
    Tcl_Obj *CONST objv[];	/* Argument objects. Someone else has already
				 * parsed this command enough to know that
				 * objv[1] is "scan". */
{
    TextDInfo *dInfoPtr = textPtr->dInfoPtr;
    TkTextIndex index;
    int c, x, y, totalScroll, gain=10;
    size_t length;

    if ((objc != 5) && (objc != 6)) {
	Tcl_AppendResult(interp, "wrong # args: should be \"",
		Tcl_GetString(objv[0]), " scan mark x y\" or \"",
		Tcl_GetString(objv[0]), " scan dragto x y ?gain?\"", 
		(char *) NULL);
	return TCL_ERROR;
    }
    if (Tcl_GetIntFromObj(interp, objv[3], &x) != TCL_OK) {
	return TCL_ERROR;
    }
    if (Tcl_GetIntFromObj(interp, objv[4], &y) != TCL_OK) {
	return TCL_ERROR;
    }
    if ((objc == 6) && (Tcl_GetIntFromObj(interp, objv[5], &gain) != TCL_OK)) {
        return TCL_ERROR;
    }
    c = Tcl_GetString(objv[2])[0];
    length = strlen(Tcl_GetString(objv[2]));
    if (c=='d' && strncmp(Tcl_GetString(objv[2]), "dragto", length)==0) {
	int newX, maxX;
	
	/*
	 * 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).
	 */

	newX = dInfoPtr->scanMarkXPixel + gain*(dInfoPtr->scanMarkX - x);
	maxX = 1 + dInfoPtr->maxLength - (dInfoPtr->maxX - dInfoPtr->x);
	if (newX < 0) {
	    newX = 0;
	    dInfoPtr->scanMarkXPixel = 0;
	    dInfoPtr->scanMarkX = x;
	} else if (newX > maxX) {
	    newX = maxX;
	    dInfoPtr->scanMarkXPixel = maxX;
	    dInfoPtr->scanMarkX = x;
	}
	dInfoPtr->newXPixelOffset = newX;

	totalScroll = gain*(dInfoPtr->scanMarkY - y);
	if (totalScroll != dInfoPtr->scanTotalYScroll) {
	    index = textPtr->topIndex;
	    YScrollByPixels(textPtr, totalScroll-dInfoPtr->scanTotalYScroll);
	    dInfoPtr->scanTotalYScroll = totalScroll;
	    if ((index.linePtr == textPtr->topIndex.linePtr) &&
		    (index.byteIndex == textPtr->topIndex.byteIndex)) {
		dInfoPtr->scanTotalYScroll = 0;
		dInfoPtr->scanMarkY = y;
	    }
	}
	dInfoPtr->flags |= DINFO_OUT_OF_DATE;
	if (!(dInfoPtr->flags & REDRAW_PENDING)) {
	    dInfoPtr->flags |= REDRAW_PENDING;
	    Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr);
	}
    } else if (c=='m' && strncmp(Tcl_GetString(objv[2]), "mark", length)==0) {
	dInfoPtr->scanMarkXPixel = dInfoPtr->newXPixelOffset;
	dInfoPtr->scanMarkX = x;
	dInfoPtr->scanTotalYScroll = 0;
	dInfoPtr->scanMarkY = y;
    } else {
	Tcl_AppendResult(interp, "bad scan option \"", Tcl_GetString(objv[2]),
		"\": must be mark or dragto", (char *) NULL);
	return TCL_ERROR;
    }
    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;
    double first, last;
    int code;
    Tcl_Obj *listObj;
    
    if (dInfoPtr->maxLength > 0) {
	first = ((double) dInfoPtr->curXPixelOffset)
		/ 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) {
	listObj = Tcl_NewListObj(0,NULL);
	Tcl_ListObjAppendElement(interp, listObj, Tcl_NewDoubleObj(first));
	Tcl_ListObjAppendElement(interp, listObj, Tcl_NewDoubleObj(last));
	Tcl_SetObjResult(interp, listObj);
	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;
    if (textPtr->xScrollCmd != NULL) {
	char buf1[TCL_DOUBLE_SPACE+1];
	char buf2[TCL_DOUBLE_SPACE+1];
	buf1[0] = ' ';
	buf2[0] = ' ';
	Tcl_PrintDouble(NULL, first, buf1+1);
	Tcl_PrintDouble(NULL, last, buf2+1);
	code = Tcl_VarEval(interp, textPtr->xScrollCmd, buf1, buf2, NULL);
	if (code != TCL_OK) {
	    Tcl_AddErrorInfo(interp,
		"\n    (horizontal scrolling command executed by text)");
	    Tcl_BackgroundError(interp);
	}
    }
}

/*
 *----------------------------------------------------------------------
 *
 * GetYPixelCount --
 *
 *	How many pixels are there between the absolute top of the
 *	widget and the top of the given DLine.
 *	
 *	While this function will work for any valid DLine, it is
 *	only ever called when dlPtr is the first display
 *	line in the widget (by 'GetYView').  This means that
 *	usually this function is a very quick calculation, since
 *	it can use the pre-calculated linked-list of DLines for
 *	height information.
 *	
 *	The only situation where this breaks down is if dlPtr's logical
 *	line wraps enough times to fill the text widget's current view
 *	-- in this case we won't have enough dlPtrs in the linked list
 *	to be able to subtract off what we want.
 *
 * Results:
 *	The number of pixels.
 *	
 *	This value has a valid range between '0' (the very top of the
 *	widget) and the number of pixels in the total widget minus the
 *	pixel-height of the last line.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */
    
static int
GetYPixelCount(textPtr, dlPtr)
    TkText *textPtr;			/* Information about text widget. */
    DLine *dlPtr;                       /* Information about the layout
                                         * of a given index */
{
    TkTextLine *linePtr = dlPtr->index.linePtr;
    /* 
     * Get the pixel count to the top of dlPtr's logical line.  The
     * rest of the function is then concerned with updating 'count'
     * for any difference between the top of the logical line and
     * the display line.
     */
    int count = TkBTreePixels(linePtr);
   
    /* 
     * For the common case where this dlPtr is also the start of the
     * logical line, we can return right away.  Note the implicit
     * assumption here that the start of a logical line is always the
     * start of a display line (if the 'elide won't elide first newline'
     * bug is fixed, this will no longer necessarily be true).
     */
    if (dlPtr->index.byteIndex == 0) {
        return count;
    }

    /* 
     * Add on the logical line's height to reach one pixel beyond the
     * bottom of the logical line.  And then subtract off the heights of
     * all the display lines from dlPtr to the end of its logical line.
     * 
     * A different approach would be to lay things out from the start of
     * the logical line until we reach dlPtr, but since none of those are
     * pre-calculated, it'll usually take a lot longer.  (But there are
     * cases where it would be more efficient: say if we're on the second
     * of 1000 wrapped lines all from a single logical line -- but that
     * sort of optimization is left for the future).
     */
    count += linePtr->pixelHeight;

    do {
	count -= dlPtr->height;
	if (dlPtr->nextPtr == NULL) {
	    /* 
	     * We've run out of pre-calculated display lines, so we
	     * have to lay them out ourselves until the end of the
	     * logical line.  Here's where we could be clever and ask:
	     * what's faster, to layout all lines from here to line-end,
	     * or all lines from the original dlPtr to the line-start?
	     * We just assume the former.
	     */
	    TkTextIndex index;
	    int notFirst = 0;
	    while (1) {
		TkTextIndexForwBytes(&dlPtr->index, dlPtr->byteCount, &index);
		if (notFirst) {
		    FreeDLines(textPtr, dlPtr, (DLine *)NULL, DLINE_FREE_TEMP);
		}
		if (index.linePtr != linePtr) {
		    break;
		}
		dlPtr = LayoutDLine(textPtr, &index);
		
		if (tkTextDebug) {
		    char string[TK_POS_CHARS];

		    /*
		     * Debugging is enabled, so keep a log of all the
		     * lines whose height was recalculated.  The test
		     * suite uses this information.
		     */

		    TkTextPrintIndex(&index, string);
		    Tcl_SetVar2(textPtr->interp, "tk_textHeightCalc", 
			    (char *) NULL, string,
			    TCL_GLOBAL_ONLY|TCL_APPEND_VALUE|TCL_LIST_ELEMENT);
		}
		count -= dlPtr->height;
		notFirst = 1;
	    }
	    break;
	} else {
	    dlPtr = dlPtr->nextPtr;
	}
    } while (dlPtr->index.linePtr == linePtr);
    
    return count;
}

/*
 *----------------------------------------------------------------------
 *
 * 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;
    double first, last;
    DLine *dlPtr;
    int totalPixels, code, count;
    Tcl_Obj *listObj;
    
    dlPtr = dInfoPtr->dLinePtr;

    if (dlPtr == NULL) {
	return;
    }

    totalPixels = TkBTreeNumPixels(textPtr->tree);
    
    if (totalPixels == 0) {
	first = 0.0;
	last = 1.0;
    } else {
	/* 
	 * Get the pixel count for the first visible pixel of the
	 * first visible line.  If the first visible line is only
	 * partially visible, then we use 'topPixelOffset' to get the
	 * difference.
	 */
	count = GetYPixelCount(textPtr, dlPtr);
	first = (count + dInfoPtr->topPixelOffset) / (double) totalPixels;

	/* 
	 * Add on the total number of visible pixels to get the count to
	 * one pixel _past_ the last visible pixel.  This is how the
	 * 'yview' command is documented, and also explains why we are
	 * dividing by 'totalPixels' and not 'totalPixels-1'.
	 */
	while (1) {
	    int extra;
	    count += dlPtr->height;

	    extra = dlPtr->y + dlPtr->height - dInfoPtr->maxY;
	    if (extra > 0) {
		/*
		 * This much of the last line is not visible, so don't
		 * count these pixels.  Since we've reached the bottom
		 * of the window, we break out of the loop.
		 */
		count -= extra;
		break;
	    }
	    if (dlPtr->nextPtr == NULL) {
		break;
	    }
	    dlPtr = dlPtr->nextPtr;
	}
	
	if (count > totalPixels) {
	    /* 
	     * It can be possible, if we do not update each line's
	     * pixelHeight cache when we lay out individual DLines that
	     * the count generated here is more up-to-date than that
	     * maintained by the BTree.  In such a case, the best we can
	     * do here is to fix up 'count' and continue, which might
	     * result in small, temporary perturbations to the size of
	     * the scrollbar.  This is basically harmless, but in a
	     * perfect world we would not have this problem.
	     * 
	     * For debugging purposes, if anyone wishes to improve the
	     * text widget further, the following 'panic' can be
	     * activated.  In principle it should be possible to ensure
	     * the BTree is always at least as up to date as the display,
	     * so in the future we might be able to leave the 'panic' in
	     * permanently when we believe we have resolved the cache
	     * synchronisation issue.
	     * 
	     * However, to achieve that goal would, I think, require a
	     * fairly substantial refactorisation of the code in this
	     * file so that there is much more obvious and explicit
	     * coordination between calls to LayoutDLine and updating of
	     * each TkTextLine's pixelHeight.  The complicated bit is
	     * that LayoutDLine deals with individual display lines, but
	     * pixelHeight is for a logical line.
	     */
    #if 0
	    Tcl_Panic("Counted more pixels (%d) than expected (%d) total "
		    "pixels in text widget scroll bar calculation.", count,
		    totalPixels);
    #endif
	    count = totalPixels;
	}
	
	last = ((double) count)/((double)totalPixels);
    }

    if (!report) {
	listObj = Tcl_NewListObj(0,NULL);
	Tcl_ListObjAppendElement(interp, listObj, Tcl_NewDoubleObj(first));
	Tcl_ListObjAppendElement(interp, listObj, Tcl_NewDoubleObj(last));
	Tcl_SetObjResult(interp, listObj);
	return;
    }
    if (FP_EQUAL_SCALE(first, dInfoPtr->yScrollFirst, totalPixels) &&
	FP_EQUAL_SCALE(last,  dInfoPtr->yScrollLast,  totalPixels)) {
	return;
    }
    dInfoPtr->yScrollFirst = first;
    dInfoPtr->yScrollLast = last;
    if (textPtr->yScrollCmd != NULL) {
	char buf1[TCL_DOUBLE_SPACE+1];
	char buf2[TCL_DOUBLE_SPACE+1];
	buf1[0] = ' ';
	buf2[0] = ' ';
	Tcl_PrintDouble(NULL, first, buf1+1);
	Tcl_PrintDouble(NULL, last, buf2+1);
	code = Tcl_VarEval(interp, textPtr->yScrollCmd, buf1, buf2, NULL);
	if (code != TCL_OK) {
	    Tcl_AddErrorInfo(interp,
		    "\n    (vertical scrolling command executed by text)");
	    Tcl_BackgroundError(interp);
	}
    }
}

/*
 *----------------------------------------------------------------------
 *
 * AsyncUpdateYScrollbar --
 *
 *	This procedure is called to update the vertical scrollbar
 *	asychronously as the pixel height calculations progress for
 *	lines in the widget.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	See 'GetYView'. In particular the scrollbar position and size
 *	may be changed.
 *
 *----------------------------------------------------------------------
 */

static void
AsyncUpdateYScrollbar(clientData)
    ClientData clientData;	/* Information about widget. */
{
    register TkText *textPtr = (TkText *) clientData;
    
    textPtr->dInfoPtr->scrollbarTimer = NULL;

    if (!(textPtr->flags & DESTROYED)) {
	GetYView(textPtr->interp, textPtr, 1);
    }
    
    if (--textPtr->refCount == 0) {
	ckfree((char *) textPtr);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * 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. */
    CONST 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) {
	    Tcl_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, nearest)
    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). */
    int *nearest;               /* If non-NULL then gets set to 0 if
                                 * (x,y) is actually over the returned 
                                 * index, and 1 if it is just nearby
                                 * (e.g. if x,y is on the border of the
                                 * widget). */
{
    TextDInfo *dInfoPtr = textPtr->dInfoPtr;
    register DLine *dlPtr, *validDlPtr;
    int nearby = 0;

    /*
     * 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;
	nearby = 1;
    }
    if (x >= dInfoPtr->maxX) {
	x = dInfoPtr->maxX - 1;
	nearby = 1;
    }
    if (x < dInfoPtr->x) {
	x = dInfoPtr->x;
	nearby = 1;
    }
    
    /*
     * 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;
	    nearby = 1;
	    break;
	}
    }
    if (dlPtr->chunkPtr == NULL) dlPtr = validDlPtr;
    
    if (nearest != NULL) {
	*nearest = nearby;
    }

    DlineIndexOfX(textPtr, dlPtr, x, indexPtr);
}

/*
 *----------------------------------------------------------------------
 *
 * DlineIndexOfX --
 *
 *	Given an x coordinate in a display line, find the index of
 *	the character closest to that location.
 *	
 *	This is effectively the opposite of DlineXOfIndex. 
 *
 * Results:
 *	The index at *indexPtr is modified to refer to the character
 *	on the display line that is closest to x.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static void
DlineIndexOfX(textPtr, dlPtr, x, indexPtr)
    TkText *textPtr;		/* Widget record for text widget. */
    DLine *dlPtr;		/* Display information for this
                 		 * display line. */
    int x;			/* Pixel x coordinate of point in widget's
				 * window. */
    TkTextIndex *indexPtr;	/* This index gets filled in with the
				 * index of the character nearest to x. */
{
    TextDInfo *dInfoPtr = textPtr->dInfoPtr;
    register TkTextDispChunk *chunkPtr;

    /*
     * 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->curXPixelOffset;
    chunkPtr = dlPtr->chunkPtr;

    if (chunkPtr == NULL) {
	/* 
	 * This may occur if everything is elided
	 */
	return;
    }

    for (; x >= (chunkPtr->x + chunkPtr->width);
	    indexPtr->byteIndex += chunkPtr->numBytes,
	    chunkPtr = chunkPtr->nextPtr) {
	if (chunkPtr->nextPtr == NULL) {
	    indexPtr->byteIndex += chunkPtr->numBytes;
	    TkTextIndexBackChars(NULL, indexPtr, 1, indexPtr, COUNT_INDICES);
	    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);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * TkTextIndexOfX --
 *
 *	Given a logical x coordinate (i.e. distance in pixels from the
 *	beginning of the display line, not taking into account any
 *	information about the window, scrolling etc.)  on the display
 *	line starting with the given index, adjust that index to refer to
 *	the object under the x coordinate.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

void
TkTextIndexOfX(textPtr, x, indexPtr)
    TkText *textPtr;		/* Widget record for text widget. */
    int x;                      /* The x coordinate for which we want
                                 * the index */
    TkTextIndex *indexPtr;	/* Index of display line start, which
                          	 * will be adjusted to the index under the
                          	 * given x coordinate. */
{
    DLine *dlPtr = LayoutDLine(textPtr, indexPtr);
    DlineIndexOfX(textPtr, dlPtr, x + textPtr->dInfoPtr->x 
		- textPtr->dInfoPtr->curXPixelOffset, indexPtr);
    FreeDLines(textPtr, dlPtr, NULL, DLINE_FREE_TEMP);
}

/*
 *----------------------------------------------------------------------
 *
 * DlineXOfIndex --
 *
 *	Given a relative byte index on a given display line (i.e. the
 *	number of byte indices from the beginning of the given display
 *	line), find the x coordinate of that index within the abstract
 *	display line, without adjusting for the x-scroll state of the
 *	line.
 *
 *	This is effectively the opposite of DlineIndexOfX. 
 *	
 *	NB. The 'byteIndex' is relative to the display line, NOT the
 *	logical line.
 *
 * Results:
 *	The x coordinate.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static int
DlineXOfIndex(textPtr, dlPtr, byteIndex)
    TkText *textPtr;		/* Widget record for text widget. */
    DLine *dlPtr;		/* Display information for this
				 * display line. */
    int byteIndex;              /* The byte index for which we want the
                                 * coordinate. */
{
    register TkTextDispChunk *chunkPtr = dlPtr->chunkPtr;
    int x;
    
    if (byteIndex == 0 || chunkPtr == NULL) return 0;

    /*
     * Scan through the line's chunks to find the one that contains
     * the desired byte index.
     */

    chunkPtr = dlPtr->chunkPtr;
    while (byteIndex > 0) {
	if (byteIndex < chunkPtr->numBytes) {
	    int y, width, height;
	    (*chunkPtr->bboxProc)(chunkPtr, byteIndex,
		    dlPtr->y + dlPtr->spaceAbove,
		    dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow,
		    dlPtr->baseline - dlPtr->spaceAbove, &x, &y, &width,
		    &height);
	    break;
	} else {
	    byteIndex -= chunkPtr->numBytes;
	}
	if (chunkPtr->nextPtr == NULL || byteIndex == 0) {
	    x = chunkPtr->x + chunkPtr->width;
	    break;
	}
	chunkPtr = chunkPtr->nextPtr;
    }
    
    return 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, charWidthPtr)
    TkText *textPtr;		/* Widget record for text widget. */
    CONST 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. */
    int *charWidthPtr;          /* If the 'character' isn't really a
                                 * character (e.g. end of a line) and 
                                 * therefore takes up a very large
                                 * width, this is used to return a
                                 * smaller width */
{
    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->curXPixelOffset;
    if ((byteIndex == (chunkPtr->numBytes - 1)) && (chunkPtr->nextPtr == NULL)) {
	/*
	 * Last character in display line.  Give it all the space up to
	 * the line.
	 */

	if (charWidthPtr != NULL) {
	    *charWidthPtr = dInfoPtr->maxX - *xPtr;
	}
	if (*xPtr > dInfoPtr->maxX) {
	    *xPtr = dInfoPtr->maxX;
	}
	*widthPtr = dInfoPtr->maxX - *xPtr;
    } else {
	if (charWidthPtr != NULL) {
	    *charWidthPtr = *widthPtr;
	}
    }
    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. */
    CONST 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->curXPixelOffset + 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;
}

/*
 * Get bounding-box information about an elided chunk
 */
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;
}

/*
 * Measure an elided chunk
 */
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,
				&nextX);
    if (bytesThatFit < maxBytes) {
	if ((bytesThatFit == 0) && noCharsYet) {
	    Tcl_UniChar ch;
	    
	    bytesThatFit = MeasureChars(tkfont, p, Tcl_UtfToUniChar(p, &ch),
		    chunkPtr->x, -1, &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, &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, &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, 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, 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, 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.
	 */

	tabX = (int) (tabArrayPtr->lastTab 
		      + (index + 1 - tabArrayPtr->numTabs) 
		      * tabArrayPtr->tabIncrement + 0.5);
	alignment = tabArrayPtr->tabs[tabArrayPtr->numTabs-1].alignment;
    }

    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, &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, &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 next tab of tabArrayPtr, assuming that
 *	the current position on the line is x and the end of the
 *	line is maxX.  The 'next tab' is determined by a combination
 *	of the current position (x) which it must be equal to or
 *	beyond, and the tab count in indexPtr.
 *	
 *	For numeric tabs, this is a conservative estimate.  The return
 *	value is always >= 0.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static int
SizeOfTab(textPtr, tabArrayPtr, indexPtr, 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 *indexPtr;			/* Contains index of previous tab 
                  			 * stop, will be updated to
                  			 * reflect the number of stops
                  			 * used. */
    int x;				/* Current x-location in line. */
    int maxX;				/* X-location of pixel just past the
					 * right edge of the line. */
{
    int tabX, result, index, spaceWidth;
    TkTextTabAlign alignment;
    
    index = *indexPtr;

    if ((tabArrayPtr == NULL) || (tabArrayPtr->numTabs == 0)) {
	tabX = NextTabStop(textPtr->tkfont, x, 0);
	/* 
	 * We used up one tab stop.
	 */
	*indexPtr = index+1;
	return tabX - x;
    }

    do {
	/* 
	 * We were given the count before this tab, so increment it
	 * first.
	 */
	index++;
	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.
	     */
	    tabX = (int) (tabArrayPtr->lastTab 
			  + (index + 1 - tabArrayPtr->numTabs) 
			  * tabArrayPtr->tabIncrement + 0.5);
	    alignment = tabArrayPtr->tabs[tabArrayPtr->numTabs-1].alignment;
	}
	/* 
	 * If this tab stop is before the current x position, then we
	 * must obviously continue until we reach the text tab stop.
	 */
    } while (tabX < x);
    
    /* 
     * Inform our caller of how many tab stops we've used up.
     */
    *indexPtr = index;
    
    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, &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_DrawChars will be used to actually display
 *	the characters.
 *
 *	If tabs are encountered in the string, they will be expanded
 *	to the next tab stop.
 *
 *	If a newline is encountered in the string, the line will be
 *	broken at that point.
 *
 * 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, 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 *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;
}

/*
 *----------------------------------------------------------------------
 *
 * TextGetScrollInfoObj --
 *
 *	This procedure is invoked to parse "xview" and "yview" scrolling
 *	commands for text widgets using the new scrolling command syntax
 *	("moveto" or "scroll" options).  It extends the public
 *	Tk_GetScrollInfoObj function with the addition of "pixels" as a
 *	valid unit alongside "pages" and "units".  It is a shame the core
 *	API isn't more flexible in this regard.
 *
 * Results:
 *	The return value is either TKTEXT_SCROLL_MOVETO,
 *	TKTEXT_SCROLL_PAGES, TKTEXT_SCROLL_UNITS, TKTEXT_SCROLL_PIXELS or
 *	TKTEXT_SCROLL_ERROR. This indicates whether the command was
 *	successfully parsed and what form the command took.  If
 *	TKTEXT_SCROLL_MOVETO, *dblPtr is filled in with the desired
 *	position; if TKTEXT_SCROLL_PAGES, TKTEXT_SCROLL_PIXELS or
 *	TKTEXT_SCROLL_UNITS, *intPtr is filled in with the number of
 *	pages/pixels/lines to move (may be negative); if
 *	TKTEXT_SCROLL_ERROR, the interp's result contains an error
 *	message.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static int
TextGetScrollInfoObj(interp, textPtr, objc, objv, dblPtr, intPtr)
    Tcl_Interp *interp;			/* Used for error reporting. */
    TkText *textPtr;			/* Information about the text
                			 * widget. */
    int objc;				/* # arguments for command. */
    Tcl_Obj *CONST objv[];		/* Arguments for command. */
    double *dblPtr;			/* Filled in with argument "moveto"
					 * option, if any. */
    int *intPtr;			/* Filled in with number of pages
					 * or lines or pixels to scroll, 
					 * if any. */
{
    char c;
    int length;
    CONST char *arg2;

    arg2 = Tcl_GetStringFromObj(objv[2], &length);
    c = arg2[0];
    if (c=='m' && strncmp(arg2, "moveto", (unsigned) length)==0) {
	if (objc != 4) {
	    Tcl_WrongNumArgs(interp, 2, objv, "moveto fraction");
	    return TKTEXT_SCROLL_ERROR;
	}
	if (Tcl_GetDoubleFromObj(interp, objv[3], dblPtr) != TCL_OK) {
	    return TKTEXT_SCROLL_ERROR;
	}
	return TKTEXT_SCROLL_MOVETO;
    } else if (c=='s' && strncmp(arg2, "scroll", (unsigned) length)==0) {
	CONST char *arg4;
	size_t argLen;
	
	if (objc != 5) {
	    Tcl_WrongNumArgs(interp, 2, objv, 
	    "scroll number units|pages|pixels");
	    return TKTEXT_SCROLL_ERROR;
	}
	arg4 = Tcl_GetStringFromObj(objv[4], &length);
	argLen = (size_t) length;
	c = arg4[0];
	if (c=='p' && length==1) {
	    Tcl_AppendResult(interp, "ambiguous argument \"", arg4,
		    "\": must be units, pages or pixels", (char *) NULL);
	    return TKTEXT_SCROLL_ERROR;
	} else if (c=='p' && strncmp(arg4, "pages", argLen)==0) {
	    if (Tcl_GetIntFromObj(interp, objv[3], intPtr) != TCL_OK) {
		return TKTEXT_SCROLL_ERROR;
	    }
	    return TKTEXT_SCROLL_PAGES;
	} else if (c=='p' && strncmp(arg4, "pixels", argLen)==0) {
	    if (Tk_GetPixelsFromObj(interp, textPtr->tkwin, objv[3], 
		    intPtr) != TCL_OK) {
		return TKTEXT_SCROLL_ERROR;
	    }
	    return TKTEXT_SCROLL_PIXELS;
	} else if (c=='u' && strncmp(arg4, "units", argLen)==0) {
	    if (Tcl_GetIntFromObj(interp, objv[3], intPtr) != TCL_OK) {
		return TKTEXT_SCROLL_ERROR;
	    }
	    return TKTEXT_SCROLL_UNITS;
	} else {
	    Tcl_AppendResult(interp, "bad argument \"", arg4,
		    "\": must be units, pages or pixels", (char *) NULL);
	    return TKTEXT_SCROLL_ERROR;
	}
    }
    Tcl_AppendResult(interp, "unknown option \"", arg2,
	    "\": must be moveto or scroll", (char *) NULL);
    return TKTEXT_SCROLL_ERROR;
}