1 /*
2  * tkTextDisp.c --
3  *
4  *	This module provides facilities to display text widgets. It is the
5  *	only place where information is kept about the screen layout of text
6  *	widgets. (Well, strictly, each TkTextLine and B-tree node caches its
7  *	last observed pixel height, but that information originates here).
8  *
9  * Copyright (c) 1992-1994 The Regents of the University of California.
10  * Copyright (c) 1994-1997 Sun Microsystems, Inc.
11  *
12  * See the file "license.terms" for information on usage and redistribution of
13  * this file, and for a DISCLAIMER OF ALL WARRANTIES.
14  */
15 
16 #include "tkInt.h"
17 #include "tkText.h"
18 
19 #ifdef _WIN32
20 #include "tkWinInt.h"
21 #elif defined(__CYGWIN__)
22 #include "tkUnixInt.h"
23 #elif defined(MAC_OSX_TK)
24 #include "tkMacOSXInt.h"
25 #define OK_TO_LOG (!TkpWillDrawWidget(textPtr->tkwin))
26 #endif
27 
28 #if !defined(MAC_OSX_TK)
29 #define OK_TO_LOG 1
30 #endif
31 
32 /*
33  * "Calculations of line pixel heights and the size of the vertical
34  * scrollbar."
35  *
36  * Given that tag, font and elide changes can happen to large numbers of
37  * diverse chunks in a text widget containing megabytes of text, it is not
38  * possible to recalculate all affected height information immediately any
39  * such change takes place and maintain a responsive user-experience. Yet, for
40  * an accurate vertical scrollbar to be drawn, we must know the total number
41  * of vertical pixels shown on display versus the number available to be
42  * displayed.
43  *
44  * The way the text widget solves this problem is by maintaining cached line
45  * pixel heights (in the BTree for each logical line), and having asynchronous
46  * timer callbacks (i) to iterate through the logical lines recalculating
47  * their heights, and (ii) to recalculate the vertical scrollbar's position
48  * and size.
49  *
50  * Typically this works well but there are some situations where the overall
51  * functional design of this file causes some problems. These problems can
52  * only arise because the calculations used to display lines on screen are not
53  * connected to those in the iterating-line- recalculation-process.
54  *
55  * The reason for this disconnect is that the display calculations operate in
56  * display lines, and the iteration and cache operates in logical lines.
57  * Given that the display calculations both need not contain complete logical
58  * lines (at top or bottom of display), and that they do not actually keep
59  * track of logical lines (for simplicity of code and historical design), this
60  * means a line may be known and drawn with a different pixel height to that
61  * which is cached in the BTree, and this might cause some temporary
62  * undesirable mismatch between display and the vertical scrollbar.
63  *
64  * All such mismatches should be temporary, however, since the asynchronous
65  * height calculations will always catch up eventually.
66  *
67  * For further details see the comments before and within the following
68  * functions below: LayoutDLine, AsyncUpdateLineMetrics, GetYView,
69  * GetYPixelCount, TkTextUpdateOneLine, TkTextUpdateLineMetrics.
70  *
71  * For details of the way in which the BTree keeps track of pixel heights, see
72  * tkTextBTree.c. Basically the BTree maintains two pieces of information: the
73  * logical line indices and the pixel height cache.
74  */
75 
76 /*
77  * TK_LAYOUT_WITH_BASE_CHUNKS:
78  *
79  *	With this macro set, collect all char chunks that have no holes
80  *	between them, that are on the same line and use the same font and font
81  *	size. Allocate the chars of all these chunks, the so-called "stretch",
82  *	in a DString in the first chunk, the so-called "base chunk". Use the
83  *	base chunk string for measuring and drawing, so that these actions are
84  *	always performed with maximum context.
85  *
86  *	This is necessary for text rendering engines that provide ligatures
87  *	and sub-pixel layout, like ATSU on Mac. If we don't do this, the
88  *	measuring will change all the time, leading to an ugly "tremble and
89  *	shiver" effect. This is because of the continuous splitting and
90  *	re-merging of chunks that goes on in a text widget, when the cursor or
91  *	the selection move.
92  *
93  * Side effects:
94  *
95  *	Memory management changes. Instead of attaching the character data to
96  *	the clientData structures of the char chunks, an additional DString is
97  *	used. The collection process will even lead to resizing this DString
98  *	for large stretches (> TCL_DSTRING_STATIC_SIZE == 200). We could
99  *	reduce the overall memory footprint by copying the result to a plain
100  *	char array after the line breaking process, but that would complicate
101  *	the code and make performance even worse speedwise. See also TODOs.
102  *
103  * TODOs:
104  *
105  *    -	Move the character collection process from the LayoutProc into
106  *	LayoutDLine(), so that the collection can be done before actual
107  *	layout. In this way measuring can look at the following text, too,
108  *	right from the beginning. Memory handling can also be improved with
109  *	this. Problem: We don't easily know which chunks are adjacent until
110  *	all the other chunks have calculated their width. Apparently marks
111  *	would return width==0. A separate char collection loop would have to
112  *	know these things.
113  *
114  *    -	Use a new context parameter to pass the context from LayoutDLine() to
115  *	the LayoutProc instead of using a global variable like now. Not
116  *	pressing until the previous point gets implemented.
117  */
118 
119 /*
120  * The following structure describes how to display a range of characters.
121  * The information is generated by scanning all of the tags associated with
122  * the characters and combining that with default information for the overall
123  * widget. These structures form the hash keys for dInfoPtr->styleTable.
124  */
125 
126 typedef struct StyleValues {
127     Tk_3DBorder border;		/* Used for drawing background under text.
128 				 * NULL means use widget background. */
129     int borderWidth;		/* Width of 3-D border for background. */
130     int relief;			/* 3-D relief for background. */
131     Pixmap bgStipple;		/* Stipple bitmap for background. None means
132 				 * draw solid. */
133     XColor *fgColor;		/* Foreground color for text. */
134     Tk_Font tkfont;		/* Font for displaying text. */
135     Pixmap fgStipple;		/* Stipple bitmap for text and other
136 				 * foreground stuff. None means draw solid.*/
137     int justify;		/* Justification style for text. */
138     int lMargin1;		/* Left margin, in pixels, for first display
139 				 * line of each text line. */
140     int lMargin2;		/* Left margin, in pixels, for second and
141 				 * later display lines of each text line. */
142     Tk_3DBorder lMarginColor;	/* Color of left margins (1 and 2). */
143     int offset;			/* Offset in pixels of baseline, relative to
144 				 * baseline of line. */
145     int overstrike;		/* Non-zero means draw overstrike through
146 				 * text. */
147     XColor *overstrikeColor;	/* Foreground color for overstrike through
148                                  * text. */
149     int rMargin;		/* Right margin, in pixels. */
150     Tk_3DBorder rMarginColor;	/* Color of right margin. */
151     int spacing1;		/* Spacing above first dline in text line. */
152     int spacing2;		/* Spacing between lines of dline. */
153     int spacing3;		/* Spacing below last dline in text line. */
154     TkTextTabArray *tabArrayPtr;/* Locations and types of tab stops (may be
155 				 * NULL). */
156     int tabStyle;		/* One of TABULAR or WORDPROCESSOR. */
157     int underline;		/* Non-zero means draw underline underneath
158 				 * text. */
159     XColor *underlineColor;	/* Foreground color for underline underneath
160                                  * text. */
161     int elide;			/* Zero means draw text, otherwise not. */
162     TkWrapMode wrapMode;	/* How to handle wrap-around for this tag.
163 				 * One of TEXT_WRAPMODE_CHAR,
164 				 * TEXT_WRAPMODE_NONE or TEXT_WRAPMODE_WORD.*/
165 } StyleValues;
166 
167 /*
168  * The following structure extends the StyleValues structure above with
169  * graphics contexts used to actually draw the characters. The entries in
170  * dInfoPtr->styleTable point to structures of this type.
171  */
172 
173 typedef struct TextStyle {
174     int refCount;		/* Number of times this structure is
175 				 * referenced in Chunks. */
176     GC bgGC;			/* Graphics context for background. None means
177 				 * use widget background. */
178     GC fgGC;			/* Graphics context for foreground. */
179     GC ulGC;			/* Graphics context for underline. */
180     GC ovGC;			/* Graphics context for overstrike. */
181     StyleValues *sValuePtr;	/* Raw information from which GCs were
182 				 * derived. */
183     Tcl_HashEntry *hPtr;	/* Pointer to entry in styleTable. Used to
184 				 * delete entry. */
185 } TextStyle;
186 
187 /*
188  * The following macro determines whether two styles have the same background
189  * so that, for example, no beveled border should be drawn between them.
190  */
191 
192 #define SAME_BACKGROUND(s1, s2) \
193     (((s1)->sValuePtr->border == (s2)->sValuePtr->border) \
194 	&& ((s1)->sValuePtr->borderWidth == (s2)->sValuePtr->borderWidth) \
195 	&& ((s1)->sValuePtr->relief == (s2)->sValuePtr->relief) \
196 	&& ((s1)->sValuePtr->bgStipple == (s2)->sValuePtr->bgStipple))
197 
198 /*
199  * The following macro is used to compare two floating-point numbers to within
200  * a certain degree of scale. Direct comparison fails on processors where the
201  * processor and memory representations of FP numbers of a particular
202  * precision is different (e.g. Intel)
203  */
204 
205 #define FP_EQUAL_SCALE(double1, double2, scaleFactor) \
206     (fabs((double1)-(double2))*((scaleFactor)+1.0) < 0.3)
207 
208 /*
209  * Macros to make debugging/testing logging a little easier.
210  *
211  * On OSX 10.14 Drawing procedures are sometimes run because the system has
212  * decided to redraw the window.  This can corrupt the data that a test is
213  * trying to collect.  So we don't write to the logging variables when the
214  * drawing procedure is being run that way.  Other systems can always log.
215  */
216 
217 #define LOG(toVar,what)							\
218     if (OK_TO_LOG)							\
219         Tcl_SetVar2(textPtr->interp, toVar, NULL, (what),		\
220 		    TCL_GLOBAL_ONLY|TCL_APPEND_VALUE|TCL_LIST_ELEMENT)
221 #define CLEAR(var)							\
222     if (OK_TO_LOG)							\
223 	Tcl_SetVar2(interp, var, NULL, "", TCL_GLOBAL_ONLY)
224 
225 /*
226  * The following structure describes one line of the display, which may be
227  * either part or all of one line of the text.
228  */
229 
230 typedef struct DLine {
231     TkTextIndex index;		/* Identifies first character in text that is
232 				 * displayed on this line. */
233     int byteCount;		/* Number of bytes accounted for by this
234 				 * display line, including a trailing space or
235 				 * newline that isn't actually displayed. */
236     int logicalLinesMerged;	/* Number of extra logical lines merged into
237 				 * this one due to elided newlines. */
238     int y;			/* Y-position at which line is supposed to be
239 				 * drawn (topmost pixel of rectangular area
240 				 * occupied by line). */
241     int oldY;			/* Y-position at which line currently appears
242 				 * on display. This is used to move lines by
243 				 * scrolling rather than re-drawing. If
244 				 * 'flags' have the OLD_Y_INVALID bit set,
245 				 * then we will never examine this field
246 				 * (which means line isn't currently visible
247 				 * on display and must be redrawn). */
248     int height;			/* Height of line, in pixels. */
249     int baseline;		/* Offset of text baseline from y, in
250 				 * pixels. */
251     int spaceAbove;		/* How much extra space was added to the top
252 				 * of the line because of spacing options.
253 				 * This is included in height and baseline. */
254     int spaceBelow;		/* How much extra space was added to the
255 				 * bottom of the line because of spacing
256 				 * options. This is included in height. */
257     Tk_3DBorder lMarginColor;	/* Background color of the area corresponding
258 				 * to the left margin of the display line. */
259     int lMarginWidth;           /* Pixel width of the area corresponding to
260                                  * the left margin. */
261     Tk_3DBorder rMarginColor;	/* Background color of the area corresponding
262 				 * to the right margin of the display line. */
263     int rMarginWidth;           /* Pixel width of the area corresponding to
264                                  * the right margin. */
265     int length;			/* Total length of line, in pixels. */
266     TkTextDispChunk *chunkPtr;	/* Pointer to first chunk in list of all of
267 				 * those that are displayed on this line of
268 				 * the screen. */
269     struct DLine *nextPtr;	/* Next in list of all display lines for this
270 				 * window. The list is sorted in order from
271 				 * top to bottom. Note: the next DLine doesn't
272 				 * always correspond to the next line of text:
273 				 * (a) can have multiple DLines for one text
274 				 * line (wrapping), (b) can have elided newlines,
275 				 * and (c) can have gaps where DLine's
276 				 * have been deleted because they're out of
277 				 * date. */
278     int flags;			/* Various flag bits: see below for values. */
279 } DLine;
280 
281 /*
282  * Flag bits for DLine structures:
283  *
284  * HAS_3D_BORDER -		Non-zero means that at least one of the chunks
285  *				in this line has a 3D border, so it
286  *				potentially interacts with 3D borders in
287  *				neighboring lines (see DisplayLineBackground).
288  * NEW_LAYOUT -			Non-zero means that the line has been
289  *				re-layed out since the last time the display
290  *				was updated.
291  * TOP_LINE -			Non-zero means that this was the top line in
292  *				in the window the last time that the window
293  *				was laid out. This is important because a line
294  *				may be displayed differently if its at the top
295  *				or bottom than if it's in the middle
296  *				(e.g. beveled edges aren't displayed for
297  *				middle lines if the adjacent line has a
298  *				similar background).
299  * BOTTOM_LINE -		Non-zero means that this was the bottom line
300  *				in the window the last time that the window
301  *				was laid out.
302  * OLD_Y_INVALID -		The value of oldY in the structure is not
303  *				valid or useful and should not be examined.
304  *				'oldY' is only useful when the DLine is
305  *				currently displayed at a different position
306  *				and we wish to re-display it via scrolling, so
307  *				this means the DLine needs redrawing.
308  */
309 
310 #define HAS_3D_BORDER	1
311 #define NEW_LAYOUT	2
312 #define TOP_LINE	4
313 #define BOTTOM_LINE	8
314 #define OLD_Y_INVALID  16
315 
316 /*
317  * Overall display information for a text widget:
318  */
319 
320 typedef struct TextDInfo {
321     Tcl_HashTable styleTable;	/* Hash table that maps from StyleValues to
322 				 * TextStyles for this widget. */
323     DLine *dLinePtr;		/* First in list of all display lines for this
324 				 * widget, in order from top to bottom. */
325     int topPixelOffset;		/* Identifies first pixel in top display line
326 				 * to display in window. */
327     int newTopPixelOffset;	/* Desired first pixel in top display line to
328 				 * display in window. */
329     GC copyGC;			/* Graphics context for copying from off-
330 				 * screen pixmaps onto screen. */
331     GC scrollGC;		/* Graphics context for copying from one place
332 				 * in the window to another (scrolling):
333 				 * differs from copyGC in that we need to get
334 				 * GraphicsExpose events. */
335     int x;			/* First x-coordinate that may be used for
336 				 * actually displaying line information.
337 				 * Leaves space for border, etc. */
338     int y;			/* First y-coordinate that may be used for
339 				 * actually displaying line information.
340 				 * Leaves space for border, etc. */
341     int maxX;			/* First x-coordinate to right of available
342 				 * space for displaying lines. */
343     int maxY;			/* First y-coordinate below available space
344 				 * for displaying lines. */
345     int topOfEof;		/* Top-most pixel (lowest y-value) that has
346 				 * been drawn in the appropriate fashion for
347 				 * the portion of the window after the last
348 				 * line of the text. This field is used to
349 				 * figure out when to redraw part or all of
350 				 * the eof field. */
351 
352     /*
353      * Information used for scrolling:
354      */
355 
356     int newXPixelOffset;	/* Desired x scroll position, measured as the
357 				 * number of pixels off-screen to the left for
358 				 * a line with no left margin. */
359     int curXPixelOffset;	/* Actual x scroll position, measured as the
360 				 * number of pixels off-screen to the left. */
361     int maxLength;		/* Length in pixels of longest line that's
362 				 * visible in window (length may exceed window
363 				 * size). If there's no wrapping, this will be
364 				 * zero. */
365     double xScrollFirst, xScrollLast;
366 				/* Most recent values reported to horizontal
367 				 * scrollbar; used to eliminate unnecessary
368 				 * reports. */
369     double yScrollFirst, yScrollLast;
370 				/* Most recent values reported to vertical
371 				 * scrollbar; used to eliminate unnecessary
372 				 * reports. */
373 
374     /*
375      * The following information is used to implement scanning:
376      */
377 
378     int scanMarkXPixel;		/* Pixel index of left edge of the window when
379 				 * the scan started. */
380     int scanMarkX;		/* X-position of mouse at time scan started. */
381     int scanTotalYScroll;	/* Total scrolling (in screen pixels) that has
382 				 * occurred since scanMarkY was set. */
383     int scanMarkY;		/* Y-position of mouse at time scan started. */
384 
385     /*
386      * Miscellaneous information:
387      */
388 
389     int dLinesInvalidated;	/* This value is set to 1 whenever something
390 				 * happens that invalidates information in
391 				 * DLine structures; if a redisplay is in
392 				 * progress, it will see this and abort the
393 				 * redisplay. This is needed because, for
394 				 * example, an embedded window could change
395 				 * its size when it is first displayed,
396 				 * invalidating the DLine that is currently
397 				 * being displayed. If redisplay continues, it
398 				 * will use freed memory and could dump
399 				 * core. */
400     int flags;			/* Various flag values: see below for
401 				 * definitions. */
402     /*
403      * Information used to handle the asynchronous updating of the y-scrollbar
404      * and the vertical height calculations:
405      */
406 
407     int lineMetricUpdateEpoch;	/* Stores a number which is incremented each
408 				 * time the text widget changes in a
409 				 * significant way (e.g. resizing or
410 				 * geometry-influencing tag changes). */
411     int currentMetricUpdateLine;/* Stores a counter which is used to iterate
412 				 * over the logical lines contained in the
413 				 * widget and update their geometry
414 				 * calculations, if they are out of date. */
415     TkTextIndex metricIndex;	/* If the current metric update line wraps
416 				 * into very many display lines, then this is
417 				 * used to keep track of what index we've got
418 				 * to so far... */
419     int metricPixelHeight;	/* ...and this is for the height calculation
420 				 * so far...*/
421     int metricEpoch;		/* ...and this for the epoch of the partial
422 				 * calculation so it can be cancelled if
423 				 * things change once more. This field will be
424 				 * -1 if there is no long-line calculation in
425 				 * progress, and take a non-negative value if
426 				 * there is such a calculation in progress. */
427     int lastMetricUpdateLine;	/* When the current update line reaches this
428 				 * line, we are done and should stop the
429 				 * asychronous callback mechanism. */
430     Tcl_TimerToken lineUpdateTimer;
431 				/* A token pointing to the current line metric
432 				 * update callback. */
433     Tcl_TimerToken scrollbarTimer;
434 				/* A token pointing to the current scrollbar
435 				 * update callback. */
436 } TextDInfo;
437 
438 /*
439  * In TkTextDispChunk structures for character segments, the clientData field
440  * points to one of the following structures:
441  */
442 
443 #if !TK_LAYOUT_WITH_BASE_CHUNKS
444 
445 typedef struct CharInfo {
446     int numBytes;		/* Number of bytes to display. */
447     char chars[TKFLEXARRAY];		/* UTF characters to display.
448 				 * Allocated as large as necessary. THIS MUST BE THE LAST
449 				 * FIELD IN THE STRUCTURE. */
450 } CharInfo;
451 
452 #else /* TK_LAYOUT_WITH_BASE_CHUNKS */
453 
454 typedef struct CharInfo {
455     TkTextDispChunk *baseChunkPtr;
456     int baseOffset;		/* Starting offset in base chunk
457 				 * baseChars. */
458     int numBytes;		/* Number of bytes that belong to this
459 				 * chunk. */
460     const char *chars;		/* UTF characters to display. Actually points
461 				 * into the baseChars of the base chunk. Only
462 				 * valid after FinalizeBaseChunk(). */
463 } CharInfo;
464 
465 /*
466  * The BaseCharInfo is a CharInfo with some additional data added.
467  */
468 
469 typedef struct BaseCharInfo {
470     CharInfo ci;
471     Tcl_DString baseChars;	/* Actual characters for the stretch of text
472 				 * represented by this base chunk. */
473     int width;			/* Width in pixels of the whole string, if
474 				 * known, else -1. Valid during
475 				 * LayoutDLine(). */
476 } BaseCharInfo;
477 
478 /* TODO: Thread safety */
479 static TkTextDispChunk *baseCharChunkPtr = NULL;
480 
481 #endif /* TK_LAYOUT_WITH_BASE_CHUNKS */
482 
483 /*
484  * Flag values for TextDInfo structures:
485  *
486  * DINFO_OUT_OF_DATE:		Non-zero means that the DLine structures for
487  *				this window are partially or completely out of
488  *				date and need to be recomputed.
489  * REDRAW_PENDING:		Means that a when-idle handler has been
490  *				scheduled to update the display.
491  * REDRAW_BORDERS:		Means window border or pad area has
492  *				potentially been damaged and must be redrawn.
493  * REPICK_NEEDED:		1 means that the widget has been modified in a
494  *				way that could change the current character (a
495  *				different character might be under the mouse
496  *				cursor now). Need to recompute the current
497  *				character before the next redisplay.
498  * OUT_OF_SYNC                  1 means that the last <<WidgetViewSync>> event had
499  *                              value 0, indicating that the widget is out of sync.
500  */
501 
502 #define DINFO_OUT_OF_DATE	1
503 #define REDRAW_PENDING		2
504 #define REDRAW_BORDERS		4
505 #define REPICK_NEEDED		8
506 #define OUT_OF_SYNC             16
507 /*
508  * Action values for FreeDLines:
509  *
510  * DLINE_FREE:		Free the lines, but no need to unlink them from the
511  *			current list of actual display lines.
512  * DLINE_UNLINK:	Free and unlink from current display.
513  * DLINE_FREE_TEMP:	Free, but don't unlink, and also don't set
514  *			'dLinesInvalidated'.
515  */
516 
517 #define DLINE_FREE	  0
518 #define DLINE_UNLINK	  1
519 #define DLINE_FREE_TEMP	  2
520 
521 /*
522  * The following counters keep statistics about redisplay that can be checked
523  * to see how clever this code is at reducing redisplays.
524  */
525 
526 static int numRedisplays;	/* Number of calls to DisplayText. */
527 static int linesRedrawn;	/* Number of calls to DisplayDLine. */
528 static int numCopies;		/* Number of calls to XCopyArea to copy part
529 				 * of the screen. */
530 static int lineHeightsRecalculated;
531 				/* Number of line layouts purely for height
532 				 * calculation purposes.*/
533 /*
534  * Forward declarations for functions defined later in this file:
535  */
536 
537 static void		AdjustForTab(TkText *textPtr,
538 			    TkTextTabArray *tabArrayPtr, int index,
539 			    TkTextDispChunk *chunkPtr);
540 static void		CharBboxProc(TkText *textPtr,
541 			    TkTextDispChunk *chunkPtr, int index, int y,
542 			    int lineHeight, int baseline, int *xPtr,
543 			    int *yPtr, int *widthPtr, int *heightPtr);
544 static int		CharChunkMeasureChars(TkTextDispChunk *chunkPtr,
545 			    const char *chars, int charsLen,
546 			    int start, int end, int startX, int maxX,
547 			    int flags, int *nextX);
548 static void		CharDisplayProc(TkText *textPtr,
549 			    TkTextDispChunk *chunkPtr, int x, int y,
550 			    int height, int baseline, Display *display,
551 			    Drawable dst, int screenY);
552 static int		CharMeasureProc(TkTextDispChunk *chunkPtr, int x);
553 static void		CharUndisplayProc(TkText *textPtr,
554 			    TkTextDispChunk *chunkPtr);
555 #if TK_LAYOUT_WITH_BASE_CHUNKS
556 static void		FinalizeBaseChunk(TkTextDispChunk *additionalChunkPtr);
557 static void		FreeBaseChunk(TkTextDispChunk *baseChunkPtr);
558 static int		IsSameFGStyle(TextStyle *style1, TextStyle *style2);
559 static void		RemoveFromBaseChunk(TkTextDispChunk *chunkPtr);
560 #endif
561 /*
562  * Definitions of elided procs. Compiler can't inline these since we use
563  * pointers to these functions. ElideDisplayProc and ElideUndisplayProc are
564  * special-cased for speed, as potentially many elided DLine chunks if large,
565  * tag toggle-filled elided region.
566  */
567 static void		ElideBboxProc(TkText *textPtr,
568 			    TkTextDispChunk *chunkPtr, int index, int y,
569 			    int lineHeight, int baseline, int *xPtr,
570 			    int *yPtr, int *widthPtr, int *heightPtr);
571 static int		ElideMeasureProc(TkTextDispChunk *chunkPtr, int x);
572 static void		DisplayDLine(TkText *textPtr, DLine *dlPtr,
573 			    DLine *prevPtr, Pixmap pixmap);
574 static void		DisplayLineBackground(TkText *textPtr, DLine *dlPtr,
575 			    DLine *prevPtr, Pixmap pixmap);
576 static void		DisplayText(ClientData clientData);
577 static DLine *		FindDLine(TkText *textPtr, DLine *dlPtr,
578                             const TkTextIndex *indexPtr);
579 static void		FreeDLines(TkText *textPtr, DLine *firstPtr,
580 			    DLine *lastPtr, int action);
581 static void		FreeStyle(TkText *textPtr, TextStyle *stylePtr);
582 static TextStyle *	GetStyle(TkText *textPtr, const TkTextIndex *indexPtr);
583 static void		GetXView(Tcl_Interp *interp, TkText *textPtr,
584 			    int report);
585 static void		GetYView(Tcl_Interp *interp, TkText *textPtr,
586 			    int report);
587 static int		GetYPixelCount(TkText *textPtr, DLine *dlPtr);
588 static DLine *		LayoutDLine(TkText *textPtr,
589 			    const TkTextIndex *indexPtr);
590 static int		MeasureChars(Tk_Font tkfont, const char *source,
591 			    int maxBytes, int rangeStart, int rangeLength,
592 			    int startX, int maxX, int flags, int *nextXPtr);
593 static void		MeasureUp(TkText *textPtr,
594 			    const TkTextIndex *srcPtr, int distance,
595 			    TkTextIndex *dstPtr, int *overlap);
596 static int		NextTabStop(Tk_Font tkfont, int x, int tabOrigin);
597 static void		UpdateDisplayInfo(TkText *textPtr);
598 static void		YScrollByLines(TkText *textPtr, int offset);
599 static void		YScrollByPixels(TkText *textPtr, int offset);
600 static int		SizeOfTab(TkText *textPtr, int tabStyle,
601 			    TkTextTabArray *tabArrayPtr, int *indexPtr, int x,
602 			    int maxX);
603 static void		TextChanged(TkText *textPtr,
604 			    const TkTextIndex *index1Ptr,
605 			    const TkTextIndex *index2Ptr);
606 static void		TextInvalidateRegion(TkText *textPtr, TkRegion region);
607 static void		TextRedrawTag(TkText *textPtr,
608 			    TkTextIndex *index1Ptr, TkTextIndex *index2Ptr,
609 			    TkTextTag *tagPtr, int withTag);
610 static void		TextInvalidateLineMetrics(TkText *textPtr,
611 			    TkTextLine *linePtr, int lineCount, int action);
612 static int		CalculateDisplayLineHeight(TkText *textPtr,
613 			    const TkTextIndex *indexPtr, int *byteCountPtr,
614 			    int *mergedLinePtr);
615 static void		DlineIndexOfX(TkText *textPtr,
616 			    DLine *dlPtr, int x, TkTextIndex *indexPtr);
617 static int		DlineXOfIndex(TkText *textPtr,
618 			    DLine *dlPtr, int byteIndex);
619 static int		TextGetScrollInfoObj(Tcl_Interp *interp,
620 			    TkText *textPtr, int objc,
621 			    Tcl_Obj *const objv[], double *dblPtr,
622 			    int *intPtr);
623 static void		AsyncUpdateLineMetrics(ClientData clientData);
624 static void		GenerateWidgetViewSyncEvent(TkText *textPtr, Bool InSync);
625 static void		AsyncUpdateYScrollbar(ClientData clientData);
626 static int              IsStartOfNotMergedLine(TkText *textPtr,
627                             const TkTextIndex *indexPtr);
628 
629 /*
630  * Result values returned by TextGetScrollInfoObj:
631  */
632 
633 #define TKTEXT_SCROLL_MOVETO	1
634 #define TKTEXT_SCROLL_PAGES	2
635 #define TKTEXT_SCROLL_UNITS	3
636 #define TKTEXT_SCROLL_ERROR	4
637 #define TKTEXT_SCROLL_PIXELS	5
638 
639 /*
640  *----------------------------------------------------------------------
641  *
642  * TkTextCreateDInfo --
643  *
644  *	This function is called when a new text widget is created. Its job is
645  *	to set up display-related information for the widget.
646  *
647  * Results:
648  *	None.
649  *
650  * Side effects:
651  *	A TextDInfo data structure is allocated and initialized and attached
652  *	to textPtr.
653  *
654  *----------------------------------------------------------------------
655  */
656 
657 void
TkTextCreateDInfo(TkText * textPtr)658 TkTextCreateDInfo(
659     TkText *textPtr)		/* Overall information for text widget. */
660 {
661     TextDInfo *dInfoPtr;
662     XGCValues gcValues;
663 
664     dInfoPtr = (TextDInfo *)ckalloc(sizeof(TextDInfo));
665     Tcl_InitHashTable(&dInfoPtr->styleTable, sizeof(StyleValues)/sizeof(int));
666     dInfoPtr->dLinePtr = NULL;
667     dInfoPtr->copyGC = NULL;
668     gcValues.graphics_exposures = True;
669     dInfoPtr->scrollGC = Tk_GetGC(textPtr->tkwin, GCGraphicsExposures,
670 	    &gcValues);
671     dInfoPtr->topOfEof = 0;
672     dInfoPtr->newXPixelOffset = 0;
673     dInfoPtr->curXPixelOffset = 0;
674     dInfoPtr->maxLength = 0;
675     dInfoPtr->xScrollFirst = -1;
676     dInfoPtr->xScrollLast = -1;
677     dInfoPtr->yScrollFirst = -1;
678     dInfoPtr->yScrollLast = -1;
679     dInfoPtr->scanMarkXPixel = 0;
680     dInfoPtr->scanMarkX = 0;
681     dInfoPtr->scanTotalYScroll = 0;
682     dInfoPtr->scanMarkY = 0;
683     dInfoPtr->dLinesInvalidated = 0;
684     dInfoPtr->flags = 0;
685     dInfoPtr->topPixelOffset = 0;
686     dInfoPtr->newTopPixelOffset = 0;
687     dInfoPtr->currentMetricUpdateLine = -1;
688     dInfoPtr->lastMetricUpdateLine = -1;
689     dInfoPtr->lineMetricUpdateEpoch = 1;
690     dInfoPtr->metricEpoch = -1;
691     dInfoPtr->metricIndex.textPtr = NULL;
692     dInfoPtr->metricIndex.linePtr = NULL;
693     dInfoPtr->lineUpdateTimer = NULL;
694     dInfoPtr->scrollbarTimer = NULL;
695 
696     textPtr->dInfoPtr = dInfoPtr;
697 }
698 
699 /*
700  *----------------------------------------------------------------------
701  *
702  * TkTextFreeDInfo --
703  *
704  *	This function is called to free up all of the private display
705  *	information kept by this file for a text widget.
706  *
707  * Results:
708  *	None.
709  *
710  * Side effects:
711  *	Lots of resources get freed.
712  *
713  *----------------------------------------------------------------------
714  */
715 
716 void
TkTextFreeDInfo(TkText * textPtr)717 TkTextFreeDInfo(
718     TkText *textPtr)		/* Overall information for text widget. */
719 {
720     TextDInfo *dInfoPtr = textPtr->dInfoPtr;
721 
722     /*
723      * Be careful to free up styleTable *after* freeing up all the DLines, so
724      * that the hash table is still intact to free up the style-related
725      * information from the lines. Once the lines are all free then styleTable
726      * will be empty.
727      */
728 
729     FreeDLines(textPtr, dInfoPtr->dLinePtr, NULL, DLINE_UNLINK);
730     Tcl_DeleteHashTable(&dInfoPtr->styleTable);
731     if (dInfoPtr->copyGC != NULL) {
732 	Tk_FreeGC(textPtr->display, dInfoPtr->copyGC);
733     }
734     Tk_FreeGC(textPtr->display, dInfoPtr->scrollGC);
735     if (dInfoPtr->flags & REDRAW_PENDING) {
736 	Tcl_CancelIdleCall(DisplayText, textPtr);
737     }
738     if (dInfoPtr->lineUpdateTimer != NULL) {
739 	Tcl_DeleteTimerHandler(dInfoPtr->lineUpdateTimer);
740 	textPtr->refCount--;
741 	dInfoPtr->lineUpdateTimer = NULL;
742     }
743     if (dInfoPtr->scrollbarTimer != NULL) {
744 	Tcl_DeleteTimerHandler(dInfoPtr->scrollbarTimer);
745 	textPtr->refCount--;
746 	dInfoPtr->scrollbarTimer = NULL;
747     }
748     ckfree(dInfoPtr);
749 }
750 
751 /*
752  *----------------------------------------------------------------------
753  *
754  * GetStyle --
755  *
756  *	This function creates all the information needed to display text at a
757  *	particular location.
758  *
759  * Results:
760  *	The return value is a pointer to a TextStyle structure that
761  *	corresponds to *sValuePtr.
762  *
763  * Side effects:
764  *	A new entry may be created in the style table for the widget.
765  *
766  *----------------------------------------------------------------------
767  */
768 
769 static TextStyle *
GetStyle(TkText * textPtr,const TkTextIndex * indexPtr)770 GetStyle(
771     TkText *textPtr,		/* Overall information about text widget. */
772     const TkTextIndex *indexPtr)/* The character in the text for which display
773 				 * information is wanted. */
774 {
775     TkTextTag **tagPtrs;
776     TkTextTag *tagPtr;
777     StyleValues styleValues;
778     TextStyle *stylePtr;
779     Tcl_HashEntry *hPtr;
780     int numTags, isNew, i;
781     int isSelected;
782     XGCValues gcValues;
783     unsigned long mask;
784     /*
785      * The variables below keep track of the highest-priority specification
786      * that has occurred for each of the various fields of the StyleValues.
787      */
788     int borderPrio, borderWidthPrio, reliefPrio, bgStipplePrio;
789     int fgPrio, fontPrio, fgStipplePrio;
790     int underlinePrio, elidePrio, justifyPrio, offsetPrio;
791     int lMargin1Prio, lMargin2Prio, rMarginPrio;
792     int lMarginColorPrio, rMarginColorPrio;
793     int spacing1Prio, spacing2Prio, spacing3Prio;
794     int overstrikePrio, tabPrio, tabStylePrio, wrapPrio;
795 
796     /*
797      * Find out what tags are present for the character, then compute a
798      * StyleValues structure corresponding to those tags (scan through all of
799      * the tags, saving information for the highest-priority tag).
800      */
801 
802     tagPtrs = TkBTreeGetTags(indexPtr, textPtr, &numTags);
803     borderPrio = borderWidthPrio = reliefPrio = bgStipplePrio = -1;
804     fgPrio = fontPrio = fgStipplePrio = -1;
805     underlinePrio = elidePrio = justifyPrio = offsetPrio = -1;
806     lMargin1Prio = lMargin2Prio = rMarginPrio = -1;
807     lMarginColorPrio = rMarginColorPrio = -1;
808     spacing1Prio = spacing2Prio = spacing3Prio = -1;
809     overstrikePrio = tabPrio = tabStylePrio = wrapPrio = -1;
810     memset(&styleValues, 0, sizeof(StyleValues));
811     styleValues.relief = TK_RELIEF_FLAT;
812     styleValues.fgColor = textPtr->fgColor;
813     styleValues.underlineColor = textPtr->fgColor;
814     styleValues.overstrikeColor = textPtr->fgColor;
815     styleValues.tkfont = textPtr->tkfont;
816     styleValues.justify = TK_JUSTIFY_LEFT;
817     styleValues.spacing1 = textPtr->spacing1;
818     styleValues.spacing2 = textPtr->spacing2;
819     styleValues.spacing3 = textPtr->spacing3;
820     styleValues.tabArrayPtr = textPtr->tabArrayPtr;
821     styleValues.tabStyle = textPtr->tabStyle;
822     styleValues.wrapMode = textPtr->wrapMode;
823     styleValues.elide = 0;
824     isSelected = 0;
825 
826     for (i = 0 ; i < numTags; i++) {
827         if (textPtr->selTagPtr == tagPtrs[i]) {
828             isSelected = 1;
829             break;
830         }
831     }
832 
833     for (i = 0 ; i < numTags; i++) {
834 	Tk_3DBorder border;
835         XColor *fgColor;
836 
837 	tagPtr = tagPtrs[i];
838 	border = tagPtr->border;
839         fgColor = tagPtr->fgColor;
840 
841 	/*
842 	 * If this is the selection tag, and inactiveSelBorder is NULL (the
843 	 * default on Windows), then we need to skip it if we don't have the
844 	 * focus.
845 	 */
846 
847 	if ((tagPtr == textPtr->selTagPtr) && !(textPtr->flags & GOT_FOCUS)) {
848 	    if (textPtr->inactiveSelBorder == NULL
849 #ifdef MAC_OSX_TK
850 		    /* Don't show inactive selection in disabled widgets. */
851 		    || textPtr->state == TK_TEXT_STATE_DISABLED
852 #endif
853 	    ) {
854 		continue;
855 	    }
856 	    border = textPtr->inactiveSelBorder;
857 	}
858 
859         if ((tagPtr->selBorder != NULL) && (isSelected)) {
860             border = tagPtr->selBorder;
861         }
862 
863         if ((tagPtr->selFgColor != NULL) && isSelected) {
864             fgColor = tagPtr->selFgColor;
865         }
866 
867 	if ((border != NULL) && (tagPtr->priority > borderPrio)) {
868 	    styleValues.border = border;
869 	    borderPrio = tagPtr->priority;
870 	}
871 	if ((tagPtr->borderWidthPtr != NULL)
872 		&& (Tcl_GetString(tagPtr->borderWidthPtr)[0] != '\0')
873 		&& (tagPtr->priority > borderWidthPrio)) {
874 	    styleValues.borderWidth = tagPtr->borderWidth;
875 	    borderWidthPrio = tagPtr->priority;
876 	}
877 	if ((tagPtr->reliefString != NULL)
878 		&& (tagPtr->priority > reliefPrio)) {
879 	    if (styleValues.border == NULL) {
880 		styleValues.border = textPtr->border;
881 	    }
882 	    styleValues.relief = tagPtr->relief;
883 	    reliefPrio = tagPtr->priority;
884 	}
885 	if ((tagPtr->bgStipple != None)
886 		&& (tagPtr->priority > bgStipplePrio)) {
887 	    styleValues.bgStipple = tagPtr->bgStipple;
888 	    bgStipplePrio = tagPtr->priority;
889 	}
890 	if ((fgColor != NULL) && (tagPtr->priority > fgPrio)) {
891 	    styleValues.fgColor = fgColor;
892 	    fgPrio = tagPtr->priority;
893 	}
894 	if ((tagPtr->tkfont != NULL) && (tagPtr->priority > fontPrio)) {
895 	    styleValues.tkfont = tagPtr->tkfont;
896 	    fontPrio = tagPtr->priority;
897 	}
898 	if ((tagPtr->fgStipple != None)
899 		&& (tagPtr->priority > fgStipplePrio)) {
900 	    styleValues.fgStipple = tagPtr->fgStipple;
901 	    fgStipplePrio = tagPtr->priority;
902 	}
903 	if ((tagPtr->justifyString != NULL)
904 		&& (tagPtr->priority > justifyPrio)) {
905 	    styleValues.justify = tagPtr->justify;
906 	    justifyPrio = tagPtr->priority;
907 	}
908 	if ((tagPtr->lMargin1String != NULL)
909 		&& (tagPtr->priority > lMargin1Prio)) {
910 	    styleValues.lMargin1 = tagPtr->lMargin1;
911 	    lMargin1Prio = tagPtr->priority;
912 	}
913 	if ((tagPtr->lMargin2String != NULL)
914 		&& (tagPtr->priority > lMargin2Prio)) {
915 	    styleValues.lMargin2 = tagPtr->lMargin2;
916 	    lMargin2Prio = tagPtr->priority;
917 	}
918 	if ((tagPtr->lMarginColor != NULL)
919 		&& (tagPtr->priority > lMarginColorPrio)) {
920 	    styleValues.lMarginColor = tagPtr->lMarginColor;
921 	    lMarginColorPrio = tagPtr->priority;
922 	}
923 	if ((tagPtr->offsetString != NULL)
924 		&& (tagPtr->priority > offsetPrio)) {
925 	    styleValues.offset = tagPtr->offset;
926 	    offsetPrio = tagPtr->priority;
927 	}
928 	if ((tagPtr->overstrikeString != NULL)
929 		&& (tagPtr->priority > overstrikePrio)) {
930 	    styleValues.overstrike = tagPtr->overstrike;
931 	    overstrikePrio = tagPtr->priority;
932             if (tagPtr->overstrikeColor != NULL) {
933                  styleValues.overstrikeColor = tagPtr->overstrikeColor;
934             } else if (fgColor != NULL) {
935                  styleValues.overstrikeColor = fgColor;
936             }
937 	}
938 	if ((tagPtr->rMarginString != NULL)
939 		&& (tagPtr->priority > rMarginPrio)) {
940 	    styleValues.rMargin = tagPtr->rMargin;
941 	    rMarginPrio = tagPtr->priority;
942 	}
943 	if ((tagPtr->rMarginColor != NULL)
944 		&& (tagPtr->priority > rMarginColorPrio)) {
945 	    styleValues.rMarginColor = tagPtr->rMarginColor;
946 	    rMarginColorPrio = tagPtr->priority;
947 	}
948 	if ((tagPtr->spacing1String != NULL)
949 		&& (tagPtr->priority > spacing1Prio)) {
950 	    styleValues.spacing1 = tagPtr->spacing1;
951 	    spacing1Prio = tagPtr->priority;
952 	}
953 	if ((tagPtr->spacing2String != NULL)
954 		&& (tagPtr->priority > spacing2Prio)) {
955 	    styleValues.spacing2 = tagPtr->spacing2;
956 	    spacing2Prio = tagPtr->priority;
957 	}
958 	if ((tagPtr->spacing3String != NULL)
959 		&& (tagPtr->priority > spacing3Prio)) {
960 	    styleValues.spacing3 = tagPtr->spacing3;
961 	    spacing3Prio = tagPtr->priority;
962 	}
963 	if ((tagPtr->tabStringPtr != NULL)
964 		&& (tagPtr->priority > tabPrio)) {
965 	    styleValues.tabArrayPtr = tagPtr->tabArrayPtr;
966 	    tabPrio = tagPtr->priority;
967 	}
968 	if ((tagPtr->tabStyle != TK_TEXT_TABSTYLE_NONE)
969 		&& (tagPtr->priority > tabStylePrio)) {
970 	    styleValues.tabStyle = tagPtr->tabStyle;
971 	    tabStylePrio = tagPtr->priority;
972 	}
973 	if ((tagPtr->underlineString != NULL)
974 		&& (tagPtr->priority > underlinePrio)) {
975 	    styleValues.underline = tagPtr->underline;
976 	    underlinePrio = tagPtr->priority;
977             if (tagPtr->underlineColor != NULL) {
978                  styleValues.underlineColor = tagPtr->underlineColor;
979             } else if (fgColor != NULL) {
980                  styleValues.underlineColor = fgColor;
981             }
982 	}
983 	if ((tagPtr->elideString != NULL)
984 		&& (tagPtr->priority > elidePrio)) {
985 	    styleValues.elide = tagPtr->elide;
986 	    elidePrio = tagPtr->priority;
987 	}
988 	if ((tagPtr->wrapMode != TEXT_WRAPMODE_NULL)
989 		&& (tagPtr->priority > wrapPrio)) {
990 	    styleValues.wrapMode = tagPtr->wrapMode;
991 	    wrapPrio = tagPtr->priority;
992 	}
993     }
994     if (tagPtrs != NULL) {
995 	ckfree(tagPtrs);
996     }
997 
998     /*
999      * Use an existing style if there's one around that matches.
1000      */
1001 
1002     hPtr = Tcl_CreateHashEntry(&textPtr->dInfoPtr->styleTable,
1003 	    (char *) &styleValues, &isNew);
1004     if (!isNew) {
1005 	stylePtr = (TextStyle *)Tcl_GetHashValue(hPtr);
1006 	stylePtr->refCount++;
1007 	return stylePtr;
1008     }
1009 
1010     /*
1011      * No existing style matched. Make a new one.
1012      */
1013 
1014     stylePtr = (TextStyle *)ckalloc(sizeof(TextStyle));
1015     stylePtr->refCount = 1;
1016     if (styleValues.border != NULL) {
1017 	gcValues.foreground = Tk_3DBorderColor(styleValues.border)->pixel;
1018 	mask = GCForeground;
1019 	if (styleValues.bgStipple != None) {
1020 	    gcValues.stipple = styleValues.bgStipple;
1021 	    gcValues.fill_style = FillStippled;
1022 	    mask |= GCStipple|GCFillStyle;
1023 	}
1024 	stylePtr->bgGC = Tk_GetGC(textPtr->tkwin, mask, &gcValues);
1025     } else {
1026 	stylePtr->bgGC = NULL;
1027     }
1028     mask = GCFont;
1029     gcValues.font = Tk_FontId(styleValues.tkfont);
1030     mask |= GCForeground;
1031     gcValues.foreground = styleValues.fgColor->pixel;
1032     if (styleValues.fgStipple != None) {
1033 	gcValues.stipple = styleValues.fgStipple;
1034 	gcValues.fill_style = FillStippled;
1035 	mask |= GCStipple|GCFillStyle;
1036     }
1037     stylePtr->fgGC = Tk_GetGC(textPtr->tkwin, mask, &gcValues);
1038     mask = GCForeground;
1039     gcValues.foreground = styleValues.underlineColor->pixel;
1040     stylePtr->ulGC = Tk_GetGC(textPtr->tkwin, mask, &gcValues);
1041     gcValues.foreground = styleValues.overstrikeColor->pixel;
1042     stylePtr->ovGC = Tk_GetGC(textPtr->tkwin, mask, &gcValues);
1043     stylePtr->sValuePtr = (StyleValues *)
1044 	    Tcl_GetHashKey(&textPtr->dInfoPtr->styleTable, hPtr);
1045     stylePtr->hPtr = hPtr;
1046     Tcl_SetHashValue(hPtr, stylePtr);
1047     return stylePtr;
1048 }
1049 
1050 /*
1051  *----------------------------------------------------------------------
1052  *
1053  * FreeStyle --
1054  *
1055  *	This function is called when a TextStyle structure is no longer
1056  *	needed. It decrements the reference count and frees up the space for
1057  *	the style structure if the reference count is 0.
1058  *
1059  * Results:
1060  *	None.
1061  *
1062  * Side effects:
1063  *	The storage and other resources associated with the style are freed up
1064  *	if no-one's still using it.
1065  *
1066  *----------------------------------------------------------------------
1067  */
1068 
1069 static void
FreeStyle(TkText * textPtr,TextStyle * stylePtr)1070 FreeStyle(
1071     TkText *textPtr,		/* Information about overall widget. */
1072     TextStyle *stylePtr)
1073 				/* Information about style to free. */
1074 {
1075     if (stylePtr->refCount-- <= 1) {
1076 	if (stylePtr->bgGC != NULL) {
1077 	    Tk_FreeGC(textPtr->display, stylePtr->bgGC);
1078 	}
1079 	if (stylePtr->fgGC != NULL) {
1080 	    Tk_FreeGC(textPtr->display, stylePtr->fgGC);
1081 	}
1082 	if (stylePtr->ulGC != NULL) {
1083 	    Tk_FreeGC(textPtr->display, stylePtr->ulGC);
1084 	}
1085 	if (stylePtr->ovGC != NULL) {
1086 	    Tk_FreeGC(textPtr->display, stylePtr->ovGC);
1087 	}
1088 	Tcl_DeleteHashEntry(stylePtr->hPtr);
1089 	ckfree(stylePtr);
1090     }
1091 }
1092 
1093 /*
1094  *----------------------------------------------------------------------
1095  *
1096  * LayoutDLine --
1097  *
1098  *	This function generates a single DLine structure for a display line
1099  *	whose leftmost character is given by indexPtr.
1100  *
1101  * Results:
1102  *	The return value is a pointer to a DLine structure describing the
1103  *	display line. All fields are filled in and correct except for y and
1104  *	nextPtr.
1105  *
1106  * Side effects:
1107  *	Storage is allocated for the new DLine.
1108  *
1109  *	See the comments in 'GetYView' for some thoughts on what the side-
1110  *	effects of this call (or its callers) should be; the synchronisation
1111  *	of TkTextLine->pixelHeight with the sum of the results of this
1112  *	function operating on all display lines within each logical line.
1113  *	Ideally the code should be refactored to ensure the cached pixel
1114  *	height is never behind what is known when this function is called
1115  *	elsewhere.
1116  *
1117  *	Unfortunately, this function is currently called from many different
1118  *	places, not just to layout a display line for actual display, but also
1119  *	simply to calculate some metric or other of one or more display lines
1120  *	(typically the height). It would be a good idea to do some profiling
1121  *	of typical text widget usage and the way in which this is called and
1122  *	see if some optimization could or should be done.
1123  *
1124  *----------------------------------------------------------------------
1125  */
1126 
1127 static DLine *
LayoutDLine(TkText * textPtr,const TkTextIndex * indexPtr)1128 LayoutDLine(
1129     TkText *textPtr,		/* Overall information about text widget. */
1130     const TkTextIndex *indexPtr)/* Beginning of display line. May not
1131 				 * necessarily point to a character
1132 				 * segment. */
1133 {
1134     DLine *dlPtr;	/* New display line. */
1135     TkTextSegment *segPtr;	/* Current segment in text. */
1136     TkTextDispChunk *lastChunkPtr;
1137 				/* Last chunk allocated so far for line. */
1138     TkTextDispChunk *chunkPtr;	/* Current chunk. */
1139     TkTextIndex curIndex;
1140     TkTextDispChunk *breakChunkPtr;
1141 				/* Chunk containing best word break point, if
1142 				 * any. */
1143     TkTextIndex breakIndex;	/* Index of first character in
1144 				 * breakChunkPtr. */
1145     int breakByteOffset;	/* Byte offset of character within
1146 				 * breakChunkPtr just to right of best break
1147 				 * point. */
1148     int noCharsYet;		/* Non-zero means that no characters have been
1149 				 * placed on the line yet. */
1150     int paragraphStart;		/* Non-zero means that we are on the first
1151 				 * line of a paragraph (used to choose between
1152 				 * lmargin1, lmargin2). */
1153     int justify;		/* How to justify line: taken from style for
1154 				 * the first character in line. */
1155     int jIndent;		/* Additional indentation (beyond margins) due
1156 				 * to justification. */
1157     int rMargin;		/* Right margin width for line. */
1158     TkWrapMode wrapMode;	/* Wrap mode to use for this line. */
1159     int x = 0, maxX = 0;	/* Initializations needed only to stop
1160 				 * compiler warnings. */
1161     int wholeLine;		/* Non-zero means this display line runs to
1162 				 * the end of the text line. */
1163     int tabIndex;		/* Index of the current tab stop. */
1164     int gotTab;			/* Non-zero means the current chunk contains a
1165 				 * tab. */
1166     TkTextDispChunk *tabChunkPtr;
1167 				/* Pointer to the chunk containing the
1168 				 * previous tab stop. */
1169     int maxBytes;		/* Maximum number of bytes to include in this
1170 				 * chunk. */
1171     TkTextTabArray *tabArrayPtr;/* Tab stops for line; taken from style for
1172 				 * the first character on line. */
1173     int tabStyle;		/* One of TABULAR or WORDPROCESSOR. */
1174     int tabSize;		/* Number of pixels consumed by current tab
1175 				 * stop. */
1176     TkTextDispChunk *lastCharChunkPtr;
1177 				/* Pointer to last chunk in display lines with
1178 				 * numBytes > 0. Used to drop 0-sized chunks
1179 				 * from the end of the line. */
1180     int byteOffset, ascent, descent, code, elide, elidesize;
1181     StyleValues *sValuePtr;
1182     TkTextElideInfo info;	/* Keep track of elide state. */
1183 
1184     /*
1185      * Create and initialize a new DLine structure.
1186      */
1187 
1188     dlPtr = (DLine *)ckalloc(sizeof(DLine));
1189     dlPtr->index = *indexPtr;
1190     dlPtr->byteCount = 0;
1191     dlPtr->y = 0;
1192     dlPtr->oldY = 0;		/* Only set to avoid compiler warnings. */
1193     dlPtr->height = 0;
1194     dlPtr->baseline = 0;
1195     dlPtr->chunkPtr = NULL;
1196     dlPtr->nextPtr = NULL;
1197     dlPtr->flags = NEW_LAYOUT | OLD_Y_INVALID;
1198     dlPtr->logicalLinesMerged = 0;
1199     dlPtr->lMarginColor = NULL;
1200     dlPtr->lMarginWidth = 0;
1201     dlPtr->rMarginColor = NULL;
1202     dlPtr->rMarginWidth = 0;
1203 
1204     /*
1205      * This is not necessarily totally correct, where we have merged logical
1206      * lines. Fixing this would require a quite significant overhaul, though,
1207      * so currently we make do with this.
1208      */
1209 
1210     paragraphStart = (indexPtr->byteIndex == 0);
1211 
1212     /*
1213      * Special case entirely elide line as there may be 1000s or more.
1214      */
1215 
1216     elide = TkTextIsElided(textPtr, indexPtr, &info);
1217     if (elide && indexPtr->byteIndex == 0) {
1218 	maxBytes = 0;
1219 	for (segPtr = info.segPtr; segPtr != NULL; segPtr = segPtr->nextPtr) {
1220 	    if (segPtr->size > 0) {
1221 		if (elide == 0) {
1222 		    /*
1223 		     * We toggled a tag and the elide state changed to
1224 		     * visible, and we have something of non-zero size.
1225 		     * Therefore we must bail out.
1226 		     */
1227 
1228 		    break;
1229 		}
1230 		maxBytes += segPtr->size;
1231 
1232 		/*
1233 		 * Reset tag elide priority, since we're on a new character.
1234 		 */
1235 
1236 	    } else if ((segPtr->typePtr == &tkTextToggleOffType)
1237 		    || (segPtr->typePtr == &tkTextToggleOnType)) {
1238 		TkTextTag *tagPtr = segPtr->body.toggle.tagPtr;
1239 
1240 		/*
1241 		 * The elide state only changes if this tag is either the
1242 		 * current highest priority tag (and is therefore being
1243 		 * toggled off), or it's a new tag with higher priority.
1244 		 */
1245 
1246 		if (tagPtr->elideString != NULL) {
1247 		    info.tagCnts[tagPtr->priority]++;
1248 		    if (info.tagCnts[tagPtr->priority] & 1) {
1249 			info.tagPtrs[tagPtr->priority] = tagPtr;
1250 		    }
1251 		    if (tagPtr->priority >= info.elidePriority) {
1252 			if (segPtr->typePtr == &tkTextToggleOffType) {
1253 			    /*
1254 			     * If it is being toggled off, and it has an elide
1255 			     * string, it must actually be the current highest
1256 			     * priority tag, so this check is redundant:
1257 			     */
1258 
1259 			    if (tagPtr->priority != info.elidePriority) {
1260 				Tcl_Panic("Bad tag priority being toggled off");
1261 			    }
1262 
1263 			    /*
1264 			     * Find previous elide tag, if any (if not then
1265 			     * elide will be zero, of course).
1266 			     */
1267 
1268 			    elide = 0;
1269 			    while (--info.elidePriority > 0) {
1270 				if (info.tagCnts[info.elidePriority] & 1) {
1271 				    elide = info.tagPtrs[info.elidePriority]
1272 					    ->elide;
1273 				    break;
1274 				}
1275 			    }
1276 			} else {
1277 			    elide = tagPtr->elide;
1278 			    info.elidePriority = tagPtr->priority;
1279 			}
1280 		    }
1281 		}
1282 	    }
1283 	}
1284 
1285 	if (elide) {
1286 	    dlPtr->byteCount = maxBytes;
1287 	    dlPtr->spaceAbove = dlPtr->spaceBelow = dlPtr->length = 0;
1288 	    if (dlPtr->index.byteIndex == 0) {
1289 		/*
1290 		 * Elided state goes from beginning to end of an entire
1291 		 * logical line. This means we can update the line's pixel
1292 		 * height, and bring its pixel calculation up to date.
1293 		 */
1294 
1295 		TkBTreeLinePixelEpoch(textPtr, dlPtr->index.linePtr)
1296 			= textPtr->dInfoPtr->lineMetricUpdateEpoch;
1297 
1298 		if (TkBTreeLinePixelCount(textPtr,dlPtr->index.linePtr) != 0) {
1299 		    TkBTreeAdjustPixelHeight(textPtr,
1300 			    dlPtr->index.linePtr, 0, 0);
1301 		}
1302 	    }
1303 	    TkTextFreeElideInfo(&info);
1304 	    return dlPtr;
1305 	}
1306     }
1307     TkTextFreeElideInfo(&info);
1308 
1309     /*
1310      * Each iteration of the loop below creates one TkTextDispChunk for the
1311      * new display line. The line will always have at least one chunk (for the
1312      * newline character at the end, if there's nothing else available).
1313      */
1314 
1315     curIndex = *indexPtr;
1316     lastChunkPtr = NULL;
1317     chunkPtr = NULL;
1318     noCharsYet = 1;
1319     elide = 0;
1320     breakChunkPtr = NULL;
1321     breakByteOffset = 0;
1322     justify = TK_JUSTIFY_LEFT;
1323     tabIndex = -1;
1324     tabChunkPtr = NULL;
1325     tabArrayPtr = NULL;
1326     tabStyle = TK_TEXT_TABSTYLE_TABULAR;
1327     rMargin = 0;
1328     wrapMode = TEXT_WRAPMODE_CHAR;
1329     tabSize = 0;
1330     lastCharChunkPtr = NULL;
1331 
1332     /*
1333      * Find the first segment to consider for the line. Can't call
1334      * TkTextIndexToSeg for this because it won't return a segment with zero
1335      * size (such as the insertion cursor's mark).
1336      */
1337 
1338   connectNextLogicalLine:
1339     byteOffset = curIndex.byteIndex;
1340     segPtr = curIndex.linePtr->segPtr;
1341     while ((byteOffset > 0) && (byteOffset >= segPtr->size)) {
1342 	byteOffset -= segPtr->size;
1343 	segPtr = segPtr->nextPtr;
1344 
1345 	if (segPtr == NULL) {
1346 	    /*
1347 	     * Two logical lines merged into one display line through eliding
1348 	     * of a newline.
1349 	     */
1350 
1351 	    TkTextLine *linePtr = TkBTreeNextLine(NULL, curIndex.linePtr);
1352 	    if (linePtr == NULL) {
1353 		break;
1354 	    }
1355 
1356 	    dlPtr->logicalLinesMerged++;
1357 	    curIndex.byteIndex = 0;
1358 	    curIndex.linePtr = linePtr;
1359 	    segPtr = curIndex.linePtr->segPtr;
1360 	}
1361     }
1362 
1363     while (segPtr != NULL) {
1364 	/*
1365 	 * Every logical line still gets at least one chunk due to
1366 	 * expectations in the rest of the code, but we are able to skip
1367 	 * elided portions of the line quickly.
1368 	 *
1369 	 * If current chunk is elided and last chunk was too, coalesce.
1370 	 *
1371 	 * This also means that each logical line which is entirely elided
1372 	 * still gets laid out into a DLine, but with zero height. This isn't
1373 	 * particularly a problem, but it does seem somewhat unnecessary. We
1374 	 * may wish to redesign the code to remove these zero height DLines in
1375 	 * the future.
1376 	 */
1377 
1378 	if (elide && (lastChunkPtr != NULL)
1379 		&& (lastChunkPtr->displayProc == NULL /*ElideDisplayProc*/)) {
1380 	    elidesize = segPtr->size - byteOffset;
1381 	    if (elidesize > 0) {
1382 		curIndex.byteIndex += elidesize;
1383 		lastChunkPtr->numBytes += elidesize;
1384 		breakByteOffset = lastChunkPtr->breakIndex
1385 			= lastChunkPtr->numBytes;
1386 
1387 		/*
1388 		 * If have we have a tag toggle, there is a chance that
1389 		 * invisibility state changed, so bail out.
1390 		 */
1391 	    } else if ((segPtr->typePtr == &tkTextToggleOffType)
1392 		    || (segPtr->typePtr == &tkTextToggleOnType)) {
1393 		if (segPtr->body.toggle.tagPtr->elideString != NULL) {
1394 		    elide = (segPtr->typePtr == &tkTextToggleOffType)
1395 			    ^ segPtr->body.toggle.tagPtr->elide;
1396 		}
1397 	    }
1398 
1399 	    byteOffset = 0;
1400 	    segPtr = segPtr->nextPtr;
1401 
1402 	    if (segPtr == NULL) {
1403 		/*
1404 		 * Two logical lines merged into one display line through
1405 		 * eliding of a newline.
1406 		 */
1407 
1408 		TkTextLine *linePtr = TkBTreeNextLine(NULL, curIndex.linePtr);
1409 
1410 		if (linePtr != NULL) {
1411 		    dlPtr->logicalLinesMerged++;
1412 		    curIndex.byteIndex = 0;
1413 		    curIndex.linePtr = linePtr;
1414 		    goto connectNextLogicalLine;
1415 		}
1416 	    }
1417 
1418 	    /*
1419 	     * Code no longer needed, now that we allow logical lines to merge
1420 	     * into a single display line.
1421 	     *
1422 	    if (segPtr == NULL && chunkPtr != NULL) {
1423 		ckfree(chunkPtr);
1424 		chunkPtr = NULL;
1425 	    }
1426 	     */
1427 
1428 	    continue;
1429 	}
1430 
1431 	if (segPtr->typePtr->layoutProc == NULL) {
1432 	    segPtr = segPtr->nextPtr;
1433 	    byteOffset = 0;
1434 	    continue;
1435 	}
1436 	if (chunkPtr == NULL) {
1437 	    chunkPtr = (TkTextDispChunk *)ckalloc(sizeof(TkTextDispChunk));
1438 	    chunkPtr->nextPtr = NULL;
1439 	    chunkPtr->clientData = NULL;
1440 	}
1441 	chunkPtr->stylePtr = GetStyle(textPtr, &curIndex);
1442 	elide = chunkPtr->stylePtr->sValuePtr->elide;
1443 
1444 	/*
1445 	 * Save style information such as justification and indentation, up
1446 	 * until the first character is encountered, then retain that
1447 	 * information for the rest of the line.
1448 	 */
1449 
1450 	if (!elide && noCharsYet) {
1451 	    tabArrayPtr = chunkPtr->stylePtr->sValuePtr->tabArrayPtr;
1452 	    tabStyle = chunkPtr->stylePtr->sValuePtr->tabStyle;
1453 	    justify = chunkPtr->stylePtr->sValuePtr->justify;
1454 	    rMargin = chunkPtr->stylePtr->sValuePtr->rMargin;
1455 	    wrapMode = chunkPtr->stylePtr->sValuePtr->wrapMode;
1456 
1457 	    /*
1458 	     * See above - this test may not be entirely correct where we have
1459 	     * partially elided lines (and therefore merged logical lines).
1460 	     * In such a case a byteIndex of zero doesn't necessarily mean the
1461 	     * beginning of a logical line.
1462 	     */
1463 
1464 	    if (paragraphStart) {
1465 		/*
1466 		 * Beginning of logical line.
1467 		 */
1468 
1469 		x = chunkPtr->stylePtr->sValuePtr->lMargin1;
1470 	    } else {
1471 		/*
1472 		 * Beginning of display line.
1473 		 */
1474 
1475 		x = chunkPtr->stylePtr->sValuePtr->lMargin2;
1476 	    }
1477             dlPtr->lMarginWidth = x;
1478 	    if (wrapMode == TEXT_WRAPMODE_NONE) {
1479 		maxX = -1;
1480 	    } else {
1481 		maxX = textPtr->dInfoPtr->maxX - textPtr->dInfoPtr->x
1482 			- rMargin;
1483 		if (maxX < x) {
1484 		    maxX = x;
1485 		}
1486 	    }
1487 	}
1488 
1489 	gotTab = 0;
1490 	maxBytes = segPtr->size - byteOffset;
1491 	if (segPtr->typePtr == &tkTextCharType) {
1492 
1493 	    /*
1494 	     * See if there is a tab in the current chunk; if so, only layout
1495 	     * characters up to (and including) the tab.
1496 	     */
1497 
1498 	    if (!elide && justify == TK_JUSTIFY_LEFT) {
1499 		char *p;
1500 
1501 		for (p = segPtr->body.chars + byteOffset; *p != 0; p++) {
1502 		    if (*p == '\t') {
1503 			maxBytes = (p + 1 - segPtr->body.chars) - byteOffset;
1504 			gotTab = 1;
1505 			break;
1506 		    }
1507 		}
1508 	    }
1509 
1510 #if TK_LAYOUT_WITH_BASE_CHUNKS
1511 	    if (baseCharChunkPtr != NULL) {
1512 		int expectedX =
1513 			((BaseCharInfo *) baseCharChunkPtr->clientData)->width
1514 			+ baseCharChunkPtr->x;
1515 
1516 		if ((expectedX != x) || !IsSameFGStyle(
1517 			baseCharChunkPtr->stylePtr, chunkPtr->stylePtr)) {
1518 		    FinalizeBaseChunk(NULL);
1519 		}
1520 	    }
1521 #endif /* TK_LAYOUT_WITH_BASE_CHUNKS */
1522 	}
1523 	chunkPtr->x = x;
1524 	if (elide /*&& maxBytes*/) {
1525 	    /*
1526 	     * Don't free style here, as other code expects to be able to do
1527 	     * that.
1528 	     */
1529 
1530 	    /* breakByteOffset =*/
1531 	    chunkPtr->breakIndex = chunkPtr->numBytes = maxBytes;
1532 	    chunkPtr->width = 0;
1533 	    chunkPtr->minAscent = chunkPtr->minDescent
1534 		    = chunkPtr->minHeight = 0;
1535 
1536 	    /*
1537 	     * Would just like to point to canonical empty chunk.
1538 	     */
1539 
1540 	    chunkPtr->displayProc = NULL;
1541 	    chunkPtr->undisplayProc = NULL;
1542 	    chunkPtr->measureProc = ElideMeasureProc;
1543 	    chunkPtr->bboxProc = ElideBboxProc;
1544 
1545 	    code = 1;
1546 	} else {
1547 	    code = segPtr->typePtr->layoutProc(textPtr, &curIndex, segPtr,
1548 		    byteOffset, maxX-tabSize, maxBytes, noCharsYet, wrapMode,
1549 		    chunkPtr);
1550 	}
1551 	if (code <= 0) {
1552 	    FreeStyle(textPtr, chunkPtr->stylePtr);
1553 	    if (code < 0) {
1554 		/*
1555 		 * This segment doesn't wish to display itself (e.g. most
1556 		 * marks).
1557 		 */
1558 
1559 		segPtr = segPtr->nextPtr;
1560 		byteOffset = 0;
1561 		continue;
1562 	    }
1563 
1564 	    /*
1565 	     * No characters from this segment fit in the window: this means
1566 	     * we're at the end of the display line.
1567 	     */
1568 
1569 	    if (chunkPtr != NULL) {
1570 		ckfree(chunkPtr);
1571 	    }
1572 	    break;
1573 	}
1574 
1575 	/*
1576 	 * We currently say we have some characters (and therefore something
1577 	 * from which to examine tag values for the first character of the
1578 	 * line) even if those characters are actually elided. This behaviour
1579 	 * is not well documented, and it might be more consistent to
1580 	 * completely ignore such elided characters and their tags. To do so
1581 	 * change this to:
1582 	 *
1583 	 * if (!elide && chunkPtr->numBytes > 0).
1584 	 */
1585 
1586 	if (!elide && chunkPtr->numBytes > 0) {
1587 	    noCharsYet = 0;
1588 	    lastCharChunkPtr = chunkPtr;
1589 	}
1590 	if (lastChunkPtr == NULL) {
1591 	    dlPtr->chunkPtr = chunkPtr;
1592 	} else {
1593 	    lastChunkPtr->nextPtr = chunkPtr;
1594 	}
1595 	lastChunkPtr = chunkPtr;
1596 	x += chunkPtr->width;
1597 	if (chunkPtr->breakIndex > 0) {
1598 	    breakByteOffset = chunkPtr->breakIndex;
1599 	    breakIndex = curIndex;
1600 	    breakChunkPtr = chunkPtr;
1601 	}
1602 	if (chunkPtr->numBytes != maxBytes) {
1603 	    break;
1604 	}
1605 
1606 	/*
1607 	 * If we're at a new tab, adjust the layout for all the chunks
1608 	 * pertaining to the previous tab. Also adjust the amount of space
1609 	 * left in the line to account for space that will be eaten up by the
1610 	 * tab.
1611 	 */
1612 
1613 	if (gotTab) {
1614 	    if (tabIndex >= 0) {
1615 		AdjustForTab(textPtr, tabArrayPtr, tabIndex, tabChunkPtr);
1616 		x = chunkPtr->x + chunkPtr->width;
1617 	    }
1618 	    tabChunkPtr = chunkPtr;
1619 	    tabSize = SizeOfTab(textPtr, tabStyle, tabArrayPtr, &tabIndex, x,
1620 		    maxX);
1621 	    if ((maxX >= 0) && (tabSize >= maxX - x)) {
1622 		break;
1623 	    }
1624 	}
1625 	curIndex.byteIndex += chunkPtr->numBytes;
1626 	byteOffset += chunkPtr->numBytes;
1627 	if (byteOffset >= segPtr->size) {
1628 	    byteOffset = 0;
1629 	    segPtr = segPtr->nextPtr;
1630 	    if (elide && segPtr == NULL) {
1631 		/*
1632 		 * An elided section started on this line, and carries on
1633 		 * until the newline. Hence the newline is actually elided,
1634 		 * and we want to merge the display of the next logical line
1635 		 * with this one.
1636 		 */
1637 
1638 		TkTextLine *linePtr = TkBTreeNextLine(NULL, curIndex.linePtr);
1639 
1640 		if (linePtr != NULL) {
1641 		    dlPtr->logicalLinesMerged++;
1642 		    curIndex.byteIndex = 0;
1643 		    curIndex.linePtr = linePtr;
1644 		    chunkPtr = NULL;
1645 		    goto connectNextLogicalLine;
1646 		}
1647 	    }
1648 	}
1649 
1650 	chunkPtr = NULL;
1651     }
1652 #if TK_LAYOUT_WITH_BASE_CHUNKS
1653     FinalizeBaseChunk(NULL);
1654 #endif /* TK_LAYOUT_WITH_BASE_CHUNKS */
1655     if (noCharsYet) {
1656 	dlPtr->spaceAbove = 0;
1657 	dlPtr->spaceBelow = 0;
1658 	dlPtr->length = 0;
1659 
1660 	/*
1661 	 * We used to Tcl_Panic here, saying that LayoutDLine couldn't place
1662 	 * any characters on a line, but I believe a more appropriate response
1663 	 * is to return a DLine with zero height. With elided lines, tag
1664 	 * transitions and asynchronous line height calculations, it is hard
1665 	 * to avoid this situation ever arising with the current code design.
1666 	 */
1667 
1668 	return dlPtr;
1669     }
1670     wholeLine = (segPtr == NULL);
1671 
1672     /*
1673      * We're at the end of the display line. Throw away everything after the
1674      * most recent word break, if there is one; this may potentially require
1675      * the last chunk to be layed out again.
1676      */
1677 
1678     if (breakChunkPtr == NULL) {
1679 	/*
1680 	 * This code makes sure that we don't accidentally display chunks with
1681 	 * no characters at the end of the line (such as the insertion
1682 	 * cursor). These chunks belong on the next line. So, throw away
1683 	 * everything after the last chunk that has characters in it.
1684 	 */
1685 
1686 	breakChunkPtr = lastCharChunkPtr;
1687 	breakByteOffset = breakChunkPtr->numBytes;
1688     }
1689     if ((breakChunkPtr != NULL) && ((lastChunkPtr != breakChunkPtr)
1690 	    || (breakByteOffset != lastChunkPtr->numBytes))) {
1691 	while (1) {
1692 	    chunkPtr = breakChunkPtr->nextPtr;
1693 	    if (chunkPtr == NULL) {
1694 		break;
1695 	    }
1696 	    FreeStyle(textPtr, chunkPtr->stylePtr);
1697 	    breakChunkPtr->nextPtr = chunkPtr->nextPtr;
1698 	    if (chunkPtr->undisplayProc != NULL) {
1699 		chunkPtr->undisplayProc(textPtr, chunkPtr);
1700 	    }
1701 	    ckfree(chunkPtr);
1702 	}
1703 	if (breakByteOffset != breakChunkPtr->numBytes) {
1704 	    if (breakChunkPtr->undisplayProc != NULL) {
1705 		breakChunkPtr->undisplayProc(textPtr, breakChunkPtr);
1706 	    }
1707 	    segPtr = TkTextIndexToSeg(&breakIndex, &byteOffset);
1708 	    segPtr->typePtr->layoutProc(textPtr, &breakIndex, segPtr,
1709 		    byteOffset, maxX, breakByteOffset, 0, wrapMode,
1710 		    breakChunkPtr);
1711 #if TK_LAYOUT_WITH_BASE_CHUNKS
1712 	    FinalizeBaseChunk(NULL);
1713 #endif /* TK_LAYOUT_WITH_BASE_CHUNKS */
1714 	}
1715 	lastChunkPtr = breakChunkPtr;
1716 	wholeLine = 0;
1717     }
1718 
1719     /*
1720      * Make tab adjustments for the last tab stop, if there is one.
1721      */
1722 
1723     if ((tabIndex >= 0) && (tabChunkPtr != NULL)) {
1724 	AdjustForTab(textPtr, tabArrayPtr, tabIndex, tabChunkPtr);
1725     }
1726 
1727     /*
1728      * Make one more pass over the line to recompute various things like its
1729      * height, length, and total number of bytes. Also modify the x-locations
1730      * of chunks to reflect justification. If we're not wrapping, I'm not sure
1731      * what is the best way to handle right and center justification: should
1732      * the total length, for purposes of justification, be (a) the window
1733      * width, (b) the length of the longest line in the window, or (c) the
1734      * length of the longest line in the text? (c) isn't available, (b) seems
1735      * weird, since it can change with vertical scrolling, so (a) is what is
1736      * implemented below.
1737      */
1738 
1739     if (wrapMode == TEXT_WRAPMODE_NONE) {
1740 	maxX = textPtr->dInfoPtr->maxX - textPtr->dInfoPtr->x - rMargin;
1741     }
1742     dlPtr->length = lastChunkPtr->x + lastChunkPtr->width;
1743     if (justify == TK_JUSTIFY_LEFT) {
1744 	jIndent = 0;
1745     } else if (justify == TK_JUSTIFY_RIGHT) {
1746 	jIndent = maxX - dlPtr->length;
1747     } else {
1748 	jIndent = (maxX - dlPtr->length)/2;
1749     }
1750     ascent = descent = 0;
1751     for (chunkPtr = dlPtr->chunkPtr; chunkPtr != NULL;
1752 	    chunkPtr = chunkPtr->nextPtr) {
1753 	chunkPtr->x += jIndent;
1754 	dlPtr->byteCount += chunkPtr->numBytes;
1755 	if (chunkPtr->minAscent > ascent) {
1756 	    ascent = chunkPtr->minAscent;
1757 	}
1758 	if (chunkPtr->minDescent > descent) {
1759 	    descent = chunkPtr->minDescent;
1760 	}
1761 	if (chunkPtr->minHeight > dlPtr->height) {
1762 	    dlPtr->height = chunkPtr->minHeight;
1763 	}
1764 	sValuePtr = chunkPtr->stylePtr->sValuePtr;
1765 	if ((sValuePtr->borderWidth > 0)
1766 		&& (sValuePtr->relief != TK_RELIEF_FLAT)) {
1767 	    dlPtr->flags |= HAS_3D_BORDER;
1768 	}
1769     }
1770     if (dlPtr->height < (ascent + descent)) {
1771 	dlPtr->height = ascent + descent;
1772 	dlPtr->baseline = ascent;
1773     } else {
1774 	dlPtr->baseline = ascent + (dlPtr->height - ascent - descent)/2;
1775     }
1776     sValuePtr = dlPtr->chunkPtr->stylePtr->sValuePtr;
1777     if (dlPtr->index.byteIndex == 0) {
1778 	dlPtr->spaceAbove = sValuePtr->spacing1;
1779     } else {
1780 	dlPtr->spaceAbove = sValuePtr->spacing2 - sValuePtr->spacing2/2;
1781     }
1782     if (wholeLine) {
1783 	dlPtr->spaceBelow = sValuePtr->spacing3;
1784     } else {
1785 	dlPtr->spaceBelow = sValuePtr->spacing2/2;
1786     }
1787     dlPtr->height += dlPtr->spaceAbove + dlPtr->spaceBelow;
1788     dlPtr->baseline += dlPtr->spaceAbove;
1789     dlPtr->lMarginColor = sValuePtr->lMarginColor;
1790     dlPtr->rMarginColor = sValuePtr->rMarginColor;
1791     if (wrapMode != TEXT_WRAPMODE_NONE) {
1792         dlPtr->rMarginWidth = rMargin;
1793     }
1794 
1795     /*
1796      * Recompute line length: may have changed because of justification.
1797      */
1798 
1799     dlPtr->length = lastChunkPtr->x + lastChunkPtr->width;
1800 
1801     return dlPtr;
1802 }
1803 
1804 /*
1805  *----------------------------------------------------------------------
1806  *
1807  * UpdateDisplayInfo --
1808  *
1809  *	This function is invoked to recompute some or all of the DLine
1810  *	structures for a text widget. At the time it is called the DLine
1811  *	structures still left in the widget are guaranteed to be correct
1812  *	except that (a) the y-coordinates aren't necessarily correct, (b)
1813  *	there may be missing structures (the DLine structures get removed as
1814  *	soon as they are potentially out-of-date), and (c) DLine structures
1815  *	that don't start at the beginning of a line may be incorrect if
1816  *	previous information in the same line changed size in a way that moved
1817  *	a line boundary (DLines for any info that changed will have been
1818  *	deleted, but not DLines for unchanged info in the same text line).
1819  *
1820  * Results:
1821  *	None.
1822  *
1823  * Side effects:
1824  *	Upon return, the DLine information for textPtr correctly reflects the
1825  *	positions where characters will be displayed. However, this function
1826  *	doesn't actually bring the display up-to-date.
1827  *
1828  *----------------------------------------------------------------------
1829  */
1830 
1831 static void
UpdateDisplayInfo(TkText * textPtr)1832 UpdateDisplayInfo(
1833     TkText *textPtr)		/* Text widget to update. */
1834 {
1835     TextDInfo *dInfoPtr = textPtr->dInfoPtr;
1836     DLine *dlPtr, *prevPtr;
1837     TkTextIndex index;
1838     TkTextLine *lastLinePtr;
1839     int y, maxY, xPixelOffset, maxOffset, lineHeight;
1840 
1841     if (!(dInfoPtr->flags & DINFO_OUT_OF_DATE)) {
1842 	return;
1843     }
1844     dInfoPtr->flags &= ~DINFO_OUT_OF_DATE;
1845 
1846     /*
1847      * Delete any DLines that are now above the top of the window.
1848      */
1849 
1850     index = textPtr->topIndex;
1851     dlPtr = FindDLine(textPtr, dInfoPtr->dLinePtr, &index);
1852     if ((dlPtr != NULL) && (dlPtr != dInfoPtr->dLinePtr)) {
1853 	FreeDLines(textPtr, dInfoPtr->dLinePtr, dlPtr, DLINE_UNLINK);
1854     }
1855     if (index.byteIndex == 0) {
1856 	lineHeight = 0;
1857     } else {
1858 	lineHeight = -1;
1859     }
1860 
1861     /*
1862      * Scan through the contents of the window from top to bottom, recomputing
1863      * information for lines that are missing.
1864      */
1865 
1866     lastLinePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree, textPtr,
1867 	    TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr));
1868     dlPtr = dInfoPtr->dLinePtr;
1869     prevPtr = NULL;
1870     y = dInfoPtr->y - dInfoPtr->newTopPixelOffset;
1871     maxY = dInfoPtr->maxY;
1872     while (1) {
1873 	DLine *newPtr;
1874 
1875 	if (index.linePtr == lastLinePtr) {
1876 	    break;
1877 	}
1878 
1879 	/*
1880 	 * There are three possibilities right now:
1881 	 * (a) the next DLine (dlPtr) corresponds exactly to the next
1882 	 *     information we want to display: just use it as-is.
1883 	 * (b) the next DLine corresponds to a different line, or to a segment
1884 	 *     that will be coming later in the same line: leave this DLine
1885 	 *     alone in the hopes that we'll be able to use it later, then
1886 	 *     create a new DLine in front of it.
1887 	 * (c) the next DLine corresponds to a segment in the line we want,
1888 	 *     but it's a segment that has already been processed or will
1889 	 *     never be processed. Delete the DLine and try again.
1890 	 *
1891 	 * One other twist on all this. It's possible for 3D borders to
1892 	 * interact between lines (see DisplayLineBackground) so if a line is
1893 	 * relayed out and has styles with 3D borders, its neighbors have to
1894 	 * be redrawn if they have 3D borders too, since the interactions
1895 	 * could have changed (the neighbors don't have to be relayed out,
1896 	 * just redrawn).
1897 	 */
1898 
1899 	if ((dlPtr == NULL) || (dlPtr->index.linePtr != index.linePtr)) {
1900 	    /*
1901 	     * Case (b) -- must make new DLine.
1902 	     */
1903 
1904 	makeNewDLine:
1905 	    if (tkTextDebug) {
1906 		char string[TK_POS_CHARS];
1907 
1908 		/*
1909 		 * Debugging is enabled, so keep a log of all the lines that
1910 		 * were re-layed out. The test suite uses this information.
1911 		 */
1912 
1913 		TkTextPrintIndex(textPtr, &index, string);
1914 		LOG("tk_textRelayout", string);
1915 	    }
1916 	    newPtr = LayoutDLine(textPtr, &index);
1917 	    if (prevPtr == NULL) {
1918 		dInfoPtr->dLinePtr = newPtr;
1919 	    } else {
1920 		prevPtr->nextPtr = newPtr;
1921 		if (prevPtr->flags & HAS_3D_BORDER) {
1922 		    prevPtr->flags |= OLD_Y_INVALID;
1923 		}
1924 	    }
1925 	    newPtr->nextPtr = dlPtr;
1926 	    dlPtr = newPtr;
1927 	} else {
1928 	    /*
1929 	     * DlPtr refers to the line we want. Next check the index within
1930 	     * the line.
1931 	     */
1932 
1933 	    if (index.byteIndex == dlPtr->index.byteIndex) {
1934 		/*
1935 		 * Case (a) - can use existing display line as-is.
1936 		 */
1937 
1938 		if ((dlPtr->flags & HAS_3D_BORDER) && (prevPtr != NULL)
1939 			&& (prevPtr->flags & (NEW_LAYOUT))) {
1940 		    dlPtr->flags |= OLD_Y_INVALID;
1941 		}
1942 		goto lineOK;
1943 	    }
1944 	    if (index.byteIndex < dlPtr->index.byteIndex) {
1945 		goto makeNewDLine;
1946 	    }
1947 
1948 	    /*
1949 	     * Case (c) - dlPtr is useless. Discard it and start again with
1950 	     * the next display line.
1951 	     */
1952 
1953 	    newPtr = dlPtr->nextPtr;
1954 	    FreeDLines(textPtr, dlPtr, newPtr, DLINE_FREE);
1955 	    dlPtr = newPtr;
1956 	    if (prevPtr != NULL) {
1957 		prevPtr->nextPtr = newPtr;
1958 	    } else {
1959 		dInfoPtr->dLinePtr = newPtr;
1960 	    }
1961 	    continue;
1962 	}
1963 
1964 	/*
1965 	 * Advance to the start of the next line.
1966 	 */
1967 
1968     lineOK:
1969 	dlPtr->y = y;
1970 	y += dlPtr->height;
1971 	if (lineHeight != -1) {
1972 	    lineHeight += dlPtr->height;
1973 	}
1974 	TkTextIndexForwBytes(textPtr, &index, dlPtr->byteCount, &index);
1975 	prevPtr = dlPtr;
1976 	dlPtr = dlPtr->nextPtr;
1977 
1978 	/*
1979 	 * If we switched text lines, delete any DLines left for the old text
1980 	 * line.
1981 	 */
1982 
1983 	if (index.linePtr != prevPtr->index.linePtr) {
1984 	    DLine *nextPtr;
1985 
1986 	    nextPtr = dlPtr;
1987 	    while ((nextPtr != NULL)
1988 		    && (nextPtr->index.linePtr == prevPtr->index.linePtr)) {
1989 		nextPtr = nextPtr->nextPtr;
1990 	    }
1991 	    if (nextPtr != dlPtr) {
1992 		FreeDLines(textPtr, dlPtr, nextPtr, DLINE_FREE);
1993 		prevPtr->nextPtr = nextPtr;
1994 		dlPtr = nextPtr;
1995 	    }
1996 
1997 	    if ((lineHeight != -1) && (TkBTreeLinePixelCount(textPtr,
1998 		    prevPtr->index.linePtr) != lineHeight)) {
1999 		/*
2000 		 * The logical line height we just calculated is actually
2001 		 * different to the currently cached height of the text line.
2002 		 * That is fine (the text line heights are only calculated
2003 		 * asynchronously), but we must update the cached height so
2004 		 * that any counts made with DLine pointers are the same as
2005 		 * counts made through the BTree. This helps to ensure that
2006 		 * the scrollbar size corresponds accurately to that displayed
2007 		 * contents, even as the window is re-sized.
2008 		 */
2009 
2010 		TkBTreeAdjustPixelHeight(textPtr, prevPtr->index.linePtr,
2011 			lineHeight, 0);
2012 
2013 		/*
2014 		 * I believe we can be 100% sure that we started at the
2015 		 * beginning of the logical line, so we can also adjust the
2016 		 * 'pixelCalculationEpoch' to mark it as being up to date.
2017 		 * There is a slight concern that we might not have got this
2018 		 * right for the first line in the re-display.
2019 		 */
2020 
2021 		TkBTreeLinePixelEpoch(textPtr, prevPtr->index.linePtr) =
2022 			dInfoPtr->lineMetricUpdateEpoch;
2023 	    }
2024 	    lineHeight = 0;
2025 	}
2026 
2027 	/*
2028 	 * It's important to have the following check here rather than in the
2029 	 * while statement for the loop, so that there's always at least one
2030 	 * DLine generated, regardless of how small the window is. This keeps
2031 	 * a lot of other code from breaking.
2032 	 */
2033 
2034 	if (y >= maxY) {
2035 	    break;
2036 	}
2037     }
2038 
2039     /*
2040      * Delete any DLine structures that don't fit on the screen.
2041      */
2042 
2043     FreeDLines(textPtr, dlPtr, NULL, DLINE_UNLINK);
2044 
2045     /*
2046      * If there is extra space at the bottom of the window (because we've hit
2047      * the end of the text), then bring in more lines at the top of the
2048      * window, if there are any, to fill in the view.
2049      *
2050      * Since the top line may only be partially visible, we try first to
2051      * simply show more pixels from that line (newTopPixelOffset). If that
2052      * isn't enough, we have to layout more lines.
2053      */
2054 
2055     if (y < maxY) {
2056 	/*
2057 	 * This counts how many vertical pixels we have left to fill by
2058 	 * pulling in more display pixels either from the first currently
2059 	 * displayed, or the lines above it.
2060 	 */
2061 
2062 	int spaceLeft = maxY - y;
2063 
2064 	if (spaceLeft <= dInfoPtr->newTopPixelOffset) {
2065 	    /*
2066 	     * We can fill up all the needed space just by showing more of the
2067 	     * current top line.
2068 	     */
2069 
2070 	    dInfoPtr->newTopPixelOffset -= spaceLeft;
2071 	    y += spaceLeft;
2072 	    spaceLeft = 0;
2073 	} else {
2074 	    int lineNum, bytesToCount;
2075 	    DLine *lowestPtr;
2076 
2077 	    /*
2078 	     * Add in all of the current top line, which won't be enough to
2079 	     * bring y up to maxY (if it was we would be in the 'if' block
2080 	     * above).
2081 	     */
2082 
2083 	    y += dInfoPtr->newTopPixelOffset;
2084 	    dInfoPtr->newTopPixelOffset = 0;
2085 
2086 	    /*
2087 	     * Layout an entire text line (potentially > 1 display line), then
2088 	     * link in as many display lines as fit without moving the bottom
2089 	     * line out of the window. Repeat this until all the extra space
2090 	     * has been used up or we've reached the beginning of the text.
2091 	     */
2092 
2093 	    spaceLeft = maxY - y;
2094 	    if (dInfoPtr->dLinePtr == NULL) {
2095 		/*
2096 		 * No lines have been laid out. This must be an empty peer
2097 		 * widget.
2098 		 */
2099 
2100                 lineNum = TkBTreeNumLines(textPtr->sharedTextPtr->tree,
2101                         textPtr) - 1;
2102                 bytesToCount = INT_MAX;
2103 	    } else {
2104 		lineNum = TkBTreeLinesTo(textPtr,
2105 			dInfoPtr->dLinePtr->index.linePtr);
2106 		bytesToCount = dInfoPtr->dLinePtr->index.byteIndex;
2107 		if (bytesToCount == 0) {
2108 		    bytesToCount = INT_MAX;
2109 		    lineNum--;
2110 		}
2111 	    }
2112 	    for ( ; (lineNum >= 0) && (spaceLeft > 0); lineNum--) {
2113 		int pixelHeight = 0;
2114 
2115 		index.linePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree,
2116 			textPtr, lineNum);
2117 		index.byteIndex = 0;
2118 		lowestPtr = NULL;
2119 
2120 		do {
2121 		    dlPtr = LayoutDLine(textPtr, &index);
2122 		    pixelHeight += dlPtr->height;
2123 		    dlPtr->nextPtr = lowestPtr;
2124 		    lowestPtr = dlPtr;
2125 		    if (dlPtr->length == 0 && dlPtr->height == 0) {
2126 			bytesToCount--;
2127 			break;
2128 		    }	/* elide */
2129 		    TkTextIndexForwBytes(textPtr, &index, dlPtr->byteCount,
2130 			    &index);
2131 		    bytesToCount -= dlPtr->byteCount;
2132 		} while ((bytesToCount > 0)
2133 			&& (index.linePtr == lowestPtr->index.linePtr));
2134 
2135 		/*
2136 		 * We may not have examined the entire line (depending on the
2137 		 * value of 'bytesToCount', so we only want to set this if it
2138 		 * is genuinely bigger).
2139 		 */
2140 
2141 		if (pixelHeight > TkBTreeLinePixelCount(textPtr,
2142 			lowestPtr->index.linePtr)) {
2143 		    TkBTreeAdjustPixelHeight(textPtr,
2144 			    lowestPtr->index.linePtr, pixelHeight, 0);
2145 		    if (index.linePtr != lowestPtr->index.linePtr) {
2146 			/*
2147 			 * We examined the entire line, so can update the
2148 			 * epoch.
2149 			 */
2150 
2151 			TkBTreeLinePixelEpoch(textPtr,
2152 				lowestPtr->index.linePtr) =
2153 				dInfoPtr->lineMetricUpdateEpoch;
2154 		    }
2155 		}
2156 
2157 		/*
2158 		 * Scan through the display lines from the bottom one up to
2159 		 * the top one.
2160 		 */
2161 
2162 		while (lowestPtr != NULL) {
2163 		    dlPtr = lowestPtr;
2164 		    spaceLeft -= dlPtr->height;
2165 		    lowestPtr = dlPtr->nextPtr;
2166 		    dlPtr->nextPtr = dInfoPtr->dLinePtr;
2167 		    dInfoPtr->dLinePtr = dlPtr;
2168 		    if (tkTextDebug) {
2169 			char string[TK_POS_CHARS];
2170 
2171 			TkTextPrintIndex(textPtr, &dlPtr->index, string);
2172 			LOG("tk_textRelayout", string);
2173 		    }
2174 		    if (spaceLeft <= 0) {
2175 			break;
2176 		    }
2177 		}
2178 		FreeDLines(textPtr, lowestPtr, NULL, DLINE_FREE);
2179 		bytesToCount = INT_MAX;
2180 	    }
2181 
2182 	    /*
2183 	     * We've either filled in the space we wanted to or we've run out
2184 	     * of display lines at the top of the text. Note that we already
2185 	     * set dInfoPtr->newTopPixelOffset to zero above.
2186 	     */
2187 
2188 	    if (spaceLeft < 0) {
2189 		/*
2190 		 * We've laid out a few too many vertical pixels at or above
2191 		 * the first line. Therefore we only want to show part of the
2192 		 * first displayed line, so that the last displayed line just
2193 		 * fits in the window.
2194 		 */
2195 
2196 		dInfoPtr->newTopPixelOffset = -spaceLeft;
2197 		if (dInfoPtr->newTopPixelOffset>=dInfoPtr->dLinePtr->height) {
2198 		    /*
2199 		     * Somehow the entire first line we laid out is shorter
2200 		     * than the new offset. This should not occur and would
2201 		     * indicate a bad problem in the logic above.
2202 		     */
2203 
2204 		    Tcl_Panic("Error in pixel height consistency while filling in spacesLeft");
2205 		}
2206 	    }
2207 	}
2208 
2209 	/*
2210 	 * Now we're all done except that the y-coordinates in all the DLines
2211 	 * are wrong and the top index for the text is wrong. Update them.
2212 	 */
2213 
2214 	if (dInfoPtr->dLinePtr != NULL) {
2215 	    textPtr->topIndex = dInfoPtr->dLinePtr->index;
2216 	    y = dInfoPtr->y - dInfoPtr->newTopPixelOffset;
2217 	    for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL;
2218 		    dlPtr = dlPtr->nextPtr) {
2219 		if (y > dInfoPtr->maxY) {
2220 		    Tcl_Panic("Added too many new lines in UpdateDisplayInfo");
2221 		}
2222 		dlPtr->y = y;
2223 		y += dlPtr->height;
2224 	    }
2225 	}
2226     }
2227 
2228     /*
2229      * If the old top or bottom line has scrolled elsewhere on the screen, we
2230      * may not be able to re-use its old contents by copying bits (e.g., a
2231      * beveled edge that was drawn when it was at the top or bottom won't be
2232      * drawn when the line is in the middle and its neighbor has a matching
2233      * background). Similarly, if the new top or bottom line came from
2234      * somewhere else on the screen, we may not be able to copy the old bits.
2235      */
2236 
2237     dlPtr = dInfoPtr->dLinePtr;
2238     if (dlPtr != NULL) {
2239 	if ((dlPtr->flags & HAS_3D_BORDER) && !(dlPtr->flags & TOP_LINE)) {
2240 	    dlPtr->flags |= OLD_Y_INVALID;
2241 	}
2242 	while (1) {
2243 	    if ((dlPtr->flags & TOP_LINE) && (dlPtr != dInfoPtr->dLinePtr)
2244 		    && (dlPtr->flags & HAS_3D_BORDER)) {
2245 		dlPtr->flags |= OLD_Y_INVALID;
2246 	    }
2247 
2248 	    /*
2249 	     * If the old top-line was not completely showing (i.e. the
2250 	     * pixelOffset is non-zero) and is no longer the top-line, then we
2251 	     * must re-draw it.
2252 	     */
2253 
2254 	    if ((dlPtr->flags & TOP_LINE) &&
2255 		    dInfoPtr->topPixelOffset!=0 && dlPtr!=dInfoPtr->dLinePtr) {
2256 		dlPtr->flags |= OLD_Y_INVALID;
2257 	    }
2258 	    if ((dlPtr->flags & BOTTOM_LINE) && (dlPtr->nextPtr != NULL)
2259 		    && (dlPtr->flags & HAS_3D_BORDER)) {
2260 		dlPtr->flags |= OLD_Y_INVALID;
2261 	    }
2262 	    if (dlPtr->nextPtr == NULL) {
2263 		if ((dlPtr->flags & HAS_3D_BORDER)
2264 			&& !(dlPtr->flags & BOTTOM_LINE)) {
2265 		    dlPtr->flags |= OLD_Y_INVALID;
2266 		}
2267 		dlPtr->flags &= ~TOP_LINE;
2268 		dlPtr->flags |= BOTTOM_LINE;
2269 		break;
2270 	    }
2271 	    dlPtr->flags &= ~(TOP_LINE|BOTTOM_LINE);
2272 	    dlPtr = dlPtr->nextPtr;
2273 	}
2274 	dInfoPtr->dLinePtr->flags |= TOP_LINE;
2275 	dInfoPtr->topPixelOffset = dInfoPtr->newTopPixelOffset;
2276     }
2277 
2278     /*
2279      * Arrange for scrollbars to be updated.
2280      */
2281 
2282     textPtr->flags |= UPDATE_SCROLLBARS;
2283 
2284     /*
2285      * Deal with horizontal scrolling:
2286      * 1. If there's empty space to the right of the longest line, shift the
2287      *	  screen to the right to fill in the empty space.
2288      * 2. If the desired horizontal scroll position has changed, force a full
2289      *	  redisplay of all the lines in the widget.
2290      * 3. If the wrap mode isn't "none" then re-scroll to the base position.
2291      */
2292 
2293     dInfoPtr->maxLength = 0;
2294     for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL;
2295 	    dlPtr = dlPtr->nextPtr) {
2296 	if (dlPtr->length > dInfoPtr->maxLength) {
2297 	    dInfoPtr->maxLength = dlPtr->length;
2298 	}
2299     }
2300     maxOffset = dInfoPtr->maxLength - (dInfoPtr->maxX - dInfoPtr->x);
2301 
2302     xPixelOffset = dInfoPtr->newXPixelOffset;
2303     if (xPixelOffset > maxOffset) {
2304 	xPixelOffset = maxOffset;
2305     }
2306     if (xPixelOffset < 0) {
2307 	xPixelOffset = 0;
2308     }
2309 
2310     /*
2311      * Here's a problem: see the tests textDisp-29.2.1-4
2312      *
2313      * If the widget is being created, but has not yet been configured it will
2314      * have a maxY of 1 above, and we won't have examined all the lines
2315      * (just the first line, in fact), and so maxOffset will not be a true
2316      * reflection of the widget's lines. Therefore we must not overwrite the
2317      * original newXPixelOffset in this case.
2318      */
2319 
2320     if (!(((Tk_FakeWin *) (textPtr->tkwin))->flags & TK_NEED_CONFIG_NOTIFY)) {
2321 	dInfoPtr->newXPixelOffset = xPixelOffset;
2322     }
2323 
2324     if (xPixelOffset != dInfoPtr->curXPixelOffset) {
2325 	dInfoPtr->curXPixelOffset = xPixelOffset;
2326 	for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL;
2327 		dlPtr = dlPtr->nextPtr) {
2328 	    dlPtr->flags |= OLD_Y_INVALID;
2329 	}
2330     }
2331 }
2332 
2333 /*
2334  *----------------------------------------------------------------------
2335  *
2336  * FreeDLines --
2337  *
2338  *	This function is called to free up all of the resources associated
2339  *	with one or more DLine structures.
2340  *
2341  * Results:
2342  *	None.
2343  *
2344  * Side effects:
2345  *	Memory gets freed and various other resources are released.
2346  *
2347  *----------------------------------------------------------------------
2348  */
2349 
2350 static void
FreeDLines(TkText * textPtr,DLine * firstPtr,DLine * lastPtr,int action)2351 FreeDLines(
2352     TkText *textPtr,		/* Information about overall text widget. */
2353     DLine *firstPtr,	/* Pointer to first DLine to free up. */
2354     DLine *lastPtr,		/* Pointer to DLine just after last one to
2355 				 * free (NULL means everything starting with
2356 				 * firstPtr). */
2357     int action)			/* DLINE_UNLINK means DLines are currently
2358 				 * linked into the list rooted at
2359 				 * textPtr->dInfoPtr->dLinePtr and they have
2360 				 * to be unlinked. DLINE_FREE means just free
2361 				 * without unlinking. DLINE_FREE_TEMP means
2362 				 * the DLine given is just a temporary one and
2363 				 * we shouldn't invalidate anything for the
2364 				 * overall widget. */
2365 {
2366     TkTextDispChunk *chunkPtr, *nextChunkPtr;
2367     DLine *nextDLinePtr;
2368 
2369     if (action == DLINE_FREE_TEMP) {
2370 	lineHeightsRecalculated++;
2371 	if (tkTextDebug) {
2372 	    char string[TK_POS_CHARS];
2373 
2374 	    /*
2375 	     * Debugging is enabled, so keep a log of all the lines whose
2376 	     * height was recalculated. The test suite uses this information.
2377 	     */
2378 
2379 	    TkTextPrintIndex(textPtr, &firstPtr->index, string);
2380 	    LOG("tk_textHeightCalc", string);
2381 	}
2382     } else if (action == DLINE_UNLINK) {
2383 	if (textPtr->dInfoPtr->dLinePtr == firstPtr) {
2384 	    textPtr->dInfoPtr->dLinePtr = lastPtr;
2385 	} else {
2386 	    DLine *prevPtr;
2387 
2388 	    for (prevPtr = textPtr->dInfoPtr->dLinePtr;
2389 		    prevPtr->nextPtr != firstPtr; prevPtr = prevPtr->nextPtr) {
2390 		/* Empty loop body. */
2391 	    }
2392 	    prevPtr->nextPtr = lastPtr;
2393 	}
2394     }
2395     while (firstPtr != lastPtr) {
2396 	nextDLinePtr = firstPtr->nextPtr;
2397 	for (chunkPtr = firstPtr->chunkPtr; chunkPtr != NULL;
2398 		chunkPtr = nextChunkPtr) {
2399 	    if (chunkPtr->undisplayProc != NULL) {
2400 		chunkPtr->undisplayProc(textPtr, chunkPtr);
2401 	    }
2402 	    FreeStyle(textPtr, chunkPtr->stylePtr);
2403 	    nextChunkPtr = chunkPtr->nextPtr;
2404 	    ckfree(chunkPtr);
2405 	}
2406 	ckfree(firstPtr);
2407 	firstPtr = nextDLinePtr;
2408     }
2409     if (action != DLINE_FREE_TEMP) {
2410 	textPtr->dInfoPtr->dLinesInvalidated = 1;
2411     }
2412 }
2413 
2414 /*
2415  *----------------------------------------------------------------------
2416  *
2417  * DisplayDLine --
2418  *
2419  *	This function is invoked to draw a single line on the screen.
2420  *
2421  * Results:
2422  *	None.
2423  *
2424  * Side effects:
2425  *	The line given by dlPtr is drawn at its correct position in textPtr's
2426  *	window. Note that this is one *display* line, not one *text* line.
2427  *
2428  *----------------------------------------------------------------------
2429  */
2430 
2431 static void
DisplayDLine(TkText * textPtr,DLine * dlPtr,DLine * prevPtr,Pixmap pixmap)2432 DisplayDLine(
2433     TkText *textPtr,		/* Text widget in which to draw line. */
2434     DLine *dlPtr,	/* Information about line to draw. */
2435     DLine *prevPtr,		/* Line just before one to draw, or NULL if
2436 				 * dlPtr is the top line. */
2437     Pixmap pixmap)		/* Pixmap to use for double-buffering. Caller
2438 				 * must make sure it's large enough to hold
2439 				 * line. */
2440 {
2441     TkTextDispChunk *chunkPtr;
2442     TextDInfo *dInfoPtr = textPtr->dInfoPtr;
2443     Display *display;
2444     int height, y_off;
2445 #ifndef TK_NO_DOUBLE_BUFFERING
2446     const int y = 0;
2447 #else
2448     const int y = dlPtr->y;
2449 #endif /* TK_NO_DOUBLE_BUFFERING */
2450 
2451     if (dlPtr->chunkPtr == NULL) return;
2452 
2453     display = Tk_Display(textPtr->tkwin);
2454 
2455     height = dlPtr->height;
2456     if ((height + dlPtr->y) > dInfoPtr->maxY) {
2457 	height = dInfoPtr->maxY - dlPtr->y;
2458     }
2459     if (dlPtr->y < dInfoPtr->y) {
2460 	y_off = dInfoPtr->y - dlPtr->y;
2461 	height -= y_off;
2462     } else {
2463 	y_off = 0;
2464     }
2465 
2466 #ifdef TK_NO_DOUBLE_BUFFERING
2467     TkpClipDrawableToRect(display, pixmap, dInfoPtr->x, y + y_off,
2468 	    dInfoPtr->maxX - dInfoPtr->x, height);
2469 #endif /* TK_NO_DOUBLE_BUFFERING */
2470 
2471     /*
2472      * First, clear the area of the line to the background color for the text
2473      * widget.
2474      */
2475 
2476     Tk_Fill3DRectangle(textPtr->tkwin, pixmap, textPtr->border, 0, y,
2477 	    Tk_Width(textPtr->tkwin), dlPtr->height, 0, TK_RELIEF_FLAT);
2478 
2479     /*
2480      * Second, draw background information for the whole line.
2481      */
2482 
2483     DisplayLineBackground(textPtr, dlPtr, prevPtr, pixmap);
2484 
2485     /*
2486      * Third, draw the background color of the left and right margins.
2487      */
2488     if (dlPtr->lMarginColor != NULL) {
2489         Tk_Fill3DRectangle(textPtr->tkwin, pixmap, dlPtr->lMarginColor, 0, y,
2490                 dlPtr->lMarginWidth + dInfoPtr->x - dInfoPtr->curXPixelOffset,
2491                 dlPtr->height, 0, TK_RELIEF_FLAT);
2492     }
2493     if (dlPtr->rMarginColor != NULL) {
2494         Tk_Fill3DRectangle(textPtr->tkwin, pixmap, dlPtr->rMarginColor,
2495                 dInfoPtr->maxX - dlPtr->rMarginWidth + dInfoPtr->curXPixelOffset,
2496                 y, dlPtr->rMarginWidth, dlPtr->height, 0, TK_RELIEF_FLAT);
2497     }
2498 
2499     /*
2500      * Make another pass through all of the chunks to redraw the insertion
2501      * cursor, if it is visible on this line. Must do it here rather than in
2502      * the foreground pass below because otherwise a wide insertion cursor
2503      * will obscure the character to its left.
2504      */
2505 
2506     if (textPtr->state == TK_TEXT_STATE_NORMAL) {
2507 	for (chunkPtr = dlPtr->chunkPtr; (chunkPtr != NULL);
2508 		chunkPtr = chunkPtr->nextPtr) {
2509 	    if (chunkPtr->displayProc == TkTextInsertDisplayProc) {
2510 		int x = chunkPtr->x + dInfoPtr->x - dInfoPtr->curXPixelOffset;
2511 
2512 		chunkPtr->displayProc(textPtr, chunkPtr, x,
2513 			y + dlPtr->spaceAbove,
2514 			dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow,
2515 			dlPtr->baseline - dlPtr->spaceAbove, display, pixmap,
2516 			dlPtr->y + dlPtr->spaceAbove);
2517 	    }
2518 	}
2519     }
2520 
2521     /*
2522      * Make yet another pass through all of the chunks to redraw all of
2523      * foreground information. Note: we have to call the displayProc even for
2524      * chunks that are off-screen. This is needed, for example, so that
2525      * embedded windows can be unmapped in this case.
2526      */
2527 
2528     for (chunkPtr = dlPtr->chunkPtr; (chunkPtr != NULL);
2529 	    chunkPtr = chunkPtr->nextPtr) {
2530 	if (chunkPtr->displayProc == TkTextInsertDisplayProc) {
2531 	    /*
2532 	     * Already displayed the insertion cursor above. Don't do it again
2533 	     * here.
2534 	     */
2535 
2536 	    continue;
2537 	}
2538 
2539 	/*
2540 	 * Don't call if elide. This tax OK since not very many visible DLines
2541 	 * in an area, but potentially many elide ones.
2542 	 */
2543 
2544 	if (chunkPtr->displayProc != NULL) {
2545 	    int x = chunkPtr->x + dInfoPtr->x - dInfoPtr->curXPixelOffset;
2546 
2547 	    if ((x + chunkPtr->width <= 0) || (x >= dInfoPtr->maxX)) {
2548 		/*
2549 		 * Note: we have to call the displayProc even for chunks that
2550 		 * are off-screen. This is needed, for example, so that
2551 		 * embedded windows can be unmapped in this case. Display the
2552 		 * chunk at a coordinate that can be clearly identified by the
2553 		 * displayProc as being off-screen to the left (the
2554 		 * displayProc may not be able to tell if something is off to
2555 		 * the right).
2556 		 */
2557 
2558 		x = -chunkPtr->width;
2559 	    }
2560 	    chunkPtr->displayProc(textPtr, chunkPtr, x,
2561 		    y + dlPtr->spaceAbove, dlPtr->height - dlPtr->spaceAbove -
2562 		    dlPtr->spaceBelow, dlPtr->baseline - dlPtr->spaceAbove,
2563 		    display, pixmap, dlPtr->y + dlPtr->spaceAbove);
2564 	}
2565 
2566 	if (dInfoPtr->dLinesInvalidated) {
2567 	    return;
2568 	}
2569     }
2570 
2571 #ifndef TK_NO_DOUBLE_BUFFERING
2572     /*
2573      * Copy the pixmap onto the screen. If this is the first or last line on
2574      * the screen then copy a piece of the line, so that it doesn't overflow
2575      * into the border area. Another special trick: copy the padding area to
2576      * the left of the line; this is because the insertion cursor sometimes
2577      * overflows onto that area and we want to get as much of the cursor as
2578      * possible.
2579      */
2580 
2581     XCopyArea(display, pixmap, Tk_WindowId(textPtr->tkwin), dInfoPtr->copyGC,
2582 	    dInfoPtr->x, y + y_off, (unsigned) (dInfoPtr->maxX - dInfoPtr->x),
2583 	    (unsigned) height, dInfoPtr->x, dlPtr->y + y_off);
2584 #else
2585     TkpClipDrawableToRect(display, pixmap, 0, 0, -1, -1);
2586 #endif /* TK_NO_DOUBLE_BUFFERING */
2587     linesRedrawn++;
2588 }
2589 
2590 /*
2591  *--------------------------------------------------------------
2592  *
2593  * DisplayLineBackground --
2594  *
2595  *	This function is called to fill in the background for a display line.
2596  *	It draws 3D borders cleverly so that adjacent chunks with the same
2597  *	style (whether on the same line or different lines) have a single 3D
2598  *	border around the whole region.
2599  *
2600  * Results:
2601  *	There is no return value. Pixmap is filled in with background
2602  *	information for dlPtr.
2603  *
2604  * Side effects:
2605  *	None.
2606  *
2607  *--------------------------------------------------------------
2608  */
2609 
2610 static void
DisplayLineBackground(TkText * textPtr,DLine * dlPtr,DLine * prevPtr,Pixmap pixmap)2611 DisplayLineBackground(
2612     TkText *textPtr,		/* Text widget containing line. */
2613     DLine *dlPtr,	/* Information about line to draw. */
2614     DLine *prevPtr,		/* Line just above dlPtr, or NULL if dlPtr is
2615 				 * the top-most line in the window. */
2616     Pixmap pixmap)		/* Pixmap to use for double-buffering. Caller
2617 				 * must make sure it's large enough to hold
2618 				 * line. Caller must also have filled it with
2619 				 * the background color for the widget. */
2620 {
2621     TextDInfo *dInfoPtr = textPtr->dInfoPtr;
2622     TkTextDispChunk *chunkPtr;	/* Pointer to chunk in the current line. */
2623     TkTextDispChunk *chunkPtr2;	/* Pointer to chunk in the line above or below
2624 				 * the current one. NULL if we're to the left
2625 				 * of or to the right of the chunks in the
2626 				 * line. */
2627     TkTextDispChunk *nextPtr2;	/* Next chunk after chunkPtr2 (it's not the
2628 				 * same as chunkPtr2->nextPtr in the case
2629 				 * where chunkPtr2 is NULL because the line is
2630 				 * indented). */
2631     int leftX;			/* The left edge of the region we're currently
2632 				 * working on. */
2633     int leftXIn;		/* 1 means beveled edge at leftX slopes right
2634 				 * as it goes down, 0 means it slopes left as
2635 				 * it goes down. */
2636     int rightX;			/* Right edge of chunkPtr. */
2637     int rightX2;		/* Right edge of chunkPtr2. */
2638     int matchLeft;		/* Does the style of this line match that of
2639 				 * its neighbor just to the left of the
2640 				 * current x coordinate? */
2641     int matchRight;		/* Does line's style match its neighbor just
2642 				 * to the right of the current x-coord? */
2643     int minX, maxX, xOffset, bw;
2644     StyleValues *sValuePtr;
2645     Display *display;
2646 #ifndef TK_NO_DOUBLE_BUFFERING
2647     const int y = 0;
2648 #else
2649     const int y = dlPtr->y;
2650 #endif /* TK_NO_DOUBLE_BUFFERING */
2651 
2652     /*
2653      * Pass 1: scan through dlPtr from left to right. For each range of chunks
2654      * with the same style, draw the main background for the style plus the
2655      * vertical parts of the 3D borders (the left and right edges).
2656      */
2657 
2658     display = Tk_Display(textPtr->tkwin);
2659     minX = dInfoPtr->curXPixelOffset;
2660     xOffset = dInfoPtr->x - minX;
2661     maxX = minX + dInfoPtr->maxX - dInfoPtr->x;
2662     chunkPtr = dlPtr->chunkPtr;
2663 
2664     /*
2665      * Note A: in the following statement, and a few others later in this file
2666      * marked with "See Note A above", the right side of the assignment was
2667      * replaced with 0 on 6/18/97. This has the effect of highlighting the
2668      * empty space to the left of a line whenever the leftmost character of
2669      * the line is highlighted. This way, multi-line highlights always line up
2670      * along their left edges. However, this may look funny in the case where
2671      * a single word is highlighted. To undo the change, replace "leftX = 0"
2672      * with "leftX = chunkPtr->x" and "rightX2 = 0" with "rightX2 =
2673      * nextPtr2->x" here and at all the marked points below. This restores the
2674      * old behavior where empty space to the left of a line is not
2675      * highlighted, leaving a ragged left edge for multi-line highlights.
2676      */
2677 
2678     leftX = 0;
2679     for (; leftX < maxX; chunkPtr = chunkPtr->nextPtr) {
2680 	if ((chunkPtr->nextPtr != NULL)
2681 		&& SAME_BACKGROUND(chunkPtr->nextPtr->stylePtr,
2682 		chunkPtr->stylePtr)) {
2683 	    continue;
2684 	}
2685 	sValuePtr = chunkPtr->stylePtr->sValuePtr;
2686 	rightX = chunkPtr->x + chunkPtr->width;
2687 	if ((chunkPtr->nextPtr == NULL) && (rightX < maxX)) {
2688 	    rightX = maxX;
2689 	}
2690 	if (chunkPtr->stylePtr->bgGC != NULL) {
2691 	    /*
2692 	     * Not visible - bail out now.
2693 	     */
2694 
2695 	    if (rightX + xOffset <= 0) {
2696 		leftX = rightX;
2697 		continue;
2698 	    }
2699 
2700 	    /*
2701 	     * Trim the start position for drawing to be no further away than
2702 	     * -borderWidth. The reason is that on many X servers drawing from
2703 	     * -32768 (or less) to +something simply does not display
2704 	     * correctly. [Patch #541999]
2705 	     */
2706 
2707 	    if ((leftX + xOffset) < -(sValuePtr->borderWidth)) {
2708 		leftX = -sValuePtr->borderWidth - xOffset;
2709 	    }
2710 	    if ((rightX - leftX) > 32767) {
2711 		rightX = leftX + 32767;
2712 	    }
2713 
2714             /*
2715              * Prevent the borders from leaking on adjacent characters,
2716              * which would happen for too large border width.
2717              */
2718 
2719             bw = sValuePtr->borderWidth;
2720             if (leftX + sValuePtr->borderWidth > rightX) {
2721                 bw = rightX - leftX;
2722             }
2723 
2724 	    XFillRectangle(display, pixmap, chunkPtr->stylePtr->bgGC,
2725 		    leftX + xOffset, y, (unsigned int) (rightX - leftX),
2726 		    (unsigned int) dlPtr->height);
2727 	    if (sValuePtr->relief != TK_RELIEF_FLAT) {
2728 		Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border,
2729 			leftX + xOffset, y, bw, dlPtr->height, 1,
2730 			sValuePtr->relief);
2731 		Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border,
2732 			rightX - bw + xOffset, y, bw, dlPtr->height, 0,
2733 			sValuePtr->relief);
2734 	    }
2735 	}
2736 	leftX = rightX;
2737     }
2738 
2739     /*
2740      * Pass 2: draw the horizontal bevels along the top of the line. To do
2741      * this, scan through dlPtr from left to right while simultaneously
2742      * scanning through the line just above dlPtr. ChunkPtr2 and nextPtr2
2743      * refer to two adjacent chunks in the line above.
2744      */
2745 
2746     chunkPtr = dlPtr->chunkPtr;
2747     leftX = 0;				/* See Note A above. */
2748     leftXIn = 1;
2749     rightX = chunkPtr->x + chunkPtr->width;
2750     if ((chunkPtr->nextPtr == NULL) && (rightX < maxX)) {
2751 	rightX = maxX;
2752     }
2753     chunkPtr2 = NULL;
2754     if (prevPtr != NULL && prevPtr->chunkPtr != NULL) {
2755 	/*
2756 	 * Find the chunk in the previous line that covers leftX.
2757 	 */
2758 
2759 	nextPtr2 = prevPtr->chunkPtr;
2760 	rightX2 = 0;			/* See Note A above. */
2761 	while (rightX2 <= leftX) {
2762 	    chunkPtr2 = nextPtr2;
2763 	    if (chunkPtr2 == NULL) {
2764 		break;
2765 	    }
2766 	    nextPtr2 = chunkPtr2->nextPtr;
2767 	    rightX2 = chunkPtr2->x + chunkPtr2->width;
2768 	    if (nextPtr2 == NULL) {
2769 		rightX2 = INT_MAX;
2770 	    }
2771 	}
2772     } else {
2773 	nextPtr2 = NULL;
2774 	rightX2 = INT_MAX;
2775     }
2776 
2777     while (leftX < maxX) {
2778 	matchLeft = (chunkPtr2 != NULL)
2779 		&& SAME_BACKGROUND(chunkPtr2->stylePtr, chunkPtr->stylePtr);
2780 	sValuePtr = chunkPtr->stylePtr->sValuePtr;
2781 	if (rightX <= rightX2) {
2782 	    /*
2783 	     * The chunk in our line is about to end. If its style changes
2784 	     * then draw the bevel for the current style.
2785 	     */
2786 
2787 	    if ((chunkPtr->nextPtr == NULL)
2788 		    || !SAME_BACKGROUND(chunkPtr->stylePtr,
2789 		    chunkPtr->nextPtr->stylePtr)) {
2790 		if (!matchLeft && (sValuePtr->relief != TK_RELIEF_FLAT)) {
2791 		    Tk_3DHorizontalBevel(textPtr->tkwin, pixmap,
2792 			    sValuePtr->border, leftX + xOffset, y,
2793 			    rightX - leftX, sValuePtr->borderWidth, leftXIn,
2794 			    1, 1, sValuePtr->relief);
2795 		}
2796 		leftX = rightX;
2797 		leftXIn = 1;
2798 
2799 		/*
2800 		 * If the chunk in the line above is also ending at the same
2801 		 * point then advance to the next chunk in that line.
2802 		 */
2803 
2804 		if ((rightX == rightX2) && (chunkPtr2 != NULL)) {
2805 		    goto nextChunk2;
2806 		}
2807 	    }
2808 	    chunkPtr = chunkPtr->nextPtr;
2809 	    if (chunkPtr == NULL) {
2810 		break;
2811 	    }
2812 	    rightX = chunkPtr->x + chunkPtr->width;
2813 	    if ((chunkPtr->nextPtr == NULL) && (rightX < maxX)) {
2814 		rightX = maxX;
2815 	    }
2816 	    continue;
2817 	}
2818 
2819 	/*
2820 	 * The chunk in the line above is ending at an x-position where there
2821 	 * is no change in the style of the current line. If the style above
2822 	 * matches the current line on one side of the change but not on the
2823 	 * other, we have to draw an L-shaped piece of bevel.
2824 	 */
2825 
2826 	matchRight = (nextPtr2 != NULL)
2827 		&& SAME_BACKGROUND(nextPtr2->stylePtr, chunkPtr->stylePtr);
2828 	if (matchLeft && !matchRight) {
2829             bw = sValuePtr->borderWidth;
2830             if (rightX2 - sValuePtr->borderWidth < leftX) {
2831                 bw = rightX2 - leftX;
2832             }
2833 	    if (sValuePtr->relief != TK_RELIEF_FLAT) {
2834 		Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border,
2835 			rightX2 - bw + xOffset, y, bw,
2836 			sValuePtr->borderWidth, 0, sValuePtr->relief);
2837 	    }
2838             leftX = rightX2 - bw;
2839 	    leftXIn = 0;
2840 	} else if (!matchLeft && matchRight
2841 		&& (sValuePtr->relief != TK_RELIEF_FLAT)) {
2842             bw = sValuePtr->borderWidth;
2843             if (rightX2 + sValuePtr->borderWidth > rightX) {
2844                 bw = rightX - rightX2;
2845             }
2846 	    Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border,
2847 		    rightX2 + xOffset, y, bw, sValuePtr->borderWidth,
2848 		    1, sValuePtr->relief);
2849 	    Tk_3DHorizontalBevel(textPtr->tkwin, pixmap, sValuePtr->border,
2850 		    leftX + xOffset, y, rightX2 + bw - leftX,
2851 		    sValuePtr->borderWidth, leftXIn, 0, 1,
2852 		    sValuePtr->relief);
2853 	}
2854 
2855     nextChunk2:
2856 	chunkPtr2 = nextPtr2;
2857 	if (chunkPtr2 == NULL) {
2858 	    rightX2 = INT_MAX;
2859 	} else {
2860 	    nextPtr2 = chunkPtr2->nextPtr;
2861 	    rightX2 = chunkPtr2->x + chunkPtr2->width;
2862 	    if (nextPtr2 == NULL) {
2863 		rightX2 = INT_MAX;
2864 	    }
2865 	}
2866     }
2867 
2868     /*
2869      * Pass 3: draw the horizontal bevels along the bottom of the line. This
2870      * uses the same approach as pass 2.
2871      */
2872 
2873     chunkPtr = dlPtr->chunkPtr;
2874     leftX = 0;				/* See Note A above. */
2875     leftXIn = 0;
2876     rightX = chunkPtr->x + chunkPtr->width;
2877     if ((chunkPtr->nextPtr == NULL) && (rightX < maxX)) {
2878 	rightX = maxX;
2879     }
2880     chunkPtr2 = NULL;
2881     if (dlPtr->nextPtr != NULL && dlPtr->nextPtr->chunkPtr != NULL) {
2882 	/*
2883 	 * Find the chunk in the next line that covers leftX.
2884 	 */
2885 
2886 	nextPtr2 = dlPtr->nextPtr->chunkPtr;
2887 	rightX2 = 0;			/* See Note A above. */
2888 	while (rightX2 <= leftX) {
2889 	    chunkPtr2 = nextPtr2;
2890 	    if (chunkPtr2 == NULL) {
2891 		break;
2892 	    }
2893 	    nextPtr2 = chunkPtr2->nextPtr;
2894 	    rightX2 = chunkPtr2->x + chunkPtr2->width;
2895 	    if (nextPtr2 == NULL) {
2896 		rightX2 = INT_MAX;
2897 	    }
2898 	}
2899     } else {
2900 	nextPtr2 = NULL;
2901 	rightX2 = INT_MAX;
2902     }
2903 
2904     while (leftX < maxX) {
2905 	matchLeft = (chunkPtr2 != NULL)
2906 		&& SAME_BACKGROUND(chunkPtr2->stylePtr, chunkPtr->stylePtr);
2907 	sValuePtr = chunkPtr->stylePtr->sValuePtr;
2908 	if (rightX <= rightX2) {
2909 	    if ((chunkPtr->nextPtr == NULL)
2910 		    || !SAME_BACKGROUND(chunkPtr->stylePtr,
2911 		    chunkPtr->nextPtr->stylePtr)) {
2912 		if (!matchLeft && (sValuePtr->relief != TK_RELIEF_FLAT)) {
2913 		    Tk_3DHorizontalBevel(textPtr->tkwin, pixmap,
2914 			    sValuePtr->border, leftX + xOffset,
2915 			    y + dlPtr->height - sValuePtr->borderWidth,
2916 			    rightX - leftX, sValuePtr->borderWidth, leftXIn,
2917 			    0, 0, sValuePtr->relief);
2918 		}
2919 		leftX = rightX;
2920 		leftXIn = 0;
2921 		if ((rightX == rightX2) && (chunkPtr2 != NULL)) {
2922 		    goto nextChunk2b;
2923 		}
2924 	    }
2925 	    chunkPtr = chunkPtr->nextPtr;
2926 	    if (chunkPtr == NULL) {
2927 		break;
2928 	    }
2929 	    rightX = chunkPtr->x + chunkPtr->width;
2930 	    if ((chunkPtr->nextPtr == NULL) && (rightX < maxX)) {
2931 		rightX = maxX;
2932 	    }
2933 	    continue;
2934 	}
2935 
2936 	matchRight = (nextPtr2 != NULL)
2937 		&& SAME_BACKGROUND(nextPtr2->stylePtr, chunkPtr->stylePtr);
2938 	if (matchLeft && !matchRight) {
2939             bw = sValuePtr->borderWidth;
2940             if (rightX2 - sValuePtr->borderWidth < leftX) {
2941                 bw = rightX2 - leftX;
2942             }
2943 	    if (sValuePtr->relief != TK_RELIEF_FLAT) {
2944 		Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border,
2945 			rightX2 - bw + xOffset,
2946 			y + dlPtr->height - sValuePtr->borderWidth,
2947 			bw, sValuePtr->borderWidth, 0, sValuePtr->relief);
2948 	    }
2949 	    leftX = rightX2 - bw;
2950 	    leftXIn = 1;
2951 	} else if (!matchLeft && matchRight
2952 		&& (sValuePtr->relief != TK_RELIEF_FLAT)) {
2953             bw = sValuePtr->borderWidth;
2954             if (rightX2 + sValuePtr->borderWidth > rightX) {
2955                 bw = rightX - rightX2;
2956             }
2957 	    Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border,
2958 		    rightX2 + xOffset,
2959 		    y + dlPtr->height - sValuePtr->borderWidth, bw,
2960 		    sValuePtr->borderWidth, 1, sValuePtr->relief);
2961 	    Tk_3DHorizontalBevel(textPtr->tkwin, pixmap, sValuePtr->border,
2962 		    leftX + xOffset,
2963 		    y + dlPtr->height - sValuePtr->borderWidth,
2964 		    rightX2 + bw - leftX, sValuePtr->borderWidth, leftXIn,
2965 		    1, 0, sValuePtr->relief);
2966 	}
2967 
2968     nextChunk2b:
2969 	chunkPtr2 = nextPtr2;
2970 	if (chunkPtr2 == NULL) {
2971 	    rightX2 = INT_MAX;
2972 	} else {
2973 	    nextPtr2 = chunkPtr2->nextPtr;
2974 	    rightX2 = chunkPtr2->x + chunkPtr2->width;
2975 	    if (nextPtr2 == NULL) {
2976 		rightX2 = INT_MAX;
2977 	    }
2978 	}
2979     }
2980 }
2981 
2982 /*
2983  *----------------------------------------------------------------------
2984  *
2985  * AsyncUpdateLineMetrics --
2986  *
2987  *	This function is invoked as a background handler to update the pixel-
2988  *	height calculations of individual lines in an asychronous manner.
2989  *
2990  *	Currently a timer-handler is used for this purpose, which continuously
2991  *	reschedules itself. It may well be better to use some other approach
2992  *	(e.g., a background thread). We can't use an idle-callback because of
2993  *	a known bug in Tcl/Tk in which idle callbacks are not allowed to
2994  *	re-schedule themselves. This just causes an effective infinite loop.
2995  *
2996  * Results:
2997  *	None.
2998  *
2999  * Side effects:
3000  *	Line heights may be recalculated.
3001  *
3002  *----------------------------------------------------------------------
3003  */
3004 
3005 static void
AsyncUpdateLineMetrics(ClientData clientData)3006 AsyncUpdateLineMetrics(
3007     ClientData clientData)	/* Information about widget. */
3008 {
3009     TkText *textPtr = (TkText *)clientData;
3010     TextDInfo *dInfoPtr = textPtr->dInfoPtr;
3011     int lineNum;
3012 
3013     dInfoPtr->lineUpdateTimer = NULL;
3014 
3015     if ((textPtr->tkwin == NULL) || (textPtr->flags & DESTROYED)
3016             || !Tk_IsMapped(textPtr->tkwin)) {
3017 	/*
3018 	 * The widget has been deleted, or is not mapped. Don't do anything.
3019 	 */
3020 
3021 	if (textPtr->refCount-- <= 1) {
3022 	    ckfree(textPtr);
3023 	}
3024 	return;
3025     }
3026 
3027     if (dInfoPtr->flags & REDRAW_PENDING) {
3028 	dInfoPtr->lineUpdateTimer = Tcl_CreateTimerHandler(1,
3029 		AsyncUpdateLineMetrics, clientData);
3030 	return;
3031     }
3032 
3033     /*
3034      * Reify where we end or all hell breaks loose with the calculations when
3035      * we try to update. [Bug 2677890]
3036      */
3037 
3038     lineNum = dInfoPtr->currentMetricUpdateLine;
3039     if (dInfoPtr->lastMetricUpdateLine == -1) {
3040 	dInfoPtr->lastMetricUpdateLine =
3041 		TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr);
3042     }
3043 
3044     /*
3045      * Update the lines in blocks of about 24 recalculations, or 250+ lines
3046      * examined, so we pass in 256 for 'doThisMuch'.
3047      */
3048 
3049     lineNum = TkTextUpdateLineMetrics(textPtr, lineNum,
3050 	    dInfoPtr->lastMetricUpdateLine, 256);
3051 
3052     dInfoPtr->currentMetricUpdateLine = lineNum;
3053 
3054     if (tkTextDebug) {
3055 	char buffer[2 * TCL_INTEGER_SPACE + 1];
3056 
3057 	sprintf(buffer, "%d %d", lineNum, dInfoPtr->lastMetricUpdateLine);
3058 	LOG("tk_textInvalidateLine", buffer);
3059     }
3060 
3061     /*
3062      * If we're not in the middle of a long-line calculation (metricEpoch==-1)
3063      * and we've reached the last line, then we're done.
3064      */
3065 
3066     if (dInfoPtr->metricEpoch == -1
3067 	    && lineNum == dInfoPtr->lastMetricUpdateLine) {
3068 	/*
3069 	 * We have looped over all lines, so we're done. We must release our
3070 	 * refCount on the widget (the timer token was already set to NULL
3071 	 * above). If there is a registered aftersync command, run that first.
3072 	 * Cancel any pending idle task which would try to run the command
3073 	 * after the afterSyncCmd pointer had been set to NULL.
3074 	 */
3075 
3076         if (textPtr->afterSyncCmd) {
3077             int code;
3078 	    Tcl_CancelIdleCall(TkTextRunAfterSyncCmd, textPtr);
3079             Tcl_Preserve((ClientData) textPtr->interp);
3080             code = Tcl_EvalObjEx(textPtr->interp, textPtr->afterSyncCmd,
3081                     TCL_EVAL_GLOBAL);
3082 	    if (code == TCL_ERROR) {
3083                 Tcl_AddErrorInfo(textPtr->interp, "\n    (text sync)");
3084                 Tcl_BackgroundError(textPtr->interp);
3085 	    }
3086             Tcl_Release((ClientData) textPtr->interp);
3087             Tcl_DecrRefCount(textPtr->afterSyncCmd);
3088             textPtr->afterSyncCmd = NULL;
3089 	}
3090 
3091         /*
3092          * Fire the <<WidgetViewSync>> event since the widget view is in sync
3093          * with its internal data (actually it will be after the next trip
3094          * through the event loop, because the widget redraws at idle-time).
3095          */
3096         GenerateWidgetViewSyncEvent(textPtr, 1);
3097 
3098 	if (textPtr->refCount-- <= 1) {
3099 	    ckfree(textPtr);
3100 	}
3101 	return;
3102     }
3103 
3104     /*
3105      * Re-arm the timer. We already have a refCount on the text widget so no
3106      * need to adjust that.
3107      */
3108 
3109     dInfoPtr->lineUpdateTimer = Tcl_CreateTimerHandler(1,
3110 	    AsyncUpdateLineMetrics, textPtr);
3111 }
3112 
3113 /*
3114  *----------------------------------------------------------------------
3115  *
3116  * GenerateWidgetViewSyncEvent --
3117  *
3118  *      Send the <<WidgetViewSync>> event related to the text widget
3119  *      line metrics asynchronous update.  These events should only
3120  *      be sent when the sync status has changed.  So this function
3121  *      compares the requested state with the state saved in the
3122  *      TkText structure, and only generates the event if they are
3123  *      different.  This means that it is safe to call this function
3124  *      at any time when the state is known.
3125  *
3126  *      If an event is sent, the effect is equivalent to:
3127  *         event generate $textWidget <<WidgetViewSync>> -data $s
3128  *      where $s is the sync status: true (when the widget view is in
3129  *      sync with its internal data) or false (when it is not).
3130  *
3131  * Results:
3132  *      None
3133  *
3134  * Side effects:
3135  *      If corresponding bindings are present, they will trigger.
3136  *
3137  *----------------------------------------------------------------------
3138  */
3139 
3140 static void
GenerateWidgetViewSyncEvent(TkText * textPtr,Bool InSync)3141 GenerateWidgetViewSyncEvent(
3142     TkText *textPtr,	  /* Information about text widget. */
3143     Bool InSync)          /* true if becoming in sync, false otherwise */
3144 {
3145     Bool NewSyncState = (InSync != 0); /* ensure 0 or 1 value */
3146     Bool OldSyncState = !(textPtr->dInfoPtr->flags & OUT_OF_SYNC);
3147 
3148     /*
3149      * OSX 10.14 needs to be told to display the window when the Text Widget
3150      * is in sync.  (That is, to run DisplayText inside of the drawRect
3151      * method.)  Otherwise the screen might not get updated until an event
3152      * like a mouse click is received.  But that extra drawing corrupts the
3153      * data that the test suite is trying to collect.
3154      */
3155 
3156     if (!tkTextDebug) {
3157 	TkpRedrawWidget(textPtr->tkwin);
3158     }
3159 
3160     if (NewSyncState != OldSyncState) {
3161 	if (NewSyncState) {
3162 	    textPtr->dInfoPtr->flags &= ~OUT_OF_SYNC;
3163 	} else {
3164 	    textPtr->dInfoPtr->flags |= OUT_OF_SYNC;
3165 	}
3166 	TkSendVirtualEvent(textPtr->tkwin, "WidgetViewSync",
3167 		Tcl_NewBooleanObj(NewSyncState));
3168     }
3169 }
3170 
3171 /*
3172  *----------------------------------------------------------------------
3173  *
3174  * TkTextUpdateLineMetrics --
3175  *
3176  *	This function updates the pixel height calculations of a range of
3177  *	lines in the widget. The range is from lineNum to endLine, but, if
3178  *	doThisMuch is positive, then the function may return earlier, once a
3179  *	certain number of lines has been examined. The line counts are from 0.
3180  *
3181  *	If doThisMuch is -1, then all lines in the range will be updated. This
3182  *	will potentially take quite some time for a large text widget.
3183  *
3184  *	Note: with bad input for lineNum and endLine, this function can loop
3185  *	indefinitely.
3186  *
3187  * Results:
3188  *	The index of the last line examined (or -1 if we are about to wrap
3189  *	around from end to beginning of the widget, and the next line will be
3190  *	the first line).
3191  *
3192  * Side effects:
3193  *	Line heights may be recalculated.
3194  *
3195  *----------------------------------------------------------------------
3196  */
3197 
3198 int
TkTextUpdateLineMetrics(TkText * textPtr,int lineNum,int endLine,int doThisMuch)3199 TkTextUpdateLineMetrics(
3200     TkText *textPtr,		/* Information about widget. */
3201     int lineNum,		/* Start at this line. */
3202     int endLine,		/* Go no further than this line. */
3203     int doThisMuch)		/* How many lines to check, or how many 10s of
3204 				 * lines to recalculate. If '-1' then do
3205 				 * everything in the range (which may take a
3206 				 * while). */
3207 {
3208     TkTextLine *linePtr = NULL;
3209     int count = 0;
3210     int totalLines = TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr);
3211     int fullUpdateRequested = (lineNum == 0 &&
3212                                endLine == totalLines &&
3213                                doThisMuch == -1);
3214 
3215     if (totalLines == 0) {
3216 	/*
3217 	 * Empty peer widget.
3218 	 */
3219 
3220 	return endLine;
3221     }
3222 
3223     while (1) {
3224 
3225 	/*
3226 	 * Get a suitable line.
3227 	 */
3228 
3229 	if (lineNum == -1 && linePtr == NULL) {
3230 	    lineNum = 0;
3231 	    linePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree, textPtr,
3232 		    lineNum);
3233 	} else {
3234 	    if (lineNum == -1 || linePtr == NULL) {
3235 		if (lineNum == -1) {
3236 		    lineNum = 0;
3237 		}
3238 		linePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree,
3239 			textPtr, lineNum);
3240 	    } else {
3241 		lineNum++;
3242 		linePtr = TkBTreeNextLine(textPtr, linePtr);
3243 	    }
3244 
3245 	    /*
3246 	     * If we're in the middle of a partial-line height calculation,
3247 	     * then we can't be done.
3248 	     */
3249 
3250 	    if (textPtr->dInfoPtr->metricEpoch == -1 && lineNum == endLine) {
3251 
3252 		/*
3253 		 * We have looped over all lines, so we're done.
3254 		 */
3255 
3256 		break;
3257 	    }
3258 	}
3259 
3260 	if (lineNum < totalLines) {
3261 	    if (tkTextDebug) {
3262 		char buffer[4 * TCL_INTEGER_SPACE + 3];
3263 
3264 		sprintf(buffer, "%d %d %d %d",
3265 			lineNum, endLine, totalLines, count);
3266 		LOG("tk_textInvalidateLine", buffer);
3267 	    }
3268 
3269 	    /*
3270 	     * Now update the line's metrics if necessary.
3271 	     */
3272 
3273 	    if (TkBTreeLinePixelEpoch(textPtr, linePtr)
3274 		    == textPtr->dInfoPtr->lineMetricUpdateEpoch) {
3275 
3276 		/*
3277 		 * This line is already up to date. That means there's nothing
3278 		 * to do here.
3279 		 */
3280 
3281 	    } else if (doThisMuch == -1) {
3282 		count += 8 * TkTextUpdateOneLine(textPtr, linePtr, 0,NULL,0);
3283 	    } else {
3284 		TkTextIndex index;
3285 		TkTextIndex *indexPtr;
3286 		int pixelHeight;
3287 
3288 		/*
3289 		 * If the metric epoch is the same as the widget's epoch, then
3290 		 * we know that indexPtrs are still valid, and if the cached
3291 		 * metricIndex (if any) is for the same line as we wish to
3292 		 * examine, then we are looking at a long line wrapped many
3293 		 * times, which we will examine in pieces.
3294 		 */
3295 
3296 		if (textPtr->dInfoPtr->metricEpoch ==
3297 			textPtr->sharedTextPtr->stateEpoch &&
3298 			textPtr->dInfoPtr->metricIndex.linePtr==linePtr) {
3299 		    indexPtr = &textPtr->dInfoPtr->metricIndex;
3300 		    pixelHeight = textPtr->dInfoPtr->metricPixelHeight;
3301 		} else {
3302 
3303 		    /*
3304 		     * We must reset the partial line height calculation data
3305 		     * here, so we don't use it when it is out of date.
3306 		     */
3307 
3308 		    textPtr->dInfoPtr->metricEpoch = -1;
3309 		    index.tree = textPtr->sharedTextPtr->tree;
3310 		    index.linePtr = linePtr;
3311 		    index.byteIndex = 0;
3312 		    index.textPtr = NULL;
3313 		    indexPtr = &index;
3314 		    pixelHeight = 0;
3315 		}
3316 
3317 		/*
3318 		 * Update the line and update the counter, counting 8 for each
3319 		 * display line we actually re-layout.
3320 		 */
3321 
3322 		count += 8 * TkTextUpdateOneLine(textPtr, linePtr,
3323 			pixelHeight, indexPtr, 1);
3324 
3325 		if (indexPtr->linePtr == linePtr) {
3326 
3327 		    /*
3328 		     * We didn't complete the logical line, because it
3329 		     * produced very many display lines, which must be because
3330 		     * it must be a long line wrapped many times. So we must
3331 		     * cache as far as we got for next time around.
3332 		     */
3333 
3334 		    if (pixelHeight == 0) {
3335 
3336 			/*
3337 			 * These have already been stored, unless we just
3338 			 * started the new line.
3339 			 */
3340 
3341 			textPtr->dInfoPtr->metricIndex = index;
3342 			textPtr->dInfoPtr->metricEpoch =
3343 				textPtr->sharedTextPtr->stateEpoch;
3344 		    }
3345 		    textPtr->dInfoPtr->metricPixelHeight =
3346 			    TkBTreeLinePixelCount(textPtr, linePtr);
3347 		    break;
3348 		}
3349 
3350 		/*
3351 		 * We're done with this long line.
3352 		 */
3353 
3354 		textPtr->dInfoPtr->metricEpoch = -1;
3355 	    }
3356 	} else {
3357 
3358 	    /*
3359 	     * We must never recalculate the height of the last artificial
3360 	     * line. It must stay at zero, and if we recalculate it, it will
3361 	     * change.
3362 	     */
3363 
3364 	    if (endLine >= totalLines) {
3365 		lineNum = endLine;
3366 		break;
3367 	    }
3368 
3369 	    /*
3370 	     * Set things up for the next loop through.
3371 	     */
3372 
3373 	    lineNum = -1;
3374 	}
3375 	count++;
3376 
3377 	if (doThisMuch != -1 && count >= doThisMuch) {
3378 	    break;
3379 	}
3380     }
3381     if (doThisMuch == -1) {
3382 
3383 	/*
3384 	 * If we were requested to update the entire range, then also update
3385 	 * the scrollbar.
3386 	 */
3387 
3388 	GetYView(textPtr->interp, textPtr, 1);
3389     }
3390     if (fullUpdateRequested) {
3391         GenerateWidgetViewSyncEvent(textPtr, 1);
3392     }
3393     return lineNum;
3394 }
3395 
3396 /*
3397  *----------------------------------------------------------------------
3398  *
3399  * TkTextInvalidateLineMetrics, TextInvalidateLineMetrics --
3400  *
3401  *	Mark a number of text lines as having invalid line metric
3402  *	calculations. Never call this with linePtr as the last (artificial)
3403  *	line in the text. Depending on 'action' which indicates whether the
3404  *	given lines are simply invalid or have been inserted or deleted, the
3405  *	pre-existing asynchronous line update range may need to be adjusted.
3406  *
3407  *	If linePtr is NULL then 'lineCount' and 'action' are ignored and all
3408  *	lines are invalidated.
3409  *
3410  * Results:
3411  *	None.
3412  *
3413  * Side effects:
3414  *	May schedule an asychronous callback.
3415  *
3416  *----------------------------------------------------------------------
3417  */
3418 
3419 void
TkTextInvalidateLineMetrics(TkSharedText * sharedTextPtr,TkText * textPtr,TkTextLine * linePtr,int lineCount,int action)3420 TkTextInvalidateLineMetrics(
3421     TkSharedText *sharedTextPtr,/* Shared widget section for all peers, or
3422 				 * NULL. */
3423     TkText *textPtr,		/* Widget record for text widget. */
3424     TkTextLine *linePtr,	/* Invalidation starts from this line. */
3425     int lineCount,		/* And includes this many following lines. */
3426     int action)			/* Indicates what type of invalidation
3427 				 * occurred (insert, delete, or simple). */
3428 {
3429     if (sharedTextPtr == NULL) {
3430 	TextInvalidateLineMetrics(textPtr, linePtr, lineCount, action);
3431     } else {
3432 	textPtr = sharedTextPtr->peers;
3433 	while (textPtr != NULL) {
3434 	    TextInvalidateLineMetrics(textPtr, linePtr, lineCount, action);
3435 	    textPtr = textPtr->next;
3436 	}
3437     }
3438 }
3439 
3440 static void
TextInvalidateLineMetrics(TkText * textPtr,TkTextLine * linePtr,int lineCount,int action)3441 TextInvalidateLineMetrics(
3442     TkText *textPtr,		/* Widget record for text widget. */
3443     TkTextLine *linePtr,	/* Invalidation starts from this line. */
3444     int lineCount,		/* And includes this many following lines. */
3445     int action)			/* Indicates what type of invalidation
3446 				 * occurred (insert, delete, or simple). */
3447 {
3448     int fromLine;
3449     TextDInfo *dInfoPtr = textPtr->dInfoPtr;
3450 
3451     if (linePtr != NULL) {
3452 	int counter = lineCount;
3453 
3454 	fromLine = TkBTreeLinesTo(textPtr, linePtr);
3455 
3456 	/*
3457 	 * Invalidate the height calculations of each line in the given range.
3458 	 */
3459 
3460 	TkBTreeLinePixelEpoch(textPtr, linePtr) = 0;
3461 	while (counter > 0 && linePtr != NULL) {
3462 	    linePtr = TkBTreeNextLine(textPtr, linePtr);
3463 	    if (linePtr != NULL) {
3464 		TkBTreeLinePixelEpoch(textPtr, linePtr) = 0;
3465 	    }
3466 	    counter--;
3467 	}
3468 
3469 	/*
3470 	 * Now schedule an examination of each line in the union of the old
3471 	 * and new update ranges, including the (possibly empty) range in
3472 	 * between. If that between range is not-empty, then we are examining
3473 	 * more lines than is strictly necessary (but the examination of the
3474 	 * extra lines should be quick, since their pixelCalculationEpoch will
3475 	 * be up to date). However, to keep track of that would require more
3476 	 * complex record-keeping than what we have.
3477 	 */
3478 
3479 	if (dInfoPtr->lineUpdateTimer == NULL) {
3480 	    dInfoPtr->currentMetricUpdateLine = fromLine;
3481 	    if (action == TK_TEXT_INVALIDATE_DELETE) {
3482 		lineCount = 0;
3483 	    }
3484 	    dInfoPtr->lastMetricUpdateLine = fromLine + lineCount + 1;
3485 	} else {
3486 	    int toLine = fromLine + lineCount + 1;
3487 
3488 	    if (action == TK_TEXT_INVALIDATE_DELETE) {
3489 		if (toLine <= dInfoPtr->currentMetricUpdateLine) {
3490 		    dInfoPtr->currentMetricUpdateLine = fromLine;
3491 		    if (dInfoPtr->lastMetricUpdateLine != -1) {
3492 			dInfoPtr->lastMetricUpdateLine -= lineCount;
3493 		    }
3494 		} else if (fromLine <= dInfoPtr->currentMetricUpdateLine) {
3495 		    dInfoPtr->currentMetricUpdateLine = fromLine;
3496 		    if (toLine <= dInfoPtr->lastMetricUpdateLine) {
3497 			dInfoPtr->lastMetricUpdateLine -= lineCount;
3498 		    }
3499 		} else {
3500 		    if (dInfoPtr->lastMetricUpdateLine != -1) {
3501 			dInfoPtr->lastMetricUpdateLine = toLine;
3502 		    }
3503 		}
3504 	    } else if (action == TK_TEXT_INVALIDATE_INSERT) {
3505 		if (toLine <= dInfoPtr->currentMetricUpdateLine) {
3506 		    dInfoPtr->currentMetricUpdateLine = fromLine;
3507 		    if (dInfoPtr->lastMetricUpdateLine != -1) {
3508 			dInfoPtr->lastMetricUpdateLine += lineCount;
3509 		    }
3510 		} else if (fromLine <= dInfoPtr->currentMetricUpdateLine) {
3511 		    dInfoPtr->currentMetricUpdateLine = fromLine;
3512 		    if (toLine <= dInfoPtr->lastMetricUpdateLine) {
3513 			dInfoPtr->lastMetricUpdateLine += lineCount;
3514 		    }
3515 		    if (toLine > dInfoPtr->lastMetricUpdateLine) {
3516 			dInfoPtr->lastMetricUpdateLine = toLine;
3517 		    }
3518 		} else {
3519 		    if (dInfoPtr->lastMetricUpdateLine != -1) {
3520 			dInfoPtr->lastMetricUpdateLine = toLine;
3521 		    }
3522 		}
3523 	    } else {
3524 		if (fromLine < dInfoPtr->currentMetricUpdateLine) {
3525 		    dInfoPtr->currentMetricUpdateLine = fromLine;
3526 		}
3527 		if (dInfoPtr->lastMetricUpdateLine != -1
3528 			&& toLine > dInfoPtr->lastMetricUpdateLine) {
3529 		    dInfoPtr->lastMetricUpdateLine = toLine;
3530 		}
3531 	    }
3532 	}
3533     } else {
3534 	/*
3535 	 * This invalidates the height of all lines in the widget.
3536 	 */
3537 
3538 	if ((++dInfoPtr->lineMetricUpdateEpoch) == 0) {
3539 	    dInfoPtr->lineMetricUpdateEpoch++;
3540 	}
3541 
3542 	/*
3543 	 * This has the effect of forcing an entire new loop of update checks
3544 	 * on all lines in the widget.
3545 	 */
3546 
3547 	if (dInfoPtr->lineUpdateTimer == NULL) {
3548 	    dInfoPtr->currentMetricUpdateLine = -1;
3549 	}
3550 	dInfoPtr->lastMetricUpdateLine = dInfoPtr->currentMetricUpdateLine;
3551     }
3552 
3553     /*
3554      * Now re-set the current update calculations.
3555      */
3556 
3557     if (dInfoPtr->lineUpdateTimer == NULL) {
3558 	textPtr->refCount++;
3559 	dInfoPtr->lineUpdateTimer = Tcl_CreateTimerHandler(1,
3560 		AsyncUpdateLineMetrics, textPtr);
3561     }
3562 
3563     /*
3564      * The widget is out of sync: send a <<WidgetViewSync>> event.
3565      */
3566     GenerateWidgetViewSyncEvent(textPtr, 0);
3567 }
3568 
3569 /*
3570  *----------------------------------------------------------------------
3571  *
3572  * TkTextFindDisplayLineEnd --
3573  *
3574  *	This function is invoked to find the index of the beginning or end of
3575  *	the particular display line on which the given index sits, whether
3576  *	that line is displayed or not.
3577  *
3578  *	If 'end' is zero, we look for the start, and if 'end' is one we look
3579  *	for the end.
3580  *
3581  *	If the beginning of the current display line is elided, and we are
3582  *	looking for the start of the line, then the returned index will be the
3583  *	first elided index on the display line.
3584  *
3585  *	Similarly if the end of the current display line is elided and we are
3586  *	looking for the end, then the returned index will be the last elided
3587  *	index on the display line.
3588  *
3589  * Results:
3590  *	Modifies indexPtr to point to the given end.
3591  *
3592  *	If xOffset is non-NULL, it is set to the x-pixel offset of the given
3593  *	original index within the given display line.
3594  *
3595  * Side effects:
3596  *	The combination of 'LayoutDLine' and 'FreeDLines' seems like a rather
3597  *	time-consuming way of gathering the information we need, so this would
3598  *	be a good place to look to speed up the calculations. In particular
3599  *	these calls will map and unmap embedded windows respectively, which I
3600  *	would hope isn't exactly necessary!
3601  *
3602  *----------------------------------------------------------------------
3603  */
3604 
3605 void
TkTextFindDisplayLineEnd(TkText * textPtr,TkTextIndex * indexPtr,int end,int * xOffset)3606 TkTextFindDisplayLineEnd(
3607     TkText *textPtr,		/* Widget record for text widget. */
3608     TkTextIndex *indexPtr,	/* Index we will adjust to the display line
3609 				 * start or end. */
3610     int end,			/* 0 = start, 1 = end. */
3611     int *xOffset)		/* NULL, or used to store the x-pixel offset
3612 				 * of the original index within its display
3613 				 * line. */
3614 {
3615     TkTextIndex index;
3616 
3617     if (!end && IsStartOfNotMergedLine(textPtr, indexPtr)) {
3618 	/*
3619 	 * Nothing to do.
3620 	 */
3621 
3622 	if (xOffset != NULL) {
3623 	    *xOffset = 0;
3624 	}
3625 	return;
3626     }
3627 
3628     index = *indexPtr;
3629     index.byteIndex = 0;
3630     index.textPtr = NULL;
3631 
3632     while (1) {
3633 	TkTextIndex endOfLastLine;
3634 
3635 	if (TkTextIndexBackBytes(textPtr, &index, 1, &endOfLastLine)) {
3636 	    /*
3637 	     * Reached beginning of text.
3638 	     */
3639 
3640 	    break;
3641 	}
3642 
3643 	if (!TkTextIsElided(textPtr, &endOfLastLine, NULL)) {
3644 	    /*
3645 	     * The eol is not elided, so 'index' points to the start of a
3646 	     * display line (as well as logical line).
3647 	     */
3648 
3649 	    break;
3650 	}
3651 
3652 	/*
3653 	 * indexPtr's logical line is actually merged with the previous
3654 	 * logical line whose eol is elided. Continue searching back to get a
3655 	 * real line start.
3656 	 */
3657 
3658 	index = endOfLastLine;
3659 	index.byteIndex = 0;
3660     }
3661 
3662     while (1) {
3663 	DLine *dlPtr;
3664 	int byteCount;
3665 	TkTextIndex nextLineStart;
3666 
3667 	dlPtr = LayoutDLine(textPtr, &index);
3668 	byteCount = dlPtr->byteCount;
3669 
3670 	TkTextIndexForwBytes(textPtr, &index, byteCount, &nextLineStart);
3671 
3672 	/*
3673 	 * 'byteCount' goes up to the beginning of the next display line, so
3674 	 * equality here says we need one more line. We try to perform a quick
3675 	 * comparison which is valid for the case where the logical line is
3676 	 * the same, but otherwise fall back on a full TkTextIndexCmp.
3677 	 */
3678 
3679 	if (((index.linePtr == indexPtr->linePtr)
3680 		&& (index.byteIndex + byteCount > indexPtr->byteIndex))
3681 		|| (dlPtr->logicalLinesMerged > 0
3682 		&& TkTextIndexCmp(&nextLineStart, indexPtr) > 0)) {
3683 	    /*
3684 	     * It's on this display line.
3685 	     */
3686 
3687 	    if (xOffset != NULL) {
3688 		/*
3689 		 * This call takes a byte index relative to the start of the
3690 		 * current _display_ line, not logical line. We are about to
3691 		 * overwrite indexPtr->byteIndex, so we must do this now.
3692 		 */
3693 
3694 		*xOffset = DlineXOfIndex(textPtr, dlPtr,
3695 			TkTextIndexCountBytes(textPtr, &dlPtr->index,
3696 			indexPtr));
3697 	    }
3698 	    if (end) {
3699 		/*
3700 		 * The index we want is one less than the number of bytes in
3701 		 * the display line.
3702 		 */
3703 
3704 		TkTextIndexBackBytes(textPtr, &nextLineStart, 1, indexPtr);
3705 	    } else {
3706 		*indexPtr = index;
3707 	    }
3708 	    FreeDLines(textPtr, dlPtr, NULL, DLINE_FREE_TEMP);
3709 	    return;
3710 	}
3711 
3712 	FreeDLines(textPtr, dlPtr, NULL, DLINE_FREE_TEMP);
3713 	index = nextLineStart;
3714     }
3715 }
3716 
3717 /*
3718  *----------------------------------------------------------------------
3719  *
3720  * CalculateDisplayLineHeight --
3721  *
3722  *	This function is invoked to recalculate the height of the particular
3723  *	display line which starts with the given index, whether that line is
3724  *	displayed or not.
3725  *
3726  *	This function does not, in itself, update any cached information about
3727  *	line heights. That should be done, where necessary, by its callers.
3728  *
3729  *	The behaviour of this function is _undefined_ if indexPtr is not
3730  *	currently at the beginning of a display line.
3731  *
3732  * Results:
3733  *	The number of vertical pixels used by the display line.
3734  *
3735  *	If 'byteCountPtr' is non-NULL, then returns in that pointer the number
3736  *	of byte indices on the given display line (which can be used to update
3737  *	indexPtr in a loop).
3738  *
3739  *	If 'mergedLinePtr' is non-NULL, then returns in that pointer the
3740  *	number of extra logical lines merged into the given display line.
3741  *
3742  * Side effects:
3743  *	The combination of 'LayoutDLine' and 'FreeDLines' seems like a rather
3744  *	time-consuming way of gathering the information we need, so this would
3745  *	be a good place to look to speed up the calculations. In particular
3746  *	these calls will map and unmap embedded windows respectively, which I
3747  *	would hope isn't exactly necessary!
3748  *
3749  *----------------------------------------------------------------------
3750  */
3751 
3752 static int
CalculateDisplayLineHeight(TkText * textPtr,const TkTextIndex * indexPtr,int * byteCountPtr,int * mergedLinePtr)3753 CalculateDisplayLineHeight(
3754     TkText *textPtr,		/* Widget record for text widget. */
3755     const TkTextIndex *indexPtr,/* The index at the beginning of the display
3756 				 * line of interest. */
3757     int *byteCountPtr,		/* NULL or used to return the number of byte
3758 				 * indices on the given display line. */
3759     int *mergedLinePtr)		/* NULL or used to return if the given display
3760 				 * line merges with a following logical line
3761 				 * (because the eol is elided). */
3762 {
3763     DLine *dlPtr;
3764     int pixelHeight;
3765 
3766     if (tkTextDebug) {
3767         int oldtkTextDebug = tkTextDebug;
3768         /*
3769          * Check that the indexPtr we are given really is at the start of a
3770          * display line. The gymnastics with tkTextDebug is to prevent
3771          * failure of a test suite test, that checks that lines are rendered
3772          * exactly once. TkTextFindDisplayLineEnd is used here for checking
3773          * indexPtr but it calls LayoutDLine/FreeDLine which makes the
3774          * counting wrong. The debug mode shall therefore be switched off
3775          * when calling TkTextFindDisplayLineEnd.
3776          */
3777 
3778         TkTextIndex indexPtr2 = *indexPtr;
3779         tkTextDebug = 0;
3780         TkTextFindDisplayLineEnd(textPtr, &indexPtr2, 0, NULL);
3781         tkTextDebug = oldtkTextDebug;
3782         if (TkTextIndexCmp(&indexPtr2,indexPtr) != 0) {
3783             Tcl_Panic("CalculateDisplayLineHeight called with bad indexPtr");
3784         }
3785     }
3786 
3787     /*
3788      * Special case for artificial last line. May be better to move this
3789      * inside LayoutDLine.
3790      */
3791 
3792     if (indexPtr->byteIndex == 0
3793 	    && TkBTreeNextLine(textPtr, indexPtr->linePtr) == NULL) {
3794 	if (byteCountPtr != NULL) {
3795 	    *byteCountPtr = 0;
3796 	}
3797 	if (mergedLinePtr != NULL) {
3798 	    *mergedLinePtr = 0;
3799 	}
3800 	return 0;
3801     }
3802 
3803     /*
3804      * Layout, find the information we need and then free the display-line we
3805      * laid-out. We must use 'FreeDLines' because it will actually call the
3806      * relevant code to unmap any embedded windows which were mapped in the
3807      * LayoutDLine call!
3808      */
3809 
3810     dlPtr = LayoutDLine(textPtr, indexPtr);
3811     pixelHeight = dlPtr->height;
3812     if (byteCountPtr != NULL) {
3813 	*byteCountPtr = dlPtr->byteCount;
3814     }
3815     if (mergedLinePtr != NULL) {
3816 	*mergedLinePtr = dlPtr->logicalLinesMerged;
3817     }
3818     FreeDLines(textPtr, dlPtr, NULL, DLINE_FREE_TEMP);
3819 
3820     return pixelHeight;
3821 }
3822 
3823 /*
3824  *----------------------------------------------------------------------
3825  *
3826  * TkTextIndexYPixels --
3827  *
3828  *	This function is invoked to calculate the number of vertical pixels
3829  *	between the first index of the text widget and the given index. The
3830  *	range from first logical line to given logical line is determined
3831  *	using the cached values, and the range inside the given logical line
3832  *	is calculated on the fly.
3833  *
3834  * Results:
3835  *	The pixel distance between first pixel in the widget and the
3836  *	top of the index's current display line (could be zero).
3837  *
3838  * Side effects:
3839  *	Just those of 'CalculateDisplayLineHeight'.
3840  *
3841  *----------------------------------------------------------------------
3842  */
3843 
3844 int
TkTextIndexYPixels(TkText * textPtr,const TkTextIndex * indexPtr)3845 TkTextIndexYPixels(
3846     TkText *textPtr,		/* Widget record for text widget. */
3847     const TkTextIndex *indexPtr)/* The index of which we want the pixel
3848 				 * distance from top of logical line to top of
3849 				 * index. */
3850 {
3851     int pixelHeight;
3852     TkTextIndex index;
3853     int alreadyStartOfLine = 1;
3854 
3855     /*
3856      * Find the index denoting the closest position being at the same time
3857      * the start of a logical line above indexPtr and the start of a display
3858      * line.
3859      */
3860 
3861     index = *indexPtr;
3862     while (1) {
3863         TkTextFindDisplayLineEnd(textPtr, &index, 0, NULL);
3864         if (index.byteIndex == 0) {
3865             break;
3866         }
3867         TkTextIndexBackBytes(textPtr, &index, 1, &index);
3868         alreadyStartOfLine = 0;
3869     }
3870 
3871     pixelHeight = TkBTreePixelsTo(textPtr, index.linePtr);
3872 
3873     /*
3874      * Shortcut to avoid layout of a superfluous display line. We know there
3875      * is nothing more to add up to the height if the index we were given was
3876      * already on the first display line of a logical line.
3877      */
3878 
3879     if (alreadyStartOfLine) {
3880         return pixelHeight;
3881     }
3882 
3883     /*
3884      * Iterate through display lines, starting at the logical line belonging
3885      * to index, adding up the pixel height of each such display line as we
3886      * go along, until we go past 'indexPtr'.
3887      */
3888 
3889     while (1) {
3890 	int bytes, height, compare;
3891 
3892 	/*
3893 	 * Currently this call doesn't have many side-effects. However, if in
3894 	 * the future we change the code so there are side-effects (such as
3895 	 * adjusting linePtr->pixelHeight), then the code might not quite work
3896 	 * as intended, specifically the 'linePtr->pixelHeight == pixelHeight'
3897 	 * test below this while loop.
3898 	 */
3899 
3900 	height = CalculateDisplayLineHeight(textPtr, &index, &bytes, NULL);
3901 
3902         TkTextIndexForwBytes(textPtr, &index, bytes, &index);
3903 
3904         compare = TkTextIndexCmp(&index,indexPtr);
3905         if (compare > 0) {
3906 	    return pixelHeight;
3907 	}
3908 
3909 	if (height > 0) {
3910 	    pixelHeight += height;
3911 	}
3912 
3913         if (compare == 0) {
3914 	    return pixelHeight;
3915 	}
3916     }
3917 }
3918 
3919 /*
3920  *----------------------------------------------------------------------
3921  *
3922  * TkTextUpdateOneLine --
3923  *
3924  *	This function is invoked to recalculate the height of a particular
3925  *	logical line, whether that line is displayed or not.
3926  *
3927  *	It must NEVER be called for the artificial last TkTextLine which is
3928  *	used internally for administrative purposes only. That line must
3929  *	retain its initial height of 0 otherwise the pixel height calculation
3930  *	maintained by the B-tree will be wrong.
3931  *
3932  * Results:
3933  *	The number of display lines in the logical line. This could be zero if
3934  *	the line is totally elided.
3935  *
3936  * Side effects:
3937  *	Line heights may be recalculated, and a timer to update the scrollbar
3938  *	may be installed. Also see the called function
3939  *	CalculateDisplayLineHeight for its side effects.
3940  *
3941  *----------------------------------------------------------------------
3942  */
3943 
3944 int
TkTextUpdateOneLine(TkText * textPtr,TkTextLine * linePtr,int pixelHeight,TkTextIndex * indexPtr,int partialCalc)3945 TkTextUpdateOneLine(
3946     TkText *textPtr,		/* Widget record for text widget. */
3947     TkTextLine *linePtr,	/* The line of which to calculate the
3948 				 * height. */
3949     int pixelHeight,		/* If indexPtr is non-NULL, then this is the
3950 				 * number of pixels in the logical line
3951 				 * linePtr, up to the index which has been
3952 				 * given. */
3953     TkTextIndex *indexPtr,	/* Either NULL or an index at the start of a
3954 				 * display line belonging to linePtr, at which
3955 				 * we wish to start (e.g. up to which we have
3956 				 * already calculated). On return this will be
3957 				 * set to the first index on the next line. */
3958     int partialCalc)		/* Set to 1 if we are allowed to do partial
3959 				 * height calculations of long-lines. In this
3960 				 * case we'll only return what we know so
3961 				 * far. */
3962 {
3963     TkTextIndex index;
3964     int displayLines;
3965     int mergedLines;
3966 
3967     if (indexPtr == NULL) {
3968 	index.tree = textPtr->sharedTextPtr->tree;
3969 	index.linePtr = linePtr;
3970 	index.byteIndex = 0;
3971 	index.textPtr = NULL;
3972 	indexPtr = &index;
3973 	pixelHeight = 0;
3974     }
3975 
3976     /*
3977      * CalculateDisplayLineHeight _must_ be called (below) with an index at
3978      * the beginning of a display line. Force this to happen. This is needed
3979      * when TkTextUpdateOneLine is called with a line that is merged with its
3980      * previous line: the number of merged logical lines in a display line is
3981      * calculated correctly only when CalculateDisplayLineHeight receives
3982      * an index at the beginning of a display line. In turn this causes the
3983      * merged lines to receive their correct zero pixel height in
3984      * TkBTreeAdjustPixelHeight.
3985      */
3986 
3987     TkTextFindDisplayLineEnd(textPtr, indexPtr, 0, NULL);
3988     linePtr = indexPtr->linePtr;
3989 
3990     /*
3991      * Iterate through all display-lines corresponding to the single logical
3992      * line 'linePtr' (and lines merged into this line due to eol elision),
3993      * adding up the pixel height of each such display line as we go along.
3994      * The final total is, therefore, the total height of all display lines
3995      * made up by the logical line 'linePtr' and subsequent logical lines
3996      * merged into this line.
3997      */
3998 
3999     displayLines = 0;
4000     mergedLines = 0;
4001 
4002     while (1) {
4003 	int bytes, height, logicalLines;
4004 
4005 	/*
4006 	 * Currently this call doesn't have many side-effects. However, if in
4007 	 * the future we change the code so there are side-effects (such as
4008 	 * adjusting linePtr->pixelHeight), then the code might not quite work
4009 	 * as intended, specifically the 'linePtr->pixelHeight == pixelHeight'
4010 	 * test below this while loop.
4011 	 */
4012 
4013         height = CalculateDisplayLineHeight(textPtr, indexPtr, &bytes,
4014 		&logicalLines);
4015 
4016 	if (height > 0) {
4017 	    pixelHeight += height;
4018 	    displayLines++;
4019 	}
4020 
4021 	mergedLines += logicalLines;
4022 
4023 	if (TkTextIndexForwBytes(textPtr, indexPtr, bytes, indexPtr)) {
4024 	    break;
4025 	}
4026 
4027         if (mergedLines == 0) {
4028             if (indexPtr->linePtr != linePtr) {
4029                 /*
4030                  * If we reached the end of the logical line, then either way
4031                  * we don't have a partial calculation.
4032                  */
4033 
4034                 partialCalc = 0;
4035                 break;
4036             }
4037         } else {
4038             if (IsStartOfNotMergedLine(textPtr, indexPtr)) {
4039                 /*
4040                  * We've ended a logical line.
4041                  */
4042 
4043                 partialCalc = 0;
4044                 break;
4045             }
4046 
4047             /*
4048              * We must still be on the same wrapped line, on a new logical
4049              * line merged with the logical line 'linePtr'.
4050              */
4051         }
4052 	if (partialCalc && displayLines > 50 && mergedLines == 0) {
4053 	    /*
4054 	     * Only calculate 50 display lines at a time, to avoid huge
4055 	     * delays. In any case it is very rare that a single line wraps 50
4056 	     * times!
4057 	     *
4058 	     * If we have any merged lines, we must complete the full logical
4059 	     * line layout here and now, because the partial-calculation code
4060 	     * isn't designed to handle merged logical lines. Hence the
4061 	     * 'mergedLines == 0' check.
4062 	     */
4063 
4064 	    break;
4065 	}
4066     }
4067 
4068     if (!partialCalc) {
4069 	int changed = 0;
4070 
4071 	/*
4072 	 * Cancel any partial line height calculation state.
4073 	 */
4074 
4075 	textPtr->dInfoPtr->metricEpoch = -1;
4076 
4077 	/*
4078 	 * Mark the logical line as being up to date (caution: it isn't yet up
4079 	 * to date, that will happen in TkBTreeAdjustPixelHeight just below).
4080 	 */
4081 
4082 	TkBTreeLinePixelEpoch(textPtr, linePtr)
4083 		= textPtr->dInfoPtr->lineMetricUpdateEpoch;
4084 	if (TkBTreeLinePixelCount(textPtr, linePtr) != pixelHeight) {
4085 	    changed = 1;
4086 	}
4087 
4088 	if (mergedLines > 0) {
4089 	    int i = mergedLines;
4090 	    TkTextLine *mergedLinePtr;
4091 
4092 	    /*
4093 	     * Loop over all merged logical lines, marking them up to date
4094 	     * (again, the pixel count setting will actually happen in
4095 	     * TkBTreeAdjustPixelHeight).
4096 	     */
4097 
4098 	    mergedLinePtr = linePtr;
4099 	    while (i-- > 0) {
4100 		mergedLinePtr = TkBTreeNextLine(textPtr, mergedLinePtr);
4101 		TkBTreeLinePixelEpoch(textPtr, mergedLinePtr)
4102 			= textPtr->dInfoPtr->lineMetricUpdateEpoch;
4103 		if (TkBTreeLinePixelCount(textPtr, mergedLinePtr) != 0) {
4104 		    changed = 1;
4105 		}
4106 	    }
4107 	}
4108 
4109 	if (!changed) {
4110 	    /*
4111 	     * If there's nothing to change, then we can already return.
4112 	     */
4113 
4114 	    return displayLines;
4115 	}
4116     }
4117 
4118     /*
4119      * We set the line's height, but the return value is now the height of the
4120      * entire widget, which may be used just below for reporting/debugging
4121      * purposes.
4122      */
4123 
4124     pixelHeight = TkBTreeAdjustPixelHeight(textPtr, linePtr, pixelHeight,
4125 	    mergedLines);
4126 
4127     if (tkTextDebug) {
4128 	char buffer[2 * TCL_INTEGER_SPACE + 1];
4129 
4130 	if (TkBTreeNextLine(textPtr, linePtr) == NULL) {
4131 	    Tcl_Panic("Mustn't ever update line height of last artificial line");
4132 	}
4133 
4134 	sprintf(buffer, "%d %d", TkBTreeLinesTo(textPtr,linePtr), pixelHeight);
4135 	LOG("tk_textNumPixels", buffer);
4136     }
4137     if (textPtr->dInfoPtr->scrollbarTimer == NULL) {
4138 	textPtr->refCount++;
4139 	textPtr->dInfoPtr->scrollbarTimer = Tcl_CreateTimerHandler(200,
4140 		AsyncUpdateYScrollbar, textPtr);
4141     }
4142     return displayLines;
4143 }
4144 
4145 /*
4146  *----------------------------------------------------------------------
4147  *
4148  * DisplayText --
4149  *
4150  *	This function is invoked as a when-idle handler to update the display.
4151  *	It only redisplays the parts of the text widget that are out of date.
4152  *
4153  * Results:
4154  *	None.
4155  *
4156  * Side effects:
4157  *	Information is redrawn on the screen.
4158  *
4159  *----------------------------------------------------------------------
4160  */
4161 
4162 static void
DisplayText(ClientData clientData)4163 DisplayText(
4164     ClientData clientData)	/* Information about widget. */
4165 {
4166     TkText *textPtr = (TkText *)clientData;
4167     TextDInfo *dInfoPtr = textPtr->dInfoPtr;
4168     DLine *dlPtr;
4169     DLine *prevPtr;
4170     Pixmap pixmap;
4171     int maxHeight, borders;
4172     int bottomY = 0;		/* Initialization needed only to stop compiler
4173 				 * warnings. */
4174     Tcl_Interp *interp;
4175 
4176 
4177     if ((textPtr->tkwin == NULL) || (textPtr->flags & DESTROYED)) {
4178 	/*
4179 	 * The widget has been deleted.	 Don't do anything.
4180 	 */
4181 
4182 	return;
4183     }
4184 
4185 #ifdef MAC_OSX_TK
4186     /*
4187      * If the toplevel is being resized it would be dangerous to try redrawing
4188      * the widget.  But we can just clear the REDRAW_PENDING flag and return.
4189      * This display proc will be called again after the widget has been
4190      * reconfigured.
4191      */
4192 
4193     TkWindow *winPtr = (TkWindow *)(textPtr->tkwin);
4194     MacDrawable *macWin = winPtr->privatePtr;
4195     if (macWin && (macWin->flags & TK_DO_NOT_DRAW)){
4196 	dInfoPtr->flags &= ~REDRAW_PENDING;
4197     	return;
4198      }
4199 #endif
4200 
4201     interp = textPtr->interp;
4202     Tcl_Preserve(interp);
4203 
4204     if (tkTextDebug) {
4205 	CLEAR("tk_textRelayout");
4206     }
4207 
4208     if (!Tk_IsMapped(textPtr->tkwin) || (dInfoPtr->maxX <= dInfoPtr->x)
4209 	    || (dInfoPtr->maxY <= dInfoPtr->y)) {
4210 	UpdateDisplayInfo(textPtr);
4211 	dInfoPtr->flags &= ~REDRAW_PENDING;
4212 	goto doScrollbars;
4213     }
4214     numRedisplays++;
4215     if (tkTextDebug) {
4216 	CLEAR("tk_textRedraw");
4217     }
4218 
4219     /*
4220      * Choose a new current item if that is needed (this could cause event
4221      * handlers to be invoked, hence the preserve/release calls and the loop,
4222      * since the handlers could conceivably necessitate yet another current
4223      * item calculation). The tkwin check is because the whole window could go
4224      * away in the Tcl_Release call.
4225      */
4226 
4227     while (dInfoPtr->flags & REPICK_NEEDED) {
4228 	textPtr->refCount++;
4229 	dInfoPtr->flags &= ~REPICK_NEEDED;
4230 	TkTextPickCurrent(textPtr, &textPtr->pickEvent);
4231 	if (textPtr->refCount-- <= 1) {
4232 	    ckfree(textPtr);
4233 	    goto end;
4234 	}
4235 	if ((textPtr->tkwin == NULL) || (textPtr->flags & DESTROYED)) {
4236 	    goto end;
4237 	}
4238     }
4239 
4240     /*
4241      * First recompute what's supposed to be displayed.
4242      */
4243 
4244     UpdateDisplayInfo(textPtr);
4245     dInfoPtr->dLinesInvalidated = 0;
4246 
4247     /*
4248      * See if it's possible to bring some parts of the screen up-to-date by
4249      * scrolling (copying from other parts of the screen). We have to be
4250      * particularly careful with the top and bottom lines of the display,
4251      * since these may only be partially visible and therefore not helpful for
4252      * some scrolling purposes.
4253      */
4254 
4255     for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL; dlPtr = dlPtr->nextPtr) {
4256 	DLine *dlPtr2;
4257 	int offset, height, y, oldY;
4258 	TkRegion damageRgn;
4259 
4260 	/*
4261 	 * These tests are, in order:
4262 	 *
4263 	 * 1. If the line is already marked as invalid
4264 	 * 2. If the line hasn't moved
4265 	 * 3. If the line overlaps the bottom of the window and we are
4266 	 *    scrolling up.
4267 	 * 4. If the line overlaps the top of the window and we are scrolling
4268 	 *    down.
4269 	 *
4270 	 * If any of these tests are true, then we can't scroll this line's
4271 	 * part of the display.
4272 	 *
4273 	 * Note that even if tests 3 or 4 aren't true, we may be able to
4274 	 * scroll the line, but we still need to be sure to call embedded
4275 	 * window display procs on top and bottom lines if they have any
4276 	 * portion non-visible (see below).
4277 	 */
4278 
4279 	if ((dlPtr->flags & OLD_Y_INVALID)
4280 		|| (dlPtr->y == dlPtr->oldY)
4281 		|| (((dlPtr->oldY + dlPtr->height) > dInfoPtr->maxY)
4282 		    && (dlPtr->y < dlPtr->oldY))
4283 		|| ((dlPtr->oldY < dInfoPtr->y) && (dlPtr->y > dlPtr->oldY))) {
4284 	    continue;
4285 	}
4286 
4287 	/*
4288 	 * This line is already drawn somewhere in the window so it only needs
4289 	 * to be copied to its new location. See if there's a group of lines
4290 	 * that can all be copied together.
4291 	 */
4292 
4293 	offset = dlPtr->y - dlPtr->oldY;
4294 	height = dlPtr->height;
4295 	y = dlPtr->y;
4296 	for (dlPtr2 = dlPtr->nextPtr; dlPtr2 != NULL;
4297 		dlPtr2 = dlPtr2->nextPtr) {
4298 	    if ((dlPtr2->flags & OLD_Y_INVALID)
4299 		    || ((dlPtr2->oldY + offset) != dlPtr2->y)
4300 		    || ((dlPtr2->oldY + dlPtr2->height) > dInfoPtr->maxY)) {
4301 		break;
4302 	    }
4303 	    height += dlPtr2->height;
4304 	}
4305 
4306 	/*
4307 	 * Reduce the height of the area being copied if necessary to avoid
4308 	 * overwriting the border area.
4309 	 */
4310 
4311 	if ((y + height) > dInfoPtr->maxY) {
4312 	    height = dInfoPtr->maxY - y;
4313 	}
4314 	oldY = dlPtr->oldY;
4315 	if (y < dInfoPtr->y) {
4316 	    /*
4317 	     * Adjust if the area being copied is going to overwrite the top
4318 	     * border of the window (so the top line is only half onscreen).
4319 	     */
4320 
4321 	    int y_off = dInfoPtr->y - dlPtr->y;
4322 	    height -= y_off;
4323 	    oldY += y_off;
4324 	    y = dInfoPtr->y;
4325 	}
4326 
4327 	/*
4328 	 * Update the lines we are going to scroll to show that they have been
4329 	 * copied.
4330 	 */
4331 
4332 	while (1) {
4333 	    /*
4334 	     * The DLine already has OLD_Y_INVALID cleared.
4335 	     */
4336 
4337 	    dlPtr->oldY = dlPtr->y;
4338 	    if (dlPtr->nextPtr == dlPtr2) {
4339 		break;
4340 	    }
4341 	    dlPtr = dlPtr->nextPtr;
4342 	}
4343 	/*
4344 	 * Scan through the lines following the copied ones to see if we are
4345 	 * going to overwrite them with the copy operation. If so, mark them
4346 	 * for redisplay.
4347 	 */
4348 
4349 	for ( ; dlPtr2 != NULL; dlPtr2 = dlPtr2->nextPtr) {
4350 	    if ((!(dlPtr2->flags & OLD_Y_INVALID))
4351 		    && ((dlPtr2->oldY + dlPtr2->height) > y)
4352 		    && (dlPtr2->oldY < (y + height))) {
4353 		dlPtr2->flags |= OLD_Y_INVALID;
4354 	    }
4355 	}
4356 
4357 	/*
4358 	 * Now scroll the lines. This may generate damage which we handle by
4359 	 * calling TextInvalidateRegion to mark the display blocks as stale.
4360 	 */
4361 
4362 	damageRgn = TkCreateRegion();
4363 	if (TkScrollWindow(textPtr->tkwin, dInfoPtr->scrollGC, dInfoPtr->x,
4364 		oldY, dInfoPtr->maxX-dInfoPtr->x, height, 0, y-oldY,
4365 		damageRgn)) {
4366 	    TextInvalidateRegion(textPtr, damageRgn);
4367 	}
4368 	numCopies++;
4369 	TkDestroyRegion(damageRgn);
4370     }
4371 
4372     /*
4373      * Clear the REDRAW_PENDING flag here. This is actually pretty tricky. We
4374      * want to wait until *after* doing the scrolling, since that could
4375      * generate more areas to redraw and don't want to reschedule a redisplay
4376      * for them. On the other hand, we can't wait until after all the
4377      * redisplaying, because the act of redisplaying could actually generate
4378      * more redisplays (e.g. in the case of a nested window with event
4379      * bindings triggered by redisplay).
4380      */
4381 
4382     dInfoPtr->flags &= ~REDRAW_PENDING;
4383 
4384     /*
4385      * Redraw the borders if that's needed.
4386      */
4387 
4388     if (dInfoPtr->flags & REDRAW_BORDERS) {
4389 	if (tkTextDebug) {
4390 	    LOG("tk_textRedraw", "borders");
4391 	}
4392 
4393 	if (textPtr->tkwin == NULL) {
4394 	    /*
4395 	     * The widget has been deleted. Don't do anything.
4396 	     */
4397 
4398 	    goto end;
4399 	}
4400 
4401 	Tk_Draw3DRectangle(textPtr->tkwin, Tk_WindowId(textPtr->tkwin),
4402 		textPtr->border, textPtr->highlightWidth,
4403 		textPtr->highlightWidth,
4404 		Tk_Width(textPtr->tkwin) - 2*textPtr->highlightWidth,
4405 		Tk_Height(textPtr->tkwin) - 2*textPtr->highlightWidth,
4406 		textPtr->borderWidth, textPtr->relief);
4407 	if (textPtr->highlightWidth != 0) {
4408 	    GC fgGC, bgGC;
4409 
4410 	    bgGC = Tk_GCForColor(textPtr->highlightBgColorPtr,
4411 		    Tk_WindowId(textPtr->tkwin));
4412 	    if (textPtr->flags & GOT_FOCUS) {
4413 		fgGC = Tk_GCForColor(textPtr->highlightColorPtr,
4414 			Tk_WindowId(textPtr->tkwin));
4415 		TkpDrawHighlightBorder(textPtr->tkwin, fgGC, bgGC,
4416 			textPtr->highlightWidth, Tk_WindowId(textPtr->tkwin));
4417 	    } else {
4418 		TkpDrawHighlightBorder(textPtr->tkwin, bgGC, bgGC,
4419 			textPtr->highlightWidth, Tk_WindowId(textPtr->tkwin));
4420 	    }
4421 	}
4422 	borders = textPtr->borderWidth + textPtr->highlightWidth;
4423 	if (textPtr->padY > 0) {
4424 	    Tk_Fill3DRectangle(textPtr->tkwin, Tk_WindowId(textPtr->tkwin),
4425 		    textPtr->border, borders, borders,
4426 		    Tk_Width(textPtr->tkwin) - 2*borders, textPtr->padY,
4427 		    0, TK_RELIEF_FLAT);
4428 	    Tk_Fill3DRectangle(textPtr->tkwin, Tk_WindowId(textPtr->tkwin),
4429 		    textPtr->border, borders,
4430 		    Tk_Height(textPtr->tkwin) - borders - textPtr->padY,
4431 		    Tk_Width(textPtr->tkwin) - 2*borders,
4432 		    textPtr->padY, 0, TK_RELIEF_FLAT);
4433 	}
4434 	if (textPtr->padX > 0) {
4435 	    Tk_Fill3DRectangle(textPtr->tkwin, Tk_WindowId(textPtr->tkwin),
4436 		    textPtr->border, borders, borders + textPtr->padY,
4437 		    textPtr->padX,
4438 		    Tk_Height(textPtr->tkwin) - 2*borders -2*textPtr->padY,
4439 		    0, TK_RELIEF_FLAT);
4440 	    Tk_Fill3DRectangle(textPtr->tkwin, Tk_WindowId(textPtr->tkwin),
4441 		    textPtr->border,
4442 		    Tk_Width(textPtr->tkwin) - borders - textPtr->padX,
4443 		    borders + textPtr->padY, textPtr->padX,
4444 		    Tk_Height(textPtr->tkwin) - 2*borders -2*textPtr->padY,
4445 		    0, TK_RELIEF_FLAT);
4446 	}
4447 	dInfoPtr->flags &= ~REDRAW_BORDERS;
4448     }
4449 
4450     /*
4451      * Now we have to redraw the lines that couldn't be updated by scrolling.
4452      * First, compute the height of the largest line and allocate an off-
4453      * screen pixmap to use for double-buffered displays.
4454      */
4455 
4456     maxHeight = -1;
4457     for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL;
4458 	    dlPtr = dlPtr->nextPtr) {
4459 	if ((dlPtr->height > maxHeight) &&
4460 		((dlPtr->flags&OLD_Y_INVALID) || (dlPtr->oldY != dlPtr->y))) {
4461 	    maxHeight = dlPtr->height;
4462 	}
4463 	bottomY = dlPtr->y + dlPtr->height;
4464     }
4465 
4466     /*
4467      * There used to be a line here which restricted 'maxHeight' to be no
4468      * larger than 'dInfoPtr->maxY', but this is incorrect for the case where
4469      * individual lines may be taller than the widget _and_ we have smooth
4470      * scrolling. What we can do is restrict maxHeight to be no larger than
4471      * 'dInfoPtr->maxY + dInfoPtr->topPixelOffset'.
4472      */
4473 
4474     if (maxHeight > (dInfoPtr->maxY + dInfoPtr->topPixelOffset)) {
4475 	maxHeight = (dInfoPtr->maxY + dInfoPtr->topPixelOffset);
4476     }
4477 
4478     if (maxHeight > 0) {
4479 #ifndef TK_NO_DOUBLE_BUFFERING
4480 	pixmap = Tk_GetPixmap(Tk_Display(textPtr->tkwin),
4481 		Tk_WindowId(textPtr->tkwin), Tk_Width(textPtr->tkwin),
4482 		maxHeight, Tk_Depth(textPtr->tkwin));
4483 #else
4484 	pixmap = Tk_WindowId(textPtr->tkwin);
4485 #endif /* TK_NO_DOUBLE_BUFFERING */
4486 	for (prevPtr = NULL, dlPtr = textPtr->dInfoPtr->dLinePtr;
4487 		(dlPtr != NULL) && (dlPtr->y < dInfoPtr->maxY);
4488 		prevPtr = dlPtr, dlPtr = dlPtr->nextPtr) {
4489 	    if (dlPtr->chunkPtr == NULL) {
4490 		continue;
4491 	    }
4492 	    if ((dlPtr->flags & OLD_Y_INVALID) || dlPtr->oldY != dlPtr->y) {
4493 		if (tkTextDebug) {
4494 		    char string[TK_POS_CHARS];
4495 
4496 		    TkTextPrintIndex(textPtr, &dlPtr->index, string);
4497 		    LOG("tk_textRedraw", string);
4498 		}
4499 		DisplayDLine(textPtr, dlPtr, prevPtr, pixmap);
4500 		if (dInfoPtr->dLinesInvalidated) {
4501 #ifndef TK_NO_DOUBLE_BUFFERING
4502 		    Tk_FreePixmap(Tk_Display(textPtr->tkwin), pixmap);
4503 #endif /* TK_NO_DOUBLE_BUFFERING */
4504 		    return;
4505 		}
4506 		dlPtr->oldY = dlPtr->y;
4507 		dlPtr->flags &= ~(NEW_LAYOUT | OLD_Y_INVALID);
4508 #ifdef MAC_OSX_TK
4509 	    } else if (dlPtr->chunkPtr != NULL) {
4510 		/*
4511 		 * On macOS we need to redisplay all embedded windows which
4512 		 * were moved by the call to TkScrollWindows above.  This is
4513 		 * not necessary on Unix or Windows because XScrollWindow will
4514 		 * have included the bounding rectangles of all of these
4515 		 * windows in the damage region.  The macosx implementation of
4516 		 * TkScrollWindow does not do this.  It simply generates a
4517 		 * damage region which is the scroll source rectangle minus
4518 		 * the scroll destination rectangle.  This is because there is
4519 		 * no efficient process available for iterating through the
4520 		 * subwindows which meet the scrolled area.  (On Unix this is
4521 		 * handled by GraphicsExpose events generated by XCopyArea and
4522 		 * on Windows by ScrollWindowEx.  On macOS the low level
4523 		 * scrolling is accomplished by calling [view scrollRect:by:].
4524 		 * This method does not provide any damage information and, in
4525 		 * any case, could not be aware of Tk windows which were not
4526 		 * based on NSView objects.
4527 		 *
4528 		 * On the other hand, this loop is already iterating through
4529 		 * all embedded windows which could possibly have been moved
4530 		 * by the scrolling.  So it is as efficient to redisplay them
4531 		 * here as it would have been if they had been redisplayed by
4532 		 * the call to TextInvalidateRegion above.
4533 		 */
4534 #else
4535 	    } else if (dlPtr->chunkPtr != NULL && ((dlPtr->y < 0)
4536 		    || (dlPtr->y + dlPtr->height > dInfoPtr->maxY))) {
4537 		/*
4538 		 * On platforms other than the Mac:
4539 		 *
4540 		 * It's the first or last DLine which are also overlapping the
4541 		 * top or bottom of the window, but we decided above it wasn't
4542 		 * necessary to display them (we were able to update them by
4543 		 * scrolling). This is fine, except that if the lines contain
4544 		 * any embedded windows, we must still call the display proc
4545 		 * on them because they might need to be unmapped or they
4546 		 * might need to be moved to reflect their new position.
4547 		 * Otherwise, everything else moves, but the embedded window
4548 		 * doesn't!
4549 		 *
4550 		 * So, we loop through all the chunks, calling the display
4551 		 * proc of embedded windows only.
4552 		 */
4553 #endif
4554 		TkTextDispChunk *chunkPtr;
4555 
4556 		for (chunkPtr = dlPtr->chunkPtr; (chunkPtr != NULL);
4557 			chunkPtr = chunkPtr->nextPtr) {
4558 		    int x;
4559 		    if (chunkPtr->displayProc != TkTextEmbWinDisplayProc) {
4560 			continue;
4561 		    }
4562 		    x = chunkPtr->x + dInfoPtr->x - dInfoPtr->curXPixelOffset;
4563 		    if ((x + chunkPtr->width <= 0) || (x >= dInfoPtr->maxX)) {
4564 			/*
4565 			 * Note: we have to call the displayProc even for
4566 			 * chunks that are off-screen. This is needed, for
4567 			 * example, so that embedded windows can be unmapped
4568 			 * in this case. Display the chunk at a coordinate
4569 			 * that can be clearly identified by the displayProc
4570 			 * as being off-screen to the left (the displayProc
4571 			 * may not be able to tell if something is off to the
4572 			 * right).
4573 			 */
4574 
4575 			x = -chunkPtr->width;
4576 		    }
4577 		    if (tkTextDebug) {
4578 			char string[TK_POS_CHARS];
4579 
4580 			TkTextPrintIndex(textPtr, &dlPtr->index, string);
4581 			LOG("tk_textEmbWinDisplay", string);
4582 		    }
4583 		    TkTextEmbWinDisplayProc(textPtr, chunkPtr, x,
4584 			    dlPtr->spaceAbove,
4585 			    dlPtr->height-dlPtr->spaceAbove-dlPtr->spaceBelow,
4586 			    dlPtr->baseline - dlPtr->spaceAbove, NULL,
4587 			    None, dlPtr->y + dlPtr->spaceAbove);
4588 		}
4589 	    }
4590 	}
4591 #ifndef TK_NO_DOUBLE_BUFFERING
4592 	Tk_FreePixmap(Tk_Display(textPtr->tkwin), pixmap);
4593 #endif /* TK_NO_DOUBLE_BUFFERING */
4594     }
4595 
4596     /*
4597      * See if we need to refresh the part of the window below the last line of
4598      * text (if there is any such area). Refresh the padding area on the left
4599      * too, since the insertion cursor might have been displayed there
4600      * previously).
4601      */
4602 
4603     if (dInfoPtr->topOfEof > dInfoPtr->maxY) {
4604 	dInfoPtr->topOfEof = dInfoPtr->maxY;
4605     }
4606     if (bottomY < dInfoPtr->topOfEof) {
4607 	if (tkTextDebug) {
4608 	    LOG("tk_textRedraw", "eof");
4609 	}
4610 
4611 	if ((textPtr->tkwin == NULL) || (textPtr->flags & DESTROYED)) {
4612 	    /*
4613 	     * The widget has been deleted. Don't do anything.
4614 	     */
4615 
4616 	    goto end;
4617 	}
4618 
4619 	Tk_Fill3DRectangle(textPtr->tkwin, Tk_WindowId(textPtr->tkwin),
4620 		textPtr->border, dInfoPtr->x - textPtr->padX, bottomY,
4621 		dInfoPtr->maxX - (dInfoPtr->x - textPtr->padX),
4622 		dInfoPtr->topOfEof-bottomY, 0, TK_RELIEF_FLAT);
4623     }
4624     dInfoPtr->topOfEof = bottomY;
4625 
4626     /*
4627      * Update the vertical scrollbar, if there is one. Note: it's important to
4628      * clear REDRAW_PENDING here, just in case the scroll function does
4629      * something that requires redisplay.
4630      */
4631 
4632   doScrollbars:
4633     if (textPtr->flags & UPDATE_SCROLLBARS) {
4634 	textPtr->flags &= ~UPDATE_SCROLLBARS;
4635 	if (textPtr->yScrollCmd != NULL) {
4636 	    GetYView(textPtr->interp, textPtr, 1);
4637 	}
4638 
4639 	if ((textPtr->tkwin == NULL) || (textPtr->flags & DESTROYED)) {
4640 	    /*
4641 	     * The widget has been deleted. Don't do anything.
4642 	     */
4643 
4644 	    goto end;
4645 	}
4646 
4647 	/*
4648 	 * Update the horizontal scrollbar, if any.
4649 	 */
4650 
4651 	if (textPtr->xScrollCmd != NULL) {
4652 	    GetXView(textPtr->interp, textPtr, 1);
4653 	}
4654     }
4655 
4656   end:
4657     Tcl_Release(interp);
4658 }
4659 
4660 /*
4661  *----------------------------------------------------------------------
4662  *
4663  * TkTextEventuallyRepick --
4664  *
4665  *	This function is invoked whenever something happens that could change
4666  *	the current character or the tags associated with it.
4667  *
4668  * Results:
4669  *	None.
4670  *
4671  * Side effects:
4672  *	A repick is scheduled as an idle handler.
4673  *
4674  *----------------------------------------------------------------------
4675  */
4676 
4677 void
TkTextEventuallyRepick(TkText * textPtr)4678 TkTextEventuallyRepick(
4679     TkText *textPtr)		/* Widget record for text widget. */
4680 {
4681     TextDInfo *dInfoPtr = textPtr->dInfoPtr;
4682 
4683     dInfoPtr->flags |= REPICK_NEEDED;
4684     if (!(dInfoPtr->flags & REDRAW_PENDING)) {
4685 	dInfoPtr->flags |= REDRAW_PENDING;
4686 	Tcl_DoWhenIdle(DisplayText, textPtr);
4687     }
4688 }
4689 
4690 /*
4691  *----------------------------------------------------------------------
4692  *
4693  * TkTextRedrawRegion --
4694  *
4695  *	This function is invoked to schedule a redisplay for a given region of
4696  *	a text widget. The redisplay itself may not occur immediately: it's
4697  *	scheduled as a when-idle handler.
4698  *
4699  * Results:
4700  *	None.
4701  *
4702  * Side effects:
4703  *	Information will eventually be redrawn on the screen.
4704  *
4705  *----------------------------------------------------------------------
4706  */
4707 
4708 void
TkTextRedrawRegion(TkText * textPtr,int x,int y,int width,int height)4709 TkTextRedrawRegion(
4710     TkText *textPtr,		/* Widget record for text widget. */
4711     int x, int y,		/* Coordinates of upper-left corner of area to
4712 				 * be redrawn, in pixels relative to textPtr's
4713 				 * window. */
4714     int width, int height)	/* Width and height of area to be redrawn. */
4715 {
4716     TextDInfo *dInfoPtr = textPtr->dInfoPtr;
4717     TkRegion damageRgn = TkCreateRegion();
4718     XRectangle rect;
4719 
4720     rect.x = x;
4721     rect.y = y;
4722     rect.width = width;
4723     rect.height = height;
4724     TkUnionRectWithRegion(&rect, damageRgn, damageRgn);
4725 
4726     TextInvalidateRegion(textPtr, damageRgn);
4727 
4728     if (!(dInfoPtr->flags & REDRAW_PENDING)) {
4729 	dInfoPtr->flags |= REDRAW_PENDING;
4730 	Tcl_DoWhenIdle(DisplayText, textPtr);
4731     }
4732     TkDestroyRegion(damageRgn);
4733 }
4734 
4735 /*
4736  *----------------------------------------------------------------------
4737  *
4738  * TextInvalidateRegion --
4739  *
4740  *	Mark a region of text as invalid.
4741  *
4742  * Results:
4743  *	None.
4744  *
4745  * Side effects:
4746  *	Updates the display information for the text widget.
4747  *
4748  *----------------------------------------------------------------------
4749  */
4750 
4751 static void
TextInvalidateRegion(TkText * textPtr,TkRegion region)4752 TextInvalidateRegion(
4753     TkText *textPtr,		/* Widget record for text widget. */
4754     TkRegion region)		/* Region of area to redraw. */
4755 {
4756     DLine *dlPtr;
4757     TextDInfo *dInfoPtr = textPtr->dInfoPtr;
4758     int maxY, inset;
4759     XRectangle rect;
4760 
4761     /*
4762      * Find all lines that overlap the given region and mark them for
4763      * redisplay.
4764      */
4765 
4766     TkClipBox(region, &rect);
4767     maxY = rect.y + rect.height;
4768     for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL;
4769 	    dlPtr = dlPtr->nextPtr) {
4770 	if ((!(dlPtr->flags & OLD_Y_INVALID))
4771 		&& (TkRectInRegion(region, rect.x, dlPtr->y,
4772 		rect.width, (unsigned int) dlPtr->height) != RectangleOut)) {
4773 	    dlPtr->flags |= OLD_Y_INVALID;
4774 	}
4775     }
4776     if (dInfoPtr->topOfEof < maxY) {
4777 	dInfoPtr->topOfEof = maxY;
4778     }
4779 
4780     /*
4781      * Schedule the redisplay operation if there isn't one already scheduled.
4782      */
4783 
4784     inset = textPtr->borderWidth + textPtr->highlightWidth;
4785     if ((rect.x < (inset + textPtr->padX))
4786 	    || (rect.y < (inset + textPtr->padY))
4787 	    || ((int) (rect.x + rect.width) > (Tk_Width(textPtr->tkwin)
4788 		    - inset - textPtr->padX))
4789 	    || (maxY > (Tk_Height(textPtr->tkwin) - inset - textPtr->padY))) {
4790 	dInfoPtr->flags |= REDRAW_BORDERS;
4791     }
4792 }
4793 
4794 /*
4795  *----------------------------------------------------------------------
4796  *
4797  * TkTextChanged, TextChanged --
4798  *
4799  *	This function is invoked when info in a text widget is about to be
4800  *	modified in a way that changes how it is displayed (e.g. characters
4801  *	were inserted or deleted, or tag information was changed). This
4802  *	function must be called *before* a change is made, so that indexes in
4803  *	the display information are still valid.
4804  *
4805  *	Note: if the range of indices may change geometry as well as simply
4806  *	requiring redisplay, then the caller should also call
4807  *	TkTextInvalidateLineMetrics.
4808  *
4809  * Results:
4810  *	None.
4811  *
4812  * Side effects:
4813  *	The range of character between index1Ptr (inclusive) and index2Ptr
4814  *	(exclusive) will be redisplayed at some point in the future (the
4815  *	actual redisplay is scheduled as a when-idle handler).
4816  *
4817  *----------------------------------------------------------------------
4818  */
4819 
4820 void
TkTextChanged(TkSharedText * sharedTextPtr,TkText * textPtr,const TkTextIndex * index1Ptr,const TkTextIndex * index2Ptr)4821 TkTextChanged(
4822     TkSharedText *sharedTextPtr,/* Shared widget section, or NULL. */
4823     TkText *textPtr,		/* Widget record for text widget, or NULL. */
4824     const TkTextIndex*index1Ptr,/* Index of first character to redisplay. */
4825     const TkTextIndex*index2Ptr)/* Index of character just after last one to
4826 				 * redisplay. */
4827 {
4828     if (sharedTextPtr == NULL) {
4829 	TextChanged(textPtr, index1Ptr, index2Ptr);
4830     } else {
4831 	textPtr = sharedTextPtr->peers;
4832 	while (textPtr != NULL) {
4833 	    TextChanged(textPtr, index1Ptr, index2Ptr);
4834 	    textPtr = textPtr->next;
4835 	}
4836     }
4837 }
4838 
4839 static void
TextChanged(TkText * textPtr,const TkTextIndex * index1Ptr,const TkTextIndex * index2Ptr)4840 TextChanged(
4841     TkText *textPtr,		/* Widget record for text widget, or NULL. */
4842     const TkTextIndex*index1Ptr,/* Index of first character to redisplay. */
4843     const TkTextIndex*index2Ptr)/* Index of character just after last one to
4844 				 * redisplay. */
4845 {
4846     TextDInfo *dInfoPtr = textPtr->dInfoPtr;
4847     DLine *firstPtr, *lastPtr;
4848     TkTextIndex rounded;
4849     TkTextLine *linePtr;
4850     int notBegin;
4851 
4852     /*
4853      * Schedule both a redisplay and a recomputation of display information.
4854      * It's done here rather than the end of the function for two reasons:
4855      *
4856      * 1. If there are no display lines to update we'll want to return
4857      *	  immediately, well before the end of the function.
4858      * 2. It's important to arrange for the redisplay BEFORE calling
4859      *	  FreeDLines. The reason for this is subtle and has to do with
4860      *	  embedded windows. The chunk delete function for an embedded window
4861      *	  will schedule an idle handler to unmap the window. However, we want
4862      *	  the idle handler for redisplay to be called first, so that it can
4863      *	  put the embedded window back on the screen again (if appropriate).
4864      *	  This will prevent the window from ever being unmapped, and thereby
4865      *	  avoid flashing.
4866      */
4867 
4868     if (!(dInfoPtr->flags & REDRAW_PENDING)) {
4869 	Tcl_DoWhenIdle(DisplayText, textPtr);
4870     }
4871     dInfoPtr->flags |= REDRAW_PENDING|DINFO_OUT_OF_DATE|REPICK_NEEDED;
4872 
4873     /*
4874      * Find the DLines corresponding to index1Ptr and index2Ptr. There is one
4875      * tricky thing here, which is that we have to relayout in units of whole
4876      * text lines: This is necessary because the indices stored in the display
4877      * lines will no longer be valid. It's also needed because any edit could
4878      * change the way lines wrap.
4879      * To relayout in units of whole text (logical) lines, round index1Ptr
4880      * back to the beginning of its text line (or, if this line start is
4881      * elided, to the beginning of the text line that starts the display line
4882      * it is included in), and include all the display lines after index2Ptr,
4883      * up to the end of its text line (or, if this line end is elided, up to
4884      * the end of the first non elided text line after this line end).
4885      */
4886 
4887     rounded = *index1Ptr;
4888     rounded.byteIndex = 0;
4889     notBegin = 0;
4890     while (!IsStartOfNotMergedLine(textPtr, &rounded) && notBegin) {
4891         notBegin = !TkTextIndexBackBytes(textPtr, &rounded, 1, &rounded);
4892         rounded.byteIndex = 0;
4893     }
4894 
4895     /*
4896      * 'rounded' now points to the start of a display line as well as the
4897      * real (non elided) start of a logical line, and this index is the
4898      * closest before index1Ptr.
4899      */
4900 
4901     firstPtr = FindDLine(textPtr, dInfoPtr->dLinePtr, &rounded);
4902 
4903     if (firstPtr == NULL) {
4904         /*
4905          * index1Ptr pertains to no display line, i.e this index is after
4906          * the last display line. Since index2Ptr is after index1Ptr, there
4907          * is no display line to free/redisplay and we can return early.
4908          */
4909 
4910 	return;
4911     }
4912 
4913     rounded = *index2Ptr;
4914     linePtr = index2Ptr->linePtr;
4915     do {
4916         linePtr = TkBTreeNextLine(textPtr, linePtr);
4917         if (linePtr == NULL) {
4918             break;
4919         }
4920         rounded.linePtr = linePtr;
4921         rounded.byteIndex = 0;
4922     } while (!IsStartOfNotMergedLine(textPtr, &rounded));
4923 
4924     if (linePtr == NULL) {
4925         lastPtr = NULL;
4926     } else {
4927         /*
4928          * 'rounded' now points to the start of a display line as well as the
4929          * start of a logical line not merged with its previous line, and
4930          * this index is the closest after index2Ptr.
4931          */
4932 
4933         lastPtr = FindDLine(textPtr, dInfoPtr->dLinePtr, &rounded);
4934 
4935         /*
4936          * At least one display line is supposed to change. This makes the
4937          * redisplay OK in case the display line we expect to get here was
4938          * unlinked by a previous call to TkTextChanged and the text widget
4939          * did not update before reaching this point. This happens for
4940          * instance when moving the cursor up one line.
4941          * Note that lastPtr != NULL here, otherwise we would have returned
4942          * earlier when we tested for firstPtr being NULL.
4943          */
4944 
4945         if (lastPtr == firstPtr) {
4946             lastPtr = lastPtr->nextPtr;
4947         }
4948     }
4949 
4950     /*
4951      * Delete all the DLines from firstPtr up to but not including lastPtr.
4952      */
4953 
4954     FreeDLines(textPtr, firstPtr, lastPtr, DLINE_UNLINK);
4955 }
4956 
4957 /*
4958  *----------------------------------------------------------------------
4959  *
4960  * TkTextRedrawTag, TextRedrawTag --
4961  *
4962  *	This function is invoked to request a redraw of all characters in a
4963  *	given range that have a particular tag on or off. It's called, for
4964  *	example, when tag options change.
4965  *
4966  * Results:
4967  *	None.
4968  *
4969  * Side effects:
4970  *	Information on the screen may be redrawn, and the layout of the screen
4971  *	may change.
4972  *
4973  *----------------------------------------------------------------------
4974  */
4975 
4976 void
TkTextRedrawTag(TkSharedText * sharedTextPtr,TkText * textPtr,TkTextIndex * index1Ptr,TkTextIndex * index2Ptr,TkTextTag * tagPtr,int withTag)4977 TkTextRedrawTag(
4978     TkSharedText *sharedTextPtr,/* Shared widget section, or NULL. */
4979     TkText *textPtr,		/* Widget record for text widget. */
4980     TkTextIndex *index1Ptr,	/* First character in range to consider for
4981 				 * redisplay. NULL means start at beginning of
4982 				 * text. */
4983     TkTextIndex *index2Ptr,	/* Character just after last one to consider
4984 				 * for redisplay. NULL means process all the
4985 				 * characters in the text. */
4986     TkTextTag *tagPtr,		/* Information about tag. */
4987     int withTag)		/* 1 means redraw characters that have the
4988 				 * tag, 0 means redraw those without. */
4989 {
4990     if (sharedTextPtr == NULL) {
4991 	TextRedrawTag(textPtr, index1Ptr, index2Ptr, tagPtr, withTag);
4992     } else {
4993 	textPtr = sharedTextPtr->peers;
4994 	while (textPtr != NULL) {
4995 	    TextRedrawTag(textPtr, index1Ptr, index2Ptr, tagPtr, withTag);
4996 	    textPtr = textPtr->next;
4997 	}
4998     }
4999 }
5000 
5001 static void
TextRedrawTag(TkText * textPtr,TkTextIndex * index1Ptr,TkTextIndex * index2Ptr,TkTextTag * tagPtr,int withTag)5002 TextRedrawTag(
5003     TkText *textPtr,		/* Widget record for text widget. */
5004     TkTextIndex *index1Ptr,	/* First character in range to consider for
5005 				 * redisplay. NULL means start at beginning of
5006 				 * text. */
5007     TkTextIndex *index2Ptr,	/* Character just after last one to consider
5008 				 * for redisplay. NULL means process all the
5009 				 * characters in the text. */
5010     TkTextTag *tagPtr,		/* Information about tag. */
5011     int withTag)		/* 1 means redraw characters that have the
5012 				 * tag, 0 means redraw those without. */
5013 {
5014     DLine *dlPtr;
5015     DLine *endPtr;
5016     int tagOn;
5017     TkTextSearch search;
5018     TextDInfo *dInfoPtr = textPtr->dInfoPtr;
5019     TkTextIndex *curIndexPtr;
5020     TkTextIndex endOfText, *endIndexPtr;
5021 
5022     /*
5023      * Invalidate the pixel calculation of all lines in the given range. This
5024      * may be a bit over-aggressive, so we could consider more subtle
5025      * techniques here in the future. In particular, when we create a tag for
5026      * the first time with '.t tag configure foo -font "Arial 20"', say, even
5027      * though that obviously can't apply to anything at all (the tag didn't
5028      * exist a moment ago), we invalidate every single line in the widget.
5029      */
5030 
5031     if (tagPtr->affectsDisplayGeometry) {
5032 	TkTextLine *startLine, *endLine;
5033 	int lineCount;
5034 
5035 	if (index2Ptr == NULL) {
5036 	    endLine = NULL;
5037 	    lineCount = TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr);
5038 	} else {
5039 	    endLine = index2Ptr->linePtr;
5040 	    lineCount = TkBTreeLinesTo(textPtr, endLine);
5041 	}
5042 	if (index1Ptr == NULL) {
5043 	    startLine = NULL;
5044 	} else {
5045 	    startLine = index1Ptr->linePtr;
5046 	    lineCount -= TkBTreeLinesTo(textPtr, startLine);
5047 	}
5048 	TkTextInvalidateLineMetrics(NULL, textPtr, startLine, lineCount,
5049 		TK_TEXT_INVALIDATE_ONLY);
5050     }
5051 
5052     /*
5053      * Round up the starting position if it's before the first line visible on
5054      * the screen (we only care about what's on the screen).
5055      */
5056 
5057     dlPtr = dInfoPtr->dLinePtr;
5058     if (dlPtr == NULL) {
5059 	return;
5060     }
5061     if ((index1Ptr == NULL) || (TkTextIndexCmp(&dlPtr->index, index1Ptr)>0)) {
5062 	index1Ptr = &dlPtr->index;
5063     }
5064 
5065     /*
5066      * Set the stopping position if it wasn't specified.
5067      */
5068 
5069     if (index2Ptr == NULL) {
5070 	int lastLine = TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr);
5071 
5072 	index2Ptr = TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr,
5073 		lastLine, 0, &endOfText);
5074     }
5075 
5076     /*
5077      * Initialize a search through all transitions on the tag, starting with
5078      * the first transition where the tag's current state is different from
5079      * what it will eventually be.
5080      */
5081 
5082     TkBTreeStartSearch(index1Ptr, index2Ptr, tagPtr, &search);
5083 
5084     /*
5085      * Make our own curIndex because at this point search.curIndex may not
5086      * equal index1Ptr->curIndex in the case the first tag toggle comes after
5087      * index1Ptr (See the use of FindTagStart in TkBTreeStartSearch).
5088      */
5089 
5090     curIndexPtr = index1Ptr;
5091     tagOn = TkBTreeCharTagged(index1Ptr, tagPtr);
5092     if (tagOn != withTag) {
5093 	if (!TkBTreeNextTag(&search)) {
5094 	    return;
5095 	}
5096 	curIndexPtr = &search.curIndex;
5097     }
5098 
5099     /*
5100      * Schedule a redisplay and layout recalculation if they aren't already
5101      * pending. This has to be done before calling FreeDLines, for the reason
5102      * given in TkTextChanged.
5103      */
5104 
5105     if (!(dInfoPtr->flags & REDRAW_PENDING)) {
5106 	Tcl_DoWhenIdle(DisplayText, textPtr);
5107     }
5108     dInfoPtr->flags |= REDRAW_PENDING|DINFO_OUT_OF_DATE|REPICK_NEEDED;
5109 
5110     /*
5111      * Each loop through the loop below is for one range of characters where
5112      * the tag's current state is different than its eventual state. At the
5113      * top of the loop, search contains information about the first character
5114      * in the range.
5115      */
5116 
5117     while (1) {
5118 	/*
5119 	 * Find the first DLine structure in the range. Note: if the desired
5120 	 * character isn't the first in its text line, then look for the
5121 	 * character just before it instead. This is needed to handle the case
5122 	 * where the first character of a wrapped display line just got
5123 	 * smaller, so that it now fits on the line before: need to relayout
5124 	 * the line containing the previous character.
5125 	 */
5126 
5127 	if (IsStartOfNotMergedLine(textPtr, curIndexPtr)) {
5128 	    dlPtr = FindDLine(textPtr, dlPtr, curIndexPtr);
5129 	} else {
5130 	    TkTextIndex tmp = *curIndexPtr;
5131 
5132             TkTextIndexBackBytes(textPtr, &tmp, 1, &tmp);
5133 	    dlPtr = FindDLine(textPtr, dlPtr, &tmp);
5134 	}
5135 	if (dlPtr == NULL) {
5136 	    break;
5137 	}
5138 
5139 	/*
5140 	 * Find the first DLine structure that's past the end of the range.
5141 	 */
5142 
5143 	if (!TkBTreeNextTag(&search)) {
5144 	    endIndexPtr = index2Ptr;
5145 	} else {
5146 	    curIndexPtr = &search.curIndex;
5147 	    endIndexPtr = curIndexPtr;
5148 	}
5149 	endPtr = FindDLine(textPtr, dlPtr, endIndexPtr);
5150 	if ((endPtr != NULL)
5151                 && (TkTextIndexCmp(&endPtr->index,endIndexPtr) < 0)) {
5152 	    endPtr = endPtr->nextPtr;
5153 	}
5154 
5155 	/*
5156 	 * Delete all of the display lines in the range, so that they'll be
5157 	 * re-layed out and redrawn.
5158 	 */
5159 
5160 	FreeDLines(textPtr, dlPtr, endPtr, DLINE_UNLINK);
5161 	dlPtr = endPtr;
5162 
5163 	/*
5164 	 * Find the first text line in the next range.
5165 	 */
5166 
5167 	if (!TkBTreeNextTag(&search)) {
5168 	    break;
5169 	}
5170     }
5171 }
5172 
5173 /*
5174  *----------------------------------------------------------------------
5175  *
5176  * TkTextRelayoutWindow --
5177  *
5178  *	This function is called when something has happened that invalidates
5179  *	the whole layout of characters on the screen, such as a change in a
5180  *	configuration option for the overall text widget or a change in the
5181  *	window size. It causes all display information to be recomputed and
5182  *	the window to be redrawn.
5183  *
5184  * Results:
5185  *	None.
5186  *
5187  * Side effects:
5188  *	All the display information will be recomputed for the window and the
5189  *	window will be redrawn.
5190  *
5191  *----------------------------------------------------------------------
5192  */
5193 
5194 void
TkTextRelayoutWindow(TkText * textPtr,int mask)5195 TkTextRelayoutWindow(
5196     TkText *textPtr,		/* Widget record for text widget. */
5197     int mask)			/* OR'd collection of bits showing what has
5198 				 * changed. */
5199 {
5200     TextDInfo *dInfoPtr = textPtr->dInfoPtr;
5201     GC newGC;
5202     XGCValues gcValues;
5203     Bool inSync = 1;
5204 
5205     /*
5206      * Schedule the window redisplay. See TkTextChanged for the reason why
5207      * this has to be done before any calls to FreeDLines.
5208      */
5209 
5210     if (!(dInfoPtr->flags & REDRAW_PENDING)) {
5211 	Tcl_DoWhenIdle(DisplayText, textPtr);
5212 	inSync = 0;
5213     }
5214     dInfoPtr->flags |= REDRAW_PENDING|REDRAW_BORDERS|DINFO_OUT_OF_DATE
5215 	    |REPICK_NEEDED;
5216 
5217     /*
5218      * (Re-)create the graphics context for drawing the traversal highlight.
5219      */
5220 
5221     gcValues.graphics_exposures = False;
5222     newGC = Tk_GetGC(textPtr->tkwin, GCGraphicsExposures, &gcValues);
5223     if (dInfoPtr->copyGC != NULL) {
5224 	Tk_FreeGC(textPtr->display, dInfoPtr->copyGC);
5225     }
5226     dInfoPtr->copyGC = newGC;
5227 
5228     /*
5229      * Throw away all the current layout information.
5230      */
5231 
5232     FreeDLines(textPtr, dInfoPtr->dLinePtr, NULL, DLINE_UNLINK);
5233     dInfoPtr->dLinePtr = NULL;
5234 
5235     /*
5236      * Recompute some overall things for the layout. Even if the window gets
5237      * very small, pretend that there's at least one pixel of drawing space in
5238      * it.
5239      */
5240 
5241     if (textPtr->highlightWidth < 0) {
5242 	textPtr->highlightWidth = 0;
5243     }
5244     dInfoPtr->x = textPtr->highlightWidth + textPtr->borderWidth
5245 	    + textPtr->padX;
5246     dInfoPtr->y = textPtr->highlightWidth + textPtr->borderWidth
5247 	    + textPtr->padY;
5248     dInfoPtr->maxX = Tk_Width(textPtr->tkwin) - textPtr->highlightWidth
5249 	    - textPtr->borderWidth - textPtr->padX;
5250     if (dInfoPtr->maxX <= dInfoPtr->x) {
5251 	dInfoPtr->maxX = dInfoPtr->x + 1;
5252     }
5253 
5254     /*
5255      * This is the only place where dInfoPtr->maxY is set.
5256      */
5257 
5258     dInfoPtr->maxY = Tk_Height(textPtr->tkwin) - textPtr->highlightWidth
5259 	    - textPtr->borderWidth - textPtr->padY;
5260     if (dInfoPtr->maxY <= dInfoPtr->y) {
5261 	dInfoPtr->maxY = dInfoPtr->y + 1;
5262     }
5263     dInfoPtr->topOfEof = dInfoPtr->maxY;
5264 
5265     /*
5266      * If the upper-left character isn't the first in a line, recompute it.
5267      * This is necessary because a change in the window's size or options
5268      * could change the way lines wrap.
5269      */
5270 
5271     if (!IsStartOfNotMergedLine(textPtr, &textPtr->topIndex)) {
5272 	TkTextFindDisplayLineEnd(textPtr, &textPtr->topIndex, 0, NULL);
5273     }
5274 
5275     /*
5276      * Invalidate cached scrollbar positions, so that scrollbars sliders will
5277      * be updated.
5278      */
5279 
5280     dInfoPtr->xScrollFirst = dInfoPtr->xScrollLast = -1;
5281     dInfoPtr->yScrollFirst = dInfoPtr->yScrollLast = -1;
5282 
5283     if (mask & TK_TEXT_LINE_GEOMETRY) {
5284 
5285 	/*
5286 	 * Set up line metric recalculation.
5287 	 *
5288 	 * Avoid the special zero value, since that is used to mark individual
5289 	 * lines as being out of date.
5290 	 */
5291 
5292 	if ((++dInfoPtr->lineMetricUpdateEpoch) == 0) {
5293 	    dInfoPtr->lineMetricUpdateEpoch++;
5294 	}
5295 
5296 	dInfoPtr->currentMetricUpdateLine = -1;
5297 
5298 	/*
5299 	 * Also cancel any partial line-height calculations (for long-wrapped
5300 	 * lines) in progress.
5301 	 */
5302 
5303 	dInfoPtr->metricEpoch = -1;
5304 
5305 	if (dInfoPtr->lineUpdateTimer == NULL) {
5306 	    textPtr->refCount++;
5307 	    dInfoPtr->lineUpdateTimer = Tcl_CreateTimerHandler(1,
5308 		    AsyncUpdateLineMetrics, textPtr);
5309 	    inSync = 0;
5310 	}
5311 
5312         GenerateWidgetViewSyncEvent(textPtr, inSync);
5313     }
5314 }
5315 
5316 /*
5317  *----------------------------------------------------------------------
5318  *
5319  * TkTextSetYView --
5320  *
5321  *	This function is called to specify what lines are to be displayed in a
5322  *	text widget.
5323  *
5324  * Results:
5325  *	None.
5326  *
5327  * Side effects:
5328  *	The display will (eventually) be updated so that the position given by
5329  *	"indexPtr" is visible on the screen at the position determined by
5330  *	"pickPlace".
5331  *
5332  *----------------------------------------------------------------------
5333  */
5334 
5335 void
TkTextSetYView(TkText * textPtr,TkTextIndex * indexPtr,int pickPlace)5336 TkTextSetYView(
5337     TkText *textPtr,		/* Widget record for text widget. */
5338     TkTextIndex *indexPtr,	/* Position that is to appear somewhere in the
5339 				 * view. */
5340     int pickPlace)		/* 0 means the given index must appear exactly
5341 				 * at the top of the screen. TK_TEXT_PICKPLACE
5342 				 * (-1) means we get to pick where it appears:
5343 				 * minimize screen motion or else display line
5344 				 * at center of screen. TK_TEXT_NOPIXELADJUST
5345 				 * (-2) indicates to make the given index the
5346 				 * top line, but if it is already the top
5347 				 * line, don't nudge it up or down by a few
5348 				 * pixels just to make sure it is entirely
5349 				 * displayed. Positive numbers indicate the
5350 				 * number of pixels of the index's line which
5351 				 * are to be off the top of the screen. */
5352 {
5353     TextDInfo *dInfoPtr = textPtr->dInfoPtr;
5354     DLine *dlPtr;
5355     int bottomY, close, lineIndex;
5356     TkTextIndex tmpIndex, rounded;
5357     int lineHeight;
5358 
5359     /*
5360      * If the specified position is the extra line at the end of the text,
5361      * round it back to the last real line.
5362      */
5363 
5364     lineIndex = TkBTreeLinesTo(textPtr, indexPtr->linePtr);
5365     if (lineIndex == TkBTreeNumLines(indexPtr->tree, textPtr)) {
5366 	TkTextIndexBackChars(textPtr, indexPtr, 1, &rounded, COUNT_INDICES);
5367 	indexPtr = &rounded;
5368     }
5369 
5370     if (pickPlace == TK_TEXT_NOPIXELADJUST) {
5371 	if (textPtr->topIndex.linePtr == indexPtr->linePtr
5372 		&& textPtr->topIndex.byteIndex == indexPtr->byteIndex) {
5373 	    pickPlace = dInfoPtr->topPixelOffset;
5374 	} else {
5375 	    pickPlace = 0;
5376 	}
5377     }
5378 
5379     if (pickPlace != TK_TEXT_PICKPLACE) {
5380 	/*
5381 	 * The specified position must go at the top of the screen. Just leave
5382 	 * all the DLine's alone: we may be able to reuse some of the
5383 	 * information that's currently on the screen without redisplaying it
5384 	 * all.
5385 	 */
5386 
5387 	textPtr->topIndex = *indexPtr;
5388         if (!IsStartOfNotMergedLine(textPtr, indexPtr)) {
5389             TkTextFindDisplayLineEnd(textPtr, &textPtr->topIndex, 0, NULL);
5390         }
5391 	dInfoPtr->newTopPixelOffset = pickPlace;
5392 	goto scheduleUpdate;
5393     }
5394 
5395     /*
5396      * We have to pick where to display the index. First, bring the display
5397      * information up to date and see if the index will be completely visible
5398      * in the current screen configuration. If so then there's nothing to do.
5399      */
5400 
5401     if (dInfoPtr->flags & DINFO_OUT_OF_DATE) {
5402 	UpdateDisplayInfo(textPtr);
5403     }
5404     dlPtr = FindDLine(textPtr, dInfoPtr->dLinePtr, indexPtr);
5405     if (dlPtr != NULL) {
5406 	if ((dlPtr->y + dlPtr->height) > dInfoPtr->maxY) {
5407 	    /*
5408 	     * Part of the line hangs off the bottom of the screen; pretend
5409 	     * the whole line is off-screen.
5410 	     */
5411 
5412 	    dlPtr = NULL;
5413         } else {
5414             if (TkTextIndexCmp(&dlPtr->index, indexPtr) <= 0) {
5415                 if (dInfoPtr->dLinePtr == dlPtr && dInfoPtr->topPixelOffset != 0) {
5416                     /*
5417                      * It is on the top line, but that line is hanging off the top
5418                      * of the screen. Change the top overlap to zero and update.
5419                      */
5420 
5421                     dInfoPtr->newTopPixelOffset = 0;
5422                     goto scheduleUpdate;
5423                 }
5424                 /*
5425                  * The line is already on screen, with no need to scroll.
5426                  */
5427                 return;
5428             }
5429         }
5430     }
5431 
5432     /*
5433      * The desired line isn't already on-screen. Figure out what it means to
5434      * be "close" to the top or bottom of the screen. Close means within 1/3
5435      * of the screen height or within three lines, whichever is greater.
5436      *
5437      * If the line is not close, place it in the center of the window.
5438      */
5439 
5440     tmpIndex = *indexPtr;
5441     TkTextFindDisplayLineEnd(textPtr, &tmpIndex, 0, NULL);
5442     lineHeight = CalculateDisplayLineHeight(textPtr, &tmpIndex, NULL, NULL);
5443 
5444     /*
5445      * It would be better if 'bottomY' were calculated using the actual height
5446      * of the given line, not 'textPtr->charHeight'.
5447      */
5448 
5449     bottomY = (dInfoPtr->y + dInfoPtr->maxY + lineHeight)/2;
5450     close = (dInfoPtr->maxY - dInfoPtr->y)/3;
5451     if (close < 3*textPtr->charHeight) {
5452 	close = 3*textPtr->charHeight;
5453     }
5454     if (dlPtr != NULL) {
5455 	int overlap;
5456 
5457 	/*
5458 	 * The desired line is above the top of screen. If it is "close" to
5459 	 * the top of the window then make it the top line on the screen.
5460 	 * MeasureUp counts from the bottom of the given index upwards, so we
5461 	 * add an extra half line to be sure we count far enough.
5462 	 */
5463 
5464 	MeasureUp(textPtr, &textPtr->topIndex, close + textPtr->charHeight/2,
5465 		&tmpIndex, &overlap);
5466 	if (TkTextIndexCmp(&tmpIndex, indexPtr) <= 0) {
5467 	    textPtr->topIndex = *indexPtr;
5468 	    TkTextFindDisplayLineEnd(textPtr, &textPtr->topIndex, 0, NULL);
5469 	    dInfoPtr->newTopPixelOffset = 0;
5470 	    goto scheduleUpdate;
5471 	}
5472     } else {
5473 	int overlap;
5474 
5475 	/*
5476 	 * The desired line is below the bottom of the screen. If it is
5477 	 * "close" to the bottom of the screen then position it at the bottom
5478 	 * of the screen.
5479 	 */
5480 
5481 	MeasureUp(textPtr, indexPtr, close + lineHeight
5482 		- textPtr->charHeight/2, &tmpIndex, &overlap);
5483 	if (FindDLine(textPtr, dInfoPtr->dLinePtr, &tmpIndex) != NULL) {
5484 	    bottomY = dInfoPtr->maxY - dInfoPtr->y;
5485 	}
5486     }
5487 
5488     /*
5489      * If the window height is smaller than the line height, prefer to make
5490      * the top of the line visible.
5491      */
5492 
5493     if (dInfoPtr->maxY - dInfoPtr->y < lineHeight) {
5494         bottomY = lineHeight;
5495     }
5496 
5497     /*
5498      * Our job now is to arrange the display so that indexPtr appears as low
5499      * on the screen as possible but with its bottom no lower than bottomY.
5500      * BottomY is the bottom of the window if the desired line is just below
5501      * the current screen, otherwise it is a half-line lower than the center
5502      * of the window.
5503      */
5504 
5505     MeasureUp(textPtr, indexPtr, bottomY, &textPtr->topIndex,
5506 	    &dInfoPtr->newTopPixelOffset);
5507 
5508   scheduleUpdate:
5509     if (!(dInfoPtr->flags & REDRAW_PENDING)) {
5510 	Tcl_DoWhenIdle(DisplayText, textPtr);
5511     }
5512     dInfoPtr->flags |= REDRAW_PENDING|DINFO_OUT_OF_DATE|REPICK_NEEDED;
5513 }
5514 
5515 /*
5516  *--------------------------------------------------------------
5517  *
5518  * TkTextMeasureDown --
5519  *
5520  *	Given one index, find the index of the first character on the highest
5521  *	display line that would be displayed no more than "distance" pixels
5522  *	below the top of the given index.
5523  *
5524  * Results:
5525  *	The srcPtr is manipulated in place to reflect the new position. We
5526  *	return the number of pixels by which 'distance' overlaps the srcPtr.
5527  *
5528  * Side effects:
5529  *	None.
5530  *
5531  *--------------------------------------------------------------
5532  */
5533 
5534 int
TkTextMeasureDown(TkText * textPtr,TkTextIndex * srcPtr,int distance)5535 TkTextMeasureDown(
5536     TkText *textPtr,		/* Text widget in which to measure. */
5537     TkTextIndex *srcPtr,	/* Index of character from which to start
5538 				 * measuring. */
5539     int distance)		/* Vertical distance in pixels measured from
5540 				 * the top pixel in srcPtr's logical line. */
5541 {
5542     TkTextLine *lastLinePtr;
5543     DLine *dlPtr;
5544     TkTextIndex loop;
5545 
5546     lastLinePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree, textPtr,
5547 	    TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr));
5548 
5549     do {
5550 	dlPtr = LayoutDLine(textPtr, srcPtr);
5551 	dlPtr->nextPtr = NULL;
5552 
5553 	if (distance < dlPtr->height) {
5554 	    FreeDLines(textPtr, dlPtr, NULL, DLINE_FREE_TEMP);
5555 	    break;
5556 	}
5557 	distance -= dlPtr->height;
5558 	TkTextIndexForwBytes(textPtr, srcPtr, dlPtr->byteCount, &loop);
5559 	FreeDLines(textPtr, dlPtr, NULL, DLINE_FREE_TEMP);
5560 	if (loop.linePtr == lastLinePtr) {
5561 	    break;
5562 	}
5563 	*srcPtr = loop;
5564     } while (distance > 0);
5565 
5566     return distance;
5567 }
5568 
5569 /*
5570  *--------------------------------------------------------------
5571  *
5572  * MeasureUp --
5573  *
5574  *	Given one index, find the index of the first character on the highest
5575  *	display line that would be displayed no more than "distance" pixels
5576  *	above the given index.
5577  *
5578  *	If this function is called with distance=0, it simply finds the first
5579  *	index on the same display line as srcPtr. However, there is a another
5580  *	function TkTextFindDisplayLineEnd designed just for that task which is
5581  *	probably better to use.
5582  *
5583  * Results:
5584  *	*dstPtr is filled in with the index of the first character on a
5585  *	display line. The display line is found by measuring up "distance"
5586  *	pixels above the pixel just below an imaginary display line that
5587  *	contains srcPtr. If the display line that covers this coordinate
5588  *	actually extends above the coordinate, then return any excess pixels
5589  *	in *overlap, if that is non-NULL.
5590  *
5591  * Side effects:
5592  *	None.
5593  *
5594  *--------------------------------------------------------------
5595  */
5596 
5597 static void
MeasureUp(TkText * textPtr,const TkTextIndex * srcPtr,int distance,TkTextIndex * dstPtr,int * overlap)5598 MeasureUp(
5599     TkText *textPtr,		/* Text widget in which to measure. */
5600     const TkTextIndex *srcPtr,	/* Index of character from which to start
5601 				 * measuring. */
5602     int distance,		/* Vertical distance in pixels measured from
5603 				 * the pixel just below the lowest one in
5604 				 * srcPtr's line. */
5605     TkTextIndex *dstPtr,	/* Index to fill in with result. */
5606     int *overlap)		/* Used to store how much of the final index
5607 				 * returned was not covered by 'distance'. */
5608 {
5609     int lineNum;		/* Number of current line. */
5610     int bytesToCount;		/* Maximum number of bytes to measure in
5611 				 * current line. */
5612     TkTextIndex index;
5613     DLine *dlPtr, *lowestPtr;
5614 
5615     bytesToCount = srcPtr->byteIndex + 1;
5616     index.tree = srcPtr->tree;
5617     for (lineNum = TkBTreeLinesTo(textPtr, srcPtr->linePtr); lineNum >= 0;
5618 	    lineNum--) {
5619 	/*
5620 	 * Layout an entire text line (potentially > 1 display line).
5621 	 *
5622 	 * For the first line, which contains srcPtr, only layout the part up
5623 	 * through srcPtr (bytesToCount is non-infinite to accomplish this).
5624 	 * Make a list of all the display lines in backwards order (the lowest
5625 	 * DLine on the screen is first in the list).
5626 	 */
5627 
5628 	index.linePtr = TkBTreeFindLine(srcPtr->tree, textPtr, lineNum);
5629 	index.byteIndex = 0;
5630         TkTextFindDisplayLineEnd(textPtr, &index, 0, NULL);
5631         lineNum = TkBTreeLinesTo(textPtr, index.linePtr);
5632 	lowestPtr = NULL;
5633 	do {
5634 	    dlPtr = LayoutDLine(textPtr, &index);
5635 	    dlPtr->nextPtr = lowestPtr;
5636 	    lowestPtr = dlPtr;
5637 	    TkTextIndexForwBytes(textPtr, &index, dlPtr->byteCount, &index);
5638 	    bytesToCount -= dlPtr->byteCount;
5639 	} while (bytesToCount>0 && index.linePtr==dlPtr->index.linePtr);
5640 
5641 	/*
5642 	 * Scan through the display lines to see if we've covered enough
5643 	 * vertical distance. If so, save the starting index for the line at
5644 	 * the desired location. If distance was zero to start with then we
5645 	 * simply get the first index on the same display line as the original
5646 	 * index.
5647 	 */
5648 
5649 	for (dlPtr = lowestPtr; dlPtr != NULL; dlPtr = dlPtr->nextPtr) {
5650 	    distance -= dlPtr->height;
5651 	    if (distance <= 0) {
5652                 *dstPtr = dlPtr->index;
5653 
5654                 /*
5655                  * dstPtr is the start of a display line that is or is not
5656                  * the start of a logical line. If it is the start of a
5657                  * logical line, we must check whether this line is merged
5658                  * with the previous logical line, and if so we must adjust
5659                  * dstPtr to the start of the display line since a display
5660                  * line start needs to be returned.
5661                  */
5662                 if (!IsStartOfNotMergedLine(textPtr, dstPtr)) {
5663                     TkTextFindDisplayLineEnd(textPtr, dstPtr, 0, NULL);
5664                 }
5665 
5666                 if (overlap != NULL) {
5667 		    *overlap = -distance;
5668 		}
5669 		break;
5670 	    }
5671 	}
5672 
5673 	/*
5674 	 * Discard the display lines, then either return or prepare for the
5675 	 * next display line to lay out.
5676 	 */
5677 
5678 	FreeDLines(textPtr, lowestPtr, NULL, DLINE_FREE);
5679 	if (distance <= 0) {
5680 	    return;
5681 	}
5682 	bytesToCount = INT_MAX;		/* Consider all chars. in next line. */
5683     }
5684 
5685     /*
5686      * Ran off the beginning of the text. Return the first character in the
5687      * text.
5688      */
5689 
5690     TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, 0, 0, dstPtr);
5691     if (overlap != NULL) {
5692 	*overlap = 0;
5693     }
5694 }
5695 
5696 /*
5697  *--------------------------------------------------------------
5698  *
5699  * TkTextSeeCmd --
5700  *
5701  *	This function is invoked to process the "see" option for the widget
5702  *	command for text widgets. See the user documentation for details on
5703  *	what it does.
5704  *
5705  * Results:
5706  *	A standard Tcl result.
5707  *
5708  * Side effects:
5709  *	See the user documentation.
5710  *
5711  *--------------------------------------------------------------
5712  */
5713 
5714 int
TkTextSeeCmd(TkText * textPtr,Tcl_Interp * interp,int objc,Tcl_Obj * const objv[])5715 TkTextSeeCmd(
5716     TkText *textPtr,		/* Information about text widget. */
5717     Tcl_Interp *interp,		/* Current interpreter. */
5718     int objc,			/* Number of arguments. */
5719     Tcl_Obj *const objv[])	/* Argument objects. Someone else has already
5720 				 * parsed this command enough to know that
5721 				 * objv[1] is "see". */
5722 {
5723     TextDInfo *dInfoPtr = textPtr->dInfoPtr;
5724     TkTextIndex index;
5725     int x, y, width, height, lineWidth, byteCount, oneThird, delta;
5726     DLine *dlPtr;
5727     TkTextDispChunk *chunkPtr;
5728 
5729     if (objc != 3) {
5730 	Tcl_WrongNumArgs(interp, 2, objv, "index");
5731 	return TCL_ERROR;
5732     }
5733     if (TkTextGetObjIndex(interp, textPtr, objv[2], &index) != TCL_OK) {
5734 	return TCL_ERROR;
5735     }
5736 
5737     /*
5738      * If the specified position is the extra line at the end of the text,
5739      * round it back to the last real line.
5740      */
5741 
5742     if (TkBTreeLinesTo(textPtr, index.linePtr)
5743 	    == TkBTreeNumLines(index.tree, textPtr)) {
5744 	TkTextIndexBackChars(textPtr, &index, 1, &index, COUNT_INDICES);
5745     }
5746 
5747     /*
5748      * First get the desired position into the vertical range of the window.
5749      */
5750 
5751     TkTextSetYView(textPtr, &index, TK_TEXT_PICKPLACE);
5752 
5753     /*
5754      * Now make sure that the character is in view horizontally.
5755      */
5756 
5757     if (dInfoPtr->flags & DINFO_OUT_OF_DATE) {
5758 	UpdateDisplayInfo(textPtr);
5759     }
5760     lineWidth = dInfoPtr->maxX - dInfoPtr->x;
5761     if (dInfoPtr->maxLength < lineWidth) {
5762 	return TCL_OK;
5763     }
5764 
5765     /*
5766      * Find the display line containing the desired index. dlPtr may be NULL
5767      * if the widget is not mapped. [Bug #641778]
5768      */
5769 
5770     dlPtr = FindDLine(textPtr, dInfoPtr->dLinePtr, &index);
5771     if (dlPtr == NULL) {
5772 	return TCL_OK;
5773     }
5774 
5775     /*
5776      * Find the chunk within the display line that contains the desired
5777      * index. The chunks making the display line are skipped up to but not
5778      * including the one crossing index. Skipping is done based on a
5779      * byteCount offset possibly spanning several logical lines in case
5780      * they are elided.
5781      */
5782 
5783     byteCount = TkTextIndexCountBytes(textPtr, &dlPtr->index, &index);
5784     for (chunkPtr = dlPtr->chunkPtr; chunkPtr != NULL ;
5785 	    chunkPtr = chunkPtr->nextPtr) {
5786 	if (byteCount < chunkPtr->numBytes) {
5787 	    break;
5788 	}
5789 	byteCount -= chunkPtr->numBytes;
5790     }
5791 
5792     /*
5793      * Call a chunk-specific function to find the horizontal range of the
5794      * character within the chunk. chunkPtr is NULL if trying to see in elided
5795      * region.
5796      */
5797 
5798     if (chunkPtr != NULL) {
5799         chunkPtr->bboxProc(textPtr, chunkPtr, byteCount,
5800                 dlPtr->y + dlPtr->spaceAbove,
5801                 dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow,
5802                 dlPtr->baseline - dlPtr->spaceAbove, &x, &y, &width,
5803                 &height);
5804         delta = x - dInfoPtr->curXPixelOffset;
5805         oneThird = lineWidth/3;
5806         if (delta < 0) {
5807             if (delta < -oneThird) {
5808                 dInfoPtr->newXPixelOffset = x - lineWidth/2;
5809             } else {
5810                 dInfoPtr->newXPixelOffset += delta;
5811             }
5812         } else {
5813             delta -= lineWidth - width;
5814             if (delta <= 0) {
5815                 return TCL_OK;
5816             }
5817             if (delta > oneThird) {
5818                 dInfoPtr->newXPixelOffset = x - lineWidth/2;
5819             } else {
5820                 dInfoPtr->newXPixelOffset += delta;
5821             }
5822         }
5823     }
5824     dInfoPtr->flags |= DINFO_OUT_OF_DATE;
5825     if (!(dInfoPtr->flags & REDRAW_PENDING)) {
5826 	dInfoPtr->flags |= REDRAW_PENDING;
5827 	Tcl_DoWhenIdle(DisplayText, textPtr);
5828     }
5829     return TCL_OK;
5830 }
5831 
5832 /*
5833  *--------------------------------------------------------------
5834  *
5835  * TkTextXviewCmd --
5836  *
5837  *	This function is invoked to process the "xview" option for the widget
5838  *	command for text widgets. See the user documentation for details on
5839  *	what it does.
5840  *
5841  * Results:
5842  *	A standard Tcl result.
5843  *
5844  * Side effects:
5845  *	See the user documentation.
5846  *
5847  *--------------------------------------------------------------
5848  */
5849 
5850 int
TkTextXviewCmd(TkText * textPtr,Tcl_Interp * interp,int objc,Tcl_Obj * const objv[])5851 TkTextXviewCmd(
5852     TkText *textPtr,		/* Information about text widget. */
5853     Tcl_Interp *interp,		/* Current interpreter. */
5854     int objc,			/* Number of arguments. */
5855     Tcl_Obj *const objv[])	/* Argument objects. Someone else has already
5856 				 * parsed this command enough to know that
5857 				 * objv[1] is "xview". */
5858 {
5859     TextDInfo *dInfoPtr = textPtr->dInfoPtr;
5860     int type, count;
5861     double fraction;
5862 
5863     if (dInfoPtr->flags & DINFO_OUT_OF_DATE) {
5864 	UpdateDisplayInfo(textPtr);
5865     }
5866 
5867     if (objc == 2) {
5868 	GetXView(interp, textPtr, 0);
5869 	return TCL_OK;
5870     }
5871 
5872     type = TextGetScrollInfoObj(interp, textPtr, objc, objv,
5873 	    &fraction, &count);
5874     switch (type) {
5875     case TKTEXT_SCROLL_ERROR:
5876 	return TCL_ERROR;
5877     case TKTEXT_SCROLL_MOVETO:
5878 	if (fraction > 1.0) {
5879 	    fraction = 1.0;
5880 	}
5881 	if (fraction < 0) {
5882 	    fraction = 0;
5883 	}
5884 	dInfoPtr->newXPixelOffset = (int)
5885 		(fraction * dInfoPtr->maxLength + 0.5);
5886 	break;
5887     case TKTEXT_SCROLL_PAGES: {
5888 	int pixelsPerPage;
5889 
5890 	pixelsPerPage = (dInfoPtr->maxX-dInfoPtr->x) - 2*textPtr->charWidth;
5891 	if (pixelsPerPage < 1) {
5892 	    pixelsPerPage = 1;
5893 	}
5894 	dInfoPtr->newXPixelOffset += pixelsPerPage * count;
5895 	break;
5896     }
5897     case TKTEXT_SCROLL_UNITS:
5898 	dInfoPtr->newXPixelOffset += count * textPtr->charWidth;
5899 	break;
5900     case TKTEXT_SCROLL_PIXELS:
5901 	dInfoPtr->newXPixelOffset += count;
5902 	break;
5903     }
5904 
5905     dInfoPtr->flags |= DINFO_OUT_OF_DATE;
5906     if (!(dInfoPtr->flags & REDRAW_PENDING)) {
5907 	dInfoPtr->flags |= REDRAW_PENDING;
5908 	Tcl_DoWhenIdle(DisplayText, textPtr);
5909     }
5910     return TCL_OK;
5911 }
5912 
5913 /*
5914  *----------------------------------------------------------------------
5915  *
5916  * YScrollByPixels --
5917  *
5918  *	This function is called to scroll a text widget up or down by a given
5919  *	number of pixels.
5920  *
5921  * Results:
5922  *	None.
5923  *
5924  * Side effects:
5925  *	The view in textPtr's window changes to reflect the value of "offset".
5926  *
5927  *----------------------------------------------------------------------
5928  */
5929 
5930 static void
YScrollByPixels(TkText * textPtr,int offset)5931 YScrollByPixels(
5932     TkText *textPtr,		/* Widget to scroll. */
5933     int offset)			/* Amount by which to scroll, in pixels.
5934 				 * Positive means that information later in
5935 				 * text becomes visible, negative means that
5936 				 * information earlier in the text becomes
5937 				 * visible. */
5938 {
5939     TextDInfo *dInfoPtr = textPtr->dInfoPtr;
5940 
5941     if (offset < 0) {
5942 	/*
5943 	 * Now we want to measure up this number of pixels from the top of the
5944 	 * screen. But the top line may not be totally visible. Note that
5945 	 * 'count' is negative here.
5946 	 */
5947 
5948 	offset -= CalculateDisplayLineHeight(textPtr,
5949 		&textPtr->topIndex, NULL, NULL) - dInfoPtr->topPixelOffset;
5950 	MeasureUp(textPtr, &textPtr->topIndex, -offset,
5951 		&textPtr->topIndex, &dInfoPtr->newTopPixelOffset);
5952     } else if (offset > 0) {
5953 	DLine *dlPtr;
5954 	TkTextLine *lastLinePtr;
5955 	TkTextIndex newIdx;
5956 
5957 	/*
5958 	 * Scrolling down by pixels. Layout lines starting at the top index
5959 	 * and count through the desired vertical distance.
5960 	 */
5961 
5962 	lastLinePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree, textPtr,
5963 		TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr));
5964 	offset += dInfoPtr->topPixelOffset;
5965 	dInfoPtr->newTopPixelOffset = 0;
5966 	while (offset > 0) {
5967 	    dlPtr = LayoutDLine(textPtr, &textPtr->topIndex);
5968 	    dlPtr->nextPtr = NULL;
5969 	    TkTextIndexForwBytes(textPtr, &textPtr->topIndex,
5970 		    dlPtr->byteCount, &newIdx);
5971 	    if (offset <= dlPtr->height) {
5972 		/*
5973 		 * Adjust the top overlap accordingly.
5974 		 */
5975 
5976 		dInfoPtr->newTopPixelOffset = offset;
5977 	    }
5978 	    offset -= dlPtr->height;
5979 	    FreeDLines(textPtr, dlPtr, NULL, DLINE_FREE_TEMP);
5980 	    if (newIdx.linePtr == lastLinePtr || offset <= 0) {
5981 		break;
5982 	    }
5983 	    textPtr->topIndex = newIdx;
5984 	}
5985     } else {
5986 	/*
5987 	 * offset = 0, so no scrolling required.
5988 	 */
5989 
5990 	return;
5991     }
5992     if (!(dInfoPtr->flags & REDRAW_PENDING)) {
5993 	Tcl_DoWhenIdle(DisplayText, textPtr);
5994     }
5995     dInfoPtr->flags |= REDRAW_PENDING|DINFO_OUT_OF_DATE|REPICK_NEEDED;
5996 }
5997 
5998 /*
5999  *----------------------------------------------------------------------
6000  *
6001  * YScrollByLines --
6002  *
6003  *	This function is called to scroll a text widget up or down by a given
6004  *	number of lines.
6005  *
6006  * Results:
6007  *	None.
6008  *
6009  * Side effects:
6010  *	The view in textPtr's window changes to reflect the value of "offset".
6011  *
6012  *----------------------------------------------------------------------
6013  */
6014 
6015 static void
YScrollByLines(TkText * textPtr,int offset)6016 YScrollByLines(
6017     TkText *textPtr,		/* Widget to scroll. */
6018     int offset)			/* Amount by which to scroll, in display
6019 				 * lines. Positive means that information
6020 				 * later in text becomes visible, negative
6021 				 * means that information earlier in the text
6022 				 * becomes visible. */
6023 {
6024     int i, bytesToCount, lineNum;
6025     TkTextIndex newIdx, index;
6026     TkTextLine *lastLinePtr;
6027     TextDInfo *dInfoPtr = textPtr->dInfoPtr;
6028     DLine *dlPtr, *lowestPtr;
6029 
6030     if (offset < 0) {
6031 	/*
6032 	 * Must scroll up (to show earlier information in the text). The code
6033 	 * below is similar to that in MeasureUp, except that it counts lines
6034 	 * instead of pixels.
6035 	 */
6036 
6037 	bytesToCount = textPtr->topIndex.byteIndex + 1;
6038 	index.tree = textPtr->sharedTextPtr->tree;
6039 	offset--;		/* Skip line containing topIndex. */
6040 	for (lineNum = TkBTreeLinesTo(textPtr, textPtr->topIndex.linePtr);
6041 		lineNum >= 0; lineNum--) {
6042 	    index.linePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree,
6043 		    textPtr, lineNum);
6044 	    index.byteIndex = 0;
6045 	    lowestPtr = NULL;
6046 	    do {
6047 		dlPtr = LayoutDLine(textPtr, &index);
6048 		dlPtr->nextPtr = lowestPtr;
6049 		lowestPtr = dlPtr;
6050 		TkTextIndexForwBytes(textPtr, &index, dlPtr->byteCount,&index);
6051 		bytesToCount -= dlPtr->byteCount;
6052 	    } while ((bytesToCount > 0)
6053 		    && (index.linePtr == dlPtr->index.linePtr));
6054 
6055 	    for (dlPtr = lowestPtr; dlPtr != NULL; dlPtr = dlPtr->nextPtr) {
6056 		offset++;
6057 		if (offset == 0) {
6058 		    textPtr->topIndex = dlPtr->index;
6059 
6060                     /*
6061                      * topIndex is the start of a logical line. However, if
6062                      * the eol of the previous logical line is elided, then
6063                      * topIndex may be elsewhere than the first character of
6064                      * a display line, which is unwanted. Adjust to the start
6065                      * of the display line, if needed.
6066                      * topIndex is the start of a display line that is or is
6067                      * not the start of a logical line. If it is the start of
6068                      * a logical line, we must check whether this line is
6069                      * merged with the previous logical line, and if so we
6070                      * must adjust topIndex to the start of the display line.
6071                      */
6072                     if (!IsStartOfNotMergedLine(textPtr, &textPtr->topIndex)) {
6073                         TkTextFindDisplayLineEnd(textPtr, &textPtr->topIndex,
6074                                 0, NULL);
6075                     }
6076 
6077                     break;
6078 		}
6079 	    }
6080 
6081 	    /*
6082 	     * Discard the display lines, then either return or prepare for
6083 	     * the next display line to lay out.
6084 	     */
6085 
6086 	    FreeDLines(textPtr, lowestPtr, NULL, DLINE_FREE);
6087 	    if (offset >= 0) {
6088 		goto scheduleUpdate;
6089 	    }
6090 	    bytesToCount = INT_MAX;
6091 	}
6092 
6093 	/*
6094 	 * Ran off the beginning of the text. Return the first character in
6095 	 * the text, and make sure we haven't left anything overlapping the
6096 	 * top window border.
6097 	 */
6098 
6099 	TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, 0, 0,
6100 		&textPtr->topIndex);
6101 	dInfoPtr->newTopPixelOffset = 0;
6102     } else {
6103 	/*
6104 	 * Scrolling down, to show later information in the text. Just count
6105 	 * lines from the current top of the window.
6106 	 */
6107 
6108 	lastLinePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree, textPtr,
6109 		TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr));
6110 	for (i = 0; i < offset; i++) {
6111 	    dlPtr = LayoutDLine(textPtr, &textPtr->topIndex);
6112 	    if (dlPtr->length == 0 && dlPtr->height == 0) {
6113 		offset++;
6114 	    }
6115 	    dlPtr->nextPtr = NULL;
6116 	    TkTextIndexForwBytes(textPtr, &textPtr->topIndex,
6117 		    dlPtr->byteCount, &newIdx);
6118 	    FreeDLines(textPtr, dlPtr, NULL, DLINE_FREE);
6119 	    if (newIdx.linePtr == lastLinePtr) {
6120 		break;
6121 	    }
6122 	    textPtr->topIndex = newIdx;
6123 	}
6124     }
6125 
6126   scheduleUpdate:
6127     if (!(dInfoPtr->flags & REDRAW_PENDING)) {
6128 	Tcl_DoWhenIdle(DisplayText, textPtr);
6129     }
6130     dInfoPtr->flags |= REDRAW_PENDING|DINFO_OUT_OF_DATE|REPICK_NEEDED;
6131 }
6132 
6133 /*
6134  *--------------------------------------------------------------
6135  *
6136  * TkTextYviewCmd --
6137  *
6138  *	This function is invoked to process the "yview" option for the widget
6139  *	command for text widgets. See the user documentation for details on
6140  *	what it does.
6141  *
6142  * Results:
6143  *	A standard Tcl result.
6144  *
6145  * Side effects:
6146  *	See the user documentation.
6147  *
6148  *--------------------------------------------------------------
6149  */
6150 
6151 int
TkTextYviewCmd(TkText * textPtr,Tcl_Interp * interp,int objc,Tcl_Obj * const objv[])6152 TkTextYviewCmd(
6153     TkText *textPtr,		/* Information about text widget. */
6154     Tcl_Interp *interp,		/* Current interpreter. */
6155     int objc,			/* Number of arguments. */
6156     Tcl_Obj *const objv[])	/* Argument objects. Someone else has already
6157 				 * parsed this command enough to know that
6158 				 * objv[1] is "yview". */
6159 {
6160     TextDInfo *dInfoPtr = textPtr->dInfoPtr;
6161     int pickPlace, type;
6162     int pixels, count;
6163     int switchLength;
6164     double fraction;
6165     TkTextIndex index;
6166 
6167     if (dInfoPtr->flags & DINFO_OUT_OF_DATE) {
6168 	UpdateDisplayInfo(textPtr);
6169     }
6170 
6171     if (objc == 2) {
6172 	GetYView(interp, textPtr, 0);
6173 	return TCL_OK;
6174     }
6175 
6176     /*
6177      * Next, handle the old syntax: "pathName yview ?-pickplace? where"
6178      */
6179 
6180     pickPlace = 0;
6181     if (Tcl_GetString(objv[2])[0] == '-') {
6182 	const char *switchStr =
6183 		Tcl_GetStringFromObj(objv[2], &switchLength);
6184 
6185 	if ((switchLength >= 2) && (strncmp(switchStr, "-pickplace",
6186 		(unsigned) switchLength) == 0)) {
6187 	    pickPlace = 1;
6188 	    if (objc != 4) {
6189 		Tcl_WrongNumArgs(interp, 3, objv, "lineNum|index");
6190 		return TCL_ERROR;
6191 	    }
6192 	}
6193     }
6194     if ((objc == 3) || pickPlace) {
6195 	int lineNum;
6196 
6197 	if (Tcl_GetIntFromObj(interp, objv[2+pickPlace], &lineNum) == TCL_OK) {
6198 	    TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr,
6199 		    lineNum, 0, &index);
6200 	    TkTextSetYView(textPtr, &index, 0);
6201 	    return TCL_OK;
6202 	}
6203 
6204 	/*
6205 	 * The argument must be a regular text index.
6206 	 */
6207 
6208 	Tcl_ResetResult(interp);
6209 	if (TkTextGetObjIndex(interp, textPtr, objv[2+pickPlace],
6210 		&index) != TCL_OK) {
6211 	    return TCL_ERROR;
6212 	}
6213 	TkTextSetYView(textPtr, &index, (pickPlace ? TK_TEXT_PICKPLACE : 0));
6214 	return TCL_OK;
6215     }
6216 
6217     /*
6218      * New syntax: dispatch based on objv[2].
6219      */
6220 
6221     type = TextGetScrollInfoObj(interp, textPtr, objc,objv, &fraction, &count);
6222     switch (type) {
6223     case TKTEXT_SCROLL_ERROR:
6224 	return TCL_ERROR;
6225     case TKTEXT_SCROLL_MOVETO: {
6226 	int numPixels = TkBTreeNumPixels(textPtr->sharedTextPtr->tree,
6227 		textPtr);
6228 	int topMostPixel;
6229 
6230 	if (numPixels == 0) {
6231 	    /*
6232 	     * If the window is totally empty no scrolling is needed, and the
6233 	     * TkTextMakePixelIndex call below will fail.
6234 	     */
6235 
6236 	    break;
6237 	}
6238 	if (fraction > 1.0) {
6239 	    fraction = 1.0;
6240 	}
6241 	if (fraction < 0) {
6242 	    fraction = 0;
6243 	}
6244 
6245 	/*
6246 	 * Calculate the pixel count for the new topmost pixel in the topmost
6247 	 * line of the window. Note that the interpretation of 'fraction' is
6248 	 * that it counts from 0 (top pixel in buffer) to 1.0 (one pixel past
6249 	 * the last pixel in buffer).
6250 	 */
6251 
6252 	topMostPixel = (int) (0.5 + fraction * numPixels);
6253 	if (topMostPixel >= numPixels) {
6254 	    topMostPixel = numPixels -1;
6255 	}
6256 
6257 	/*
6258 	 * This function returns the number of pixels by which the given line
6259 	 * should overlap the top of the visible screen.
6260 	 *
6261 	 * This is then used to provide smooth scrolling.
6262 	 */
6263 
6264 	pixels = TkTextMakePixelIndex(textPtr, topMostPixel, &index);
6265 	TkTextSetYView(textPtr, &index, pixels);
6266 	break;
6267     }
6268     case TKTEXT_SCROLL_PAGES: {
6269 	/*
6270 	 * Scroll up or down by screenfuls. Actually, use the window height
6271 	 * minus two lines, so that there's some overlap between adjacent
6272 	 * pages.
6273 	 */
6274 
6275 	int height = dInfoPtr->maxY - dInfoPtr->y;
6276 
6277 	if (textPtr->charHeight * 4 >= height) {
6278 	    /*
6279 	     * A single line is more than a quarter of the display. We choose
6280 	     * to scroll by 3/4 of the height instead.
6281 	     */
6282 
6283 	    pixels = 3*height/4;
6284 	    if (pixels < textPtr->charHeight) {
6285 		/*
6286 		 * But, if 3/4 of the height is actually less than a single
6287 		 * typical character height, then scroll by the minimum of the
6288 		 * linespace or the total height.
6289 		 */
6290 
6291 		if (textPtr->charHeight < height) {
6292 		    pixels = textPtr->charHeight;
6293 		} else {
6294 		    pixels = height;
6295 		}
6296 	    }
6297 	    pixels *= count;
6298 	} else {
6299 	    pixels = (height - 2*textPtr->charHeight)*count;
6300 	}
6301 	YScrollByPixels(textPtr, pixels);
6302 	break;
6303     }
6304     case TKTEXT_SCROLL_PIXELS:
6305 	YScrollByPixels(textPtr, count);
6306 	break;
6307     case TKTEXT_SCROLL_UNITS:
6308 	YScrollByLines(textPtr, count);
6309 	break;
6310     }
6311     return TCL_OK;
6312 }
6313 
6314 /*
6315  *--------------------------------------------------------------
6316  *
6317  * TkTextPendingsync --
6318  *
6319  *	This function checks if any line heights are not up-to-date.
6320  *
6321  * Results:
6322  *	Returns a boolean true if it is the case, or false if all line
6323  *      heights are up-to-date.
6324  *
6325  * Side effects:
6326  *	None.
6327  *
6328  *--------------------------------------------------------------
6329  */
6330 
6331 Bool
TkTextPendingsync(TkText * textPtr)6332 TkTextPendingsync(
6333     TkText *textPtr)		/* Information about text widget. */
6334 {
6335     TextDInfo *dInfoPtr = textPtr->dInfoPtr;
6336 
6337     return ((dInfoPtr->flags & OUT_OF_SYNC) != 0);
6338 }
6339 
6340 /*
6341  *--------------------------------------------------------------
6342  *
6343  * TkTextScanCmd --
6344  *
6345  *	This function is invoked to process the "scan" option for the widget
6346  *	command for text widgets. See the user documentation for details on
6347  *	what it does.
6348  *
6349  * Results:
6350  *	A standard Tcl result.
6351  *
6352  * Side effects:
6353  *	See the user documentation.
6354  *
6355  *--------------------------------------------------------------
6356  */
6357 
6358 int
TkTextScanCmd(TkText * textPtr,Tcl_Interp * interp,int objc,Tcl_Obj * const objv[])6359 TkTextScanCmd(
6360     TkText *textPtr,	/* Information about text widget. */
6361     Tcl_Interp *interp,		/* Current interpreter. */
6362     int objc,			/* Number of arguments. */
6363     Tcl_Obj *const objv[])	/* Argument objects. Someone else has already
6364 				 * parsed this command enough to know that
6365 				 * objv[1] is "scan". */
6366 {
6367     TextDInfo *dInfoPtr = textPtr->dInfoPtr;
6368     TkTextIndex index;
6369     int c, x, y, totalScroll, gain=10;
6370     size_t length;
6371 
6372     if ((objc != 5) && (objc != 6)) {
6373 	Tcl_WrongNumArgs(interp, 2, objv, "mark x y");
6374 	Tcl_AppendResult(interp, " or \"", Tcl_GetString(objv[0]),
6375 		" scan dragto x y ?gain?\"", NULL);
6376 	/*
6377 	 * Ought to be:
6378 	 * Tcl_WrongNumArgs(interp, 2, objc, "dragto x y ?gain?");
6379 	 */
6380 	return TCL_ERROR;
6381     }
6382     if (Tcl_GetIntFromObj(interp, objv[3], &x) != TCL_OK) {
6383 	return TCL_ERROR;
6384     }
6385     if (Tcl_GetIntFromObj(interp, objv[4], &y) != TCL_OK) {
6386 	return TCL_ERROR;
6387     }
6388     if ((objc == 6) && (Tcl_GetIntFromObj(interp, objv[5], &gain) != TCL_OK)) {
6389 	return TCL_ERROR;
6390     }
6391     c = Tcl_GetString(objv[2])[0];
6392     length = strlen(Tcl_GetString(objv[2]));
6393     if (c=='d' && strncmp(Tcl_GetString(objv[2]), "dragto", length)==0) {
6394 	int newX, maxX;
6395 
6396 	/*
6397 	 * Amplify the difference between the current position and the mark
6398 	 * position to compute how much the view should shift, then update the
6399 	 * mark position to correspond to the new view. If we run off the edge
6400 	 * of the text, reset the mark point so that the current position
6401 	 * continues to correspond to the edge of the window. This means that
6402 	 * the picture will start dragging as soon as the mouse reverses
6403 	 * direction (without this reset, might have to slide mouse a long
6404 	 * ways back before the picture starts moving again).
6405 	 */
6406 
6407 	newX = dInfoPtr->scanMarkXPixel + gain*(dInfoPtr->scanMarkX - x);
6408 	maxX = 1 + dInfoPtr->maxLength - (dInfoPtr->maxX - dInfoPtr->x);
6409 	if (newX < 0) {
6410 	    newX = 0;
6411 	    dInfoPtr->scanMarkXPixel = 0;
6412 	    dInfoPtr->scanMarkX = x;
6413 	} else if (newX > maxX) {
6414 	    newX = maxX;
6415 	    dInfoPtr->scanMarkXPixel = maxX;
6416 	    dInfoPtr->scanMarkX = x;
6417 	}
6418 	dInfoPtr->newXPixelOffset = newX;
6419 
6420 	totalScroll = gain*(dInfoPtr->scanMarkY - y);
6421 	if (totalScroll != dInfoPtr->scanTotalYScroll) {
6422 	    index = textPtr->topIndex;
6423 	    YScrollByPixels(textPtr, totalScroll-dInfoPtr->scanTotalYScroll);
6424 	    dInfoPtr->scanTotalYScroll = totalScroll;
6425 	    if ((index.linePtr == textPtr->topIndex.linePtr) &&
6426 		    (index.byteIndex == textPtr->topIndex.byteIndex)) {
6427 		dInfoPtr->scanTotalYScroll = 0;
6428 		dInfoPtr->scanMarkY = y;
6429 	    }
6430 	}
6431 	dInfoPtr->flags |= DINFO_OUT_OF_DATE;
6432 	if (!(dInfoPtr->flags & REDRAW_PENDING)) {
6433 	    dInfoPtr->flags |= REDRAW_PENDING;
6434 	    Tcl_DoWhenIdle(DisplayText, textPtr);
6435 	}
6436     } else if (c=='m' && strncmp(Tcl_GetString(objv[2]), "mark", length)==0) {
6437 	dInfoPtr->scanMarkXPixel = dInfoPtr->newXPixelOffset;
6438 	dInfoPtr->scanMarkX = x;
6439 	dInfoPtr->scanTotalYScroll = 0;
6440 	dInfoPtr->scanMarkY = y;
6441     } else {
6442 	Tcl_SetObjResult(interp, Tcl_ObjPrintf(
6443 		"bad scan option \"%s\": must be mark or dragto",
6444 		Tcl_GetString(objv[2])));
6445 	Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "INDEX", "scan option",
6446 		Tcl_GetString(objv[2]), NULL);
6447 	return TCL_ERROR;
6448     }
6449     return TCL_OK;
6450 }
6451 
6452 /*
6453  *----------------------------------------------------------------------
6454  *
6455  * GetXView --
6456  *
6457  *	This function computes the fractions that indicate what's visible in a
6458  *	text window and, optionally, evaluates a Tcl script to report them to
6459  *	the text's associated scrollbar.
6460  *
6461  * Results:
6462  *	If report is zero, then the interp's result is filled in with two real
6463  *	numbers separated by a space, giving the position of the left and
6464  *	right edges of the window as fractions from 0 to 1, where 0 means the
6465  *	left edge of the text and 1 means the right edge. If report is
6466  *	non-zero, then the interp's result isn't modified directly, but
6467  *	instead a script is evaluated in interp to report the new horizontal
6468  *	scroll position to the scrollbar (if the scroll position hasn't
6469  *	changed then no script is invoked).
6470  *
6471  * Side effects:
6472  *	None.
6473  *
6474  *----------------------------------------------------------------------
6475  */
6476 
6477 static void
GetXView(Tcl_Interp * interp,TkText * textPtr,int report)6478 GetXView(
6479     Tcl_Interp *interp,		/* If "report" is FALSE, string describing
6480 				 * visible range gets stored in the interp's
6481 				 * result. */
6482     TkText *textPtr,		/* Information about text widget. */
6483     int report)			/* Non-zero means report info to scrollbar if
6484 				 * it has changed. */
6485 {
6486     TextDInfo *dInfoPtr = textPtr->dInfoPtr;
6487     double first, last;
6488     int code;
6489     Tcl_Obj *listObj;
6490 
6491     if (dInfoPtr->maxLength > 0) {
6492 	first = ((double) dInfoPtr->curXPixelOffset)
6493 		/ dInfoPtr->maxLength;
6494 	last = ((double) (dInfoPtr->curXPixelOffset + dInfoPtr->maxX
6495 		- dInfoPtr->x))/dInfoPtr->maxLength;
6496 	if (last > 1.0) {
6497 	    last = 1.0;
6498 	}
6499     } else {
6500 	first = 0;
6501 	last = 1.0;
6502     }
6503     if (!report) {
6504 	listObj = Tcl_NewListObj(0, NULL);
6505 	Tcl_ListObjAppendElement(interp, listObj, Tcl_NewDoubleObj(first));
6506 	Tcl_ListObjAppendElement(interp, listObj, Tcl_NewDoubleObj(last));
6507 	Tcl_SetObjResult(interp, listObj);
6508 	return;
6509     }
6510     if (FP_EQUAL_SCALE(first, dInfoPtr->xScrollFirst, dInfoPtr->maxLength) &&
6511 	    FP_EQUAL_SCALE(last, dInfoPtr->xScrollLast, dInfoPtr->maxLength)) {
6512 	return;
6513     }
6514     dInfoPtr->xScrollFirst = first;
6515     dInfoPtr->xScrollLast = last;
6516     if (textPtr->xScrollCmd != NULL) {
6517 	char buf1[TCL_DOUBLE_SPACE+1];
6518 	char buf2[TCL_DOUBLE_SPACE+1];
6519 	Tcl_DString buf;
6520 
6521 	buf1[0] = ' ';
6522 	buf2[0] = ' ';
6523 	Tcl_PrintDouble(NULL, first, buf1+1);
6524 	Tcl_PrintDouble(NULL, last, buf2+1);
6525 	Tcl_DStringInit(&buf);
6526 	Tcl_DStringAppend(&buf, textPtr->xScrollCmd, -1);
6527 	Tcl_DStringAppend(&buf, buf1, -1);
6528 	Tcl_DStringAppend(&buf, buf2, -1);
6529 	code = Tcl_EvalEx(interp, Tcl_DStringValue(&buf), -1, TCL_EVAL_GLOBAL);
6530 	Tcl_DStringFree(&buf);
6531 	if (code != TCL_OK) {
6532 	    Tcl_AddErrorInfo(interp,
6533 		    "\n    (horizontal scrolling command executed by text)");
6534 	    Tcl_BackgroundException(interp, code);
6535 	}
6536     }
6537 }
6538 
6539 /*
6540  *----------------------------------------------------------------------
6541  *
6542  * GetYPixelCount --
6543  *
6544  *	How many pixels are there between the absolute top of the widget and
6545  *	the top of the given DLine.
6546  *
6547  *	While this function will work for any valid DLine, it is only ever
6548  *	called when dlPtr is the first display line in the widget (by
6549  *	'GetYView'). This means that usually this function is a very quick
6550  *	calculation, since it can use the pre-calculated linked-list of DLines
6551  *	for height information.
6552  *
6553  *	The only situation where this breaks down is if dlPtr's logical line
6554  *	wraps enough times to fill the text widget's current view - in this
6555  *	case we won't have enough dlPtrs in the linked list to be able to
6556  *	subtract off what we want.
6557  *
6558  * Results:
6559  *	The number of pixels.
6560  *
6561  *	This value has a valid range between '0' (the very top of the widget)
6562  *	and the number of pixels in the total widget minus the pixel-height of
6563  *	the last line.
6564  *
6565  * Side effects:
6566  *	None.
6567  *
6568  *----------------------------------------------------------------------
6569  */
6570 
6571 static int
GetYPixelCount(TkText * textPtr,DLine * dlPtr)6572 GetYPixelCount(
6573     TkText *textPtr,		/* Information about text widget. */
6574     DLine *dlPtr)		/* Information about the layout of a given
6575 				 * index. */
6576 {
6577     TkTextLine *linePtr = dlPtr->index.linePtr;
6578     int count;
6579 
6580     /*
6581      * Get the pixel count to the top of dlPtr's logical line. The rest of the
6582      * function is then concerned with updating 'count' for any difference
6583      * between the top of the logical line and the display line.
6584      */
6585 
6586     count = TkBTreePixelsTo(textPtr, linePtr);
6587 
6588     /*
6589      * For the common case where this dlPtr is also the start of the logical
6590      * line, we can return right away.
6591      */
6592 
6593     if (IsStartOfNotMergedLine(textPtr, &dlPtr->index)) {
6594 	return count;
6595     }
6596 
6597     /*
6598      * Add on the logical line's height to reach one pixel beyond the bottom
6599      * of the logical line. And then subtract off the heights of all the
6600      * display lines from dlPtr to the end of its logical line.
6601      *
6602      * A different approach would be to lay things out from the start of the
6603      * logical line until we reach dlPtr, but since none of those are
6604      * pre-calculated, it'll usually take a lot longer. (But there are cases
6605      * where it would be more efficient: say if we're on the second of 1000
6606      * wrapped lines all from a single logical line - but that sort of
6607      * optimization is left for the future).
6608      */
6609 
6610     count += TkBTreeLinePixelCount(textPtr, linePtr);
6611 
6612     do {
6613 	count -= dlPtr->height;
6614 	if (dlPtr->nextPtr == NULL) {
6615 	    /*
6616 	     * We've run out of pre-calculated display lines, so we have to
6617 	     * lay them out ourselves until the end of the logical line. Here
6618 	     * is where we could be clever and ask: what's faster, to layout
6619 	     * all lines from here to line-end, or all lines from the original
6620 	     * dlPtr to the line-start? We just assume the former.
6621 	     */
6622 
6623 	    TkTextIndex index;
6624 	    int notFirst = 0;
6625 
6626 	    while (1) {
6627 		TkTextIndexForwBytes(textPtr, &dlPtr->index,
6628 			dlPtr->byteCount, &index);
6629 		if (notFirst) {
6630 		    FreeDLines(textPtr, dlPtr, NULL, DLINE_FREE_TEMP);
6631 		}
6632 		if (index.linePtr != linePtr) {
6633 		    break;
6634 		}
6635 		dlPtr = LayoutDLine(textPtr, &index);
6636 
6637 		if (tkTextDebug) {
6638 		    char string[TK_POS_CHARS];
6639 
6640 		    /*
6641 		     * Debugging is enabled, so keep a log of all the lines
6642 		     * whose height was recalculated. The test suite uses this
6643 		     * information.
6644 		     */
6645 
6646 		    TkTextPrintIndex(textPtr, &index, string);
6647 		    LOG("tk_textHeightCalc", string);
6648 		}
6649 		count -= dlPtr->height;
6650 		notFirst = 1;
6651 	    }
6652 	    break;
6653 	}
6654 	dlPtr = dlPtr->nextPtr;
6655     } while (dlPtr->index.linePtr == linePtr);
6656 
6657     return count;
6658 }
6659 
6660 /*
6661  *----------------------------------------------------------------------
6662  *
6663  * GetYView --
6664  *
6665  *	This function computes the fractions that indicate what's visible in a
6666  *	text window and, optionally, evaluates a Tcl script to report them to
6667  *	the text's associated scrollbar.
6668  *
6669  * Results:
6670  *	If report is zero, then the interp's result is filled in with two real
6671  *	numbers separated by a space, giving the position of the top and
6672  *	bottom of the window as fractions from 0 to 1, where 0 means the
6673  *	beginning of the text and 1 means the end. If report is non-zero, then
6674  *	the interp's result isn't modified directly, but a script is evaluated
6675  *	in interp to report the new scroll position to the scrollbar (if the
6676  *	scroll position hasn't changed then no script is invoked).
6677  *
6678  * Side effects:
6679  *	None.
6680  *
6681  *----------------------------------------------------------------------
6682  */
6683 
6684 static void
GetYView(Tcl_Interp * interp,TkText * textPtr,int report)6685 GetYView(
6686     Tcl_Interp *interp,		/* If "report" is FALSE, string describing
6687 				 * visible range gets stored in the interp's
6688 				 * result. */
6689     TkText *textPtr,		/* Information about text widget. */
6690     int report)			/* Non-zero means report info to scrollbar if
6691 				 * it has changed. */
6692 {
6693     TextDInfo *dInfoPtr = textPtr->dInfoPtr;
6694     double first, last;
6695     DLine *dlPtr;
6696     int totalPixels, code, count;
6697     Tcl_Obj *listObj;
6698 
6699     dlPtr = dInfoPtr->dLinePtr;
6700 
6701     if (dlPtr == NULL) {
6702 	return;
6703     }
6704 
6705     totalPixels = TkBTreeNumPixels(textPtr->sharedTextPtr->tree, textPtr);
6706 
6707     if (totalPixels == 0) {
6708 	first = 0.0;
6709 	last = 1.0;
6710     } else {
6711 	/*
6712 	 * Get the pixel count for the first visible pixel of the first
6713 	 * visible line. If the first visible line is only partially visible,
6714 	 * then we use 'topPixelOffset' to get the difference.
6715 	 */
6716 
6717 	count = GetYPixelCount(textPtr, dlPtr);
6718 	first = (count + dInfoPtr->topPixelOffset) / (double) totalPixels;
6719 
6720 	/*
6721 	 * Add on the total number of visible pixels to get the count to one
6722 	 * pixel _past_ the last visible pixel. This is how the 'yview'
6723 	 * command is documented, and also explains why we are dividing by
6724 	 * 'totalPixels' and not 'totalPixels-1'.
6725 	 */
6726 
6727 	while (1) {
6728 	    int extra;
6729 
6730 	    count += dlPtr->height;
6731 	    extra = dlPtr->y + dlPtr->height - dInfoPtr->maxY;
6732 	    if (extra > 0) {
6733 		/*
6734 		 * This much of the last line is not visible, so don't count
6735 		 * these pixels. Since we've reached the bottom of the window,
6736 		 * we break out of the loop.
6737 		 */
6738 
6739 		count -= extra;
6740 		break;
6741 	    }
6742 	    if (dlPtr->nextPtr == NULL) {
6743 		break;
6744 	    }
6745 	    dlPtr = dlPtr->nextPtr;
6746 	}
6747 
6748 	if (count > totalPixels) {
6749 	    /*
6750 	     * It can be possible, if we do not update each line's pixelHeight
6751 	     * cache when we lay out individual DLines that the count
6752 	     * generated here is more up-to-date than that maintained by the
6753 	     * BTree. In such a case, the best we can do here is to fix up
6754 	     * 'count' and continue, which might result in small, temporary
6755 	     * perturbations to the size of the scrollbar. This is basically
6756 	     * harmless, but in a perfect world we would not have this
6757 	     * problem.
6758 	     *
6759 	     * For debugging purposes, if anyone wishes to improve the text
6760 	     * widget further, the following 'panic' can be activated. In
6761 	     * principle it should be possible to ensure the BTree is always
6762 	     * at least as up to date as the display, so in the future we
6763 	     * might be able to leave the 'panic' in permanently when we
6764 	     * believe we have resolved the cache synchronisation issue.
6765 	     *
6766 	     * However, to achieve that goal would, I think, require a fairly
6767 	     * substantial refactorisation of the code in this file so that
6768 	     * there is much more obvious and explicit coordination between
6769 	     * calls to LayoutDLine and updating of each TkTextLine's
6770 	     * pixelHeight. The complicated bit is that LayoutDLine deals with
6771 	     * individual display lines, but pixelHeight is for a logical
6772 	     * line.
6773 	     */
6774 
6775 #if 0
6776 	    Tcl_Panic("Counted more pixels (%d) than expected (%d) total "
6777 		    "pixels in text widget scroll bar calculation.", count,
6778 		    totalPixels);
6779 #endif
6780 	    count = totalPixels;
6781 	}
6782 
6783 	last = ((double) count)/((double)totalPixels);
6784     }
6785 
6786     if (!report) {
6787 	listObj = Tcl_NewListObj(0,NULL);
6788 	Tcl_ListObjAppendElement(interp, listObj, Tcl_NewDoubleObj(first));
6789 	Tcl_ListObjAppendElement(interp, listObj, Tcl_NewDoubleObj(last));
6790 	Tcl_SetObjResult(interp, listObj);
6791 	return;
6792     }
6793 
6794     if (FP_EQUAL_SCALE(first, dInfoPtr->yScrollFirst, totalPixels) &&
6795 	    FP_EQUAL_SCALE(last, dInfoPtr->yScrollLast, totalPixels)) {
6796 	return;
6797     }
6798 
6799     dInfoPtr->yScrollFirst = first;
6800     dInfoPtr->yScrollLast = last;
6801     if (textPtr->yScrollCmd != NULL) {
6802 	char buf1[TCL_DOUBLE_SPACE+1];
6803 	char buf2[TCL_DOUBLE_SPACE+1];
6804 	Tcl_DString buf;
6805 
6806 	buf1[0] = ' ';
6807 	buf2[0] = ' ';
6808 	Tcl_PrintDouble(NULL, first, buf1+1);
6809 	Tcl_PrintDouble(NULL, last, buf2+1);
6810 	Tcl_DStringInit(&buf);
6811 	Tcl_DStringAppend(&buf, textPtr->yScrollCmd, -1);
6812 	Tcl_DStringAppend(&buf, buf1, -1);
6813 	Tcl_DStringAppend(&buf, buf2, -1);
6814 	code = Tcl_EvalEx(interp, Tcl_DStringValue(&buf), -1, TCL_EVAL_GLOBAL);
6815 	Tcl_DStringFree(&buf);
6816 	if (code != TCL_OK) {
6817 	    Tcl_AddErrorInfo(interp,
6818 		    "\n    (vertical scrolling command executed by text)");
6819 	    Tcl_BackgroundException(interp, code);
6820 	}
6821     }
6822 }
6823 
6824 /*
6825  *----------------------------------------------------------------------
6826  *
6827  * AsyncUpdateYScrollbar --
6828  *
6829  *	This function is called to update the vertical scrollbar asychronously
6830  *	as the pixel height calculations progress for lines in the widget.
6831  *
6832  * Results:
6833  *	None.
6834  *
6835  * Side effects:
6836  *	See 'GetYView'. In particular the scrollbar position and size may be
6837  *	changed.
6838  *
6839  *----------------------------------------------------------------------
6840  */
6841 
6842 static void
AsyncUpdateYScrollbar(ClientData clientData)6843 AsyncUpdateYScrollbar(
6844     ClientData clientData)	/* Information about widget. */
6845 {
6846     TkText *textPtr = (TkText *)clientData;
6847 
6848     textPtr->dInfoPtr->scrollbarTimer = NULL;
6849 
6850     if (!(textPtr->flags & DESTROYED)) {
6851 	GetYView(textPtr->interp, textPtr, 1);
6852     }
6853 
6854     if (textPtr->refCount-- <= 1) {
6855 	ckfree(textPtr);
6856     }
6857 }
6858 
6859 /*
6860  *----------------------------------------------------------------------
6861  *
6862  * FindDLine --
6863  *
6864  *	This function is called to find the DLine corresponding to a given
6865  *	text index.
6866  *
6867  * Results:
6868  *	The return value is a pointer to the first DLine found in the list
6869  *	headed by dlPtr that displays information at or after the specified
6870  *	position. If there is no such line in the list then NULL is returned.
6871  *
6872  * Side effects:
6873  *	None.
6874  *
6875  *----------------------------------------------------------------------
6876  */
6877 
6878 static DLine *
FindDLine(TkText * textPtr,DLine * dlPtr,const TkTextIndex * indexPtr)6879 FindDLine(
6880     TkText *textPtr,		/* Widget record for text widget. */
6881     DLine *dlPtr,	/* Pointer to first in list of DLines to
6882 				 * search. */
6883     const TkTextIndex *indexPtr)/* Index of desired character. */
6884 {
6885     DLine *dlPtrPrev;
6886     TkTextIndex indexPtr2;
6887 
6888     if (dlPtr == NULL) {
6889 	return NULL;
6890     }
6891     if (TkBTreeLinesTo(NULL, indexPtr->linePtr)
6892 	    < TkBTreeLinesTo(NULL, dlPtr->index.linePtr)) {
6893 	/*
6894 	 * The first display line is already past the desired line.
6895 	 */
6896 
6897 	return dlPtr;
6898     }
6899 
6900     /*
6901      * The display line containing the desired index is such that the index
6902      * of the first character of this display line is at or before the
6903      * desired index, and the index of the first character of the next
6904      * display line is after the desired index.
6905      */
6906 
6907     while (TkTextIndexCmp(&dlPtr->index,indexPtr) < 0) {
6908         dlPtrPrev = dlPtr;
6909         dlPtr = dlPtr->nextPtr;
6910         if (dlPtr == NULL) {
6911             /*
6912              * We're past the last display line, either because the desired
6913              * index lies past the visible text, or because the desired index
6914              * is on the last display line.
6915              */
6916             indexPtr2 = dlPtrPrev->index;
6917             TkTextIndexForwBytes(textPtr, &indexPtr2, dlPtrPrev->byteCount,
6918                     &indexPtr2);
6919             if (TkTextIndexCmp(&indexPtr2,indexPtr) > 0) {
6920                 /*
6921                  * The desired index is on the last display line.
6922                  * --> return this display line.
6923                  */
6924                 dlPtr = dlPtrPrev;
6925             } else {
6926                 /*
6927                  * The desired index is past the visible text. There is no
6928                  * display line displaying something at the desired index.
6929                  * --> return NULL.
6930                  */
6931             }
6932             break;
6933         }
6934         if (TkTextIndexCmp(&dlPtr->index,indexPtr) > 0) {
6935             /*
6936              * If we're here then we would normally expect that:
6937              *   dlPtrPrev->index  <=  indexPtr  <  dlPtr->index
6938              * i.e. we have found the searched display line being dlPtr.
6939              * However it is possible that some DLines were unlinked
6940              * previously, leading to a situation where going through
6941              * the list of display lines skips display lines that did
6942              * exist just a moment ago.
6943              */
6944             indexPtr2 = dlPtrPrev->index;
6945             TkTextIndexForwBytes(textPtr, &indexPtr2, dlPtrPrev->byteCount,
6946                     &indexPtr2);
6947             if (TkTextIndexCmp(&indexPtr2,indexPtr) > 0) {
6948                 /*
6949                  * Confirmed:
6950                  *   dlPtrPrev->index  <=  indexPtr  <  dlPtr->index
6951                  * --> return dlPtrPrev.
6952                  */
6953                 dlPtr = dlPtrPrev;
6954             } else {
6955                 /*
6956                  * The last (rightmost) index shown by dlPtrPrev is still
6957                  * before the desired index. This may be because there was
6958                  * previously a display line between dlPtrPrev and dlPtr
6959                  * and this display line has been unlinked.
6960                  * --> return dlPtr.
6961                  */
6962             }
6963             break;
6964         }
6965     }
6966 
6967     return dlPtr;
6968 }
6969 
6970 /*
6971  *----------------------------------------------------------------------
6972  *
6973  * IsStartOfNotMergedLine --
6974  *
6975  *	This function checks whether the given index is the start of a
6976  *      logical line that is not merged with the previous logical line
6977  *      (due to elision of the eol of the previous line).
6978  *
6979  * Results:
6980  *	Returns whether the given index denotes the first index of a
6981 *       logical line not merged with its previous line.
6982  *
6983  * Side effects:
6984  *	None.
6985  *
6986  *----------------------------------------------------------------------
6987  */
6988 
6989 static int
IsStartOfNotMergedLine(TkText * textPtr,const TkTextIndex * indexPtr)6990 IsStartOfNotMergedLine(
6991       TkText *textPtr,              /* Widget record for text widget. */
6992       const TkTextIndex *indexPtr)  /* Index to check. */
6993 {
6994     TkTextIndex indexPtr2;
6995 
6996     if (indexPtr->byteIndex != 0) {
6997         /*
6998          * Not the start of a logical line.
6999          */
7000         return 0;
7001     }
7002 
7003     if (TkTextIndexBackBytes(textPtr, indexPtr, 1, &indexPtr2)) {
7004         /*
7005          * indexPtr is the first index of the text widget.
7006          */
7007         return 1;
7008     }
7009 
7010     if (!TkTextIsElided(textPtr, &indexPtr2, NULL)) {
7011         /*
7012          * The eol of the line just before indexPtr is elided.
7013          */
7014         return 1;
7015     }
7016 
7017     return 0;
7018 }
7019 
7020 /*
7021  *----------------------------------------------------------------------
7022  *
7023  * TkTextPixelIndex --
7024  *
7025  *	Given an (x,y) coordinate on the screen, find the location of the
7026  *	character closest to that location.
7027  *
7028  * Results:
7029  *	The index at *indexPtr is modified to refer to the character on the
7030  *	display that is closest to (x,y).
7031  *
7032  * Side effects:
7033  *	None.
7034  *
7035  *----------------------------------------------------------------------
7036  */
7037 
7038 void
TkTextPixelIndex(TkText * textPtr,int x,int y,TkTextIndex * indexPtr,int * nearest)7039 TkTextPixelIndex(
7040     TkText *textPtr,		/* Widget record for text widget. */
7041     int x, int y,		/* Pixel coordinates of point in widget's
7042 				 * window. */
7043     TkTextIndex *indexPtr,	/* This index gets filled in with the index of
7044 				 * the character nearest to (x,y). */
7045     int *nearest)		/* If non-NULL then gets set to 0 if (x,y) is
7046 				 * actually over the returned index, and 1 if
7047 				 * it is just nearby (e.g. if x,y is on the
7048 				 * border of the widget). */
7049 {
7050     TextDInfo *dInfoPtr = textPtr->dInfoPtr;
7051     DLine *dlPtr, *validDlPtr;
7052     int nearby = 0;
7053 
7054     /*
7055      * Make sure that all of the layout information about what's displayed
7056      * where on the screen is up-to-date.
7057      */
7058 
7059     if (dInfoPtr->flags & DINFO_OUT_OF_DATE) {
7060 	UpdateDisplayInfo(textPtr);
7061     }
7062 
7063     /*
7064      * If the coordinates are above the top of the window, then adjust them to
7065      * refer to the upper-right corner of the window. If they're off to one
7066      * side or the other, then adjust to the closest side.
7067      */
7068 
7069     if (y < dInfoPtr->y) {
7070 	y = dInfoPtr->y;
7071 	x = dInfoPtr->x;
7072 	nearby = 1;
7073     }
7074     if (x >= dInfoPtr->maxX) {
7075 	x = dInfoPtr->maxX - 1;
7076 	nearby = 1;
7077     }
7078     if (x < dInfoPtr->x) {
7079 	x = dInfoPtr->x;
7080 	nearby = 1;
7081     }
7082 
7083     /*
7084      * Find the display line containing the desired y-coordinate.
7085      */
7086 
7087     if (dInfoPtr->dLinePtr == NULL) {
7088 	if (nearest != NULL) {
7089 	    *nearest = 1;
7090 	}
7091 	*indexPtr = textPtr->topIndex;
7092 	return;
7093     }
7094     for (dlPtr = validDlPtr = dInfoPtr->dLinePtr;
7095 	    y >= (dlPtr->y + dlPtr->height);
7096 	    dlPtr = dlPtr->nextPtr) {
7097 	if (dlPtr->chunkPtr != NULL) {
7098 	    validDlPtr = dlPtr;
7099 	}
7100 	if (dlPtr->nextPtr == NULL) {
7101 	    /*
7102 	     * Y-coordinate is off the bottom of the displayed text. Use the
7103 	     * last character on the last line.
7104 	     */
7105 
7106 	    x = dInfoPtr->maxX - 1;
7107 	    nearby = 1;
7108 	    break;
7109 	}
7110     }
7111     if (dlPtr->chunkPtr == NULL) {
7112 	dlPtr = validDlPtr;
7113     }
7114 
7115     if (nearest != NULL) {
7116 	*nearest = nearby;
7117     }
7118 
7119     DlineIndexOfX(textPtr, dlPtr, x, indexPtr);
7120 }
7121 
7122 /*
7123  *----------------------------------------------------------------------
7124  *
7125  * DlineIndexOfX --
7126  *
7127  *	Given an x coordinate in a display line, find the index of the
7128  *	character closest to that location.
7129  *
7130  *	This is effectively the opposite of DlineXOfIndex.
7131  *
7132  * Results:
7133  *	The index at *indexPtr is modified to refer to the character on the
7134  *	display line that is closest to x.
7135  *
7136  * Side effects:
7137  *	None.
7138  *
7139  *----------------------------------------------------------------------
7140  */
7141 
7142 static void
DlineIndexOfX(TkText * textPtr,DLine * dlPtr,int x,TkTextIndex * indexPtr)7143 DlineIndexOfX(
7144     TkText *textPtr,		/* Widget record for text widget. */
7145     DLine *dlPtr,		/* Display information for this display
7146 				 * line. */
7147     int x,			/* Pixel x coordinate of point in widget's
7148 				 * window. */
7149     TkTextIndex *indexPtr)	/* This index gets filled in with the index of
7150 				 * the character nearest to x. */
7151 {
7152     TextDInfo *dInfoPtr = textPtr->dInfoPtr;
7153     TkTextDispChunk *chunkPtr;
7154 
7155     /*
7156      * Scan through the line's chunks to find the one that contains the
7157      * desired x-coordinate. Before doing this, translate the x-coordinate
7158      * from the coordinate system of the window to the coordinate system of
7159      * the line (to take account of x-scrolling).
7160      */
7161 
7162     *indexPtr = dlPtr->index;
7163     x = x - dInfoPtr->x + dInfoPtr->curXPixelOffset;
7164     chunkPtr = dlPtr->chunkPtr;
7165 
7166     if (chunkPtr == NULL || x == 0) {
7167 	/*
7168 	 * This may occur if everything is elided, or if we're simply already
7169 	 * at the beginning of the line.
7170 	 */
7171 
7172 	return;
7173     }
7174 
7175     while (x >= (chunkPtr->x + chunkPtr->width)) {
7176 	/*
7177 	 * Note that this forward then backward movement of the index can be
7178 	 * problematic at the end of the buffer (we can't move forward, and
7179 	 * then when we move backward, we do, leading to the wrong position).
7180 	 * Hence when x == 0 we take special action above.
7181 	 */
7182 
7183 	if (TkTextIndexForwBytes(NULL,indexPtr,chunkPtr->numBytes,indexPtr)) {
7184 	    /*
7185 	     * We've reached the end of the text.
7186 	     */
7187 
7188             TkTextIndexBackChars(NULL, indexPtr, 1, indexPtr, COUNT_INDICES);
7189 	    return;
7190 	}
7191 	if (chunkPtr->nextPtr == NULL) {
7192 	    /*
7193 	     * We've reached the end of the display line.
7194 	     */
7195 
7196             TkTextIndexBackChars(NULL, indexPtr, 1, indexPtr, COUNT_INDICES);
7197 	    return;
7198 	}
7199 	chunkPtr = chunkPtr->nextPtr;
7200     }
7201 
7202     /*
7203      * If the chunk has more than one byte in it, ask it which character is at
7204      * the desired location. In this case we can manipulate
7205      * 'indexPtr->byteIndex' directly, because we know we're staying inside a
7206      * single logical line.
7207      */
7208 
7209     if (chunkPtr->numBytes > 1) {
7210 	indexPtr->byteIndex += chunkPtr->measureProc(chunkPtr, x);
7211     }
7212 }
7213 
7214 /*
7215  *----------------------------------------------------------------------
7216  *
7217  * TkTextIndexOfX --
7218  *
7219  *	Given a logical x coordinate (i.e. distance in pixels from the
7220  *	beginning of the display line, not taking into account any information
7221  *	about the window, scrolling etc.) on the display line starting with
7222  *	the given index, adjust that index to refer to the object under the x
7223  *	coordinate.
7224  *
7225  * Results:
7226  *	None.
7227  *
7228  * Side effects:
7229  *	None.
7230  *
7231  *----------------------------------------------------------------------
7232  */
7233 
7234 void
TkTextIndexOfX(TkText * textPtr,int x,TkTextIndex * indexPtr)7235 TkTextIndexOfX(
7236     TkText *textPtr,		/* Widget record for text widget. */
7237     int x,			/* The x coordinate for which we want the
7238 				 * index. */
7239     TkTextIndex *indexPtr)	/* Index of display line start, which will be
7240 				 * adjusted to the index under the given x
7241 				 * coordinate. */
7242 {
7243     DLine *dlPtr = LayoutDLine(textPtr, indexPtr);
7244     DlineIndexOfX(textPtr, dlPtr, x + textPtr->dInfoPtr->x
7245 		- textPtr->dInfoPtr->curXPixelOffset, indexPtr);
7246     FreeDLines(textPtr, dlPtr, NULL, DLINE_FREE_TEMP);
7247 }
7248 
7249 /*
7250  *----------------------------------------------------------------------
7251  *
7252  * DlineXOfIndex --
7253  *
7254  *	Given a relative byte index on a given display line (i.e. the number
7255  *	of byte indices from the beginning of the given display line), find
7256  *	the x coordinate of that index within the abstract display line,
7257  *	without adjusting for the x-scroll state of the line.
7258  *
7259  *	This is effectively the opposite of DlineIndexOfX.
7260  *
7261  *	NB. The 'byteIndex' is relative to the display line, NOT the logical
7262  *	line.
7263  *
7264  * Results:
7265  *	The x coordinate.
7266  *
7267  * Side effects:
7268  *	None.
7269  *
7270  *----------------------------------------------------------------------
7271  */
7272 
7273 static int
DlineXOfIndex(TkText * textPtr,DLine * dlPtr,int byteIndex)7274 DlineXOfIndex(
7275     TkText *textPtr,		/* Widget record for text widget. */
7276     DLine *dlPtr,		/* Display information for this display
7277 				 * line. */
7278     int byteIndex)		/* The byte index for which we want the
7279 				 * coordinate. */
7280 {
7281     TkTextDispChunk *chunkPtr = dlPtr->chunkPtr;
7282     int x = 0;
7283 
7284     if (byteIndex == 0 || chunkPtr == NULL) {
7285 	return x;
7286     }
7287 
7288     /*
7289      * Scan through the line's chunks to find the one that contains the
7290      * desired byte index.
7291      */
7292 
7293     chunkPtr = dlPtr->chunkPtr;
7294     while (byteIndex > 0) {
7295 	if (byteIndex < chunkPtr->numBytes) {
7296 	    int y, width, height;
7297 
7298 	    chunkPtr->bboxProc(textPtr, chunkPtr, byteIndex,
7299 		    dlPtr->y + dlPtr->spaceAbove,
7300 		    dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow,
7301 		    dlPtr->baseline - dlPtr->spaceAbove, &x, &y, &width,
7302 		    &height);
7303 	    break;
7304 	}
7305 	byteIndex -= chunkPtr->numBytes;
7306 	if (chunkPtr->nextPtr == NULL || byteIndex == 0) {
7307 	    x = chunkPtr->x + chunkPtr->width;
7308 	    break;
7309 	}
7310 	chunkPtr = chunkPtr->nextPtr;
7311     }
7312 
7313     return x;
7314 }
7315 
7316 /*
7317  *----------------------------------------------------------------------
7318  *
7319  * TkTextIndexBbox --
7320  *
7321  *	Given an index, find the bounding box of the screen area occupied by
7322  *	the entity (character, window, image) at that index.
7323  *
7324  * Results:
7325  *	Zero is returned if the index is on the screen. -1 means the index is
7326  *	not on the screen. If the return value is 0, then the bounding box of
7327  *	the part of the index that's visible on the screen is returned to
7328  *	*xPtr, *yPtr, *widthPtr, and *heightPtr.
7329  *
7330  * Side effects:
7331  *	None.
7332  *
7333  *----------------------------------------------------------------------
7334  */
7335 
7336 int
TkTextIndexBbox(TkText * textPtr,const TkTextIndex * indexPtr,int * xPtr,int * yPtr,int * widthPtr,int * heightPtr,int * charWidthPtr)7337 TkTextIndexBbox(
7338     TkText *textPtr,		/* Widget record for text widget. */
7339     const TkTextIndex *indexPtr,/* Index whose bounding box is desired. */
7340     int *xPtr, int *yPtr,	/* Filled with index's upper-left
7341 				 * coordinate. */
7342     int *widthPtr, int *heightPtr,
7343 				/* Filled in with index's dimensions. */
7344     int *charWidthPtr)		/* If the 'index' is at the end of a display
7345 				 * line and therefore takes up a very large
7346 				 * width, this is used to return the smaller
7347 				 * width actually desired by the index. */
7348 {
7349     TextDInfo *dInfoPtr = textPtr->dInfoPtr;
7350     DLine *dlPtr;
7351     TkTextDispChunk *chunkPtr;
7352     int byteCount;
7353 
7354     /*
7355      * Make sure that all of the screen layout information is up to date.
7356      */
7357 
7358     if (dInfoPtr->flags & DINFO_OUT_OF_DATE) {
7359 	UpdateDisplayInfo(textPtr);
7360     }
7361 
7362     /*
7363      * Find the display line containing the desired index.
7364      */
7365 
7366     dlPtr = FindDLine(textPtr, dInfoPtr->dLinePtr, indexPtr);
7367 
7368     /*
7369      * Two cases shall be trapped here because the logic later really
7370      * needs dlPtr to be the display line containing indexPtr:
7371      *   1. if no display line contains the desired index (NULL dlPtr)
7372      *   2. if indexPtr is before the first display line, in which case
7373      *      dlPtr currently points to the first display line
7374      */
7375 
7376     if ((dlPtr == NULL) || (TkTextIndexCmp(&dlPtr->index, indexPtr) > 0)) {
7377 	return -1;
7378     }
7379 
7380     /*
7381      * Find the chunk within the display line that contains the desired
7382      * index. The chunks making the display line are skipped up to but not
7383      * including the one crossing indexPtr. Skipping is done based on
7384      * a byteCount offset possibly spanning several logical lines in case
7385      * they are elided.
7386      */
7387 
7388     byteCount = TkTextIndexCountBytes(textPtr, &dlPtr->index, indexPtr);
7389     for (chunkPtr = dlPtr->chunkPtr; ; chunkPtr = chunkPtr->nextPtr) {
7390 	if (chunkPtr == NULL) {
7391 	    return -1;
7392 	}
7393 	if (byteCount < chunkPtr->numBytes) {
7394 	    break;
7395 	}
7396 	byteCount -= chunkPtr->numBytes;
7397     }
7398 
7399     /*
7400      * Call a chunk-specific function to find the horizontal range of the
7401      * character within the chunk, then fill in the vertical range. The
7402      * x-coordinate returned by bboxProc is a coordinate within a line, not a
7403      * coordinate on the screen. Translate it to reflect horizontal scrolling.
7404      */
7405 
7406     chunkPtr->bboxProc(textPtr, chunkPtr, byteCount,
7407 	    dlPtr->y + dlPtr->spaceAbove,
7408 	    dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow,
7409 	    dlPtr->baseline - dlPtr->spaceAbove, xPtr, yPtr, widthPtr,
7410 	    heightPtr);
7411     *xPtr = *xPtr + dInfoPtr->x - dInfoPtr->curXPixelOffset;
7412     if ((byteCount == chunkPtr->numBytes-1) && (chunkPtr->nextPtr == NULL)) {
7413 	/*
7414 	 * Last character in display line. Give it all the space up to the
7415 	 * line.
7416 	 */
7417 
7418 	if (charWidthPtr != NULL) {
7419 	    *charWidthPtr = dInfoPtr->maxX - *xPtr;
7420             if (*charWidthPtr > textPtr->charWidth) {
7421                 *charWidthPtr = textPtr->charWidth;
7422             }
7423 	}
7424 	if (*xPtr > dInfoPtr->maxX) {
7425 	    *xPtr = dInfoPtr->maxX;
7426 	}
7427 	*widthPtr = dInfoPtr->maxX - *xPtr;
7428     } else {
7429 	if (charWidthPtr != NULL) {
7430 	    *charWidthPtr = *widthPtr;
7431 	}
7432     }
7433     if (*widthPtr == 0) {
7434 	/*
7435 	 * With zero width (e.g. elided text) we just need to make sure it is
7436 	 * onscreen, where the '=' case here is ok.
7437 	 */
7438 
7439 	if (*xPtr < dInfoPtr->x) {
7440 	    return -1;
7441 	}
7442     } else {
7443 	if ((*xPtr + *widthPtr) <= dInfoPtr->x) {
7444 	    return -1;
7445 	}
7446     }
7447     if ((*xPtr + *widthPtr) > dInfoPtr->maxX) {
7448 	*widthPtr = dInfoPtr->maxX - *xPtr;
7449 	if (*widthPtr <= 0) {
7450 	    return -1;
7451 	}
7452     }
7453     if ((*yPtr + *heightPtr) > dInfoPtr->maxY) {
7454 	*heightPtr = dInfoPtr->maxY - *yPtr;
7455 	if (*heightPtr <= 0) {
7456 	    return -1;
7457 	}
7458     }
7459     return 0;
7460 }
7461 
7462 /*
7463  *----------------------------------------------------------------------
7464  *
7465  * TkTextDLineInfo --
7466  *
7467  *	Given an index, return information about the display line containing
7468  *	that character.
7469  *
7470  * Results:
7471  *	Zero is returned if the character is on the screen. -1 means the
7472  *	character isn't on the screen. If the return value is 0, then
7473  *	information is returned in the variables pointed to by xPtr, yPtr,
7474  *	widthPtr, heightPtr, and basePtr.
7475  *
7476  * Side effects:
7477  *	None.
7478  *
7479  *----------------------------------------------------------------------
7480  */
7481 
7482 int
TkTextDLineInfo(TkText * textPtr,const TkTextIndex * indexPtr,int * xPtr,int * yPtr,int * widthPtr,int * heightPtr,int * basePtr)7483 TkTextDLineInfo(
7484     TkText *textPtr,		/* Widget record for text widget. */
7485     const TkTextIndex *indexPtr,/* Index of character whose bounding box is
7486 				 * desired. */
7487     int *xPtr, int *yPtr,	/* Filled with line's upper-left
7488 				 * coordinate. */
7489     int *widthPtr, int *heightPtr,
7490 				/* Filled in with line's dimensions. */
7491     int *basePtr)		/* Filled in with the baseline position,
7492 				 * measured as an offset down from *yPtr. */
7493 {
7494     TextDInfo *dInfoPtr = textPtr->dInfoPtr;
7495     DLine *dlPtr;
7496     int dlx;
7497 
7498     /*
7499      * Make sure that all of the screen layout information is up to date.
7500      */
7501 
7502     if (dInfoPtr->flags & DINFO_OUT_OF_DATE) {
7503 	UpdateDisplayInfo(textPtr);
7504     }
7505 
7506     /*
7507      * Find the display line containing the desired index.
7508      */
7509 
7510     dlPtr = FindDLine(textPtr, dInfoPtr->dLinePtr, indexPtr);
7511 
7512     /*
7513      * Two cases shall be trapped here because the logic later really
7514      * needs dlPtr to be the display line containing indexPtr:
7515      *   1. if no display line contains the desired index (NULL dlPtr)
7516      *   2. if indexPtr is before the first display line, in which case
7517      *      dlPtr currently points to the first display line
7518      */
7519 
7520     if ((dlPtr == NULL) || (TkTextIndexCmp(&dlPtr->index, indexPtr) > 0)) {
7521 	return -1;
7522     }
7523 
7524     dlx = (dlPtr->chunkPtr != NULL? dlPtr->chunkPtr->x: 0);
7525     *xPtr = dInfoPtr->x - dInfoPtr->curXPixelOffset + dlx;
7526     *widthPtr = dlPtr->length - dlx;
7527     *yPtr = dlPtr->y;
7528     if ((dlPtr->y + dlPtr->height) > dInfoPtr->maxY) {
7529 	*heightPtr = dInfoPtr->maxY - dlPtr->y;
7530     } else {
7531 	*heightPtr = dlPtr->height;
7532     }
7533     *basePtr = dlPtr->baseline;
7534     return 0;
7535 }
7536 
7537 /*
7538  * Get bounding-box information about an elided chunk.
7539  */
7540 
7541 static void
ElideBboxProc(TCL_UNUSED (TkText *),TkTextDispChunk * chunkPtr,TCL_UNUSED (int),int y,TCL_UNUSED (int),TCL_UNUSED (int),int * xPtr,int * yPtr,int * widthPtr,int * heightPtr)7542 ElideBboxProc(
7543     TCL_UNUSED(TkText *),
7544     TkTextDispChunk *chunkPtr,	/* Chunk containing desired char. */
7545     TCL_UNUSED(int),		/* Index of desired character within the
7546 				 * chunk. */
7547     int y,			/* Topmost pixel in area allocated for this
7548 				 * line. */
7549     TCL_UNUSED(int),	/* Height of line, in pixels. */
7550     TCL_UNUSED(int),	/* Location of line's baseline, in pixels
7551 				 * measured down from y. */
7552     int *xPtr, int *yPtr,	/* Gets filled in with coords of character's
7553 				 * upper-left pixel. X-coord is in same
7554 				 * coordinate system as chunkPtr->x. */
7555     int *widthPtr,		/* Gets filled in with width of character, in
7556 				 * pixels. */
7557     int *heightPtr)		/* Gets filled in with height of character, in
7558 				 * pixels. */
7559 {
7560     *xPtr = chunkPtr->x;
7561     *yPtr = y;
7562     *widthPtr = *heightPtr = 0;
7563 }
7564 
7565 /*
7566  * Measure an elided chunk.
7567  */
7568 
7569 static int
ElideMeasureProc(TCL_UNUSED (TkTextDispChunk *),TCL_UNUSED (int))7570 ElideMeasureProc(
7571     TCL_UNUSED(TkTextDispChunk *),	/* Chunk containing desired coord. */
7572     TCL_UNUSED(int))		/* X-coordinate, in same coordinate system as
7573 				 * chunkPtr->x. */
7574 {
7575     return 0 /*chunkPtr->numBytes - 1*/;
7576 }
7577 
7578 /*
7579  *--------------------------------------------------------------
7580  *
7581  * TkTextCharLayoutProc --
7582  *
7583  *	This function is the "layoutProc" for character segments.
7584  *
7585  * Results:
7586  *	If there is something to display for the chunk then a non-zero value
7587  *	is returned and the fields of chunkPtr will be filled in (see the
7588  *	declaration of TkTextDispChunk in tkText.h for details). If zero is
7589  *	returned it means that no characters from this chunk fit in the
7590  *	window. If -1 is returned it means that this segment just doesn't need
7591  *	to be displayed (never happens for text).
7592  *
7593  * Side effects:
7594  *	Memory is allocated to hold additional information about the chunk.
7595  *
7596  *--------------------------------------------------------------
7597  */
7598 
7599 int
TkTextCharLayoutProc(TCL_UNUSED (TkText *),TCL_UNUSED (TkTextIndex *),TkTextSegment * segPtr,int byteOffset,int maxX,int maxBytes,int noCharsYet,TkWrapMode wrapMode,TkTextDispChunk * chunkPtr)7600 TkTextCharLayoutProc(
7601     TCL_UNUSED(TkText *),	/* Text widget being layed out. */
7602     TCL_UNUSED(TkTextIndex *),	/* Index of first character to lay out
7603 				 * (corresponds to segPtr and offset). */
7604     TkTextSegment *segPtr,	/* Segment being layed out. */
7605     int byteOffset,		/* Byte offset within segment of first
7606 				 * character to consider. */
7607     int maxX,			/* Chunk must not occupy pixels at this
7608 				 * position or higher. */
7609     int maxBytes,		/* Chunk must not include more than this many
7610 				 * characters. */
7611     int noCharsYet,		/* Non-zero means no characters have been
7612 				 * assigned to this display line yet. */
7613     TkWrapMode wrapMode,	/* How to handle line wrapping:
7614 				 * TEXT_WRAPMODE_CHAR, TEXT_WRAPMODE_NONE, or
7615 				 * TEXT_WRAPMODE_WORD. */
7616     TkTextDispChunk *chunkPtr)
7617 				/* Structure to fill in with information about
7618 				 * this chunk. The x field has already been
7619 				 * set by the caller. */
7620 {
7621     Tk_Font tkfont;
7622     int nextX, bytesThatFit, count;
7623     CharInfo *ciPtr;
7624     char *p;
7625     TkTextSegment *nextPtr;
7626     Tk_FontMetrics fm;
7627 #if TK_LAYOUT_WITH_BASE_CHUNKS
7628     const char *line;
7629     int lineOffset;
7630     BaseCharInfo *bciPtr;
7631     Tcl_DString *baseString;
7632 #endif
7633 
7634     /*
7635      * Figure out how many characters will fit in the space we've got. Include
7636      * the next character, even though it won't fit completely, if any of the
7637      * following is true:
7638      *	 (a) the chunk contains no characters and the display line contains no
7639      *	     characters yet (i.e. the line isn't wide enough to hold even a
7640      *	     single character).
7641      *	 (b) at least one pixel of the character is visible, we have not
7642      *	     already exceeded the character limit, and the next character is a
7643      *	     white space character.
7644      * In the specific case of 'word' wrapping mode however, include all space
7645      * characters following the characters that fit in the space we've got,
7646      * even if no pixel of them is visible.
7647      */
7648 
7649     p = segPtr->body.chars + byteOffset;
7650     tkfont = chunkPtr->stylePtr->sValuePtr->tkfont;
7651 
7652 #if TK_LAYOUT_WITH_BASE_CHUNKS
7653     if (baseCharChunkPtr == NULL) {
7654 	baseCharChunkPtr = chunkPtr;
7655 	bciPtr = ckalloc(sizeof(BaseCharInfo));
7656 	baseString = &bciPtr->baseChars;
7657 	Tcl_DStringInit(baseString);
7658 	bciPtr->width = 0;
7659 
7660 	ciPtr = &bciPtr->ci;
7661     } else {
7662 	bciPtr = baseCharChunkPtr->clientData;
7663 	ciPtr = ckalloc(sizeof(CharInfo));
7664 	baseString = &bciPtr->baseChars;
7665     }
7666 
7667     lineOffset = Tcl_DStringLength(baseString);
7668     line = Tcl_DStringAppend(baseString,p,maxBytes);
7669 
7670     chunkPtr->clientData = ciPtr;
7671     ciPtr->baseChunkPtr = baseCharChunkPtr;
7672     ciPtr->baseOffset = lineOffset;
7673     ciPtr->chars = NULL;
7674     ciPtr->numBytes = 0;
7675 
7676     bytesThatFit = CharChunkMeasureChars(chunkPtr, line,
7677 	    lineOffset + maxBytes, lineOffset, -1, chunkPtr->x, maxX,
7678 	    TK_ISOLATE_END, &nextX);
7679 #else /* !TK_LAYOUT_WITH_BASE_CHUNKS */
7680     bytesThatFit = CharChunkMeasureChars(chunkPtr, p, maxBytes, 0, -1,
7681 	    chunkPtr->x, maxX, TK_ISOLATE_END, &nextX);
7682 #endif /* TK_LAYOUT_WITH_BASE_CHUNKS */
7683 
7684     if (bytesThatFit < maxBytes) {
7685 	if ((bytesThatFit == 0) && noCharsYet) {
7686 	    int ch;
7687 	    int chLen = TkUtfToUniChar(p, &ch);
7688 
7689 #if TK_LAYOUT_WITH_BASE_CHUNKS
7690 	    bytesThatFit = CharChunkMeasureChars(chunkPtr, line,
7691 		    lineOffset+chLen, lineOffset, -1, chunkPtr->x, -1, 0,
7692 		    &nextX);
7693 #else /* !TK_LAYOUT_WITH_BASE_CHUNKS */
7694 	    bytesThatFit = CharChunkMeasureChars(chunkPtr, p, chLen, 0, -1,
7695 		    chunkPtr->x, -1, 0, &nextX);
7696 #endif /* TK_LAYOUT_WITH_BASE_CHUNKS */
7697 	}
7698 	if ((nextX < maxX) && ((p[bytesThatFit] == ' ')
7699 		|| (p[bytesThatFit] == '\t'))) {
7700 	    /*
7701 	     * Space characters are funny, in that they are considered to fit
7702 	     * if there is at least one pixel of space left on the line. Just
7703 	     * give the space character whatever space is left.
7704 	     */
7705 
7706 	    nextX = maxX;
7707 	    bytesThatFit++;
7708 	}
7709         if (wrapMode == TEXT_WRAPMODE_WORD) {
7710             while (p[bytesThatFit] == ' ') {
7711                 /*
7712                  * Space characters that would go at the beginning of the
7713                  * next line are allocated to the current line. This gives
7714                  * the effect of trimming white spaces that would otherwise
7715                  * be seen at the beginning of wrapped lines.
7716                  * Note that testing for '\t' is useless here because the
7717                  * chunk always includes at most one trailing \t, see
7718                  * LayoutDLine.
7719                  */
7720 
7721                 bytesThatFit++;
7722             }
7723         }
7724 	if (p[bytesThatFit] == '\n') {
7725 	    /*
7726 	     * A newline character takes up no space, so if the previous
7727 	     * character fits then so does the newline.
7728 	     */
7729 
7730 	    bytesThatFit++;
7731 	}
7732 	if (bytesThatFit == 0) {
7733 #if TK_LAYOUT_WITH_BASE_CHUNKS
7734 	    chunkPtr->clientData = NULL;
7735 	    if (chunkPtr == baseCharChunkPtr) {
7736 		baseCharChunkPtr = NULL;
7737 		Tcl_DStringFree(baseString);
7738 	    } else {
7739 		Tcl_DStringSetLength(baseString,lineOffset);
7740 	    }
7741 	    ckfree(ciPtr);
7742 #endif /* TK_LAYOUT_WITH_BASE_CHUNKS */
7743 	    return 0;
7744 	}
7745     }
7746 
7747     Tk_GetFontMetrics(tkfont, &fm);
7748 
7749     /*
7750      * Fill in the chunk structure and allocate and initialize a CharInfo
7751      * structure. If the last character is a newline then don't bother to
7752      * display it.
7753      */
7754 
7755     chunkPtr->displayProc = CharDisplayProc;
7756     chunkPtr->undisplayProc = CharUndisplayProc;
7757     chunkPtr->measureProc = CharMeasureProc;
7758     chunkPtr->bboxProc = CharBboxProc;
7759     chunkPtr->numBytes = bytesThatFit;
7760     chunkPtr->minAscent = fm.ascent + chunkPtr->stylePtr->sValuePtr->offset;
7761     chunkPtr->minDescent = fm.descent - chunkPtr->stylePtr->sValuePtr->offset;
7762     chunkPtr->minHeight = 0;
7763     chunkPtr->width = nextX - chunkPtr->x;
7764     chunkPtr->breakIndex = -1;
7765 
7766 #if !TK_LAYOUT_WITH_BASE_CHUNKS
7767     ciPtr = (CharInfo *)ckalloc((Tk_Offset(CharInfo, chars) + 1) + bytesThatFit);
7768     chunkPtr->clientData = ciPtr;
7769     memcpy(ciPtr->chars, p, bytesThatFit);
7770 #endif /* TK_LAYOUT_WITH_BASE_CHUNKS */
7771 
7772     ciPtr->numBytes = bytesThatFit;
7773     if (p[bytesThatFit - 1] == '\n') {
7774 	ciPtr->numBytes--;
7775     }
7776 
7777 #if TK_LAYOUT_WITH_BASE_CHUNKS
7778     /*
7779      * Final update for the current base chunk data.
7780      */
7781 
7782     Tcl_DStringSetLength(baseString,lineOffset+ciPtr->numBytes);
7783     bciPtr->width = nextX - baseCharChunkPtr->x;
7784 
7785     /*
7786      * Finalize the base chunk if this chunk ends in a tab, which definitly
7787      * breaks the context and needs to be handled on a higher level.
7788      */
7789 
7790     if (ciPtr->numBytes > 0 && p[ciPtr->numBytes - 1] == '\t') {
7791 	FinalizeBaseChunk(chunkPtr);
7792     }
7793 #endif /* TK_LAYOUT_WITH_BASE_CHUNKS */
7794 
7795     /*
7796      * Compute a break location. If we're in word wrap mode, a break can occur
7797      * after any space character, or at the end of the chunk if the next
7798      * segment (ignoring those with zero size) is not a character segment.
7799      */
7800 
7801     if (wrapMode != TEXT_WRAPMODE_WORD) {
7802 	chunkPtr->breakIndex = chunkPtr->numBytes;
7803     } else {
7804 	for (count = bytesThatFit, p += bytesThatFit - 1; count > 0;
7805 		count--, p--) {
7806 	    /*
7807 	     * Don't use isspace(); effects are unpredictable and can lead to
7808 	     * odd word-wrapping problems on some platforms. Also don't use
7809 	     * Tcl_UniCharIsSpace here either, as it identifies non-breaking
7810 	     * spaces as places to break. What we actually want is only the
7811 	     * ASCII space characters, so use them explicitly...
7812 	     */
7813 
7814 	    switch (*p) {
7815 	    case '\t': case '\n': case '\v': case '\f': case '\r': case ' ':
7816 		chunkPtr->breakIndex = count;
7817 		goto checkForNextChunk;
7818 	    }
7819 	}
7820     checkForNextChunk:
7821 	if ((bytesThatFit + byteOffset) == segPtr->size) {
7822 	    for (nextPtr = segPtr->nextPtr; nextPtr != NULL;
7823 		    nextPtr = nextPtr->nextPtr) {
7824 		if (nextPtr->size != 0) {
7825 		    if (nextPtr->typePtr != &tkTextCharType) {
7826 			chunkPtr->breakIndex = chunkPtr->numBytes;
7827 		    }
7828 		    break;
7829 		}
7830 	    }
7831 	}
7832     }
7833     return 1;
7834 }
7835 
7836 /*
7837  *---------------------------------------------------------------------------
7838  *
7839  * CharChunkMeasureChars --
7840  *
7841  *	Determine the number of characters from a char chunk that will fit in
7842  *	the given horizontal span.
7843  *
7844  *	This is the same as MeasureChars (which see), but in the context of a
7845  *	char chunk, i.e. on a higher level of abstraction. Use this function
7846  *	whereever possible instead of plain MeasureChars, so that the right
7847  *	context is used automatically.
7848  *
7849  * Results:
7850  *	The return value is the number of bytes from the range of start to end
7851  *	in source that fit in the span given by startX and maxX. *nextXPtr is
7852  *	filled in with the x-coordinate at which the first character that
7853  *	didn't fit would be drawn, if it were to be drawn.
7854  *
7855  * Side effects:
7856  *	None.
7857  *--------------------------------------------------------------
7858  */
7859 
7860 static int
CharChunkMeasureChars(TkTextDispChunk * chunkPtr,const char * chars,int charsLen,int start,int end,int startX,int maxX,int flags,int * nextXPtr)7861 CharChunkMeasureChars(
7862     TkTextDispChunk *chunkPtr,	/* Chunk from which to measure. */
7863     const char *chars,		/* Chars to use, instead of the chunk's own.
7864 				 * Used by the layoutproc during chunk setup.
7865 				 * All other callers use NULL. Not
7866 				 * NUL-terminated. */
7867     int charsLen,		/* Length of the "chars" parameter. */
7868     int start, int end,		/* The range of chars to measure inside the
7869 				 * chunk (or inside the additional chars). */
7870     int startX,			/* Starting x coordinate where the measured
7871 				 * span will begin. */
7872     int maxX,			/* Maximum pixel width of the span. May be -1
7873 				 * for unlimited. */
7874     int flags,			/* Flags to pass to MeasureChars. */
7875     int *nextXPtr)		/* The function puts the newly calculated
7876 				 * right border x-position of the span
7877 				 * here. */
7878 {
7879     Tk_Font tkfont = chunkPtr->stylePtr->sValuePtr->tkfont;
7880     CharInfo *ciPtr = (CharInfo *)chunkPtr->clientData;
7881 
7882 #if !TK_LAYOUT_WITH_BASE_CHUNKS
7883     if (chars == NULL) {
7884 	chars = ciPtr->chars;
7885 	charsLen = ciPtr->numBytes;
7886     }
7887     if (end == -1) {
7888 	end = charsLen;
7889     }
7890 
7891     return MeasureChars(tkfont, chars, charsLen, start, end-start,
7892 	    startX, maxX, flags, nextXPtr);
7893 #else /* TK_LAYOUT_WITH_BASE_CHUNKS */
7894     {
7895 	int xDisplacement;
7896 	int fit, bstart = start, bend = end;
7897 
7898 	if (chars == NULL) {
7899 	    Tcl_DString *baseChars = &((BaseCharInfo *)
7900 		    ciPtr->baseChunkPtr->clientData)->baseChars;
7901 
7902 	    chars = Tcl_DStringValue(baseChars);
7903 	    charsLen = Tcl_DStringLength(baseChars);
7904 	    bstart += ciPtr->baseOffset;
7905 	    if (bend == -1) {
7906 		bend = ciPtr->baseOffset + ciPtr->numBytes;
7907 	    } else {
7908 		bend += ciPtr->baseOffset;
7909 	    }
7910 	} else if (bend == -1) {
7911 	    bend = charsLen;
7912 	}
7913 
7914 	if (bstart == ciPtr->baseOffset) {
7915 	    xDisplacement = startX - chunkPtr->x;
7916 	} else {
7917 	    int widthUntilStart = 0;
7918 
7919 	    MeasureChars(tkfont, chars, charsLen, 0, bstart,
7920 		    0, -1, 0, &widthUntilStart);
7921 	    xDisplacement = startX - widthUntilStart - ciPtr->baseChunkPtr->x;
7922 	}
7923 
7924 	fit = MeasureChars(tkfont, chars, charsLen, 0, bend,
7925 		ciPtr->baseChunkPtr->x + xDisplacement, maxX, flags, nextXPtr);
7926 
7927 	if (fit < bstart) {
7928 	    return 0;
7929 	} else {
7930 	    return fit - bstart;
7931 	}
7932     }
7933 #endif /* TK_LAYOUT_WITH_BASE_CHUNKS */
7934 }
7935 
7936 /*
7937  *--------------------------------------------------------------
7938  *
7939  * CharDisplayProc --
7940  *
7941  *	This function is called to display a character chunk on the screen or
7942  *	in an off-screen pixmap.
7943  *
7944  * Results:
7945  *	None.
7946  *
7947  * Side effects:
7948  *	Graphics are drawn.
7949  *
7950  *--------------------------------------------------------------
7951  */
7952 
7953 static void
CharDisplayProc(TCL_UNUSED (TkText *),TkTextDispChunk * chunkPtr,int x,int y,TCL_UNUSED (int),int baseline,Display * display,Drawable dst,TCL_UNUSED (int))7954 CharDisplayProc(
7955     TCL_UNUSED(TkText *),
7956     TkTextDispChunk *chunkPtr,	/* Chunk that is to be drawn. */
7957     int x,			/* X-position in dst at which to draw this
7958 				 * chunk (may differ from the x-position in
7959 				 * the chunk because of scrolling). */
7960     int y,			/* Y-position at which to draw this chunk in
7961 				 * dst. */
7962     TCL_UNUSED(int),		/* Total height of line. */
7963     int baseline,		/* Offset of baseline from y. */
7964     Display *display,		/* Display to use for drawing. */
7965     Drawable dst,		/* Pixmap or window in which to draw chunk. */
7966     TCL_UNUSED(int))	/* Y-coordinate in text window that
7967 				 * corresponds to y. */
7968 {
7969     CharInfo *ciPtr = (CharInfo *)chunkPtr->clientData;
7970     const char *string;
7971     TextStyle *stylePtr;
7972     StyleValues *sValuePtr;
7973     int numBytes, offsetBytes, offsetX;
7974 #if TK_DRAW_IN_CONTEXT
7975     BaseCharInfo *bciPtr;
7976 #endif /* TK_DRAW_IN_CONTEXT */
7977 
7978     if ((x + chunkPtr->width) <= 0) {
7979 	/*
7980 	 * The chunk is off-screen.
7981 	 */
7982 
7983 	return;
7984     }
7985 
7986 #if TK_DRAW_IN_CONTEXT
7987     bciPtr = ciPtr->baseChunkPtr->clientData;
7988     numBytes = Tcl_DStringLength(&bciPtr->baseChars);
7989     string = Tcl_DStringValue(&bciPtr->baseChars);
7990 
7991 #elif TK_LAYOUT_WITH_BASE_CHUNKS
7992     if (ciPtr->baseChunkPtr != chunkPtr) {
7993 	/*
7994 	 * Without context drawing only base chunks display their foreground.
7995 	 */
7996 
7997 	return;
7998     }
7999 
8000     numBytes = Tcl_DStringLength(&((BaseCharInfo *) ciPtr)->baseChars);
8001     string = ciPtr->chars;
8002 
8003 #else /* !TK_LAYOUT_WITH_BASE_CHUNKS */
8004     numBytes = ciPtr->numBytes;
8005     string = ciPtr->chars;
8006 #endif /* TK_LAYOUT_WITH_BASE_CHUNKS */
8007 
8008     stylePtr = chunkPtr->stylePtr;
8009     sValuePtr = stylePtr->sValuePtr;
8010 
8011     /*
8012      * If the text sticks out way to the left of the window, skip over the
8013      * characters that aren't in the visible part of the window. This is
8014      * essential if x is very negative (such as less than 32K); otherwise
8015      * overflow problems will occur in servers that use 16-bit arithmetic,
8016      * like X.
8017      */
8018 
8019     offsetX = x;
8020     offsetBytes = 0;
8021     if (x < 0) {
8022 	offsetBytes = CharChunkMeasureChars(chunkPtr, NULL, 0, 0, -1,
8023 		x, 0, 0, &offsetX);
8024     }
8025 
8026     /*
8027      * Draw the text, underline, and overstrike for this chunk.
8028      */
8029 
8030     if (!sValuePtr->elide && (numBytes > offsetBytes)
8031 	    && (stylePtr->fgGC != NULL)) {
8032 #if TK_DRAW_IN_CONTEXT
8033 	int start = ciPtr->baseOffset + offsetBytes;
8034 	int len = ciPtr->numBytes - offsetBytes;
8035 	int xDisplacement = x - chunkPtr->x;
8036 
8037 	if ((len > 0) && (string[start + len - 1] == '\t')) {
8038 	    len--;
8039 	}
8040 	if (len <= 0) {
8041 	    return;
8042 	}
8043 
8044 	TkpDrawCharsInContext(display, dst, stylePtr->fgGC, sValuePtr->tkfont,
8045 		string, numBytes, start, len,
8046 		ciPtr->baseChunkPtr->x + xDisplacement,
8047 		y + baseline - sValuePtr->offset);
8048 
8049 	if (sValuePtr->underline) {
8050 	    TkUnderlineCharsInContext(display, dst, stylePtr->ulGC,
8051 		    sValuePtr->tkfont, string, numBytes,
8052 		    ciPtr->baseChunkPtr->x + xDisplacement,
8053 		    y + baseline - sValuePtr->offset,
8054 		    start, start+len);
8055 	}
8056 	if (sValuePtr->overstrike) {
8057 	    Tk_FontMetrics fm;
8058 
8059 	    Tk_GetFontMetrics(sValuePtr->tkfont, &fm);
8060 	    TkUnderlineCharsInContext(display, dst, stylePtr->ovGC,
8061 		    sValuePtr->tkfont, string, numBytes,
8062 		    ciPtr->baseChunkPtr->x + xDisplacement,
8063 		    y + baseline - sValuePtr->offset
8064 			    - fm.descent - (fm.ascent * 3) / 10,
8065 		    start, start+len);
8066 	}
8067 #else /* !TK_DRAW_IN_CONTEXT */
8068 	string += offsetBytes;
8069 	numBytes -= offsetBytes;
8070 
8071 	if ((numBytes > 0) && (string[numBytes - 1] == '\t')) {
8072 	    numBytes--;
8073 	}
8074 	Tk_DrawChars(display, dst, stylePtr->fgGC, sValuePtr->tkfont, string,
8075 		numBytes, offsetX, y + baseline - sValuePtr->offset);
8076 	if (sValuePtr->underline) {
8077 	    Tk_UnderlineChars(display, dst, stylePtr->ulGC, sValuePtr->tkfont,
8078 		    string, offsetX,
8079 		    y + baseline - sValuePtr->offset,
8080 		    0, numBytes);
8081 
8082 	}
8083 	if (sValuePtr->overstrike) {
8084 	    Tk_FontMetrics fm;
8085 
8086 	    Tk_GetFontMetrics(sValuePtr->tkfont, &fm);
8087 	    Tk_UnderlineChars(display, dst, stylePtr->ovGC, sValuePtr->tkfont,
8088 		    string, offsetX,
8089 		    y + baseline - sValuePtr->offset
8090 			    - fm.descent - (fm.ascent * 3) / 10,
8091 		    0, numBytes);
8092 	}
8093 #endif /* TK_DRAW_IN_CONTEXT */
8094     }
8095 }
8096 
8097 /*
8098  *--------------------------------------------------------------
8099  *
8100  * CharUndisplayProc --
8101  *
8102  *	This function is called when a character chunk is no longer going to
8103  *	be displayed. It frees up resources that were allocated to display the
8104  *	chunk.
8105  *
8106  * Results:
8107  *	None.
8108  *
8109  * Side effects:
8110  *	Memory and other resources get freed.
8111  *
8112  *--------------------------------------------------------------
8113  */
8114 
8115 static void
CharUndisplayProc(TCL_UNUSED (TkText *),TkTextDispChunk * chunkPtr)8116 CharUndisplayProc(
8117     TCL_UNUSED(TkText *),	/* Overall information about text widget. */
8118     TkTextDispChunk *chunkPtr)	/* Chunk that is about to be freed. */
8119 {
8120     CharInfo *ciPtr = (CharInfo *)chunkPtr->clientData;
8121 
8122     if (ciPtr) {
8123 #if TK_LAYOUT_WITH_BASE_CHUNKS
8124 	if (chunkPtr == ciPtr->baseChunkPtr) {
8125 	    /*
8126 	     * Basechunks are undisplayed first, when DLines are freed or
8127 	     * partially freed, so this makes sure we don't access their data
8128 	     * any more.
8129 	     */
8130 
8131 	    FreeBaseChunk(chunkPtr);
8132 	} else if (ciPtr->baseChunkPtr != NULL) {
8133 	    /*
8134 	     * When other char chunks are undisplayed, drop their characters
8135 	     * from the base chunk. This usually happens, when they are last
8136 	     * in a line and need to be re-layed out.
8137 	     */
8138 
8139 	    RemoveFromBaseChunk(chunkPtr);
8140 	}
8141 
8142 	ciPtr->baseChunkPtr = NULL;
8143 	ciPtr->chars = NULL;
8144 	ciPtr->numBytes = 0;
8145 #endif /* TK_LAYOUT_WITH_BASE_CHUNKS */
8146 
8147 	ckfree(ciPtr);
8148 	chunkPtr->clientData = NULL;
8149     }
8150 }
8151 
8152 /*
8153  *--------------------------------------------------------------
8154  *
8155  * CharMeasureProc --
8156  *
8157  *	This function is called to determine which character in a character
8158  *	chunk lies over a given x-coordinate.
8159  *
8160  * Results:
8161  *	The return value is the index *within the chunk* of the character that
8162  *	covers the position given by "x".
8163  *
8164  * Side effects:
8165  *	None.
8166  *
8167  *--------------------------------------------------------------
8168  */
8169 
8170 static int
CharMeasureProc(TkTextDispChunk * chunkPtr,int x)8171 CharMeasureProc(
8172     TkTextDispChunk *chunkPtr,	/* Chunk containing desired coord. */
8173     int x)			/* X-coordinate, in same coordinate system as
8174 				 * chunkPtr->x. */
8175 {
8176     int endX;
8177 
8178     return CharChunkMeasureChars(chunkPtr, NULL, 0, 0, chunkPtr->numBytes-1,
8179 	    chunkPtr->x, x, 0, &endX); /* CHAR OFFSET */
8180 }
8181 
8182 /*
8183  *--------------------------------------------------------------
8184  *
8185  * CharBboxProc --
8186  *
8187  *	This function is called to compute the bounding box of the area
8188  *	occupied by a single character.
8189  *
8190  * Results:
8191  *	There is no return value. *xPtr and *yPtr are filled in with the
8192  *	coordinates of the upper left corner of the character, and *widthPtr
8193  *	and *heightPtr are filled in with the dimensions of the character in
8194  *	pixels. Note: not all of the returned bbox is necessarily visible on
8195  *	the screen (the rightmost part might be off-screen to the right, and
8196  *	the bottommost part might be off-screen to the bottom).
8197  *
8198  * Side effects:
8199  *	None.
8200  *
8201  *--------------------------------------------------------------
8202  */
8203 
8204 static void
CharBboxProc(TCL_UNUSED (TkText *),TkTextDispChunk * chunkPtr,int byteIndex,int y,TCL_UNUSED (int),int baseline,int * xPtr,int * yPtr,int * widthPtr,int * heightPtr)8205 CharBboxProc(
8206     TCL_UNUSED(TkText *),
8207     TkTextDispChunk *chunkPtr,	/* Chunk containing desired char. */
8208     int byteIndex,		/* Byte offset of desired character within the
8209 				 * chunk. */
8210     int y,			/* Topmost pixel in area allocated for this
8211 				 * line. */
8212     TCL_UNUSED(int),	/* Height of line, in pixels. */
8213     int baseline,		/* Location of line's baseline, in pixels
8214 				 * measured down from y. */
8215     int *xPtr, int *yPtr,	/* Gets filled in with coords of character's
8216 				 * upper-left pixel. X-coord is in same
8217 				 * coordinate system as chunkPtr->x. */
8218     int *widthPtr,		/* Gets filled in with width of character, in
8219 				 * pixels. */
8220     int *heightPtr)		/* Gets filled in with height of character, in
8221 				 * pixels. */
8222 {
8223     CharInfo *ciPtr = (CharInfo *)chunkPtr->clientData;
8224     int maxX;
8225 
8226     maxX = chunkPtr->width + chunkPtr->x;
8227     CharChunkMeasureChars(chunkPtr, NULL, 0, 0, byteIndex,
8228 	    chunkPtr->x, -1, 0, xPtr);
8229 
8230     if (byteIndex == ciPtr->numBytes) {
8231 	/*
8232 	 * This situation only happens if the last character in a line is a
8233 	 * space character, in which case it absorbs all of the extra space in
8234 	 * the line (see TkTextCharLayoutProc).
8235 	 */
8236 
8237 	*widthPtr = maxX - *xPtr;
8238     } else if ((ciPtr->chars[byteIndex] == '\t')
8239 	    && (byteIndex == ciPtr->numBytes - 1)) {
8240 	/*
8241 	 * The desired character is a tab character that terminates a chunk;
8242 	 * give it all the space left in the chunk.
8243 	 */
8244 
8245 	*widthPtr = maxX - *xPtr;
8246     } else {
8247 	CharChunkMeasureChars(chunkPtr, NULL, 0, byteIndex, byteIndex+1,
8248 		*xPtr, -1, 0, widthPtr);
8249 	if (*widthPtr > maxX) {
8250 	    *widthPtr = maxX - *xPtr;
8251 	} else {
8252 	    *widthPtr -= *xPtr;
8253 	}
8254     }
8255     *yPtr = y + baseline - chunkPtr->minAscent;
8256     *heightPtr = chunkPtr->minAscent + chunkPtr->minDescent;
8257 }
8258 
8259 /*
8260  *----------------------------------------------------------------------
8261  *
8262  * AdjustForTab --
8263  *
8264  *	This function is called to move a series of chunks right in order to
8265  *	align them with a tab stop.
8266  *
8267  * Results:
8268  *	None.
8269  *
8270  * Side effects:
8271  *	The width of chunkPtr gets adjusted so that it absorbs the extra space
8272  *	due to the tab. The x locations in all the chunks after chunkPtr are
8273  *	adjusted rightward to align with the tab stop given by tabArrayPtr and
8274  *	index.
8275  *
8276  *----------------------------------------------------------------------
8277  */
8278 
8279 static void
AdjustForTab(TkText * textPtr,TkTextTabArray * tabArrayPtr,int index,TkTextDispChunk * chunkPtr)8280 AdjustForTab(
8281     TkText *textPtr,		/* Information about the text widget as a
8282 				 * whole. */
8283     TkTextTabArray *tabArrayPtr,/* Information about the tab stops that apply
8284 				 * to this line. May be NULL to indicate
8285 				 * default tabbing (every 8 chars). */
8286     int index,			/* Index of current tab stop. */
8287     TkTextDispChunk *chunkPtr)	/* Chunk whose last character is the tab; the
8288 				 * following chunks contain information to be
8289 				 * shifted right. */
8290 {
8291     int x, desired, delta, width, decimal, i, gotDigit;
8292     TkTextDispChunk *chunkPtr2, *decimalChunkPtr;
8293     CharInfo *ciPtr;
8294     int tabX, spaceWidth;
8295     const char *p;
8296     TkTextTabAlign alignment;
8297 
8298     if (chunkPtr->nextPtr == NULL) {
8299 	/*
8300 	 * Nothing after the actual tab; just return.
8301 	 */
8302 
8303 	return;
8304     }
8305 
8306     x = chunkPtr->nextPtr->x;
8307 
8308     /*
8309      * If no tab information has been given, assuming tab stops are at 8
8310      * average-sized characters. Still ensure we respect the tabular versus
8311      * wordprocessor tab style.
8312      */
8313 
8314     if ((tabArrayPtr == NULL) || (tabArrayPtr->numTabs == 0)) {
8315 	/*
8316 	 * No tab information has been given, so use the default
8317 	 * interpretation of tabs.
8318 	 */
8319 
8320 	if (textPtr->tabStyle == TK_TEXT_TABSTYLE_TABULAR) {
8321 	    int tabWidth = Tk_TextWidth(textPtr->tkfont, "0", 1) * 8;
8322 	    if (tabWidth == 0) {
8323 		tabWidth = 1;
8324 	    }
8325 
8326 	    desired = tabWidth * (index + 1);
8327 	} else {
8328 	    desired = NextTabStop(textPtr->tkfont, x, 0);
8329 	}
8330 
8331 	goto update;
8332     }
8333 
8334     if (index < tabArrayPtr->numTabs) {
8335 	alignment = tabArrayPtr->tabs[index].alignment;
8336 	tabX = tabArrayPtr->tabs[index].location;
8337     } else {
8338 	/*
8339 	 * Ran out of tab stops; compute a tab position by extrapolating from
8340 	 * the last two tab positions.
8341 	 */
8342 
8343 	tabX = (int) (tabArrayPtr->lastTab +
8344 		(index + 1 - tabArrayPtr->numTabs)*tabArrayPtr->tabIncrement +
8345 		0.5);
8346 	alignment = tabArrayPtr->tabs[tabArrayPtr->numTabs-1].alignment;
8347     }
8348 
8349     if (alignment == LEFT) {
8350 	desired = tabX;
8351 	goto update;
8352     }
8353 
8354     if ((alignment == CENTER) || (alignment == RIGHT)) {
8355 	/*
8356 	 * Compute the width of all the information in the tab group, then use
8357 	 * it to pick a desired location.
8358 	 */
8359 
8360 	width = 0;
8361 	for (chunkPtr2 = chunkPtr->nextPtr; chunkPtr2 != NULL;
8362 		chunkPtr2 = chunkPtr2->nextPtr) {
8363 	    width += chunkPtr2->width;
8364 	}
8365 	if (alignment == CENTER) {
8366 	    desired = tabX - width/2;
8367 	} else {
8368 	    desired = tabX - width;
8369 	}
8370 	goto update;
8371     }
8372 
8373     /*
8374      * Must be numeric alignment. Search through the text to be tabbed,
8375      * looking for the last , or . before the first character that isn't a
8376      * number, comma, period, or sign.
8377      */
8378 
8379     decimalChunkPtr = NULL;
8380     decimal = gotDigit = 0;
8381     for (chunkPtr2 = chunkPtr->nextPtr; chunkPtr2 != NULL;
8382 	    chunkPtr2 = chunkPtr2->nextPtr) {
8383 	if (chunkPtr2->displayProc != CharDisplayProc) {
8384 	    continue;
8385 	}
8386 	ciPtr = (CharInfo *)chunkPtr2->clientData;
8387 	for (p = ciPtr->chars, i = 0; i < ciPtr->numBytes; p++, i++) {
8388 	    if (isdigit(UCHAR(*p))) {
8389 		gotDigit = 1;
8390 	    } else if ((*p == '.') || (*p == ',')) {
8391 		decimal = p-ciPtr->chars;
8392 		decimalChunkPtr = chunkPtr2;
8393 	    } else if (gotDigit) {
8394 		if (decimalChunkPtr == NULL) {
8395 		    decimal = p-ciPtr->chars;
8396 		    decimalChunkPtr = chunkPtr2;
8397 		}
8398 		goto endOfNumber;
8399 	    }
8400 	}
8401     }
8402 
8403   endOfNumber:
8404     if (decimalChunkPtr != NULL) {
8405 	int curX;
8406 
8407 	ciPtr = (CharInfo *)decimalChunkPtr->clientData;
8408 	CharChunkMeasureChars(decimalChunkPtr, NULL, 0, 0, decimal,
8409 		decimalChunkPtr->x, -1, 0, &curX);
8410 	desired = tabX - (curX - x);
8411 	goto update;
8412     }
8413 
8414     /*
8415      * There wasn't a decimal point. Right justify the text.
8416      */
8417 
8418     width = 0;
8419     for (chunkPtr2 = chunkPtr->nextPtr; chunkPtr2 != NULL;
8420 	    chunkPtr2 = chunkPtr2->nextPtr) {
8421 	width += chunkPtr2->width;
8422     }
8423     desired = tabX - width;
8424 
8425     /*
8426      * Shift all of the chunks to the right so that the left edge is at the
8427      * desired location, then expand the chunk containing the tab. Be sure
8428      * that the tab occupies at least the width of a space character.
8429      */
8430 
8431   update:
8432     delta = desired - x;
8433     MeasureChars(textPtr->tkfont, " ", 1, 0, 1, 0, -1, 0, &spaceWidth);
8434     if (delta < spaceWidth) {
8435 	delta = spaceWidth;
8436     }
8437     for (chunkPtr2 = chunkPtr->nextPtr; chunkPtr2 != NULL;
8438 	    chunkPtr2 = chunkPtr2->nextPtr) {
8439 	chunkPtr2->x += delta;
8440     }
8441     chunkPtr->width += delta;
8442 }
8443 
8444 /*
8445  *----------------------------------------------------------------------
8446  *
8447  * SizeOfTab --
8448  *
8449  *	This returns an estimate of the amount of white space that will be
8450  *	consumed by a tab.
8451  *
8452  * Results:
8453  *	The return value is the minimum number of pixels that will be occupied
8454  *	by the next tab of tabArrayPtr, assuming that the current position on
8455  *	the line is x and the end of the line is maxX. The 'next tab' is
8456  *	determined by a combination of the current position (x) which it must
8457  *	be equal to or beyond, and the tab count in indexPtr.
8458  *
8459  *	For numeric tabs, this is a conservative estimate. The return value is
8460  *	always >= 0.
8461  *
8462  * Side effects:
8463  *	None.
8464  *
8465  *----------------------------------------------------------------------
8466  */
8467 
8468 static int
SizeOfTab(TkText * textPtr,int tabStyle,TkTextTabArray * tabArrayPtr,int * indexPtr,int x,int maxX)8469 SizeOfTab(
8470     TkText *textPtr,		/* Information about the text widget as a
8471 				 * whole. */
8472     int tabStyle,		/* One of TK_TEXT_TABSTYLE_TABULAR
8473 				 * or TK_TEXT_TABSTYLE_WORDPROCESSOR. */
8474     TkTextTabArray *tabArrayPtr,/* Information about the tab stops that apply
8475 				 * to this line. NULL means use default
8476 				 * tabbing (every 8 chars.) */
8477     int *indexPtr,		/* Contains index of previous tab stop, will
8478 				 * be updated to reflect the number of stops
8479 				 * used. */
8480     int x,			/* Current x-location in line. */
8481     int maxX)			/* X-location of pixel just past the right
8482 				 * edge of the line. */
8483 {
8484     int tabX, result, index, spaceWidth, tabWidth;
8485     TkTextTabAlign alignment;
8486 
8487     index = *indexPtr;
8488 
8489     if ((tabArrayPtr == NULL) || (tabArrayPtr->numTabs == 0)) {
8490 	/*
8491 	 * We're using a default tab spacing of 8 characters.
8492 	 */
8493 
8494 	tabWidth = Tk_TextWidth(textPtr->tkfont, "0", 1) * 8;
8495 	if (tabWidth == 0) {
8496 	    tabWidth = 1;
8497 	}
8498     } else {
8499 	tabWidth = 0;		/* Avoid compiler error. */
8500     }
8501 
8502     do {
8503 	/*
8504 	 * We were given the count before this tab, so increment it first.
8505 	 */
8506 
8507 	index++;
8508 
8509 	if ((tabArrayPtr == NULL) || (tabArrayPtr->numTabs == 0)) {
8510 	    /*
8511 	     * We're using a default tab spacing calculated above.
8512 	     */
8513 
8514 	    tabX = tabWidth * (index + 1);
8515 	    alignment = LEFT;
8516 	} else if (index < tabArrayPtr->numTabs) {
8517 	    tabX = tabArrayPtr->tabs[index].location;
8518 	    alignment = tabArrayPtr->tabs[index].alignment;
8519 	} else {
8520 	    /*
8521 	     * Ran out of tab stops; compute a tab position by extrapolating.
8522 	     */
8523 
8524 	    tabX = (int) (tabArrayPtr->lastTab
8525 		    + (index + 1 - tabArrayPtr->numTabs)
8526 		    * tabArrayPtr->tabIncrement + 0.5);
8527 	    alignment = tabArrayPtr->tabs[tabArrayPtr->numTabs-1].alignment;
8528 	}
8529 
8530 	/*
8531 	 * If this tab stop is before the current x position, then we have two
8532 	 * cases:
8533 	 *
8534 	 * With 'wordprocessor' style tabs, we must obviously continue until
8535 	 * we reach the text tab stop.
8536 	 *
8537 	 * With 'tabular' style tabs, we always use the index'th tab stop.
8538 	 */
8539     } while (tabX <= x && (tabStyle == TK_TEXT_TABSTYLE_WORDPROCESSOR));
8540 
8541     /*
8542      * Inform our caller of how many tab stops we've used up.
8543      */
8544 
8545     *indexPtr = index;
8546 
8547     if (alignment == CENTER) {
8548 	/*
8549 	 * Be very careful in the arithmetic below, because maxX may be the
8550 	 * largest positive number: watch out for integer overflow.
8551 	 */
8552 
8553 	if ((maxX-tabX) < (tabX - x)) {
8554 	    result = (maxX - x) - 2*(maxX - tabX);
8555 	} else {
8556 	    result = 0;
8557 	}
8558 	goto done;
8559     }
8560     if (alignment == RIGHT) {
8561 	result = 0;
8562 	goto done;
8563     }
8564 
8565     /*
8566      * Note: this treats NUMERIC alignment the same as LEFT alignment, which
8567      * is somewhat conservative. However, it's pretty tricky at this point to
8568      * figure out exactly where the damn decimal point will be.
8569      */
8570 
8571     if (tabX > x) {
8572 	result = tabX - x;
8573     } else {
8574 	result = 0;
8575     }
8576 
8577   done:
8578     MeasureChars(textPtr->tkfont, " ", 1, 0, 1, 0, -1, 0, &spaceWidth);
8579     if (result < spaceWidth) {
8580 	result = spaceWidth;
8581     }
8582     return result;
8583 }
8584 
8585 /*
8586  *---------------------------------------------------------------------------
8587  *
8588  * NextTabStop --
8589  *
8590  *	Given the current position, determine where the next default tab stop
8591  *	would be located. This function is called when the current chunk in
8592  *	the text has no tabs defined and so the default tab spacing for the
8593  *	font should be used, provided we are using wordprocessor style tabs.
8594  *
8595  * Results:
8596  *	The location in pixels of the next tab stop.
8597  *
8598  * Side effects:
8599  *	None.
8600  *
8601  *---------------------------------------------------------------------------
8602  */
8603 
8604 static int
NextTabStop(Tk_Font tkfont,int x,int tabOrigin)8605 NextTabStop(
8606     Tk_Font tkfont,		/* Font in which chunk that contains tab stop
8607 				 * will be drawn. */
8608     int x,			/* X-position in pixels where last character
8609 				 * was drawn. The next tab stop occurs
8610 				 * somewhere after this location. */
8611     int tabOrigin)		/* The origin for tab stops. May be non-zero
8612 				 * if text has been scrolled. */
8613 {
8614     int tabWidth, rem;
8615 
8616     tabWidth = Tk_TextWidth(tkfont, "0", 1) * 8;
8617     if (tabWidth == 0) {
8618 	tabWidth = 1;
8619     }
8620 
8621     x += tabWidth;
8622     rem = (x - tabOrigin) % tabWidth;
8623     if (rem < 0) {
8624 	rem += tabWidth;
8625     }
8626     x -= rem;
8627     return x;
8628 }
8629 
8630 /*
8631  *---------------------------------------------------------------------------
8632  *
8633  * MeasureChars --
8634  *
8635  *	Determine the number of characters from the string that will fit in
8636  *	the given horizontal span. The measurement is done under the
8637  *	assumption that Tk_DrawChars will be used to actually display the
8638  *	characters.
8639  *
8640  *	If tabs are encountered in the string, they will be ignored (they
8641  *	should only occur as last character of the string anyway).
8642  *
8643  *	If a newline is encountered in the string, the line will be broken at
8644  *	that point.
8645  *
8646  * Results:
8647  *	The return value is the number of bytes from the range of start to end
8648  *	in source that fit in the span given by startX and maxX. *nextXPtr is
8649  *	filled in with the x-coordinate at which the first character that
8650  *	didn't fit would be drawn, if it were to be drawn.
8651  *
8652  * Side effects:
8653  *	None.
8654  *
8655  *--------------------------------------------------------------
8656  */
8657 
8658 static int
MeasureChars(Tk_Font tkfont,const char * source,int maxBytes,int rangeStart,int rangeLength,int startX,int maxX,int flags,int * nextXPtr)8659 MeasureChars(
8660     Tk_Font tkfont,		/* Font in which to draw characters. */
8661     const char *source,		/* Characters to be displayed. Need not be
8662 				 * NULL-terminated. */
8663     int maxBytes,		/* Maximum # of bytes to consider from
8664 				 * source. */
8665     int rangeStart, int rangeLength,
8666 				/* Range of bytes to consider in source.*/
8667     int startX,			/* X-position at which first character will be
8668 				 * drawn. */
8669     int maxX,			/* Don't consider any character that would
8670 				 * cross this x-position. */
8671     int flags,			/* Flags to pass to Tk_MeasureChars. */
8672     int *nextXPtr)		/* Return x-position of terminating character
8673 				 * here. */
8674 {
8675     int curX, width, ch;
8676     const char *special, *end, *start;
8677 
8678     ch = 0;
8679     curX = startX;
8680     start = source + rangeStart;
8681     end = start + rangeLength;
8682     special = start;
8683     while (start < end) {
8684 	if (start >= special) {
8685 	    /*
8686 	     * Find the next special character in the string.
8687 	     */
8688 
8689 	    for (special = start; special < end; special++) {
8690 		ch = *special;
8691 		if ((ch == '\t') || (ch == '\n')) {
8692 		    break;
8693 		}
8694 	    }
8695 	}
8696 
8697 	/*
8698 	 * Special points at the next special character (or the end of the
8699 	 * string). Process characters between start and special.
8700 	 */
8701 
8702 	if ((maxX >= 0) && (curX >= maxX)) {
8703 	    break;
8704 	}
8705 #if TK_DRAW_IN_CONTEXT
8706 	start += TkpMeasureCharsInContext(tkfont, source, maxBytes,
8707 		start - source, special - start,
8708 		maxX >= 0 ? maxX - curX : -1, flags, &width);
8709 #else
8710 	(void) maxBytes;
8711 	start += Tk_MeasureChars(tkfont, start, special - start,
8712 		maxX >= 0 ? maxX - curX : -1, flags, &width);
8713 #endif /* TK_DRAW_IN_CONTEXT */
8714 	curX += width;
8715 	if (start < special) {
8716 	    /*
8717 	     * No more chars fit in line.
8718 	     */
8719 
8720 	    break;
8721 	}
8722 	if (special < end) {
8723 	    if (ch != '\t') {
8724 		break;
8725 	    }
8726 	    start++;
8727 	}
8728     }
8729 
8730     *nextXPtr = curX;
8731     return start - (source+rangeStart);
8732 }
8733 
8734 /*
8735  *----------------------------------------------------------------------
8736  *
8737  * TextGetScrollInfoObj --
8738  *
8739  *	This function is invoked to parse "xview" and "yview" scrolling
8740  *	commands for text widgets using the new scrolling command syntax
8741  *	("moveto" or "scroll" options). It extends the public
8742  *	Tk_GetScrollInfoObj function with the addition of "pixels" as a valid
8743  *	unit alongside "pages" and "units". It is a shame the core API isn't
8744  *	more flexible in this regard.
8745  *
8746  * Results:
8747  *	The return value is either TKTEXT_SCROLL_MOVETO, TKTEXT_SCROLL_PAGES,
8748  *	TKTEXT_SCROLL_UNITS, TKTEXT_SCROLL_PIXELS or TKTEXT_SCROLL_ERROR. This
8749  *	indicates whether the command was successfully parsed and what form
8750  *	the command took. If TKTEXT_SCROLL_MOVETO, *dblPtr is filled in with
8751  *	the desired position; if TKTEXT_SCROLL_PAGES, TKTEXT_SCROLL_PIXELS or
8752  *	TKTEXT_SCROLL_UNITS, *intPtr is filled in with the number of
8753  *	pages/pixels/lines to move (may be negative); if TKTEXT_SCROLL_ERROR,
8754  *	the interp's result contains an error message.
8755  *
8756  * Side effects:
8757  *	None.
8758  *
8759  *----------------------------------------------------------------------
8760  */
8761 
8762 static int
TextGetScrollInfoObj(Tcl_Interp * interp,TkText * textPtr,int objc,Tcl_Obj * const objv[],double * dblPtr,int * intPtr)8763 TextGetScrollInfoObj(
8764     Tcl_Interp *interp,		/* Used for error reporting. */
8765     TkText *textPtr,		/* Information about the text widget. */
8766     int objc,			/* # arguments for command. */
8767     Tcl_Obj *const objv[],	/* Arguments for command. */
8768     double *dblPtr,		/* Filled in with argument "moveto" option, if
8769 				 * any. */
8770     int *intPtr)		/* Filled in with number of pages or lines or
8771 				 * pixels to scroll, if any. */
8772 {
8773     static const char *const subcommands[] = {
8774 	"moveto", "scroll", NULL
8775     };
8776     enum viewSubcmds {
8777 	VIEW_MOVETO, VIEW_SCROLL
8778     };
8779     static const char *const units[] = {
8780 	"units", "pages", "pixels", NULL
8781     };
8782     enum viewUnits {
8783 	VIEW_SCROLL_UNITS, VIEW_SCROLL_PAGES, VIEW_SCROLL_PIXELS
8784     };
8785     int index;
8786 
8787     if (Tcl_GetIndexFromObjStruct(interp, objv[2], subcommands,
8788 	    sizeof(char *), "option", 0, &index) != TCL_OK) {
8789 	return TKTEXT_SCROLL_ERROR;
8790     }
8791 
8792     switch ((enum viewSubcmds) index) {
8793     case VIEW_MOVETO:
8794 	if (objc != 4) {
8795 	    Tcl_WrongNumArgs(interp, 3, objv, "fraction");
8796 	    return TKTEXT_SCROLL_ERROR;
8797 	}
8798 	if (Tcl_GetDoubleFromObj(interp, objv[3], dblPtr) != TCL_OK) {
8799 	    return TKTEXT_SCROLL_ERROR;
8800 	}
8801 	return TKTEXT_SCROLL_MOVETO;
8802     case VIEW_SCROLL:
8803 	if (objc != 5) {
8804 	    Tcl_WrongNumArgs(interp, 3, objv, "number units|pages|pixels");
8805 	    return TKTEXT_SCROLL_ERROR;
8806 	}
8807 	if (Tcl_GetIndexFromObjStruct(interp, objv[4], units,
8808 		sizeof(char *), "argument", 0, &index) != TCL_OK) {
8809 	    return TKTEXT_SCROLL_ERROR;
8810 	}
8811 	switch ((enum viewUnits) index) {
8812 	case VIEW_SCROLL_PAGES:
8813 	    if (Tcl_GetIntFromObj(interp, objv[3], intPtr) == TCL_OK) {
8814 		return TKTEXT_SCROLL_PAGES;
8815 	    }
8816 	    break;
8817 	case VIEW_SCROLL_PIXELS:
8818 	    if (Tk_GetPixelsFromObj(interp, textPtr->tkwin, objv[3],
8819 		    intPtr) == TCL_OK) {
8820 		return TKTEXT_SCROLL_PIXELS;
8821 	    }
8822 	    break;
8823 	case VIEW_SCROLL_UNITS:
8824 	    if (Tcl_GetIntFromObj(interp, objv[3], intPtr) == TCL_OK) {
8825 		return TKTEXT_SCROLL_UNITS;
8826 	    }
8827 	    break;
8828 	default:
8829 	    Tcl_Panic("unexpected switch fallthrough");
8830 	}
8831     }
8832     return TKTEXT_SCROLL_ERROR;
8833 }
8834 
8835 #if TK_LAYOUT_WITH_BASE_CHUNKS
8836 /*
8837  *----------------------------------------------------------------------
8838  *
8839  * FinalizeBaseChunk --
8840  *
8841  *	This procedure makes sure that all the chunks of the stretch are
8842  *	up-to-date. It is invoked when the LayoutProc has been called for all
8843  *	chunks and the base chunk is stable.
8844  *
8845  * Results:
8846  *	None.
8847  *
8848  * Side effects:
8849  *	The CharInfo.chars of all dependent chunks point into
8850  *	BaseCharInfo.baseChars for easy access (and compatibility).
8851  *
8852  *----------------------------------------------------------------------
8853  */
8854 
8855 static void
FinalizeBaseChunk(TkTextDispChunk * addChunkPtr)8856 FinalizeBaseChunk(
8857     TkTextDispChunk *addChunkPtr)
8858 				/* An additional chunk to add to the stretch,
8859 				 * even though it may not be in the linked
8860 				 * list yet. Used by the LayoutProc, otherwise
8861 				 * NULL. */
8862 {
8863     const char *baseChars;
8864     TkTextDispChunk *chunkPtr;
8865     CharInfo *ciPtr;
8866 #if TK_DRAW_IN_CONTEXT
8867     int widthAdjust = 0;
8868     int newwidth;
8869 #endif /* TK_DRAW_IN_CONTEXT */
8870 
8871     if (baseCharChunkPtr == NULL) {
8872 	return;
8873     }
8874 
8875     baseChars = Tcl_DStringValue(
8876 	    &((BaseCharInfo *) baseCharChunkPtr->clientData)->baseChars);
8877 
8878     for (chunkPtr = baseCharChunkPtr; chunkPtr != NULL;
8879 	    chunkPtr = chunkPtr->nextPtr) {
8880 #if TK_DRAW_IN_CONTEXT
8881 	chunkPtr->x += widthAdjust;
8882 #endif /* TK_DRAW_IN_CONTEXT */
8883 
8884 	if (chunkPtr->displayProc != CharDisplayProc) {
8885 	    continue;
8886 	}
8887 	ciPtr = chunkPtr->clientData;
8888 	if (ciPtr->baseChunkPtr != baseCharChunkPtr) {
8889 	    break;
8890 	}
8891 	ciPtr->chars = baseChars + ciPtr->baseOffset;
8892 
8893 #if TK_DRAW_IN_CONTEXT
8894 	newwidth = 0;
8895 	CharChunkMeasureChars(chunkPtr, NULL, 0, 0, -1, 0, -1, 0, &newwidth);
8896 	if (newwidth < chunkPtr->width) {
8897 	    widthAdjust += newwidth - chunkPtr->width;
8898 	    chunkPtr->width = newwidth;
8899 	}
8900 #endif /* TK_DRAW_IN_CONTEXT */
8901     }
8902 
8903     if (addChunkPtr != NULL) {
8904 	ciPtr = addChunkPtr->clientData;
8905 	ciPtr->chars = baseChars + ciPtr->baseOffset;
8906 
8907 #if TK_DRAW_IN_CONTEXT
8908 	addChunkPtr->x += widthAdjust;
8909 	CharChunkMeasureChars(addChunkPtr, NULL, 0, 0, -1, 0, -1, 0,
8910 		&addChunkPtr->width);
8911 #endif /* TK_DRAW_IN_CONTEXT */
8912     }
8913 
8914     baseCharChunkPtr = NULL;
8915 }
8916 
8917 /*
8918  *----------------------------------------------------------------------
8919  *
8920  * FreeBaseChunk --
8921  *
8922  *	This procedure makes sure that all the chunks of the stretch are
8923  *	disconnected from the base chunk and the base chunk specific data is
8924  *	freed. It is invoked from the UndisplayProc. The procedure doesn't
8925  *	ckfree the base chunk clientData itself, that's up to the main
8926  *	UndisplayProc.
8927  *
8928  * Results:
8929  *	None.
8930  *
8931  * Side effects:
8932  *	The CharInfo.chars of all dependent chunks are set to NULL. Memory
8933  *	that belongs specifically to the base chunk is freed.
8934  *
8935  *----------------------------------------------------------------------
8936  */
8937 
8938 static void
FreeBaseChunk(TkTextDispChunk * baseChunkPtr)8939 FreeBaseChunk(
8940     TkTextDispChunk *baseChunkPtr)
8941 				/* The base chunk of the stretch and head of
8942 				 * the linked list. */
8943 {
8944     TkTextDispChunk *chunkPtr;
8945     CharInfo *ciPtr;
8946 
8947     if (baseCharChunkPtr == baseChunkPtr) {
8948 	baseCharChunkPtr = NULL;
8949     }
8950 
8951     for (chunkPtr=baseChunkPtr; chunkPtr!=NULL; chunkPtr=chunkPtr->nextPtr) {
8952 	if (chunkPtr->undisplayProc != CharUndisplayProc) {
8953 	    continue;
8954 	}
8955 	ciPtr = chunkPtr->clientData;
8956 	if (ciPtr->baseChunkPtr != baseChunkPtr) {
8957 	    break;
8958 	}
8959 
8960 	ciPtr->baseChunkPtr = NULL;
8961 	ciPtr->chars = NULL;
8962     }
8963 
8964     if (baseChunkPtr) {
8965 	Tcl_DStringFree(&((BaseCharInfo *) baseChunkPtr->clientData)->baseChars);
8966     }
8967 }
8968 
8969 /*
8970  *----------------------------------------------------------------------
8971  *
8972  * IsSameFGStyle --
8973  *
8974  *	Compare the foreground attributes of two styles. Specifically must
8975  *	consider: foreground color, font, font style and font decorations,
8976  *	elide, "offset" and foreground stipple. Do *not* consider: background
8977  *	color, border, relief or background stipple.
8978  *
8979  *	If we use TkpDrawCharsInContext(), we also don't need to check
8980  *	foreground color, font decorations, elide, offset and foreground
8981  *	stipple, so all that is left is font (including font size and font
8982  *	style) and "offset".
8983  *
8984  * Results:
8985  *	1 if the two styles match, 0 otherwise.
8986  *
8987  * Side effects:
8988  *	None.
8989  *
8990  *----------------------------------------------------------------------
8991  */
8992 
8993 static int
IsSameFGStyle(TextStyle * style1,TextStyle * style2)8994 IsSameFGStyle(
8995     TextStyle *style1,
8996     TextStyle *style2)
8997 {
8998     StyleValues *sv1;
8999     StyleValues *sv2;
9000 
9001     if (style1 == style2) {
9002 	return 1;
9003     }
9004 
9005 #if !TK_DRAW_IN_CONTEXT
9006     if (
9007 #ifdef MAC_OSX_TK
9008 	    !TkMacOSXCompareColors(style1->fgGC->foreground,
9009 		    style2->fgGC->foreground)
9010 #else
9011 	    style1->fgGC->foreground != style2->fgGC->foreground
9012 #endif
9013 	    ) {
9014 	return 0;
9015     }
9016 #endif /* !TK_DRAW_IN_CONTEXT */
9017 
9018     sv1 = style1->sValuePtr;
9019     sv2 = style2->sValuePtr;
9020 
9021 #if TK_DRAW_IN_CONTEXT
9022     return sv1->tkfont == sv2->tkfont && sv1->offset == sv2->offset;
9023 #else
9024     return sv1->tkfont == sv2->tkfont
9025 	    && sv1->underline == sv2->underline
9026 	    && sv1->overstrike == sv2->overstrike
9027 	    && sv1->elide == sv2->elide
9028 	    && sv1->offset == sv2->offset
9029 	    && sv1->fgStipple == sv1->fgStipple;
9030 #endif /* TK_DRAW_IN_CONTEXT */
9031 }
9032 
9033 /*
9034  *----------------------------------------------------------------------
9035  *
9036  * RemoveFromBaseChunk --
9037  *
9038  *	This procedure removes a chunk from the stretch as a result of
9039  *	UndisplayProc. The chunk in question should be the last in a stretch.
9040  *	This happens during re-layouting of the break position.
9041  *
9042  * Results:
9043  *	None.
9044  *
9045  * Side effects:
9046  *	The characters that belong to this chunk are removed from the base
9047  *	chunk. It is assumed that LayoutProc and FinalizeBaseChunk are called
9048  *	next to repair any damage that this causes to the integrity of the
9049  *	stretch and the other chunks. For that reason the base chunk is also
9050  *	put into baseCharChunkPtr automatically, so that LayoutProc can resume
9051  *	correctly.
9052  *
9053  *----------------------------------------------------------------------
9054  */
9055 
9056 static void
RemoveFromBaseChunk(TkTextDispChunk * chunkPtr)9057 RemoveFromBaseChunk(
9058     TkTextDispChunk *chunkPtr)	/* The chunk to remove from the end of the
9059 				 * stretch. */
9060 {
9061     CharInfo *ciPtr;
9062     BaseCharInfo *bciPtr;
9063 
9064     if (chunkPtr->displayProc != CharDisplayProc) {
9065 #ifdef DEBUG_LAYOUT_WITH_BASE_CHUNKS
9066 	fprintf(stderr,"RemoveFromBaseChunk called with wrong chunk type\n");
9067 #endif
9068 	return;
9069     }
9070 
9071     /*
9072      * Reinstitute this base chunk for re-layout.
9073      */
9074 
9075     ciPtr = chunkPtr->clientData;
9076     baseCharChunkPtr = ciPtr->baseChunkPtr;
9077 
9078     /*
9079      * Remove the chunk data from the base chunk data.
9080      */
9081 
9082     bciPtr = baseCharChunkPtr->clientData;
9083 
9084 #ifdef DEBUG_LAYOUT_WITH_BASE_CHUNKS
9085     if ((ciPtr->baseOffset + ciPtr->numBytes)
9086 	    != Tcl_DStringLength(&bciPtr->baseChars)) {
9087 	fprintf(stderr,"RemoveFromBaseChunk called with wrong chunk "
9088 		"(not last)\n");
9089     }
9090 #endif
9091 
9092     Tcl_DStringSetLength(&bciPtr->baseChars, ciPtr->baseOffset);
9093 
9094     /*
9095      * Invalidate the stored pixel width of the base chunk.
9096      */
9097 
9098     bciPtr->width = -1;
9099 }
9100 #endif /* TK_LAYOUT_WITH_BASE_CHUNKS */
9101 
9102 /*
9103  * Local Variables:
9104  * mode: c
9105  * c-basic-offset: 4
9106  * fill-column: 78
9107  * End:
9108  */
9109