/* 
 * tkTextDisp.c --
 *
 *	This module provides facilities to display text widgets.  It is
 *	the only place where information is kept about the screen layout
 *	of text widgets.
 *
 * Copyright (c) 1992-1994 The Regents of the University of California.
 * Copyright (c) 1994 Sun Microsystems, Inc.
 *
 * See the file "license.terms" for information on usage and redistribution
 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
 */

/* TkWin version 0.3 -- Tk for Windows
 * Copyright (C) 1994 K. K. Kubota
 * Mathematical Sciences Computing Facility, University of Kentucky,
 * Lexington, KY 40506, USA (e-mail: ken@ms.uky.edu)
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose is granted without fee, provided that the above copyright notice
 * and the following paragraph appear in all copies of this software
 * and supporting documentation.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY, even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  In no event shall the author be
 * liable to any party for direct, indirect, special, incidental, or
 * consequential damages arising out of the use of this software and its
 * documentation.
 */

static char sccsid[] = "@(#) tkTextDisp.c 1.66 94/12/22 17:45:23";

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

/*
 * 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. */
#ifdef TCL_MSDOS_PORT
    HBITMAP hbmp;		/* Stipple bitmap for background.  None
				 * means draw solid. */
#else
    Pixmap bgStipple;		/* Stipple bitmap for background.  None
				 * means draw solid. */
#endif
    XColor *fgColor;		/* Foreground color for text. */
#ifdef TCL_MSDOS_PORT
    HFONT hfont;
    int ascent;
    int descent;
#else
    XFontStruct *fontPtr;	/* Font for displaying text. */
#endif
    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 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. */
    int underline;		/* Non-zero means draw underline underneath
				 * text. */
} 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 Style {
    int refCount;		/* Number of times this structure is
				 * referenced in Chunks. */
#ifdef TCL_MSDOS_PORT
    XColor *fgColor;		/* Foreground color for text */
    HBRUSH hbr;
#else
    GC bgGC;			/* Graphics context for background.  None
				 * means use widget background. */
    GC fgGC;			/* Graphics context for foreground. */
#endif
    StyleValues *sValuePtr;	/* Raw information from which GCs were
				 * derived. */
    Tcl_HashEntry *hPtr;	/* Pointer to entry in styleTable.  Used
				 * to delete entry. */
} Style;

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

#ifdef TCL_MSDOS_PORT
#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->hbmp == (s2)->sValuePtr->hbmp))
#else
#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))
#endif

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

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

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

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

typedef struct DInfo {
    Tcl_HashTable styleTable;	/* Hash table that maps from StyleValues
				 * to Styles for this widget. */
    DLine *dLinePtr;		/* First in list of all display lines for
				 * this widget, in order from top to bottom. */
#ifndef TCL_MSDOS_PORT
    GC copyGC;			/* Graphics context for copying from off-
				 * screen pixmaps onto screen and for drawing
				 * traversal highlights. */
    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. */
#endif
    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 newCharOffset;		/* Desired x scroll position, measured as the
				 * number of average-size characters off-screen
				 * to the left for a line with no left
				 * margin. */
    int curPixelOffset;		/* Actual x scroll position, measured as the
				 * number of pixels off-screen to the left. */
    int maxLength;		/* Length in pixels of longest line that's
				 * visible in window (length may exceed window
				 * size).  If there's no wrapping, this will
				 * be zero. */

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

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

    /*
     * Miscellaneous information:
     */

    int flags;			/* Various flag values:  see below for
				 * definitions. */
} DInfo;

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

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

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

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

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

static int numRedisplays;	/* Number of calls to DisplayText. */
static int linesRedrawn;	/* Number of calls to DisplayDLine. */
static int numCopies;		/* Number of calls to XCopyArea to copy part
				 * of the screen. */
static int damagedCopies;	/* Number of times that XCopyAreas didn't
				 * completely work because some of the source
				 * information was damaged. */

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

static void		CharBboxProc _ANSI_ARGS_((TkTextDispChunk *chunkPtr,
			    int index, int y, int lineHeight, int baseline,
			    int *xPtr, int *yPtr, int *widthPtr,
			    int *heightPtr));
#ifdef TCL_MSDOS_PORT
static void		CharDisplayProc _ANSI_ARGS_((TkTextDispChunk *chunkPtr,
			    int x, int y, int height, int baseline,
			    HDC memDC, int screenY));
#else
static void		CharDisplayProc _ANSI_ARGS_((TkTextDispChunk *chunkPtr,
			    int x, int y, int height, int baseline,
			    Display *display, Drawable dst, int screenY));
#endif
static int		CharMeasureProc _ANSI_ARGS_((TkTextDispChunk *chunkPtr,
			    int x));
static void		CharUndisplayProc _ANSI_ARGS_((TkText *textPtr,
			    TkTextDispChunk *chunkPtr));
#ifdef TCL_MSDOS_PORT
static void		DisplayDLine _ANSI_ARGS_((TkText *textPtr,
			    DLine *dlPtr, DLine *prevPtr, HDC hdc, HDC memDC));
static void		DisplayLineBackground _ANSI_ARGS_((TkText *textPtr,
			    DLine *dlPtr, DLine *prevPtr, HDC memDC));
#else
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));
#endif
static void		DisplayText _ANSI_ARGS_((ClientData clientData));
static DLine *		FindDLine _ANSI_ARGS_((DLine *dlPtr,
			    TkTextIndex *indexPtr));
static void		FreeDLines _ANSI_ARGS_((TkText *textPtr,
			    DLine *firstPtr, DLine *lastPtr, int unlink));
static void		FreeStyle _ANSI_ARGS_((TkText *textPtr,
			    Style *stylePtr));
static Style *		GetStyle _ANSI_ARGS_((TkText *textPtr,
			    TkTextIndex *indexPtr));
static void		GetYView _ANSI_ARGS_((TkText *textPtr, char *buffer));
static DLine *		LayoutDLine _ANSI_ARGS_((TkText *textPtr,
			    TkTextIndex *indexPtr));
static void		MeasureUp _ANSI_ARGS_((TkText *textPtr,
			    TkTextIndex *srcPtr, int distance,
			    TkTextIndex *dstPtr));
static void		UpdateDisplayInfo _ANSI_ARGS_((TkText *textPtr));
static void		ScrollByLines _ANSI_ARGS_((TkText *textPtr,
			    int offset));

/*
 *----------------------------------------------------------------------
 *
 * 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 DInfo data structure is allocated and initialized and attached
 *	to textPtr.
 *
 *----------------------------------------------------------------------
 */

void
TkTextCreateDInfo(textPtr)
    TkText *textPtr;		/* Overall information for text widget. */
{
    register DInfo *dInfoPtr;
#ifndef TCL_MSDOS_PORT
    XGCValues gcValues;
#endif

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

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

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

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

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

    /*
     * 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, justifyPrio, offsetPrio;
    int lMargin1Prio, lMargin2Prio, rMarginPrio;
    int spacing1Prio, spacing2Prio, spacing3Prio;

    /*
     * 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 = justifyPrio = offsetPrio = -1;
    lMargin1Prio = lMargin2Prio = rMarginPrio = -1;
    spacing1Prio = spacing2Prio = spacing3Prio = -1;
    memset((VOID *) &styleValues, 0, sizeof(StyleValues));
    styleValues.relief = TK_RELIEF_FLAT;
    styleValues.fgColor = textPtr->fgColor;
#ifdef TCL_MSDOS_PORT
    styleValues.hfont = textPtr->hfont;
    styleValues.ascent = textPtr->ascent;
    styleValues.descent = textPtr->descent;
#else
    styleValues.fontPtr = textPtr->fontPtr;
#endif
    styleValues.justify = TK_JUSTIFY_LEFT;
    styleValues.spacing1 = textPtr->spacing1;
    styleValues.spacing2 = textPtr->spacing2;
    styleValues.spacing3 = textPtr->spacing3;
    for (i = 0 ; i < numTags; i++) {
	tagPtr = tagPtrs[i];
	if ((tagPtr->border != NULL) && (tagPtr->priority > borderPrio)) {
	    styleValues.border = tagPtr->border;
	    borderPrio = tagPtr->priority;
	}
	if ((tagPtr->bdString != NULL)
		&& (tagPtr->priority > borderWidthPrio)) {
	    styleValues.borderWidth = tagPtr->borderWidth;
	    borderWidthPrio = tagPtr->priority;
	}
	if ((tagPtr->reliefString != NULL)
		&& (tagPtr->priority > reliefPrio)) {
	    if (styleValues.border == NULL) {
		styleValues.border = textPtr->border;
	    }
	    styleValues.relief = tagPtr->relief;
	    reliefPrio = tagPtr->priority;
	}
#ifdef TCL_MSDOS_PORT
	if ((tagPtr->hbmp != None)
		&& (tagPtr->priority > bgStipplePrio)) {
	    styleValues.hbmp = tagPtr->hbmp;
	    bgStipplePrio = tagPtr->priority;
	}
#else
	if ((tagPtr->bgStipple != None)
		&& (tagPtr->priority > bgStipplePrio)) {
	    styleValues.bgStipple = tagPtr->bgStipple;
	    bgStipplePrio = tagPtr->priority;
	}
#endif
	if ((tagPtr->fgColor != None) && (tagPtr->priority > fgPrio)) {
	    styleValues.fgColor = tagPtr->fgColor;
	    fgPrio = tagPtr->priority;
	}
#ifdef TCL_MSDOS_PORT
	if ((tagPtr->hfont != None) && (tagPtr->priority > fontPrio)) {
	    styleValues.hfont = tagPtr->hfont;
	    styleValues.ascent = tagPtr->ascent;
	    styleValues.descent = tagPtr->descent;
	    fontPrio = tagPtr->priority;
	}
#else
	if ((tagPtr->fontPtr != None) && (tagPtr->priority > fontPrio)) {
	    styleValues.fontPtr = tagPtr->fontPtr;
	    fontPrio = tagPtr->priority;
	}
#endif
	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->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->underlineString != NULL)
		&& (tagPtr->priority > underlinePrio)) {
	    styleValues.underline = tagPtr->underline;
	    underlinePrio = 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 = (Style *) Tcl_GetHashValue(hPtr);
	stylePtr->refCount++;
	return stylePtr;
    }

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

    stylePtr = (Style *) ckalloc(sizeof(Style));
    stylePtr->refCount = 1;
#ifdef TCL_MSDOS_PORT
    if((styleValues.border != NULL) && (styleValues.hbmp != None)){
	stylePtr->hbr = CreatePatternBrush(styleValues.hbmp);
    } else stylePtr->hbr = None;
    stylePtr->fgColor = styleValues.fgColor;
#else
    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 = GCForeground|GCFont;
    gcValues.foreground = styleValues.fgColor->pixel;
    gcValues.font = styleValues.fontPtr->fid;
    if (styleValues.fgStipple != None) {
	gcValues.stipple = styleValues.fgStipple;
	gcValues.fill_style = FillStippled;
	mask |= GCStipple|GCFillStyle;
    }
    stylePtr->fgGC = Tk_GetGC(textPtr->tkwin, mask, &gcValues);
#endif
    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 Style 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 Style *stylePtr;	/* Information about style to be freed. */

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

static DLine *
LayoutDLine(textPtr, indexPtr)
    TkText *textPtr;		/* Overall information about text widget. */
    TkTextIndex *indexPtr;	/* Beginning of display line.  May not
				 * necessarily point to a character segment. */
{
    register DLine *dlPtr;		/* New display line. */
    TkTextSegment *segPtr;		/* Current segment in text. */
    TkTextDispChunk *lastChunkPtr;	/* Last chunk allocated so far
					 * for line. */
    TkTextDispChunk *chunkPtr;		/* Current chunk. */
    TkTextIndex curIndex;
    TkTextDispChunk *breakChunkPtr;	/* Chunk containing best word break
					 * point, if any. */
    TkTextIndex breakIndex;		/* Index of first character in
					 * breakChunkPtr. */
    int breakCharOffset;		/* 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 first character in line. */
    int jIndent;			/* Additional indentation (beyond
					 * margins) due to justification. */
    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 offset, ascent, descent, code;
    StyleValues *sValuePtr;

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

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

    /*
     * 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;
    breakChunkPtr = NULL;
    breakCharOffset = 0;
    justify = TK_JUSTIFY_LEFT;

    /*
     * 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 (offset = curIndex.charIndex, segPtr = curIndex.linePtr->segPtr;
	    (offset > 0) && (offset >= segPtr->size);
	    offset -= segPtr->size, segPtr = segPtr->nextPtr) {
	/* Empty loop body. */
    }

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

	/*
	 * 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) {
	    justify = chunkPtr->stylePtr->sValuePtr->justify;
	    x = ((curIndex.charIndex == 0)
		    ? chunkPtr->stylePtr->sValuePtr->lMargin1
		    : chunkPtr->stylePtr->sValuePtr->lMargin2);
	    if (textPtr->wrapMode == tkTextNoneUid) {
		maxX = INT_MAX;
	    } else {
		maxX = textPtr->dInfoPtr->maxX - textPtr->dInfoPtr->x
			- chunkPtr->stylePtr->sValuePtr->rMargin;
		if (maxX < x) {
		    maxX = x;
		}
	    }
	}
	chunkPtr->x = x;
	code = (*segPtr->typePtr->layoutProc)(textPtr, &curIndex, segPtr,
		offset, maxX, segPtr->size - offset, noCharsYet, chunkPtr);
	if (code <= 0) {
	    FreeStyle(textPtr, chunkPtr->stylePtr);
	    if (code < 0) {
		/*
		 * This segment doesn't wish to display itself (e.g. most
		 * marks).
		 */

		continue;
	    }

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

	    if (chunkPtr != NULL) {
		ckfree((char *) chunkPtr);
	    }
	    break;
	}
	if (chunkPtr->numChars > 0) {
	    noCharsYet = 0;
	}
	if (lastChunkPtr == NULL) {
	    dlPtr->chunkPtr = chunkPtr;
	} else {
	    lastChunkPtr->nextPtr = chunkPtr;
	}
	lastChunkPtr = chunkPtr;
	x += chunkPtr->width;
	if (chunkPtr->breakIndex > 0) {
	    breakCharOffset = chunkPtr->breakIndex;
	    breakIndex = curIndex;
	    breakChunkPtr = chunkPtr;
	}
	if ((offset + chunkPtr->numChars) != segPtr->size) {
	    break;
	}
	curIndex.charIndex += segPtr->size - offset;
	chunkPtr = NULL;
    }
    if (noCharsYet) {
	panic("LayoutDLine couldn't place any characters on a line");
    }
    wholeLine = (segPtr == NULL);

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

    if ((breakChunkPtr != NULL) && ((lastChunkPtr != breakChunkPtr)
	    || (breakCharOffset != lastChunkPtr->numChars))) {
	while (1) {
	    chunkPtr = breakChunkPtr->nextPtr;
	    if (chunkPtr == NULL) {
		break;
	    }
	    breakChunkPtr->nextPtr = chunkPtr->nextPtr;
	    (*chunkPtr->undisplayProc)(textPtr, chunkPtr);
	    ckfree((char *) chunkPtr);
	}
	if (breakCharOffset != breakChunkPtr->numChars) {
	    (*breakChunkPtr->undisplayProc)(textPtr, breakChunkPtr);
	    segPtr = TkTextIndexToSeg(&breakIndex, &offset);
	    (*segPtr->typePtr->layoutProc)(textPtr, &breakIndex,
		    segPtr, offset, maxX, breakCharOffset, 0, breakChunkPtr);
	}
	lastChunkPtr = breakChunkPtr;
	wholeLine = 0;
    }

    /*
     * Make one more pass over the line to recompute various things
     * like its height, length, and total number of characters.  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 (textPtr->wrapMode == tkTextNoneUid) {
	maxX = textPtr->dInfoPtr->maxX - textPtr->dInfoPtr->x;
    }
    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->count += chunkPtr->numChars;
	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.charIndex == 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 DInfo *dInfoPtr = textPtr->dInfoPtr;
    register DLine *dlPtr, *prevPtr;
    TkTextIndex index;
    TkTextLine *lastLinePtr;
    int y, maxY, pixelOffset, maxOffset;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

	    newPtr = dlPtr->nextPtr;
	    FreeDLines(textPtr, dlPtr, newPtr, 0);
	    dlPtr = newPtr;
	    continue;
	}

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

	lineOK:
	dlPtr->y = y;
	y += dlPtr->height;
	TkTextIndexForwChars(&index, dlPtr->count, &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 = dlPtr->nextPtr;
	    }
	    if (nextPtr != dlPtr) {
		FreeDLines(textPtr, dlPtr, nextPtr, 0);
		prevPtr->nextPtr = nextPtr;
		dlPtr = nextPtr;
	    }
	}

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

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

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

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

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

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

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

	spaceLeft = maxY - y;
	lineNum = TkBTreeLineIndex(dInfoPtr->dLinePtr->index.linePtr);
	charsToCount = dInfoPtr->dLinePtr->index.charIndex;
	if (charsToCount == 0) {
	    charsToCount = INT_MAX;
	    lineNum--;
	}
	for ( ; (lineNum >= 0) && (spaceLeft > 0); lineNum--) {
	    index.linePtr = TkBTreeFindLine(textPtr->tree, lineNum);
	    index.charIndex = 0;
	    lowestPtr = NULL;
	    do {
		dlPtr = LayoutDLine(textPtr, &index);
		dlPtr->nextPtr = lowestPtr;
		lowestPtr = dlPtr;
		TkTextIndexForwChars(&index, dlPtr->count, &index);
		charsToCount -= dlPtr->count;
	    } while ((charsToCount > 0)
		    && (index.linePtr == lowestPtr->index.linePtr));

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

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

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

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

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

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

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

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

    textPtr->flags |= UPDATE_SCROLLBARS;

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

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

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

    if (unlink) {
	if (textPtr->dInfoPtr->dLinePtr == firstPtr) {
	    textPtr->dInfoPtr->dLinePtr = lastPtr;
	} else {
	    register DLine *prevPtr;
	    for (prevPtr = textPtr->dInfoPtr->dLinePtr;
		    prevPtr->nextPtr != firstPtr; prevPtr = prevPtr->nextPtr) {
		/* Empty loop body. */
	    }
	    prevPtr->nextPtr = lastPtr;
	}
    }
    while (firstPtr != lastPtr) {
	nextDLinePtr = firstPtr->nextPtr;
	for (chunkPtr = firstPtr->chunkPtr; chunkPtr != NULL;
		chunkPtr = nextChunkPtr) {
	    if (chunkPtr->undisplayProc != NULL) {
		(*chunkPtr->undisplayProc)(textPtr, chunkPtr);
	    }
	    FreeStyle(textPtr, chunkPtr->stylePtr);
	    nextChunkPtr = chunkPtr->nextPtr;
	    ckfree((char *) chunkPtr);
	}
	ckfree((char *) firstPtr);
	firstPtr = nextDLinePtr;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * 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.
 *
 *----------------------------------------------------------------------
 */

#ifdef TCL_MSDOS_PORT
static void
DisplayDLine(textPtr, dlPtr, prevPtr, hdc, memDC)
#else
static void
DisplayDLine(textPtr, dlPtr, prevPtr, pixmap)
#endif
    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. */
#ifdef TCL_MSDOS_PORT
    HDC hdc, memDC;
#else
    Pixmap pixmap;		/* Pixmap to use for double-buffering.
				 * Caller must make sure it's large enough
				 * to hold line. */
#endif
{
    register TkTextDispChunk *chunkPtr;
    DInfo *dInfoPtr = textPtr->dInfoPtr;
#ifndef TCL_MSDOS_PORT
    Display *display;
#endif
    int height, x;

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

#ifdef TCL_MSDOS_PORT
    Tk_Fill3DRectangle(memDC, textPtr->border, 0, 0,
	    Tk_Width(textPtr->tkwin), dlPtr->height, 0, TK_RELIEF_FLAT);
#else
    display = Tk_Display(textPtr->tkwin);
    Tk_Fill3DRectangle(display, pixmap, textPtr->border, 0, 0,
	    Tk_Width(textPtr->tkwin), dlPtr->height, 0, TK_RELIEF_FLAT);
#endif

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

#ifdef TCL_MSDOS_PORT
    DisplayLineBackground(textPtr, dlPtr, prevPtr, memDC);
#else
    DisplayLineBackground(textPtr, dlPtr, prevPtr, pixmap);
#endif

    /*
     * 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.
     */

    for (chunkPtr = dlPtr->chunkPtr; (chunkPtr != NULL);
	    chunkPtr = chunkPtr->nextPtr) {
	x = chunkPtr->x + dInfoPtr->x - dInfoPtr->curPixelOffset;
	if (chunkPtr->displayProc == TkTextInsertDisplayProc) {
#ifdef TCL_MSDOS_PORT
	    (*chunkPtr->displayProc)(chunkPtr, x, dlPtr->spaceAbove,
		    dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow,
		    dlPtr->baseline - dlPtr->spaceAbove, memDC,
		    dlPtr->y + dlPtr->spaceAbove);
#else
	    (*chunkPtr->displayProc)(chunkPtr, x, dlPtr->spaceAbove,
		    dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow,
		    dlPtr->baseline - dlPtr->spaceAbove, display, pixmap,
		    dlPtr->y + dlPtr->spaceAbove);
#endif
	}
    }

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

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

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

#ifdef TCL_MSDOS_PORT
	    (*chunkPtr->displayProc)(chunkPtr, -chunkPtr->width,
		    dlPtr->spaceAbove,
		    dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow,
		    dlPtr->baseline - dlPtr->spaceAbove, memDC,
		    dlPtr->y + dlPtr->spaceAbove);
#else
	    (*chunkPtr->displayProc)(chunkPtr, -chunkPtr->width,
		    dlPtr->spaceAbove,
		    dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow,
		    dlPtr->baseline - dlPtr->spaceAbove, display, pixmap,
		    dlPtr->y + dlPtr->spaceAbove);
#endif
	} else {
#ifdef TCL_MSDOS_PORT
	    (*chunkPtr->displayProc)(chunkPtr, x, dlPtr->spaceAbove,
		    dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow,
		    dlPtr->baseline - dlPtr->spaceAbove, memDC,
		    dlPtr->y + dlPtr->spaceAbove);
#else
	    (*chunkPtr->displayProc)(chunkPtr, x, dlPtr->spaceAbove,
		    dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow,
		    dlPtr->baseline - dlPtr->spaceAbove, display, pixmap,
		    dlPtr->y + dlPtr->spaceAbove);
#endif
	}
    }

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

    height = dlPtr->height;
    if ((height + dlPtr->y) > dInfoPtr->maxY) {
	height = dInfoPtr->maxY - dlPtr->y;
    }
#ifdef TCL_MSDOS_PORT
    BitBlt(hdc, dInfoPtr->x, dlPtr->y,  dInfoPtr->maxX - dInfoPtr->x, height,
	    memDC,  dInfoPtr->x, 0, SRCCOPY);
#else
    XCopyArea(display, pixmap, Tk_WindowId(textPtr->tkwin), dInfoPtr->copyGC,
	    dInfoPtr->x, 0, dInfoPtr->maxX - dInfoPtr->x, height,
	    dInfoPtr->x, dlPtr->y);
#endif
    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.
 *
 *--------------------------------------------------------------
 */

#ifdef TCL_MSDOS_PORT
static void
DisplayLineBackground(textPtr, dlPtr, prevPtr, memDC)
#else
static void
DisplayLineBackground(textPtr, dlPtr, prevPtr, pixmap)
#endif
    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. */
#ifdef TCL_MSDOS_PORT
    HDC memDC;
#else
    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. */
#endif
{
    DInfo *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;
#ifndef TCL_MSDOS_PORT
    Display *display;
#endif

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

#ifndef TCL_MSDOS_PORT
    display = Tk_Display(textPtr->tkwin);
#endif
    minX = dInfoPtr->curPixelOffset;
    xOffset = dInfoPtr->x - minX;
    maxX = minX + dInfoPtr->maxX - dInfoPtr->x;
    chunkPtr = dlPtr->chunkPtr;
    leftX = chunkPtr->x;
    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;
	}
#ifdef TCL_MSDOS_PORT
	if (chunkPtr->stylePtr->hbr != None) {
	    RECT rect;
   	    SetRect(&rect, leftX + xOffset, 0, rightX + xOffset,
		dlPtr->height);
   	    FillRect(memDC, &rect, chunkPtr->stylePtr->hbr);
	    if (sValuePtr->relief != TK_RELIEF_FLAT) {
		Tk_3DVerticalBevel(memDC, sValuePtr->border,
			leftX + xOffset, 0, sValuePtr->borderWidth,
			dlPtr->height, 1, sValuePtr->relief);
		Tk_3DVerticalBevel(memDC, sValuePtr->border,
			rightX - sValuePtr->borderWidth + xOffset,
			0, sValuePtr->borderWidth, dlPtr->height, 0,
			sValuePtr->relief);
	    }
	}
#else
	if (chunkPtr->stylePtr->bgGC != None) {
	    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(display, pixmap, sValuePtr->border,
			leftX + xOffset, 0, sValuePtr->borderWidth,
			dlPtr->height, 1, sValuePtr->relief);
		Tk_3DVerticalBevel(display, pixmap, sValuePtr->border,
			rightX - sValuePtr->borderWidth + xOffset,
			0, sValuePtr->borderWidth, dlPtr->height, 0,
			sValuePtr->relief);
	    }
	}
#endif
	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 = chunkPtr->x;
    leftXIn = 1;
    rightX = chunkPtr->x + chunkPtr->width;
    if ((chunkPtr->nextPtr == NULL) && (rightX < maxX)) {
	rightX = maxX;
    }
    chunkPtr2 = NULL;
    if (prevPtr != NULL) {
	/*
	 * Find the chunk in the previous line that covers leftX.
	 */

	nextPtr2 = prevPtr->chunkPtr;
	rightX2 = nextPtr2->x;
	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)) {
#ifdef TCL_MSDOS_PORT
		    Tk_3DHorizontalBevel(memDC, sValuePtr->border,
			    leftX + xOffset, 0, rightX - leftX,
			    sValuePtr->borderWidth, leftXIn, 1, 1,
			    sValuePtr->relief);
#else
		    Tk_3DHorizontalBevel(display, pixmap, sValuePtr->border,
			    leftX + xOffset, 0, rightX - leftX,
			    sValuePtr->borderWidth, leftXIn, 1, 1,
			    sValuePtr->relief);
#endif
		}
		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) {
#ifdef TCL_MSDOS_PORT
		Tk_3DVerticalBevel(memDC, sValuePtr->border,
			rightX2 - sValuePtr->borderWidth + xOffset, 0,
			sValuePtr->borderWidth, sValuePtr->borderWidth, 0,
			sValuePtr->relief);
#else
		Tk_3DVerticalBevel(display, pixmap, sValuePtr->border,
			rightX2 - sValuePtr->borderWidth + xOffset, 0,
			sValuePtr->borderWidth, sValuePtr->borderWidth, 0,
			sValuePtr->relief);
#endif
	    }
	    leftX = rightX2 - sValuePtr->borderWidth;
	    leftXIn = 0;
	} else if (!matchLeft && matchRight
		&& (sValuePtr->relief != TK_RELIEF_FLAT)) {
#ifdef TCL_MSDOS_PORT
	    Tk_3DVerticalBevel(memDC, sValuePtr->border,
		    rightX2 + xOffset, 0, sValuePtr->borderWidth,
		    sValuePtr->borderWidth, 1, sValuePtr->relief);
	    Tk_3DHorizontalBevel(memDC, sValuePtr->border,
		    leftX + xOffset, 0, rightX2 + sValuePtr->borderWidth -leftX,
		    sValuePtr->borderWidth, leftXIn, 0, 1,
		    sValuePtr->relief);
#else
	    Tk_3DVerticalBevel(display, pixmap, sValuePtr->border,
		    rightX2 + xOffset, 0, sValuePtr->borderWidth,
		    sValuePtr->borderWidth, 1, sValuePtr->relief);
	    Tk_3DHorizontalBevel(display, pixmap, sValuePtr->border,
		    leftX + xOffset, 0, rightX2 + sValuePtr->borderWidth -leftX,
		    sValuePtr->borderWidth, leftXIn, 0, 1,
		    sValuePtr->relief);
#endif
	}

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

	nextPtr2 = dlPtr->nextPtr->chunkPtr;
	rightX2 = nextPtr2->x;
	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)) {
#ifdef TCL_MSDOS_PORT
		    Tk_3DHorizontalBevel(memDC, sValuePtr->border,
			    leftX + xOffset,
			    dlPtr->height - sValuePtr->borderWidth,
			    rightX - leftX, sValuePtr->borderWidth, leftXIn,
			    0, 0, sValuePtr->relief);
#else
		    Tk_3DHorizontalBevel(display, pixmap, sValuePtr->border,
			    leftX + xOffset,
			    dlPtr->height - sValuePtr->borderWidth,
			    rightX - leftX, sValuePtr->borderWidth, leftXIn,
			    0, 0, sValuePtr->relief);
#endif
		}
		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) {
#ifdef TCL_MSDOS_PORT
		Tk_3DVerticalBevel(memDC, sValuePtr->border,
			rightX2 - sValuePtr->borderWidth + xOffset,
			dlPtr->height - sValuePtr->borderWidth,
			sValuePtr->borderWidth, sValuePtr->borderWidth, 0,
			sValuePtr->relief);
#else
		Tk_3DVerticalBevel(display, pixmap, sValuePtr->border,
			rightX2 - sValuePtr->borderWidth + xOffset,
			dlPtr->height - sValuePtr->borderWidth,
			sValuePtr->borderWidth, sValuePtr->borderWidth, 0,
			sValuePtr->relief);
#endif
	    }
	    leftX = rightX2 - sValuePtr->borderWidth;
	    leftXIn = 1;
	} else if (!matchLeft && matchRight
		&& (sValuePtr->relief != TK_RELIEF_FLAT)) {
#ifdef TCL_MSDOS_PORT
	    Tk_3DVerticalBevel(memDC, sValuePtr->border,
		    rightX2 + xOffset, dlPtr->height - sValuePtr->borderWidth,
		    sValuePtr->borderWidth, sValuePtr->borderWidth,
		    1, sValuePtr->relief);
	    Tk_3DHorizontalBevel(memDC, sValuePtr->border,
		    leftX + xOffset, dlPtr->height - sValuePtr->borderWidth,
		    rightX2 + sValuePtr->borderWidth - leftX,
		    sValuePtr->borderWidth, leftXIn, 1, 0, sValuePtr->relief);
#else
	    Tk_3DVerticalBevel(display, pixmap, sValuePtr->border,
		    rightX2 + xOffset, dlPtr->height - sValuePtr->borderWidth,
		    sValuePtr->borderWidth, sValuePtr->borderWidth,
		    1, sValuePtr->relief);
	    Tk_3DHorizontalBevel(display, pixmap, sValuePtr->border,
		    leftX + xOffset, dlPtr->height - sValuePtr->borderWidth,
		    rightX2 + sValuePtr->borderWidth - leftX,
		    sValuePtr->borderWidth, leftXIn, 1, 0, sValuePtr->relief);
#endif
	}

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

static void
DisplayText(clientData)
    ClientData clientData;	/* Information about widget. */
{
    register TkText *textPtr = (TkText *) clientData;
    DInfo *dInfoPtr = textPtr->dInfoPtr;
    Tk_Window tkwin;
    register DLine *dlPtr;
    DLine *prevPtr;
#ifdef TCL_MSDOS_PORT
    HDC hdc, memDC;
    HFONT hfontOld;
    HBITMAP cbm, cbmOld;
#else
    Pixmap pixmap;
#endif
    int maxHeight;
    int bottomY = 0;		/* Initialization needed only to stop
				 * compiler warnings. */

    if (textPtr->tkwin == NULL) {
	/*
	 * The widget has been deleted.  Don't do anything.
	 */

	return;
    }

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

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

    /*
     * 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 Tk_Release call.
     */

    while (dInfoPtr->flags & REPICK_NEEDED) {
	Tk_Preserve((ClientData) textPtr);
	dInfoPtr->flags &= ~REPICK_NEEDED;
	TkTextPickCurrent(textPtr, &textPtr->pickEvent);
	tkwin = textPtr->tkwin;
	Tk_Release((ClientData) textPtr);
	if (tkwin == NULL) {
	    return;
	}
    }

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

    UpdateDisplayInfo(textPtr);

    /*
     * See if it's possible to bring some parts of the screen up-to-date
     * by scrolling (copying from other parts of the screen).
     */
#ifdef TCL_MSDOS_PORT
    BringWindowToTop(((TkWindow *)(tkwin = textPtr->tkwin))->window);
    hdc = GetDC(((TkWindow *)(tkwin = textPtr->tkwin))->window);
#endif

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

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

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

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

	/*
	 * Copy the information and update the lines to show that they've
	 * been copied.  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;
	}
#ifdef TCL_MSDOS_PORT
	BitBlt(hdc, dInfoPtr->x - textPtr->padX, y, 
		dInfoPtr->maxX - (int)(dInfoPtr->x - textPtr->padX), height,
		hdc, dInfoPtr->x - textPtr->padX, dlPtr->oldY, SRCCOPY);
#else
	XCopyArea(Tk_Display(textPtr->tkwin), Tk_WindowId(textPtr->tkwin),
		Tk_WindowId(textPtr->tkwin), dInfoPtr->scrollGC,
		dInfoPtr->x - textPtr->padX, dlPtr->oldY,
		dInfoPtr->maxX - (dInfoPtr->x - textPtr->padX),
		height, dInfoPtr->x - textPtr->padX, y);
#endif
	numCopies++;
	while (1) {
	    dlPtr->oldY = dlPtr->y;
	    if (dlPtr->nextPtr == dlPtr2) {
		break;
	    }
	    dlPtr = dlPtr->nextPtr;
	}

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

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

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

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

	/*
	 * It's possible that part of the area copied above was obscured.
	 * To handle this situation, read expose-related events generated
	 * during the XCopyArea operation.
	 */

#ifndef TCL_MSDOS_PORT
	while (1) {
	    XEvent event;

	    XWindowEvent(Tk_Display(textPtr->tkwin),
		    Tk_WindowId(textPtr->tkwin), ExposureMask, &event);
	    if (event.type == NoExpose) {
		break;
	    } else if (event.type == GraphicsExpose) {
		TkTextRedrawRegion(textPtr, event.xgraphicsexpose.x,
			event.xgraphicsexpose.y, event.xgraphicsexpose.width,
			event.xgraphicsexpose.height);
		if (event.xgraphicsexpose.count == 0) {
		    damagedCopies++;
		    break;
		}
	    } else if (event.type == Expose) {
		/*
		 * A tricky situation.  This event must already have been
		 * queued up before the XCopyArea was issued.  If the area
		 * in this event overlaps the area copied, then some of the
		 * bits that were copied were bogus.  The easiest way to
		 * handle this is to issue two redisplays:  one for the
		 * original area and one for the area shifted as if it was
		 * in the copied area.
		 */

		TkTextRedrawRegion(textPtr, event.xexpose.x,
			event.xexpose.y, event.xexpose.width,
			event.xexpose.height);
		TkTextRedrawRegion(textPtr, event.xexpose.x,
			event.xexpose.y + offset, event.xexpose.width,
			event.xexpose.height);
	    } else {
		panic("DisplayText received unknown exposure event");
	    }
	}
#endif
    }

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

    if (dInfoPtr->flags & REDRAW_BORDERS) {
	if (tkTextDebug) {
	    Tcl_SetVar2(textPtr->interp, "tk_textRedraw",
		    (char *) NULL, "borders",
		    TCL_GLOBAL_ONLY|TCL_APPEND_VALUE|TCL_LIST_ELEMENT);
	}
#ifdef TCL_MSDOS_PORT
	Tk_Draw3DRectangle(hdc, textPtr->border,
		textPtr->highlightWidth, textPtr->highlightWidth,
		Tk_Width(textPtr->tkwin) - 2*textPtr->highlightWidth,
		Tk_Height(textPtr->tkwin) - 2*textPtr->highlightWidth,
		textPtr->borderWidth, textPtr->relief);
#else
	Tk_Draw3DRectangle(Tk_Display(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);
#endif
	if (textPtr->highlightWidth != 0) {
#ifdef TCL_MSDOS_PORT
	    HBRUSH brush;
	    if (textPtr->flags & GOT_FOCUS) {
	        XColor *cp = textPtr->highlightColorPtr;
	        brush =
		    CreateSolidBrush(RGB(cp->peRed, cp->peGreen, cp->peBlue));
	    } else {
	        brush = Tk_3DBorderGC(textPtr->border, TK_3D_FLAT_GC);
	    }
	    TkDrawFocusHighlight(tkwin, brush, textPtr->highlightWidth, hdc);
	    if (textPtr->flags & GOT_FOCUS) DeleteObject(brush);
#else
	    GC gc;
    
	    if (textPtr->flags & GOT_FOCUS) {
		gc = dInfoPtr->copyGC;
	    } else {
		gc = Tk_3DBorderGC(textPtr->border, TK_3D_FLAT_GC);
	    }
	    TkDrawFocusHighlight(textPtr->tkwin, gc, textPtr->highlightWidth,
		    Tk_WindowId(textPtr->tkwin));
#endif
	}
	dInfoPtr->flags &= ~REDRAW_BORDERS;
    }

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

    maxHeight = -1;
    for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL;
	    dlPtr = dlPtr->nextPtr) {
	if ((dlPtr->height > maxHeight) && (dlPtr->oldY != dlPtr->y)) {
	    maxHeight = dlPtr->height;
	}
	bottomY = dlPtr->y + dlPtr->height;
    }
    if (maxHeight > dInfoPtr->maxY) {
	maxHeight = dInfoPtr->maxY;
    }
    if (maxHeight > 0) {
#ifdef TCL_MSDOS_PORT
        memDC = CreateCompatibleDC(hdc);
        cbm = CreateCompatibleBitmap(hdc, Tk_Width(textPtr->tkwin), maxHeight);
        cbmOld = SelectObject(memDC, cbm);
        hfontOld = SelectObject(memDC, textPtr->hfont);
#ifndef NOTDEF
        SetTextColor(memDC, RGB(textPtr->fgColor->peRed,
	    textPtr->fgColor->peGreen, textPtr->fgColor->peBlue));
#endif
        SetBkMode(memDC, TRANSPARENT);
#else
	pixmap = Tk_GetPixmap(Tk_Display(textPtr->tkwin),
		Tk_WindowId(textPtr->tkwin), Tk_Width(textPtr->tkwin),
		maxHeight, Tk_Depth(textPtr->tkwin));
#endif
	for (prevPtr = NULL, dlPtr = textPtr->dInfoPtr->dLinePtr;
		(dlPtr != NULL) && (dlPtr->y < dInfoPtr->maxY);
		prevPtr = dlPtr, dlPtr = dlPtr->nextPtr) {
	    if (dlPtr->oldY != dlPtr->y) {
		if (tkTextDebug) {
		    char string[TK_POS_CHARS];
		    TkTextPrintIndex(&dlPtr->index, string);
		    Tcl_SetVar2(textPtr->interp, "tk_textRedraw",
			    (char *) NULL, string,
			    TCL_GLOBAL_ONLY|TCL_APPEND_VALUE|TCL_LIST_ELEMENT);
		}
#ifdef TCL_MSDOS_PORT
		DisplayDLine(textPtr, dlPtr, prevPtr, hdc, memDC);
#else
		DisplayDLine(textPtr, dlPtr, prevPtr, pixmap);
#endif
		dlPtr->oldY = dlPtr->y;
		dlPtr->flags &= ~NEW_LAYOUT;
	    }
	}
#ifdef TCL_MSDOS_PORT
        SelectObject(memDC, hfontOld);
	SelectObject(memDC, cbmOld);
	DeleteObject(cbm);
	DeleteDC(memDC);
#else
	Tk_FreePixmap(Tk_Display(textPtr->tkwin), pixmap);
#endif
    }

    /*
     * 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);
	}
#ifdef TCL_MSDOS_PORT
	Tk_Fill3DRectangle(hdc, textPtr->border,
		dInfoPtr->x - textPtr->padX, bottomY,
		dInfoPtr->maxX - (dInfoPtr->x - textPtr->padX),
		dInfoPtr->topOfEof-bottomY, 0, TK_RELIEF_FLAT);
#else
	Tk_Fill3DRectangle(Tk_Display(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);
#endif
    }
    dInfoPtr->topOfEof = bottomY;
#ifdef TCL_MSDOS_PORT
    ReleaseDC(((TkWindow *)tkwin)->window, hdc);
#endif

    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.
     */

    dInfoPtr->flags &= ~REDRAW_PENDING;
    if (textPtr->flags & UPDATE_SCROLLBARS) {
	textPtr->flags &= ~UPDATE_SCROLLBARS;
	if (textPtr->yScrollCmd != NULL) {
	    char buffer[200];
	    int code;

	    GetYView(textPtr, buffer);
	    code = Tcl_VarEval(textPtr->interp, textPtr->yScrollCmd, " ",
		    buffer, (char *) NULL);
	    if (code != TCL_OK) {
		Tcl_AddErrorInfo(textPtr->interp,
			"\n    (vertical scrolling command executed by text)");
		Tk_BackgroundError(textPtr->interp);
	    }
	}

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

	if (textPtr->xScrollCmd != NULL) {
	    double start, end;
	    char string[100];
	    int code;

	    if ((textPtr->wrapMode == tkTextNoneUid)
		    && (dInfoPtr->maxLength > 0)) {
		start = ((double) dInfoPtr->curPixelOffset)
			/ dInfoPtr->maxLength;
		end = start + ((double) (dInfoPtr->maxX - dInfoPtr->x))
			/ dInfoPtr->maxLength;
		if (end > 1.0) {
		    end = 1.0;
		}
		sprintf(string, " %g %g", start, end);
	    } else {
		sprintf(string, " 0 1");
	    }
    
	    code = Tcl_VarEval(textPtr->interp, textPtr->xScrollCmd, string,
		    (char *) NULL);
	    if (code != TCL_OK) {
		Tcl_AddErrorInfo(textPtr->interp,
			"\n    (horizontal scrolling command executed by text)");
		Tk_BackgroundError(textPtr->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. */
{
    DInfo *dInfoPtr = textPtr->dInfoPtr;

    dInfoPtr->flags |= REPICK_NEEDED;
    if (!(dInfoPtr->flags & REDRAW_PENDING)) {
	dInfoPtr->flags |= REDRAW_PENDING;
	Tk_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. */
{
    register DLine *dlPtr;
    DInfo *dInfoPtr = textPtr->dInfoPtr;
    int maxY, inset;

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

    maxY = y + height;
    for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL;
	    dlPtr = dlPtr->nextPtr) {
	if (((dlPtr->y + dlPtr->height) > y) && (dlPtr->y < maxY)) {
	    dlPtr->oldY = -1;
	}
    }
    if (dInfoPtr->topOfEof < maxY) {
	dInfoPtr->topOfEof = maxY;
    }

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

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

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

    /*
     * 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.charIndex = 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;
    }

    /*
     * Schedule both a redisplay and a recomputation of display information.
     * It's important to do this 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)) {
	Tk_DoWhenIdle(DisplayText, (ClientData) textPtr);
    }
    dInfoPtr->flags |= REDRAW_PENDING|DINFO_OUT_OF_DATE|REPICK_NEEDED;

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

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

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

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

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

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

    if (index2Ptr == NULL) {
	index2Ptr = TkTextMakeIndex(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);
    tagOn = TkBTreeCharTagged(index1Ptr, tagPtr);
    if (tagOn != withTag) {
	if (!TkBTreeNextTag(&search)) {
	    return;
	}
    }

    /*
     * 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)) {
	Tk_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 (search.curIndex.charIndex == 0) {
	    dlPtr = FindDLine(dlPtr, &search.curIndex);
	} else {
	    TkTextIndex tmp;

	    tmp = search.curIndex;
	    tmp.charIndex -= 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 {
	    endIndexPtr = &search.curIndex;
	}
	endPtr = FindDLine(dlPtr, endIndexPtr);
	if ((endPtr != NULL) && (endPtr->index.linePtr == endIndexPtr->linePtr)
		&& (endPtr->index.charIndex < endIndexPtr->charIndex)) {
	    endPtr = endPtr->nextPtr;
	}

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

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

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

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

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

    /*
     * 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)) {
	Tk_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.
     */

#ifndef TCL_MSDOS_PORT
    gcValues.graphics_exposures = False;
    gcValues.foreground = textPtr->highlightColorPtr->pixel;
    new = Tk_GetGC(textPtr->tkwin, GCForeground|GCGraphicsExposures,
	    &gcValues);
    if (dInfoPtr->copyGC != None) {
	Tk_FreeGC(textPtr->display, dInfoPtr->copyGC);
    }
    dInfoPtr->copyGC = new;
#endif

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

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

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

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

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

    if (textPtr->topIndex.charIndex != 0) {
	MeasureUp(textPtr, &textPtr->topIndex, 0, &textPtr->topIndex);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * TkTextSetYView --
 *
 *	This procedure is called to specify what lines are to be
 *	displayed in a text widget.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The display will (eventually) be updated so that the position
 *	given by "indexPtr" is visible on the screen at the position
 *	determined by "pickPlace".
 *
 *----------------------------------------------------------------------
 */

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

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

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

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

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

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

    if (dInfoPtr->flags & DINFO_OUT_OF_DATE) {
	UpdateDisplayInfo(textPtr);
    }
    dlPtr = FindDLine(dInfoPtr->dLinePtr, indexPtr);
    if ((dlPtr != NULL) && (dlPtr->index.linePtr == indexPtr->linePtr)
	    && (dlPtr->index.charIndex <= indexPtr->charIndex)
	    && ((dlPtr->y + dlPtr->height) <= dInfoPtr->maxY)) {
	return;
    }

    /*
     * The desired line isn't already on-screen.
     */

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

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

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

    /*
     * 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 the center of the window.
     */

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

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

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

    noBestYet = 1;
    charsToCount = srcPtr->charIndex + 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 (charsToCount 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.charIndex = 0;
	lowestPtr = NULL;
	do {
	    dlPtr = LayoutDLine(textPtr, &index);
	    dlPtr->nextPtr = lowestPtr;
	    lowestPtr = dlPtr;
	    TkTextIndexForwChars(&index, dlPtr->count, &index);
	    charsToCount -= dlPtr->count;
	} while ((charsToCount > 0) && (index.linePtr == dlPtr->index.linePtr));

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

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

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

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

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

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

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

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

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

    TkTextSetYView(textPtr, &index, 1);

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

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

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

    dlPtr = FindDLine(dInfoPtr->dLinePtr, &index);
    charCount = index.charIndex - dlPtr->index.charIndex;
    for (chunkPtr = dlPtr->chunkPtr; ; chunkPtr = chunkPtr->nextPtr) {
	if (charCount < chunkPtr->numChars) {
	    break;
	}
	charCount -= chunkPtr->numChars;
    }

    /*
     * Call a chunk-specific procedure to find the horizontal range of
     * the character within the chunk.
     */

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

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

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

    if (argc == 2) {
	if (textPtr->wrapMode == tkTextNoneUid) {
	    start = ((double) dInfoPtr->curPixelOffset)/dInfoPtr->maxLength;
	    end = start + ((double) (dInfoPtr->maxX - dInfoPtr->x))
		    / dInfoPtr->maxLength;
	    if (end > 1.0) {
		end = 1.0;
	    }
	    sprintf(interp->result, "%g %g", start, end);
	} else {
	    interp->result = "0 1";
	}
	return TCL_OK;
    }

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

    /*
     * Don't bother scrolling if we're wrapping lines.
     */

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

static void
ScrollByLines(textPtr, offset)
    TkText *textPtr;		/* Widget to scroll. */
    int offset;			/* Amount by which to scroll, in *screen*
				 * lines.  Positive means that information
				 * later in text becomes visible, negative
				 * means that information earlier in the
				 * text becomes visible. */
{
    int i, charsToCount, lineNum;
    TkTextIndex new, index;
    TkTextLine *lastLinePtr;
    DInfo *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.
	 */

	charsToCount = textPtr->topIndex.charIndex + 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.charIndex = 0;
	    lowestPtr = NULL;
	    do {
		dlPtr = LayoutDLine(textPtr, &index);
		dlPtr->nextPtr = lowestPtr;
		lowestPtr = dlPtr;
		TkTextIndexForwChars(&index, dlPtr->count, &index);
		charsToCount -= dlPtr->count;
	    } while ((charsToCount > 0)
		    && (index.linePtr == dlPtr->index.linePtr));

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

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

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

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

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

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

    if (argc == 2) {
	GetYView(textPtr, interp->result);
	return TCL_OK;
    }

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

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

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

    type = Tk_GetScrollInfo(interp, argc, argv, &fraction, &count);
    switch (type) {
	case TK_SCROLL_ERROR:
	    return TCL_ERROR;
	case TK_SCROLL_MOVETO:
	    fraction *= TkBTreeNumLines(textPtr->tree);
	    lineNum = fraction;
	    TkTextMakeIndex(textPtr->tree, lineNum+1, 0, &index);
	    TkTextIndexBackChars(&index, 1, &index);
	    index.charIndex = (index.charIndex+1)*(fraction-lineNum);
	    TkTextSetYView(textPtr, &index, 0);
	    break;
	case TK_SCROLL_PAGES:
	    /*
	     * Scroll up or down by screenfulls.  Actually, use the
	     * window height minus two lines, so that there's some
	     * overlap between adjacent pages.
	     */

#ifdef TCL_MSDOS_PORT
	    lineHeight = textPtr->ascent + textPtr->descent;
#else
	    lineHeight = textPtr->fontPtr->ascent + textPtr->fontPtr->descent;
#endif
	    if (count < 0) {
		pixels = (dInfoPtr->maxY - 2*lineHeight - dInfoPtr->y)*(-count)
			+ lineHeight;
		MeasureUp(textPtr, &textPtr->topIndex, pixels, &new);
		if (TkTextIndexCmp(&textPtr->topIndex, &new) == 0) {
		    /*
		     * A page of scrolling ended up being less than one line.
		     * Scroll one line anyway.
		     */

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

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

int
TkTextScanCmd(textPtr, interp, argc, argv)
    register TkText *textPtr;	/* Information about text widget. */
    Tcl_Interp *interp;		/* Current interpreter. */
    int argc;			/* Number of arguments. */
    char **argv;		/* Argument strings.  Someone else has already
				 * parsed this command enough to know that
				 * argv[1] is "scan". */
{
    DInfo *dInfoPtr = textPtr->dInfoPtr;
    TkTextIndex index;
#ifdef TCL_MSDOS_PORT
    int length, x, y, totalScroll;
    long newChar, maxChar;
#else
    int length, x, y, totalScroll, newChar, maxChar;
#endif
    char c;

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

#ifdef TCL_MSDOS_PORT
	newChar = dInfoPtr->scanMarkChar + (10L*(dInfoPtr->scanMarkX - x))
		/ (textPtr->charWidth);
	maxChar = 1 + (dInfoPtr->maxLength - (dInfoPtr->maxX - dInfoPtr->x)
		+ textPtr->charWidth - 1)/(long)textPtr->charWidth;
#else
	newChar = dInfoPtr->scanMarkChar + (10*(dInfoPtr->scanMarkX - x))
		/ (textPtr->charWidth);
	maxChar = 1 + (dInfoPtr->maxLength - (dInfoPtr->maxX - dInfoPtr->x)
		+ textPtr->charWidth - 1)/textPtr->charWidth;
#endif
	if (newChar < 0) {
	    dInfoPtr->scanMarkChar = newChar = 0;
	    dInfoPtr->scanMarkX = x;
	} else if (newChar > maxChar) {
	    dInfoPtr->scanMarkChar = newChar = maxChar;
	    dInfoPtr->scanMarkX = x;
	}
	if (textPtr->wrapMode == tkTextNoneUid) {
	    dInfoPtr->newCharOffset = newChar;
	}

#ifdef TCL_MSDOS_PORT
	totalScroll = (10*(dInfoPtr->scanMarkY - y))
		/ (textPtr->ascent + textPtr->descent);
#else
	totalScroll = (10*(dInfoPtr->scanMarkY - y))
		/ (textPtr->fontPtr->ascent + textPtr->fontPtr->descent);
#endif
	if (totalScroll != dInfoPtr->scanTotalScroll) {
	    index = textPtr->topIndex;
	    ScrollByLines(textPtr, totalScroll-dInfoPtr->scanTotalScroll);
	    dInfoPtr->scanTotalScroll = totalScroll;
	    if ((index.linePtr == textPtr->topIndex.linePtr) &&
		    (index.charIndex == textPtr->topIndex.charIndex)) {
		dInfoPtr->scanTotalScroll = 0;
		dInfoPtr->scanMarkY = y;
	    }
	}
    } else if ((c == 'm') && (strncmp(argv[2], "mark", length) == 0)) {
	dInfoPtr->scanMarkChar = dInfoPtr->newCharOffset;
	dInfoPtr->scanMarkX = x;
	dInfoPtr->scanTotalScroll = 0;
	dInfoPtr->scanMarkY = y;
    } else {
	Tcl_AppendResult(interp, "bad scan option \"", argv[2],
		"\":  must be mark or dragto", (char *) NULL);
	return TCL_ERROR;
    }
    dInfoPtr->flags |= DINFO_OUT_OF_DATE;
    if (!(dInfoPtr->flags & REDRAW_PENDING)) {
	dInfoPtr->flags |= REDRAW_PENDING;
	Tk_DoWhenIdle(DisplayText, (ClientData) textPtr);
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * GetYView --
 *
 *	This procedure computes the fractions that indicate what's
 *	visible in a text window.
 *
 * Results:
 *	The string at buffer 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.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static void
GetYView(textPtr, buffer)
    TkText *textPtr;			/* Information about text widget. */
    char *buffer;			/* Storage area big enough to hold
					 * two real numbers. */
{
    DInfo *dInfoPtr = textPtr->dInfoPtr;
    double first, last;
    DLine *dlPtr;
    int totalLines;

    dlPtr = dInfoPtr->dLinePtr;
    totalLines = TkBTreeNumLines(textPtr->tree);
    first = ((double) TkBTreeLineIndex(dlPtr->index.linePtr))
	    + ((double) dlPtr->index.charIndex)
	    / (TkBTreeCharsInLine(dlPtr->index.linePtr));
    first /= totalLines;
    while (dlPtr->nextPtr != NULL) {
	dlPtr = dlPtr->nextPtr;
    }
    last = ((double) TkBTreeLineIndex(dlPtr->index.linePtr))
	    + ((double) (dlPtr->index.charIndex + dlPtr->count))
	    / (TkBTreeCharsInLine(dlPtr->index.linePtr));
    last /= totalLines;
    sprintf(buffer, "%g %g", first, last);
}

/*
 *----------------------------------------------------------------------
 *
 * FindDLine --
 *
 *	This procedure is called to find the DLine corresponding to a
 *	given text index.
 *
 * Results:
 *	The return value is a pointer to the first DLine found in the
 *	list headed by dlPtr that displays information at or after the
 *	specified position.  If there is no such line in the list then
 *	NULL is returned.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

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

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

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

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

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

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

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

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

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

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

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

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

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

	    x = dInfoPtr->maxX - 1;
	    break;
	}
    }

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

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

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

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

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

    /*
     * 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.
     */

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

    /*
     * 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, index, dlPtr->y + dlPtr->spaceAbove,
	    dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow,
	    dlPtr->baseline - dlPtr->spaceAbove, xPtr, yPtr, widthPtr,
	    heightPtr);
    *xPtr = *xPtr + dInfoPtr->x - dInfoPtr->curPixelOffset;
    if ((*xPtr + *widthPtr) <= dInfoPtr->x) {
	return -1;
    }
    if ((*xPtr + *widthPtr) > dInfoPtr->maxX) {
	*widthPtr = dInfoPtr->maxX - *xPtr;
	if (*widthPtr <= 0) {
	    return -1;
	}
    } else if ((index == (chunkPtr->numChars-1))
	    && (chunkPtr->nextPtr == NULL)) {
	/*
	 * Last character in display line;  give it all the rest of
	 * the space in the line.
	 */

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

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

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

    *xPtr = dInfoPtr->x - dInfoPtr->curPixelOffset + dlPtr->chunkPtr->x;
    *widthPtr = dlPtr->length - dlPtr->chunkPtr->x;
    *yPtr = dlPtr->y;
    if ((dlPtr->y + dlPtr->height) > dInfoPtr->maxY) {
	*heightPtr = dInfoPtr->maxY - dlPtr->y;
    } else {
	*heightPtr = dlPtr->height;
    }
    *basePtr = dlPtr->baseline;
    return 0;
}

/*
 *--------------------------------------------------------------
 *
 * 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, offset, maxX, maxChars,
	noCharsYet, 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 offset;			/* Offset within segment of first character
				 * to consider. */
    int maxX;			/* Chunk must not occupy pixels at this
				 * position or higher. */
    int maxChars;		/* Chunk must not include more than this
				 * many characters. */
    int noCharsYet;		/* Non-zero means no characters have been
				 * assigned to this display line yet. */
    register TkTextDispChunk *chunkPtr;
				/* Structure to fill in with information
				 * about this chunk.  The x field has already
				 * been set by the caller. */
{
#ifdef TCL_MSDOS_PORT
    HFONT hfont;
    StyleValues *sValuePtr;
#else
    XFontStruct *fontPtr;
#endif
    int nextX, charsThatFit, count;
    CharInfo *ciPtr;
    char *p;
    TkTextSegment *nextPtr;

    /*
     * 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 + offset;
#ifdef TCL_MSDOS_PORT
    sValuePtr = chunkPtr->stylePtr->sValuePtr;
    hfont = sValuePtr->hfont;
    charsThatFit = TkMeasureChars(hfont, p, maxChars, chunkPtr->x,
	    maxX, 0, 0, &nextX);
#else
    fontPtr = chunkPtr->stylePtr->sValuePtr->fontPtr;
    charsThatFit = TkMeasureChars(fontPtr, p, maxChars, chunkPtr->x,
	    maxX, 0, 0, &nextX);
#endif
    if (charsThatFit < maxChars) {
	if ((charsThatFit == 0) && noCharsYet) {
	    charsThatFit = 1;
#ifdef TCL_MSDOS_PORT
	    TkMeasureChars(hfont, p, 1, chunkPtr->x, INT_MAX, 0, 0, &nextX);
#else
	    TkMeasureChars(fontPtr, p, 1, chunkPtr->x, INT_MAX, 0, 0, &nextX);
#endif
	} else if ((nextX < maxX) && (isspace(UCHAR(p[charsThatFit])))) {
	    if (p[charsThatFit] != '\n') {
		/*
		 * This is very tricky.  Expand the width of the line to
		 * include all of the available space, *except* if the
		 * character is the terminating newline.  Don't expand
		 * the width in this case, so that we get an accurate
		 * measurement of the line's total length.
		 */

		nextX = maxX;
	    }
	    charsThatFit++;
	}
	if (charsThatFit == 0) {
	    return 0;
	}
    }

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

    chunkPtr->displayProc = CharDisplayProc;
    chunkPtr->undisplayProc = CharUndisplayProc;
    chunkPtr->measureProc = CharMeasureProc;
    chunkPtr->bboxProc = CharBboxProc;
    chunkPtr->numChars = charsThatFit;
#ifdef TCL_MSDOS_PORT
    chunkPtr->minAscent = sValuePtr->ascent
	    + chunkPtr->stylePtr->sValuePtr->offset;
    chunkPtr->minDescent = sValuePtr->descent
	    - chunkPtr->stylePtr->sValuePtr->offset;;
#else
    chunkPtr->minAscent = fontPtr->ascent
	    + chunkPtr->stylePtr->sValuePtr->offset;
    chunkPtr->minDescent = fontPtr->descent
	    - chunkPtr->stylePtr->sValuePtr->offset;;
#endif
    chunkPtr->minHeight = 0;
    chunkPtr->width = nextX - chunkPtr->x;
    chunkPtr->breakIndex = -1;
    ciPtr = (CharInfo *) ckalloc((unsigned)
	    (sizeof(CharInfo) - 3 + charsThatFit));
    chunkPtr->clientData = (ClientData) ciPtr;
    ciPtr->numChars = charsThatFit;
    strncpy(ciPtr->chars, p, charsThatFit);
    if (isspace(UCHAR(p[charsThatFit-1]))) {
	ciPtr->numChars--;
    }

    /*
     * 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 (textPtr->wrapMode != tkTextWordUid) {
	chunkPtr->breakIndex = chunkPtr->numChars;
    } else {
	for (count = charsThatFit, p += charsThatFit-1; count > 0;
		count--, p--) {
	    if (isspace(UCHAR(*p))) {
		chunkPtr->breakIndex = count;
		break;
	    }
	}
	if ((charsThatFit+offset) == segPtr->size) {
	    for (nextPtr = segPtr->nextPtr; nextPtr != NULL;
		    nextPtr = nextPtr->nextPtr) {
		if (nextPtr->size != 0) {
		    if (nextPtr->typePtr != &tkTextCharType) {
			chunkPtr->breakIndex = chunkPtr->numChars;
		    }
		    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.
 *
 *--------------------------------------------------------------
 */

#ifdef TCL_MSDOS_PORT
static void
CharDisplayProc(chunkPtr, x, y, height, baseline, memDC, screenY)
#else
static void
CharDisplayProc(chunkPtr, x, y, height, baseline, display, dst, screenY)
#endif
    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. */
#ifdef TCL_MSDOS_PORT
    HDC memDC;
#else
    Display *display;			/* Display to use for drawing. */
    Drawable dst;			/* Pixmap or window in which to draw
					 * chunk. */
#endif
    int screenY;			/* Y-coordinate in text window that
					 * corresponds to y. */
{
    CharInfo *ciPtr = (CharInfo *) chunkPtr->clientData;
    Style *stylePtr;
    StyleValues *sValuePtr;

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

	return;
    }

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

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

    if (ciPtr->numChars > 0) {
#ifdef TCL_MSDOS_PORT
	/* Adjust y to top of character */
	TkDisplayChars(memDC, sValuePtr->hfont,
		ciPtr->chars, ciPtr->numChars, x,
		y - sValuePtr->offset, x - chunkPtr->x, 0);
#else
	TkDisplayChars(display, dst, stylePtr->fgGC, sValuePtr->fontPtr,
		ciPtr->chars, ciPtr->numChars, x,
		y + baseline - sValuePtr->offset, x - chunkPtr->x, 0);
#endif
	if (sValuePtr->underline) {
#ifdef TCL_MSDOS_PORT
	    /* Adjust y to top of character */
	    TkUnderlineChars(memDC,
		    sValuePtr->hfont, ciPtr->chars, x,
		    y - sValuePtr->offset, x - chunkPtr->x,
		    0, 0, ciPtr->numChars-1);
#else
	    TkUnderlineChars(display, dst, stylePtr->fgGC,
		    sValuePtr->fontPtr, ciPtr->chars, x,
		    y + baseline - sValuePtr->offset, x - chunkPtr->x,
		    0, 0, ciPtr->numChars-1);
#endif
	}
    }
}

/*
 *--------------------------------------------------------------
 *
 * 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;

#ifdef TCL_MSDOS_PORT
    return TkMeasureChars(chunkPtr->stylePtr->sValuePtr->hfont,
	    ciPtr->chars, chunkPtr->numChars-1, chunkPtr->x, x, 0, 0, &endX);
#else
    return TkMeasureChars(chunkPtr->stylePtr->sValuePtr->fontPtr,
	    ciPtr->chars, chunkPtr->numChars-1, chunkPtr->x, x, 0, 0, &endX);
#endif
}

/*
 *--------------------------------------------------------------
 *
 * 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, 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. */
{
    CharInfo *ciPtr = (CharInfo *) chunkPtr->clientData;
    int maxX;

    maxX = chunkPtr->width + chunkPtr->x;
#ifdef TCL_MSDOS_PORT
    TkMeasureChars(chunkPtr->stylePtr->sValuePtr->hfont,
	    ciPtr->chars, index, chunkPtr->x, 10000, 0, 0, xPtr);
#else
    TkMeasureChars(chunkPtr->stylePtr->sValuePtr->fontPtr,
	    ciPtr->chars, index, chunkPtr->x, 1000000, 0, 0, xPtr);
#endif
    if (index == ciPtr->numChars) {
	/*
	 * 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 {
#ifdef TCL_MSDOS_PORT
	TkMeasureChars(chunkPtr->stylePtr->sValuePtr->hfont,
		ciPtr->chars + index, 1, *xPtr, 10000, 0, 0, widthPtr);
#else
	TkMeasureChars(chunkPtr->stylePtr->sValuePtr->fontPtr,
		ciPtr->chars + index, 1, *xPtr, 1000000, 0, 0, widthPtr);
#endif
	if (*widthPtr > maxX) {
	    *widthPtr = maxX - *xPtr;
	} else {
	    *widthPtr -= *xPtr;
	}
    }
    *yPtr = y + baseline - chunkPtr->minAscent;
    *heightPtr = chunkPtr->minAscent + chunkPtr->minDescent;
}
